Skip to content

1.1.0 - Correctness and the missing APIs

Latest

Choose a tag to compare

@isaced isaced released this 26 Oct 13:04

First release since 1.0.0 (2023-07-01). It adds the request/response APIs the README had been promising, and fixes a long list of correctness and lifecycle bugs that made the server unreliable under restarts and load.

Added

  • iOS background support: automaticallySuspendInBackground stops the server in the background and restarts it in the foreground, with a background task held while a connection is served from the background
  • Example iOS app under Example/
  • Path parameters: server.get("/users/:id") with values on req.params; exact routes win over parameterized ones (#31)
  • Query helpers req.queryValue(_:) / req.queryValues(_:), duplicates preserved (#27)
  • res.send(_:contentType:) for binary bodies and res.sendJSON(_:) for Encodable values (#28)
  • SSE: id: field, dispatchRetry(_:), multiline data: (#34)
  • HTTPServer.readTimeout — opt-in per-connection read timeout (#26)
  • HTTPServer.maxRequestBodySize — body cap, default 10 MiB (#15)
  • CI: macOS build + tests, and iOS/tvOS cross-builds that also compile the test target (#10, #17)

Fixed

  • macOS build broken by an unguarded UIApplication reference (#1)
  • Chunked responses used Character counts instead of UTF-8 bytes, corrupting multi-byte payloads (#14)
  • Status line always said OK; the reason phrase now follows the status code (#11)
  • Malformed/oversized requests were dropped silently; now 400, 413, 431, 501 (#15)
  • Chunked request bodies were ignored and misparsed as a new request; now rejected explicitly (#15)
  • Percent-encoded query values were double-encoded (#27)
  • A restart could close the new listener's descriptor (duplicate close on a recycled fd) (#2)
  • Writing to a closed socket could write into an unrelated connection; closed sockets now reject writes (#19)
  • _stop() iterated the connection set outside its queue — random bind failures, hung requests, and a memory-corruption crash (#30)
  • A stale accept loop could steal connections from a newer listener; stop() now waits for the loop to exit (#33)
  • Blocking accept() on the Swift concurrency pool starved unrelated work; accept now has its own thread (#16)
  • The built-in router was re-registered on every start(), running handlers twice after a restart (#16)
  • iOS background tasks never started, ended the wrong identifier, and leaked observers (#17)
  • HTTPMiddleware was not actually subclassable by consumers: handle is now open, init is public (#36)

Behaviour changes when upgrading

  • res.send(_:) sets Content-Type: text/plain; charset=utf-8 when you have not set one, and always sends Content-Length — responses are no longer close-delimited with a sniffed type
  • Requests outside the parser limits are rejected rather than tolerated
  • The reason phrase follows statusCode unless you assign res.reasonPhrase
  • Each server owns a dedicated accept thread; connection I/O is still blocking

Testing