Skip to content
latest
Framework Packages / latest

Server

There is no separate pkg/server module in v2.0.0-beta.1. The HTTP listener lives in github.com/mbvlabs/andurel/pkg/anduin as anduin.Server, next to the Router and the rest of the HTTP layer.

Server owns one listener and nothing else. Dispatch belongs to Router, so a test can exercise routes with httptest and only a real process pays for a socket. See Routing for the router and Framework Packages for the package boundaries.

Construction

anduin.NewServer takes the process context, a listen host and port, an http.Handler, and optional timeout settings. The context becomes the base context of every request.

 1app.Server = anduin.NewServer(
 2    ctx,
 3    cfg.HTTP.Host,
 4    cfg.HTTP.Port,
 5    app.Handler,
 6    anduin.WithTimeouts(
 7        cfg.HTTP.IdleTimeout,
 8        cfg.HTTP.ReadTimeout,
 9        cfg.HTTP.WriteTimeout,
10    ),
11)

In a generated application, app.Handler is router.Handler() and the server is built inside runtime.New. Nothing else should construct a listener.

Timeout policy

1type ServerOptions struct {
2    IdleTimeout  time.Duration
3    ReadTimeout  time.Duration
4    WriteTimeout time.Duration
5}
6
7func DefaultConfig() ServerOptions
8func (config ServerOptions) Validate() error
9func WithTimeouts(idle, read, write time.Duration) ServerOption

DefaultConfig sets a 120-second idle timeout, a 10-second read timeout and a 30-second write timeout. A zero value disables that bound. ServerOptions.Validate rejects negative values; NewServer applies options but does not call it, so validate timeout values where they come from configuration, as config.NewHTTP does.

Read timeout bounds request ingestion, write timeout bounds response writing, and idle timeout bounds idle keep-alive connections. Do not disable a global bound to accommodate one long-lived route. Streams and uploads usually deserve their own endpoint and a deliberate timeout strategy.

Lifecycle

Start blocks while serving and treats http.ErrServerClosed as a normal stop; any other listen failure is returned wrapped as anduin: server listen.

Shutdown gracefully stops the listener: it stops accepting connections and waits for in-flight requests until the context deadline. It does not close the database, cache or other dependencies. The process owns that ordering.

cmd/app creates a signal-aware context, builds the app, starts the server through lifecycle.Run, and releases dependencies afterwards:

 1ctx, cancel := lifecycle.NotifyContext(context.Background())
 2defer cancel()
 3
 4app, err := runtime.New(ctx, cfg)
 5if err != nil {
 6    return err
 7}
 8defer closeApp(app)
 9
10return lifecycle.Run(ctx, func(context.Context) error {
11    return app.Start()
12}, app.Shutdown, lifecycle.WithBlockingStart())

WithBlockingStart is what a server needs: Start does not return until shutdown begins. runtime.App.Shutdown calls Server.Shutdown, and runtime.App.Close releases the remaining dependencies in reverse construction order. cmd/app defers Close so it runs even when serving fails.

anduin.Shutdowner is the shared shape for anything that stops within a deadline:

1type Shutdowner interface {
2    Shutdown(ctx context.Context) error
3}

lifecycle.Stack applies the same idea to arbitrary cleanup functions; use it for components that do not expose Shutdown.

Addresses and readiness

Host and port describe the local listener, not the public URL. Configure the public origin, protocol and hostnames separately; TLS commonly terminates at a proxy or platform.

The scaffold checks dependencies with a handler rather than a second server:

1func NewAPIHandler(health interface{ Health(context.Context) error }) APIHandler {
2    return APIHandler{health: anduin.Health(health)}
3}
4
5func (a APIHandler) Register(rt *anduin.Router) {
6    rt.Get(routes.Health.Path(), a.health)
7}

anduin.Health answers with JSON and returns 503 when its checker fails. storage.Connection.Health is the usual checker. Keep readiness meaningful and bounded, and let liveness report process health without failing for every transient dependency outage.

Testing and extension

Test handlers through pkg/anduin/anduintest or httptest instead of binding a port. Bind a real port only for connection, timeout or shutdown behavior. When a long-lived dependency needs coordinated shutdown, give its constructor a stop method and let internal/runtime decide ordering and the overall deadline.