SoFunction
Updated on 2025-03-03

A brief analysis of net/http routing registration and request processing in Golang

Register a route

When we are new to the web, we often use () to register routes, and then use () to start the service. Next, let’s analyze how the routes are registered within the http package.

In addition to the commonly used () routes, you can also register. Let’s take a look at the source code first.

func Handle(pattern string, handler Handler) { 
    (pattern, handler) 
}

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

Compare the differences between the two functions:

1. The Handle and HandleFunc methods of DefaultServeMux are called respectively.

2. The handler parameter types are interface and func(ResponseWriter, *Request) types respectively. To explain, DefaultServerMux is a global variable of the http package. If the default multiplexer is not used

Next, let’s take a look at the main source codes of Handle and HandleFunc and the Handler interface.

// Register a routefunc (mux *ServeMux) Handle(pattern string, handler Handler) {
    ()
    defer ()
    ...
    
    if  == nil {
         = make(map[string]muxEntry)
    }
    e := muxEntry{h: handler, pattern: pattern}
    [pattern] = e
	
    ...
}

// Call Handle to register the routefunc (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    if handler == nil {
        panic("http: nil handler")
    }
    (pattern, HandlerFunc(handler))
}

// Implements the function type of Handlertype HandlerFunc func(ResponseWriter, *Request)

func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

You can see that the routing registration is finally implemented, recording the mapping between the route and the processor; and converting the handler parameter to the HandlerFunc type and then calling it.

So the question is, the handler parameter is the Handler interface type. The processor function signature we pass in by calling() is func(ResponseWriter, *Request). How does the function we pass in implement the Handler interface?

The answer lies in the HandlerFunc type, which implements the Handler interface. The processor function we passed in is consistent with the HandlerFunc type function signature. If there is no HandlerFunc and to register the function, we have to define the structure ourselves, write the ServeHTTP method, and implement the Handler interface. With HandlerFunc, we can save this step. In the design pattern, this is called the decorator pattern.

Processing a request

ServerMux

Both the routes used and registered are registered to DefaultServerMux, which also implements the handler interface. Let's take a look at the ServerMux's ServeHTTP method.

func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
    if  == "*" {
        if (1, 1) {
            ().Set("Connection", "close")
        }
        (StatusBadRequest)
        return
    }
    h, _ := (r)
    (w, r)
}

() will be called (), and the route and corresponding processor functions (we passed in) will be obtained from (map[string]muxEntry type), and then (w,r) ​​can be called to process the corresponding request.

Now we have learned that the processing function we passed in is called by (), so how is () called? Next, track the source code of ().

Server

func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return ()
}

If you start the service with (), the handler parameter is generally passed nil, and DefaultServerMux is used as the processor. The following analysis is based on this as the premise.

We have created a Server structure inside the function. If you want to use it more flexibly, you can create the Server structure yourself and call ().

func (srv *Server) ListenAndServe() error {
    if () {
        return ErrServerClosed
    }
    addr := 
    if addr == "" {
        addr = ":http"
    }
    ln, err := ("tcp", addr)	// Listening address    if err != nil {
        return err
    }
    return (ln)
}

The binding of the listening address is completed in () and then () is called again

func (srv *Server) Serve(l ) error {
    
	...
    
    ctx := (baseCtx, ServerContextKey, srv)
    for {
        rw, err := ()

        ...
            
        connCtx := ctx
        if cc := ; cc != nil {
            connCtx = cc(connCtx, rw)
            if connCtx == nil {
                panic("ConnContext returned nil")
            }
        }
        c := (rw)
        (, StateNew, runHooks)
        go (connCtx)
    }
}

A for loop is enabled to receive connections, and a contextxt is created for each connection, and a coroutine is opened to continue processing.

func (c *conn) serve(ctx ) {
     = ().String()
    ctx = (ctx, LocalAddrContextKey, ())
    var inFlightResponse *response
    defer func() {
        if err := recover(); err != nil && err != ErrAbortHandler {
            const size = 64 << 10
            buf := make([]byte, size)
            buf = buf[:(buf, false)]
            ("http: panic serving %v: %v\n%s", , err, buf)
        }
        if inFlightResponse != nil {
            ()
        }
        if !() {
            if inFlightResponse != nil {
                ()
                ()
            }
            ()
            (, StateClosed, runHooks)
        }
    }()

	...
    
    for {
        w, err := (ctx)

        ...

        inFlightResponse = w
        serverHandler{}.ServeHTTP(w, )  // This is not the ServeHTTP method of the Handler interface        inFlightResponse = nil
        ()
        if () {
            return
        }
        
      	...
    }

    ...
}

In line 1991 of the http package, the serverHandler structure nested with the Server structure calls the ServeHTTP method. It should be noted that this is not the ServeHTTP method of the Handler interface, and the call of the processor function we passed in is still inside it.

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
    handler := 
    if handler == nil {
        handler = DefaultServeMux	// If the Handler parameter is nil when creating a Server, use DefaultServeMux                                                                    // DefaultServeMux is usually used by opening the service    }
    if  == "*" &&  == "OPTIONS" {
        handler = globalOptionsHandler{}
    }

    if  != nil && (, ";") {
        var allowQuerySemicolonsInUse int32
        req = (((), silenceSemWarnContextKey, func() {
            atomic.StoreInt32(&allowQuerySemicolonsInUse, 1)
        }))
        defer func() {
            if atomic.LoadInt32(&allowQuerySemicolonsInUse) == 0 {
                ("http: URL query contains semicolon, which is no longer a supported separator; parts of the query may be stripped when parsed; see /issue/25192")
            }
        }()
    }

    (rw, req)	// Call DefaultServeMux's ServeHTTP}

The last line calls DefaultServeMux's ServeHTTP. As mentioned above, its function is to obtain the corresponding processor function of the request and execute it.

extend

The gin framework is encapsulated based on http packages. The way gin matches routing is different from that of native packages. It uses a prefix routing tree to match routes. By starting the service, it is also called (). So how does gin apply the custom matching method? It is actually very simple. As mentioned above, if the second parameter handler is nil when calling(), DefaultServeMux will be used as a multiplexer. Then, if you pass the gin-defined multiplexer when calling().

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

    if () {
        debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
            "Please check //gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
    }

    address := resolveAddress(addr)
    debugPrint("Listening and serving HTTP on %s\n", address)
    err = (address, ())	// () Returns the custom processor of gin    return
}

The above is a brief analysis of the detailed content of net/http routing registration and request processing in Golang. For more information about go net/http, please follow my other related articles!