-
-
Save dstroot/faef20a6c863b5fa8e3438f6055e36cd to your computer and use it in GitHub Desktop.
A simple golang web server with basic logging, tracing, health check, graceful shutdown and zero dependencies
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" | |
| "flag" | |
| "fmt" | |
| "log" | |
| "net/http" | |
| "os" | |
| "os/signal" | |
| "sync/atomic" | |
| "time" | |
| ) | |
| type key int | |
| const ( | |
| requestIDKey key = 0 | |
| ) | |
| var ( | |
| listenAddr string | |
| healthy int32 | |
| ) | |
| func main() { | |
| flag.StringVar(&listenAddr, "listen-addr", ":5000", "server listen address") | |
| flag.Parse() | |
| logger := log.New(os.Stdout, "http: ", log.LstdFlags) | |
| logger.Println("Server is starting...") | |
| router := http.NewServeMux() | |
| router.Handle("/", index()) | |
| router.Handle("/healthz", healthz()) | |
| nextRequestID := func() string { | |
| return fmt.Sprintf("%d", time.Now().UnixNano()) | |
| } | |
| server := &http.Server{ | |
| Addr: listenAddr, | |
| Handler: tracing(nextRequestID)(logging(logger)(router)), | |
| ErrorLog: logger, | |
| ReadTimeout: 5 * time.Second, | |
| WriteTimeout: 10 * time.Second, | |
| IdleTimeout: 15 * time.Second, | |
| } | |
| quit := make(chan os.Signal, 1) | |
| signal.Notify(quit, os.Interrupt) | |
| go func() { | |
| <-quit | |
| logger.Println("Server is shoutting down...") | |
| atomic.StoreInt32(&healthy, 0) | |
| ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) | |
| defer cancel() | |
| server.SetKeepAlivesEnabled(false) | |
| if err := server.Shutdown(ctx); err != nil { | |
| logger.Fatalf("Could not gracefully shoutdown the server: %v\n", err) | |
| } | |
| }() | |
| logger.Println("Server is ready to handle requests at", listenAddr) | |
| atomic.StoreInt32(&healthy, 1) | |
| if err := server.ListenAndServe(); err != http.ErrServerClosed { | |
| logger.Fatalf("Could not listen on %s: %v\n", listenAddr, err) | |
| } | |
| logger.Println("Server stopped") | |
| } | |
| func index() http.Handler { | |
| return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| if r.URL.Path != "/" { | |
| http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) | |
| return | |
| } | |
| w.Header().Set("Content-Type", "text/plain; charset=utf-8") | |
| w.Header().Set("X-Content-Type-Options", "nosniff") | |
| w.WriteHeader(http.StatusOK) | |
| fmt.Fprintln(w, "Hello, World!") | |
| }) | |
| } | |
| func healthz() http.Handler { | |
| return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| if atomic.LoadInt32(&healthy) == 1 { | |
| w.WriteHeader(http.StatusNoContent) | |
| return | |
| } | |
| w.WriteHeader(http.StatusServiceUnavailable) | |
| }) | |
| } | |
| func logging(logger *log.Logger) func(http.Handler) http.Handler { | |
| return func(next http.Handler) http.Handler { | |
| return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| defer func() { | |
| requestID, ok := r.Context().Value(requestIDKey).(string) | |
| if !ok { | |
| requestID = "unknown" | |
| } | |
| logger.Println(requestID, r.Method, r.URL.Path, r.RemoteAddr, r.UserAgent()) | |
| }() | |
| next.ServeHTTP(w, r) | |
| }) | |
| } | |
| } | |
| func tracing(nextRequestID func() string) func(http.Handler) http.Handler { | |
| return func(next http.Handler) http.Handler { | |
| return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| requestID := r.Header.Get("X-Request-Id") | |
| if requestID == "" { | |
| requestID = nextRequestID() | |
| } | |
| ctx := context.WithValue(r.Context(), requestIDKey, requestID) | |
| w.Header().Set("X-Request-Id", requestID) | |
| next.ServeHTTP(w, r.WithContext(ctx)) | |
| }) | |
| } | |
| } |
The sequence of middlewares is wrong in
main.go.The right one should be:
(middlewares{ logging(logger), tracing(func() string { return fmt.Sprintf("%d", time.Now().UnixNano()) })}).apply(router),Instead of
(middlewares{tracing(func() string { return fmt.Sprintf("%d", time.Now().UnixNano()) }), logging(logger)}).apply(router),
I agree 👍
Author
Thanks for the catch - I changed it. ;)
Thanks for the catch - I changed it. ;)
💯 nice work
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The sequence of middlewares is wrong in
main.go.The right one should be:
Instead of