Skip to content

Instantly share code, notes, and snippets.

@mattkasun
Last active February 2, 2026 15:20
Show Gist options
  • Select an option

  • Save mattkasun/8331cc9faa287858c8112051638ba3b1 to your computer and use it in GitHub Desktop.

Select an option

Save mattkasun/8331cc9faa287858c8112051638ba3b1 to your computer and use it in GitHub Desktop.
go stdlib http router, sub-router, middleware, custom 404/405 handler
package main
import (
"context"
"errors"
"fmt"
"io"
"log"
"net/http"
"os"
"os/signal"
"strings"
"syscall"
"time"
)
func main() {
router := http.NewServeMux()
router.HandleFunc("GET /{$}", func(w http.ResponseWriter, _ *http.Request) {
io.WriteString(w, "main page")
})
router.Handle("/", notFound(router))
// add middleware
handler := Logger(router)
// groups
group := http.NewServeMux()
// group routes
group.HandleFunc("GET /hello", func(w http.ResponseWriter, _ *http.Request) {
io.WriteString(w, "hello world")
})
group.Handle("/", notFound(group))
// group middleware
groupHandler := middleware(group)
groupHandler = middleware2(groupHandler)
router.Handle("/group/", http.StripPrefix("/group", groupHandler))
server := http.Server{
Addr: ":8080",
Handler: handler,
ReadHeaderTimeout: time.Second,
}
go func() {
err := server.ListenAndServe()
if err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatal(err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGTERM, syscall.SIGINT)
log.Println("server started on port 8080")
<-quit
if err := server.Shutdown(context.Background()); err != nil {
log.Fatal("server shutdow", err)
}
}
func middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Println("middleware")
next.ServeHTTP(w, r)
})
}
func middleware2(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Println("middleware2")
next.ServeHTTP(w, r)
})
}
func methods() []string {
return []string{
http.MethodGet,
http.MethodHead,
http.MethodPost,
http.MethodPut,
http.MethodPatch,
http.MethodDelete,
http.MethodConnect,
http.MethodOptions,
http.MethodTrace,
}
}
func notFound(mux *http.ServeMux) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var allowed []string
method := r.Method
_, current := mux.Handler(r)
for _, method := range methods() {
r.Method = method
if _, pattern := mux.Handler(r); pattern != current {
allowed = append(allowed, method)
}
}
r.Method = method
if len(allowed) != 0 {
w.Header().Set("Allow", strings.Join(allowed, ", "))
w.WriteHeader(http.StatusMethodNotAllowed)
io.WriteString(w, "this is not the method you are looking for...")
return
}
w.WriteHeader(http.StatusNotFound)
io.WriteString(w,
"this is not the page you are loooking for...\n\ngo about your business\nmove along")
})
}
type statusRecorder struct {
http.ResponseWriter
status int
}
// WriteHeader overrides std WriteHeader to save response code.
func (rec *statusRecorder) WriteHeader(code int) {
rec.status = code
rec.ResponseWriter.WriteHeader(code)
}
// Logger is a logging middleware that logs useragent, RemoteAddr, Method, Host, Path and response.Status to stdlib log.
func Logger(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
now := time.Now()
rec := statusRecorder{w, http.StatusOK}
next.ServeHTTP(&rec, r)
// remote := strings.Split(r.RemoteAddr, ":")[0]
remote := r.RemoteAddr
if r.Header.Get("X-Forwarded-For") != "" {
remote = r.Header.Get("X-Forwarded-For")
}
details := fmt.Sprintf(
"%s %s%s %d %s %s %s",
r.Method,
r.Host,
r.URL.Path,
rec.status,
remote,
time.Since(now).String(),
r.UserAgent(),
)
log.Println(details)
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment