golang的http分析
Posted qqwx
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了golang的http分析相关的知识,希望对你有一定的参考价值。
首先,要认识一个贯穿始终的接口http.Handler
type Handler interface { ServeHTTP(ResponseWriter, *Request) }
其中,两个参数,一个是表示响应的接口,另一个表示请求。具体方法先忽略:
type ResponseWriter interface { }
使用时,这个函数指这定地址和对应的handler
func ListenAndServe(addr string, handler Handler)
再看下http包内的一个重要函数,Handle,可见,传入的是一个监听的http path,第二个参数是上述的handler.
func Handle(pattern string, handler Handler)
看一下如何使用的:
使用接口形式的Handle + ListenAndServe
type ImpHandler struct {} func (h ImpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // 实现方法 w.Write([]byte("haha")) } func main() { http.Handle("/", ImpHandler{}) http.ListenAndServe(":12345", nil ) }
这里,http消息来了应该是在底层直接调用对应的ServeHTTP。具体是怎么调到的,一层层来看。
首先看下http.Handle做了什么。
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }
可见,这个Handle函数底层封装了一个对象,其实是对此对象DefaultServeMux进行调用。
这类型如下:
type ServeMux struct { mu sync.RWMutex m map[string]muxEntry hosts bool // whether any patterns contain hostnames } type muxEntry struct { explicit bool h Handler pattern string }
可见,http的path和对应的处理handler的关系以muxEntry维护在这个默认的hash表m中。http.Handle传入的两个参数以hash形式保存在内部的全局变量DefaultServeMux中。
到此,只是在http业务层面上将相关信息保存下,最后在http请求来时的ListenAndServe中,才进行连接的处理。
func ListenAndServe(addr string, handler Handler) error { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe() }
同样,ListenAndServe本身只是一个对外接口,内部也有相应对象Server进行封装。前面说过这个方法是处理连接层面的事,那么这个server就是tcp server的一个抽象。
另一方面,这里又传入了一个handler,这是干吗用的?这里传的是nil,后面再看。
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)}) }
可见,这里直接就监听TCP连接了。其中的ln是个Listener接口,代码这样写比较漂亮:
// Multiple goroutines may invoke methods on a Listener simultaneously. type Listener interface { // Accept waits for and returns the next connection to the listener. Accept() (Conn, error) // Close closes the listener. // Any blocked Accept operations will be unblocked and return errors. Close() error // Addr returns the listener‘s network address. Addr() Addr } // 这里实现得比较好,覆盖了一个Accept方法,在其中加入了keepAlived的选项。其他两个方法仍旧使用原listener的 type tcpKeepAliveListener struct { *net.TCPListener // 外层可直接调它的方法不需要指定成员 } func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) { tc, err := ln.AcceptTCP() if err != nil { return } tc.SetKeepAlive(true) tc.SetKeepAlivePeriod(3 * time.Minute) return tc, nil }
继续看Server的连接监听处理:
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 } ////////////////skip for { rw, e := l.Accept() // 取出一个连接,对应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) } }
可见,调用Listener的Accept()后,形成一个抽象的连接,再启单独协程去处理它。
协程内读出对应的数据后,会进行如下调用,此调用将http的业务与底层的tcp连接结合了起来:
serverHandler{c.server}.ServeHTTP(w, w.req)
看下面,最终回调回去了。
// 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) } // 最终回到最开始注册Handle的地方,进行ServeHTTP的调用 func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) { if r.RequestURI == "*" { if r.ProtoAtLeast(1, 1) { w.Header().Set("Connection", "close") } w.WriteHeader(StatusBadRequest) return } h, _ := mux.Handler(r) h.ServeHTTP(w, r) }
最终调用到了上文的DefaultServeMux中来。
以上是http一的基础的结构,下面是一些衍生的用法。
使用HandleFunc + ListenAndServe
func main() { fmt.Println("Hello.") http.HandleFunc("/", func (w http.ResponseWriter, r *http.Request) { w.Write([]byte("haha2")) }) http.ListenAndServe(":12346", nil ) }
其中,func可使用闭包也可不用。
看下面代码:
// HandleFunc registers the handler function for the given pattern. func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { mux.Handle(pattern, HandlerFunc(handler)) } // 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) }
可见,HandlerFunc只是对Handler的封装,下面同样是通过DefaultServeMux来进行。
这里的重点是以下的写法,用一个函数来实现某个接口,虽然这接口底层仍然是调用函数本身,这样就可以直接用函数和之前的接口匹配:
// 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) }
实际上的效果是,明明只写了一个函数func(ResponseWriter, *Request),但其他代码却可以通过golang的隐式接口方式通过另一个你不知道的函数调用你!这里,不知道的函数就是ServeHTTP。
Handle掌握了,这里的HandleFunc就容易了。
更进一步,ServeMux也是可以使用自定义的值。这时,传入http.ListenAndServe的第二个参数就是这个mux。
func NewServeMux() *ServeMux { return new(ServeMux) }
这个ServeMux,本身又是隐式实现了Handler。
再次回到这里,可见最终是调到了ServerMux这里:
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) }
总结下:
http包给外面提供了三个层次的接口,每个层次暴露的东西不一样:
第一层: 只需要关心处理逻辑,直接以HandleFunc实现;
第二层: 以Handle实现,这一层,对外额外暴露了一个Handler接口,需要用户关注一个ServeHTTP的函数;底层仍然是通过DefaultMux来实现。
第三层: 对外暴露了一个ServeMux,处理请求的方法注册到这个ServeMux上,将ServeMux传入。
以上是关于golang的http分析的主要内容,如果未能解决你的问题,请参考以下文章