Mastering Go's ServeMux: Custom Handlers, Redirects, and Closures

This guide explains Go's net/http ServeMux as a request router, demonstrates built‑in handlers like RedirectHandler, shows how to create custom http.Handler implementations, use functions as handlers, and pass variables via closures, complete with runnable code examples.

FunTester
FunTester
FunTester
Mastering Go's ServeMux: Custom Handlers, Redirects, and Closures

If you are familiar with the MVC pattern, think of a handler as the controller that executes core logic and generates HTTP response headers and bodies, while a ServeMux (router) acts as a dispatcher mapping URL paths to handlers, typically using a single ServeMux to manage all routes.

The Go net/http package provides a simple yet powerful http.ServeMux and includes helper functions such as http.FileServer() , http.NotFoundHandler() , and http.RedirectHandler() .

Using ServeMux and Built‑in Handlers

Below is a minimal example that creates a ServeMux, registers a redirect handler for /foo, and starts an HTTP server on port 3000.

$ mkdir example
$ cd example
$ go mod init example.com
$ touch main.go

File:

main.go
package main

import (
    "log"
    "net/http"
)

func main() {
    // Create an empty ServeMux
    mux := http.NewServeMux()

    // Create a redirect handler that sends all requests to http://example.org
    rh := http.RedirectHandler("http://example.org", 307)

    // Register the handler on path /foo
    mux.Handle("/foo", rh)

    log.Print("FunTester listening...")
    // Start the server with the ServeMux
    http.ListenAndServe(":3000", mux)
}

Running the program and requesting http://localhost:3000/foo returns a 307 Temporary Redirect to http://example.org. Requests to other paths return a 404 Not Found error.

Custom Handlers

Although the net/http package offers built‑in handlers, real‑world applications often need custom handlers. Any type that implements the http.Handler interface can serve as a handler. The interface is defined as:

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

A custom handler must implement a ServeHTTP() method. The following example defines a timeHandler struct that returns the current time in a specified format.

type timeHandler struct {
    format string
}

func (th timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    tm := time.Now().Format(th.format)
    w.Write([]byte("FunTester current time is: " + tm))
}

Registering this handler with a ServeMux:

package main

import (
    "log"
    "net/http"
    "time"
)

type timeHandler struct {
    format string
}

func (th timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    tm := time.Now().Format(th.format)
    w.Write([]byte("FunTester current time is: " + tm))
}

func main() {
    mux := http.NewServeMux()
    th := timeHandler{format: time.RFC1123}
    mux.Handle("/time", th)
    log.Print("FunTester listening...")
    http.ListenAndServe(":3000", mux)
}

Accessing http://localhost:3000/time returns a response such as:

$ curl localhost:3000/time
FunTester current time is: Mon, 06 Dec 2021 15:33:21 CET

Functions as Handlers

For simple cases, a function can be used directly as a handler by converting it to http.HandlerFunc. Example:

func timeHandler(w http.ResponseWriter, r *http.Request) {
    tm := time.Now().Format(time.RFC1123)
    w.Write([]byte("Current time is: " + tm))
}

Register with mux.HandleFunc():

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/time", timeHandler)
    log.Print("Listening...")
    http.ListenAndServe(":3000", mux)
}

Passing Variables to Handlers

When more complex data needs to be supplied to a handler, closures can capture variables. The following factory returns an http.Handler that uses a format string supplied at creation time.

func timeHandler(format string) http.Handler {
    fn := func(w http.ResponseWriter, r *http.Request) {
        tm := time.Now().Format(format)
        w.Write([]byte("FunTester current time is: " + tm))
    }
    return http.HandlerFunc(fn)
}

func main() {
    mux := http.NewServeMux()
    th := timeHandler(time.RFC1123)
    mux.Handle("/time", th)
    log.Print("FunTester listening...")
    http.ListenAndServe(":3000", mux)
}
GoWeb developmentnet/httpCustom HandlerHTTP handlersServeMux
FunTester
Written by

FunTester

10k followers, 1k articles | completely useless

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.