-
-
Save aguart/1e3269b7c7a3616bba408aab9c67f3a5 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)) | |
| }) | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment