hummingbird

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Hummingbird 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.swift
:
swift
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.swift
swift
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
BasicRequestContext
or create custom contexts:
swift
// 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)
每个请求都会对应一个上下文实例。可以使用
BasicRequestContext
或自定义上下文:
swift
// 使用基础上下文
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
ChildRequestContext
to transform contexts and guarantee properties exist:
swift
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
references/request-context.md
for detailed patterns including multi-level child contexts and protocol composition.
使用
ChildRequestContext
转换上下文,确保所需属性存在:
swift
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.md

Route Handlers

路由处理器

Handlers receive
Request
and
Context
, returning any
ResponseGenerator
:
swift
// 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"))
    )
}
处理器接收
Request
Context
,返回任意
ResponseGenerator
类型:
swift
// 返回字符串
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
ResponseCodable
auto-encode to JSON:
swift
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")
}
符合
ResponseCodable
协议的类型会自动编码为JSON:
swift
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:
  • references/request-context.md
    - RequestContext protocol, ChildRequestContext, protocol composition, AuthRequestContext, multi-level contexts
  • references/routing.md
    - Advanced routing patterns, result builder router, wildcards
  • references/middleware.md
    - Custom middleware, authentication, CORS, file serving
  • references/testing.md
    - Testing strategies, mocking, integration tests
根据需求加载以下文件以了解特定主题:
  • references/request-context.md
    - RequestContext协议、ChildRequestContext、协议组合、AuthRequestContext、多级上下文
  • references/routing.md
    - 高级路由模式、结果构建器路由、通配符
  • references/middleware.md
    - 自定义中间件、认证、CORS、文件服务
  • references/testing.md
    - 测试策略、模拟、集成测试