kuda.ai

Thoughts on programming and music theory.

Go Routes Signature Return Types

Created on April 18, 2025
programming/golang

When you setup your routes, you return *http.ServeMux. If you wrap mux into middleware, you return http.Handler.

// without middleware:
func (app *application) routes() *http.ServeMux {
    ...
    return mux
}

// with middleware:
func (app *application) routes() http.Handler {
    ...
    return commonHeaders(mux)
}

Here is the sample middleware:

func commonHeaders(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Server", "Go")
		next.ServeHTTP(w, r)
	})
}

In your main, you use app.routes() either way like this:

func main() {
	var err error
	app := &application{}
	...
	log.Print("Starting web server, listening on port 8873")
	err = http.ListenAndServe(":8873", app.routes())
	log.Fatal(err)
}

So what does http.ListenAndServe expect as second arg if it can either be a *http.ServeMux or a http.Handler? Let’s find out!

Here is the source code:

// ListenAndServe listens on the TCP network address addr and then calls
// [Serve] with handler to handle requests on incoming connections.
// Accepted connections are configured to enable TCP keep-alives.
//
// The handler is typically nil, in which case [DefaultServeMux] is used.
//
// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
	server := &Server{Addr: addr, Handler: handler}
	return server.ListenAndServe()
}

So then, what is a Handler? 🤔

// A Handler responds to an HTTP request.
//
// [Handler.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)
}

Alright, so this must mean that both the *http.ServeMux and http.Handler must have the ServeHTTP method.