weather/internal/web/server.go

133 lines
3.5 KiB
Go

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