|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +published: true |
| 4 | +date: 2023-07-10 10:00:00 |
| 5 | +title: Introducing Swift HTTP Types |
| 6 | +author: [guoyezhang, erickinnear, corybenfield] |
| 7 | +--- |
| 8 | + |
| 9 | +We're excited to announce a new open source package called [Swift HTTP Types](https://github.com/apple/swift-http-types). |
| 10 | + |
| 11 | +Building upon insights from Swift on server, app developers, and the broader Swift community, Swift HTTP Types is designed to provide a shared set of currency types for client/server HTTP operations in Swift. |
| 12 | + |
| 13 | +## HTTP in Swift |
| 14 | + |
| 15 | +Networking in Swift is ubiquitous for many use cases today, spanning clients, servers, intermediaries, and many other participants across the internet and other networks. HTTP is among the most popular networking technologies, powering daily experiences across the world. |
| 16 | + |
| 17 | +On Apple platforms, the system HTTP implementation is exposed via the `URLSession` API in the Foundation framework. For Swift on server projects, the recommended HTTP stack is implemented in [SwiftNIO](https://github.com/apple/swift-nio). |
| 18 | + |
| 19 | +To provide the best possible experience for using HTTP in Swift, shared currency types, useful across many projects, are essential. |
| 20 | + |
| 21 | +## Swift HTTP Types |
| 22 | + |
| 23 | +[Swift HTTP Types](https://github.com/apple/swift-http-types) provides a common representation of the core building blocks of HTTP messages. |
| 24 | + |
| 25 | +`HTTPRequest` and `HTTPResponse` represent HTTP messages for both client and server use cases. By adopting them across multiple projects, more code can be shared between clients and servers, eliminating the cost of converting between types when working with multiple frameworks. |
| 26 | + |
| 27 | +These types are built Swift-first to represent every valid HTTP message. Their representations are focused on modern HTTP versions like HTTP/3 and HTTP/2, while also retaining compatibility with HTTP/1.1. |
| 28 | + |
| 29 | +As the package matures, our goal is to replace SwiftNIO's `HTTPRequestHead` and `HTTPResponseHead` as well as the HTTP message details of Foundation's `URLRequest` and `URLResponse`. |
| 30 | + |
| 31 | +The new currency types are designed to be suitable for use in any HTTP scenario and are not tied to any existing framework, eliminating the need for duplicate HTTP abstractions. |
| 32 | + |
| 33 | +## Example Usage |
| 34 | + |
| 35 | +The API is designed to offer ergonomic ways to perform the most common HTTP operations, while scaling cleanly to represent advanced use cases. |
| 36 | + |
| 37 | +At their core, HTTP requests are comprised of a method, a scheme, an authority, and a path: |
| 38 | + |
| 39 | +```swift |
| 40 | +let request = HTTPRequest(method: .get, scheme: "https", authority: "www.example.com", path: "/") |
| 41 | +``` |
| 42 | + |
| 43 | +We can also create the same request from a Foundation URL: |
| 44 | + |
| 45 | +```swift |
| 46 | +var request = HTTPRequest(method: .get, url: URL(string: "https://www.example.com/")!) |
| 47 | +``` |
| 48 | + |
| 49 | +We can change the method, or other properties, after the fact: |
| 50 | + |
| 51 | +```swift |
| 52 | +request.method = .post |
| 53 | +request.path = "/upload" |
| 54 | +``` |
| 55 | + |
| 56 | +Creating a response is similarly straightforward: |
| 57 | + |
| 58 | +```swift |
| 59 | +let response = HTTPResponse(status: .ok) |
| 60 | +``` |
| 61 | + |
| 62 | +We can access and modify header fields using the `headerFields` property: |
| 63 | + |
| 64 | +```swift |
| 65 | +request.headerFields[.userAgent] = "MyApp/1.0" |
| 66 | +``` |
| 67 | + |
| 68 | +Common header fields are built-in, and we can easily provide extensions for custom header fields and values, so we can use the same convenient syntax in our business logic: |
| 69 | + |
| 70 | +```swift |
| 71 | +extension HTTPField.Name { |
| 72 | + static let myCustomHeader = Self("My-Custom-Header")! |
| 73 | +} |
| 74 | + |
| 75 | +request.headerFields[.myCustomHeader] = "custom-value" |
| 76 | +``` |
| 77 | + |
| 78 | +We can directly set the value of the header field, including an array of values: |
| 79 | + |
| 80 | +```swift |
| 81 | +request.headerFields[raw: .acceptLanguage] = ["en-US", "zh-Hans-CN"] |
| 82 | +``` |
| 83 | + |
| 84 | +Accessing header fields is much the same, and we can use system-defined fields or our own extensions: |
| 85 | + |
| 86 | +```swift |
| 87 | +request.headerFields[.userAgent] // "MyApp/1.0" |
| 88 | +request.headerFields[.myCustomHeader] // "custom-value" |
| 89 | + |
| 90 | +request.headerFields[.acceptLanguage] // "en-US, zh-Hans-CN" |
| 91 | +request.headerFields[raw: .acceptLanguage] // ["en-US", "zh-Hans-CN"] |
| 92 | +``` |
| 93 | + |
| 94 | +### Integrating with Foundation |
| 95 | + |
| 96 | +Using `URLSession`, we can easily create a new `HTTPRequest` to send a POST request to "www.example.com". Setting a custom `User-Agent` header field value on this request is intuitive and integrates with the type system to offer auto-completions: |
| 97 | + |
| 98 | +```swift |
| 99 | +var request = HTTPRequest(method: .post, url: URL(string: "https://www.example.com/upload")!) |
| 100 | +request.headerFields[.userAgent] = "MyApp/1.0" |
| 101 | +let (responseBody, response) = try await URLSession.shared.upload(for: request, from: requestBody) |
| 102 | +guard response.status == .created else { |
| 103 | + // Handle error |
| 104 | +} |
| 105 | +``` |
| 106 | + |
| 107 | +### Integrating with SwiftNIO |
| 108 | + |
| 109 | +SwiftNIO integration will be available in the [swift-nio-extras](https://github.com/apple/swift-nio-extras) package when this package becomes stable. To configure a NIO channel handler for use with the new HTTP types, we can add `HTTP2FramePayloadToHTTPServerCodec` before our other channel handlers: |
| 110 | + |
| 111 | +```swift |
| 112 | +NIOTSListenerBootstrap(group: NIOTSEventLoopGroup()) |
| 113 | + .childChannelInitializer { channel in |
| 114 | + channel.configureHTTP2Pipeline(mode: .server) { channel in |
| 115 | + channel.pipeline.addHandlers([ |
| 116 | + HTTP2FramePayloadToHTTPServerCodec(), |
| 117 | + ExampleChannelHandler() |
| 118 | + ]) |
| 119 | + }.map { _ in () } |
| 120 | + } |
| 121 | + .tlsOptions(tlsOptions) |
| 122 | +``` |
| 123 | + |
| 124 | +Our example channel implementation processes both `HTTPRequest` and `HTTPResponse` types: |
| 125 | + |
| 126 | +```swift |
| 127 | +final class ExampleChannelHandler: ChannelDuplexHandler { |
| 128 | + typealias InboundIn = HTTPTypeServerRequestPart |
| 129 | + typealias OutboundOut = HTTPTypeServerResponsePart |
| 130 | + |
| 131 | + func channelRead(context: ChannelHandlerContext, data: NIOAny) { |
| 132 | + switch unwrapInboundIn(data) { |
| 133 | + case .head(let request): |
| 134 | + // Handle request headers |
| 135 | + case .body(let body): |
| 136 | + // Handle request body |
| 137 | + case .end(let trailers): |
| 138 | + // Handle complete request |
| 139 | + let response = HTTPResponse(status: .ok) |
| 140 | + context.write(wrapOutboundOut(.head(response)), promise: nil) |
| 141 | + context.writeAndFlush(wrapOutboundOut(.end(nil)), promise: nil) |
| 142 | + } |
| 143 | + } |
| 144 | +} |
| 145 | +``` |
| 146 | + |
| 147 | +## Request and Response Body |
| 148 | + |
| 149 | +HTTP request and response bodies are not currently part of this package. |
| 150 | + |
| 151 | +Please continue to use existing the mechanisms: `Data` and `InputStream` for Foundation and `ByteBuffer` for SwiftNIO. |
| 152 | + |
| 153 | +We are interested in exploring proposals with the community to provide body handling in the future. |
| 154 | + |
| 155 | +## Get Involved |
| 156 | + |
| 157 | +The experience and expertise from this community is invaluable in creating the building blocks for a great HTTP experience in Swift. |
| 158 | + |
| 159 | +The version of Swift HTTP Types we're releasing today is a starting point for feedback and discussion with the community. |
| 160 | + |
| 161 | +You can get started by: |
| 162 | + |
| 163 | +* Trying out the [swift-http-types package](https://github.com/apple/swift-http-types) |
| 164 | +* Participating in discussions with the [#http tag on the Swift forums](https://forums.swift.org/tag/http) |
| 165 | +* Opening [issues and contributing](https://github.com/apple/swift-http-types/issues) for any problems you find |
| 166 | + |
| 167 | +We're looking forward to exploring the future of HTTP in Swift together. |
0 commit comments