Go-HTTP
package net/http
这个package支持:web application, web APIs;简单地说,就是可以用net/http来实现HTTP客户端和服务器的开发。 另外,还要用到package html/template。
该书作者不建议初学者一开始就用第三方库,而是先用Go发布时的net/http等标准库。
在本系列中,会略过HTTP的概念,这方面可以找专门的HTTP书籍做参考。
处理HTTP请求
如果是构建WEB APIs,那么服务器会返回XML或JSON;如果是构建WEB应用,则服务器返回HTML网页。
net/http的两个主要构件(src/net/http/server.go):
- ServeMux: 多路复用,或HTTP请求路由。讲HTTP请求和预定义的URIs进行比较,然后调用对应的Handler来处理这个HTTP请求。——对应于Tomcat容器。
- Handler: 负责写HTTP响应结果(Header&Body)。http.Handler是一个接口,用户要做的就是实现这个接口。——可以和Spring @Controller进行对比。
Handler
源码
// A Handler responds to an HTTP request. // // ServeHTTP should write reply headers and data to the ResponseWriter // and then return. Returning signals that the request is finished; it // is not valid to use the ResponseWriter or read from the // Request.Body after or concurrently with the completion of the // ServeHTTP call. // // Depending on the HTTP client software, HTTP protocol version, and // any intermediaries between the client and the Go server, it may not // be possible to read from the Request.Body after writing to the // ResponseWriter. Cautious handlers should read the Request.Body // first, and then reply. // // Except for reading the body, handlers should not modify the // provided Request. // // If ServeHTTP panics, the server (the caller of ServeHTTP) assumes // that the effect of the panic was isolated to the active request. // It recovers the panic, logs a stack trace to the server error log, // and hangs up the connection. type Handler interface { ServeHTTP(ResponseWriter, *Request) }
要点
ResponseWriter对象用来构件Response,包括Header和Body。 一旦往ResponseWriter中写了数据之后,就不要再访问Request对象了。 ServeHttp不能修改Request的值。
ResponseWriter
ResponseWriter也是一个接口,定义了3个方法。
// A ResponseWriter interface is used by an HTTP handler to // construct an HTTP response. // // A ResponseWriter may not be used after the Handler.ServeHTTP method // has returned. type ResponseWriter interface { // Header returns the header map that will be sent by // WriteHeader. Changing the header after a call to // WriteHeader (or Write) has no effect unless the modified // headers were declared as trailers by setting the // "Trailer" header before the call to WriteHeader (see example). // To suppress implicit response headers, set their value to nil. Header() Header // Write writes the data to the connection as part of an HTTP reply. // // If WriteHeader has not yet been called, Write calls // WriteHeader(http.StatusOK) before writing the data. If the Header // does not contain a Content-Type line, Write adds a Content-Type set // to the result of passing the initial 512 bytes of written data to // DetectContentType. // // Depending on the HTTP protocol version and the client, calling // Write or WriteHeader may prevent future reads on the // Request.Body. For HTTP/1.x requests, handlers should read any // needed request body data before writing the response. Once the // headers have been flushed (due to either an explicit Flusher.Flush // call or writing enough data to trigger a flush), the request body // may be unavailable. For HTTP/2 requests, the Go HTTP server permits // handlers to continue to read the request body while concurrently // writing the response. However, such behavior may not be supported // by all HTTP/2 clients. Handlers should read before writing if // possible to maximize compatibility. Write([]byte) (int, error) // WriteHeader sends an HTTP response header with status code. // If WriteHeader is not called explicitly, the first call to Write // will trigger an implicit WriteHeader(http.StatusOK). // Thus explicit calls to WriteHeader are mainly used to // send error codes. WriteHeader(int) }
FileServer/静态页面
FileServer/静态页面
package main import ( "net/http" ) func main() { mux := http.NewServeMux() fs := http.FileServer(http.Dir("public")) mux.Handle("/", fs) http.ListenAndServe(":8080", mux) }
这里需要创建一个public文件夹,这个public文件夹和.go文件(如helloworld.go)是在同一级目录;——为此只需要注意到public采用的是相对路径。
比如接下来在public目录下创建一个about.html文件,那么就可以用https://localhost:8080/about.html来访问这个页面。
这里的FileServer()定义在src/http/fs.go中,这个函数返回的是type fileHandler struct,这个struct实现了Handler接口。
自定义的Handler/Creating Custom Handlers
自定义的Handler/Creating Custom Handlers
用户可以创建自己的struct,只要实现了Handler接口,就可以用来作为HTTP消息处理的handler。
package main import ( "fmt" "log" "net/http" ) type MyServeMux struct { http.ServeMux id int } type MessageHandler struct { msg string } func (m *MessageHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, m.msg) } /* https://localhost:8080/first -> handler1 https://localhost:8080/second -> handler2 https://localhost:8080/second/item1 -> handler21 https://localhost:8080/second/item2 -> 404 page not found */ func main() { mux := new (MyServeMux) handler1 := &MessageHandler{"The first handler."} handler2 := &MessageHandler{"The second handler."} handler21 := &MessageHandler{"The second handler, and item is 1."} mux.Handle("/first", handler1) mux.Handle("/second", handler2) mux.Handle("/second/item1", handler21) log.Println("Listening...") http.ListenAndServe(":8080", mux) }
中间部分代码也可以写作:
handler1 := MessageHandler{"The first handler."} handler2 := MessageHandler{"The second handler."} handler21 := MessageHandler{"The second handler, and item is 1."} mux.Handle("/first", &handler1) mux.Handle("/second", &handler2) mux.Handle("/second/item1", &handler21)
要点:
需要通过指针/地址方式,实现多态功能。 ServeMux是用来维护URI和Handler之间的映射关系。 每个Handler就是Spring的@Controller。
http.HandlerFunc type
http.HandlerFunc type
前面的例子中,先创建一个struct,并为之实现ServeHttp()接口,从而将struct变成一个Handler。有时候,如果struct没有field,则可以用http.HandlerFunc类型的方法。
定义/server.go
定义/server.go
// The HandlerFunc type is an adapter to allow the use of // ordinary functions as HTTP handlers. If f is a function // with the appropriate signature, HandlerFunc(f) is a // Handler that calls f. type HandlerFunc func(ResponseWriter, *Request) // ServeHTTP calls f(w, r). func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) }
这里的代码说明了,type不仅可以用于数据类型,也可以用于函数。
HandlerFunc是一个函数类型,主要用户自定义的函数符合这个signature,那么就可以用HandlerFunc(foo)将这个foo函数强制转换成HandlerFunc类型。又因为HandlerFunc类型实现了ServerHttp()接口,所以这个HandlerFunc对象(这又像C++的functor了)也就自动是一个Handler了。
注意到:抛开receiver,那么HandlerFunc的signature和ServerHttp是一样的。
示例
示例
package main import ( "fmt" "log" "net/http" ) func msgHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Hello, HandlerFunc!") } func main() { mux := http.NewServeMux() handler := http.HandlerFunc(msgHandler) mux.Handle("/hello", handler) log.Println("Listening...") http.ListenAndServe(":8080", mux) }
有struct改成func之后,之前的参数化功能(可定制msg)消失了。为此,可以对func进行改进。
改进:函数参数化
改进:函数参数化
package main import ( "fmt" "log" "net/http" ) func msgHandler(msg string) http.Handler { return http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, msg) }) } func main() { mux := http.NewServeMux() handler1 := msgHandler("first handler") handler2 := msgHandler("second handler") mux.Handle("/first", handler1) mux.Handle("/second", handler2) log.Println("Listening...") http.ListenAndServe(":8080", mux) }
ServeMux.HandleFunc()
ServeMux.HandleFunc()
Go总是尽力为用户提供方便。为了阐述这一点,先给出示例:
示例一
示例一
package main import ( "fmt" "log" "net/http" ) func msgHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Hello, HandlerFunc!") } func main() { mux := http.NewServeMux() //handler := http.HandlerFunc(msgHandler) //mux.Handle("/hello", handler) mux.HandleFunc("/hello", msgHandler) log.Println("Listening...") http.ListenAndServe(":8080", mux) }
在这个例子中,把之前的两句合并为一句。为了说明其含义,给出ServeMux.HandleFunc()的定义。
ServeMux.HandleFunc()
ServeMux.HandleFunc()
// HandleFunc registers the handler function for the given pattern. func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { mux.Handle(pattern, HandlerFunc(handler)) }
对照这个源码,不难理解两句合并为一句的原理。故无须再啰嗦。
示例二
示例二
另一个优化示例。
package main import ( "fmt" "log" "net/http" ) func msgHandler(msg string) http.HandlerFunc/*http.Handler*/ { return http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, msg) }) } func main() { mux := http.NewServeMux() /*handler1 := msgHandler("first handler") handler2 := msgHandler("second handler") mux.Handle("/first", handler1) mux.Handle("/second", handler2)*/ mux.HandleFunc("/first", msgHandler("first handler")) mux.HandleFunc("/second", msgHandler("second handler")) log.Println("Listening...") http.ListenAndServe(":8080", mux) }
或者改为:
func msgHandler2(msg string) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, msg) } } func main() { //... mux.HandleFunc("/first", msgHandler2("first handler")) mux.HandleFunc("/second", msgHandler2("second handler")) //... }
ServeMux
ServeMux
NewServeMux()
NewServeMux()
该函数(src/net/http/server.go)的定义如下:
// NewServeMux allocates and returns a new ServeMux. func NewServeMux() *ServeMux { return new(ServeMux) }
也就是说,这个函数创建一个ServeMux对象,并返回该对象(的指针)。
DefaultServeMux
DefaultServeMux
DefaultServeMux是http package中定义的一个ServeMux对象:
// DefaultServeMux is the default ServeMux used by Serve. var DefaultServeMux = &defaultServeMux var defaultServeMux ServeMux
那么什么时候用到DefaultServeMux呢?——当用户代码没有定义ServeMux对象的时候。
示例代码:
package main import ( "fmt" "log" "net/http" ) func msgHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Hello, HandlerFunc!") } func main() { //mux := http.NewServeMux() /*mux*/http.HandleFunc("/hello", msgHandler) log.Println("Listening...") http.ListenAndServe(":8080", nil/*mux*/) }
可以看到,用DefaultServeMux会简化代码。
那么接下来,就把相关的函数的源码列出来,以此理解背后的原理。
HandleFunc
HandleFunc
// HandleFunc registers the handler function for the given pattern // in the DefaultServeMux. // The documentation for ServeMux explains how patterns are matched. func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, handler) } // HandleFunc registers the handler function for the given pattern. func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { mux.Handle(pattern, HandlerFunc(handler)) }
ListenAndServe()
ListenAndServe()
这个流程相当长,可以跳过前面,直接跑到最后一个函数。——中间函数罗列在这里,供参考。
func ListenAndServe(addr string, handler Handler) error { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe() } // ListenAndServe listens on the TCP network address srv.Addr and then // calls Serve to handle requests on incoming connections. // Accepted connections are configured to enable TCP keep-alives. // If srv.Addr is blank, ":http" is used. // ListenAndServe always returns a non-nil error. func (srv *Server) ListenAndServe() error { addr := srv.Addr if addr == "" { addr = ":http" } ln, err := net.Listen("tcp", addr) if err != nil { return err } return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)}) } // Serve accepts incoming connections on the Listener l, creating a // new service goroutine for each. The service goroutines read requests and // then call srv.Handler to reply to them. // // For HTTP/2 support, srv.TLSConfig should be initialized to the // provided listener's TLS Config before calling Serve. If // srv.TLSConfig is non-nil and doesn't include the string "h2" in // Config.NextProtos, HTTP/2 support is not enabled. // // Serve always returns a non-nil error. func (srv *Server) Serve(l net.Listener) error { defer l.Close() if fn := testHookServerServe; fn != nil { fn(srv, l) } var tempDelay time.Duration // how long to sleep on accept failure if err := srv.setupHTTP2_Serve(); err != nil { return err } // TODO: allow changing base context? can't imagine concrete // use cases yet. baseCtx := context.Background() ctx := context.WithValue(baseCtx, ServerContextKey, srv) ctx = context.WithValue(ctx, LocalAddrContextKey, l.Addr()) for { rw, e := l.Accept() if e != nil { if ne, ok := e.(net.Error); ok && ne.Temporary() { if tempDelay == 0 { tempDelay = 5 * time.Millisecond } else { tempDelay *= 2 } if max := 1 * time.Second; tempDelay > max { tempDelay = max } srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay) time.Sleep(tempDelay) continue } return e } tempDelay = 0 c := srv.newConn(rw) c.setState(c.rwc, StateNew) // before Serve can return go c.serve(ctx) } } // Serve a new connection. func (c *conn) serve(ctx context.Context) { c.remoteAddr = c.rwc.RemoteAddr().String() defer func() { if err := recover(); err != nil { const size = 64 << 10 buf := make([]byte, size) buf = buf[:runtime.Stack(buf, false)] c.server.logf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf) } if !c.hijacked() { c.close() c.setState(c.rwc, StateClosed) } }() if tlsConn, ok := c.rwc.(*tls.Conn); ok { if d := c.server.ReadTimeout; d != 0 { c.rwc.SetReadDeadline(time.Now().Add(d)) } if d := c.server.WriteTimeout; d != 0 { c.rwc.SetWriteDeadline(time.Now().Add(d)) } if err := tlsConn.Handshake(); err != nil { c.server.logf("http: TLS handshake error from %s: %v", c.rwc.RemoteAddr(), err) return } c.tlsState = new(tls.ConnectionState) *c.tlsState = tlsConn.ConnectionState() if proto := c.tlsState.NegotiatedProtocol; validNPN(proto) { if fn := c.server.TLSNextProto[proto]; fn != nil { h := initNPNRequest{tlsConn, serverHandler{c.server}} fn(c.server, tlsConn, h) } return } } // HTTP/1.x from here on. c.r = &connReader{r: c.rwc} c.bufr = newBufioReader(c.r) c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10) ctx, cancelCtx := context.WithCancel(ctx) defer cancelCtx() for { w, err := c.readRequest(ctx) if c.r.remain != c.server.initialReadLimitSize() { // If we read any bytes off the wire, we're active. c.setState(c.rwc, StateActive) } if err != nil { if err == errTooLarge { // Their HTTP client may or may not be // able to read this if we're // responding to them and hanging up // while they're still writing their // request. Undefined behavior. io.WriteString(c.rwc, "HTTP/1.1 431 Request Header Fields Too Large\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\n431 Request Header Fields Too Large") c.closeWriteAndWait() return } if err == io.EOF { return // don't reply } if neterr, ok := err.(net.Error); ok && neterr.Timeout() { return // don't reply } var publicErr string if v, ok := err.(badRequestError); ok { publicErr = ": " + string(v) } io.WriteString(c.rwc, "HTTP/1.1 400 Bad Request\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\n400 Bad Request"+publicErr) return } // Expect 100 Continue support req := w.req if req.expectsContinue() { if req.ProtoAtLeast(1, 1) && req.ContentLength != 0 { // Wrap the Body reader with one that replies on the connection req.Body = &expectContinueReader{readCloser: req.Body, resp: w} } } else if req.Header.get("Expect") != "" { w.sendExpectationFailed() return } // HTTP cannot have multiple simultaneous active requests.[*] // Until the server replies to this request, it can't read another, // so we might as well run the handler in this goroutine. // [*] Not strictly true: HTTP pipelining. We could let them all process // in parallel even if their responses need to be serialized. serverHandler{c.server}.ServeHTTP(w, w.req) w.cancelCtx() if c.hijacked() { return } w.finishRequest() if !w.shouldReuseConnection() { if w.requestBodyLimitHit || w.closedRequestBodyEarly() { c.closeWriteAndWait() } return } c.setState(c.rwc, StateIdle) } } // initNPNRequest is an HTTP handler that initializes certain // uninitialized fields in its *Request. Such partially-initialized // Requests come from NPN protocol handlers. type initNPNRequest struct { c *tls.Conn h serverHandler } // serverHandler delegates to either the server's Handler or // DefaultServeMux and also handles "OPTIONS *" requests. type serverHandler struct { srv *Server } func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) { handler := sh.srv.Handler if handler == nil { handler = DefaultServeMux } if req.RequestURI == "*" && req.Method == "OPTIONS" { handler = globalOptionsHandler{} } handler.ServeHTTP(rw, req) }
ServeMux struct
ServeMux struct
数据成员:
type ServeMux struct { mu sync.RWMutex m map[string]muxEntry hosts bool // whether any patterns contain hostnames }
其中map是URI到Handler之间的映射关系。相关的函数或成员方法如下(godoc cmd/net/http ServeMux):
type ServeMux struct { // contains filtered or unexported fields } ServeMux is an HTTP request multiplexer. It matches the URL of each incoming request against a list of registered patterns and calls the handler for the pattern that most closely matches the URL. Patterns name fixed, rooted paths, like "/favicon.ico", or rooted subtrees, like "/images/" (note the trailing slash). Longer patterns take precedence over shorter ones, so that if there are handlers registered for both "/images/" and "/images/thumbnails/", the latter handler will be called for paths beginning "/images/thumbnails/" and the former will receive requests for any other paths in the "/images/" subtree. Note that since a pattern ending in a slash names a rooted subtree, the pattern "/" matches all paths not matched by other registered patterns, not just the URL with Path == "/". If a subtree has been registered and a request is received naming the subtree root without its trailing slash, ServeMux redirects that request to the subtree root (adding the trailing slash). This behavior can be overridden with a separate registration for the path without the trailing slash. For example, registering "/images/" causes ServeMux to redirect a request for "/images" to "/images/", unless "/images" has been registered separately. Patterns may optionally begin with a host name, restricting matches to URLs on that host only. Host-specific patterns take precedence over general patterns, so that a handler might register for the two patterns "/codesearch" and "codesearch.google.com/" without also taking over requests for "https://www.google.com/". ServeMux also takes care of sanitizing the URL request path, redirecting any request containing . or .. elements or repeated slashes to an equivalent, cleaner URL. func NewServeMux() *ServeMux NewServeMux allocates and returns a new ServeMux. func (mux *ServeMux) Handle(pattern string, handler Handler) Handle registers the handler for the given pattern. If a handler already exists for pattern, Handle panics. func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) HandleFunc registers the handler function for the given pattern. func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) Handler returns the handler to use for the given request, consulting r.Method, r.Host, and r.URL.Path. It always returns a non-nil handler. If the path is not in its canonical form, the handler will be an internally-generated handler that redirects to the canonical path. Handler also returns the registered pattern that matches the request or, in the case of internally-generated redirects, the pattern that will match after following the redirect. If there is no registered handler that applies to the request, Handler returns a ``page not found'' handler and an empty pattern. func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) ServeHTTP dispatches the request to the handler whose pattern most closely matches the request URL.