mcpField
H
hummingbird

hummingbird

github.com/wendylabsinc开发6277 次安装5 周前更新
代码仓库

关于 hummingbird

Hummingbird 是面向 Swift 开发者的轻量级 HTTP 服务端框架,基于 SwiftNIO 并支持 Swift 并发。它提供路由、请求上下文、中间件和响应处理,并可扩展认证、WebSockets、Fluent ORM、HTTP/2 与 TLS。

主要功能

  1. 01HTTP 路由与路由分组
  2. 02自定义请求上下文
  3. 03请求处理中间件
  4. 04JSON 请求与响应处理
  5. 05WebSockets 和 Fluent ORM 扩展

适用场景

  1. 01用 Swift 构建 HTTP API
  2. 02按路由组组织端点
  3. 03添加认证或日志中间件
  4. 04测试服务端点

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/wendylabsinc/claude-skills --skill hummingbird

安装后在 Claude Code 中输入/hummingbird确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
middleware.md
request-context.md
routing.md
testing.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