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 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 请求处理流程分析的主要内容,如果未能解决你的问题,请参考以下文章