208 lines
5.4 KiB
Go
208 lines
5.4 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
"gitea.dwysokinski.me/twhelp/corev3/internal/adapter"
|
|
"gitea.dwysokinski.me/twhelp/corev3/internal/app"
|
|
"gitea.dwysokinski.me/twhelp/corev3/internal/chislog"
|
|
"gitea.dwysokinski.me/twhelp/corev3/internal/health"
|
|
"gitea.dwysokinski.me/twhelp/corev3/internal/port"
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/go-chi/chi/v5/middleware"
|
|
"github.com/urfave/cli/v2"
|
|
)
|
|
|
|
var (
|
|
apiServerPortFlag = &cli.UintFlag{
|
|
Name: "api.port",
|
|
EnvVars: []string{"API_PORT"},
|
|
Value: 9234, //nolint:gomnd
|
|
}
|
|
apiServerReadTimeoutFlag = &cli.DurationFlag{
|
|
Name: "api.readTimeout",
|
|
EnvVars: []string{"API_READ_TIMEOUT"},
|
|
Value: 5 * time.Second, //nolint:gomnd
|
|
}
|
|
apiServerReadHeaderTimeoutFlag = &cli.DurationFlag{
|
|
Name: "api.readHeaderTimeout",
|
|
EnvVars: []string{"API_READ_HEADER_TIMEOUT"},
|
|
Value: time.Second,
|
|
}
|
|
apiServerWriteTimeoutFlag = &cli.DurationFlag{
|
|
Name: "api.writeTimeout",
|
|
EnvVars: []string{"API_WRITE_TIMEOUT"},
|
|
Value: 10 * time.Second, //nolint:gomnd
|
|
}
|
|
apiServerIdleTimeoutFlag = &cli.DurationFlag{
|
|
Name: "api.idleTimeout",
|
|
EnvVars: []string{"API_IDLE_TIMEOUT"},
|
|
Value: 180 * time.Second, //nolint:gomnd
|
|
}
|
|
apiServerShutdownTimeoutFlag = &cli.DurationFlag{
|
|
Name: "api.shutdownTimeout",
|
|
EnvVars: []string{"API_SHUTDOWN_TIMEOUT"},
|
|
Value: 10 * time.Second, //nolint:gomnd
|
|
}
|
|
apiServerOpenAPIEnabledFlag = &cli.BoolFlag{
|
|
Name: "api.openApi.enabled",
|
|
EnvVars: []string{"API_OPENAPI_ENABLED"},
|
|
Value: true,
|
|
}
|
|
apiServerOpenAPISwaggerEnabledFlag = &cli.BoolFlag{
|
|
Name: "api.openApi.swaggerEnabled",
|
|
EnvVars: []string{"API_OPENAPI_SWAGGER_ENABLED"},
|
|
Value: true,
|
|
}
|
|
apiServerOpenAPIServersFlag = &cli.StringSliceFlag{
|
|
Name: "api.openApi.servers",
|
|
EnvVars: []string{"API_OPENAPI_SERVERS"},
|
|
}
|
|
apiServerFlags = []cli.Flag{
|
|
apiServerPortFlag,
|
|
apiServerReadTimeoutFlag,
|
|
apiServerReadHeaderTimeoutFlag,
|
|
apiServerWriteTimeoutFlag,
|
|
apiServerIdleTimeoutFlag,
|
|
apiServerOpenAPIEnabledFlag,
|
|
apiServerOpenAPISwaggerEnabledFlag,
|
|
apiServerOpenAPIServersFlag,
|
|
}
|
|
)
|
|
|
|
const (
|
|
apiBasePath = "/api"
|
|
metaBasePath = "/_meta"
|
|
)
|
|
|
|
var cmdServe = &cli.Command{
|
|
Name: "serve",
|
|
Usage: "Run the HTTP server",
|
|
Flags: concatSlices(apiServerFlags, dbFlags),
|
|
Action: func(c *cli.Context) error {
|
|
logger := loggerFromCtx(c.Context)
|
|
|
|
// deps
|
|
bunDB, err := newBunDBFromFlags(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
logger.Debug("closing db connections...", slog.Int("db.openConnections", bunDB.Stats().OpenConnections))
|
|
if dbCloseErr := bunDB.Close(); dbCloseErr != nil {
|
|
logger.Warn("couldn't close db connections", slog.Any("error", dbCloseErr))
|
|
} else {
|
|
logger.Debug("db connections closed")
|
|
}
|
|
}()
|
|
|
|
// adapters
|
|
versionRepo := adapter.NewVersionBunRepository(bunDB)
|
|
serverRepo := adapter.NewServerBunRepository(bunDB)
|
|
|
|
// services
|
|
versionSvc := app.NewVersionService(versionRepo)
|
|
serverSvc := app.NewServerService(serverRepo, nil, nil)
|
|
|
|
// health
|
|
h := health.New()
|
|
|
|
server, err := newHTTPServer(
|
|
httpServerConfig{
|
|
port: c.Uint(apiServerPortFlag.Name),
|
|
readTimeout: c.Duration(apiServerReadTimeoutFlag.Name),
|
|
readHeaderTimeout: c.Duration(apiServerReadHeaderTimeoutFlag.Name),
|
|
writeTimeout: c.Duration(apiServerWriteTimeoutFlag.Name),
|
|
idleTimeout: c.Duration(apiServerIdleTimeoutFlag.Name),
|
|
},
|
|
func(r chi.Router) error {
|
|
oapiCfg, oapiCfgErr := newOpenAPIConfigFromFlags(c)
|
|
if oapiCfgErr != nil {
|
|
return oapiCfgErr
|
|
}
|
|
|
|
r.Use(newAPIMiddlewares(logger)...)
|
|
|
|
r.Mount(metaBasePath, port.NewMetaHTTPHandler(h))
|
|
|
|
r.Mount(apiBasePath, port.NewAPIHTTPHandler(
|
|
versionSvc,
|
|
serverSvc,
|
|
port.WithOpenAPIConfig(oapiCfg),
|
|
))
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("couldn't construct HTTP server: %w", err)
|
|
}
|
|
|
|
idleConnsClosed := make(chan struct{})
|
|
go func() {
|
|
waitForShutdownSignal(c.Context)
|
|
|
|
logger.Debug("received shutdown signal")
|
|
|
|
ctx, cancel := context.WithTimeout(c.Context, c.Duration(apiServerShutdownTimeoutFlag.Name))
|
|
defer cancel()
|
|
|
|
if err := server.Shutdown(ctx); err != nil {
|
|
logger.Warn("shutdown failed", slog.Any("error", err))
|
|
}
|
|
|
|
close(idleConnsClosed)
|
|
}()
|
|
|
|
logger.Info("Server is listening on the addr "+server.Addr, slog.String("addr", server.Addr))
|
|
|
|
if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
|
return fmt.Errorf("ListenAndServe failed: %w", err)
|
|
}
|
|
|
|
<-idleConnsClosed
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
func newOpenAPIConfigFromFlags(c *cli.Context) (port.OpenAPIConfig, error) {
|
|
rawURLs := c.StringSlice(apiServerOpenAPIServersFlag.Name)
|
|
|
|
servers := make([]port.OpenAPIConfigServer, 0, len(rawURLs))
|
|
|
|
for _, raw := range rawURLs {
|
|
u, err := url.Parse(raw)
|
|
if err != nil {
|
|
return port.OpenAPIConfig{}, err
|
|
}
|
|
|
|
servers = append(servers, port.OpenAPIConfigServer{
|
|
URL: u,
|
|
})
|
|
}
|
|
|
|
return port.OpenAPIConfig{
|
|
Enabled: c.Bool(apiServerOpenAPIEnabledFlag.Name),
|
|
SwaggerEnabled: c.Bool(apiServerOpenAPISwaggerEnabledFlag.Name),
|
|
BasePath: apiBasePath,
|
|
Servers: servers,
|
|
}, nil
|
|
}
|
|
|
|
func newAPIMiddlewares(logger *slog.Logger) chi.Middlewares {
|
|
return chi.Middlewares{
|
|
chislog.Logger(logger, chislog.WithFilter(func(r *http.Request) bool {
|
|
return !strings.HasPrefix(r.URL.Path, metaBasePath)
|
|
})),
|
|
middleware.Recoverer,
|
|
}
|
|
}
|