How Go’s HTTP Standard Library Is Implemented

This article walks through Go’s net/http package, explaining how http.HandleFunc registers handlers via DefaultServeMux, how ServeMux stores routes, performs exact and longest‑prefix matching, and how to extend the server with custom ServeMux and middleware for panic recovery and request timing.

Nullbody Notes
Nullbody Notes
Nullbody Notes
How Go’s HTTP Standard Library Is Implemented

Simple demo

A minimal program defines two handler functions and registers them with http.HandleFunc and http.Handle, then starts the server with http.ListenAndServe(":8080", nil):

func commonFunc(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("I'm index1,Wecome path " + r.URL.Path))
}

func panicFunc(w http.ResponseWriter, r *http.Request) {
    panic("panic!!!")
}

func main() {
    http.HandleFunc("/a/", commonFunc)
    http.Handle("/b", http.HandlerFunc(panicFunc))
    http.ListenAndServe(":8080", nil)
}

The core idea is that a handler is any function with the signature func(ResponseWriter, *Request).

Source‑code walk‑through

http.HandleFunc

forwards the pattern and handler to the default multiplexer DefaultServeMux:

var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux

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

Inside ServeMux.HandleFunc the handler is converted to the Handler interface via the HandlerFunc type, which implements ServeHTTP by calling the underlying function.

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    if handler == nil {
        panic("http: nil handler")
    }
    mux.Handle(pattern, HandlerFunc(handler))
}

type Handler interface { ServeHTTP(ResponseWriter, *Request) }

type HandlerFunc func(ResponseWriter, *Request)

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

This conversion allows a plain function to satisfy the Handler interface.

ServeMux internals

ServeMux

stores routes in a map m for exact matches and a slice es for patterns ending with ‘/’ to support longest‑prefix matching. Registration locks the mux, checks for nil handlers, duplicate patterns, and lazily creates the map on first use.

type ServeMux struct {
    mu    sync.RWMutex
    m     map[string]muxEntry // exact matches
    es    []muxEntry          // longest‑prefix entries
    hosts bool
}

func (mux *ServeMux) Handle(pattern string, handler Handler) {
    if handler == nil { panic("http: nil handler") }
    if _, exist := mux.m[pattern]; exist { panic("http: multiple registrations for " + pattern) }
    if mux.m == nil { mux.m = make(map[string]muxEntry) }
    e := muxEntry{h: handler, pattern: pattern}
    mux.m[pattern] = e
    if pattern[len(pattern)-1] == '/' { mux.es = appendSorted(mux.es, e) }
    if pattern[0] != '/' { mux.hosts = true }
}

Routing works by first looking for an exact key in mux.m. If none is found, it iterates over mux.es (sorted by pattern length descending) and returns the first entry whose pattern is a prefix of the request path.

func (mux *ServeMux) match(path string) (h Handler, pattern string) {
    if v, ok := mux.m[path]; ok { return v.h, v.pattern }
    for _, e := range mux.es {
        if strings.HasPrefix(path, e.pattern) { return e.h, e.pattern }
    }
    return nil, ""
}

Advanced demo – custom ServeMux and middleware

Reusing the global DefaultServeMux makes it hard to run multiple servers on different ports without sharing routes. The article defines a custom MyServeMux that embeds *http.ServeMux and holds a slice of middleware functions.

type MyServeMux struct {
    *http.ServeMux
    handlers []MiddlerWare
}

type MiddlerWare func(http.Handler) http.Handler

func (mux *MyServeMux) Use(mws ...MiddlerWare) { mux.handlers = append(mux.handlers, mws...) }

func (mux *MyServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    h, _ := mux.Handler(r)
    for i := len(mux.handlers) - 1; i >= 0; i-- { h = mux.handlers[i](h) }
    h.ServeHTTP(w, r)
}

Two example middleware are provided: PanicMiddleWare recovers from panics, logs the error, and writes a fallback response. ElaspedMiddleWare records the time before handling the request and prints the elapsed milliseconds after.

func PanicMiddleWare(handler http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer func() { if err := recover(); err != nil { fmt.Println("panic info", err); w.Write([]byte("panic recover")) } }()
        handler.ServeHTTP(w, r)
    })
}

func ElaspedMiddleWare(handler http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        t := time.Now()
        defer func() { fmt.Printf("%s elapsed %d ms
", r.URL.Path, time.Since(t).Milliseconds()) }()
        handler.ServeHTTP(w, r)
    })
}

Using the custom mux:

func main() {
    myMux := &MyServeMux{ServeMux: http.NewServeMux()}
    myMux.Use(PanicMiddleWare, ElaspedMiddleWare)
    myMux.HandleFunc("/a/", commonFunc)
    myMux.Handle("/b", http.HandlerFunc(panicFunc))
    srv := http.Server{Addr: ":8081", Handler: myMux, WriteTimeout: 500*time.Millisecond, ReadTimeout: 200*time.Millisecond}
    srv.ListenAndServe()
}

This solves the earlier drawbacks: each server has its own route table, and middleware is applied to every handler in reverse registration order.

High‑level note

The article mentions that frameworks like Gin replace the default *http.ServeMux entirely, implementing their own routing (often a compressed prefix tree) for higher performance and flexibility, as shown in Gin’s Run method which ultimately calls http.ListenAndServe(address, engine.Handler()).

Overall, the piece demonstrates step‑by‑step how Go’s HTTP server builds request handling from low‑level functions to a full‑featured multiplexer, and how developers can extend it with custom muxes and middleware.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

middlewareGoroutingnet/httpServeMuxDefaultServeMux
Nullbody Notes
Written by

Nullbody Notes

Go backend development, learning open-source project source code together, focusing on simplicity and practicality.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.