go http源码分析

作者: GGBond_8488 | 来源:发表于2020-03-14 12:03 被阅读0次

    在Go里你只需以下几行代码就能实现一个http服务是的没错,不需要容器,不需要服务器软件。

    func main() {
        /**
        第一个参数:pattern string,
        第二个参数:handler func(ResponseWriter, *Request)
         */
        http.HandleFunc("/", sayHello)           // 设置访问的路由
        /**
        第一个参数addr:监听地址
        第二个参数handler:通常为空,意味着服务端调用http.DefaultServerMux进行处理,
        而服务端编写的业务逻辑处理程序http.Handle()或http.HandleFunc()默认注入http.DefaultServeMux中
        */
        err := http.ListenAndServe(":8080", nil) //设置监听的端口
        if err != nil {
            log.Fatal("ListenAndServe: ", err)
        }
    

    可以看到,是http.ListenAndServer("8080",nil)这个方法开启了一个http服务,让我们追进去看看,实际上,初始化一个server对象,调用了 server 的 ListenAndServe 方法。
    下面就是Go/Http服务的流程


    想要看懂这张图,先来了解几个结构

    Handler

    任何结构体,只要实现了ServeHTTP方法,这个结构就可以称之为handler对象。ServeMux会使用handler并调用其ServeHTTP方法处理请求并返回响应。
    这个handler其实就是真正的处理函数

    type Handler interface {
        ServeHTTP(ResponseWriter, *Request)
    }
    

    ServeMux

    ServeMux结构中最重要的字段为m,这是一个map,key是一些url模式,value是一个muxEntry结构,后者里定义存储了具体的url模式和handler。
    m就将路由与相应处理函数绑定了

    type ServeMux struct {
        mu    sync.RWMutex
        m     map[string]muxEntry
        es    []muxEntry // slice of entries sorted from longest to shortest.
        hosts bool       // whether any patterns contain hostnames
    }
    
    type muxEntry struct {
        h       Handler
        pattern string
    }
    

    Server

    下面省去了很多部分,我们只关心一下重点的部分
    server结构存储了服务器处理请求常见的字段。其中Handler字段也保留Handler接口。如果Server接口没有提供Handler结构对象,那么会使用DefautServeMux做multiplexer,(可以看到官方就是这么说的)

    type Server struct {
        Addr string
         //Server也实现了Handler接口
        Handler Handler // handler to invoke, http.DefaultServeMux if nil
        // https
        TLSConfig *tls.Config
    
        ReadTimeout time.Duration
        ReadHeaderTimeout time.Duration
        WriteTimeout time.Duration
        IdleTimeout time.Duration
        MaxHeaderBytes int
        mu         sync.Mutex
        listeners  map[*net.Listener]struct{}
        activeConn map[*conn]struct{}
        doneChan   chan struct{}
        onShutdown []func()
    }
    

    HTTP服务

    创建一个http服务,大致需要经历两个过程,首先需要注册路由,即提供url模式和handler函数的映射,其次就是实例化一个server对象,并开启对客户端的监听。

    注册路由

    http包提供了默认的DefaultServeMux作为路由解析器

    // Handle registers the handler for the given pattern
    // in the DefaultServeMux.
    // The documentation for ServeMux explains how patterns are matched.
    func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, 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)
    }
    
    // Handle registers the handler for the given pattern.
    // If a handler already exists for pattern, Handle panics.
    func (mux *ServeMux) Handle(pattern string, handler Handler) {
        mux.mu.Lock()
        defer mux.mu.Unlock()
    
        if pattern == "" {
            panic("http: invalid pattern")
        }
        if handler == nil {
            panic("http: nil handler")
        }
        if _, exist := mux.m[pattern]; exist {
            panic("http: multiple registrations for " + pattern)
        }
    
        if mux.m == nil {
            mux.m = make(map[string]muxEntry)
        }
        e := muxEntry{h: handler, pattern: pattern}
        mux.m[pattern] = e
        if pattern[len(pattern)-1] == '/' {
            mux.es = appendSorted(mux.es, e)
        }
    
        if pattern[0] != '/' {
            mux.hosts = true
        }
    }
    

    这个就是路由与handler的绑定,很好理解

    // HandleFunc registers the handler function for the given pattern.
    func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
        if handler == nil {
            panic("http: nil handler")
        }
        mux.Handle(pattern, HandlerFunc(handler))
    }
    

    HandlerFunc是一个函数类型。同时实现了Handler接口的ServeHTTP方法。使用HandlerFunc类型包装一下路由定义的sayHello函数(类型转换),
    HandlerFunc(handler)
    其目的就是为了让这个函数也实现ServeHTTP方法,即转变成一个handler处理器(函数)。

    // 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)
    }
    

    一旦这样做了,就意味着我们的 sayHello函数也有了ServeHTTP方法。
    此外,ServeMux的Handle方法,将会对pattern和handler函数做一个map映射

    可以看到其实DefaultServeMux就是ServeMux的一个实例,当然我们也可以创建自己的ServeMux,http包也提供了这个方法。
    很多框架其实就是实现了自己的ServeMux

    // NewServeMux allocates and returns a new ServeMux.
    func NewServeMux() *ServeMux { return new(ServeMux) }
    
    // DefaultServeMux is the default ServeMux used by Serve.
    var DefaultServeMux = &defaultServeMux
    
    var defaultServeMux ServeMux
    

    Handle函数的主要目的在于把handler和pattern模式绑定到map[string]muxEntry的map上,其中muxEntry保存了更多pattern和handler的信息,还记得前面讨论的Server结构吗?Server的m字段就是map[string]muxEntry这样一个map。

    此时,pattern和handler的路由注册完成。接下来就是如何开始server的监听,以接收客户端的请求。

    server监听

    func ListenAndServe(addr string, handler Handler) error {
        server := &Server{Addr: addr, Handler: handler}
        return server.ListenAndServe()
    }
    

    这里初始化了一个Server,这里的Handler也提到了,为nil的话会使用默认使用http.DefaultServeMux,当然你也可以自己初始化,而很多框架就是这么干的。

    func (srv *Server) ListenAndServe() error {
    //如果正在关闭服务,就返回一个var ErrServerClosed = errors.New("http: Server closed")
        if srv.shuttingDown() {
            return ErrServerClosed
        }
        addr := srv.Addr
        if addr == "" {
            addr = ":http"
        }
    //开启tcp端口监听
        ln, err := net.Listen("tcp", addr)
        if err != nil {
            return err
        }
    //server服务的
        return srv.Serve(ln)
    }
    

    这里就是开启了一个监听,并把这个监听传给Serve函数,让它做相应的处理。看到这两个函数返回类型都是error

    func (srv *Server) Serve(l net.Listener) error 
    

    监听开启之后,一旦客户端请求到底,go就开启一个协程处理请求,主要逻辑都在serve方法之中。

    serve方法比较长,其主要职能就是,创建一个上下文对象,然后调用Listener的Accept方法用来 获取连接数据并使用newConn方法创建连接对象。最后使用goroutein协程的方式处理连接请求。因为每一个连接都开起了一个协程,请求的上下文都不同,同时又保证了go的高并发。serve也是一个长长的方法:

    func (srv *Server) Serve(l net.Listener) error {
        //测试钩子
        if fn := testHookServerServe; fn != nil {
            fn(srv, l) // call hook with unwrapped listener
        }
    
        origListener := l
    //这里将传入的l封装成onceClose类型,oncloseListener包装一个net.Listener,
    //以保护它免受多个Close调用的影响。
        l = &onceCloseListener{Listener: l}
        defer l.Close()
    
        if err := srv.setupHTTP2_Serve(); err != nil {
            return err
        }
    
        if !srv.trackListener(&l, true) {
            return ErrServerClosed
        }
        defer srv.trackListener(&l, false)
     //这里创建了一个上下文,有兴趣的也可以去看看Context的源码分析,主要用于控制goroutine
    //BaseContext可选地指定一个函数,该函数返回此服务器上传入请求的基本上下文。
    //提供的Listener是即将开始接受请求的特定Listener。如果BaseContext为nil,
    //则默认值为context.Background()。如果非nil,则为 必须返回非nil上下文。
        baseCtx := context.Background()
        if srv.BaseContext != nil {
            baseCtx = srv.BaseContext(origListener)
            if baseCtx == nil {
                panic("BaseContext returned a nil context")
            }
        }
    
        var tempDelay time.Duration // how long to sleep on accept failure
        //写入一个值
        //ServerContextKey = &contextKey{"http-server"}
    //ServerContextKey是上下文key。 
    //可以在带有Context.Value的HTTP处理程序中使用它来访问启动处理程序的服务器。 
    //关联的值将为* Server类型。
        ctx := context.WithValue(baseCtx, ServerContextKey, srv)
        for {
            //从l里一直接收请求
           // Accept waits for and returns the next connection to the listener.
            rw, err := l.Accept()
            if err != nil {
                select {
                //外部主动调用close方法
                case <-srv.getDoneChan():
                    return ErrServerClosed
                default:
                }
                //尝试等待,重新尝试
                if ne, ok := err.(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", err, tempDelay)
                    time.Sleep(tempDelay)
                    continue
                }
                return err
            }
            connCtx := ctx
    //ConnContext可选地指定一个函数,该函数修改用于新连接的上下文c。 
    //提供的ctx派生自基本上下文,并且具有ServerContextKeyvalue。
            if cc := srv.ConnContext; cc != nil {
                connCtx = cc(connCtx, rw)
                if connCtx == nil {
                    panic("ConnContext returned nil")
                }
            }
    //重置时延并创建新连接
            tempDelay = 0
            c := srv.newConn(rw)
            c.setState(c.rwc, StateNew) // before Serve can return
            go c.serve(connCtx)
        }
    }
    
    

    再来看看serve函数

    // Serve a new connection.
    func (c *conn) serve(ctx context.Context) {
        //获取远程地址
        c.remoteAddr = c.rwc.RemoteAddr().String()
        //保存本地地址
        ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
        //defer收尾工作
        defer func() {
            if err := recover(); err != nil && err != ErrAbortHandler {
                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 {
                // If the handshake failed due to the client not speaking
                // TLS, assume they're speaking plaintext HTTP and write a
                // 400 response on the TLS conn's underlying net.Conn.
                if re, ok := err.(tls.RecordHeaderError); ok && re.Conn != nil && tlsRecordHeaderLooksLikeHTTP(re.RecordHeader) {
                    io.WriteString(re.Conn, "HTTP/1.0 400 Bad Request\r\n\r\nClient sent an HTTP request to an HTTPS server.\n")
                    re.Conn.Close()
                    return
                }
                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; validNextProto(proto) {
                if fn := c.server.TLSNextProto[proto]; fn != nil {
                    h := initALPNRequest{ctx, tlsConn, serverHandler{c.server}}
                    fn(c.server, tlsConn, h)
                }
                return
            }
        }
    
        // HTTP/1.x from here on.
        //封装取消
        ctx, cancelCtx := context.WithCancel(ctx)
        c.cancelCtx = cancelCtx
        defer cancelCtx()
        //把连接封装到读的结构体
        c.r = &connReader{conn: c}
        //缓冲IO
        c.bufr = newBufioReader(c.r)
        //
        c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)
    
        //读取多次请求的包
        for {
            //w是一个Response
            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)
            }
            //这里是错误处理就略过了
    
            // 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.
            // But we're not going to implement HTTP pipelining because it
            // was never deployed in the wild and the answer is HTTP/2.
    
            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)
            c.curReq.Store((*response)(nil))
    
            if !w.conn.server.doKeepAlives() {
                // We're in shutdown mode. We might've replied
                // to the user without "Connection: close" and
                // they might think they can send another
                // request, but such is life with HTTP/1.1.
                return
            }
    
            if d := c.server.idleTimeout(); d != 0 {
                c.rwc.SetReadDeadline(time.Now().Add(d))
                if _, err := c.bufr.Peek(4); err != nil {
                    return
                }
            }
            c.rwc.SetReadDeadline(time.Time{})
        }
    }
    

    尽管serve很长,里面的结构和逻辑还是很清晰的,使用defer定义了函数退出时,连接关闭相关的处理。然后就是读取连接的网络数据,并处理读取完毕时候的状态。接下来就是调用serverHandler{c.server}.ServeHTTP(w, w.req)方法处理请求了。最后就是请求处理完毕的逻辑。serverHandler是一个重要的结构,它近有一个字段,即Server结构,同时它也实现了Handler接口方法ServeHTTP,并在该接口方法中做了一个重要的事情,初始化multiplexer路由多路复用器。如果server对象没有指定Handler,则使用默认的DefaultServeMux作为路由Multiplexer。并调用初始化Handler的ServeHTTP方法。

    serverHandler{c.server}.ServeHTTP(w, w.req)

    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)
    }
    

    这里DefaultServeMux的ServeHTTP方法其实也是定义在ServeMux结构中的,相关代码如下:

    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)
    }
    

    Handler函数如下
    Handler返回用于给定请求的处理程序,r.Method,r.Host和r.URL.Path。 它总是返回一个非nilhandler。 如果路径的格式不规范,则该handler将是内部生成的处理程序,该handler将重定向到规范路径。 如果主机包含端口,则在匹配handler时将忽略该端口。
    //
    // CONNECT请求的路径和主机保持不变。
    //
    //handler还会返回与请求匹配的注册pattern,如果是内部生成的重定向,则将返回在重定向之后匹配的pattern。
    //
    //如果没有适用于该请求的注册handler,则处理程序返回一个“找不到页面”handler和一个空pattern。

    func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
    
        // CONNECT requests are not canonicalized.
        if r.Method == "CONNECT" {
            // If r.URL.Path is /tree and its handler is not registered,
            // the /tree -> /tree/ redirect applies to CONNECT requests
            // but the path canonicalization does not.
            if u, ok := mux.redirectToPathSlash(r.URL.Host, r.URL.Path, r.URL); ok {
                return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
            }
    
            return mux.handler(r.Host, r.URL.Path)
        }
    
        // All other requests have any port stripped and path cleaned
        // before passing to mux.handler.
        host := stripHostPort(r.Host)
        path := cleanPath(r.URL.Path)
    
        // If the given path is /tree and its handler is not registered,
        // redirect for /tree/.
        if u, ok := mux.redirectToPathSlash(host, path, r.URL); ok {
            return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
        }
    
        if path != r.URL.Path {
            _, pattern = mux.handler(host, path)
            url := *r.URL
            url.Path = path
            return RedirectHandler(url.String(), StatusMovedPermanently), pattern
        }
    
        return mux.handler(host, r.URL.Path)
    }
    
    func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
        mux.mu.RLock()
        defer mux.mu.RUnlock()
    
        // Host-specific pattern takes precedence over generic ones
        if mux.hosts {
            h, pattern = mux.match(host + path)
        }
        if h == nil {
            h, pattern = mux.match(path)
        }
        if h == nil {
            h, pattern = NotFoundHandler(), ""
        }
        return
    }
    
    func (mux *ServeMux) match(path string) (h Handler, pattern string) {
        // Check for exact match first.
        v, ok := mux.m[path]
        if ok {
            return v.h, v.pattern
        }
    
        // Check for longest valid match.  mux.es contains all patterns
        // that end in / sorted from longest to shortest.
        for _, e := range mux.es {
            if strings.HasPrefix(path, e.pattern) {
                return e.h, e.pattern
            }
        }
        return nil, ""
    }
    
    h, _ := mux.Handler(r)
    h.ServeHTTP(w, r)
    

    h, _ := mux.Handler(r)在找到对应的Handler之后 就执行他的ServeHTTP方法,也就是我们定义的真正的handler处理业务逻辑的方法。

    结束

    至此,整个http服务的流程已经讲清楚了,当然里面还有很多细节没说,比如reponse和request的结构,这些就留给大家去看了。

    相关文章

      网友评论

        本文标题:go http源码分析

        本文链接:https://www.haomeiwen.com/subject/jscushtx.html