Original address
?? Before I wrote an article about using http.HandlerFunc
to implement a custom handler type to avoid some common mistakes. func MyHandler(w http.ResponseWriter, r *http.Request)
signatures are often visible. This is a useful generic handler type that contains some basic functionality, but as with everything else, there are some disadvantages:
- When you want to stop processing in a handler, you must remember to show the call a return. This is common when you want to run out of the state of a direction (301, 302), not found (404), or server-side error (500). Failure to do so may cause some subtle errors (the function will continue) because the function does not need a return value and the compiler will not warn you.
- It is not easy to pass additional parameters (for example, database connection pool, configuration). You end up having to work with a series of global variables (not too bad, but tracking them can be difficult to extend) or save them in the request context, and then take them out every time. It is cumbersome to do so.
- Constantly repeating the same statement. Want to log the errors returned by the database package? You can either call in each query method again
log.Printf
, or you can return an error in each handler. If your handler can return to a function that records errors centrally, it is better to run a 500 error.
?? I used a signature in my previous method func(http.ResponseWriter, *http.Request)
. This has been shown to be a brief introduction, but there is a strange place to return an error-Free State, for example, 200,302,303 is often superfluous, because either you have already set it elsewhere, or it is useless. For example:
Func Somehandler (w http. Responsewriter, R *http. Request) (int, error) {db, err:=Somedbcall ()ifErr! =Nil {//This makes sense. return -, err}ifuser. LoggedIn {http. Redirect (W, R,"/dashboard",302) //superfluous! Our HTTP. Redirect function handles the 302, not//Our return value (which is effectively ignored). return 302, nil}}
It looks okay, but we can do better than that.
?? So how should we improve it? Let's start by listing the code:
Package Handler//error represents a handler error. It provides methods for a HTTP status//code and embeds the built-in error interface.Type ErrorInterface{error Status ()int}//Statuserror represents an error with an associated HTTP status code.Type Statuserrorstruct{CodeintERR Error}//allows Statuserror to satisfy the error interface.Func (SE statuserror) Error ()string { returnSE. Err.error ()}//Returns our HTTP status code.Func (SE statuserror) Status ()int { returnSE. Code}//A (Simple) Example of our application-wide configuration.Type ENVstruct{DB*SQL. DB PortstringHoststring}//The Handler struct that takes a configured ENV and a function matching//Our useful signature.Type Handlerstruct { *ENV H func (e*env, W http. Responsewriter, R *http. Request) Error}//Servehttp allows our Handler type to satisfy HTTP. Handler.Func (H Handler) servehttp (w http. Responsewriter, R *http. Request) {err:=H.H (H.env, W, R)ifErr! =Nil {SwitchE: =Err. (type) { CaseError://We can retrieve the status here and write out a specific//HTTP status code.Log. Printf ("HTTP%d-%s", E.status (), e) http. Error (W, E.error (), E.status ())default: //Any error types we don ' t specifically look out for default//To serving a HTTPhttp. Error (W, http. StatusText (http. Statusinternalservererror), HTTP. statusinternalservererror) }}}
The above code is self-explanatory, but to illustrate some of the salient points:
- We have customized a
Error
type (interface), and he has built in the error interface of Go, and provides a Status() int
method.
- We provide a simple
StatusError
type (struct), which satisfies handler.Error
the interface. Statuserror accepts an HTTP status code (int type), an error type that allows us to wrap errors to record or query.
- Our
ServeHTTP
approach has wrapped up an "e: = Err." Type asserts that it can test the errors we need to handle, allowing us to handle those special errors. In this example, he is just a handler.Error
type. Other errors, such as errors in other packages, want net. Error, or other additional errors that we define, can also be checked if you want to check.
?? If we don't want to catch those errors, we default
'll snap to them by default. Keep in mind that ServeHTTP
our handler type satisfies the Http.handler interface so that he can use it in any place where http.handler is used, such as Go's net/http package or all other third-party frameworks. This makes custom handler more useful, and they are flexible to use.
?? It's easy to note that the net package handles things. It's a net. Error interface, built-in error interface embedded. Some specific types have implemented it. The exact type returned by the function is the same as the wrong type (DNS error, parsing error, etc.). The dberror defined in the Datastore package has a query () string method, which can be well explained. All examples
?? What does it look like at the end? Can we divide it into different packages?
Package Handlerimport ("net/http")//error represents a handler error. It provides methods for a HTTP status//code and embeds the built-in error interface.Type ErrorInterface{error Status ()int}//Statuserror represents an error with an associated HTTP status code.Type Statuserrorstruct{CodeintERR Error}//allows Statuserror to satisfy the error interface.Func (SE statuserror) Error ()string { returnSE. Err.error ()}//Returns our HTTP status code.Func (SE statuserror) Status ()int { returnSE. Code}//A (Simple) Example of our application-wide configuration.Type ENVstruct{DB*SQL. DB PortstringHoststring}//The Handler struct that takes a configured ENV and a function matching//Our useful signature.Type Handlerstruct { *ENV H func (e*env, W http. Responsewriter, R *http. Request) Error}//Servehttp allows our Handler type to satisfy HTTP. Handler.Func (H Handler) servehttp (w http. Responsewriter, R *http. Request) {err:=H.H (H.env, W, R)ifErr! =Nil {SwitchE: =Err. (type) { CaseError://We can retrieve the status here and write out a specific//HTTP status code.Log. Printf ("HTTP%d-%s", E.status (), e) http. Error (W, E.error (), E.status ())default: //Any error types we don ' t specifically look out for default//To serving a HTTPhttp. Error (W, http. StatusText (http. Statusinternalservererror), HTTP. Statusinternalservererror)}}}func getindex (env*env, W http. Responsewriter, R *http. Request) Error {Users, err:=Env. Db. GetAllUsers ()ifErr! =Nil {//We return a status error here, which conveniently wraps the error//returned from our DB queries. We can clearly define which errors//is worth raising a HTTP vs. which might just be a HTTP//404, 403 or 401 (as appropriate). It ' s also clear where our//handler should stop processing by returning early. returnstatuserror{ -, err}} Fmt. fprintf (W,"%+v", users)returnNil}
Main package:
Package Mainimport ("net/http" "Github.com/you/somepkg/handler") Func main () {db, err:= SQL. Open ("Connectionstringhere") ifErr! =Nil {log. Fatal (ERR)}//initialise Our app-wide environment with the services/info we need.ENV: = &handler. env{db:db, Port:os. Getenv ("PORT"), Host:os. Getenv ("HOST"), //We might also have a custom log. Logger, our//template instance, and a config struct as fields//In our Env struct. } //Note that we ' re using HTTP. Handle, not http. Handlefunc. the//latter only accepts the HTTP. Handlerfunc type, which is not//What do we have here .http. Handle ("/", Handler. Handler{env, Handler. GetIndex})//Logs the error if Listenandserve fails.Log. Fatal (http. Listenandserve (": 8000", nil))}
In practical use, handler and Env are placed in different packages, just for simplicity in the same package.
http. Handler and go error handling