Releases: atom2ueki/SwiftWebServer
0.3.1
Memory-management fixes
Two pre-existing issues called out during the 0.3.0 review are now resolved. No source changes required for consumers — same behavior for correct call sites; previously-leaked memory is now reclaimed.
Fixes
Connection↔SwiftWebServerretain cycle (#6, PR #8).Connection.serveris now aweakreference, so the staticSwiftWebServer.connectionsdictionary no longer pins the server graph (and itsrouteHandlers/ middleware / closures) through its connections. Masked in CLI / one-shot use, but observable as growing RSS in long-running apps that bring servers up and down.- CFSocket context retain leak in
listen()(#7, PR #9).listen(_:host:completion:)previously usedUnmanaged.passRetained(self)for theCFSocketContextinfopointer with bothretain/releasecallbacksnil, leaking one refcount perlisten()call. Now usespassUnretained(self)with matchingretain/releasecallbacks so the IPv4 and IPv6 sockets each contribute a balanced retain/release pair againstself.
Documentation
- README refresh (PR #10). Brings the README into sync with the actual public API on
main: replaces the outdatedstart()/stop()snippets withlisten(_:host:completion:)/close(), documents the@MainActor/Sendablecontract, adds a loopback-bind subsection, and corrects the platform Requirements (iOS 15 / macOS 12 / Swift 5.10 / Xcode 15.3 for the library; iOS 17 / macOS 14 only for the SwiftUI example app, which uses SwiftData). Reviewed by Claude, Codex, and Copilot.
Tests
46/46 passing, including two new regression tests:
ConnectionRetainCycleTests— constructs aConnectionreferencing a server, drops the user's strong reference, asserts the server deallocates even while theConnectionis alive.testServerDeinitsAfterCloseDoesNotLeak— listens on an ephemeral loopback port, closes, asserts the server deallocates via a weak reference.
Compatibility
- No source changes required.
- No behavior change for correct call sites; previously-leaked memory is now reclaimed.
0.3.0
@MainActor isolation for honest Sendable conformance
Promotes SwiftWebServer to @MainActor isolation, which makes it automatically Sendable and matches what the runtime already requires. The class installs its CFSocket accept callbacks via CFRunLoopGetCurrent(), so listen() and close() already had to be invoked from a thread running its own run loop (in practice: the main thread on iOS/macOS apps). @MainActor makes that contract explicit, and consumers no longer need an @unchecked Sendable wrapper to pass instances across actor boundaries.
What's new
@MainActoron theSwiftWebServerclass.nonisolated(unsafe)on the configuration stateConnectionreads from its background queue (router,routeHandlers,middlewareManager,staticDirectories), under the documented contract that it is configured beforelisten()and treated as read-only thereafter.assertNotRunning()preconditions on every route registration and middleware/static-directory mutator, including the variadic-middleware overloads. Post-listen()mutation now traps with a clear message before touching any shared state — what was undefined behavior is now a deterministic crash.MainActor.assumeIsolatedwraps inside the@convention(c)accept callbacks. The IPv6 callback now carries the same explanatory comment as the IPv4 one.
Migration
- Callers already on
@MainActoror hopping viaTask { @MainActor in ... }keep working unchanged. - Sites previously calling from a background context now get a compiler diagnostic instead of a silent race — strictly an improvement.
- Per-request work in
Connectionstill runs on a background dispatch queue, sorecv()does not block main.
Compatibility
- Swift tools version raised to 5.10.
nonisolated(unsafe)(Swift 5.10) andMainActor.assumeIsolated(Swift 5.9) are used in the source set; consumers on older toolchains would have hit a parse failure. The manifest now reflects the real minimum. - Platform minimum:
iOS 15,macOS 12(themacOSentry was missing from 0.2.0'sPackage.swift).
Tests
43/43 passing. Test classes annotated @MainActor since they construct and configure the server.
See PR #5 for the full review trail (Codex P1 + P2 findings + Claude review feedback all addressed).
Pre-existing issues called out during review (out of scope, follow-up issues should be opened):
Connection.serveris a non-weakreference andSwiftWebServer.connectionsholds strong references toConnections — together they form a retain cycle that can prevent server deallocation while connections are alive.Unmanaged.passRetained(self)intoCFSocketContextwithrelease: nilleaks one refcount perlisten().
0.2.0
Loopback-bind support
Adds an optional host: parameter to SwiftWebServer.listen(_:host:completion:). When nil (the default), preserves the existing dual-stack INADDR_ANY / in6addr_any bind — all 0.1.0 call sites work unchanged.
New supported values
"localhost"— dual-stack loopback (127.0.0.1+::1). Recommended for OAuth callbacks, IPC, and any "this machine only" flow. Browsers that resolvelocalhostto::1first stay reachable."127.0.0.1"/"::1"— single-family loopback"0.0.0.0"/"::"— single-family any- Any IPv4/IPv6 literal — single-family bind on that address
Hostnames other than "localhost" are not resolved; pass IP literals.
Why
Native-app OAuth callbacks per RFC 8252 §7.3 should bind to loopback only — without that, the local server is reachable on the LAN while a login is in progress. This release makes that opt-in without breaking existing consumers.
Other improvements (review-driven)
- Failed
listen()no longer leaksCFSockets or run-loop sources (pre-existing leaks plugged via a newfailStartup(_:)helper). currentPortnow consistently reports0wheneverstatusisn't.running(was incorrectly reporting the requested port on error paths).- Hardened test helpers and added regression coverage.
Compatibility
- Source: additive parameter with default value. No 0.1.0 source change required.
- Behavior:
host == nilreproduces the previousINADDR_ANY+in6addr_anybind exactly, including the legacy "best-effort IPv6" semantic for hosts where IPv6 binding fails.
See #3 for the full changeset.
0.1.0
SwiftWebServer 0.1.0
Features
- 🚀 Lightweight HTTP server with custom port configuration
- 🔧 Extensible middleware architecture for request/response processing
- 🛣️ Route handling with path parameters (/users/{id}) and multiple HTTP methods
- 🦾 JSON and form data body parsing
- 📁 Static file serving with automatic MIME type detection
- 🍪 Cookie parsing and management with secure attributes
- 🔒 Bearer token authentication middleware with JWT support
- 🌐 CORS middleware with configurable options
- 📝 Configurable request/response logging
- 🏷️ ETag support for conditional requests and caching
- 🔄 HTTP redirects (temporary and permanent)
- 🎯 Comprehensive error handling with proper status codes
- 📱 SwiftUI integration examples