I started writing go from a release before [R59] (https://golang.org/doc/devel/pre_go1.html#r59)--1.0, and have been building HTTP APIs and services with go for the last seven years. In [Machine Box] (https://machinebox.io/?utm_source=matblog-3May2018&utm_medium=matblog-3May2018&utm_ campaign=matblog-3may2018&utm_term=matblog-3may2018&utm_content=matblog-3may2018), Most of my technical work involves building a wide variety of APIs. Machine learning itself is very complex and most developers will not use, so my job is through the API terminal to explain briefly, at present, the response is very good. > If you have not seen the experience of the machine Box developer, [Please try] (https://machinebox.io/docs/facebox/teaching-facebox) and let me know your opinion. My approach to writing services has changed over the past few years, so I'm going to share my current experience of writing services--perhaps these methods can help you and your work. # # A server struct all my components have a separate ' server ' struct, which is usually similar to this form: ' ' gotype server struct {db *somedatabase router *somerouter em Ail Emailsender} "-The public component is the field of the struct. # # Routes.go In each component I have a unique file ' Routes.go ', where all the routes can run. "' Gopackage Appfunc (S *server) routes () {S.router.handlefunc ("/api/", S.handleapi ()) S.router.handlefunc ("/about ", S.handleabout ()) S.router.handlefunc ("/", S.handleindex ())} "is handy because most of the code maintenance starts with a URL and a reported error-so just browse 'Routes.go ' will guide us to our destination. # # hangs the server's handler my HTTP handler hangs the server: ' Gofunc (S *server) handlesomething () http. Handlerfunc {...} The "handler" can access the dependencies through the s server variable. # # Return Handler My handler function does not process the request, they return the function to complete the processing work. This gives us a closed environment where handler can run. "' Gofunc (S *server) handlesomething () http. Handlerfunc {thing: = Preparething () return func (w http. Responsewriter, R *http. Request) {//Use thing}} ' ' Preparething ' function will only be called once, so you can use it to complete the one-time initialization of each handler, then using ' thing ' in handler. Make sure you only perform read operations on shared data, and if handler overwrites shared data, remember that you need to use locks or other mechanisms to protect shared data. # # for handler proprietary dependency pass parameters if a particular handler has a dependency, take this dependency as a parameter. "Gofunc (S *server) handlegreeting (format string) http. Handlerfunc {return func (w http. Responsewriter, R *http. Request) {fmt. fprintf (w, Format, "World")}} ' ' Format ' variable can be accessed by handler. # # Replace Handler with Handlerfunc now I use ' http in almost every use case. Handlerfunc '  , rather than ' http. Handler '. "' Gofunc (S *server) handlesomething () http. Handlerfunc {return func (w http. Responsewriter, R *http. Request) {...}} "The two are mostly interchangeable., so you can choose which one is easy to read. To me, ' http. Handlerfunc ' more suitable. # # Middleware is just a Go function middleware function that accepts a ' http. Handlerfunc ' and returns a new Handlerfunc that handler can run code before or after the initial handler is called-or it can decide whether to invoke the initial handler. "' Gofunc (S *server) adminonly (H http. Handlerfunc) http. Handlerfunc {return func (w http. Responsewriter, R *http. Request) {if!currentuser (r). ISAdmin {http. NotFound (W, R) return} "H (W, R)}}" This handler internal logic can optionally decide whether to invoke the initial handler--in the above example, if ' ISAdmin ' is ' false ', the H Andler will return an HTTP ' 404 Not Found ' and return (abort); note that the ' H ' handler is not called. If ' ISAdmin ' is ' true ', it will run to ' H ' handler. Usually I put the middleware into the ' routes.go ' file: ' Gopackage appfunc (S *server) routes () {S.router.handlefunc ("/api/", S.handleapi () ) S.router.handlefunc ("/about", S.handleabout ()) S.router.handlefunc ("/", S.handleindex ()) S.router.handlefunc ("/ Admin ", S.adminonly (S.handleadminindex))} ' # # Request and response types can also be placed there if the terminal has its own request and response type, Usually these types are only useful for specific handler. Suppose one example, you can define them inside the function. "' Gofunc (s *server) handLesomething () http. Handlerfunc {type request struct {Name string} type response struct {greeting string ' JSON: "Greeting" '} Return func (w http. Responsewriter, R *http. Request) {...}} "This frees the space of the package and allows you to define the type as the same name, eliminating the specific handler to consider naming it." When testing the code, you can copy the types directly into your test function and do the same. Or the other ... # # test type helps the framework test frame if your request/response type is hidden inside handler, you can define the new type directly in the test code. This gives you the opportunity to do some explanatory work so that future successors can understand your code. For example, we assume that a ' person ' type exists in the code and that it is reused in many terminals. If we have a '/greet ' terminal, then we may only care about its ' Name ', so it can be stated in the test code: ' ' Gofunc testgreet (t *testing. T) {is: = is. New (t) P: = struct {name string ' JSON: ' name ' '} {name: ' Mat ryer ',} var buf bytes. Buffer ERR: = json. Newencoder (&BUF). Encode (P) is. NOERR (ERR)//JSON. Newencoder req, Err: = http. Newrequest (http. Methodpost, "/greet", &buf) is. NOERR (ERR)//... more Test code "Here" is clearly seen in the test codes, and we only care about the ' Name ' field of ' person '. # # Sync. Once organization Dependencies If I had to do some expensive things to prepare for handler, I would postpone them until the first time I called handler. This can improve the startup time of your app. "Gofunc (S *server) handletemplate (Files string ...) http. HanDlerfunc {var (init sync). Once TPL *template. Template err Error) return func (w http. Responsewriter, R *http. Request) {init. Do (func () {TPL, err = template. Parsefiles (Files ...)}) If err! = Nil {http. Error (W, err. Error (), HTTP. STATUSINTERNALSERVERERROR) return}//Use TPL} "" Sync. Once ' ensures that the code runs only once, and if there are other calls (others initiate the same request) it will block until the end of the code. -Error checking is placed outside the ' init ' function, so we can still capture it if something is wrong, and it will not be lost in the log. -If handler has not been called, these expensive operations will never happen-this can be of great benefit to your code deployment. Remember this by moving the initialization time from the start point to the run time (when the endpoint is first accessed). I've been using Google App Engine for a long time and it's understandable to me, but it may not be possible for you. So you need to think about when and where it's worth ' sync. Once ' This way. # # server must be easy to test our server type needs to be able to test easily. "' Gofunc testhandleabout (t *testing. T) {is: = is. New (t) SRV: = server{db:mockdatabase, Email:mockemailsender,} srv.routes () req, err: = http. Newrequest ("GET", "/about", nil) is. NOERR (Err) W: = Httptest. Newrecorder () srv. Servehttp (W, R) is. Equal (W.statuscode, HTTP. Statusok)} "-Creates a server instance in each set of tests--if you defer loading expensive operations, this will not take much time, even for large components. -by invoking the serverhttp on the server, we will test the entire stack, including routing andMiddleware and so on. Of course, if you want to avoid this situation, you can also call the handler function directly. -Use ' httptest '. Newrecorder ' to record the operations performed by handler. -This code example uses a micro-framework that is being tested in my [] (https://godoc.org/github.com/matryer/is) (an easy option for verification) # # Conclusion I hope that the content covered in this article may be of some use to you and will help you with your work. If you have a different opinion or other ideas, [please contact us] (https://twitter.com/matryer).
via:https://medium.com/statuscode/how-i-write-go-http-services-after-seven-years-37c208122831
Author: Mat Ryer Translator: Sunzhaohao proofreading: polaris1119
This article by GCTT original compilation, go language Chinese network honor launches
This article was originally translated by GCTT and the Go Language Chinese network. Also want to join the ranks of translators, for open source to do some of their own contribution? Welcome to join Gctt!
Translation work and translations are published only for the purpose of learning and communication, translation work in accordance with the provisions of the CC-BY-NC-SA agreement, if our work has violated your interests, please contact us promptly.
Welcome to the CC-BY-NC-SA agreement, please mark and keep the original/translation link and author/translator information in the text.
The article only represents the author's knowledge and views, if there are different points of view, please line up downstairs to spit groove
482 Reads