Last active
February 2, 2026 15:20
-
-
Save mattkasun/8331cc9faa287858c8112051638ba3b1 to your computer and use it in GitHub Desktop.
go stdlib http router, sub-router, middleware, custom 404/405 handler
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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