package web import ( "context" "crypto/tls" "encoding/json" "fmt" "go/weather/pkg/logger" "net" "net/http" "os" "time" "github.com/gorilla/mux" "go.elastic.co/apm" "go.uber.org/zap" ) const ( apiPrefix = "/api" apiRouterName = "api" ) //WeatherServer http server for weather application type WeatherServer struct { http.Server router *mux.Router wlogger *logger.WeatherLogger subRouters map[string]*mux.Router version string } //New construct new instance func New(logger *logger.WeatherLogger, addr ListenAddr, version string) *WeatherServer { return &WeatherServer{ Server: http.Server{ Addr: addr.String(), BaseContext: func(l net.Listener) context.Context { // here configure context properties dt := apm.DefaultTracer tx := dt.StartTransactionOptions("http", "request", apm.TransactionOptions{ Start: time.Now(), }) defer tx.End() return apm.ContextWithTransaction(context.Background(), tx) }, WriteTimeout: 15 * time.Second, ReadTimeout: 15 * time.Second, }, router: mux.NewRouter(), wlogger: logger, subRouters: make(map[string]*mux.Router, 1), version: version, } } //WithHandler register weather handler func (ws *WeatherServer) WithHandler() *WeatherServer { ws.router.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) { rw.Write([]byte("coucou")) }) api := ws.getOrCreateSubRouterByName(apiRouterName, apiPrefix) api.HandleFunc("/health", func(rw http.ResponseWriter, r *http.Request) { json.NewEncoder(rw).Encode(map[string]interface{}{ "ok": true, "version": ws.version, }) }) return ws } //WithHTTPLogging add http server request/response logging capability func (ws *WeatherServer) WithHTTPLogging() *WeatherServer { ws.Server.Handler = ws.wlogger.HTTPLogHandler(ws.router) api := ws.getOrCreateSubRouterByName(apiRouterName, apiPrefix) api.HandleFunc("/log", ws.wlogger.Level.ServeHTTP) return ws } //WithErrorLogging add http server error logging capability func (ws *WeatherServer) WithErrorLogging() *WeatherServer { stdLogger, err := zap.NewStdLogAt(ws.wlogger.Logger, zap.ErrorLevel) ws.handleError(err) ws.Server.ErrorLog = stdLogger return ws } //WithTLSConfigure add cert and key file to server, permit TLS capability func (ws *WeatherServer) WithTLSConfigure() *WeatherServer { currentPath, err := os.Getwd() ws.handleError(err) certPath := fmt.Sprintf("%s%ccerts%cout%cweather.pem", currentPath, os.PathSeparator, os.PathSeparator, os.PathSeparator) keyPath := fmt.Sprintf("%s%ccerts%cout%cweather-key.pem", currentPath, os.PathSeparator, os.PathSeparator, os.PathSeparator) cert, err := tls.LoadX509KeyPair( certPath, keyPath, ) ws.handleError(err) ws.Server.TLSConfig = &tls.Config{Certificates: []tls.Certificate{cert}} return ws } //Serve start http server func (ws *WeatherServer) Serve() error { // Server.Hanlder is nil when we don't use WithHTTPLogging if ws.Server.Handler == nil { ws.Server.Handler = ws.router } return ws.Server.ListenAndServeTLS("", "") } //getOrCreateSubRouterByName return a router by name or create if it doesn't exist yet func (ws *WeatherServer) getOrCreateSubRouterByName(name, prefix string) *mux.Router { if api := ws.subRouters[name]; api == nil { ws.subRouters[name] = ws.router.PathPrefix(prefix).Subrouter() } return ws.subRouters[name] } //handleError generic fatal error func (ws *WeatherServer) handleError(err error) { if err != nil { ws.wlogger.Fatal("Server error", zap.Error(err)) } }