Master Go Middleware: Build, Modify, and Manage HTTP Handlers
This guide walks through creating Go middleware, showing how to read and modify requests, manage response headers with custom ResponseWriter implementations, handle edge cases like missing Write calls, and preserve additional interfaces such as http.Pusher and http.Flusher.
Introduction
Writing middleware in Go may seem easy, but many pitfalls appear in practice. This article demonstrates how to create reusable HTTP middleware components.
1. Read Request
All middleware receive an http.Handler and return an http.Handler. The basic pattern is:
func X(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Something here...
h.ServeHTTP(w, r)
})
}Example: redirect trailing slash.
func TrailingSlashRedirect(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" && r.URL.Path[len(r.URL.Path)-1] == '/' {
http.Redirect(w, r, r.URL.Path[:len(r.URL.Path)-1], http.StatusMovedPermanently)
return
}
h.ServeHTTP(w, r)
})
}2. Modify Request
To add or change request headers, create a shallow copy of *http.Request and modify the copy before passing it on.
func RequestID(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
r2 := new(http.Request)
*r2 = *r
r2.Header.Set("X-Request-Id", uuid.NewV4().String())
h.ServeHTTP(w, r2)
})
}3. Write Response Header
Setting a response header can be done directly, but if downstream handlers also set the same header it may be overwritten. Implement a custom ResponseWriter to control header writing.
type serverWriter struct {
w http.ResponseWriter
name string
wroteHeaders bool
}
func (s *serverWriter) Header() http.Header { return s.w.Header() }
func (s *serverWriter) WriteHeader(code int) {
if !s.wroteHeaders {
s.w.Header().Set("Server", s.name)
s.wroteHeaders = true
}
s.w.WriteHeader(code)
}
func (s *serverWriter) Write(b []byte) (int, error) {
if !s.wroteHeaders {
s.w.Header().Set("Server", s.name)
s.wroteHeaders = true
}
return s.w.Write(b)
}Middleware using it:
func Server(h http.Handler, servername string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
sw := &serverWriter{w: w, name: servername}
h.ServeHTTP(sw, r)
})
}Problem
If a handler never calls Write or WriteHeader (e.g., a 200 response with empty body), the custom header may never be set. Adding a check after ServeHTTP ensures the header is written.
Other ResponseWriter Interfaces
The ResponseWriter may also implement interfaces such as http.Pusher or http.Flusher. Forwarding these methods preserves HTTP/2 support and flushing behavior.
// Push implements http.Pusher
func (s *serverWriter) Push(target string, opts *http.PushOptions) error {
if pusher, ok := s.w.(http.Pusher); ok {
return pusher.Push(target, opts)
}
return http.ErrNotSupported
}
// Flush implements http.Flusher
func (s *serverWriter) Flush() {
if f, ok := s.w.(http.Flusher); ok {
f.Flush()
}
}Summary
The article provides a complete overview of building Go middleware, covering request reading, modification, response header handling, and preserving additional interfaces.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
360 Zhihui Cloud Developer
360 Zhihui Cloud is an enterprise open service platform that aims to "aggregate data value and empower an intelligent future," leveraging 360's extensive product and technology resources to deliver platform services to customers.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
