Files
2025-11-28 18:21:25 +01:00

135 lines
3.7 KiB
Go

// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package server
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"log/slog"
"os"
"os/signal"
"syscall"
"time"
)
const StopWaitTime = 5 * time.Second
// Server is an interface that defines the methods to start and stop a server.
type Server interface {
// Start starts the server.
Start() error
// Stop stops the server.
Stop() error
}
// Config is a struct that contains the configuration for the server.
type Config struct {
Host string `env:"HOST" envDefault:"localhost"`
Port string `env:"PORT" envDefault:""`
CertFile string `env:"SERVER_CERT" envDefault:""`
KeyFile string `env:"SERVER_KEY" envDefault:""`
ServerCAFile string `env:"SERVER_CA_CERTS" envDefault:""`
ClientCAFile string `env:"CLIENT_CA_CERTS" envDefault:""`
ReadTimeout time.Duration `env:"SERVER_READ_TIMEOUT" envDefault:"15s"`
WriteTimeout time.Duration `env:"SERVER_WRITE_TIMEOUT" envDefault:"15s"`
ReadHeaderTimeout time.Duration `env:"SERVER_READ_HEADER_TIMEOUT" envDefault:"5s"`
IdleTimeout time.Duration `env:"SERVER_IDLE_TIMEOUT" envDefault:"60s"`
MaxHeaderBytes int `env:"SERVER_MAX_HEADER_BYTES" envDefault:"1048576"` // 1 << 20
}
type BaseServer struct {
Ctx context.Context
Cancel context.CancelFunc
Name string
Address string
Config Config
Logger *slog.Logger
Protocol string
}
func NewBaseServer(ctx context.Context, cancel context.CancelFunc, name string, config Config, logger *slog.Logger) BaseServer {
address := fmt.Sprintf("%s:%s", config.Host, config.Port)
return BaseServer{
Ctx: ctx,
Cancel: cancel,
Name: name,
Address: address,
Config: config,
Logger: logger,
}
}
func stopAllServer(servers ...Server) error {
var err error
for _, server := range servers {
err1 := server.Stop()
if err1 != nil {
if err == nil {
err = fmt.Errorf("%w", err1)
} else {
err = fmt.Errorf("%v ; %w", err, err1)
}
}
}
return err
}
// StopSignalHandler stops the server when a signal is received.
func StopSignalHandler(ctx context.Context, cancel context.CancelFunc, logger *slog.Logger, svcName string, servers ...Server) error {
var err error
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGABRT)
select {
case sig := <-c:
defer cancel()
err = stopAllServer(servers...)
if err != nil {
logger.Error(fmt.Sprintf("%s service error during shutdown: %v", svcName, err))
}
logger.Info(fmt.Sprintf("%s service shutdown by signal: %s", svcName, sig))
return err
case <-ctx.Done():
return nil
}
}
func ReadFileOrData(input string) ([]byte, error) {
if _, err := os.Stat(input); err == nil {
return os.ReadFile(input)
}
return []byte(input), nil
}
func LoadX509KeyPair(certFile, keyFile string) (tls.Certificate, error) {
cert, err := ReadFileOrData(certFile)
if err != nil {
return tls.Certificate{}, fmt.Errorf("failed to read cert: %v", err)
}
key, err := ReadFileOrData(keyFile)
if err != nil {
return tls.Certificate{}, fmt.Errorf("failed to read key: %v", err)
}
return tls.X509KeyPair(cert, key)
}
func LoadRootCACerts(input string) (*x509.CertPool, error) {
pemData, err := ReadFileOrData(input)
if err != nil {
return nil, fmt.Errorf("failed to load root CA data: %w", err)
}
if len(pemData) > 0 {
capool := x509.NewCertPool()
if !capool.AppendCertsFromPEM(pemData) {
return nil, fmt.Errorf("failed to append root ca to tls.Config")
}
return capool, nil
}
return nil, nil
}