-
-
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 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 ( | |
"io" | |
"net/http" | |
"sync/atomic" | |
) | |
func index() http.Handler { | |
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
if r.URL.Path != "/" { | |
http.NotFound(w, r) | |
return | |
} | |
w.Header().Set("X-Content-Type-Options", "nosniff") | |
w.Header().Set("Content-Type", "text/plain; charset=utf-8") | |
io.WriteString(w, "Hello, World!") | |
}) | |
} | |
func healthz() http.Handler { | |
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
if atomic.LoadInt32(&healthy) == 1 { | |
w.Header().Set("X-Content-Type-Options", "nosniff") | |
w.Header().Set("Content-Type", "application/json; charset=utf-8") | |
io.WriteString(w, `{"alive": true}`) | |
return | |
} | |
w.WriteHeader(http.StatusServiceUnavailable) | |
}) | |
} |
This file contains 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 ( | |
"fmt" | |
"log" | |
"os" | |
"time" | |
) | |
func main() { | |
listenAddr := "5000" | |
// create a logger, router and server | |
logger := log.New(os.Stdout, "http: ", log.LstdFlags) | |
router := newRouter() | |
server := newServer( | |
listenAddr, | |
(middlewares{ logging(logger), tracing(func() string { return fmt.Sprintf("%d", time.Now().UnixNano()) })}).apply(router), | |
logger, | |
) | |
// run our server | |
if err := server.run(); err != nil { | |
log.Fatal(err) | |
} | |
} |
This file contains 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" | |
"log" | |
"net/http" | |
) | |
type key int | |
const ( | |
requestIDKey key = 0 | |
) | |
type middleware func(http.Handler) http.Handler | |
type middlewares []middleware | |
func (mws middlewares) apply(hdlr http.Handler) http.Handler { | |
if len(mws) == 0 { | |
return hdlr | |
} | |
return mws[1:].apply(mws[0](hdlr)) | |
} | |
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)) | |
}) | |
} | |
} |
This file contains 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 ( | |
"net/http" | |
) | |
func newRouter() *http.ServeMux { | |
router := http.NewServeMux() | |
// routes | |
router.Handle("/", index()) | |
router.Handle("/healthz", healthz()) | |
return router | |
} |
This file contains 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" | |
"fmt" | |
"log" | |
"net/http" | |
"os" | |
"os/signal" | |
"sync/atomic" | |
"syscall" | |
"time" | |
) | |
var ( | |
healthy int32 | |
) | |
// Server implements our HTTP server | |
type Server struct { | |
server *http.Server | |
} | |
// NewServer creates a new HTTP Server | |
func newServer(port string, h http.Handler, l *log.Logger) *Server { | |
return &Server{ | |
server: &http.Server{ | |
Addr: ":" + port, | |
Handler: h, // pass in mux/router | |
ErrorLog: l, | |
ReadTimeout: 5 * time.Second, | |
WriteTimeout: 10 * time.Second, | |
IdleTimeout: 30 * time.Second, | |
MaxHeaderBytes: 1 << 20, | |
}, | |
} | |
} | |
// Run starts the HTTP server | |
func (s *Server) run() error { | |
// Get hostname | |
hostname, err := os.Hostname() | |
if err != nil { | |
return err | |
} | |
done := make(chan bool) | |
quit := make(chan os.Signal, 1) | |
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) | |
go func() { | |
<-quit | |
fmt.Println("") | |
s.server.ErrorLog.Printf("%s - Shutdown signal received...\n", hostname) | |
atomic.StoreInt32(&healthy, 0) | |
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) | |
defer cancel() | |
s.server.SetKeepAlivesEnabled(false) | |
if err := s.server.Shutdown(ctx); err != nil { | |
s.server.ErrorLog.Fatalf("Could not gracefully shutdown the server: %v\n", err) | |
} | |
close(done) | |
}() | |
s.server.ErrorLog.Printf("%s - Starting server on port %v", hostname, s.server.Addr) | |
atomic.StoreInt32(&healthy, 1) | |
if err := s.server.ListenAndServe(); err != nil && err != http.ErrServerClosed { | |
s.server.ErrorLog.Fatalf("Could not listen on %s: %v\n", s.server.Addr, err) | |
} | |
<-done | |
s.server.ErrorLog.Printf("%s - Server gracefully stopped.\n", hostname) | |
return nil | |
} |
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 👍
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