112 lines
3.2 KiB
Go
112 lines
3.2 KiB
Go
package cli
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
"net"
|
|
"net/http"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
|
|
"code.icb4dc0.de/prskr/searcherside/core/services"
|
|
v1 "code.icb4dc0.de/prskr/searcherside/handlers/api/v1"
|
|
"code.icb4dc0.de/prskr/searcherside/infrastructure/api"
|
|
"code.icb4dc0.de/prskr/searcherside/internal/flags"
|
|
"code.icb4dc0.de/prskr/searcherside/internal/logging"
|
|
)
|
|
|
|
const (
|
|
jwtSecretLength = 64
|
|
)
|
|
|
|
type ServerHandler struct {
|
|
ListenAddress string `env:"LISTEN_ADDRESS" name:"listen-address" short:"a" help:"Listen address" default:":3000"`
|
|
DataDirectory string `env:"DATA_DIRECTORY" name:"data-directory" short:"d" help:"Data directory" default:"${CWD}/data"`
|
|
Config struct {
|
|
ReadHeaderTimeout time.Duration `env:"HTTP_READ_HEADER_TIMEOUT" name:"read-header-timeout" help:"Read header timeout" default:"5s"`
|
|
ShutDownTimeout time.Duration `env:"HTTP_SHUTDOWN_TIMEOUT" name:"shutdown-timeout" help:"Shutdown timeout" default:"5s"`
|
|
ParseMaxMemoryBytes int64 `env:"HTTP_PARSE_MAX_MEMORY_BYTES" name:"parse-max-memory-bytes" help:"Parse max memory bytes" default:"33554432"`
|
|
} `embed:"" prefix:"http."`
|
|
Auth struct {
|
|
JwtSecret flags.HexString `env:"AUTH_JWT_SECRET" name:"jwt-secret" help:"JWT secret"`
|
|
} `embed:"" prefix:"auth."`
|
|
}
|
|
|
|
func (h *ServerHandler) Run(ctx context.Context, logger *slog.Logger) error {
|
|
indexCurator, err := services.NewFileIndexCurator(
|
|
filepath.Join(h.DataDirectory, "searcherside.json"),
|
|
services.BleveIndexer{DataDirectory: h.DataDirectory},
|
|
services.TarZSTIndexArchiver{DataDirectory: h.DataDirectory},
|
|
)
|
|
|
|
if err != nil {
|
|
logger.Error("Failed to create index curator", logging.Error(err))
|
|
return err
|
|
}
|
|
|
|
secret, err := h.jwtSecret()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
r := chi.NewRouter()
|
|
r.Use(api.LoggingMiddleware)
|
|
|
|
r.Route("/api/v1", func(r chi.Router) {
|
|
indexHandler := v1.IndexHandler{
|
|
MaxMemoryBytes: h.Config.ParseMaxMemoryBytes,
|
|
Indexer: indexCurator,
|
|
}
|
|
|
|
searchHandler := v1.SearchHandler{
|
|
Curator: indexCurator,
|
|
}
|
|
|
|
v1.Mount(r, secret, indexHandler, searchHandler)
|
|
})
|
|
|
|
srv := http.Server{
|
|
Addr: h.ListenAddress,
|
|
Handler: r,
|
|
ReadHeaderTimeout: h.Config.ReadHeaderTimeout,
|
|
BaseContext: func(listener net.Listener) context.Context {
|
|
return logging.ContextWithLogger(ctx, logger)
|
|
},
|
|
}
|
|
|
|
logger.Info("Starting server", slog.String("address", h.ListenAddress))
|
|
|
|
go func() {
|
|
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
|
logger.Error("Failed to start server", logging.Error(err))
|
|
}
|
|
}()
|
|
|
|
<-ctx.Done()
|
|
logger.Info("Shutting down server")
|
|
shutdownCtx, cancel := context.WithTimeout(context.Background(), h.Config.ShutDownTimeout)
|
|
if err := srv.Shutdown(shutdownCtx); err != nil {
|
|
logger.Error("Failed to shutdown server", logging.Error(err))
|
|
}
|
|
cancel()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (h *ServerHandler) jwtSecret() ([]byte, error) {
|
|
if len(h.Auth.JwtSecret) == 0 {
|
|
h.Auth.JwtSecret = make([]byte, jwtSecretLength)
|
|
if n, err := rand.Read(h.Auth.JwtSecret); err != nil {
|
|
return nil, err
|
|
} else if n != jwtSecretLength {
|
|
return nil, fmt.Errorf("expected to read %d random bytes but got %d", jwtSecretLength, n)
|
|
}
|
|
}
|
|
|
|
return h.Auth.JwtSecret, nil
|
|
}
|