Skip to content

Instantly share code, notes, and snippets.

@dstroot
Forked from enricofoltran/main.go
Last active April 10, 2023 13:22
Show Gist options
  • Save dstroot/faef20a6c863b5fa8e3438f6055e36cd to your computer and use it in GitHub Desktop.
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
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)
})
}
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)
}
}
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))
})
}
}
package main
import (
"net/http"
)
func newRouter() *http.ServeMux {
router := http.NewServeMux()
// routes
router.Handle("/", index())
router.Handle("/healthz", healthz())
return router
}
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
}
@dantin
Copy link

dantin commented Apr 25, 2019

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),

@mcasado
Copy link

mcasado commented Jan 2, 2020

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 👍

@dstroot
Copy link
Author

dstroot commented Jan 3, 2020

Thanks for the catch - I changed it. ;)

@mcasado
Copy link

mcasado commented Jan 3, 2020

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