From c1d3f8617b89265f704743576ebde2b4693a6627 Mon Sep 17 00:00:00 2001 From: Eric Rosenberg Date: Thu, 27 Aug 2026 10:25:32 +0000 Subject: [PATCH] Add `HTTPVersion` type and `HTTPVersionInfo` server capability ### Motivation Handlers and middleware have no way to observe which HTTP version a request arrived over without coupling to a concrete server implementation. Fixes #180. ### Modifications Adds `NetworkTypes.HTTPVersion` and HTTPVersionInfo capability. ### Result Request contexts can advertise their HTTP version through the capability, and generic handlers can require it via a constraint rather than a concrete type. Purely additive; no existing API changes. ### Test Plan `TestClientAndServer.HTTPRequestContext` gains a `httpVersion` (defaulting to `.http1_1`) and conforms to the new capability, and the existing `ServerCapabilityTests` request-context test now asserts the version reaches the handler. --- ...HTTPServerCapability+HTTPVersionInfo.swift | 24 ++++++++ Sources/NetworkTypes/HTTPVersion.swift | 59 +++++++++++++++++++ .../Helpers/HTTPClientAndServerTests.swift | 12 +++- .../HTTPAPIsTests/ServerCapabilityTests.swift | 5 ++ 4 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 Sources/HTTPAPIs/Server/HTTPServerCapability+HTTPVersionInfo.swift create mode 100644 Sources/NetworkTypes/HTTPVersion.swift diff --git a/Sources/HTTPAPIs/Server/HTTPServerCapability+HTTPVersionInfo.swift b/Sources/HTTPAPIs/Server/HTTPServerCapability+HTTPVersionInfo.swift new file mode 100644 index 0000000..07f2953 --- /dev/null +++ b/Sources/HTTPAPIs/Server/HTTPServerCapability+HTTPVersionInfo.swift @@ -0,0 +1,24 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift HTTP API Proposal open source project +// +// Copyright (c) 2026 Apple Inc. and the Swift HTTP API Proposal project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +public import NetworkTypes + +@available(anyAppleOS 26.0, *) +extension HTTPServerCapability { + /// A capability for request contexts that expose the HTTP version used for the + /// connection a request arrived on. + public protocol HTTPVersionInfo: RequestContext, ~Copyable, ~Escapable { + /// The HTTP version used for this request + var httpVersion: HTTPVersion { get } + } +} diff --git a/Sources/NetworkTypes/HTTPVersion.swift b/Sources/NetworkTypes/HTTPVersion.swift new file mode 100644 index 0000000..04c4adf --- /dev/null +++ b/Sources/NetworkTypes/HTTPVersion.swift @@ -0,0 +1,59 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift HTTP API Proposal open source project +// +// Copyright (c) 2026 Apple Inc. and the Swift HTTP API Proposal project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +/// Representation of an HTTP version. +public struct HTTPVersion: Sendable, Hashable { + private enum Representation: UInt8, Sendable, Hashable { + case http1_0 + case http1_1 + case http2 + case http3 + } + + private let representation: Representation + + private init(_ representation: Representation) { + self.representation = representation + } + + /// HTTP/1.0, as defined in RFC 1945. + public static var http1_0: HTTPVersion { + .init(.http1_0) + } + + /// HTTP/1.1, as defined in RFC 9112. + public static var http1_1: HTTPVersion { + .init(.http1_1) + } + + /// HTTP/2, as defined in RFC 9113. + public static var http2: HTTPVersion { + .init(.http2) + } + + /// HTTP/3, as defined in RFC 9114. + public static var http3: HTTPVersion { + .init(.http3) + } +} + +extension HTTPVersion: CustomStringConvertible { + public var description: String { + switch self.representation { + case .http1_0: "HTTP/1.0" + case .http1_1: "HTTP/1.1" + case .http2: "HTTP/2" + case .http3: "HTTP/3" + } + } +} diff --git a/Tests/HTTPAPIsTests/Helpers/HTTPClientAndServerTests.swift b/Tests/HTTPAPIsTests/Helpers/HTTPClientAndServerTests.swift index 84c5198..a6ccda1 100644 --- a/Tests/HTTPAPIsTests/Helpers/HTTPClientAndServerTests.swift +++ b/Tests/HTTPAPIsTests/Helpers/HTTPClientAndServerTests.swift @@ -17,6 +17,7 @@ import ContainersPreview import DequeModule import HTTPAPIs import HTTPTypes +import NetworkTypes import Synchronization import Testing @@ -30,10 +31,16 @@ final class TestClientAndServer: HTTPClient, HTTPServer { struct HTTPRequestContext: HTTPServerCapability.RequestContext { var remoteAddress: String? var localAddress: String? + var httpVersion: HTTPVersion - init(remoteAddress: String? = nil, localAddress: String? = nil) { + init( + remoteAddress: String? = nil, + localAddress: String? = nil, + httpVersion: HTTPVersion = .http1_1 + ) { self.remoteAddress = remoteAddress self.localAddress = localAddress + self.httpVersion = httpVersion } } @@ -333,7 +340,8 @@ final class TestClientAndServer: HTTPClient, HTTPServer { request: httpRequest, requestContext: HTTPRequestContext( remoteAddress: "127.0.0.1:54321", - localAddress: "0.0.0.0:8080" + localAddress: "0.0.0.0:8080", + httpVersion: .http2 ), reader: requestReader, responseSender: responseSender diff --git a/Tests/HTTPAPIsTests/ServerCapabilityTests.swift b/Tests/HTTPAPIsTests/ServerCapabilityTests.swift index 1d08d92..05450a7 100644 --- a/Tests/HTTPAPIsTests/ServerCapabilityTests.swift +++ b/Tests/HTTPAPIsTests/ServerCapabilityTests.swift @@ -14,6 +14,7 @@ import BasicContainers import Foundation import HTTPAPIs +import NetworkTypes import Testing @available(anyAppleOS 26.0, *) @@ -27,12 +28,16 @@ extension HTTPServerCapability { @available(anyAppleOS 26.0, *) extension TestClientAndServer.HTTPRequestContext: HTTPServerCapability.ConnectionInfo {} +@available(anyAppleOS 26.0, *) +extension TestClientAndServer.HTTPRequestContext: HTTPServerCapability.HTTPVersionInfo {} + @available(anyAppleOS 26.0, *) extension TestClientAndServer { func serveWithContextAssertions() async throws { try await self.serve { request, requestContext, reader, responseSender in #expect(requestContext.remoteAddress == "127.0.0.1:54321") #expect(requestContext.localAddress == "0.0.0.0:8080") + #expect(requestContext.httpVersion == .http2) try await responseSender.sendAndFinish(.init(status: .ok)) }