A lightweight, Swift-based HTTP web server with middleware support.
- π Lightweight & Fast: Minimal overhead with efficient request handling
- π§ Middleware System: Extensible middleware architecture for request/response processing
- π£οΈ Route Handling: Support for path parameters (
/users/{id}
) and multiple HTTP methods - π¦Ύ Body Parsing: JSON and form data parsing middleware
- π Static File Serving: Built-in static file serving with automatic MIME type detection
- πͺ Cookie Support: Full cookie parsing and setting capabilities with secure attributes
- π Authentication: Bearer token authentication middleware with JWT support
- π CORS Support: Cross-Origin Resource Sharing middleware with configurable options
- π Logging: Configurable request/response logging with detailed output options
- π·οΈ ETag Support: Conditional requests with 304 Not Modified responses for caching
- π HTTP Redirects: Support for temporary and permanent redirects with proper status codes
- π― Error Handling: Comprehensive error responses with proper HTTP status codes
- π± SwiftUI Integration: Native iOS/macOS integration with example application
Add SwiftWebServer to your Package.swift
:
dependencies: [
.package(url: "https://github.com/atom2ueki/SwiftWebServer.git", from: "1.0.0")
]
import SwiftWebServer
let server = SwiftWebServer()
// Add middleware
server.use(LoggerMiddleware())
server.use(CORSMiddleware())
// Define routes
server.get("/hello") { req, res in
res.send("Hello, World!")
}
server.get("/users/{id}") { req, res in
let userId = req.pathParameters["id"] ?? "unknown"
res.json("""
{
"userId": "\(userId)",
"message": "User details"
}
""")
}
// Start server
try server.start(port: 8080)
print("Server running on http://localhost:8080")
SwiftWebServer follows a middleware-based architecture where requests flow through a chain of middleware functions before reaching route handlers, and responses flow back through the same chain.
The diagram above shows how requests flow through SwiftWebServer:
- Client Request: HTTP request arrives at the server
- Connection Handler: Accepts and manages the connection
- Request Parsing: Parses HTTP headers, method, path, and body
- Middleware Chain: Request flows through registered middleware in order
- Route Matching: Attempts to find a matching route handler
- Static Files: If no route matches, checks for static files
- Response Processing: Generates response through middleware chain
- Client Response: Sends final HTTP response back to client
Middleware functions are the core of SwiftWebServer's extensibility. Each middleware can:
- Inspect and modify incoming requests
- Add functionality like authentication, logging, or parsing
- Short-circuit the request chain (e.g., for authentication failures)
- Process responses on the way back to the client
Request β [Middleware 1] β [Middleware 2] β [Route Handler]
Response β [Middleware 1] β [Middleware 2] β [Route Handler]
Middleware executes in the order it's registered using server.use()
, and response processing happens in reverse order.
SwiftWebServer comes with several built-in middleware components:
Parses JSON and form data from request bodies.
server.use(BodyParser())
server.post("/api/users") { req, res in
if let jsonBody = req.jsonBody {
// Handle JSON data
let userData = jsonBody
} else if let formBody = req.formBody {
// Handle form data
let name = formBody["name"]
}
}
Logs incoming requests and outgoing responses with configurable detail levels.
// Basic logging
server.use(LoggerMiddleware())
// Detailed logging with headers
server.use(LoggerMiddleware(options: LoggerOptions(
level: .detailed,
includeHeaders: true
)))
Handles Cross-Origin Resource Sharing (CORS) headers for web applications.
// Default CORS settings
server.use(CORSMiddleware())
// Custom CORS configuration
server.use(CORSMiddleware(options: CORSOptions(
allowedOrigins: ["https://myapp.com"],
allowedMethods: [.get, .post, .put],
allowedHeaders: [.contentType, .authorization],
allowCredentials: true
)))
Parses incoming cookies and provides methods for setting response cookies.
server.use(CookieMiddleware())
// In route handlers
server.get("/login") { req, res in
// Read cookies
let sessionId = req.cookie("sessionId")
// Set cookies
res.cookie("sessionId", "abc123", attributes: CookieAttributes(
expires: Date().addingTimeInterval(3600),
httpOnly: true,
secure: true
))
}
Provides Bearer token authentication for protected routes with JWT support.
let authMiddleware = BearerTokenMiddleware(options: BearerTokenOptions(
validator: { token in
// Validate token against your auth system
return token == "valid-api-key" || validateJWT(token)
}
))
// Apply to specific routes
server.get("/protected", middleware: [authMiddleware]) { req, res in
// Access authenticated user info
if let authToken = req.middlewareStorage["authToken"] as? String {
res.json("""{"message": "Access granted", "token": "\(authToken)"}""")
} else {
res.json("""{"message": "Access granted"}""")
}
}
Implements conditional requests with ETag support for caching.
server.use(ETagMiddleware(options: ETagOptions(
strategy: .strong // or .weak
)))
// In route handlers
server.get("/data") { req, res in
let content = generateDynamicContent()
res.sendWithETag(content, contentType: .applicationJson)
}
The middleware system is designed to be easily extensible. You can create custom middleware by implementing the BaseMiddleware
class or the ConfigurableMiddleware
protocol.
import SwiftWebServer
/// Custom middleware that adds a request timestamp
class TimestampMiddleware: BaseMiddleware {
override func execute(request: Request, response: Response, next: @escaping NextFunction) throws {
// Add timestamp to request
let timestamp = Date().timeIntervalSince1970
request.middlewareStorage["timestamp"] = timestamp
// Add custom header to response
response.header("X-Request-Timestamp", "\(timestamp)")
// Continue to next middleware
try next()
// Post-processing (after route handler)
print("Request processed in \(Date().timeIntervalSince1970 - timestamp) seconds")
}
}
// Usage
server.use(TimestampMiddleware())
Middleware can share data through the request's middlewareStorage
dictionary:
// In authentication middleware
class AuthMiddleware: BaseMiddleware {
override func execute(request: Request, response: Response, next: @escaping NextFunction) throws {
// Validate token and store user info
if let user = validateAndGetUser(from: request) {
request.middlewareStorage["currentUser"] = user
request.middlewareStorage["isAuthenticated"] = true
}
try next()
}
}
// In route handler
server.get("/profile") { req, res in
if let user = req.middlewareStorage["currentUser"] as? User {
res.json(user.toJSON())
} else {
res.status(.unauthorized).json(["error": "Not authenticated"])
}
}
/// Configuration options for rate limiting
public struct RateLimitOptions {
public let maxRequests: Int
public let windowSeconds: Int
public let message: String
public init(maxRequests: Int = 100, windowSeconds: Int = 60, message: String = "Rate limit exceeded") {
self.maxRequests = maxRequests
self.windowSeconds = windowSeconds
self.message = message
}
public static let `default` = RateLimitOptions()
}
/// Rate limiting middleware
public class RateLimitMiddleware: BaseMiddleware, ConfigurableMiddleware {
public typealias Options = RateLimitOptions
private let options: RateLimitOptions
private var requestCounts: [String: (count: Int, resetTime: Date)] = [:]
private let queue = DispatchQueue(label: "rateLimit", attributes: .concurrent)
public required init(options: RateLimitOptions = .default) {
self.options = options
super.init()
}
public convenience override init() {
self.init(options: .default)
}
public override func execute(request: Request, response: Response, next: @escaping NextFunction) throws {
let clientIP = request.clientIP ?? "unknown"
let now = Date()
let shouldAllow = queue.sync {
if let entry = requestCounts[clientIP] {
if now > entry.resetTime {
// Reset window
requestCounts[clientIP] = (count: 1, resetTime: now.addingTimeInterval(TimeInterval(options.windowSeconds)))
return true
} else if entry.count < options.maxRequests {
// Increment count
requestCounts[clientIP] = (count: entry.count + 1, resetTime: entry.resetTime)
return true
} else {
// Rate limit exceeded
return false
}
} else {
// First request from this IP
requestCounts[clientIP] = (count: 1, resetTime: now.addingTimeInterval(TimeInterval(options.windowSeconds)))
return true
}
}
if shouldAllow {
try next()
} else {
response.status(.tooManyRequests).send(options.message)
}
}
}
// Usage
server.use(RateLimitMiddleware(options: RateLimitOptions(
maxRequests: 50,
windowSeconds: 60,
message: "Too many requests. Please try again later."
)))
SwiftWebServer supports flexible routing with path parameters and multiple HTTP methods.
// HTTP Methods
server.get("/users") { req, res in res.send("Get all users") }
server.post("/users") { req, res in res.send("Create user") }
server.put("/users/{id}") { req, res in res.send("Update user") }
server.delete("/users/{id}") { req, res in res.send("Delete user") }
// Single parameter
server.get("/users/{id}") { req, res in
let userId = req.pathParameters["id"] ?? "unknown"
res.send("User ID: \(userId)")
}
// Multiple parameters
server.get("/users/{userId}/posts/{postId}") { req, res in
let userId = req.pathParameters["userId"] ?? "unknown"
let postId = req.pathParameters["postId"] ?? "unknown"
res.json("""
{
"userId": "\(userId)",
"postId": "\(postId)"
}
""")
}
server.get("/search") { req, res in
let query = req.queryParameters["q"] ?? ""
let page = Int(req.queryParameters["page"] ?? "1") ?? 1
res.json("""
{
"query": "\(query)",
"page": \(page),
"results": []
}
""")
}
// Serve files from a directory
server.use(staticDirectory: "./public")
// Multiple static directories
server.use(staticDirectory: "./assets")
server.use(staticDirectory: "./uploads")
let server = SwiftWebServer() // Basic initialization
let server = SwiftWebServer(port: 8080) // With default port
try server.start(port: 8080) // Start server on specified port
server.stop() // Stop the server
server.status // Get current server status (.stopped, .starting, .running, .stopping)
server.currentPort // Get current port (0 if not running)
server.isRunning // Check if server is running
server.use(middleware) // Add middleware
server.use(staticDirectory: "./public") // Serve static files
server.get(pattern, handler) // GET route
server.post(pattern, handler) // POST route
server.put(pattern, handler) // PUT route
server.delete(pattern, handler) // DELETE route
The Request
object provides access to all incoming request data:
// Basic properties
req.method // HTTPMethod (.get, .post, etc.)
req.path // Request path ("/users/123")
req.httpVersion // HTTP version ("HTTP/1.1")
req.headers // HTTPHeaders object
req.body // Raw request body as Data?
req.bodyString // Request body as String?
// Parsed data
req.pathParameters // Path parameters ["id": "123"]
req.queryParameters // Query parameters ["page": "1"]
req.cookies // Parsed cookies ["session": "abc123"]
req.jsonBody // Parsed JSON body (if BodyParser middleware is used)
req.formBody // Parsed form data (if BodyParser middleware is used)
req.middlewareStorage // Generic storage for middleware data sharing
// Convenience methods
req.header("Content-Type") // Get header by name
req.header(.contentType) // Get header by enum
req.cookie("sessionId") // Get cookie by name
req.contentType // Parsed content type
req.contentLength // Content length as Int?
req.userAgent // User-Agent header
req.host // Host header
req.clientIP // Client IP address
req.isSecure // Whether request is HTTPS
req.accepts(.applicationJson) // Check if client accepts content type
The Response
object provides methods for sending responses:
// Status codes
res.status(.ok) // Set status code
res.status(200) // Set status code by number
// Headers
res.header("Content-Type", "application/json") // Set header
res.header(.contentType, "application/json") // Set header by enum
// Response methods
res.send("Hello World") // Send text response
res.json("""{"key": "value"}""") // Send JSON response
res.html("<h1>Hello</h1>") // Send HTML response
res.file("./public/index.html") // Send file
// Cookies
res.cookie("name", "value") // Set cookie
res.cookie("session", "abc123", attributes: CookieAttributes(
expires: Date().addingTimeInterval(3600),
httpOnly: true,
secure: true
))
res.clearCookie("session") // Clear cookie
// ETag support (with ETagMiddleware)
res.sendWithETag(content, contentType: .applicationJson)
res.notModified() // Send 304 Not Modified
// Redirects
res.redirect("/new-path") // Temporary redirect (302)
res.redirect("/new-path", permanent: true) // Permanent redirect (301)
res.redirectPermanent("/new-path") // Permanent redirect (301)
res.redirectTemporary("/new-path") // Temporary redirect (302)
res.redirectTemporaryPreserveMethod("/new-path") // 307 redirect
res.redirectPermanentPreserveMethod("/new-path") // 308 redirect
// Error responses with messages
res.badRequest("Invalid input data")
res.notFound("Resource not found")
res.internalServerError("Something went wrong")
// Method chaining
res.status(.ok)
.header(.contentType, "application/json")
.json("""{"message": "Success"}""")
Here's a comprehensive example showing a REST API with authentication, logging, and error handling:
import SwiftWebServer
let server = SwiftWebServer()
// Add middleware in order
server.use(LoggerMiddleware(options: LoggerOptions(level: .detailed)))
server.use(CORSMiddleware())
server.use(CookieMiddleware())
server.use(BodyParser())
server.use(ETagMiddleware())
// Authentication middleware for protected routes
let authMiddleware = BearerTokenMiddleware(options: BearerTokenOptions(
validator: { token in
// Validate token against your auth system
return validateJWT(token) || validateDatabaseToken(token)
}
))
// Public routes
server.get("/") { req, res in
res.html("""
<h1>Welcome to SwiftWebServer</h1>
<p>A lightweight HTTP server for Swift</p>
""")
}
server.get("/api/status") { req, res in
res.sendWithETag("""
{
"status": "healthy",
"timestamp": "\(Date().iso8601Formatted())",
"version": "1.0.0"
}
""", contentType: .applicationJson)
}
// Protected routes
server.get("/api/users", middleware: [authMiddleware]) { req, res in
let page = Int(req.queryParameters["page"] ?? "1") ?? 1
let limit = Int(req.queryParameters["limit"] ?? "10") ?? 10
res.json("""
{
"users": [],
"pagination": {
"page": \(page),
"limit": \(limit),
"total": 0
}
}
""")
}
server.post("/api/users", middleware: [authMiddleware]) { req, res in
guard let jsonBody = req.jsonBody,
let userData = jsonBody as? [String: Any],
let name = userData["name"] as? String else {
res.status(.badRequest).json("""{"error": "Invalid user data"}""")
return
}
// Create user logic here
let userId = UUID().uuidString
res.status(.created).json("""
{
"id": "\(userId)",
"name": "\(name)",
"created": "\(Date().iso8601Formatted())"
}
""")
}
// Error handling
server.get("/api/error") { req, res in
res.status(.internalServerError).json("""
{
"error": "Something went wrong",
"timestamp": "\(Date().iso8601Formatted())"
}
""")
}
// Serve static files
server.use(staticDirectory: "./public")
// Start server
do {
try server.start(port: 8080)
print("π Server running on http://localhost:8080")
print("π Status endpoint: http://localhost:8080/api/status")
print("π Protected endpoint: http://localhost:8080/api/users (requires Bearer token)")
// Keep the server running
RunLoop.current.run()
} catch {
print("β Failed to start server: \(error)")
}
The SwiftWebServerExample project demonstrates a comprehensive blog application with both frontend and backend servers, featuring a native SwiftUI interface:
- Backend Server (Port 8080): REST API with JWT authentication, user management, and blog posts
- Frontend Server (Port 3000): Serves static HTML/CSS/JS files with responsive design
- SwiftUI Dashboard: Native iOS interface with server controls and data management
- SwiftData Integration: Modern data persistence with automatic relationship management
- Blog Interface: Public blog page with responsive design and post details
- Admin Login: Secure login with JWT token authentication
- Admin Dashboard: Clean blog management interface for authenticated users
- Session Management: Automatic token cleanup and session tracking
- Real-time Logging: Request/response logging with filtering and haptic feedback
- Data Management: Native SwiftUI interface for managing users, posts, and comments
- Dual Server Setup: Separate frontend and backend servers for realistic deployment
- Dashboard Layout: Card-based interface with server status and data management
- Server Controls: Start/stop servers with real-time status updates
- Data Management: Create, edit, and delete users, posts, and comments
- Session Monitoring: View and manage active authentication tokens
- Console Logging: Real-time request/response logs with filtering options
- Haptic Feedback: Enhanced user experience with tactile feedback
- Users access the blog at
http://localhost:3000/
- Admin login is available at
http://localhost:3000/login.html
- JWT tokens are issued upon successful authentication
- Admin dashboard at
http://localhost:3000/admin.html
validates tokens - Automatic logout when tokens expire
- Session management through SwiftUI interface
- Open
SwiftWebServerExample.xcodeproj
in Xcode - Run the project on iOS Simulator or device (iOS 17.0+)
- Start both servers using the dashboard controls
- Access the blog at
http://localhost:3000/
- Use demo credentials:
johndoe
/password123
- Manage data through the native SwiftUI interface
- Clone the repository
- Open in Xcode or use Swift Package Manager
- Run tests:
swift test
- Build:
swift build
- iOS: 17.0+ (for SwiftUI example app)
- macOS: 14.0+ (for command-line usage)
- Xcode: 15.0+
- Swift: 5.9+
- β Session Management: Automatic JWT token cleanup and session tracking
- β Enhanced Authentication: Improved Bearer token middleware with detailed error responses
- β SwiftUI Integration: Native iOS dashboard with haptic feedback and real-time updates
- β SwiftData Support: Modern data persistence with automatic relationship management
- β Responsive Design: Mobile-first web interface with adaptive layouts
- β Error Handling: Comprehensive error responses with proper HTTP status codes
- β Middleware Improvements: Enhanced logging, CORS, and cookie handling
- β HTTP Redirects: Full support for temporary and permanent redirects
SwiftWebServer includes comprehensive unit tests for all middleware and core functionality:
# Run all tests
swift test
# Run specific test suite
swift test --filter SwiftWebServerTests
# Run with verbose output
swift test --verbose
- β Core Server: Server lifecycle, routing, and request handling
- β Middleware: All built-in middleware components
- β HTTP Methods: GET, POST, PUT, DELETE, and other HTTP methods
- β Path Parameters: Route matching and parameter extraction
- β Authentication: Bearer token validation and error handling
- β CORS: Cross-origin request handling
- β Cookie Management: Cookie parsing and setting
- β Error Handling: Proper error responses and status codes
We welcome contributions! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
- Code Style: Follow Swift conventions and use SwiftLint
- Testing: Add tests for new features and bug fixes
- Documentation: Update README and inline documentation
- Compatibility: Maintain backward compatibility when possible
This project is licensed under the MIT License - see the LICENSE file for details.