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)) }