v0.94.0
Salvo 0.94.0 focuses on security hardening, OpenAPI 3.1 correctness, lower runtime overhead in core routing/dispatch paths, and API naming cleanup. This release also raises the Rust MSRV to 1.94.
Highlights
- Rust MSRV is now
1.94. - Core routing and dispatch were reworked to reduce per-request allocations and improve path parameter rollback.
- Server lifecycle control is clearer, with
Server::max_connections,ConnCtrl, improved graceful/forceful shutdown behavior, and safer default connection fuse protection. - OpenAPI generation is closer to OpenAPI 3.1:
jsonSchemaDialect, webhooks, reusable components, PathItem refs, parameter content/examples/allowEmptyValue, and stricter required path parameters. - Security hardening landed across proxying, CORS, CSRF, static files, default error pages, JWT/OIDC, WebSocket upgrades, tus uploads, cookies, and ACME.
- Response rendering semantics are documented and tightened: text-like scribes append, while
Json<T>now replaces previously buffered body bytes because JSON is a complete document. salvo-tusexposes storage/locking types, addsTus::storage_root, and addsTus::absolute_locationfor safe absolute Location URLs.
Migration Guide
Rust
Update your toolchain to Rust 1.94 or newer.
rustup update stableCore API naming
Several old names remain as deprecated aliases, but new code should use the clearer names:
| Old | New |
|---|---|
Depot::inject(value)
| Depot::insert_typed(value)
|
Depot::obtain::<T>()
| Depot::get_typed::<T>()
|
Depot::obtain_mut::<T>()
| Depot::get_typed_mut::<T>()
|
Depot::contains::<T>()
| Depot::contains_typed::<T>()
|
Depot::scrape::<T>()
| Depot::remove_typed::<T>()
|
Depot::delete(key)
| Depot::remove(key).is_some()
|
Response::stuff(status, value)
| Response::render_with_status(status, value)
|
Server::stop_forcible()
| Server::stop_forceful()
|
ServerHandle::stop_forcible()
| ServerHandle::stop_forceful()
|
SchemeFilter::lack(...)
| SchemeFilter::fallback(...)
|
HostFilter::lack(...)
| HostFilter::fallback(...)
|
PortFilter::lack(...)
| PortFilter::fallback(...)
|
StatusError::request_header_fields_toolarge()
| StatusError::request_header_fields_too_large()
|
StatusError::unavailable_for_legalreasons()
| StatusError::unavailable_for_legal_reasons()
|
AcmeListener::get_directory(...)
| AcmeListener::directory(...)
|
Depot now has separate named and type-keyed storage. Use insert/get/remove for string keys and insert_typed/get_typed/remove_typed for type keys. Capacity and inner() refer to named storage.
Response body rendering
Response::write_body appends to buffered bodies. Json<T> now replaces existing buffered bytes instead of appending, because concatenated JSON documents are invalid. If you intentionally emit NDJSON or another appendable format, serialize each record yourself and call write_body.
salvo-tus builder methods
The tus builder API was aligned with Rust API naming guidelines. Update call sites:
| Old | New |
|---|---|
with_store(...)
| store(...)
|
with_locker(...)
| locker(...)
|
with_upload_id_naming_function(...)
| upload_id_naming_function(...)
|
with_generate_url_function(...)
| generate_url_function(...)
|
with_on_incoming_request(...)
| on_incoming_request(...)
|
with_on_incoming_request_sync(...)
| on_incoming_request_sync(...)
|
with_on_upload_create(...)
| on_upload_create(...)
|
with_on_upload_finish(...)
| on_upload_finish(...)
|
Also prefer MaybeUploadId over the deprecated UploadId alias. TusOptions::extract_file_id_from_request replaces the old file-id helper naming.
For absolute tus Location headers, prefer:
let tus = Tus::new().absolute_location("https://uploads.example.com");Avoid relative_location(false) without a canonical origin on public services, because that derives absolute URLs from request host/forwarded headers.
OpenAPI changes
- Top-level
$schemaoutput was corrected tojsonSchemaDialectfor OpenAPI 3.1. Parameter::parameter_in(...)is deprecated; useParameter::location(...).ToParametersnow defaults generated parameters to query location, so required query parameters are represented correctly.- Path parameters are now always serialized as
required: true. - Routes without an HTTP method filter are skipped during OpenAPI generation instead of being fanned out to every method.
#[derive(Extractible)]now rejects#[serde(flatten)]on fields. Use#[salvo(extract(flatten))]for Salvo request extraction flattening.
CORS, proxy, JWT/OIDC, and cookies
- CORS now rejects or strips unsafe combinations of
Access-Control-Allow-Credentials: truewith wildcard CORS response headers. Use explicit allowed origins, headers, and methods for credentialed APIs. ForwardedHeaderIssueris deprecated because it trusts client-controlled forwarded headers unconditionally. UseTrustedProxyIssuer::new([...])behind known proxies, orRemoteIpIssuerfor direct client IPs.Proxynow uses the standards-compliant host header getter by default, including non-default upstream ports. If you need the old bare-host behavior, configuredefault_host_header_getter.- OIDC JWKS symmetric keys (
kty: "oct"/ HS*) are rejected by default. Opt in withallow_symmetric_jwks(true)only for issuers that intentionally publish trusted symmetric keys. SecureCookiePolicyis available for CSRF, flash, and session cookies. If TLS terminates before Salvo and the request scheme appears as HTTP, force secure cookies with the relevantsecure_cookie(true)or policy API.
Notable Changes
Core and server
- Added
Server::max_connectionsto bound concurrent accepted connections. - Reworked connection protection, lifecycle control, and router matching for lower overhead and clearer shutdown behavior.
- Added
ConnCtrlto the prelude and public exports. - Improved routing parameter rollback, wildcard capture handling, host/port filtering for IPv6, and matched-path handling.
- Added response render API clarity and deprecated awkward or misleading names.
OpenAPI
- Completed several OpenAPI 3.1 reusable component types.
- Added top-level webhooks and PathItem
$refsupport. - Added Parameter Object
content,examples, andallowEmptyValue. - Fixed schema generation for inline generic schemas, request body refs, generic
Extractiblebounds, number equality, macro diagnostics, and several user-triggerable macro panics. - Added endpoint attribute aliases and accepted singular/plural parameter keys interchangeably.
Security hardening
- Escaped the default error page and Swagger UI/static directory generated output to reduce reflected XSS risk.
- Switched CSRF token verification to constant-time comparison.
- Hardened proxy path normalization, upgrade handling, host headers, body error propagation, and optional authorization forwarding.
- Added WebSocket Origin checking APIs to mitigate CSWSH on cookie-authenticated WebSocket endpoints.
- Hardened StaticDir root boundary checks and precompressed file handling.
- Tightened tus CORS handling, upload id validation, expiration handling, offset overflow checks, finalized-file protection, and absolute Location URL guidance.
- Bounded OIDC/JWKS fetch sizes and cache behavior, improved OIDC revalidation races, and avoided leaking sensitive panic/error payloads.
- Added safer secure-cookie policy handling for session, flash, and CSRF cookies.
Fixes
- Fixed
Set-Cookieparsing/round-tripping and tower-compatible response conversion. - Fixed cache behavior that could swallow requests or cache non-success responses.
- Fixed conditional request ordering, multi-range response handling, IPv6 authority parsing, ETag formatting, and range streaming.
- Fixed multipart parsing edge cases, missing boundary handling, file-part detection, temp-file cleanup, and panic paths.
- Fixed sliding-window rate limiting and normalized quota behavior.
- Fixed OIDC HTTP/2 negotiation and clock-skew panic paths.
- Fixed
RequestId, caching headers, basic auth multi-header fallthrough, and logging path/address behavior. - Fixed multiple tus protocol and storage edge cases.
Performance
- Reduced allocations in routing/path matching, WebSocket upgrade, proxy path encoding, JSON response rendering, CORS header joining, compression Accept-Encoding parsing,
Acceptparsing,text_nonce, andwrite_body. - Added a pass-through hasher for Depot type-keyed storage.
- Bounded cache in-flight miss coalescing and improved sliding-window rate limiter bookkeeping.
- Added criterion benchmarks for routing and dispatch hot paths.
Contributors
Thanks to everyone who contributed to this release:
- @chrislearn
- @cnlancehu — connection/lifecycle/router rework (#1622),
FlowCtrlperformance (#1608),auto_alt_svc_headerforquinn::Builder(#1585), code quality cleanups (#1544, #1524),salvo-corsrefactor (#1525), anddocs.rsbuild fixes (#1523) - @dashitongzhi — JWT audience claim handling docs (#1610)
- @dependabot — dependency updates (#1659, #1466)
- @Copilot — multipart file-part detection fix (#1486)
New Contributors
- @dashitongzhi made their first contribution in #1610
Full Changelog: v0.93.0...v0.94.0