-
Notifications
You must be signed in to change notification settings - Fork 0
Router Configuration
type RouterConfig struct {
Addr string
BaseURL string
ListenSSL bool
CertFile *string
KeyFile *string
TLSConfig *tls.Config
ReadHeaderTimeout time.Duration
ReadTimeout time.Duration
WriteTimeout time.Duration
IdleTimeout time.Duration
MaxHeaderBytes int
MaxHeaderValueCount int
MaxBodyBytes int64
}One of these configures one listener. The application's default router takes
Config.DefaultRouter; an extension creating its own listener passes one to
CreateRouter.
| Field | Default | Notes |
|---|---|---|
Addr |
":8080" |
any net.Listen address |
BaseURL |
"/" |
prefix for every route on this router |
BaseURL is normalised before use — no trailing slash, "" for the root — and
that normalised form is what appears in RouteInfo.BaseURL and
RouterRouteRegisteredEvent.BaseURL.
TLS is opt-in. Resolution order:
-
ListenSSL == false→ plain HTTP, whatever else is set. -
TLSConfig != nil→ used verbatim;CertFile/KeyFileare ignored. -
CertFileandKeyFileboth set → TLS from those files. - Anything else with
ListenSSLenabled is an error, not a silent downgrade to plaintext.
cfg := rextension.RouterConfig{
Addr: ":443",
ListenSSL: true,
TLSConfig: &tls.Config{
GetCertificate: certManager.GetCertificate, // per-handshake
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: pool,
},
}TLSConfig is the injection point for everything the two file paths cannot
express. Setting GetCertificate makes the certificate a per-handshake
decision, which is what allows a certificate to be replaced without restarting
the process. Client certificate verification (mTLS) is ClientAuth plus
ClientCAs — there is nowhere else to configure it.
SSLVerifyis gone, removed rather than deprecated. It configured nothing: the value was stored on the router and never read. The name invited the mistake more than once —InsecureSkipVerifyis read bytls.Client, nevertls.Server, so a listener has no such setting. What people reached for it expecting isTLSConfig.ClientAuth, above.
net/http applies no timeouts and no body limit of its own. Left unset,
every one of these is effectively unlimited: a single client holding a
connection open without completing its request headers occupies a goroutine for
as long as it cares to, and a request body is read until the client stops
sending.
Zero takes the default; negative disables.
| Field | Default | What it bounds |
|---|---|---|
ReadHeaderTimeout |
10s | time to read request headers — the Slowloris bound |
ReadTimeout |
30s | time to read the whole request, headers and body |
WriteTimeout |
0 — unset, deliberately | absolute deadline on the response |
IdleTimeout |
120s | how long a keep-alive connection may sit unused |
MaxHeaderBytes |
1 MiB | total size of request headers |
MaxHeaderValueCount |
0 → net/http's own 500 | number of header values |
MaxBodyBytes |
4 MiB | request body size |
Slowloris works by trickling headers so the request never completes and the
connection is never released. ReadHeaderTimeout is the field that closes it.
No framework default fills it in. WriteTimeout is an absolute deadline on
the whole response, not an idle timeout, so any non-zero value truncates
responses that are legitimately long-lived: server-sent events, long polling,
large downloads, and any slow client on a fast endpoint.
Slowloris is a read attack and is already closed by ReadHeaderTimeout, so
setting this buys no protection that is not already in place. Set it only on a
router you know serves nothing streaming.
A few thousand tiny headers stay well inside 1 MiB while still forcing the
server to allocate and hash every one of them. net/http applies its own limit
of 500 whether or not this field is set, so leaving it zero is already safe — it
exists so the limit is visible and adjustable alongside the other six rather
than being the one bound nothing in the configuration mentions.
MaxBodyBytes is the router's default; a route overrides it by implementing
BodyLimitedRoute:
func (r *UploadRoute) MaxBodyBytes() int64 { return 64 << 20 } // 64 MiB
func (r *IngestRoute) MaxBodyBytes() int64 { return -1 } // no limitNegative on the RouterConfig disables the cap for the whole router.
const (
DefaultReadHeaderTimeout = 10 * time.Second
DefaultReadTimeout = 30 * time.Second
DefaultIdleTimeout = 120 * time.Second
DefaultMaxHeaderBytes = 1 << 20 // 1 MiB
DefaultMaxBodyBytes = 4 << 20 // 4 MiB
DefaultMaxHeaderValueCount = 500 // mirrors net/http; not applied by the framework
)WriteTimeout has no constant, because it has no default.
Extensions that create their own router usually want different limits than the public one — a metrics or health endpoint accepts no body worth speaking of:
err := r.CreateRouter("health", rextension.RouterConfig{
Addr: ":9091",
BaseURL: "/",
ReadTimeout: 5 * time.Second,
MaxBodyBytes: 4 << 10, // 4 KiB
})
if err != nil && !errors.Is(err, rextension.ErrRouterExists) {
return err
}rextension — the Rex extension contract · MIT · © 2026 Kryovyx · pre-1.0, interfaces may change
Building an extension
Contracts
Reference
Ecosystem