この記事の要点
- HTTP のクライアント・サーバー処理で共通して使える型を提供する、新しいオープンソースパッケージ Swift HTTP Types(swift-http-types)が発表されました。
- 中心となるのは HTTP メッセージを表す
HTTPRequestとHTTPResponseで、クライアントとサーバーの両方で同じ型を使えます。これにより、フレームワークをまたいで型を変換するコストや、HTTP の抽象化を二重に持つ必要がなくなります。 - これらの型は HTTP/3 や HTTP/2 といった新しいバージョンを念頭に Swift ファーストで設計され、HTTP/1.1 との互換性も保たれています。
URLSession(Foundation)や SwiftNIO と組み合わせて使えます。 - 発表時点ではフィードバックを集めるための出発点であり、リクエスト・レスポンスのボディはこのパッケージの対象外です。
何が発表されたのか
Swift HTTP Types は、Swift で HTTP メッセージを扱うための共通の型(currency types)を提供するパッケージです。
HTTP は、クライアント・サーバー・中継など多くの場面で使われる代表的なネットワーク技術です。Apple プラットフォームでは Foundation の URLSession を通じて、サーバーサイドの Swift では SwiftNIO を通じて HTTP が利用されますが、それぞれが独自に HTTP メッセージの表現を持っていると、フレームワークをまたぐたびに型を変換する手間がかかります。
そこで Swift HTTP Types は、HTTP メッセージの基本要素を表す共通の型を用意します。HTTPRequest と HTTPResponse をプロジェクト間で共有することで、クライアントとサーバーの間でより多くのコードを再利用でき、型変換のコストや重複した抽象化をなくせます。これらの型は、あらゆる正当な HTTP メッセージを表現できるよう Swift ファーストで設計され、HTTP/3・HTTP/2 を主眼に置きつつ HTTP/1.1 との互換性も維持しています。
将来的には、SwiftNIO の HTTPRequestHead / HTTPResponseHead や、Foundation の URLRequest / URLResponse が持つ HTTP メッセージの詳細を、これらの型で置き換えることが目標とされています。これは発表時点の方針であり、実現を約束するものではありません。
何に使えるのか
リクエストとレスポンスの作成
HTTP リクエストは、メソッド・スキーム・authority・パスから構成されます。次のように直接組み立てられます。
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"
独自のヘッダーフィールドは HTTPField.Name をエクステンションで定義すれば、同じ構文で扱えます。
extension HTTPField.Name {
static let myCustomHeader = Self("My-Custom-Header")!
}
request.headerFields[.myCustomHeader] = "custom-value"
複数の値を持つフィールドは raw: 添字で配列として直接設定・取得できます。
request.headerFields[raw: .acceptLanguage] = ["en-US", "zh-Hans-CN"]
request.headerFields[.userAgent] // "MyApp/1.0"
request.headerFields[.acceptLanguage] // "en-US, zh-Hans-CN"
request.headerFields[raw: .acceptLanguage] // ["en-US", "zh-Hans-CN"]
Foundation との統合
URLSession と組み合わせると、HTTPRequest をそのまま使って通信できます。型システムと統合されているため、ヘッダーフィールドの指定では補完が効きます。
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 を通じて提供される予定です。チャネルハンドラの前に HTTP2FramePayloadToHTTPServerCodec を挿入することで、新しい HTTP 型を使うように設定できます。
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)
}
}
}
ボディの扱い
リクエスト・レスポンスのボディは、発表時点ではこのパッケージの対象外です。当面は従来どおり、Foundation では Data や InputStream、SwiftNIO では ByteBuffer を使います。ボディの扱いについては、今後コミュニティと議論しながら検討していくとされています。
導入・今後の位置づけ
今回公開されたバージョンは、コミュニティからのフィードバックや議論を得るための出発点と位置づけられています。swift-http-types パッケージ を試したり、Swift フォーラムの #http タグで議論に参加したり、見つけた問題について issue やプルリクエストを送ったりすることで参加できます。
同じ HTTP 領域では、OpenAPI ドキュメントからクライアント・サーバーのコードを生成する Swift OpenAPI Generator の発表 も近い時期に行われています。