package server import ( "context" "database/sql" "fmt" "net/http" "time" "github.com/charmbracelet/log" "github.com/gorilla/mux" ) type Server struct { addr string db *sql.DB logger *log.Logger } type statusRecorder struct { http.ResponseWriter status int } func NewServer(addr string, db *sql.DB, logger *log.Logger) *Server { return &Server{ addr, db, logger, } } func (s *Server) Start() error { router := mux.NewRouter() router.Use(s.requestLoggerMiddleware) router.Use(s.contextWithTimeoutMiddleware) server := &http.Server{ Addr: s.addr, WriteTimeout: 5 * time.Second, ReadTimeout: 10 * time.Second, IdleTimeout: 30 * time.Second, Handler: router, } router.HandleFunc("/healthcheck", s.handleHomeRoute) router.HandleFunc("/", s.handleHealthCheckRoute) s.logger.Info(fmt.Sprintf("app server started at 0.0.0.0%s", s.addr)) return server.ListenAndServe() } func (s *Server) contextWithTimeoutMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithTimeout(r.Context(), time.Second*30) defer cancel() r = r.WithContext(ctx) next.ServeHTTP(w, r) }) } func (s *Server) requestLoggerMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { recorder := statusRecorder{ ResponseWriter: w, status: http.StatusOK, } next.ServeHTTP(recorder, r) var ( ip = r.RemoteAddr method = r.Method url = r.URL.String() proto = r.Proto timestamp = time.Now().Format(time.RFC850) ) s.logger.Info(fmt.Sprintf("%s [%s] %s %s %s %d", ip, timestamp, method, url, proto, recorder.status)) }) }