Golang net/http 请求处理流程分析

Posted 衣舞晨风

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Golang net/http 请求处理流程分析相关的知识,希望对你有一定的参考价值。

先看一下Golang Web Server是如何监听请求的:

srv := &http.Server
		Addr:    ":8080",
		Handler: obj.App,
	

if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed 
   log.Fatalf("listen: %s\\n", err)

具体的监听处理在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
	
	addr := srv.Addr
	if addr == "" 
		addr = ":http"
	
	ln, err := net.Listen("tcp", addr)
	if err != nil 
		return err
	
	return srv.Serve(ln)

下面看一下Serve(l net.Listener)

// 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
	l = &onceCloseListenerListener: 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)

	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

	ctx := context.WithValue(baseCtx, ServerContextKey, srv)
	for 
		rw, err := l.Accept()
		if err != nil 
			select 
			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
		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, runHooks) // before Serve can return
		go c.serve(connCtx)  // <--- 注意这里
	

注意一下,在serve(l net.Listener)函数中,对于每一个请求都会开一个协程来处理。

协程内部都做了啥呢?

// 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())
	var inFlightResponse *response
	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 inFlightResponse != nil 
			inFlightResponse.cancelCtx()
		
		if !c.hijacked() 
			if inFlightResponse != nil 
				inFlightResponse.conn.r.abortPendingRead()
				inFlightResponse.reqBody.Close()
			
			c.close()
			c.setState(c.rwc, StateClosed, runHooks)
		
	()

	if tlsConn, ok := c.rwc.(*tls.Conn); ok 
		tlsTO := c.server.tlsHandshakeTimeout()
		if tlsTO > 0 
			dl := time.Now().Add(tlsTO)
			c.rwc.SetReadDeadline(dl)
			c.rwc.SetWriteDeadline(dl)
		
		if err := tlsConn.HandshakeContext(ctx); 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
		
		// Restore Conn-level deadlines.
		if tlsTO > 0 
			c.rwc.SetReadDeadline(time.Time)
			c.rwc.SetWriteDeadline(time.Time)
		
		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 := initALPNRequestctx, tlsConn, serverHandlerc.server
				// Mark freshly created HTTP/2 as active and prevent any server state hooks
				// from being run on these connections. This prevents closeIdleConns from
				// closing such connections. See issue https://golang.org/issue/39776.
				c.setState(c.rwc, StateActive, skipHooks)
				fn(c.server, tlsConn, h)
			
			return
		
	

	// HTTP/1.x from here on.

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

	c.r = &connReaderconn: c
	c.bufr = newBufioReader(c.r)
	c.bufw = newBufioWriterSize(checkConnErrorWriterc, 4<<10)

	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, runHooks)
		
		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:
				if v, ok := err.(statusError); ok 
					fmt.Fprintf(c.rwc, "HTTP/1.1 %d %s: %s%s%d %s: %s", v.code, StatusText(v.code), v.text, errorHeaders, v.code, StatusText(v.code), v.text)
					return
				
				publicErr := "400 Bad Request"
				fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+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 = &expectContinueReaderreadCloser: req.Body, resp: w
				w.canWriteContinue.setTrue()
			
		 else if req.Header.get("Expect") != "" 
			w.sendExpectationFailed()
			return
		

		c.curReq.Store(w)

		if requestBodyRemains(req.Body) 
			registerOnHitEOF(req.Body, w.conn.r.startBackgroundRead)
		 else 
			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.
		inFlightResponse = w
		serverHandlerc.server.ServeHTTP(w, w.req) // <--- 注意这里
		inFlightResponse = nil
		w.cancelCtx()
		if c.hijacked() 
			return
		
		w.finishRequest()
		if !w.shouldReuseConnection() 
			if w.requestBodyLimitHit || w.closedRequestBodyEarly() 
				c.closeWriteAndWait()
			
			return
		
		c.setState(c.rwc, StateIdle, runHooks)
		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)
	

注意这行代码

serverHandlerc.server.ServeHTTP(w, w.req)

其中ServeHTTP是一个接口,绝大多数Web框架都是通过实现该接口,从而替换掉Golang默认的路由。

具体的接口定义:

// 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 either closes the network connection or sends an HTTP/2
// RST_STREAM, depending on the HTTP protocol. To abort a handler so
// the client sees an interrupted response but the server doesn't log
// an error, panic with the value ErrAbortHandler.
type Handler interface 
	ServeHTTP(ResponseWriter, *Request)

Golang默认实现:

// https://github.com/golang/go/blob/master/src/net/http/server.go#L2926
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
	

	if req.URL != nil && strings.Contains(req.URL.RawQuery, ";") 
		var allowQuerySemicolonsInUse int32
		req = req.WithContext(context.WithValue(req.Context(), silenceSemWarnContextKey, func() 
			atomic.StoreInt32(&allowQuerySemicolonsInUse, 1)
		))
		defer func() 
			if atomic.LoadInt32(&allowQuerySemicolonsInUse) == 0 
				sh.srv.logf("http: URL query contains semicolon, which is no longer a supported separator; parts of the query may be stripped when parsed; see golang.org/issue/25192")
			
		()
	

	handler.ServeHTTP(rw, req)


// https://github.com/golang/go/blob/master/src/net/http/server.go#L2478
// 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)


// https://github.com/golang/go/blob/master/src/net/http/server.go#L2423
// 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. If the host contains a port, it is ignored
// when matching handlers.
//
// The path and host are used unchanged for CONNECT requests.
//
// 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) 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)
		u := &url.URLPath: path, RawQuery: r.URL.RawQuery
		return RedirectHandler(u.String(), StatusMovedPermanently), pattern
	

	return mux.handler(host, r.URL.Path)


// https://github.com/golang/go/blob/master/src/net/http/server.go#L2459
// handler is the main implementation of Handler.
// The path is known to be in canonical form, except for CONNECT methods.
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


// https://github.com/golang/go/blob/master/src/net/http/server.go#L2350
// 从代码中可以看出,匹配规则过于简单,导致基本所有的go框架
// 最重要的一点就是:重写路由匹配这部分,比如gin
// Find a handler on a handler map given a path string.
// Most-specific (longest) pattern wins.
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, ""


// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) 
	f(w, r)


// 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) // <--- 接口型函数

比如Gin就通过实现ServeHTTP(w http.ResponseWriter, req *http.Request)接口,在这里重新实现路由匹配

// ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) 
	c := engine.pool.Get().(*Context)
	c.writermem.reset(w)
	c.Request = req
	c.reset()

	engine.handleHTTPRequest(c)

	engine.pool.Put(c)


// https://github.com/gin-gonic/gin/blob/master/gin.go#L588
func (engine *Engine) handleHTTPRequest(c *Context) 
	httpMethod := c.Request.Method
	rPath := c.Request.URL.Path
	unescape := false
	if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 
		rPath = c.Request.URL.RawPath
		unescape = engine.UnescapePathValues
	

	if engine.RemoveExtraSlash 
		rPath = cleanPath(rPath)
	

	// Find root of the tree for the given HTTP method
	t := engine.trees
	for i, tl := 0, len(t); i < tl; i++ 
		if t[i].method != httpMethod 
			continue
		
		root := t[i].root
		// Find route in tree
		value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
		if value.params != nil 
			c.Params = *value.params
		
		if value.handlers != nil 
			c.handlers = value.handlers
			c.fullPath = value.fullPath
			c.Next()
			c.writermem.WriteHeaderNow()
			return
		
		if httpMethod != http.MethodConnect && rPath != "/" 
			if value.tsr && engine.RedirectTrailingSlash 
				redirectTrailingSlash(c)
				return
			
			if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) 
				return
			
		
		break
	

	if engine.HandleMethodNotAllowed 
		for _, tree := range engine.trees 
			if tree.method == httpMethod 
				continue
			
			if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil 
				c.handlers = engine.allNoMethod
				serveError(c, http.StatusMethodNotAllowed, default405Body)
				return
			
		
	
	c.handlers = engine.allNoRoute
	serveError(c, http.StatusNotFound, default404Body)

以上是关于Golang net/http 请求处理流程分析的主要内容,如果未能解决你的问题,请参考以下文章

Golang net/http 请求处理流程分析

golang之http请求的dns解析代码流程

简析 Golang net/http 包

Golang使用net/http包写http服务端

Golang使用net/http包写http服务端

Golang的Context介绍及其源码分析