v0.3.0
Zinc 0.3.0
Zinc 0.3 is a hardening release. It tightens security defaults, bounds memory in stateful middleware, and makes routing, binding, and response handling behave the same way on every path, while recovering the performance that correctness work cost.
This is an intentional pre-1.0 breaking release. Few signatures change, so most applications compile unchanged, but several behaviours are stricter. Read the migration guide before upgrading. In particular:
- Session cookies from 0.2 are invalidated, so users must sign in again. Secrets need at least 32 random bytes, and
Session.SetandDeletenow return errors. - Prometheus registries are per middleware, not shared globally. A
/metricsroute outside that middleware needs an explicit registry. BodyLimit(4 MiB by default) now covers forms and multipart uploads.- Unsafe configurations panic at startup: wildcard CORS origins with credentials, invalid trusted proxies, short session secrets, and invalid rate-limiter settings.
Security
c.IP()walks forwarded chains from the right and stops at the first untrusted hop, so clients can no longer choose their own address.Scheme()accepts onlyhttporhttpsfrom trusted proxies.- Credentialed CORS requires explicit origins.
- Signed sessions carry an authenticated, server-checked expiry and support key rotation with
PreviousSecrets. - Group middleware, including authorization, now applies to
Static,File, andMountregistered on the group, and to requests rewritten into a prefix. - Static serving rejects ambiguous paths and keeps symlinks inside the root.
- The reverse proxy strips hop-by-hop headers before
Directorruns and retries only idempotent, replayable requests by default.
Bounded resources
- Request-body budgets apply to every binding and upload path, and to decompressed bodies.
- Keyed rate limiters hold at most
MaxKeysbuckets (10,000 by default) and expire idle ones. - Prometheus label sets are bounded, with a series cap and a
zinc_http_metrics_dropped_totalcounter. - Oversized route-cache keys bypass the cache instead of growing it.
Correctness
- A response commits only when bytes, final headers, a flush, or a hijack go out. The error handler runs at most once per request.
- Binding failures return a generic
400and keep HTTP causes such as413.Bind().Allmerges path and query values for every struct body format, and fields acceptencoding.TextUnmarshalerand optional pointers. - Routing precedence is consistent under case-insensitive matching. Mounted handlers see a consistently stripped path and
RequestURI.app.URLproduces URLs that route back to the same values. - Gzip, Recover, and Accept negotiation handle edge cases correctly: small flushes,
http.ErrAbortHandler, andq=0exclusions.
Performance
The hardening work added some overhead. This release recovers it with faster query and JSON binding, cached canonical header names in binding plans, lower response and default-error overhead, reuse of confined static roots, and smaller route-cache snapshots.
In the latest comparison against Gin, Echo, and Chi on an Apple M1 Pro, Zinc had the lowest median latency in 60 of 77 workloads. Zinc was measured at 42e11d2 against rival samples recorded the day before; see BENCHMARKS.md for the method and the full results. Correct per-response header ownership adds one small allocation to common string responses.
New APIs
App.Close()releases static roots when you run your ownhttp.Server.Context.BodyLimit()exposes the body budget to middleware.- Session:
PreviousSecrets,Lifetime,ErrSessionCommitted,ErrSessionTooLarge. - Rate Limiter:
MaxKeys,MaxKeyBytes,IdleTTL,Now. NewPrometheusMetrics(maxSeries).
Documentation and tooling
- A redesigned documentation site at zinc.carbonsoft.sh, with an interactive route tree, a middleware table that builds your
app.Usechain in the right order, and results read straight from the benchmark report. - A new Migrating to 0.3 guide, and corrected pages on client IPs, CORS, and sessions.
zincbench, a Go tool for recording benchmark runs locally and comparing them against a baseline.
Breaking-change checklist
- Warn users that 0.2 session cookies are invalidated, and use a session secret of at least 32 random bytes.
- Check the errors from
Session.SetandDelete, and call them before writing the response. - Replace
AllowOrigins: ["*"]with explicit origins wherever CORS allows credentials. - List every proxy you operate in
TrustedProxies, and check the IPs you log and rate-limit by. - Pass one explicit registry to
PrometheusandPrometheusHandlerif/metricsis not behind that middleware. - Size
MaxKeysfor keyed rate limiters. - Raise
Config.BodyLimitif you accept forms or uploads over 4 MiB. - Return your own errors or set
ErrorHandlerif clients read binding error details. - Replace type assertions on
c.Writer()withhttp.NewResponseControllerorUnwrap(). - Move public assets off authenticated groups, and call
app.Close()when you run your own server.
See the migration guide for details.
Full changelog: v0.2.1...v0.3.0