|
|
@@ -0,0 +1,126 @@ |
|
|
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)) |
|
|
}) |
|
|
} |
|
|
} |