error-pages/internal/http/server.go

148 lines
3.9 KiB
Go
Raw Normal View History

2021-09-29 15:38:50 +00:00
package http
import (
2024-07-03 14:12:13 +00:00
"context"
"errors"
"fmt"
"net"
2024-07-03 14:12:13 +00:00
"net/http"
"strings"
2021-09-29 15:38:50 +00:00
"time"
"github.com/valyala/fasthttp"
2024-07-03 14:12:13 +00:00
"gh.tarampamp.am/error-pages/internal/appmeta"
"gh.tarampamp.am/error-pages/internal/config"
2024-07-03 14:12:13 +00:00
ep "gh.tarampamp.am/error-pages/internal/http/handlers/error_page"
"gh.tarampamp.am/error-pages/internal/http/handlers/live"
"gh.tarampamp.am/error-pages/internal/http/handlers/static"
"gh.tarampamp.am/error-pages/internal/http/handlers/version"
"gh.tarampamp.am/error-pages/internal/http/middleware/logreq"
"gh.tarampamp.am/error-pages/internal/logger"
2021-09-29 15:38:50 +00:00
)
2024-07-03 14:12:13 +00:00
// Server is an HTTP server for serving error pages.
2021-09-29 15:38:50 +00:00
type Server struct {
2024-07-03 14:12:13 +00:00
log *logger.Logger
server *fasthttp.Server
beforeStop func()
2021-09-29 15:38:50 +00:00
}
2024-07-03 14:12:13 +00:00
// NewServer creates a new HTTP server.
func NewServer(log *logger.Logger, readBufferSize uint) Server {
const (
readTimeout = 30 * time.Second
writeTimeout = readTimeout + 10*time.Second // should be bigger than the read timeout
)
2021-09-29 15:38:50 +00:00
return Server{
2024-07-03 14:12:13 +00:00
log: log,
server: &fasthttp.Server{
ReadTimeout: readTimeout,
WriteTimeout: writeTimeout,
ReadBufferSize: int(readBufferSize),
DisablePreParseMultipartForm: true,
NoDefaultServerHeader: true,
CloseOnShutdown: true,
Logger: logger.NewStdLog(log),
2021-09-29 15:38:50 +00:00
},
2024-07-03 14:12:13 +00:00
beforeStop: func() {}, // noop
2021-09-29 15:38:50 +00:00
}
}
2024-07-03 14:12:13 +00:00
// Register server handlers, middlewares, etc.
func (s *Server) Register(cfg *config.Config) error { //nolint:funlen
var (
liveHandler = live.New()
versionHandler = version.New(appmeta.Version())
faviconHandler = static.New(static.Favicon)
errorPagesHandler, closeCache = ep.New(cfg, s.log)
notFound = http.StatusText(http.StatusNotFound) + "\n"
notAllowed = http.StatusText(http.StatusMethodNotAllowed) + "\n"
)
// wrap the before shutdown function to close the cache
s.beforeStop = closeCache
s.server.Handler = func(ctx *fasthttp.RequestCtx) {
var url, method = string(ctx.Path()), string(ctx.Method())
switch {
// live endpoints
case url == "/healthz" || url == "/health/live" || url == "/health" || url == "/live":
liveHandler(ctx)
// version endpoint
case url == "/version":
versionHandler(ctx)
// favicon.ico endpoint
case url == "/favicon.ico":
faviconHandler(ctx)
// error pages endpoints:
// - /
// - /{code}.html
// - /{code}.htm
// - /{code}
//
// the HTTP method is not limited to GET and HEAD - it can be any
case url == "/" || ep.URLContainsCode(url) || ep.HeadersContainCode(&ctx.Request.Header):
errorPagesHandler(ctx)
// wrong requests handling
default:
switch {
case method == fasthttp.MethodHead:
ctx.Error(notAllowed, fasthttp.StatusNotFound)
case method == fasthttp.MethodGet:
ctx.Error(notFound, fasthttp.StatusNotFound)
default:
ctx.Error(notAllowed, fasthttp.StatusMethodNotAllowed)
}
}
}
// apply middleware
s.server.Handler = logreq.New(s.log, func(ctx *fasthttp.RequestCtx) bool {
// skip logging healthcheck and .ico (favicon) requests
return strings.Contains(strings.ToLower(string(ctx.UserAgent())), "healthcheck") ||
strings.HasSuffix(string(ctx.Path()), ".ico")
})(s.server.Handler)
return nil
}
2021-09-29 15:38:50 +00:00
// Start server.
func (s *Server) Start(ip string, port uint16) (err error) {
if net.ParseIP(ip) == nil {
return errors.New("invalid IP address")
}
var ln net.Listener
2024-07-03 14:12:13 +00:00
if strings.Count(ip, ":") >= 2 { //nolint:mnd // ipv6
if ln, err = net.Listen("tcp6", fmt.Sprintf("[%s]:%d", ip, port)); err != nil {
return err
}
} else { // ipv4
if ln, err = net.Listen("tcp4", fmt.Sprintf("%s:%d", ip, port)); err != nil {
return err
}
}
2024-07-03 14:12:13 +00:00
return s.server.Serve(ln)
}
2021-09-29 15:38:50 +00:00
2024-07-03 14:12:13 +00:00
// Stop server gracefully.
func (s *Server) Stop(timeout time.Duration) error {
var ctx, cancel = context.WithTimeout(context.Background(), timeout)
defer cancel()
2022-01-28 15:42:08 +00:00
2024-07-03 14:12:13 +00:00
s.beforeStop()
2024-07-03 14:12:13 +00:00
return s.server.ShutdownWithContext(ctx)
}