A modern Swift 6 embedded HTTP/1.1 WebServer framework for iOS, iPadOS, macOS, tvOS, and visionOS.
Inspired by GCDWebServer, rebuilt with Network.framework, async/await, and Swift 6 strict concurrency.
- HTTP/1.1 server with async handler API
- Strongly typed
Request/Response - Path parameter routing (
/users/:id,/files/:path...for nested static paths) - Static file serving with directory-traversal protection
- gzip compression
- HTTP Range requests
application/x-www-form-urlencodedparsingmultipart/form-dataparsing- Basic and Digest authentication
- JSON request/response helpers
- Streaming / chunked responses
- Optional
SwiftWebServerWebUploadmodule (file upload / list / download / delete) - Logging with a custom handler
- Lifecycle management: start, stop, suspend, resume
- HTTP keep-alive
- HTTPS with a custom or bundled self-signed identity
- HTTP/2 ALPN negotiation is rejected with
505 HTTP Version Not Supported(HTTP/1.1 only)
- Swift 6.0+
- iOS 17+ / iPadOS 17+ / macOS 14+ / tvOS 17+ / visionOS 1+
Add the dependency to Package.swift:
dependencies:[.package(url:"https://github.com/tylinux/SwiftWebServer.git", from:"0.1.0")]Then add the products you need:
.target(name:"YourTarget", dependencies:[.product(name:"SwiftWebServer",package:"SwiftWebServer"),.product(name:"SwiftWebServerWebUpload",package:"SwiftWebServer"), // optional
])Or add it in Xcode via File → Add Package Dependencies.
import SwiftWebServer
letserver=WebServer()
// `addRoute` is isolated to the `WebServer` actor, so call it with `await`.
await server.addRoute(method:.get, path:"/hello"){ request inResponse(text:"Hello, \(request.query["name"]??"world")!")}await server.addRoute(method:.post, path:"/api/user"){ request instructUser:Codable{letname:String}letuser:User=try request.decodeJSON()returntryResponse(json:["id":1,"name": user.name]).status(.created)}await server.addRoute(
method:.get,
path:"/admin",
authenticator:Authentication.basic{ username, password in
username =="admin" && password =="secret"}){ request inResponse(text:"secret")}tryawait server.start(port:8080)Parse application/x-www-form-urlencoded request bodies:
await server.addRoute(method:.post, path:"/login"){ request inletfields=try request.formFields()letusername=fields["username"]returnResponse(text:"Hello, \(username ??"guest")")}Serve files from a directory with optional index-file fallback:
import Foundation
letstaticDirectory=FileManager.default
.urls(for:.documentDirectory, in:.userDomainMask).first!
.appendingPathComponent("public")await server.addStaticFiles(
at:"static",
directory: staticDirectory,
indexFile:"index.html")Use a variadic path parameter (:path...) for nested paths:
await server.addRoute(method:.get, path:"/files/:path..."){ request inguardlet path = request.pathParameter("path")else{returnResponse(text:"Not found").status(.notFound)}returnResponse(text:"Requested: \(path)")}Return an AsyncThrowingStream<Data, Error> for long-running or incremental responses. The server automatically uses chunked transfer encoding:
await server.addRoute(method:.get, path:"/stream"){ request inletstream=AsyncThrowingStream<Data,Error>{ continuation in
continuation.yield(Data("hello ".utf8))
continuation.yield(Data("world".utf8))
continuation.finish()}returnResponse(stream: stream, headers:["Content-Type":"text/plain"])}Add a ready-to-use upload / list / download / delete endpoint:
import SwiftWebServerWebUpload
letuploadRoot=FileManager.default
.urls(for:.documentDirectory, in:.userDomainMask).first!
.appendingPathComponent("Uploads")letwebUpload=WebUpload(
server: server,
rootDirectory: uploadRoot,
// Optional: provide your own upload.html template.
customIndexHTML:URL(fileURLWithPath:"/path/to/custom-upload.html"))await webUpload.configure()The default page is available at http://localhost:8080/upload. The bundled HTML template uses {{prefix}} and {{fileRows}} placeholders; a custom template can use the same placeholders.
Receive server log messages with a custom handler:
server.setLogHandler{ level, message inprint("[\(level.rawValue.uppercased())] \(message)")}Control the server lifecycle from your app:
tryawait server.start(port:8080) // start
await server.suspend() // pause accepting new connections
tryawait server.resume() // resume
await server.stop() // stop and close active connectionsUseful for handling app background/foreground transitions.
Start a TLS server with your own SecIdentity, or use the bundled self-signed identity for local testing:
import Security
letidentity=tryTLSIdentity.makeSelfSigned(host:"localhost")lettlsConfig=TLSConfiguration(
identity: identity,
applicationProtocols:["http/1.1"])tryawait server.start(port:8443, tls: tlsConfig)In production, create a SecIdentity from your own certificate and pass it to TLSConfiguration.
SwiftWebServer is an HTTP/1.1 server. If a TLS client negotiates HTTP/2 via ALPN, the server responds with 505 HTTP Version Not Supported and closes the connection.
A multi-platform demo app is included in the Demo/ folder. It contains Xcode targets for iOS, iPadOS, macOS, tvOS, and visionOS, and demonstrates:
- Starting, suspending, resuming, and stopping the server
- Basic routing and JSON responses
- Basic authentication
- WebUpload file uploads
- A native uploaded-file list
Generate the Xcode project with xcodegen:
cd Demo
xcodegen generate
open SwiftWebServerDemo.xcodeprojFull API reference is available via DocC in Xcode. The DocC catalog includes articles on routing, handlers, and getting started.
MIT