hummingbird
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseHummingbird 2
Hummingbird 2
Hummingbird is a lightweight, flexible HTTP server framework for Swift, built on SwiftNIO with full Swift Concurrency support. It's an SSWG (Swift Server Work Group) incubated project.
Hummingbird是一款轻量、灵活的Swift HTTP服务器框架,基于SwiftNIO构建,全面支持Swift并发模型。它是SSWG(Swift Server Work Group,Swift服务器工作组)的孵化项目。
Quick Start
快速开始
Installation
安装
Add to :
Package.swiftswift
dependencies: [
.package(url: "https://github.com/hummingbird-project/hummingbird.git", from: "2.0.0")
]Add to your target:
swift
.target(
name: "App",
dependencies: [
.product(name: "Hummingbird", package: "hummingbird")
]
)将以下内容添加到:
Package.swiftswift
dependencies: [
.package(url: "https://github.com/hummingbird-project/hummingbird.git", from: "2.0.0")
]添加到你的目标:
swift
.target(
name: "App",
dependencies: [
.product(name: "Hummingbird", package: "hummingbird")
]
)Minimal Application
最简应用
swift
import Hummingbird
@main
struct App {
static func main() async throws {
let router = Router()
router.get("/") { _, _ in
"Hello, World!"
}
router.get("/health") { _, _ -> HTTPResponse.Status in
.ok
}
let app = Application(
router: router,
configuration: .init(address: .hostname("0.0.0.0", port: 8080))
)
try await app.runService()
}
}swift
import Hummingbird
@main
struct App {
static func main() async throws {
let router = Router()
router.get("/") { _, _ in
"Hello, World!"
}
router.get("/health") { _, _ -> HTTPResponse.Status in
.ok
}
let app = Application(
router: router,
configuration: .init(address: .hostname("0.0.0.0", port: 8080))
)
try await app.runService()
}
}Core Concepts
核心概念
Router
路由
The router directs requests to handlers based on path and HTTP method:
swift
let router = Router()
// Basic routes
router.get("/users") { request, context in
// Return all users
}
router.post("/users") { request, context in
// Create user
}
router.get("/users/{id}") { request, context in
let id = context.parameters.get("id")!
// Return user by ID
}
router.put("/users/{id}") { request, context in
// Update user
}
router.delete("/users/{id}") { request, context in
// Delete user
}路由会根据路径和HTTP方法将请求分发到对应的处理器:
swift
let router = Router()
// 基础路由
router.get("/users") { request, context in
// 返回所有用户
}
router.post("/users") { request, context in
// 创建用户
}
router.get("/users/{id}") { request, context in
let id = context.parameters.get("id")!
// 根据ID返回用户
}
router.put("/users/{id}") { request, context in
// 更新用户
}
router.delete("/users/{id}") { request, context in
// 删除用户
}Route Groups
路由组
Organize routes with common prefixes:
swift
let router = Router()
router.group("api/v1") { api in
api.group("users") { users in
users.get { _, _ in /* list users */ }
users.post { _, _ in /* create user */ }
users.get("{id}") { _, context in /* get user */ }
}
api.group("posts") { posts in
posts.get { _, _ in /* list posts */ }
}
}使用公共前缀组织路由:
swift
let router = Router()
router.group("api/v1") { api in
api.group("users") { users in
users.get { _, _ in /* 列出用户 */ }
users.post { _, _ in /* 创建用户 */ }
users.get("{id}") { _, context in /* 获取用户 */ }
}
api.group("posts") { posts in
posts.get { _, _ in /* 列出帖子 */ }
}
}Request Context
请求上下文
Each request gets a context instance. Use or create custom contexts:
BasicRequestContextswift
// Using basic context
let router = Router(context: BasicRequestContext.self)
// Custom context for additional properties
struct AppRequestContext: RequestContext {
var coreContext: CoreRequestContextStorage
var requestId: String?
var authenticatedUser: User?
init(source: Source) {
self.coreContext = .init(source: source)
}
}
let router = Router(context: AppRequestContext.self)每个请求都会对应一个上下文实例。可以使用或自定义上下文:
BasicRequestContextswift
// 使用基础上下文
let router = Router(context: BasicRequestContext.self)
// 自定义上下文以添加额外属性
struct AppRequestContext: RequestContext {
var coreContext: CoreRequestContextStorage
var requestId: String?
var authenticatedUser: User?
init(source: Source) {
self.coreContext = .init(source: source)
}
}
let router = Router(context: AppRequestContext.self)Child Request Context
子请求上下文
Use to transform contexts and guarantee properties exist:
ChildRequestContextswift
import HummingbirdAuth
// Parent context with optional authentication
struct AppRequestContext: AuthRequestContext {
var coreContext: CoreRequestContextStorage
var auth: LoginCache
init(source: Source) {
self.coreContext = .init(source: source)
self.auth = .init()
}
}
// Child context guarantees authenticated user
struct AuthenticatedContext: ChildRequestContext {
typealias ParentContext = AppRequestContext
var coreContext: CoreRequestContextStorage
var user: User // Non-optional, guaranteed to exist
init(context: AppRequestContext) throws {
self.coreContext = context.coreContext
self.user = try context.auth.require(User.self)
}
}
// Use in routes
let router = Router(context: AppRequestContext.self)
router.group("api")
.add(middleware: BearerAuthenticator())
.group(context: AuthenticatedContext.self) { protected in
// All routes here have guaranteed user
protected.get("/me") { _, context -> User in
context.user // Non-optional access
}
}See for detailed patterns including multi-level child contexts and protocol composition.
references/request-context.md使用转换上下文,确保所需属性存在:
ChildRequestContextswift
import HummingbirdAuth
// 包含可选认证信息的父上下文
struct AppRequestContext: AuthRequestContext {
var coreContext: CoreRequestContextStorage
var auth: LoginCache
init(source: Source) {
self.coreContext = .init(source: source)
self.auth = .init()
}
}
// 确保已认证用户存在的子上下文
struct AuthenticatedContext: ChildRequestContext {
typealias ParentContext = AppRequestContext
var coreContext: CoreRequestContextStorage
var user: User // 非可选,确保存在
init(context: AppRequestContext) throws {
self.coreContext = context.coreContext
self.user = try context.auth.require(User.self)
}
}
// 在路由中使用
let router = Router(context: AppRequestContext.self)
router.group("api")
.add(middleware: BearerAuthenticator())
.group(context: AuthenticatedContext.self) { protected in
// 此组下的所有路由都确保用户已认证
protected.get("/me") { _, context -> User in
context.user // 非可选访问
}
}如需详细模式(包括多级子上下文和协议组合),请参阅。
references/request-context.mdRoute Handlers
路由处理器
Handlers receive and , returning any :
RequestContextResponseGeneratorswift
// Return String
router.get("/hello") { _, _ in
"Hello, World!"
}
// Return HTTP status
router.get("/health") { _, _ -> HTTPResponse.Status in
.ok
}
// Return Codable (auto-encoded to JSON)
router.get("/user") { _, _ -> User in
User(id: 1, name: "Alice")
}
// Return full Response
router.get("/custom") { _, _ -> Response in
Response(
status: .ok,
headers: [.contentType: "text/plain"],
body: .init(byteBuffer: ByteBuffer(string: "Custom response"))
)
}处理器接收和,返回任意类型:
RequestContextResponseGeneratorswift
// 返回字符串
router.get("/hello") { _, _ in
"Hello, World!"
}
// 返回HTTP状态
router.get("/health") { _, _ -> HTTPResponse.Status in
.ok
}
// 返回Codable类型(自动编码为JSON)
router.get("/user") { _, _ -> User in
User(id: 1, name: "Alice")
}
// 返回完整Response
router.get("/custom") { _, _ -> Response in
Response(
status: .ok,
headers: [.contentType: "text/plain"],
body: .init(byteBuffer: ByteBuffer(string: "Custom response"))
)
}Request Handling
请求处理
Path Parameters
路径参数
swift
router.get("/users/{id}") { request, context in
let id = context.parameters.get("id")!
return "User ID: \(id)"
}
router.get("/posts/{postId}/comments/{commentId}") { request, context in
let postId = context.parameters.get("postId")!
let commentId = context.parameters.get("commentId")!
return "Post \(postId), Comment \(commentId)"
}swift
router.get("/users/{id}") { request, context in
let id = context.parameters.get("id")!
return "用户ID: \(id)"
}
router.get("/posts/{postId}/comments/{commentId}") { request, context in
let postId = context.parameters.get("postId")!
let commentId = context.parameters.get("commentId")!
return "帖子 \(postId),评论 \(commentId)"
}Query Parameters
查询参数
swift
router.get("/search") { request, context in
let query = request.uri.queryParameters.get("q") ?? ""
let page = request.uri.queryParameters.get("page").flatMap(Int.init) ?? 1
return "Searching for '\(query)' on page \(page)"
}swift
router.get("/search") { request, context in
let query = request.uri.queryParameters.get("q") ?? ""
let page = request.uri.queryParameters.get("page").flatMap(Int.init) ?? 1
return "正在搜索'\(query)',页码:\(page)"
}Request Body (JSON)
请求体(JSON)
swift
struct CreateUserRequest: Decodable {
let name: String
let email: String
}
router.post("/users") { request, context in
let input = try await request.decode(as: CreateUserRequest.self, context: context)
// Create user with input.name, input.email
return HTTPResponse.Status.created
}swift
struct CreateUserRequest: Decodable {
let name: String
let email: String
}
router.post("/users") { request, context in
let input = try await request.decode(as: CreateUserRequest.self, context: context)
// 使用input.name和input.email创建用户
return HTTPResponse.Status.created
}Headers
请求头
swift
router.get("/protected") { request, context in
guard let auth = request.headers[.authorization] else {
throw HTTPError(.unauthorized)
}
// Process authorization header
return "Authorized"
}swift
router.get("/protected") { request, context in
guard let auth = request.headers[.authorization] else {
throw HTTPError(.unauthorized)
}
// 处理授权头
return "已授权"
}Response Handling
响应处理
ResponseCodable
ResponseCodable
Types conforming to auto-encode to JSON:
ResponseCodableswift
struct User: ResponseCodable {
let id: Int
let name: String
let email: String
}
router.get("/users/{id}") { request, context -> User in
return User(id: 1, name: "Alice", email: "alice@example.com")
}符合协议的类型会自动编码为JSON:
ResponseCodableswift
struct User: ResponseCodable {
let id: Int
let name: String
let email: String
}
router.get("/users/{id}") { request, context -> User in
return User(id: 1, name: "Alice", email: "alice@example.com")
}EditedResponse
EditedResponse
Control status code and headers with response body:
swift
router.post("/users") { request, context -> EditedResponse<User> in
let user = User(id: 1, name: "Alice", email: "alice@example.com")
return EditedResponse(
status: .created,
headers: [.location: "/users/1"],
response: user
)
}通过响应体控制状态码和请求头:
swift
router.post("/users") { request, context -> EditedResponse<User> in
let user = User(id: 1, name: "Alice", email: "alice@example.com")
return EditedResponse(
status: .created,
headers: [.location: "/users/1"],
response: user
)
}HTTPError
HTTPError
Throw errors for HTTP error responses:
swift
router.get("/users/{id}") { request, context in
guard let user = findUser(id: context.parameters.get("id")!) else {
throw HTTPError(.notFound, message: "User not found")
}
return user
}抛出错误以返回HTTP错误响应:
swift
router.get("/users/{id}") { request, context in
guard let user = findUser(id: context.parameters.get("id")!) else {
throw HTTPError(.notFound, message: "用户不存在")
}
return user
}Middleware
中间件
Middleware processes requests before handlers and responses after:
swift
struct LoggingMiddleware<Context: RequestContext>: RouterMiddleware {
func handle(
_ request: Request,
context: Context,
next: (Request, Context) async throws -> Response
) async throws -> Response {
let start = ContinuousClock.now
let response = try await next(request, context)
let duration = ContinuousClock.now - start
print("\(request.method) \(request.uri.path) - \(response.status) (\(duration))")
return response
}
}
// Apply to router
router.middlewares.add(LoggingMiddleware())
// Apply to route group
router.group("api") { api in
api.middlewares.add(AuthMiddleware())
api.get("/protected") { _, _ in "Secret data" }
}中间件会在处理器之前处理请求,在处理器之后处理响应:
swift
struct LoggingMiddleware<Context: RequestContext>: RouterMiddleware {
func handle(
_ request: Request,
context: Context,
next: (Request, Context) async throws -> Response
) async throws -> Response {
let start = ContinuousClock.now
let response = try await next(request, context)
let duration = ContinuousClock.now - start
print("\(request.method) \(request.uri.path) - \(response.status) (\(duration))")
return response
}
}
// 应用到路由
router.middlewares.add(LoggingMiddleware())
// 应用到路由组
router.group("api") { api in
api.middlewares.add(AuthMiddleware())
api.get("/protected") { _, _ in "机密数据" }
}Built-in Middleware
内置中间件
swift
import Hummingbird
// CORS
router.middlewares.add(CORSMiddleware(
allowOrigin: .originBased,
allowHeaders: [.contentType, .authorization],
allowMethods: [.get, .post, .put, .delete]
))
// File serving
router.middlewares.add(FileMiddleware(rootFolder: "public"))
// Metrics (with swift-metrics)
router.middlewares.add(MetricsMiddleware())
// Tracing (with swift-distributed-tracing)
router.middlewares.add(TracingMiddleware())swift
import Hummingbird
// CORS
router.middlewares.add(CORSMiddleware(
allowOrigin: .originBased,
allowHeaders: [.contentType, .authorization],
allowMethods: [.get, .post, .put, .delete]
))
// 文件服务
router.middlewares.add(FileMiddleware(rootFolder: "public"))
// 指标(基于swift-metrics)
router.middlewares.add(MetricsMiddleware())
// 追踪(基于swift-distributed-tracing)
router.middlewares.add(TracingMiddleware())Application Configuration
应用配置
swift
let app = Application(
router: router,
configuration: .init(
address: .hostname("0.0.0.0", port: 8080),
serverName: "MyApp"
)
)
try await app.runService()swift
let app = Application(
router: router,
configuration: .init(
address: .hostname("0.0.0.0", port: 8080),
serverName: "MyApp"
)
)
try await app.runService()With ServiceLifecycle
结合ServiceLifecycle使用
swift
import Hummingbird
import ServiceLifecycle
@main
struct App {
static func main() async throws {
let router = Router()
// ... configure routes
let app = Application(
router: router,
configuration: .init(address: .hostname("0.0.0.0", port: 8080))
)
let serviceGroup = ServiceGroup(
services: [app],
gracefulShutdownSignals: [.sigterm, .sigint],
logger: Logger(label: "app")
)
try await serviceGroup.run()
}
}swift
import Hummingbird
import ServiceLifecycle
@main
struct App {
static func main() async throws {
let router = Router()
// ... 配置路由
let app = Application(
router: router,
configuration: .init(address: .hostname("0.0.0.0", port: 8080))
)
let serviceGroup = ServiceGroup(
services: [app],
gracefulShutdownSignals: [.sigterm, .sigint],
logger: Logger(label: "app")
)
try await serviceGroup.run()
}
}Extensions
扩展
Authentication (HummingbirdAuth)
认证(HummingbirdAuth)
swift
.product(name: "HummingbirdAuth", package: "hummingbird-auth")swift
import HummingbirdAuth
// Context with authentication
struct AppRequestContext: AuthRequestContext {
var coreContext: CoreRequestContextStorage
var auth: LoginCache
init(source: Source) {
self.coreContext = .init(source: source)
self.auth = .init()
}
}
// Bearer token authentication
router.group("api")
.add(middleware: BearerAuthenticator())
.get("/me") { request, context -> User in
let user = try context.auth.require(User.self)
return user
}swift
.product(name: "HummingbirdAuth", package: "hummingbird-auth")swift
import HummingbirdAuth
// 包含认证信息的上下文
struct AppRequestContext: AuthRequestContext {
var coreContext: CoreRequestContextStorage
var auth: LoginCache
init(source: Source) {
self.coreContext = .init(source: source)
self.auth = .init()
}
}
// Bearer令牌认证
router.group("api")
.add(middleware: BearerAuthenticator())
.get("/me") { request, context -> User in
let user = try context.auth.require(User.self)
return user
}WebSockets (HummingbirdWebSocket)
WebSocket(HummingbirdWebSocket)
swift
.product(name: "HummingbirdWebSocket", package: "hummingbird-websocket")swift
import HummingbirdWebSocket
router.ws("/chat") { inbound, outbound, context in
for try await message in inbound {
switch message {
case .text(let text):
try await outbound.write(.text("Echo: \(text)"))
case .binary(let data):
try await outbound.write(.binary(data))
}
}
}swift
.product(name: "HummingbirdWebSocket", package: "hummingbird-websocket")swift
import HummingbirdWebSocket
router.ws("/chat") { inbound, outbound, context in
for try await message in inbound {
switch message {
case .text(let text):
try await outbound.write(.text("Echo: \(text)"))
case .binary(let data):
try await outbound.write(.binary(data))
}
}
}Fluent ORM (HummingbirdFluent)
Fluent ORM(HummingbirdFluent)
swift
.product(name: "HummingbirdFluent", package: "hummingbird-fluent")swift
import HummingbirdFluent
import FluentPostgresDriver
// Add Fluent to application
let fluent = Fluent(logger: logger)
fluent.databases.use(.postgres(configuration: postgresConfig), as: .psql)
let app = Application(
router: router,
configuration: .init(address: .hostname("0.0.0.0", port: 8080))
)
app.addServices(fluent)swift
.product(name: "HummingbirdFluent", package: "hummingbird-fluent")swift
import HummingbirdFluent
import FluentPostgresDriver
// 为应用添加Fluent
let fluent = Fluent(logger: logger)
fluent.databases.use(.postgres(configuration: postgresConfig), as: .psql)
let app = Application(
router: router,
configuration: .init(address: .hostname("0.0.0.0", port: 8080))
)
app.addServices(fluent)HTTP/2 and TLS
HTTP/2与TLS
swift
.product(name: "HummingbirdHTTP2", package: "hummingbird")
.product(name: "HummingbirdTLS", package: "hummingbird")swift
.product(name: "HummingbirdHTTP2", package: "hummingbird")
.product(name: "HummingbirdTLS", package: "hummingbird")Testing
测试
swift
import HummingbirdTesting
import Testing
@Test func testHealthEndpoint() async throws {
let router = Router()
router.get("/health") { _, _ -> HTTPResponse.Status in .ok }
let app = Application(router: router)
try await app.test(.router) { client in
try await client.execute(uri: "/health", method: .get) { response in
#expect(response.status == .ok)
}
}
}
@Test func testCreateUser() async throws {
let app = buildApplication()
try await app.test(.router) { client in
let user = CreateUserRequest(name: "Alice", email: "alice@example.com")
try await client.execute(
uri: "/users",
method: .post,
headers: [.contentType: "application/json"],
body: JSONEncoder().encodeAsByteBuffer(user, allocator: .init())
) { response in
#expect(response.status == .created)
}
}
}swift
import HummingbirdTesting
import Testing
@Test func testHealthEndpoint() async throws {
let router = Router()
router.get("/health") { _, _ -> HTTPResponse.Status in .ok }
let app = Application(router: router)
try await app.test(.router) { client in
try await client.execute(uri: "/health", method: .get) { response in
#expect(response.status == .ok)
}
}
}
@Test func testCreateUser() async throws {
let app = buildApplication()
try await app.test(.router) { client in
let user = CreateUserRequest(name: "Alice", email: "alice@example.com")
try await client.execute(
uri: "/users",
method: .post,
headers: [.contentType: "application/json"],
body: JSONEncoder().encodeAsByteBuffer(user, allocator: .init())
) { response in
#expect(response.status == .created)
}
}
}Best Practices
最佳实践
1. Use Custom Request Contexts
1. 使用自定义请求上下文
Extend contexts for type-safe access to authentication, database connections, etc:
swift
struct AppRequestContext: AuthRequestContext {
var coreContext: CoreRequestContextStorage
var auth: LoginCache
init(source: Source) {
self.coreContext = .init(source: source)
self.auth = .init()
}
}扩展上下文以实现对认证、数据库连接等的类型安全访问:
swift
struct AppRequestContext: AuthRequestContext {
var coreContext: CoreRequestContextStorage
var auth: LoginCache
init(source: Source) {
self.coreContext = .init(source: source)
self.auth = .init()
}
}2. Organize Routes
2. 组织路由
Split routes into separate files/functions:
swift
// UserRoutes.swift
func addUserRoutes(to router: Router<AppRequestContext>) {
router.group("users") { users in
users.get { _, _ in /* list */ }
users.post { _, _ in /* create */ }
users.get("{id}") { _, _ in /* get */ }
}
}
// Application setup
let router = Router(context: AppRequestContext.self)
addUserRoutes(to: router)
addPostRoutes(to: router)将路由拆分到不同的文件/函数中:
swift
// UserRoutes.swift
func addUserRoutes(to router: Router<AppRequestContext>) {
router.group("users") { users in
users.get { _, _ in /* 列出用户 */ }
users.post { _, _ in /* 创建用户 */ }
users.get("{id}") { _, _ in /* 获取用户 */ }
}
}
// 应用初始化
let router = Router(context: AppRequestContext.self)
addUserRoutes(to: router)
addPostRoutes(to: router)3. Use Middleware for Cross-Cutting Concerns
3. 使用中间件处理横切关注点
Apply authentication, logging, and metrics via middleware rather than in handlers.
通过中间件应用认证、日志和指标,而非在处理器中处理。
4. Handle Errors Gracefully
4. 优雅处理错误
swift
router.get("/users/{id}") { request, context in
guard let id = context.parameters.get("id"),
let user = try await userService.find(id: id) else {
throw HTTPError(.notFound, message: "User not found")
}
return user
}swift
router.get("/users/{id}") { request, context in
guard let id = context.parameters.get("id"),
let user = try await userService.find(id: id) else {
throw HTTPError(.notFound, message: "用户不存在")
}
return user
}Reference Files
参考文档
Load these files as needed for specific topics:
- - RequestContext protocol, ChildRequestContext, protocol composition, AuthRequestContext, multi-level contexts
references/request-context.md - - Advanced routing patterns, result builder router, wildcards
references/routing.md - - Custom middleware, authentication, CORS, file serving
references/middleware.md - - Testing strategies, mocking, integration tests
references/testing.md
根据需求加载以下文件以了解特定主题:
- - RequestContext协议、ChildRequestContext、协议组合、AuthRequestContext、多级上下文
references/request-context.md - - 高级路由模式、结果构建器路由、通配符
references/routing.md - - 自定义中间件、认证、CORS、文件服务
references/middleware.md - - 测试策略、模拟、集成测试
references/testing.md