quadrant_server 2.5.1
quadrant_server: ^2.5.1 copied to clipboard
A Dart-first, batteries-included HTTP server framework built on dart:io with zero external dependencies.
2.5.1 #
Security #
- Swagger UI SRI — The
integrityvalues shipped in 2.5.0 for/quadrant_docswere malformed and the CDN URL used a floating@5tag. The page now pinsswagger-ui-dist@5.33.1with valid SHA-384 hashes. staticFiles()— The root-boundary check now requires a path separator, so a sibling directory whose name starts with the root's name (for example/srv/public-privatefor root/srv/public) is no longer reachable.multipartParser()— NewmaxTotalBytesoption (default 50 MB) caps the whole request body, checked againstContent-Lengthand again while streaming. Previously only per-file size was checked, after the full body had been buffered.
Fixed #
- Dropped cookies —
cors()andrateLimit()no longer discardSet-Cookievalues set withResponse.withCookie(). StreamResponsethrough middleware —helmet(),cors(),requestId()andrateLimit()no longer replace aStreamResponsewith an empty body.compress()middleware order — Compressed bodies now survive outer middlewares. Previouslycompress()only worked when it was the first middleware; otherwise clients received a gzip header with an empty body.bodyParser()/formParser()413 path — Removedreq.raw.drain()calls that ran after the request stream was already cancelled.rateLimit()— Idle clients are now evicted from memory. The docs now call it a sliding-window limiter (it never used a token bucket).
Added #
Response.copyWith(), overridden byStreamResponse;StreamResponsenow acceptscookies.multipartParser(maxTotalBytes: ...).
Changed #
compress()now returns aStreamResponse. The internalconsumeCompressedBodyhelper was removed.
2.5.0 #
Security & Hardening #
Response.redirect()validation — Replacedassertwith runtimeArgumentErrorvalidation so invalid status codes throw in release mode.bodyParser()error propagation — Narrowed body parser try-catch toon FormatExceptionso low-level socket and connection errors are not swallowed. AddedmaxBodyBytesguard to defend against body-size DoS (returns413 Payload Too Large).cors()preflight caching — AddedmaxAgeparameter (defaults to 86,400s) emittingAccess-Control-Max-Ageheader on OPTIONS preflights to prevent repeated OPTIONS round-trips.docs.dartCDN asset integrity — Added Subresource Integrity (SRI) hashes (integrity&crossorigin) to Swagger UI CDN scripts and stylesheets.- Static files path traversal security — Path resolution in
staticFiles()now normalizes paths with URI semantics to prevent directory traversal escapes (..). docsLocalOnlyoption wiring — Fixed constructor parameter passthrough inQuadrantServersodocsLocalOnly: falseis honored when intentionally deployed behind a proxy.
Added #
helmet()middleware — Injects 8 essential HTTP security headers (X-Content-Type-Options,X-Frame-Options,Referrer-Policy, etc.).requestId()middleware — Generates and forwardsX-Request-ID(UUID v4) for distributed request tracing, exposed inreq.context['requestId'].rateLimit()middleware — Sliding-window rate limiting per client IP or custom key withX-RateLimit-*andRetry-Afterheaders.bearerAuth()middleware — Extracts bearer token, validates against async callback, and injects principal intoreq.context['user'].ipFilter()middleware — Allow-list or deny-list client IP addresses supporting exact match and IPv4/IPv6 CIDR ranges.cookieParser()middleware & Response cookie helpers — ParsesCookieheader intoreq.context['cookies']; addedResponse.withCookie()andResponse.clearCookie()withSet-Cookiegeneration.formParser()middleware — Decodesapplication/x-www-form-urlencodedpayloads intoreq.bodywith configurable byte limits.multipartParser()middleware — Handles multipart file uploads and form fields, exposing typedUploadedFileinstances inreq.context['files'].compress()middleware — Response gzip compression usingdart:ioGZipCodecwithAccept-Encodingnegotiation.etag()middleware — Generates weak ETags and handlesIf-None-Matchconditional requests returning304 Not Modified.SseContext— Push-based Server-Sent Events (SSE) compatible with browserEventSourceand automatic keep-alive comment pings.StreamResponse— Response subclass for streaming raw bytes directly to client sockets without memory buffering.- Request validation DSL —
BodySchemaandField(string,int_,double_,bool_,list) withvalidate()middleware returning422 Unprocessable Entity. listenSecure()— First-class HTTPS/TLS binding withcertificateChainandprivateKeyfiles.- Health check endpoint — Optional built-in
/healthreporting uptime, request counts, and RSS memory usage. - WebSocket heartbeats — Configurable
heartbeatduration onWebSocketRoutefor automatic ping/pong keepalive. - Graceful in-flight request draining —
app.close()drains active requests before terminating. timeout(Duration, {onTimeout})middleware — Races the handler chain against a deadline; returns408with{"error":"Request Timeout","timeoutMs":…}or youronTimeoutresponse.staticFiles(rootDir, {indexFallback})route handler — MIME detection,ETag/Last-Modifiedwith304forIf-None-Match/If-Modified-Since,Cache-Control: public, max-age=3600,index.htmlfallback.healthCheckPathoption — Default/health. The endpoint is answered before middleware runs, so it skips auth, rate limiting and logging.app.close({force})—force: trueskips draining in-flight requests.- TLS/binding options —
listenSecure(password:)for encrypted keys;listen/listenSecureacceptaddress. - Validation rules —
required,minLength,maxLength,matches(RegExp),email,oneOf,min,max,minItems,maxItems. String inputs are coerced for int/double/bool fields. Failures return422 {"errors": {field: message}}. SseContextAPI —send(id:, retry:),sendComment(),close(),isOpen,keepAliveInterval(default 15 s,Duration.zerodisables),X-Accel-Buffering: no.WebSocketGroup—exclude:onbroadcast/broadcastJson, plusisEmpty,isNotEmpty,length,members.- Middleware options —
bearerAuth(contextKey, realm)withWWW-Authenticateon 401;ipFilter(onDeny);multipartParser(maxFileSize, filesContextKey, fieldsContextKey)returning413for oversized files;bodyParser/formParseracceptmaxBodyBytes: 0to disable the limit; all 8helmet()headers are overridable andnullremoves one.
Fixed #
- WebSocket upgrade error handling — Upgrades failing in
onErrornow return an HTTP error response to the client rather than leaving the connection hanging. - WebSocket middleware chain sentinel — Replaced dummy 200 response with
Response(204)sentinel. - Connection close resilience — Guarded
_writeResponseagainst writing to already-closed HTTP sockets. - Banner ASCII padding — Handled high route counts safely with
max(0, ...).
2.0.0 #
Added #
QuadrantRouter— prefix-mounted route grouping; organise large apps without repeating path prefixes. Supports router-level middlewares applied to all routes in the group.WebSocketGroup— built-in broadcast/room utility.add(),remove(),broadcast(),broadcastJson(),closeAll()with lazy cleanup of stale connections.WebSocketContext.isOpen/isClosed— check connection state before sending to avoid throwing on a closed socket.WebSocketContext.sendJson()— convenience JSON-encode-and-send shortcut.- Wildcard route segments —
Route.get(path: '/files/*', ...)captures the full remaining path asreq.params['*']. Response.redirect()— 301/302/307/308 redirect withlocationheader.Response.text()— plain-text response withcontent-type: text/plain.Response.html()— HTML response withcontent-type: text/html.Response.conflict()— 409 Conflict.Response.unprocessableEntity()— 422 Unprocessable Entity.- Typed query helpers on
Request—queryString(),queryInt(),queryDouble(),queryBool()with default value support. Request.bodyAsMap/bodyAsList— safe typed casts for the parsed body.app.close()— graceful shutdown that waits for in-flight requests.docsLocalOnlyparameter — control whether/quadrant_docsis loopback-only (default:true).logger()output sink — injectable log destination:logger(output: myLogger.info).cors()named parameters —methodsandallowedHeadersnow configurable.- Startup banner — printed when
docs: trueshowing host, route counts, and docs URL. - Unit test suite (
test/quadrant_server_test.dart) covering router, response factories, QuadrantRouter, and middleware. analysis_options.yamlwithlints/recommendedand strict type-checking.
Fixed #
RequestHolderhash collision — replacedhashCode-basedMapkey withExpando<Object>keyed on theHttpRequestobject itself. Eliminates body cross-contamination under concurrent load.bodyParsersilently dropped JSON array bodies —req.bodyis nowdynamicand accepts top-levelListpayloads.- Plain-string responses lacked
Content-Type— all string response bodies now carrycontent-type: text/plain; charset=utf-8. - WebSocket upgrade errors silently swallowed — upgrade failures now delegate to
onErroror print a warning. /quadrant_docsaccessible from external IPs — loopback guard is now active by default (docsLocalOnly: true).internalServerErrorleaked raw exception messages — default error handler now returns a generic safe message; users opt in to detail viaonError.
Improved #
cors()now setsVary: Originwhenorigin != '*'for correct CDN/proxy caching behaviour.WebSocketContext.send()is now a no-op when the socket is closed instead of throwing.
1.2.0 #
Added #
- WebSocket support — declarative
WebSocketRoutewith four named callbacks (onStart,onMessage,onClose,onError) WebSocketContext— per-connection context withsend(),close(), and access to the original upgrade request (params, query, headers)webSocketRoutesparameter onQuadrantServer— declare WS endpoints alongside HTTP routes- Middleware support for WebSocket — route-level middlewares run before upgrade; rejection prevents the upgrade entirely
- Path parameters on WS routes —
/ws/chat/:roomIdextracts params just like HTTP routes MatchedWebSocketsealed type — router distinguishes WS upgrades from HTTP requests at the type level- Docs: WebSocket section —
/quadrant_docsnow renders WS endpoints in a table below Swagger UI
Improved #
- Router —
match()acceptsisUpgradeRequestflag; WS upgrades never fall through to HTTP GET routes
1.1.1 #
Added #
/quadrant_docsendpoint — opt-in interactive API explorer powered by Swagger UI (CDN-loaded), enabled viadocs: true- 405 Method Not Allowed — router now distinguishes "path exists, wrong method" from "path not found", returns proper
Allowheader - HEAD request support — HEAD automatically falls back to GET handlers per HTTP spec
- Trailing slash normalization —
/users/and/usersnow match the same route - URI-decoded path params —
%20and other encoded characters decoded automatically Route.paramNamesgetter — extracts parameter names from path pattern for introspection
Improved #
- Router performance — routes grouped by HTTP method for faster lookup (O(n/m) vs O(n))
- Method normalization —
'get'and'GET'both resolve correctly
Security #
/quadrant_docslocked to loopback addresses only — external IPs always get 404- Docs route excluded from generated OpenAPI spec (no self-referencing)
1.0.0 #
- Initial release
- Core framework:
QuadrantServer,Request,Response,Route,Router - Middleware system with global and route-level support
- Built-in middlewares:
cors(),logger(),bodyParser() - Path parameter extraction (
:paramsegments) - Immutable response pattern with static constructors
- Zero external dependencies — built entirely on
dart:io