Gin source code reading - how do http requests flow into gin?

This article is the second in the gin source code analysis series. In this article, we mainly clarify a problem: how does a request return to gin to process logic after receiving the request through the net/http socket?

We still start with the example of net/http

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello World"))
    })

    if err := http.ListenAndServe(":8000", nil); err != nil {
        fmt.Println("start http server fail:", err)
    }
}

In this example, http.HandleFunc can see that the URI "/" is registered on defaultservermux by looking at the source code.

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
   DefaultServeMux.HandleFunc(pattern, handler)
}

Net / HTTP serverhttp

net/http has a very important Handler interface. Only when this method is implemented can the requested processing logic be introduced into its own processing flow.

// https://github.com/golang/go/blob/master/src/net/http/server.go#L86-L88
type Handler interface {
   ServeHTTP(ResponseWriter, *Request)
}

The default defaultservermux implements this serverhttp

The flow process of this request:

  1. After receiving the client request, socket.accept starts the go c.serve(connCtx) [net/http server.go:L3013] line to process the request. The server continues to wait for the client connection

  2. Get the handler that can handle this request - > serverhandler {c.server}. Serverhttp (W, w.req) [net / HTTP server. Go: l1952]

  3. Jump to the real serverhttp to match the route and get the handler

  4. Since there is no custom route, the default route [net/http server.go:L2880-2887] is used

  5. Therefore, the defaultservermux is finally called to match the route, and the output returns the corresponding result

Explore the call link of gin serverhttp

The following is the official demo of gin. Just a few lines of code start an echo server.

package main

import "github.com/gin-gonic/gin"

func main() {
    r := gin.Default()
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "pong",
        })
    })
    r.Run() // listen and serve on 0.0.0.0:8080
}

The general flow of this Code:

  1. R: = gin. Default() initializes the relevant parameters

  2. Register the route / ping and the corresponding handler in the route tree

  3. Use r.Run()   Start server

r. The bottom layer of run is still http.ListenAndServe

func (engine *Engine) Run(addr ...string) (err error) {
    defer func() { debugPrintError(err) }()

    trustedCIDRs, err := engine.prepareTrustedCIDRs()
    if err != nil {
        return err
    }
    engine.trustedCIDRs = trustedCIDRs
    address := resolveAddress(addr)
    debugPrint("Listening and serving HTTP on %s\n", address)
    err = http.ListenAndServe(address, engine)
    return
}

Therefore, the process of establishing socket in gin, the process of accept ing client request is no different from that in net/http, and the above process will be repeated. The only difference is to get the location of ServeHTTP

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

sh.srv.Handler is of interface type, but its real type is gin.Engine. According to the dynamic forwarding characteristics of interface, it will eventually jump to the gin.Engine.ServeHTTP function.

Implementation of gin.ServeHTTP

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

So far, we have finally seen the whole picture of gin.ServeHTTP

  1. Take a piece of memory from sync.pool

  2. Initialize this memory to prevent data pollution

  3. Handle request handleHTTPRequest

  4. After the request is processed, return this memory to sync.pool

Now it seems that this implementation is very simple, but it is not. This is the first step for gin to process data, and it only flows requests into gin's processing flow.

Here's a conclusion: through the above source code process analysis, we know that the function net/http.ServeHTTP is very important. Only with the existence of this function can the request flow into the current Go frameworks. If you are interested, you can see how echo, iris, Go zero and other frameworks implement ServeHTTP.

For information on how gin matches routes and how to get handler s, please pay attention to the following articles. If you think the article is good, welcome to like + look again + forward.

Welcome to the official account. More learning materials sharing, attention to the official account reply order:

  • Reply to 0 and get the Go manual

  • Reply 1: get the Go source code flow chart

Recommended for reading the series of articles on gin source code:

1. Gin source code reading (1) - the relationship between gin and net/http

Tags: Go http

Posted on Fri, 24 Sep 2021 11:10:03 -0400 by zimmo