This is a creation in Article, where the information may have evolved or changed.
Often see a lot of students in the plan to use go to develop the time will ask what HTTP framework is better. In fact, the HTTP package of Go is very powerful, for the general HTTP Rest API development, can not use the framework to achieve the desired function.
We started experimenting with less than 100 lines of code to customize the basic functional framework.
First, consider the basic requirements:
- Output access days, you need to know:
- Method
- Status code
- wr.
- Response consumption time
- Response Content-length
- Error trapping, catching an error when an HTTP request is unexpected, returning exception information
The above is a few basic needs, the future may have a lot of, so should try to design into a form of middleware to deal with the future changes in demand, so that the function according to demand increases or decreases.
We can put the HTTP framework middleware design than onion, one layer at a level, to the middle of the business layer, and then a layer of out.
Draw the process out like this:
Http:| LogRequst| ErrCatch| Cookie| Handler| cookie| ErrCatchV LogRequst
The invocation process is similar to each middleware layer by package, which conforms to the process of the function stack layer call.
Note: Because this small frame is eventually going to be http. Serve or http. Listenandserve is called, so HTTP needs to be implemented . Handler interface, the received parameter is http. Responsewriter and *http. Request
All right! The target is determined, and we're starting to make it work.
First you need to define a struct structure, where you need to save the middleware, and the final HTTP to execute . Handler
// MiddlewareFunc filter typetype MiddlewareFunc func(ResponseWriteReader, *http.Request, func())// MiddlewareServe server structtype MiddlewareServe struct { middlewares []MiddlewareFunc Handler http.Handler}
Here's a problem because the default is to receive the parameter http. The Responsewriter interface is a write-only interface that cannot be read, but we need to be able to read status code and content-length . This time the magic of interface design is reflected, redefine an interface and contain http. Responsewriter , adding the ability to read status code and content-length
// ResponseWriteReader for middlewaretype ResponseWriteReader interface { StatusCode() int ContentLength() int http.ResponseWriter}
Define a warp struct implementation Responsewritereader interface
// WrapResponseWriter implement ResponseWriteReader interfacetype WrapResponseWriter struct { status int length int http.ResponseWriter}// NewWrapResponseWriter create wrapResponseWriterfunc NewWrapResponseWriter(w http.ResponseWriter) *WrapResponseWriter { wr := new(WrapResponseWriter) wr.ResponseWriter = w wr.status = 200 return wr}// WriteHeader write status codefunc (p *WrapResponseWriter) WriteHeader(status int) { p.status = status p.ResponseWriter.WriteHeader(status)}func (p *WrapResponseWriter) Write(b []byte) (int, error) { n, err := p.ResponseWriter.Write(b) p.length += n return n, err}// StatusCode return status codefunc (p *WrapResponseWriter) StatusCode() int { return p.status}// ContentLength return content lengthfunc (p *WrapResponseWriter) ContentLength() int { return p.length}
Next,Middlewareserve itself needs to conform to http. Handler, so we need to define servehttp.
// ServeHTTP for http.Handler interfacefunc (p *MiddlewareServe) ServeHTTP(w http.ResponseWriter, r *http.Request) { i := 0 // warp http.ResponseWriter 可以让中间件读取到 status code wr := NewWrapResponseWriter(w) var next func() // next 函数指针 next = func() { if i < len(p.middlewares) { i++ p.middlewares[i-1](wr, r, next) } else if p.Handler != nil { p.Handler.ServeHTTP(wr, r) } } next()}
Then add a method to insert the middleware
// Use push MiddlewareFuncfunc (p *MiddlewareServe) Use(funcs ...MiddlewareFunc) { // 可以一次插入一个或多个 for _, f := range funcs { p.middlewares = append(p.middlewares, f) }}
Here, a small framework that supports middleware is defined, with comments less than 80 lines of code
Let's start by implementing a few middleware tests.
// LogRequest print a request statusfunc LogRequest(w ResponseWriteReader, r *http.Request, next func()) { t := time.Now() next() log.Printf("%v %v %v use time %v content-length %v", r.Method, w.StatusCode(), r.URL.String(), time.Now().Sub(t).String(), w.ContentLength())}
This function prints out the HTTP request Method, status code, URL, processing request consumption time, response content-length
Test it.
package mainimport ( "fmt" "net/http")func helloHandle(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, " hello ! this's a http request \n method %v \n request url is %v ", r.Method, r.URL.String())}func main() { // create middleware server s := new(MiddlewareServe) s.Handler = http.HandlerFunc(helloHandle) s.Use(LogRequest) // start server fmt.Println(http.ListenAndServe(":3000", s))}
Run
$ go run *.go$ curl 127.0.0.1:3000> hello ! this's a http request> method GET> request url is# 输出日志> 2016/04/24 02:28:12 GET 200 / use time 61.717µs content-length 64$ curl 127.0.0.1:3000/hello/go> hello ! this's a http request> method GET> request url is /hello/go# 输出日志> 2016/04/24 02:31:36 GET 200 /hello/go use time 28.207µs content-length 72
Or use the browser to request an address to view the effect
- http://127.0.0.1:3000
- Http://127.0.0.1:3000/hello/go
Add one more error capture middleware:
// ErrCatch catch and recoverfunc ErrCatch(w ResponseWriteReader, r *http.Request, next func()) { defer func() { if err := recover(); err != nil { fmt.Println(err) debug.PrintStack() w.WriteHeader(http.StatusInternalServerError) // 500 } }() next()}
Test
package mainimport ( "fmt" "net/http")func helloHandle(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, " hello ! this's a http request \n method %v \n request url is %v \n", r.Method, r.URL.String())}func panicHandle(w http.ResponseWriter, r *http.Request) { panic("help me !")}func main() { // create middleware server s := new(MiddlewareServe) route := http.NewServeMux() route.Handle("/hello", http.HandlerFunc(helloHandle)) route.Handle("/panic", http.HandlerFunc(panicHandle)) s.Handler = route s.Use(LogRequest, ErrCatch) // start server fmt.Println(http.ListenAndServe(":3000", s))}
Run
$ curl -i 127.0.0.1:3000/panic> HTTP/1.1 500 Internal Server Error> Date: Sat, 23 Apr 2016 18:51:12 GMT> Content-Length: 0> Content-Type: text/plain; charset=utf-8# log> help me !> ... # debug.Stack> 2016/04/24 02:51:12 GET 500 /panic use time 142.885µs content-length 0$ curl -i 127.0.0.1:3000/hello/go> HTTP/1.1 404 Not Found> Content-Type: text/plain; charset=utf-8> X-Content-Type-Options: nosniff> Date: Sat, 23 Apr 2016 18:55:30 GMT> Content-Length: 19>> 404 page not found# log2016/04/24 02:55:30 GET 404 /hello/go use time 41.14µs content-length 19
Here, a flexible core is realized. I try to use only standard packages, not the introduction of third-party packages, I hope this may help the students just learn to learn more about the Net.http package, of course, in real-world use can be introduced to other HTTP-compliant . Router substitution for Handler interface Servemux
Some students may have seen it, since Middlewareserve implemented http. Handler that you can hang into the router . Yes, that would be equivalent to customizing the Middlewareserve for a certain path, such as some interfaces that require permission validation, and I'll try write permission validation in the next article.
Calls involving templates I'm not going to write because this is really a scripting language better suited
All code for GitHub address: https://github.com/ifanfan/golearn/tree/master/websrv
The first blog I wrote I hope to continue to write