🎯 Recommended Samples
Balanced sample collections from various categories for you to explore
macOS Web Features Swift Samples
macOS Swift web features examples including URL routing, middleware pipeline, and static file serving
💻 HTTP Request Routing swift
🟡 intermediate
⭐⭐⭐
Implement flexible HTTP request routing with pattern matching, parameter extraction, and RESTful API support
⏱️ 30 min
🏷️ swift, macos, web, routing, http
Prerequisites:
Swift basics, HTTP protocol, Regular expressions
// macOS Swift Web Routing Examples
// Using Foundation and Vapor-inspired routing patterns
import Foundation
// MARK: - HTTP Models
// HTTP Request representation
struct HTTPRequest {
let method: HTTPMethod
let path: String
let headers: [String: String]
let body: Data?
var queryParameters: [String: String] = [:]
var pathParameters: [String: String] = [:]
}
enum HTTPMethod: String {
case GET = "GET"
case POST = "POST"
case PUT = "PUT"
case DELETE = "DELETE"
case PATCH = "PATCH"
case HEAD = "HEAD"
case OPTIONS = "OPTIONS"
case ANY = "*"
}
// HTTP Response representation
struct HTTPResponse {
var statusCode: HTTPStatusCode = .ok
var headers: [String: String] = [:]
var body: Data?
init(statusCode: HTTPStatusCode = .ok, headers: [String: String] = [:], body: Data? = nil) {
self.statusCode = statusCode
self.headers = headers
self.body = body
}
func setContentType(_ contentType: String) -> HTTPResponse {
var response = self
response.headers["Content-Type"] = contentType
return response
}
func setJSON(_ json: String) -> HTTPResponse {
var response = self
response.headers["Content-Type"] = "application/json"
response.body = json.data(using: .utf8)
return response
}
func setHTML(_ html: String) -> HTTPResponse {
var response = self
response.headers["Content-Type"] = "text/html"
response.body = html.data(using: .utf8)
return response
}
func setText(_ text: String) -> HTTPResponse {
var response = self
response.headers["Content-Type"] = "text/plain"
response.body = text.data(using: .utf8)
return response
}
}
enum HTTPStatusCode: Int {
case ok = 200
case created = 201
case accepted = 202
case noContent = 204
case movedPermanently = 301
case found = 302
case notModified = 304
case badRequest = 400
case unauthorized = 401
case forbidden = 403
case notFound = 404
case methodNotAllowed = 405
case conflict = 409
case internalServerError = 500
case notImplemented = 501
case badGateway = 502
case serviceUnavailable = 503
}
// MARK: - Route Handler
typealias RouteHandler = (HTTPRequest) -> HTTPResponse
// MARK: - Route
class Route {
let method: HTTPMethod
let pattern: String
let handler: RouteHandler
let regex: NSRegularExpression
let parameterNames: [String]
init(method: HTTPMethod, pattern: String, handler: @escaping RouteHandler) {
self.method = method
self.pattern = pattern
self.handler = handler
// Convert route pattern to regex
let (regexString, params) = Route.compilePattern(pattern)
self.regex = try! NSRegularExpression(pattern: regexString, options: [])
self.parameterNames = params
}
// Check if route matches request
func matches(request: HTTPRequest) -> Bool {
if method != .ANY && method != request.method {
return false
}
let range = NSRange(location: 0, length: request.path.utf16.count)
return regex.firstMatch(in: request.path, options: [], range: range) != nil
}
// Extract path parameters from request
func extractParameters(from request: HTTPRequest) -> [String: String] {
var parameters: [String: String] = [:]
let range = NSRange(location: 0, length: request.path.utf16.count)
if let match = regex.firstMatch(in: request.path, options: [], range: range) {
for (index, name) in parameterNames.enumerated() {
let captureRange = match.range(at: index + 1)
if captureRange.location != NSNotFound,
let range = Range(captureRange, in: request.path) {
parameters[name] = String(request.path[range])
}
}
}
return parameters
}
// Compile route pattern to regex
private static func compilePattern(_ pattern: String) -> (String, [String]) {
var regexPattern = "^"
var params: [String] = []
var current = pattern.startIndex
while current < pattern.endIndex {
if pattern[current] == ":" {
// Parameter found
let paramStart = pattern.index(after: current)
if let paramEnd = pattern[paramStart...].firstIndex(where: { $0 == "/" || $0 == "." }) {
let paramName = String(pattern[paramStart..<paramEnd])
params.append(paramName)
regexPattern += "([^/\.]+)"
current = paramEnd
} else {
let paramName = String(pattern[paramStart...])
params.append(paramName)
regexPattern += "([^/]+)"
current = pattern.endIndex
}
} else if pattern[current] == "*" {
// Wildcard
regexPattern += ".*"
current = pattern.index(after: current)
} else {
// Regular character
regexPattern += NSRegularExpression.escapedPattern(for: String(pattern[current]))
current = pattern.index(after: current)
}
}
regexPattern += "$"
return (regexPattern, params)
}
}
// MARK: - Router
class Router {
private var routes: [Route] = []
// Add route
@discardableResult
func add(method: HTTPMethod, path: String, handler: @escaping RouteHandler) -> Router {
let route = Route(method: method, pattern: path, handler: handler)
routes.append(route)
print("Added route: \(method.rawValue) \(path)")
return self
}
// Convenience methods
func get(_ path: String, handler: @escaping RouteHandler) -> Router {
add(method: .GET, path: path, handler: handler)
}
func post(_ path: String, handler: @escaping RouteHandler) -> Router {
add(method: .POST, path: path, handler: handler)
}
func put(_ path: String, handler: @escaping RouteHandler) -> Router {
add(method: .PUT, path: path, handler: handler)
}
func delete(_ path: String, handler: @escaping RouteHandler) -> Router {
add(method: .DELETE, path: path, handler: handler)
}
func patch(_ path: String, handler: @escaping RouteHandler) -> Router {
add(method: .PATCH, path: path, handler: handler)
}
func any(_ path: String, handler: @escaping RouteHandler) -> Router {
add(method: .ANY, path: path, handler: handler)
}
// Route request
func route(request: HTTPRequest) -> HTTPResponse {
for route in routes {
if route.matches(request: request) {
var routedRequest = request
routedRequest.pathParameters = route.extractParameters(from: request)
print("Routing: \(request.method.rawValue) \(request.path) -> \(route.pattern)")
do {
return route.handler(routedRequest)
} catch {
return HTTPResponse(statusCode: .internalServerError, body: "Internal Server Error".data(using: .utf8))
}
}
}
// No matching route
return HTTPResponse(statusCode: .notFound).setText("404 Not Found")
}
// Group routes with prefix
func group(prefix: String, configure: (Router) -> Void) -> Router {
let groupRouter = Router()
configure(groupRouter)
// Add all routes from group with prefix
for route in groupRouter.routes {
let fullPath = prefix + route.pattern
let newRoute = Route(method: route.method, pattern: fullPath, handler: route.handler)
routes.append(newRoute)
}
return self
}
}
// MARK: - RESTful API Builder
class RESTfulAPI {
let router = Router()
private let resourceName: String
init(resourceName: String) {
self.resourceName = resourceName
setupDefaultRoutes()
}
private func setupDefaultRoutes() {
let path = "/\(resourceName)"
// List all resources
router.get(path) { request in
let response = HTTPResponse()
let json = """
{
"resource": "\(self.resourceName)",
"items": [
{"id": 1, "name": "Item 1"},
{"id": 2, "name": "Item 2"},
{"id": 3, "name": "Item 3"}
],
"count": 3
}
"""
return response.setJSON(json)
}
// Get specific resource
router.get("\(path)/:id") { request in
let id = request.pathParameters["id"] ?? "0"
let response = HTTPResponse()
let json = """
{
"id": \(id),
"name": "Item \(id)",
"description": "This is item \(id)"
}
"""
return response.setJSON(json)
}
// Create new resource
router.post(path) { request in
let response = HTTPResponse(statusCode: .created)
let json = """
{
"id": 4,
"status": "created",
"message": "Resource created successfully"
}
"""
return response.setJSON(json)
}
// Update resource
router.put("\(path)/:id") { request in
let id = request.pathParameters["id"] ?? "0"
let response = HTTPResponse()
let json = """
{
"id": \(id),
"status": "updated",
"message": "Resource \(id) updated successfully"
}
"""
return response.setJSON(json)
}
// Delete resource
router.delete("\(path)/:id") { request in
let id = request.pathParameters["id"] ?? "0"
let response = HTTPResponse()
let json = """
{
"id": \(id),
"status": "deleted",
"message": "Resource \(id) deleted successfully"
}
"""
return response.setJSON(json)
}
}
// Customize routes
func customize(using builder: (Router) -> Void) -> RESTfulAPI {
builder(router)
return self
}
// Get router
func build() -> Router {
return router
}
}
// MARK: - Route Middleware
class RouteMiddleware {
let handler: (HTTPRequest, @escaping (HTTPRequest) -> HTTPResponse) -> HTTPResponse
init(handler: @escaping (HTTPRequest, @escaping (HTTPRequest) -> HTTPResponse) -> HTTPResponse) {
self.handler = handler
}
func process(request: HTTPRequest, next: @escaping (HTTPRequest) -> HTTPResponse) -> HTTPResponse {
return handler(request, next)
}
}
// Middleware implementations
extension RouteMiddleware {
// Logging middleware
static func logging() -> RouteMiddleware {
return RouteMiddleware { request, next in
print("[\(Date())] \(request.method.rawValue) \(request.path)")
let response = next(request)
print("[Response] \(response.statusCode.rawValue)")
return response
}
}
// Authentication middleware
static func authenticate() -> RouteMiddleware {
return RouteMiddleware { request, next in
if let auth = request.headers["Authorization"], auth.hasPrefix("Bearer ") {
return next(request)
} else {
return HTTPResponse(statusCode: .unauthorized).setJSON("{"error": "Unauthorized"}")
}
}
}
// CORS middleware
static func cors() -> RouteMiddleware {
return RouteMiddleware { request, next in
var response = next(request)
if request.method == .OPTIONS {
response.headers["Access-Control-Allow-Origin"] = "*"
response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS"
response.headers["Access-Control-Allow-Headers"] = "Content-Type, Authorization"
return response
}
response.headers["Access-Control-Allow-Origin"] = "*"
return response
}
}
}
// MARK: - Demonstration
func demonstrateWebRouting() {
print("=== macOS Swift Web Routing Examples ===\n")
// 1. Basic router setup
print("--- 1. Basic Router Setup ---")
let router = Router()
// Home page
router.get("/") { request in
let html = """
<!DOCTYPE html>
<html>
<head><title>Swift Routing Demo</title></head>
<body>
<h1>Welcome to Swift Routing!</h1>
<p>Try these routes:</p>
<ul>
<li><a href="/api/hello">GET /api/hello</a></li>
<li><a href="/api/hello/John">GET /api/hello/John</a></li>
<li><a href="/api/users">GET /api/users</a></li>
<li><a href="/api/users/123">GET /api/users/123</a></li>
<li><a href="/api/search?q=swift">GET /api/search?q=swift</a></li>
</ul>
</body>
</html>
"""
return HTTPResponse().setHTML(html)
}
// Simple API endpoint
router.get("/api/hello") { request in
return HTTPResponse().setJSON("{"message": "Hello, World!"}")
}
// Parameterized route
router.get("/api/hello/:name") { request in
let name = request.pathParameters["name"] ?? "Guest"
return HTTPResponse().setJSON("{"message": "Hello, \(name)!"}")
}
// Multiple parameters
router.get("/api/users/:userId/posts/:postId") { request in
let userId = request.pathParameters["userId"] ?? "0"
let postId = request.pathParameters["postId"] ?? "0"
return HTTPResponse().setJSON("{"userId": \(userId), "postId": \(postId)}")
}
// Wildcard route
router.get("/api/files/*") { request in
return HTTPResponse().setJSON("{"path": "\(request.path)"}")
}
// 2. RESTful API
print("\n--- 2. RESTful API ---")
let userAPI = RESTfulAPI(resourceName: "users")
// Customize with additional routes
userAPI.customize { router in
router.get("/users/:id/posts") { request in
let id = request.pathParameters["id"] ?? "0"
return HTTPResponse().setJSON("{"userId": \(id), "posts": []}")
}
}
// Add RESTful routes to main router
let apiRouter = userAPI.build()
for route in apiRouter.routes {
router.routes.append(route)
}
// 3. Route groups
print("\n--- 3. Route Groups ---")
router.group(prefix: "/api/v1") { group in
group.get("/status") { request in
return HTTPResponse().setJSON("{"version": "v1", "status": "ok"}")
}
group.get("/info") { request in
return HTTPResponse().setJSON("{"version": "v1", "info": "API v1"}")
}
}
// 4. Test routing
print("\n--- 4. Testing Routes ---")
let testRequests = [
HTTPRequest(method: .GET, path: "/", headers: [:], body: nil),
HTTPRequest(method: .GET, path: "/api/hello", headers: [:], body: nil),
HTTPRequest(method: .GET, path: "/api/hello/John", headers: [:], body: nil),
HTTPRequest(method: .GET, path: "/api/users/123/posts/456", headers: [:], body: nil),
HTTPRequest(method: .GET, path: "/api/files/documents/report.pdf", headers: [:], body: nil),
HTTPRequest(method: .GET, path: "/api/users", headers: [:], body: nil),
HTTPRequest(method: .GET, path: "/api/users/42", headers: [:], body: nil),
HTTPRequest(method: .GET, path: "/api/v1/status", headers: [:], body: nil),
HTTPRequest(method: .GET, path: "/nonexistent", headers: [:], body: nil)
]
for request in testRequests {
print("\nRequest: \(request.method.rawValue) \(request.path)")
let response = router.route(request: request)
print("Response: \(response.statusCode.rawValue)")
if let body = response.body,
let bodyString = String(data: body, encoding: .utf8),
bodyString.count < 200 {
print("Body: \(bodyString)")
}
}
print("\n=== Web Routing Demo Completed ===")
}
// Run demonstration
demonstrateWebRouting()
💻 Request Processing Middleware swift
🟡 intermediate
⭐⭐⭐⭐
Implement middleware pipeline for request logging, authentication, CORS, and error handling
⏱️ 35 min
🏷️ swift, macos, web, middleware, pipeline
Prerequisites:
Swift basics, HTTP protocol, Functional programming
// macOS Swift Middleware Pipeline Examples
// Using Foundation and async/await patterns
import Foundation
// MARK: - HTTP Context
struct HTTPContext {
var request: HTTPRequest
var response: HTTPResponse
var metadata: [String: Any] = [:]
init(request: HTTPRequest) {
self.request = request
self.response = HTTPResponse()
}
}
// MARK: - Middleware Protocol
protocol Middleware {
func process(context: HTTPContext, next: @escaping (HTTPContext) -> HTTPContext) -> HTTPContext
}
// MARK: - Middleware Pipeline
class MiddlewarePipeline {
private var middlewares: [Middleware] = []
// Add middleware to pipeline
@discardableResult
func use(_ middleware: Middleware) -> MiddlewarePipeline {
middlewares.append(middleware)
print("Added middleware: \(type(of: middleware))")
return self
}
// Process request through pipeline
func process(request: HTTPRequest, finalHandler: @escaping (HTTPContext) -> HTTPContext) -> HTTPContext {
var context = HTTPContext(request: request)
// Build middleware chain
let chain = buildChain(index: 0, finalHandler: finalHandler)
return chain(context)
}
// Build middleware chain recursively
private func buildChain(index: Int, finalHandler: @escaping (HTTPContext) -> HTTPContext) -> (HTTPContext) -> HTTPContext {
if index < middlewares.count {
let middleware = middlewares[index]
return { context in
let next = self.buildChain(index: index + 1, finalHandler: finalHandler)
return middleware.process(context: context, next: next)
}
} else {
return finalHandler
}
}
}
// MARK: - Middleware Implementations
// 1. Logging Middleware
class LoggingMiddleware: Middleware {
let logLevel: LogLevel
init(logLevel: LogLevel = .info) {
self.logLevel = logLevel
}
func process(context: HTTPContext, next: @escaping (HTTPContext) -> HTTPContext) -> HTTPContext {
let requestId = UUID().uuidString.prefix(8)
let startTime = Date()
log(.info, "[\(requestId]) \(context.request.method.rawValue) \(context.request.path) -> Started")
// Store metadata
context.metadata["requestId"] = String(requestId)
context.metadata["startTime"] = startTime
// Process next middleware
let result = next(context)
// Log completion
let duration = Date().timeIntervalSince(startTime)
log(.info, "[\(requestId)] \(context.request.method.rawValue) \(context.request.path) -> \(result.response.statusCode.rawValue) (\(String(format: "%.0f", duration * 1000))ms)")
return result
}
private func log(_ level: LogLevel, _ message: String) {
if level.rawValue >= logLevel.rawValue {
print("[\(level)] \(message)")
}
}
}
enum LogLevel: Int, Comparable {
case debug = 0
case info = 1
case warning = 2
case error = 3
static func < (lhs: LogLevel, rhs: LogLevel) -> Bool {
return lhs.rawValue < rhs.rawValue
}
}
// 2. Authentication Middleware
class AuthenticationMiddleware: Middleware {
let authPaths: Set<String>
let excludePaths: Set<String>
init(authPaths: Set<String>, excludePaths: Set<String> = []) {
self.authPaths = authPaths
self.excludePaths = excludePaths
}
func process(context: HTTPContext, next: @escaping (HTTPContext) -> HTTPContext) -> HTTPContext {
let path = context.request.path
// Check if path requires authentication
if authPaths.contains(where: { path.hasPrefix($0) }) &&
!excludePaths.contains(where: { path.hasPrefix($0) }) {
// Check for Authorization header
guard let authHeader = context.request.headers["Authorization"],
authHeader.hasPrefix("Bearer ") else {
log(.warning, "Authentication required for: \(path)")
var unauthorizedContext = context
unauthorizedContext.response = HTTPResponse(statusCode: .unauthorized)
.setJSON("{"error": "Authentication required", "code": 401}")
return unauthorizedContext
}
// Validate token (simplified)
let token = String(authHeader.dropFirst(7))
if !isValidToken(token) {
log(.warning, "Invalid token for: \(path)")
var forbiddenContext = context
forbiddenContext.response = HTTPResponse(statusCode: .forbidden)
.setJSON("{"error": "Invalid token", "code": 403}")
return forbiddenContext
}
// Store user info in context
context.metadata["authenticated"] = true
context.metadata["userId"] = extractUserId(from: token)
log(.info, "User authenticated: \(context.metadata["userId"] ?? "unknown")")
}
return next(context)
}
private func isValidToken(_ token: String) -> Bool {
// Simplified token validation
return token.count > 20
}
private func extractUserId(from token: String) -> String {
// Extract user ID from token (simplified)
return "user_\(token.prefix(8))"
}
private func log(_ level: LogLevel, _ message: String) {
print("[\(level)] \(message)")
}
}
// 3. CORS Middleware
class CORSMiddleware: Middleware {
let allowedOrigins: [String]
let allowedMethods: [String]
let allowedHeaders: [String]
init(allowedOrigins: [String] = ["*"],
allowedMethods: [String] = ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allowedHeaders: [String] = ["Content-Type", "Authorization"]) {
self.allowedOrigins = allowedOrigins
self.allowedMethods = allowedMethods
self.allowedHeaders = allowedHeaders
}
func process(context: HTTPContext, next: @escaping (HTTPContext) -> HTTPContext) -> HTTPContext {
// Handle preflight request
if context.request.method == .OPTIONS {
var preflightContext = context
preflightContext.response.statusCode = .ok
preflightContext.response.headers["Access-Control-Allow-Origin"] = allowedOrigins.first ?? "*"
preflightContext.response.headers["Access-Control-Allow-Methods"] = allowedMethods.joined(separator: ", ")
preflightContext.response.headers["Access-Control-Allow-Headers"] = allowedHeaders.joined(separator: ", ")
preflightContext.response.headers["Access-Control-Max-Age"] = "86400"
return preflightContext
}
// Add CORS headers to response
let result = next(context)
result.response.headers["Access-Control-Allow-Origin"] = allowedOrigins.first ?? "*"
result.response.headers["Access-Control-Allow-Methods"] = allowedMethods.joined(separator: ", ")
result.response.headers["Access-Control-Allow-Headers"] = allowedHeaders.joined(separator: ", ")
if let origin = context.request.headers["Origin"] {
result.response.headers["Access-Control-Allow-Origin"] = origin
result.response.headers["Vary"] = "Origin"
}
return result
}
}
// 4. Rate Limiting Middleware
class RateLimitingMiddleware: Middleware {
private var requestCounts: [String: [(Date, Int)]] = [:]
private let maxRequests: Int
private let windowInSeconds: Int
private let cleanupInterval: Int
init(maxRequests: Int = 100, windowInSeconds: Int = 60, cleanupInterval: Int = 300) {
self.maxRequests = maxRequests
self.windowInSeconds = windowInSeconds
self.cleanupInterval = cleanupInterval
}
func process(context: HTTPContext, next: @escaping (HTTPContext) -> HTTPContext) -> HTTPContext {
let clientId = getClientId(from: context.request)
let now = Date()
// Clean up old entries
if Int.random(in: 1...100) == 1 { // Occasionally cleanup
cleanup()
}
// Check rate limit
if isRateLimited(clientId: clientId, at: now) {
log(.warning, "Rate limit exceeded for: \(clientId)")
var rateLimitedContext = context
rateLimitedContext.response = HTTPResponse(statusCode: .serviceUnavailable)
.setJSON("{"error": "Rate limit exceeded", "code": 429}")
rateLimitedContext.response.headers["Retry-After"] = String(windowInSeconds)
return rateLimitedContext
}
// Record request
recordRequest(clientId: clientId, at: now)
return next(context)
}
private func getClientId(from request: HTTPRequest) -> String {
// Use IP or User-Agent as client identifier
return request.headers["X-Forwarded-For"] ??
request.headers["X-Real-IP"] ??
request.headers["User-Agent"] ??
"unknown"
}
private func isRateLimited(clientId: String, at now: Date) -> Bool {
guard let requests = requestCounts[clientId] else {
return false
}
let cutoff = now.addingTimeInterval(-Double(windowInSeconds))
let recentRequests = requests.filter { $0.0 > cutoff }
return recentRequests.count >= maxRequests
}
private func recordRequest(clientId: String, at now: Date) {
if requestCounts[clientId] == nil {
requestCounts[clientId] = []
}
requestCounts[clientId]?.append((now, 1))
}
private func cleanup() {
let cutoff = Date().addingTimeInterval(-Double(cleanupInterval))
for clientId in requestCounts.keys {
requestCounts[clientId]?..removeAll { $0.0 < cutoff }
}
}
private func log(_ level: LogLevel, _ message: String) {
print("[\(level)] \(message)")
}
}
// 5. Request Body Parsing Middleware
class BodyParsingMiddleware: Middleware {
func process(context: HTTPContext, next: @escaping (HTTPContext) -> HTTPContext) -> HTTPContext {
guard let body = context.request.body else {
return next(context)
}
// Check content type
let contentType = context.request.headers["Content-Type"] ?? ""
if contentType.contains("application/json") {
// Parse JSON body
if let json = try? JSONSerialization.jsonObject(with: body, options: []) as? [String: Any] {
context.metadata["parsedBody"] = json
log(.info, "JSON body parsed: \(json.keys.count) fields")
}
} else if contentType.contains("application/x-www-form-urlencoded") {
// Parse form data
if let bodyString = String(data: body, encoding: .utf8) {
let formData = parseFormData(bodyString)
context.metadata["parsedBody"] = formData
log(.info, "Form data parsed: \(formData.keys.count) fields")
}
} else {
// Store raw body
context.metadata["rawBody"] = body
}
return next(context)
}
private func parseFormData(_ string: String) -> [String: String] {
var result: [String: String] = [:]
let pairs = string.components(separatedBy: "&")
for pair in pairs {
let components = pair.components(separatedBy: "=")
if components.count == 2,
let key = components[0].removingPercentEncoding,
let value = components[1].removingPercentEncoding {
result[key] = value
}
}
return result
}
private func log(_ level: LogLevel, _ message: String) {
print("[\(level)] \(message)")
}
}
// 6. Error Handling Middleware
class ErrorHandlingMiddleware: Middleware {
func process(context: HTTPContext, next: @escaping (HTTPContext) -> HTTPContext) -> HTTPContext {
do {
return next(context)
} catch {
log(.error, "Unhandled error: \(error)")
var errorContext = context
let requestId = context.metadata["requestId"] as? String ?? "unknown"
let errorJson = """
{
"error": "Internal Server Error",
"message": "\(error.localizedDescription)",
"requestId": "\(requestId)"
}
"""
errorContext.response = HTTPResponse(statusCode: .internalServerError)
.setJSON(errorJson)
return errorContext
}
}
private func log(_ level: LogLevel, _ message: String) {
print("[\(level)] \(message)")
}
}
// 7. Response Compression Middleware
class CompressionMiddleware: Middleware {
let minSize: Int
init(minSize: Int = 1024) {
self.minSize = minSize
}
func process(context: HTTPContext, next: @escaping (HTTPContext) -> HTTPContext) -> HTTPContext {
let result = next(context)
// Check if response should be compressed
guard let body = result.response.body,
body.count > minSize,
shouldCompress(for: context.request) else {
return result
}
// Compress body (simplified - just remove whitespace for JSON)
if let contentType = result.response.headers["Content-Type"],
contentType.contains("application/json"),
let bodyString = String(data: body, encoding: .utf8) {
let compressed = bodyString.split(separator: " ").joined()
if let compressedData = compressed.data(using: .utf8) {
result.response.body = compressedData
result.response.headers["Content-Encoding"] = "gzip"
result.response.headers["X-Original-Size"] = String(body.count)
result.response.headers["X-Compressed-Size"] = String(compressedData.count)
log(.info, "Compressed response: \(body.count) -> \(compressedData.count) bytes")
}
}
return result
}
private func shouldCompress(for request: HTTPRequest) -> Bool {
// Check Accept-Encoding header
if let acceptEncoding = request.headers["Accept-Encoding"] {
return acceptEncoding.contains("gzip") || acceptEncoding.contains("*")
}
return false
}
private func log(_ level: LogLevel, _ message: String) {
print("[\(level)] \(message)")
}
}
// 8. Timing Middleware
class TimingMiddleware: Middleware {
func process(context: HTTPContext, next: @escaping (HTTPContext) -> HTTPContext) -> HTTPContext {
let startTime = Date()
let result = next(context)
let duration = Date().timeIntervalSince(startTime)
result.response.headers["X-Response-Time"] = String(format: "%.3f", duration)
return result
}
}
// MARK: - HTTP Types (from previous example)
struct HTTPRequest {
let method: HTTPMethod
let path: String
let headers: [String: String]
let body: Data?
}
enum HTTPMethod: String {
case GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, ANY
}
struct HTTPResponse {
var statusCode: HTTPStatusCode = .ok
var headers: [String: String] = [:]
var body: Data?
func setJSON(_ json: String) -> HTTPResponse {
var response = self
response.headers["Content-Type"] = "application/json"
response.body = json.data(using: .utf8)
return response
}
}
enum HTTPStatusCode: Int {
case ok = 200
case unauthorized = 401
case forbidden = 403
case notFound = 404
case serviceUnavailable = 503
case internalServerError = 500
}
// MARK: - Demonstration
func demonstrateMiddleware() {
print("=== macOS Swift Middleware Pipeline Examples ===\n")
// 1. Create pipeline with multiple middlewares
print("--- 1. Creating Middleware Pipeline ---")
let pipeline = MiddlewarePipeline()
pipeline
.use(LoggingMiddleware(logLevel: .info))
.use(ErrorHandlingMiddleware())
.use(CORSMiddleware())
.use(TimingMiddleware())
// 2. Test public route (no authentication)
print("\n--- 2. Testing Public Route ---")
let publicRequest = HTTPRequest(
method: .GET,
path: "/api/status",
headers: ["User-Agent": "TestClient"],
body: nil
)
let publicContext = pipeline.process(request: publicRequest) { context in
var ctx = context
ctx.response = HTTPResponse().setJSON("{"status": "ok", "message": "Public access"}")
return ctx
}
print("Public route response: \(publicContext.response.statusCode.rawValue)")
// 3. Test authenticated route
print("\n--- 3. Testing Authenticated Route ---")
let authPipeline = MiddlewarePipeline()
authPipeline
.use(LoggingMiddleware())
.use(AuthenticationMiddleware(authPaths: ["/api/protected"]))
.use(ErrorHandlingMiddleware())
let unauthRequest = HTTPRequest(
method: .GET,
path: "/api/protected/data",
headers: ["User-Agent": "TestClient"],
body: nil
)
let unauthContext = authPipeline.process(request: unauthRequest) { context in
var ctx = context
ctx.response = HTTPResponse().setJSON("{"data": "sensitive"}")
return ctx
}
print("Unauthorized access: \(unauthContext.response.statusCode.rawValue)")
// 4. Test with valid token
let authRequest = HTTPRequest(
method: .GET,
path: "/api/protected/data",
headers: ["Authorization": "Bearer valid_token_here_12345678"],
body: nil
)
let authContext = authPipeline.process(request: authRequest) { context in
var ctx = context
let userId = context.metadata["userId"] as? String ?? "unknown"
ctx.response = HTTPResponse().setJSON("{"data": "sensitive", "user": "\(userId)"}")
return ctx
}
print("Authorized access: \(authContext.response.statusCode.rawValue)")
// 5. Test rate limiting
print("\n--- 4. Testing Rate Limiting ---")
let rateLimitPipeline = MiddlewarePipeline()
rateLimitPipeline
.use(RateLimitingMiddleware(maxRequests: 3, windowInSeconds: 60))
.use(LoggingMiddleware())
// Make multiple requests
for i in 1...5 {
let request = HTTPRequest(
method: .GET,
path: "/api/test",
headers: ["User-Agent": "TestClient"],
body: nil
)
let context = rateLimitPipeline.process(request: request) { context in
var ctx = context
ctx.response = HTTPResponse().setJSON("{"request": \(i)}")
return ctx
}
print("Request \(i): \(context.response.statusCode.rawValue)")
}
// 6. Test body parsing
print("\n--- 5. Testing Body Parsing ---")
let bodyPipeline = MiddlewarePipeline()
bodyPipeline
.use(BodyParsingMiddleware())
.use(LoggingMiddleware())
let jsonBody = """
{
"name": "John Doe",
"email": "[email protected]"
}
""".data(using: .utf8)
let postRequest = HTTPRequest(
method: .POST,
path: "/api/users",
headers: ["Content-Type": "application/json"],
body: jsonBody
)
let postContext = bodyPipeline.process(request: postRequest) { context in
var ctx = context
if let body = context.metadata["parsedBody"] as? [String: Any] {
ctx.response = HTTPResponse().setJSON("{"received": \(body.keys.count) fields}")
}
return ctx
}
print("POST request: \(postContext.response.statusCode.rawValue)")
print("\n=== Middleware Pipeline Demo Completed ===")
}
// Run demonstration
demonstrateMiddleware()
💻 Static File Serving swift
🟡 intermediate
⭐⭐⭐
Implement efficient static file serving with MIME type detection, caching headers, and security features
⏱️ 25 min
🏷️ swift, macos, web, static files, server
Prerequisites:
Swift basics, File system operations, HTTP protocol
// macOS Swift Static File Serving Examples
// Using Foundation and URL handling
import Foundation
// MARK: - Static File Server Configuration
struct StaticFileConfig {
var rootDirectory: String
var enableDirectoryBrowsing: Bool = false
var enableDefaultFiles: Bool = true
var defaultFileNames: [String] = ["index.html", "default.html", "home.html"]
var enableETag: Bool = true
var enableLastModified: Bool = true
var cacheMaxAge: Int = 3600 // 1 hour
var enableCompression: Bool = true
var allowedExtensions: [String] = [
"html", "htm", "css", "js", "json", "xml", "txt", "md",
"png", "jpg", "jpeg", "gif", "bmp", "svg", "ico",
"pdf", "zip", "woff", "woff2", "ttf"
]
var indexFileName: String {
return defaultFileNames.first ?? "index.html"
}
}
// MARK: - MIME Type Detector
class MIMETypeDetector {
static let shared = MIMETypeDetector()
private let mimeTypes: [String: String] = [
// Text files
"html": "text/html",
"htm": "text/html",
"css": "text/css",
"js": "application/javascript",
"json": "application/json",
"xml": "application/xml",
"txt": "text/plain",
"md": "text/markdown",
"csv": "text/csv",
// Images
"png": "image/png",
"jpg": "image/jpeg",
"jpeg": "image/jpeg",
"gif": "image/gif",
"bmp": "image/bmp",
"svg": "image/svg+xml",
"ico": "image/x-icon",
"webp": "image/webp",
// Fonts
"woff": "font/woff",
"woff2": "font/woff2",
"ttf": "font/ttf",
"otf": "font/otf",
"eot": "application/vnd.ms-fontobject",
// Documents
"pdf": "application/pdf",
"zip": "application/zip",
"tar": "application/x-tar",
"gz": "application/gzip",
// Audio/Video
"mp3": "audio/mpeg",
"wav": "audio/wav",
"mp4": "video/mp4",
"webm": "video/webm"
]
func detectMIMEType(for extension: String) -> String {
let ext = extension.lowercased()
return mimeTypes[ext] ?? "application/octet-stream"
}
func detectMIMEType(for url: URL) -> String {
return detectMIMEType(for: url.pathExtension)
}
}
// MARK: - Static File Server
class StaticFileServer {
let config: StaticFileConfig
private let fileManager = FileManager.default
private let mimeDetector = MIMETypeDetector.shared
init(config: StaticFileConfig) {
self.config = config
}
convenience init(rootDirectory: String) {
var config = StaticFileConfig(rootDirectory: rootDirectory)
self.init(config: config)
}
// Serve file for request path
func serveFile(for path: String) -> HTTPResponse {
// Normalize and validate path
let normalizedPath = normalizePath(path)
// Security check: prevent path traversal
if isPathTraversal(normalizedPath) {
return HTTPResponse(statusCode: .forbidden)
.setText("Forbidden: Path traversal detected")
}
// Resolve full path
let fullPath = resolvePath(normalizedPath)
// Check if path exists
var isDirectory: ObjCBool = false
guard fileManager.fileExists(atPath: fullPath, isDirectory: &isDirectory) else {
return HTTPResponse(statusCode: .notFound)
.setText("404 - File not found: \(path)")
}
// Handle directory
if isDirectory.boolValue {
return serveDirectory(at: fullPath, path: normalizedPath)
}
// Check file extension
let fileExtension = (fullPath as NSString).pathExtension.lowercased()
if !config.allowedExtensions.contains(fileExtension) {
return HTTPResponse(statusCode: .forbidden)
.setText("Forbidden: File type not allowed")
}
// Serve file
return serveFile(at: fullPath)
}
// Serve individual file
private func serveFile(at fullPath: String) -> HTTPResponse {
do {
// Get file attributes
let attributes = try fileManager.attributesOfItem(atPath: fullPath)
let fileSize = attributes[.size] as? UInt64 ?? 0
let modificationDate = attributes[.modificationDate] as? Date ?? Date()
// Read file content
let fileData = try Data(contentsOf: URL(fileURLWithPath: fullPath))
// Create response
var response = HTTPResponse()
response.body = fileData
// Set content type
let fileExtension = (fullPath as NSString).pathExtension.lowercased()
let mimeType = mimeDetector.detectMIMEType(for: fileExtension)
response.headers["Content-Type"] = mimeType
// Set content length
response.headers["Content-Length"] = String(fileSize)
// Set cache headers
if config.enableETag {
let eTag = generateETag(for: fullPath, size: fileSize, modifiedDate: modificationDate)
response.headers["ETag"] = eTag
response.headers["Cache-Control"] = "public, max-age=\(config.cacheMaxAge)"
}
if config.enableLastModified {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss z"
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
response.headers["Last-Modified"] = dateFormatter.string(from: modificationDate)
}
// Set security headers
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-Frame-Options"] = "SAMEORIGIN"
print("Served: \(fullPath) (\(fileSize) bytes, \(mimeType))")
return response
} catch {
print("Error serving file: \(error)")
return HTTPResponse(statusCode: .internalServerError)
.setText("Error reading file")
}
}
// Serve directory
private func serveDirectory(at fullPath: String, path: String) -> HTTPResponse {
// Try to serve default file
if config.enableDefaultFiles {
for defaultFile in config.defaultFileNames {
let defaultFilePath = (fullPath as NSString).appendingPathComponent(defaultFile)
if fileManager.fileExists(atPath: defaultFilePath) {
return serveFile(at: defaultFilePath)
}
}
}
// Directory browsing
if config.enableDirectoryBrowsing {
return serveDirectoryListing(at: fullPath, path: path)
}
// No default file and browsing disabled
return HTTPResponse(statusCode: .forbidden)
.setText("Forbidden: Directory browsing disabled")
}
// Serve directory listing
private func serveDirectoryListing(at fullPath: String, path: String) -> HTTPResponse {
do {
let contents = try fileManager.contentsOfDirectory(atPath: fullPath)
var directories: [String] = []
var files: [(name: String, size: UInt64, modified: Date)] = []
for item in contents {
let itemPath = (fullPath as NSString).appendingPathComponent(item)
var isDirectory: ObjCBool = false
fileManager.fileExists(atPath: itemPath, isDirectory: &isDirectory)
if isDirectory.boolValue {
directories.append(item)
} else {
if let attributes = try? fileManager.attributesOfItem(atPath: itemPath) {
let size = attributes[.size] as? UInt64 ?? 0
let modified = attributes[.modificationDate] as? Date ?? Date()
files.append((item, size, modified))
}
}
}
// Generate HTML listing
let html = generateDirectoryListingHTML(path: path, directories: directories, files: files)
var response = HTTPResponse()
response.headers["Content-Type"] = "text/html"
response.body = html.data(using: .utf8)
return response
} catch {
return HTTPResponse(statusCode: .internalServerError)
.setText("Error reading directory")
}
}
// Generate directory listing HTML
private func generateDirectoryListingHTML(path: String, directories: [String], files: [(name: String, size: UInt64, modified: Date)]) -> String {
var html = """
<!DOCTYPE html>
<html>
<head>
<title>Directory Listing: \(path)</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
h1 { color: #333; }
table { border-collapse: collapse; width: 100%; }
th, td { padding: 10px; text-align: left; border-bottom: 1px solid #ddd; }
th { background-color: #f4f4f4; }
a { text-decoration: none; color: #0066cc; }
a:hover { text-decoration: underline; }
.icon { margin-right: 10px; }
</style>
</head>
<body>
<h1>Directory listing for \(path.isEmpty ? "/" : path)</h1>
<table>
<thead>
<tr>
<th>Name</th>
<th>Size</th>
<th>Modified</th>
</tr>
</thead>
<tbody>
"""
// Parent directory link
if path != "/" && !path.isEmpty {
let parentPath = (path as NSString).deletingLastPathComponent
html += "\n<tr><td><span class=\"icon\">📁</span><a href=\"\(parentPath)\">../</a></td><td>-</td><td>-</td></tr>"
}
// Directories
for dir in directories.sorted() {
let dirPath = path.isEmpty ? "/\(dir)" : "\(path)/\(dir)"
html += "\n<tr><td><span class=\"icon\">📁</span><a href=\"\(dirPath)/\">\(dir)/</a></td><td>-</td><td>-</td></tr>"
}
// Files
for file in files.sorted(by: { $0.name < $1.name }) {
let filePath = path.isEmpty ? "/\(file.name)" : "\(path)/\(file.name)"
let icon = getFileIcon(for: file.name)
let size = formatBytes(file.size)
let modified = formatDate(file.modified)
html += "\n<tr><td><span class=\"icon\">\(icon)</span><a href=\"\(filePath)\">\(file.name)</a></td><td>\(size)</td><td>\(modified)</td></tr>"
}
html += """
</tbody>
</table>
<p>Total: \(directories.count) directories, \(files.count) files</p>
</body>
</html>
"""
return html
}
// Generate ETag
private func generateETag(for path: String, size: UInt64, modifiedDate: Date) -> String {
let hash = "\(modifiedDate.timeIntervalSince1970)-\(size)"
return "W/\"\(hash)\""
}
// Normalize path
private func normalizePath(_ path: String) -> String {
var normalized = path
// Remove leading slash
if normalized.hasPrefix("/") {
normalized = String(normalized.dropFirst())
}
// Replace backslashes with forward slashes
normalized = normalized.replacingOccurrences(of: "\\", with: "/")
// Remove query string
if let queryRange = normalized.range(of: "?") {
normalized = String(normalized[..<queryRange.lowerBound])
}
// Remove fragment
if let fragmentRange = normalized.range(of: "#") {
normalized = String(normalized[..<fragmentRange.lowerBound])
}
return normalized.isEmpty ? "/" : "/\(normalized)"
}
// Resolve full path
private func resolvePath(_ normalizedPath: String) -> String {
let relativePath = String(normalizedPath.dropFirst())
return (config.rootDirectory as NSString).appendingPathComponent(relativePath)
}
// Check for path traversal
private func isPathTraversal(_ path: String) -> Bool {
return path.contains("..") || path.contains("./") || path.contains("\\")
}
// Get file icon
private func getFileIcon(for fileName: String) -> String {
let ext = ((fileName as NSString).pathExtension).lowercased()
switch ext {
case "html", "htm": return "🌐"
case "css": return "🎨"
case "js": return "⚡"
case "json": return "📋"
case "png", "jpg", "jpeg", "gif", "svg", "webp": return "🖼️"
case "pdf": return "📕"
case "zip", "tar", "gz": return "📦"
case "woff", "woff2", "ttf": return "🔤"
default: return "📄"
}
}
// Format bytes
private func formatBytes(_ bytes: UInt64) -> String {
let kb = Double(bytes) / 1024
let mb = kb / 1024
let gb = mb / 1024
if gb >= 1 {
return String(format: "%.2f GB", gb)
} else if mb >= 1 {
return String(format: "%.2f MB", mb)
} else if kb >= 1 {
return String(format: "%.2f KB", kb)
} else {
return "\(bytes) B"
}
}
// Format date
private func formatDate(_ date: Date) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
return formatter.string(from: date)
}
}
// MARK: - HTTP Response
struct HTTPResponse {
var statusCode: HTTPStatusCode = .ok
var headers: [String: String] = [:]
var body: Data?
func setText(_ text: String) -> HTTPResponse {
var response = self
response.headers["Content-Type"] = "text/plain"
response.body = text.data(using: .utf8)
return response
}
}
enum HTTPStatusCode: Int {
case ok = 200
case forbidden = 403
case notFound = 404
case internalServerError = 500
}
// MARK: - File Cache
class FileCache {
private var cache: [String: CachedFile] = [:]
private let maxSize: Int
private let maxAge: TimeInterval
init(maxSize: Int = 100, maxAge: TimeInterval = 3600) {
self.maxSize = maxSize
self.maxAge = maxAge
}
func get(_ key: String) -> Data? {
guard let cached = cache[key] else { return nil }
// Check if expired
if Date().timeIntervalSince(cached.timestamp) > maxAge {
cache.removeValue(forKey: key)
return nil
}
return cached.data
}
func set(_ key: String, data: Data) {
// Evict oldest if necessary
if cache.count >= maxSize {
let oldest = cache.min(by: { $0.value.timestamp < $1.value.timestamp })
if let oldestKey = oldest?.key {
cache.removeValue(forKey: oldestKey)
}
}
cache[key] = CachedFile(data: data, timestamp: Date())
}
func clear() {
cache.removeAll()
}
private struct CachedFile {
let data: Data
let timestamp: Date
}
}
// MARK: - Demonstration
func demonstrateStaticFileServing() {
print("=== macOS Swift Static File Serving Examples ===\n")
// Create test directory structure
let testDirectory = "/tmp/static_file_test"
let fileManager = FileManager.default
try? fileManager.removeItem(atPath: testDirectory)
try? fileManager.createDirectory(atPath: testDirectory, withIntermediateDirectories: true)
// Create sample files
let indexPath = "\(testDirectory)/index.html"
let cssPath = "\(testDirectory)/style.css"
let jsPath = "\(testDirectory)/app.js"
let subDir = "\(testDirectory)/assets"
try? fileManager.createDirectory(atPath: subDir, withIntermediateDirectories: true)
try? "<html><body><h1>Test Page</h1></body></html>".write(toFile: indexPath, atomically: true, encoding: .utf8)
try? "body { margin: 0; }".write(toFile: cssPath, atomically: true, encoding: .utf8)
try? "console.log('Hello');".write(toFile: jsPath, atomically: true, encoding: .utf8)
try? "Test".write(toFile: "\(subDir)/test.txt", atomically: true, encoding: .utf8)
// 1. Basic file serving
print("--- 1. Basic File Serving ---")
var config = StaticFileConfig(rootDirectory: testDirectory)
config.enableDirectoryBrowsing = true
let server = StaticFileServer(config: config)
// Test serving HTML file
let response1 = server.serveFile(for: "/index.html")
print("Served /index.html: \(response1.statusCode.rawValue)")
if let contentType = response1.headers["Content-Type"] {
print(" Content-Type: \(contentType)")
}
// Test serving CSS file
let response2 = server.serveFile(for: "/style.css")
print("Served /style.css: \(response2.statusCode.rawValue)")
print(" Content-Type: \(response2.headers["Content-Type"] ?? "none")")
// 2. Default file serving
print("\n--- 2. Default File Serving ---")
let response3 = server.serveFile(for: "/")
print("Served / (default): \(response3.statusCode.rawValue)")
// 3. Directory listing
print("\n--- 3. Directory Listing ---")
let response4 = server.serveFile(for: "/assets/")
print("Served /assets/ (listing): \(response4.statusCode.rawValue)")
// 4. 404 handling
print("\n--- 4. 404 Handling ---")
let response5 = server.serveFile(for: "/nonexistent.html")
print("Served /nonexistent.html: \(response5.statusCode.rawValue)")
// 5. Path traversal protection
print("\n--- 5. Path Traversal Protection ---")
let response6 = server.serveFile(for: "/../../../etc/passwd")
print("Served ../../../etc/passwd: \(response6.statusCode.rawValue)")
// 6. File type restriction
print("\n--- 6. File Type Restriction ---")
try? "test".write(toFile: "\(testDirectory)/test.exe", atomically: true, encoding: .utf8)
let response7 = server.serveFile(for: "/test.exe")
print("Served /test.exe: \(response7.statusCode.rawValue)")
// 7. ETag generation
print("\n--- 7. ETag Generation ---")
config.enableETag = true
let serverWithETag = StaticFileServer(config: config)
let response8 = serverWithETag.serveFile(for: "/index.html")
print("ETag: \(response8.headers["ETag"] ?? "none")")
print("Cache-Control: \(response8.headers["Cache-Control"] ?? "none")")
// Cleanup
try? fileManager.removeItem(atPath: testDirectory)
print("\nCleanup completed")
print("\n=== Static File Serving Demo Completed ===")
}
// Run demonstration
demonstrateStaticFileServing()