关于 hummingbird
Hummingbird 是面向 Swift 开发者的轻量级 HTTP 服务端框架,基于 SwiftNIO 并支持 Swift 并发。它提供路由、请求上下文、中间件和响应处理,并可扩展认证、WebSockets、Fluent ORM、HTTP/2 与 TLS。
主要功能
- 01HTTP 路由与路由分组
- 02自定义请求上下文
- 03请求处理中间件
- 04JSON 请求与响应处理
- 05WebSockets 和 Fluent ORM 扩展
适用场景
- 01用 Swift 构建 HTTP API
- 02按路由组组织端点
- 03添加认证或日志中间件
- 04测试服务端点
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ wendylabsinc/ claude-skills --skill hummingbird安装后在 Claude Code 中输入/hummingbird确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/middleware.mdrequest-context.mdrouting.mdtesting.md详细介绍
Hummingbird 2
Hummingbird 是一个轻量、灵活的 Swift HTTP 服务端框架,基于 SwiftNIO 构建,并全面支持 Swift 并发。它是 SSWG(Swift 服务端工作组)的孵化项目。
快速入门
安装
添加到 Package.swift:
dependencies: [
.package(url: "https://github.com/hummingbird-project/hummingbird.git", from: "2.0.0")
]
添加到你的目标中:
.target(
name: "App",
dependencies: [
.product(name: "Hummingbird", package: "hummingbird")
]
)
最小应用
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()
}
}
核心概念
路由器
路由器根据路径和 HTTP 方法将请求分发给处理器:
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
}
路由组
使用通用前缀组织路由:
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 */ }
}
}
请求上下文
每个请求都会获得一个上下文实例。使用 BasicRequestContext,或创建自定义上下文:
// 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)
子请求上下文
使用 ChildRequestContext 转换上下文,并确保属性存在:
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
}
}
有关详细用法模式(包括多级子上下文和协议组合),请参阅 references/request-context.md。
路由处理器
处理器接收 Request 和 Context,并返回任意 ResponseGenerator:
// 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"))
)
}
请求处理
路径参数
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)"
}
查询参数
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)"
}
请求体(JSON)
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
}
标头
router.get("/protected") { request, context in
guard let auth = request.headers[.authorization] else {
throw HTTPError(.unauthorized)
}
// Process authorization header
return "Authorized"
}
响应处理
ResponseCodable
遵循 ResponseCodable 的类型会自动编码为 JSON:
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: "[email protected]")
}
EditedResponse
使用响应体控制状态码和标头:
router.post("/users") { request, context -> EditedResponse<User> in
let user = User(id: 1, name: "Alice", email: "[email protected]")
return EditedResponse(
status: .created,
headers: [.location: "/users/1"],
response: user
)
}
HTTPError
抛出错误以返回 HTTP 错误响应:
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
}
中间件
中间件在处理器处理请求之前以及响应之后处理请求和响应:
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" }
}
内置中间件
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())
应用配置
let app = Application(
router: router,
configuration: .init(
address: .hostname("0.0.0.0", port: 8080),
serverName: "MyApp"
)
)
try await app.runService()
与 ServiceLifecycle 搭配使用
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()
}
}
扩展
身份验证(HummingbirdAuth)
.product(name: "HummingbirdAuth", package: "hummingbird-auth")
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
}
WebSockets(HummingbirdWebSocket)
.product(name: "HummingbirdWebSocket", package: "hummingbird-websocket")
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)
.product(name: "HummingbirdFluent", package: "hummingbird-fluent")
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)
HTTP/2 和 TLS
.product(name: "HummingbirdHTTP2", package: "hummingbird")
.product(name: "HummingbirdTLS", package: "hummingbird")
测试
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: "[email protected]")
try await client.execute(
uri: "/users",
method: .post,
headers: [.contentType: "application/json"],
body: JSONEncoder().encodeAsByteBuffer(user, allocator: .init())
) { response in
#expect(response.status == .created)
}
}
}
最佳实践
1. 使用自定义请求上下文
扩展上下文,以便类型安全地访问身份验证、数据库连接等:
struct AppRequestContext: AuthRequestContext {
var coreContext: CoreRequestContextStorage
var auth: LoginCache
init(source: Source) {
self.coreContext = .init(source: source)
self.auth = .init()
}
}
2. 组织路由
将路由拆分到不同的文件或函数中:
// 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)
3. 使用中间件处理横切关注点
通过中间件而非处理器来实现身份验证、日志记录和指标收集。
4. 妥善处理错误
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
}
参考文件
根据需要加载以下文件,了解特定主题:
references/request-context.md- RequestContext 协议、ChildRequestContext、协议组合、AuthRequestContext、多级上下文references/routing.md- 高级路由模式、结果构建器路由器、通配符references/middleware.md- 自定义中间件、身份验证、CORS、文件服务references/testing.md- 测试策略、模拟、集成测试
评论
支持 Markdown