介绍 Swift HTTP Types

我们很高兴地宣布一个新的开源软件包,名为 Swift HTTP Types

Swift HTTP Types 基于来自服务器端 Swift、应用程序开发者和更广泛的 Swift 社区的见解而构建,旨在为 Swift 中的客户端/服务器 HTTP 操作提供一组共享的通用类型。

Swift 中的 HTTP

如今,对于许多用例来说,Swift 中的网络编程无处不在,涵盖了客户端、服务器、中间件以及互联网和其他网络中的许多其他参与者。HTTP 是最流行的网络技术之一,为全球各地的日常体验提供支持。

在 Apple 平台上,系统 HTTP 实现通过 Foundation 框架中的 URLSession API 公开。对于服务器端 Swift 项目,推荐的 HTTP 堆栈在 SwiftNIO 中实现。

为了在使用 Swift 中的 HTTP 时提供最佳体验,跨多个项目通用的共享通用类型至关重要。

Swift HTTP Types

Swift HTTP Types 提供了 HTTP 消息核心构建块的通用表示。

HTTPRequestHTTPResponse 代表客户端和服务器用例的 HTTP 消息。通过在多个项目中采用它们,可以在客户端和服务器之间共享更多代码,从而消除在使用多个框架时类型转换的成本。

这些类型以 Swift 优先的方式构建,以表示每个有效的 HTTP 消息。它们的表示侧重于现代 HTTP 版本,如 HTTP/3 和 HTTP/2,同时还保留了与 HTTP/1.1 的兼容性。

随着软件包的成熟,我们的目标是替换 SwiftNIO 的 HTTPRequestHeadHTTPResponseHead 以及 Foundation 的 URLRequestURLResponse 的 HTTP 消息详细信息。

新的通用类型旨在适用于任何 HTTP 场景,并且不与任何现有框架绑定,从而消除了对重复 HTTP 抽象的需求。

使用示例

该 API 旨在提供符合人体工程学的方式来执行最常见的 HTTP 操作,同时可以干净地扩展以表示高级用例。

HTTP 请求的核心由方法、scheme、authority 和 path 组成

let request = HTTPRequest(method: .get, scheme: "https", authority: "www.example.com", path: "/")

我们也可以从 Foundation URL 创建相同的请求

var request = HTTPRequest(method: .get, url: URL(string: "https://www.example.com/")!)

我们可以在事后更改方法或其他属性

request.method = .post
request.path = "/upload"

创建响应同样简单直接

let response = HTTPResponse(status: .ok)

我们可以使用 headerFields 属性访问和修改标头字段

request.headerFields[.userAgent] = "MyApp/1.0"

常见的标头字段是内置的,我们可以轻松地为自定义标头字段和值提供扩展,因此我们可以在业务逻辑中使用相同的便捷语法

extension HTTPField.Name {
    static let myCustomHeader = Self("My-Custom-Header")!
}

request.headerFields[.myCustomHeader] = "custom-value"

我们可以直接设置标头字段的值,包括值数组

request.headerFields[raw: .acceptLanguage] = ["en-US", "zh-Hans-CN"]

访问标头字段的方式大致相同,我们可以使用系统定义的字段或我们自己的扩展

request.headerFields[.userAgent] // "MyApp/1.0"
request.headerFields[.myCustomHeader] // "custom-value"

request.headerFields[.acceptLanguage] // "en-US, zh-Hans-CN"
request.headerFields[raw: .acceptLanguage] // ["en-US", "zh-Hans-CN"]

与 Foundation 集成

使用 URLSession,我们可以轻松创建一个新的 HTTPRequest 来向 “www.example.com” 发送 POST 请求。在此请求上设置自定义 User-Agent 标头字段值非常直观,并且与类型系统集成以提供自动完成功能

var request = HTTPRequest(method: .post, url: URL(string: "https://www.example.com/upload")!)
request.headerFields[.userAgent] = "MyApp/1.0"
let (responseBody, response) = try await URLSession.shared.upload(for: request, from: requestBody)
guard response.status == .created else {
    // Handle error
}

与 SwiftNIO 集成

当此软件包变得稳定时,SwiftNIO 集成将在 swift-nio-extras 软件包中提供。要配置 NIO 通道处理程序以与新的 HTTP 类型一起使用,我们可以在其他通道处理程序之前添加 HTTP2FramePayloadToHTTPServerCodec

NIOTSListenerBootstrap(group: NIOTSEventLoopGroup())
    .childChannelInitializer { channel in
        channel.configureHTTP2Pipeline(mode: .server) { channel in
            channel.pipeline.addHandlers([
                HTTP2FramePayloadToHTTPServerCodec(),
                ExampleChannelHandler()
            ])
        }.map { _ in () }
    }
    .tlsOptions(tlsOptions)

我们的示例通道实现同时处理 HTTPRequestHTTPResponse 类型

final class ExampleChannelHandler: ChannelDuplexHandler {
    typealias InboundIn = HTTPTypeServerRequestPart
    typealias OutboundOut = HTTPTypeServerResponsePart

    func channelRead(context: ChannelHandlerContext, data: NIOAny) {
        switch unwrapInboundIn(data) {
        case .head(let request):
            // Handle request headers
        case .body(let body):
            // Handle request body
        case .end(let trailers):
            // Handle complete request
            let response = HTTPResponse(status: .ok)
            context.write(wrapOutboundOut(.head(response)), promise: nil)
            context.writeAndFlush(wrapOutboundOut(.end(nil)), promise: nil)
        }
    }
}

请求和响应主体

HTTP 请求和响应主体目前不属于此软件包的一部分。

请继续使用现有的机制:Foundation 的 DataInputStream 以及 SwiftNIO 的 ByteBuffer

我们有兴趣与社区一起探讨关于未来提供主体处理的提案。

参与进来

这个社区的经验和专业知识对于创建 Swift 中出色 HTTP 体验的构建块是无价的。

我们今天发布的 Swift HTTP Types 版本是与社区进行反馈和讨论的起点。

您可以开始通过以下方式参与:

我们期待着共同探索 Swift 中 HTTP 的未来。