Skip to content

Releases: atom2ueki/SwiftWebServer

0.3.1

01 May 16:03
904f2bb

Choose a tag to compare

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

  • ConnectionSwiftWebServer retain cycle (#6, PR #8). Connection.server is now a weak reference, so the static SwiftWebServer.connections dictionary no longer pins the server graph (and its routeHandlers / 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 used Unmanaged.passRetained(self) for the CFSocketContext info pointer with both retain / release callbacks nil, leaking one refcount per listen() call. Now uses passUnretained(self) with matching retain / release callbacks so the IPv4 and IPv6 sockets each contribute a balanced retain/release pair against self.

Documentation

  • README refresh (PR #10). Brings the README into sync with the actual public API on main: replaces the outdated start() / stop() snippets with listen(_:host:completion:) / close(), documents the @MainActor / Sendable contract, 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 a Connection referencing a server, drops the user's strong reference, asserts the server deallocates even while the Connection is 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

01 May 10:20
15de0c3

Choose a tag to compare

@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

  • @MainActor on the SwiftWebServer class.
  • nonisolated(unsafe) on the configuration state Connection reads from its background queue (router, routeHandlers, middlewareManager, staticDirectories), under the documented contract that it is configured before listen() 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.assumeIsolated wraps inside the @convention(c) accept callbacks. The IPv6 callback now carries the same explanatory comment as the IPv4 one.

Migration

  • Callers already on @MainActor or hopping via Task { @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 Connection still runs on a background dispatch queue, so recv() does not block main.

Compatibility

  • Swift tools version raised to 5.10. nonisolated(unsafe) (Swift 5.10) and MainActor.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 (the macOS entry was missing from 0.2.0's Package.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.server is a non-weak reference and SwiftWebServer.connections holds strong references to Connections — together they form a retain cycle that can prevent server deallocation while connections are alive.
  • Unmanaged.passRetained(self) into CFSocketContext with release: nil leaks one refcount per listen().

0.2.0

01 May 08:42
ff94ea2

Choose a tag to compare

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 resolve localhost to ::1 first 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 leaks CFSockets or run-loop sources (pre-existing leaks plugged via a new failStartup(_:) helper).
  • currentPort now consistently reports 0 whenever status isn'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 == nil reproduces the previous INADDR_ANY + in6addr_any bind exactly, including the legacy "best-effort IPv6" semantic for hosts where IPv6 binding fails.

See #3 for the full changeset.

0.1.0

21 Jun 04:07

Choose a tag to compare

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