Skip to content

Instantly share code, notes, and snippets.

@alexesDev
Last active October 18, 2018 19:52
Show Gist options
  • Save alexesDev/71e6bcf91fa945b8af52cfcaa8f4330a to your computer and use it in GitHub Desktop.
Save alexesDev/71e6bcf91fa945b8af52cfcaa8f4330a to your computer and use it in GitHub Desktop.
golang http server template
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"sync/atomic"
"time"
"github.com/caarlos0/env"
"github.com/gorilla/mux"
"github.com/ory/graceful"
)
type config struct {
ListenAddr string `env:"LISTEN_ADDR" envDefault:"localhost:4444"`
}
type key int
const requestIDKey key = 0
var healthy int32
func main() {
var cfg config
err := env.Parse(&cfg)
if err != nil {
log.Fatalf("%+v\n", err)
}
logger := log.New(os.Stdout, "http: ", log.LstdFlags)
logger.Println("Server is starting...")
router := mux.NewRouter()
router.Handle("/", index()).Methods("POST")
router.Handle("/healthz", healthz())
nextRequestID := func() string {
return fmt.Sprintf("%d", time.Now().UnixNano())
}
router.Use(tracing(nextRequestID))
router.Use(logging(logger))
server := graceful.WithDefaults(&http.Server{
Addr: cfg.ListenAddr,
Handler: router,
ErrorLog: logger,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 15 * time.Second,
})
logger.Println("Server is ready to handle requests at", cfg.ListenAddr)
atomic.StoreInt32(&healthy, 1)
if err := graceful.Graceful(server.ListenAndServe, server.Shutdown); err != nil {
logger.Fatalln("main: Failed to gracefully shutdown")
}
logger.Println("main: Server was shutdown gracefully")
}
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