main.go

A simple golang web server with basic logging, tracing, health check, graceful shutdown and zero dep

18

Votes

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

    done := make(chan bool)
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, os.Interrupt)

    go func() {
        <-quit
        logger.Println("Server is shutting down...")
        atomic.StoreInt32(&healthy, 0)

        ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
        defer cancel()

        server.SetKeepAlivesEnabled(false)
        if err := server.Shutdown(ctx); err != nil {
            logger.Fatalf("Could not gracefully shutdown the server: %v\n", err)
        }
        close(done)
    }()

    logger.Println("Server is ready to handle requests at", listenAddr)
    atomic.StoreInt32(&healthy, 1)
    if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
        logger.Fatalf("Could not listen on %s: %v\n", listenAddr, err)
    }

    <-done
    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))
        })
    }
}

Vote Here

You must earn at least 1 vote on your snippets to be allowed to vote

Terms Of Use

Privacy Policy

Featured snippets are MIT license

Gears & Masters

Advertise

DevOpsnipp.com © 2020

medium.png