/* go build for linux: env GOOS=linux go build -o /tmp/server server.go run with docker: docker run -it --rm --name test -v /tmp/server:/server -p 3000:80 -p 3001:6060 alpine /server run pprof debugger: go get github.com/google/pprof pprof --seconds 30 -http=:4444 /tmp/server http://localhost:3001/debug/pprof/profile benchmarking with wrk: brew install wrk wrk -d 5s http://localhost:3000/ */ package main import ( "encoding/json" "fmt" "log" "net/http" "os" "os/signal" "syscall" "time" _ "net/http/pprof" ) // http routes var routes = map[string]string{ "/": "apiRoot", "/healthz": "apiHealthz", "/error": "apiError", } // handle route paths with methods func routeHandler(w http.ResponseWriter, r *http.Request) error { route, ok := routes[r.RequestURI] if !ok { return fmt.Errorf("Route %s path does not exist", r.RequestURI) } switch route { case "apiRoot": return apiRoot(w, r) case "apiHealthz": return apiHealthz(w, r) case "apiError": return apiError(w, r) default: return fmt.Errorf("Route %s method not exist", r.RequestURI) } } // route methods // root/index path func apiRoot(w http.ResponseWriter, r *http.Request) error { data := map[string]string{"hello": "welcome"} return outputJSON(w, data) } // simple health check func apiHealthz(w http.ResponseWriter, r *http.Request) error { data := map[string]interface{}{"alive": time.Now().Unix()} return outputJSON(w, data) } // simulate error with 1 second delay func apiError(w http.ResponseWriter, r *http.Request) error { defer timeTrack(time.Now(), "/error endpoint bug taking too long") time.Sleep(1 * time.Second) return fmt.Errorf("some error") } // capture errors func errorHandler(f func(http.ResponseWriter, *http.Request) error) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { startTime := time.Now() w.Header().Set("Content-Type", "application/json") err := f(w, r) if err != nil { errMsg := fmt.Sprintf("{ \"error:\": \"%s\" }", err.Error()) http.Error(w, errMsg, http.StatusInternalServerError) log.Printf("[latency: %s] %d Error - Path: %s -- err: %v", time.Since(startTime), http.StatusInternalServerError, r.RequestURI, err) return } log.Printf("[latency: %s] %d OK - Path: %s", time.Since(startTime), http.StatusOK, r.RequestURI) } } // map to json string func outputJSON(w http.ResponseWriter, data interface{}) error { jsonString, err := json.Marshal(data) if err != nil { log.Fatalf("[Internal Error] Unable to stringify map argument, err: %v", err) return fmt.Errorf("[Internal Error] JSON data") } fmt.Fprint(w, string(jsonString)) return nil } // time track func timeTrack(start time.Time, name string) { elapsed := time.Since(start) log.Printf("TimeTrack (%s) -> %s", elapsed, name) } // signal handler func signalHandler(signal os.Signal) { fmt.Printf("\nCaught signal: %+v", signal) fmt.Println("\nWait for 1 second to finish processing") time.Sleep(1 * time.Second) switch signal { case syscall.SIGHUP: // kill -SIGHUP XXXX fmt.Println("- got hungup") case syscall.SIGINT: // kill -SIGINT XXXX or Ctrl+c fmt.Println("- got ctrl+c") case syscall.SIGTERM: // kill -SIGTERM XXXX fmt.Println("- got force stop") case syscall.SIGQUIT: // kill -SIGQUIT XXXX fmt.Println("- stop and core dump") default: fmt.Println("- unknown signal") } fmt.Println("\nFinished server cleanup") os.Exit(0) } // initialize signal handler func initSignals() { var captureSignal = make(chan os.Signal, 1) signal.Notify(captureSignal, syscall.SIGINT, syscall.SIGTERM, syscall.SIGABRT) signalHandler(<-captureSignal) } // initialize PPROF debugger func initPprofDebugger() { port := "6060" // default port if os.Getenv("PPROF_PORT") != "" { port = os.Getenv("PPROF_PORT") } log.Println("Running pprof debugger on :" + port) log.Println(fmt.Sprint(http.ListenAndServe(":"+port, nil).Error())) } // initialize server listener func initApiServer() { // initialize routes for route, _ := range routes { http.HandleFunc(route, errorHandler(routeHandler)) } port := "80" // default port if os.Getenv("SERVER_PORT") != "" { port = os.Getenv("SERVER_PORT") } log.Println("Running api server on port :" + port) err := http.ListenAndServe(":"+port, nil) if err != nil { log.Printf("[Error] ListenAndServe: %v", err) } } func main() { // start PPROF debugger go initPprofDebugger() // capture signals go initSignals() // start HTTP server initApiServer() }