126 lines
3.0 KiB
Go
126 lines
3.0 KiB
Go
package bootstrap
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
|
|
"spark-server/internal/infrastructure/id"
|
|
"spark-server/internal/infrastructure/logger"
|
|
"spark-server/internal/infrastructure/mysql"
|
|
redisx "spark-server/internal/infrastructure/redis"
|
|
httpx "spark-server/internal/transport/http"
|
|
tcp "spark-server/internal/transport/tcp"
|
|
"spark-server/internal/transport/tcp/session"
|
|
)
|
|
|
|
type App struct {
|
|
cfg *Config
|
|
log *slog.Logger
|
|
http *httpx.Server
|
|
tcp *tcp.Server
|
|
pool tcp.TaskPool
|
|
mysql *mysql.DB
|
|
redis *redisx.Client
|
|
idgen *id.Generator
|
|
sessions session.Manager
|
|
}
|
|
|
|
func New(cfg *Config) (*App, error) {
|
|
log := logger.New()
|
|
idgen, err := id.New(cfg.ID.SnowflakeWorkerID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
db, err := mysql.Open(cfg.MySQL.DSN)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rdb, err := redisx.Open(cfg.Redis.Addr)
|
|
if err != nil {
|
|
_ = db.Close()
|
|
return nil, err
|
|
}
|
|
pool := tcp.NewTaskPool(cfg.TaskPool.Size, cfg.TaskPool.Queue)
|
|
sessions := session.NewManager()
|
|
tlsConf, err := loadTLS(cfg.Server.TCP.TLS)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tcpSrv := tcp.NewServer(log, pool, sessions, &tcp.Options{
|
|
Addr: cfg.Server.TCP.Addr,
|
|
Multicore: cfg.Server.TCP.Multicore,
|
|
KeepAlive: cfg.Server.TCP.KeepAlive,
|
|
RecvBuffer: cfg.Server.TCP.RecvBuffer,
|
|
SendBuffer: cfg.Server.TCP.SendBuffer,
|
|
MaxFrameSize: cfg.Server.TCP.MaxFrameSize,
|
|
TLS: tlsConf,
|
|
ProxyProtocol: cfg.Server.TCP.ProxyProtocol,
|
|
IdleTimeout: cfg.Server.TCP.IdleTimeout,
|
|
OutboundSize: cfg.Session.OutboundQueueSize,
|
|
RatePerSec: cfg.RateLimit.MessagePerSecond,
|
|
CompressMin: cfg.Server.TCP.CompressMinSize,
|
|
IDs: idgen,
|
|
})
|
|
return &App{
|
|
cfg: cfg,
|
|
log: log,
|
|
http: httpx.New(log, cfg.Server.HTTP.Addr),
|
|
tcp: tcpSrv,
|
|
pool: pool,
|
|
mysql: db,
|
|
redis: rdb,
|
|
idgen: idgen,
|
|
sessions: sessions,
|
|
}, nil
|
|
}
|
|
|
|
func loadTLS(t TLSConfig) (*tls.Config, error) {
|
|
if t.CertFile == "" || t.KeyFile == "" {
|
|
return nil, nil
|
|
}
|
|
cert, err := tls.LoadX509KeyPair(t.CertFile, t.KeyFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("tls: %w", err)
|
|
}
|
|
return &tls.Config{MinVersion: tls.VersionTLS12, Certificates: []tls.Certificate{cert}}, nil
|
|
}
|
|
|
|
func (a *App) Run() error {
|
|
errCh := make(chan error, 2)
|
|
go func() { errCh <- a.http.Start() }()
|
|
go func() { errCh <- a.tcp.Start() }()
|
|
|
|
sigCh := make(chan os.Signal, 1)
|
|
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
|
select {
|
|
case err := <-errCh:
|
|
return err
|
|
case sig := <-sigCh:
|
|
a.log.Info("shutdown", "signal", sig.String())
|
|
return a.Shutdown()
|
|
}
|
|
}
|
|
|
|
func (a *App) Shutdown() error {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
|
defer cancel()
|
|
var err error
|
|
if e := a.http.Stop(ctx); e != nil && !errors.Is(e, http.ErrServerClosed) {
|
|
err = errors.Join(err, e)
|
|
}
|
|
if e := a.tcp.Stop(ctx); e != nil {
|
|
err = errors.Join(err, e)
|
|
}
|
|
a.pool.Close()
|
|
err = errors.Join(err, a.redis.Close(), a.mysql.Close())
|
|
return err
|
|
}
|