diff --git a/Sources/NIOHTTPServer/HTTPServerCapability+ConnectionCapabilities.swift b/Sources/NIOHTTPServer/HTTPServerCapability+ConnectionCapabilities.swift index e33bdfb..a4deacb 100644 --- a/Sources/NIOHTTPServer/HTTPServerCapability+ConnectionCapabilities.swift +++ b/Sources/NIOHTTPServer/HTTPServerCapability+ConnectionCapabilities.swift @@ -31,6 +31,16 @@ extension HTTPServerCapability { var localAddress: NIOHTTPServer.SocketAddress? { get } } + /// A request-context capability exposing the application-level HTTP version. + /// + /// Servers whose request context conforms to this capability surface the + /// HTTP version in use on the connection carrying the request, whether it + /// was negotiated via ALPN or fixed by a plaintext listener. + public protocol HTTPVersion: RequestContext { + /// The application-level HTTP version in use on the connection. + var httpVersion: NIOHTTPServer.HTTPVersion { get } + } + /// A request-context capability exposing the validated peer certificate chain. /// /// Servers whose request context conforms to this capability surface the diff --git a/Sources/NIOHTTPServer/NIOHTTPServer+ConnectionContext.swift b/Sources/NIOHTTPServer/NIOHTTPServer+ConnectionContext.swift index 7b3dd41..48751e8 100644 --- a/Sources/NIOHTTPServer/NIOHTTPServer+ConnectionContext.swift +++ b/Sources/NIOHTTPServer/NIOHTTPServer+ConnectionContext.swift @@ -40,6 +40,7 @@ extension NIOHTTPServer { /// /// User code accesses this state via the corresponding ``RequestContext`` /// capabilities (``HTTPServerCapability/ConnectionInfo``, + /// ``HTTPServerCapability/HTTPVersion``, /// ``HTTPServerCapability/PeerCertificate``) when handling individual /// requests, and directly when implementing an /// ``NIOHTTPServerConnectionHandler``. diff --git a/Sources/NIOHTTPServer/NIOHTTPServer+RequestContext.swift b/Sources/NIOHTTPServer/NIOHTTPServer+RequestContext.swift index 7d4f145..8b65a6f 100644 --- a/Sources/NIOHTTPServer/NIOHTTPServer+RequestContext.swift +++ b/Sources/NIOHTTPServer/NIOHTTPServer+RequestContext.swift @@ -22,6 +22,7 @@ extension NIOHTTPServer { /// /// Conforms to: /// - ``HTTPServerCapability/ConnectionInfo`` — peer / local addresses. + /// - ``HTTPServerCapability/HTTPVersion`` — HTTP version of the connection. /// - ``HTTPServerCapability/PeerCertificate`` — mTLS-validated peer chain. /// /// Generic library code can constrain on these capabilities to access @@ -60,6 +61,15 @@ extension NIOHTTPServer.RequestContext: HTTPServerCapability.ConnectionInfo { } } +@available(anyAppleOS 27.0, *) +extension NIOHTTPServer.RequestContext: HTTPServerCapability.HTTPVersion { + /// The application-level HTTP version in use on the connection carrying + /// this request. + public var httpVersion: NIOHTTPServer.HTTPVersion { + self.connectionContext.httpVersion + } +} + @available(anyAppleOS 27.0, *) extension NIOHTTPServer.RequestContext: HTTPServerCapability.PeerCertificate { /// The peer's mTLS-validated certificate chain, when available. diff --git a/Tests/NIOHTTPServerTests/NIOHTTPServerTests.swift b/Tests/NIOHTTPServerTests/NIOHTTPServerTests.swift index 2828159..5481d53 100644 --- a/Tests/NIOHTTPServerTests/NIOHTTPServerTests.swift +++ b/Tests/NIOHTTPServerTests/NIOHTTPServerTests.swift @@ -118,6 +118,43 @@ struct NIOHTTPServerTests { } } + @available(anyAppleOS 27.0, *) + #if HTTP3 + @Test( + "Request context reports the HTTP version", + arguments: [NIOHTTPServer.HTTPVersion.plaintextHTTP1_1, .http1_1, .http2, .http3] + ) + #else + @Test( + "Request context reports the HTTP version", + arguments: [NIOHTTPServer.HTTPVersion.plaintextHTTP1_1, .http1_1, .http2] + ) + #endif + func testRequestContextHTTPVersion(httpVersion: NIOHTTPServer.HTTPVersion) async throws { + let (server, clientConfiguration) = try TestHelpers.makeServerAndClientConfiguration( + for: httpVersion, + clientLogger: self.clientLogger, + serverLogger: self.serverLogger + ) + + try await TestHelpers.withClientServerRequestChannel( + clientConfiguration: clientConfiguration, + server: server, + serverHandler: HTTPServerClosureRequestHandler { request, requestContext, reader, responseWriter in + #expect(requestContext.httpVersion == httpVersion) + try await responseWriter.sendAndFinish(.init(status: .ok)) + } + ) { _, inbound, outbound in + try await outbound.write(.testHead(method: .get, for: httpVersion)) + try await outbound.write(.end(nil)) + + var iterator = inbound.makeAsyncIterator() + while let part = try await iterator.next() { + if case .end = part { break } + } + } + } + @available(anyAppleOS 27.0, *) @Test( "mTLS request-response with custom verification callback returning peer certificates",