美文网首页
go net/http 源码浅析

go net/http 源码浅析

作者: 竹羔 | 来源:发表于2020-06-28 16:43 被阅读0次

基于go 1.13版本

在分析源码前,要知道

在 http 服务器上进行了如下工作:

  • 客户端通过 TCP/IP 协议建立与服务器的 TCP 连接

  • 客户端向服务器发送 HTTP 协议请求报文,请求获得服务器资源

  • 服务器解析接收到的 HTTP 协议请求报文,并根据报文内容处理相关的数据,然后把请求的资源通过 HTTP 协议响应报文发送回给客户端

  • 客户端与服务器断开,由客户端的浏览器解析并渲染返回的 HTML 文档,并把网页显示在屏幕上

源码分析

在pkgdoc的标准库文档中,有http server的demo

http.Handle("/foo", fooHandler)
http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
})
log.Fatal(http.ListenAndServe(":8080", nil))
http.ListenAndServe(":8080", nil)入口开始看源码调用链

监听主机的8080端口,nil表示不使用自定义的路由handler,即使用默认的DefaultServeMux(一个使用hashmap的简单路由,gin的话是自己实现了一个路由树的路由handler)

// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
 server := &Server{Addr: addr, Handler: handler}
 return server.ListenAndServe()//关键行
}

func (srv *Server) ListenAndServe() error {
 if srv.shuttingDown() {
 return ErrServerClosed
 }
 addr := srv.Addr
 if addr == "" {
 addr = ":http"
 }
 ln, err := net.Listen("tcp", addr)//关键行
 if err != nil {
 return err
 }
 return srv.Serve(ln) //关键行
}

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
}

其server.ListenAndServe()通过调研net包的net.Listen("tcp", addr)返回一个listener接口,这个listener接口定义了Accetp,Close,Addr方法,分别用于建立新连接,关闭连接和返回连接对应的网络地址。

func (srv *Server) Serve(l net.Listener) error {
 if fn := testHookServerServe; fn != nil {
 fn(srv, l) // call hook with unwrapped listener
 }
 origListener := l
 l = &onceCloseListener{Listener: l}
 defer l.Close()
 // 去掉不关键代码....
 ctx := context.WithValue(baseCtx, ServerContextKey, srv)
 for {
 rw, e := l.Accept()
  // 去掉不关键代码....
 c := srv.newConn(rw)
 c.setState(c.rwc, StateNew) // before Serve can return
 go c.serve(ctx)
 }
}

和正常的tcp编程一样,对于每一个listener返回的Conn ,都开辟一个新的协程去处理他。然后当前协程继续执行Accept()

对于c.serve(ctx)函数内部,关键操作有两个:

  • w, err := c.readRequest(ctx),解析客户端请求(只解析http包的请求行和请求头。payload在用户传入的handler使用到时,才解析。如调用func (r *Request) FormValue(key string) string

  • serverHandler{c.server}.ServeHTTP(w, w.req)使用c.server构造serverHandler后调用ServeHTTP,默认的c.server就是DefaultServeMux,根据path调用mux.Handler(r)获取用户编写的handler且调用

// ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request URL.
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)
}
// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
    // 省略部分代码

    // HTTP/1.x from here on.

    ctx, cancelCtx := context.WithCancel(ctx)
    c.cancelCtx = cancelCtx
    defer cancelCtx()

    c.r = &connReader{conn: c}
    c.bufr = newBufioReader(c.r)
    c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)

    for {
        w, err := c.readRequest(ctx)
        // 省略部分代码
        // 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{})
    }
}

相关文章

网友评论

      本文标题:go net/http 源码浅析

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