Go使用net/http标准库(二)源码学习之- http.ListenAndServe()

原文地址,转载请注明出处: https://blog.csdn.net/qq_34021712/article/details/109959201  ©王赛超

目录

  • 一.搭建简单的web服务器
  • 二.分析一下http.ListenAndServe(":8080", nil)这个函数
    • http.ListenAndServe()
    • server.ListenAndServe()
    • Server.Serve()
    • net.Conn
    • http.conn
    • *conn.serve()
    • c.readRequest(ctx)
        • readRequest(b *bufio.Reader, deleteHostHeader bool)
        • shouldClose()
        • readTransfer()
        • parseTransferEncoding()
        • fixLength()
    • finishRequest()
    • startBackgroundRead()
    • backgroundRead()
    • 匹配路由serverHandler{c.server}.ServeHTTP(w, w.req)下章讲

在上一章我们学习了http.HandleFunc()的源码,本章分析http.ListenAndServe(":8080", nil)函数。

一.搭建简单的web服务器

把之前用http包写一个最简单的hello world服务器代码拿过来,如下:

func main() {
    http.HandleFunc("/", HelloServer)
    _ = http.ListenAndServe(":8080", nil)
}

func HelloServer(w http.ResponseWriter, r *http.Request) {
    _, err := w.Write([]byte(`hello world`))
    if err != nil {
        fmt.Println(err)
    }
}

二.分析一下http.ListenAndServe(":8080", nil)这个函数

http.ListenAndServe()

http.ListenAndServe()是整个服务运行的入口,代码如下:

// ListenAndServe listens on the TCP network address addr and then calls
// Serve with handler to handle requests on incoming connections.
// Accepted connections are configured to enable TCP keep-alives.
//
// The handler is typically nil, in which case the DefaultServeMux is used.
//
// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

以上代码创建一个Server结构体,调用该结构体的ListenAndServer方法然后返回。

func上有这么一句话注意:

The handler is typically nil, in which case the DefaultServeMux is used.
Handler通常是nil,此时,会默认使用DefaultServeMux。

http.HandleFunc()中,就是把传进来的patternhandler保存在muxEntry结构中,并且pattern作为key,把muxEntry装入到DefaultServeMuxMap里面。

上面的示例代码中Handlernil,所以使用的就是默认的DefaultServeMux

调用http.ListenAndServe()之后真正起作用的是Server结构体LisntenAndServe()方法,给http.ListenAndServe()传递的参数只是用来创建一个Server结构体实例,Server结构体的定义如下:

// A Server defines parameters for running an HTTP server.
// The zero value for Server is a valid configuration.
type Server struct {
    // 监听的TCP地址 格式为: host:port 如果为空 默认使用 :http 端口为 80
    Addr string

    // 调用的 handler(路由处理器), 设为 nil 表示 http.DefaultServeMux
    Handler Handler // handler to invoke, http.DefaultServeMux if nil

    // 如果服务需要支持https协议 需要相应的配置
    TLSConfig *tls.Config

    // 请求超时时间,包含请求头和请求体。
    ReadTimeout time.Duration

    // 请求头超时时间。读取请求头之后,连接的读取截止日期会被重置.
    // Handler可以再根据 ReadTimeout 判断Body的超时时间。
    // 如果ReadHeaderTimeout为零,则使用ReadTimeout的值。如果两者都为零,则不会超时。
    ReadHeaderTimeout time.Duration

    // 响应超时时间。 每当读取新请求的Header时,它将重置。 
    // 与ReadTimeout一样,它也不允许处理程序根据每个请求做出决策。
    WriteTimeout time.Duration

    // 启用keep-alives时,保持连接等待下一个请求的最长时间。
    // 如果IdleTimeout为零,则使用ReadTimeout的值。如果两者都为零,则不会超时。
    IdleTimeout time.Duration

    // 解析请求头的key和value(包括请求行)时将读取的最大字节数。
    // 它不限制请求Body大小。如果为零,则使用DefaultMaxHeaderBytes。
    MaxHeaderBytes int

    // 当 '应用层协议协商 (NPN/ALPN)' 时发生协议升级时,TLSNextProto 需要指定可选的 function 去接管 TLS 连接
    // map的key为: 协商的协议名称。
    // Handler参数应用于处理HTTP请求,如果尚未设置,它将初始化请求的TLS和RemoteAddr。 
    // 函数返回时,连接将自动关闭。 如果TLSNextProto不为nil,则不会自动启用HTTP/2支持。
    TLSNextProto map[string]func(*Server, *tls.Conn, Handler)

    //  当客户端连接状态改变时调用的回调函数。有关详细信息,请参阅ConnState类型常量。
    ConnState func(net.Conn, ConnState)

    // 日志记录对象
    ErrorLog *log.Logger

    // 为来到此服务器的请求指定 context 上下文 ,不设就是 context.Background()
    // 如果BaseContext为nil,则默认值为context.Background()。 
    // 如果为非nil,则它必须返回非nil上下文。
    BaseContext func(net.Listener) context.Context

    // 提供一个函数用于修改新连接的context上下文,提供的ctx是从基上下文派生的,具有ServerContextKey值。
    ConnContext func(ctx context.Context, c net.Conn) context.Context

    // 当服务器关闭时为true
    inShutdown atomicBool // true when when server is in shutdown

    // 关闭 keep-alives
    disableKeepAlives int32     // accessed atomically.
    nextProtoOnce     sync.Once // guards setupHTTP2_* init
    nextProtoErr      error     // result of http2.ConfigureServer if used

    // 互斥锁 保证资源的安全
    mu         sync.Mutex
    // 监听socket表
    listeners  map[*net.Listener]struct{}
    // 存活的客户端链接表
    activeConn map[*conn]struct{}
    // 用于通知服务关闭 信道
    doneChan   chan struct{}
    // 注册服务器关闭执行的一些行为
    // 通过 RegisterOnShutdown 注册,在 Shutdown 时调用当中的钩子函数
    onShutdown []func()
}

如果我们不传具体的参数给http.ListenAndServe(),那么它会自动以":http"(等价于":80")和DefaulServeMux作为参数来创建Server结构体实例。

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. After Shutdown or Close,
// the returned error is ErrServerClosed.
func (srv *Server) ListenAndServe() error {
    if srv.shuttingDown() {
        return ErrServerClosed // 如果Server已关闭,直接返回 ErrServerClosed
    }
    addr := srv.Addr
    if addr == "" {
        addr = ":http" // 如果不指定服务器地址信息,默认以":http"作为地址信息
    }
    ln, err := net.Listen("tcp", addr)  // 创建TCPListener,接收客户端的连接请求
    if err != nil {
        return err
    }
    return srv.Serve(ln)  // 调用Server.Serve()函数并返回
}

net.Listen() 方法监听本地的网络地址,network参数可以是 tcptcp4tcp6unix 或者 unixpacketaddress 参数可以用主机名(hostname),但是不建议,因为这样创建的listener(监听器)最多监听主机的一个IP地址。如果 address 参数的 port或者"0",如"127.0.0.1:"或者"[::1]:0",将自动选择一个端口号。

server.ListenAndServe()函数的主要作用就是初始化一个 TCPListener 结构体的实例,然后调用Server.Serve()方法为这个连接提供服务。

Server.Serve()

// 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.
//
// HTTP/2 support is only enabled if the Listener returns *tls.Conn
// connections and they were configured with "h2" in the TLS
// Config.NextProtos.
//
// Serve always returns a non-nil error and closes l.
// After Shutdown or Close, the returned error is ErrServerClosed.
func (srv *Server) Serve(l net.Listener) error {
    // 测试用的钩子函数,一般用不到
    if fn := testHookServerServe; fn != nil {
        fn(srv, l) // call hook with unwrapped listener
    }

    origListener := l
    // onceCloseListener 包装 net.Listener,用于防止多次关闭链接
    // sync.Once.Do(f func()) 能保证once只执行一次
    l = &onceCloseListener{Listener: l}
    // 结束的时候关闭监听socket
    defer l.Close()

    // http2相关的设置
    if err := srv.setupHTTP2_Serve(); err != nil {
        return err
    }

    // 把监听socket添加监听表
    if !srv.trackListener(&l, true) {
        return ErrServerClosed
    }
    // 结束的时候从监听表删除
    defer srv.trackListener(&l, false)

    // 获取一个非 nil 的 上下文。
    baseCtx := context.Background()
    if srv.BaseContext != nil {
        baseCtx = srv.BaseContext(origListener)
        if baseCtx == nil {
            panic("BaseContext returned a nil context")
        }
    }

    // 设置临时过期时间,当accept发生 错误的时候等待一段时间
    var tempDelay time.Duration // how long to sleep on accept failure

    // 把server本身放入上下文,用于参数传递,可以在 其他子协程中 根据 ServerContextKey 获取到 srv
    ctx := context.WithValue(baseCtx, ServerContextKey, srv)
    // 循环监听客户端到来
    for {
        // accept 阻塞等待客户单到来
        rw, err := l.Accept()
        // 如果发生错误后的处理逻辑
        if err != nil {
            // 如果从server.doneChan中读取到内容,代表服务已关闭,返回 ErrServerClosed
            select {
            case <-srv.getDoneChan():
                return ErrServerClosed
            default:
            }
            // 如果 e 是 net.Error, 并且错误是临时性的
            if ne, ok := err.(net.Error); ok && ne.Temporary() {
                // 第一次没接收到数据,睡眠5毫秒
                if tempDelay == 0 {
                    tempDelay = 5 * time.Millisecond
                } else {
                    // 每次睡眠结束,唤醒后还是没接收到数据,睡眠时间加倍
                    tempDelay *= 2
                }
                // 单次睡眠时间上限设定为1秒
                if max := 1 * time.Second; tempDelay > max {
                    tempDelay = max
                }
                // 输出重新等待
                srv.logf("http: Accept error: %v; retrying in %v", err, tempDelay)
                // 休眠一段时间
                time.Sleep(tempDelay)
                continue
            }
            // 如果e不是net.Error,或者不是临时性错误,就返回错误
            return err
        }
        // 如果接收到数据就做如下处理:
        // 如果指定了server的ConnContext,就用它修改连接的context
        connCtx := ctx
        if cc := srv.ConnContext; cc != nil {
            connCtx = cc(connCtx, rw)
            if connCtx == nil {
                panic("ConnContext returned nil")
            }
        }
        // 休眠定时器归零
        tempDelay = 0
        // 使用当前的Conn接口构建新的 conn 实例,它包含了srv服务器和rw连接。
        // conn 实例代表一个 HTTP 连接的服务端,rw是底层的网络连接
        c := srv.newConn(rw)
        // 更新连接状态
        // 第一次更新连接状态会初始化srv.activeConn为一个map[*conn]struct{} 并将活跃连接添加进去。
        // srv.activeConn是一个集合,保存服务器的活跃连接,当连接关闭 或者 被劫持 会从集合中删除。
        // 详细可以往里点看  trackConn函数。
        // 如果 server.ConnState 不为nil 也会在连接状态改变时,执行该回调函数
        c.setState(c.rwc, StateNew) // before Serve can return
        // 启动goroutine处理socket
        go c.serve(connCtx)
    }
}

上面的代码有点多,核心是下面几行代码,简化之后如下:

for {
    rw, e := l.Accept()
    ...
    c, err := srv.newConn(rw)
    c.setState(c.rwc, StateNew)
    go c.serve()
}

死循环,循环内l.Accept()阻塞等待接收监听到的网络连接,一旦有新的连接建立,将返回新的net.Conn实例,然后把服务器实例Server底层连接(net.Conn)封装为一个内部的conn结构体的实例,并将conn连接的状态标志为StateNew,然后开启一个goroutine执行它的serve()方法。这里对每一个连接开启一个goroutine来处理,正是支持并发的体现。

这里注意l.Accept()返回net.Conn为底层TCP连接, 而srv.newConn(rw)返回的http.conn可以理解为HTTP连接

实际上state被维护在Server里,只不过通过conn来调用了。一共有StateNew, StateActive, StateIdle, StateHijacked, StateClosed五个状态。从new开始,当读取了一个字节之后进入active,读取完了并发送response之后,进入idle。终结有两种,主动终结closed以及被接管: Hijack让调用者接管连接,在调用Hijack()后,http server库将不再对该连接进行处理,对于该连接的管理和关闭责任将由调用者接管。

net.Conn

// Conn is a generic stream-oriented network connection.
//
// Multiple goroutines may invoke methods on a Conn simultaneously.
type Conn interface {
    // 读取连接中数据
    Read(b []byte) (n int, err error)

    // 发送数据
    Write(b []byte) (n int, err error)

    // 关闭链接
    Close() error

    // 返回本地连接地址
    LocalAddr() Addr

    // 返回远程连接的地址
    RemoteAddr() Addr

    // 设置与连接关联的读写期限。 
    // 等效于同时调用SetReadDeadline和SetWriteDeadline。 
    // 注意: 截止期限是绝对时间,在该绝对时间之后,I/O操作将失败而不是阻塞。 
    // 超过期限后,可以通过设置将来的期限来刷新连接。
    SetDeadline(t time.Time) error

    // 单独设置读取超时时间
    SetReadDeadline(t time.Time) error

    // 单独设置写超时时间
    SetWriteDeadline(t time.Time) error
}

注意: 这里的参数t是一个未来的时间点,所以每次读或写之前,都要调用SetXXX重新设置超时时间。

net.Conn包含以下两个方法,也意味着实现了io.Reader, io.Writer接口

Read(b []byte) (n int, err error)
Write(b []byte) (n int, err error)

http.conn

server.go 内的conn结构体如下:

// A conn represents the server side of an HTTP connection.
type conn struct {
    // 连接绑定的Server 不可变,不可为nil
    server *Server

    // 用于取消连接的上下文
    cancelCtx context.CancelFunc

    // rwc是底层网络连接。它从不被其他类型包装,并且是分配给CloseNotifier调用方的值。通常类型为*net.TCPConn或*tls.Conn。
    rwc net.Conn

    // remoteAddr 就是 rwc.RemoteAddr().String()。它不在 Listener 接收数据的 goroutine,里同步填充。
    // 它在 (*conn).serve() goroutine 里被立即填充.
    // 这是 Handler 的 (*Request).RemoteAddr 的值。
    remoteAddr string

    // tlsState 是使用 TLS 时 TLS 的连接状态。
    // nil 表示不用 TLS。
    tlsState *tls.ConnectionState

    // werr设置为对rwc的第一个写入错误
    // 通过 checkConnErrorWriter{w} 设置,bufw 写入。
    werr error

    // r是bufr的读取源。它是rwc的包装器,它提供io.LimitedReader-style限制(在读取请求头时)
    // 支持CloseNotifier的功能。参见*connReader文档。
    r *connReader

    //  bufr从r读取 还是前面的reader,加了缓冲
    bufr *bufio.Reader

    // bufw 写入 checkConnErrorWriter{c}, 当发生错误时填充 werr。
    bufw *bufio.Writer

    // lastMethod 是当前连接的最后一个请求的方法。是 POST 请求还是 Get请求
    lastMethod string

    // 当前的请求
    curReq atomic.Value // of *response (which has a Request in it)

    // 当前cnn状态
    curState struct{ atomic uint64 } // packed (unixtime<<8|uint8(ConnState))

    // mu guards hijackedv
    mu sync.Mutex

    // 表示这个连接是否被一个带有Hijacker接口的 Handler hijacked了,主要用于切换协议
    hijackedv bool
}

conn可以理解为一条基于TCPHTTP连接,封装了3个重要的数据结构:
server *Server就是http.Server
rwc net.Conn表示底层连接net.Conn
r *connReader是读取http数据的connReader(从rwc读取数据)。

后续的requestresponse都基于该conn结构体。

*conn.serve()

回到上面的server.Serve()函数,针对每一个conn开启一个新的goroutine执行它的serve()方法。

// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
    // remoteAddr 在这里被直接填充。
    c.remoteAddr = c.rwc.RemoteAddr().String()
    // 把 c.rwc.LocalAddr() 放入上下文,用于参数传递。
    // 可以在 后续流程 或者 其他子协程中 根据 LocalAddrContextKey 获取到。
    ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
    // 方法结束时处理,
    defer func() {
        // recover 是一个内置函数,用于重新获得 panic 协程的控制。
        // 只有在延迟函数的内部,调用 recover 才有用。
        if err := recover(); err != nil && err != ErrAbortHandler {
            const size = 64 << 10
            buf := make([]byte, size)
            buf = buf[:runtime.Stack(buf, false)]
            // 打印 panic 信息
            c.server.logf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf)
        }
        // 如果连接没有被劫持,需要手动关闭。
        // 被劫持的连接无需操作,因为被劫持的连接需要 用户 手动处理。
        if !c.hijacked() {
            c.close()
            c.setState(c.rwc, StateClosed)
        }
    }()

    //如果该连接是tls.Conn  ,常见连接是net.TcpConn 和 tls.Conn
    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.
            // 如果由于客户端未使用TLS而导致握手失败,假定它们使用的是明文HTTP,并在TLS Conn的底层net.conn上编写400响应。
            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()
        // 用于判断是否使用TLS的NPN扩展协商出非http/1.1和http/1.0的上层协议,如果存在则使用server.TLSNextProto处理请求
        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.
    // 返回Context和取消函数用来取消Context
    ctx, cancelCtx := context.WithCancel(ctx)
    // 将取消函数 赋值给 conn.cancelCtx
    c.cancelCtx = cancelCtx
    // 方法结束时调用 取消函数。
    defer cancelCtx()

    // connReader 是一个包裹了 *conn的 io.Reader。赋值给 conn.r
    c.r = &connReader{conn: c}
    // newBufioReader 将 c.r 封装成一个带有缓冲区的字节流bufio.Reader用于读取HTTP的request。
    // read buf长度默认为4096, 底层使用sync.pool提高存取效率。
    c.bufr = newBufioReader(c.r)
    // 初始化bufw用于发送response,缓冲区长度默认为 4<<10=4096。
    c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)

    // 循环处理HTTP请求
    for {
        // c.readRequest(ctx)方法内部调用http.readTransfer函数,如果有读取到Body内容,则设置为http.body类型,没有读到则设置为 NoBody类型
        // w 是一个包含 request 的response实例。
        w, err := c.readRequest(ctx)
        // 判断是否有读取过数据,如果读取过数据则设置TCP状态为active
        // c.readRequest(ctx) 方法内 有对c.r.remain赋值
        if c.r.remain != c.server.initialReadLimitSize() {
            // If we read any bytes off the wire, we're active.
            c.setState(c.rwc, StateActive)
        }
        // 如果读取请求信息时出错,根据错误码,向底层的net.Conn 实例写入错误信息
        if err != nil {
            const errorHeaders = "\r\nContent-Type: text/plain; charset=utf-8\r\nConnection: close\r\n\r\n"

            switch {
            case 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.
                const publicErr = "431 Request Header Fields Too Large"
                fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr)
                // 写回响应并关闭连接。
                c.closeWriteAndWait()
                return

            case isUnsupportedTEError(err):
                // 无法识别请求编码。
                // Respond as per RFC 7230 Section 3.3.1 which says,
                //      A server that receives a request message with a
                //      transfer coding it does not understand SHOULD
                //      respond with 501 (Unimplemented).
                code := StatusNotImplemented

                // We purposefully aren't echoing back the transfer-encoding's value,
                // so as to mitigate the risk of cross side scripting by an attacker.
                fmt.Fprintf(c.rwc, "HTTP/1.1 %d %s%sUnsupported transfer encoding", code, StatusText(code), errorHeaders)
                return

            case isCommonNetReadError(err):
                return // don't reply

            default:
                publicErr := "400 Bad Request"
                if v, ok := err.(badRequestError); ok {
                    publicErr = publicErr + ": " + string(v)
                }

                fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr)
                return
            }
        }

        // Expect 100 Continue support
        req := w.req
        // 如果用户的请求期望 100-continue。
        // 100-continue: http 100-continue用于客户端在发送POST数据给服务器前,征询服务器情况,看服务器是否处理POST的数据.
        // 如果不处理,客户端则不上传POST数据,如果处理,则POST上传数据。在现实应用中,通过在POST大数据时,才会使用100-continue协议。
        // 具体的RFC相关描述: http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html#sec8.2.3
        if req.expectsContinue() {
            // "100-continue"的首部要求http1.1版本以上,且http.body长度不为0
            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}
                w.canWriteContinue.setTrue()
            }
        } else if req.Header.get("Expect") != "" {
            // 非"100-continue"但首部包含"Expect"字段的请求为非法请求
            w.sendExpectationFailed()
            return
        }

        // curReq保存了当前的 response,当前代码中主要用于在读失败后调用response中的closeNotifyCh传递信号,此时连接断开。
        c.curReq.Store(w)

        // 判断是否还能从 body 读取到数据,true 表示能继续读 (未到 io.EOF)
        // 主要是为了支持管线化,处理当前请求时可能还在接收请求
        if requestBodyRemains(req.Body) {
            // 如果Body是http.body类型registerOnHitEOF注册的就是遇到io.EOF时执行的函数http.body.onHitEOF 也就是传入的 startBackgroundRead。
            registerOnHitEOF(req.Body, w.conn.r.startBackgroundRead)
        } else {
            // 如果已经读取完请求头和请求体 调用下面函数在新的goroutine中阻塞等待数据的到来,通知 finishRequest
            w.conn.r.startBackgroundRead()
        }

        // 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.
        // HTTP 不能同时有多个活跃的请求.
        // 服务器回复了一个请求后才能读取下一个请求.
        // 所以我们可以在这个 goroutine 里执行 handler
        // serverHandler只是一个包装,这句实际上调用的是c.server.Handler.ServeHTTP()。
        // 而在前面讲到的server的初始化中,Handler就是DefaultServeMux或者用户指定的ServeMux
        // 我们称之为路由器。在路由器中,根据用户定义路由规则,来具体调用用户的业务逻辑方法.
        serverHandler{c.server}.ServeHTTP(w, w.req)
        // 调用上下文的取消函数
        w.cancelCtx()
        // 如果连接已经被劫持,直接return
        if c.hijacked() {
            return
        }
        // 将处理的请求写入输出缓冲区将处理完成数据返回给客户端
        // 1. 刷掉bufio.writer里的数据
        // 2. 关闭chunkWriter写入流
        // 3. 刷掉conn缓冲流里的数据
        // 4. 关闭tcp连接
        w.finishRequest()
        // 判断是否需要重用底层TCP连接,即是否退出本函数的for循环,退出for循环将断开连接
        if !w.shouldReuseConnection() {
            // 如果连接不允许重用
            // requestBodyLimitHit 在 requestTooLarge 函数中设置,当此值为true,停止读取后续的 request 和输入
            // closedRequestBodyEarly 表示连接之前是否已关闭
            if w.requestBodyLimitHit || w.closedRequestBodyEarly() {
                c.closeWriteAndWait()
            }
            return
        }
        // 重用连接,设置底层状态为idle
        // StateIdle 表示此连接已处理完一个 request 并处于 keep-alive 状态,等待后续 request
        c.setState(c.rwc, StateIdle)
        // 将当前请求置为空
        c.curReq.Store((*response)(nil))

        // 如果没有通过SetKeepAlivesEnabled设置HTTP keepalive或底层连接已经通过如Server.Close关闭,则直接退出
        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
        }

        // 如果设置了idle状态超时时间,则调用SetReadDeadline设置底层连接deadline,并调用bufr.Peek等待请求
        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{})
    }
}

这个conn.serve()代码很长,核心是下面几行代码,简化之后如下:

for{
  w, err := c.readRequest(ctx)
  ...
  serverHandler{c.server}.ServeHTTP(w, w.req)
  ...
  w.finishRequest()
}

看到这个ServeHTTP()函数是不是有点熟悉?没错,就是serverHandler{c.server}.ServeHTTP(w, w.req)这行代码会去匹配在http.HandleFunc()中注册的路由,找到对应的处理函数,执行我们写的业务逻辑,大家先知道就好了,下一章再讲。

ServeHTTP()参数是两个高度封装的response对象和Request对象,response是私有类型,暴露在外的是ResponseWriter 后续响应客户端都是基于ResponseWriter

c.readRequest(ctx)

封装Requestresponse的逻辑代码如下:

// Read next request from connection.
func (c *conn) readRequest(ctx context.Context) (w *response, err error) {
    // 如果连接被劫持, 直接返回 ErrHijacked
    if c.hijacked() {
        return nil, ErrHijacked
    }

    var (
        wholeReqDeadline time.Time // or zero if none
        hdrDeadline      time.Time // or zero if none
    )
    t0 := time.Now()
    // 设置读取请求头超时时间
    if d := c.server.readHeaderTimeout(); d != 0 {
        hdrDeadline = t0.Add(d)
    }
    // 设置读取整个请求头+请求体超时时间
    if d := c.server.ReadTimeout; d != 0 {
        wholeReqDeadline = t0.Add(d)
    }
    // 设置底层TCP连接读超时时间
    c.rwc.SetReadDeadline(hdrDeadline)
    if d := c.server.WriteTimeout; d != 0 {
        // 通过defer设置TCP写超时时间,本函数主要处理读请求,在本函数处理完request之后再设置写超时时间
        defer func() {
            c.rwc.SetWriteDeadline(time.Now().Add(d))
        }()
    }

    // 设置读取请求的最大字节数,为DefaultMaxHeaderBytes+4096=1052672,用于防止超大报文攻击
    c.r.setReadLimit(c.server.initialReadLimitSize())
    // 处理老客户端
    if c.lastMethod == "POST" {
        // RFC 7230 section 3 tolerance for old buggy clients.
        // https://tools.ietf.org/html/rfc7230#section-3.5
        // HTTP响应头拆分(CRLF注入)
        peek, _ := c.bufr.Peek(4) // ReadRequest will get err below
        c.bufr.Discard(numLeadingCRorLF(peek))
    }
    // 从bufr读取request,并返回Request结构体格式
    req, err := readRequest(c.bufr, keepHostHeader)
    if err != nil {
        // 如果读取的报文超过限制,则返回错误
        if c.r.hitReadLimit() {
            return nil, errTooLarge
        }
        return nil, err
    }
    
    // 判断是否是go服务所支持的HTTP/1.x的请求
    if !http1ServerSupportsRequest(req) {
        return nil, badRequestError("unsupported protocol version")
    }

    // 更新最后一次的方法类型
    c.lastMethod = req.Method
    // 设置读取请求的最大字节数为 maxInt64
    c.r.setInfiniteReadLimit()

    // 从 header 中获取 hosts
    hosts, haveHost := req.Header["Host"]
    // 判断是否使用 HTTP/2.0 通信
    // 首先客户端会发一个字符串(PRI * HTTP/2.0)到服务端,表示即将要通过HTTP/2来通信
    isH2Upgrade := req.isH2Upgrade()
    // 如果http协议版本大于1.1 并且 请求头不包含 hosts 并且不是 http2.0 并且 请求的方法类型不是  CONNECT 返回错误
    // Method == "CONNECT" 类似于我们常使用的 POST GET ,http 1.1定义了8种方法,connect为其中之一
    if req.ProtoAtLeast(1, 1) && (!haveHost || len(hosts) == 0) && !isH2Upgrade && req.Method != "CONNECT" {
        return nil, badRequestError("missing required Host header")
    }
    // 如果有多个Host 请求头字段
    if len(hosts) > 1 {
        return nil, badRequestError("too many Host headers")
    }
    // Host请求头字段格式不正确
    if len(hosts) == 1 && !httpguts.ValidHostHeader(hosts[0]) {
        return nil, badRequestError("malformed Host header")
    }
    // 判断请求头字段值是否有非法字符
    for k, vv := range req.Header {
        if !httpguts.ValidHeaderFieldName(k) {
            return nil, badRequestError("invalid header name")
        }
        for _, v := range vv {
            if !httpguts.ValidHeaderFieldValue(v) {
                return nil, badRequestError("invalid header value")
            }
        }
    }
    // 把Host移除
    delete(req.Header, "Host")

    ctx, cancelCtx := context.WithCancel(ctx)
    req.ctx = ctx
    req.RemoteAddr = c.remoteAddr
    req.TLS = c.tlsState
    if body, ok := req.Body.(*body); ok {
        body.doEarlyClose = true
    }

    // Adjust the read deadline if necessary.
    // 设置底层TCP连接读取整个请求的超时时间
    if !hdrDeadline.Equal(wholeReqDeadline) {
        c.rwc.SetReadDeadline(wholeReqDeadline)
    }

    w = &response{
        conn:          c, // 当前http连接
        cancelCtx:     cancelCtx,
        req:           req, // 当前请求对应的request对象
        reqBody:       req.Body,
        handlerHeader: make(Header),
        contentLength: -1,
        closeNotifyCh: make(chan bool, 1),

        // We populate these ahead of time so we're not
        // reading from req.Header after their Handler starts
        // and maybe mutates it (Issue 14940)
        wants10KeepAlive: req.wantsHttp10KeepAlive(),
        wantsClose:       req.wantsClose(),
    }
    if isH2Upgrade {
        // 在响应后关闭连接
        w.closeAfterReply = true
    }
    // w.cw.res中保存了response的信息,而response中又保存了底层连接conn,后续将通过w.cw.res.conn写数据
    w.cw.res = w
    // 创建2048字节的写bufio,用于发送response
    w.w = newBufioWriterSize(&w.cw, bufferBeforeChunkingSize)
    return w, nil
}
readRequest(b *bufio.Reader, deleteHostHeader bool)
func readRequest(b *bufio.Reader, deleteHostHeader bool) (req *Request, err error) {
    // 封装为textproto.Reader,该结构体实现了读取HTTP的相关方法
    tp := newTextprotoReader(b)
    // 初始化一个Request结构体,该函数后续工作就是填充该变量并返回
    req = new(Request)

    // First line: GET /index.html HTTP/1.0
    // 读取第一行 拿到 GET /index.html HTTP/1.0
    var s string
    // ReadLine会调用textproto.(*Reader).readLineSlice->bufio.(*Reader).ReadLine->
    // bufio.(*Reader).ReadSlic->bufio.(*Reader).fill->http.(*connReader).Read>读取HTTP的请求并填充b.buf,并返回以"\n"作为
    // 分隔符的首行字符串
    if s, err = tp.ReadLine(); err != nil {
        return nil, err
    }
    defer func() {
        // putTextprotoReader函数使用sync.pool来保存textproto.Reader变量,通过重用内存来提升在大量HTTP请求下执行效率。
        // 对应函数首部的newTextprotoReader
        putTextprotoReader(tp)
        if err == io.EOF {
            err = io.ErrUnexpectedEOF
        }
    }()

    var ok bool
    // 解析请求方法 例如:Get,请求URL,例如 /test, 请求协议 HTTP/1.1
    req.Method, req.RequestURI, req.Proto, ok = parseRequestLine(s)
    if !ok {
        return nil, badStringError("malformed HTTP request", s)
    }
    // 判断方法是否包含非法字符
    if !validMethod(req.Method) {
        return nil, badStringError("invalid method", req.Method)
    }
    // 获取请求路径,如HTTP请求为"http://127.0.0.1:8000/test"时,rawurl为"/test"
    rawurl := req.RequestURI
    // 解析HTTP协议版本
    if req.ProtoMajor, req.ProtoMinor, ok = ParseHTTPVersion(req.Proto); !ok {
        return nil, badStringError("malformed HTTP version", req.Proto)
    }

    // CONNECT requests are used two different ways, and neither uses a full URL:
    // The standard use is to tunnel HTTPS through an HTTP proxy.
    // It looks like "CONNECT www.google.com:443 HTTP/1.1", and the parameter is
    // just the authority section of a URL. This information should go in req.URL.Host.
    //
    // The net/rpc package also uses CONNECT, but there the parameter is a path
    // that starts with a slash. It can be parsed with the regular URL parser,
    // and the path will end up in req.URL.Path, where it needs to be in order for
    // RPC to work.
    // 处理代理场景,使用"CONNECT"与代理建立连接时会使用完整的URL(带host)
    // strings.HasPrefix()函数用来检测字符串是否以指定的前缀开头。
    justAuthority := req.Method == "CONNECT" && !strings.HasPrefix(rawurl, "/")
    if justAuthority {
        rawurl = "http://" + rawurl
    }
    // 解析 rawurl 为URL 可以知道 请求的path  path上是否带参数。参数是什么 等等。
    if req.URL, err = url.ParseRequestURI(rawurl); err != nil {
        return nil, err
    }

    if justAuthority {
        // Strip the bogus "http://" back off.
        // 如果 Method == "CONNECT" scheme 被省略
        req.URL.Scheme = ""
    }

    // Subsequent lines: Key: value.
    // 解析请求头的key:value 例如: Host 、User-Agent、Content-Type等
    mimeHeader, err := tp.ReadMIMEHeader()
    if err != nil {
        return nil, err
    }
    req.Header = Header(mimeHeader)

    // RFC 7230, section 5.3: Must treat
    //  GET /index.html HTTP/1.1
    //  Host: www.google.com
    // and
    //  GET http://www.google.com/index.html HTTP/1.1
    //  Host: doesntmatter
    // the same. In the second case, any Host line is ignored.
    // 如果是上面注释中的第一种需要从req.Header中获取"Host"字段
    req.Host = req.URL.Host
    if req.Host == "" {
        req.Host = req.Header.get("Host")
    }
    // 从请求头中删除Host
    // 为什么需要清理 Host? 
    // 我们应该根据规范清理主机标头:
    //   https://tools.ietf.org/html/rfc7230#section-5.4 (Host = uri-host [ ":" port ]")
    //   https://tools.ietf.org/html/rfc7230#section-2.7 (uri-host -> rfc3986's host)
    //   https://tools.ietf.org/html/rfc3986#section-3.2.2 (definition of host)
    // 但实际上,我们试图避免的是 issue 11206中的情况,在代理上下文中使用的格式错误的Host请求头会创建错误的请求。因此,只需在第一个令人不快的字符处截断就足够了。
    if deleteHostHeader {
        delete(req.Header, "Host")
    }

    // 处理"Cache-Control"
    // 旧式HTTP 1.0 服务器不能使用 Cache-Control 标题。 所以为了向后兼容 HTTP 1.0 服务器,IE使用Pragma:no-cache 标题对 HTTP 提供特殊支持。
    fixPragmaCacheControl(req.Header)

    // 判断是否是长连接,如果是,则保持连接Connection=keep-alives,反之则断开并删除"Connection"请求头。
    req.Close = shouldClose(req.ProtoMajor, req.ProtoMinor, req.Header, false)

    // 读取header body信息, 填充进 req
    err = readTransfer(req, b)
    if err != nil {
        return nil, err
    }

    // 如果是 http/2.0 
    // https://tools.ietf.org/html/rfc7540#section-3.5
    if req.isH2Upgrade() {
        // Because it's neither chunked, nor declared:
        req.ContentLength = -1

        // We want to give handlers a chance to hijack the
        // connection, but we need to prevent the Server from
        // dealing with the connection further if it's not
        // hijacked. Set Close to ensure that:
        req.Close = true
    }
    return req, nil
}
shouldClose()
func shouldClose(major, minor int, header Header, removeCloseHeader bool) bool {
    // HTTP/1.x以下不支持"connection"指定长连接
    if major < 1 {
        return true
    }
    
    conv := header["Connection"]
    // 如果请求头中包含"Connection: close"则断开连接
    hasClose := httpguts.HeaderValuesContainsToken(conv, "close")
    // 使用HTTP/1.0时,如果包含"Connection: close"或不包含"Connection: keep-alive",则使用短连接;
    // HTTP/1.1中不指定"Connection",默认使用长连接
    if major == 1 && minor == 0 {
        return hasClose || !httpguts.HeaderValuesContainsToken(conv, "keep-alive")
    }

    // 如果不使用长连接,则需要删除首部中的Connection字段。
    // https://tools.ietf.org/html/rfc2616#section-14.10
    if hasClose && removeCloseHeader {
        header.Del("Connection")
    }

    return hasClose
}
readTransfer()
// msg is *Request or *Response.
func readTransfer(msg interface{}, r *bufio.Reader) (err error) {
    t := &transferReader{RequestMethod: "GET"}

    // Unify input
    isResponse := false
    switch rr := msg.(type) {
    // msg为响应信息时
    case *Response:
        t.Header = rr.Header
        t.StatusCode = rr.StatusCode
        t.ProtoMajor = rr.ProtoMajor
        t.ProtoMinor = rr.ProtoMinor
        t.Close = shouldClose(t.ProtoMajor, t.ProtoMinor, t.Header, true)
        isResponse = true
        if rr.Request != nil {
            t.RequestMethod = rr.Request.Method
        }
    // msg为请求信息时
    case *Request:
        t.Header = rr.Header
        t.RequestMethod = rr.Method
        t.ProtoMajor = rr.ProtoMajor
        t.ProtoMinor = rr.ProtoMinor
        // Transfer semantics for Requests are exactly like those for
        // Responses with status code 200, responding to a GET method
        t.StatusCode = 200
        t.Close = rr.Close
    default:
        panic("unexpected type")
    }

    // Default to HTTP/1.1
    // 如果没有传入http协议  默认为HTTP/1.1
    if t.ProtoMajor == 0 && t.ProtoMinor == 0 {
        t.ProtoMajor, t.ProtoMinor = 1, 1
    }

    // Transfer-Encoding: chunked, and overriding Content-Length.
    // 处理 消息头 Transfer-Encoding 
    // 参考博客:
    if err := t.parseTransferEncoding(); err != nil {
        return err
    }

    // 处理 消息头 Content-Length 返回的是真正的消息体长度
    realLength, err := fixLength(isResponse, t.StatusCode, t.RequestMethod, t.Header, t.Chunked)
    if err != nil {
        return err
    }
    // 如果是响应信息 并且对应的请求方法为HEAD。
    if isResponse && t.RequestMethod == "HEAD" {
        // 如果响应头包含Content-Length字段,则将此作为响应的ContentLength的值 
        if n, err := parseContentLength(t.Header.get("Content-Length")); err != nil {
            return err
        } else {
            t.ContentLength = n
        }
    } else {
        t.ContentLength = realLength
    }

    // Trailer
    // 处理 消息头 Trailer
    t.Trailer, err = fixTrailer(t.Header, t.Chunked)
    if err != nil {
        return err
    }

    // If there is no Content-Length or chunked Transfer-Encoding on a *Response
    // and the status is not 1xx, 204 or 304, then the body is unbounded.
    // See RFC 7230, section 3.3.
    // 含body但不是chunked且不包含length字段的响应称为unbounded(无法衡量长度的消息)消息,根据RFC 7230会被关闭
    switch msg.(type) {
    case *Response:
        if realLength == -1 && !t.Chunked && bodyAllowedForStatus(t.StatusCode) {
            // Unbounded body.
            t.Close = true
        }
    }

    // Prepare body reader. ContentLength < 0 means chunked encoding
    // or close connection when finished, since multipart is not supported yet
    // 消息体Body赋值
    switch {
    case t.Chunked:
        // 如果请求为HEAD或响应状态码为1xx, 204 or 304,则消息不包含有消息体
        if noResponseBodyExpected(t.RequestMethod) || !bodyAllowedForStatus(t.StatusCode) {
            t.Body = NoBody
        } else {
            // 下面会创建chunkedReader
            t.Body = &body{src: internal.NewChunkedReader(r), hdr: msg, r: r, closing: t.Close}
        }
    case realLength == 0:
        // 没有消息体
        t.Body = NoBody
    case realLength > 0:
        // 有读取到消息体
        t.Body = &body{src: io.LimitReader(r, realLength), closing: t.Close}
    default:
        // realLength < 0, i.e. "Content-Length" not mentioned in header
        if t.Close {
            // 此处对于消息有效载体unbounded场景,断开底层连接
            // Close semantics (i.e. HTTP/1.0)
            t.Body = &body{src: r, closing: t.Close}
        } else {
            // Persistent connection (i.e. HTTP/1.1)
            t.Body = NoBody
        }
    }

    // Unify output
    // 给消息结构体赋值并通过指针返回
    switch rr := msg.(type) {
    case *Request:
        rr.Body = t.Body
        rr.ContentLength = t.ContentLength
        if t.Chunked {
            rr.TransferEncoding = []string{"chunked"}
        }
        rr.Close = t.Close
        rr.Trailer = t.Trailer
    case *Response:
        rr.Body = t.Body
        rr.ContentLength = t.ContentLength
        if t.Chunked {
            rr.TransferEncoding = []string{"chunked"}
        }
        rr.Close = t.Close
        rr.Trailer = t.Trailer
    }

    return nil
}
parseTransferEncoding()
// parseTransferEncoding sets t.Chunked based on the Transfer-Encoding header.
func (t *transferReader) parseTransferEncoding() error {
    // 如果消息头中不含有 Transfer-Encoding 直接 return
    raw, present := t.Header["Transfer-Encoding"]
    if !present {
        return nil
    }
    // 为什么移除 Transfer-Encoding
    // https://tools.ietf.org/html/rfc7540#section-8.1.2.2
    delete(t.Header, "Transfer-Encoding")

    // Issue 12785; ignore Transfer-Encoding on HTTP/1.0 requests.
    // HTTP/1.0不处理该请求头
    if !t.protoAtLeast(1, 1) {
        return nil
    }

    // Like nginx, we only support a single Transfer-Encoding header field, and
    // only if set to "chunked". This is one of the most security sensitive
    // surfaces in HTTP/1.1 due to the risk of request smuggling, so we keep it
    // strict and simple.
    // 和nginx一样,我们只支持一个Transfer-Encoding消息头字段,而且只有在设置为"Chunked"时才支持。
    // 为了防止 http请求走私(HTTP Request Smuggling) 因此对其进行了严格的限制
    if len(raw) != 1 {
        return &unsupportedTEError{fmt.Sprintf("too many transfer encodings: %q", raw)}
    }
    if strings.ToLower(textproto.TrimString(raw[0])) != "chunked" {
        return &unsupportedTEError{fmt.Sprintf("unsupported transfer encoding: %q", raw[0])}
    }

    // RFC 7230 3.3.2 says "A sender MUST NOT send a Content-Length header field
    // in any message that contains a Transfer-Encoding header field."
    //
    // but also: "If a message is received with both a Transfer-Encoding and a
    // Content-Length header field, the Transfer-Encoding overrides the
    // Content-Length. Such a message might indicate an attempt to perform
    // request smuggling (Section 9.5) or response splitting (Section 9.4) and
    // ought to be handled as an error. A sender MUST remove the received
    // Content-Length field prior to forwarding such a message downstream."
    //
    // Reportedly, these appear in the wild.
    // 不得在任何包含Transfer-Encoding消息头的消息中 带有 Content-Length消息头字段。
    // 如果同时接收到带有Transfer-Encoding和Content-Length消息头的消息,则Transfer-Encoding将覆盖Content-Length。
    // 此类消息可能尝试执行 请求走私 或 响应拆分 ,应将其作为错误处理。发送方在向下游转发此类消息之前,必须删除接收到的Content-Length字段
    // 在此处删除 Content-Length ,不在fixLength函数中处理
    delete(t.Header, "Content-Length")

    t.Chunked = true
    return nil
}
fixLength()
// Determine the expected body length, using RFC 7230 Section 3.3. This
// function is not a method, because ultimately it should be shared by
// ReadResponse and ReadRequest.
func fixLength(isResponse bool, status int, requestMethod string, header Header, chunked bool) (int64, error) {
    // 判断是处理请求信息 还是 响应信息
    isRequest := !isResponse
    // 从消息头中获取 Content-Length
    contentLens := header["Content-Length"]

    // Hardening against HTTP request smuggling
    // 判断 Content-Length 是否有多个
    if len(contentLens) > 1 {
        // Per RFC 7230 Section 3.3.2, prevent multiple
        // Content-Length headers if they differ in value.
        // If there are dups of the value, remove the dups.
        // See Issue 16490.
        first := textproto.TrimString(contentLens[0])
        // 如果一个Content-Length包含多个不同的value,则认为该请求无效
        for _, ct := range contentLens[1:] {
            if first != textproto.TrimString(ct) {
                return 0, fmt.Errorf("http: message cannot contain multiple Content-Length headers; got %q", contentLens)
            }
        }

        // deduplicate Content-Length
        // 如果一个Content-Length包含多个相同的value,则仅保留一个
        header.Del("Content-Length")
        header.Add("Content-Length", first)

        contentLens = header["Content-Length"]
    }

    // Logic based on response type or status
    // 处理HEAD请求
    if noResponseBodyExpected(requestMethod) {
        // For HTTP requests, as part of hardening against request
        // smuggling (RFC 7230), don't allow a Content-Length header for
        // methods which don't permit bodies. As an exception, allow
        // exactly one Content-Length header if its value is "0".
        // HEAD请求中的Content-Length为0时允许存在该字段
        if isRequest && len(contentLens) > 0 && !(len(contentLens) == 1 && contentLens[0] == "0") {
            return 0, fmt.Errorf("http: method cannot contain a Content-Length; got %q", contentLens)
        }
        return 0, nil
    }
    // 处理状态码为1xx的响应,不包含消息体 一般为响应消息  请求的消息在刚开始赋值的是200
    if status/100 == 1 {
        return 0, nil
    }
    // // 处理状态码为204和304的响应,不包含消息体 一般为响应消息  请求的消息在刚开始赋值的是200
    switch status {
    case 204, 304:
        return 0, nil
    }

    // Logic based on Transfer-Encoding
    // 包含Transfer-Encoding时无法衡量数据长度,以Transfer-Encoding为准,设置返回长度为-1,直接返回
    if chunked {
        return -1, nil
    }

    // Logic based on Content-Length
    var cl string
    if len(contentLens) == 1 {
        // 获取Content-Length字段字符串值
        cl = textproto.TrimString(contentLens[0])
    }
    if cl != "" {
        // 对Content-Length字段的值进行有效性验证,如果有效则返回该值的整型,无效返回错误
        n, err := parseContentLength(cl)
        if err != nil {
            return -1, err
        }
        return n, nil
    }
    // 数值为空,删除该消息头
    header.Del("Content-Length")

    // 如果是请求消息 请求中没有Content-Length且没有Transfer-Encoding字段的请求被认为没有消息体
    if isRequest {
        // RFC 7230 neither explicitly permits nor forbids an
        // entity-body on a GET request so we permit one if
        // declared, but we default to 0 here (not -1 below)
        // if there's no mention of a body.
        // Likewise, all other request methods are assumed to have
        // no body if neither Transfer-Encoding chunked nor a
        // Content-Length are set.
        return 0, nil
    }

    // Body-EOF logic based on other methods (like closing, or chunked coding)
    // 如果为响应消息 返回长度为-1 正常执行后续操作 像 关闭操作 分块传输编码等
    return -1, nil
}

readRequest()函数内就是读取conn中的数据,组装成Requestresponse 其中,rsponse中包含Request。然后在ServeHTTP(w, w.req)中传给我们的处理函数

finishRequest()

函数处理完成之后通过finishRequest()进行最后处理工作,异常处理,资源回收,状态更新等。

func (w *response) finishRequest() {
    // 自己写的handler结束了 设置为true
    w.handlerDone.setTrue()

    // wroteHeader表示是否已经将响应头写入,没有则写入
    if !w.wroteHeader {
        w.WriteHeader(StatusOK)
    }
    // 调用底层连接的write将buf中的数据发送出去
    w.w.Flush()
    // w.w重置并放入sync.pool中,待后续重用
    putBufioWriter(w.w)
    // 主要构造 chunked 的结束符:"0\r\n","\r\n",通过cw.chunking判断是否是chunked编码
    w.cw.close()
    // 发送bufw缓存的数据
    w.conn.bufw.Flush()

    // 用于等待处理未读取完的数据,与connReader.backgroundRead中的cr.cond.Broadcast()对应
    w.conn.r.abortPendingRead()

    // Close the body (regardless of w.closeAfterReply) so we can
    // re-use its bufio.Reader later safely.
    w.reqBody.Close()

    // MultipartForm字段包含multipart form的数据,该字段只有在调用了ParseMultipartForm()之后才有数据
    // HTTP客户端忽略MultipartForm,使用Body。
    if w.req.MultipartForm != nil {
        w.req.MultipartForm.RemoveAll()
    }
}
// shouldReuseConnection reports whether the underlying TCP connection can be reused.
// It must only be called after the handler is done executing.
// TCP连接是否可以重复使用。只有在处理程序执行完毕后才能调用它。
func (w *response) shouldReuseConnection() bool {
    // 表示是否需要在响应之后关闭底层连接。requestTooLarge,isH2Upgrade或包含首部字段"Connection:close"时置位
    if w.closeAfterReply {
        // The request or something set while executing the
        // handler indicated we shouldn't reuse this
        // connection.
        return false
    }

    // 写入数据与"content-length"不匹配,为避免不同步,不重用连接
    if w.req.Method != "HEAD" && w.contentLength != -1 && w.bodyAllowed() && w.contentLength != w.written {
        // Did not write enough. Avoid getting out of sync.
        return false
    }

    // There was some error writing to the underlying connection
    // during the request, so don't re-use this conn.
    // 底层连接出现错误,不可重用
    if w.conn.werr != nil {
        return false
    }

    // 判断是否在读取完数据前执行关闭
    if w.closedRequestBodyEarly() {
        return false
    }

    return true
}
// closeWrite flushes any outstanding data and sends a FIN packet (if
// client is connected via TCP), signalling that we're done. We then
// pause for a bit, hoping the client processes it before any
// subsequent RST.
//
// See https://golang.org/issue/3595
func (c *conn) closeWriteAndWait() {
    // 在关闭写之前将缓冲区中的数据发送出去
    c.finalFlush()
    if tcp, ok := c.rwc.(closeWriter); ok {
        // 关闭写
        tcp.CloseWrite()
    }
    time.Sleep(rstAvoidanceDelay)
}
func (c *conn) finalFlush() {
    // 如果 bufr 中还有数据, 可能是因为其他 重置并重用这部分内存
    // 底层将 bufr 指向了 新的Reader结构体 
    if c.bufr != nil {
        // Steal the bufio.Reader (~4KB worth of memory) and its associated
        // reader for a future connection.
        putBufioReader(c.bufr)
        c.bufr = nil
    }

    if c.bufw != nil {
        // 将缓存区中的数据全部通过底层发送出去
        // b.wr.Write -> checkConnErrorWriter.Write
        c.bufw.Flush()
        // Steal the bufio.Writer (~4KB worth of memory) and its associated
        // writer for a future connection.
        // 重置并重用这部分内存
        // 底层将 bufw 指向了 新的Writer结构体 
        putBufioWriter(c.bufw)
        c.bufw = nil
    }
}

同一个TCP连接可以处理多个请求,当一个请求读完之后,会在后台开启一个新的协程继续等待数据的到来,注意startBackgroundRead()函数。你可能会有疑问?读取完请求数据,还没响应就可以读取新的请求嘛?当读完请求内容,把请求头和请求体等信息封装成一个Request之后,其实再接受新的请求数据也没事,之前的请求已经在Request结构体中有了。读取和响应都是需要先获得锁再进行操作,所以第二个请求响应之前要等第一个请求先响应完成。

都是加锁之后再操作,代码如下:

// Read implements io.Reader.
func (fd *FD) Read(p []byte) (int, error) {
    if err := fd.readLock(); err != nil {
        return 0, err
    }
    defer fd.readUnlock()
    ...
}
// Write implements io.Writer.
func (fd *FD) Write(p []byte) (int, error) {
    if err := fd.writeLock(); err != nil {
        return 0, err
    }
    defer fd.writeUnlock()
    ...
}

startBackgroundRead()

func (cr *connReader) startBackgroundRead() {
    cr.lock()
    defer cr.unlock()
    // 该连接正在被读取 触发panic
    if cr.inRead {
        panic("invalid concurrent Body.Read call")
    }
    // 该连接上是否还有数据
    if cr.hasByte {
        return
    }
    // 设置连接正在读取 
    cr.inRead = true
    // 设置读写超时时间
    cr.conn.rwc.SetReadDeadline(time.Time{})
    // 在新的goroutine中等待数据
    go cr.backgroundRead()
}

backgroundRead()

func (cr *connReader) backgroundRead() {
    // 阻塞等待读取一个字节的数
    n, err := cr.conn.rwc.Read(cr.byteBuf[:])
    cr.lock()
    // 如果存在数据则设置cr.hasByte为true,byteBuf容量为1
    if n == 1 {
        cr.hasByte = true
        // We were past the end of the previous request's body already
        // (since we wouldn't be in a background read otherwise), so
        // this is a pipelined HTTP request. Prior to Go 1.11 we used to
        // send on the CloseNotify channel and cancel the context here,
        // but the behavior was documented as only "may", and we only
        // did that because that's how CloseNotify accidentally behaved
        // in very early Go releases prior to context support. Once we
        // added context support, people used a Handler's
        // Request.Context() and passed it along. Having that context
        // cancel on pipelined HTTP requests caused problems.
        // Fortunately, almost nothing uses HTTP/1.x pipelining.
        // Unfortunately, apt-get does, or sometimes does.
        // New Go 1.11 behavior: don't fire CloseNotify or cancel
        // contexts on pipelined requests. Shouldn't affect people, but
        // fixes cases like Issue 23921. This does mean that a client
        // closing their TCP connection after sending a pipelined
        // request won't cancel the context, but we'll catch that on any
        // write failure (in checkConnErrorWriter.Write).
        // If the server never writes, yes, there are still contrived
        // server & client behaviors where this fails to ever cancel the
        // context, but that's kinda why HTTP/1.x pipelining died
        // anyway.
    }
    // 如果是 net.Error 并且 客户端断开连接 并且超时 忽略错误
    // aborted状态说明请求是客户端断开的
    if ne, ok := err.(net.Error); ok && cr.aborted && ne.Timeout() {
        // Ignore this error. It's the expected error from
        // another goroutine calling abortPendingRead.
        // 忽略该错误。这是另一个Goroutine调用 abortPendingRead 时出现的预期错误。
    } else if err != nil {
        cr.handleReadError(err)
    }
    cr.aborted = false
    cr.inRead = false
    cr.unlock()
    // 唤醒其它等待的线程
    cr.cond.Broadcast()
}

匹配路由serverHandler{c.server}.ServeHTTP(w, w.req)下章讲

到此,简单的梳理了一个http请求的基本的处理流程,但是还差一点serverHandler{c.server}.ServeHTTP(w, w.req)如何匹配路由,找到并执行处理函数还没有讲,我打算在下一章中再进行讲解,本篇幅已经很长了。

通过上面的代码注释可以看到大量的http协议相关的知识,有的我本人并没有深入研究,如有错误请批评指正。后续会专门学习一下上面遇到的一些新知识点。

你可能感兴趣的:(go,web基础,go,go,go,web,net/http)