Skip to content

Commit 4c90596

Browse files
committed
Add a RequestContext
1 parent 8f1c17e commit 4c90596

10 files changed

+87
-25
lines changed

Sources/Example/Example.swift

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,13 @@ struct Example {
2222

2323
// Using the new extension method that doesn't require type hints
2424
let privateKey = P256.Signing.PrivateKey()
25-
let server = NIOHTTPServer<HTTPServerClosureRequestHandler<HTTPRequestConcludingAsyncReader, HTTPResponseConcludingAsyncWriter>>(
25+
let server = NIOHTTPServer<HTTPServerClosureRequestHandler<
26+
HTTPServerRequestContext,
27+
HTTPRequestConcludingAsyncReader,
28+
HTTPRequestConcludingAsyncReader.Underlying,
29+
HTTPResponseConcludingAsyncWriter,
30+
HTTPResponseConcludingAsyncWriter.Underlying
31+
>>(
2632
logger: logger,
2733
configuration: .init(
2834
bindTarget: .hostAndPort(host: "127.0.0.1", port: 12345),
@@ -45,7 +51,7 @@ struct Example {
4551
)
4652
)
4753
)
48-
try await server.serve { request, requestBodyAndTrailers, responseSender in
54+
try await server.serve { request, requestContext, requestBodyAndTrailers, responseSender in
4955
let writer = try await responseSender.send(HTTPResponse(status: .ok))
5056
try await writer.writeAndConclude(element: "Well, hello!".utf8.span, finalElement: nil)
5157
}
@@ -54,16 +60,22 @@ struct Example {
5460

5561
// MARK: - Server Extensions
5662

57-
// This has to be commented out because of the compiler bug above. Workaround doesn't apply here.
58-
5963
@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, visionOS 26.0, *)
60-
extension NIOHTTPServer where RequestHandler == HTTPServerClosureRequestHandler<HTTPRequestConcludingAsyncReader, HTTPResponseConcludingAsyncWriter> {
64+
extension NIOHTTPServer
65+
where RequestHandler == HTTPServerClosureRequestHandler<
66+
HTTPServerRequestContext,
67+
HTTPRequestConcludingAsyncReader,
68+
HTTPRequestConcludingAsyncReader.Underlying,
69+
HTTPResponseConcludingAsyncWriter,
70+
HTTPResponseConcludingAsyncWriter.Underlying
71+
> {
6172
/// Serve HTTP requests using a middleware chain built with the provided builder
6273
/// This method handles the type inference for HTTP middleware components
6374
func serve(
6475
@MiddlewareChainBuilder
6576
withMiddleware middlewareBuilder: () -> some Middleware<
66-
RequestResponseMiddlewareBox<
77+
RequestResponseMiddlewareBox<
78+
HTTPServerRequestContext,
6779
HTTPRequestConcludingAsyncReader,
6880
HTTPResponseConcludingAsyncWriter
6981
>,
@@ -72,9 +84,10 @@ extension NIOHTTPServer where RequestHandler == HTTPServerClosureRequestHandler<
7284
) async throws {
7385
let chain = middlewareBuilder()
7486

75-
try await self.serve { request, reader, responseSender in
87+
try await self.serve { request, requestContext, reader, responseSender in
7688
try await chain.intercept(input: RequestResponseMiddlewareBox(
7789
request: request,
90+
requestContext: requestContext,
7891
requestReader: reader,
7992
responseSender: responseSender
8093
)) { _ in }

Sources/Example/Middlewares/HTTPRequestLoggingMiddleware.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Middleware
55

66
@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, visionOS 26.0, *)
77
struct HTTPRequestLoggingMiddleware<
8+
RequestContext: HTTPRequestContext,
89
RequestConcludingAsyncReader: ConcludingAsyncReader & ~Copyable,
910
ResponseConcludingAsyncWriter: ConcludingAsyncWriter & ~Copyable
1011
>: Middleware
@@ -14,8 +15,9 @@ where
1415
ResponseConcludingAsyncWriter.Underlying.WriteElement == Span<UInt8>,
1516
ResponseConcludingAsyncWriter.FinalElement == HTTPFields?
1617
{
17-
typealias Input = RequestResponseMiddlewareBox<RequestConcludingAsyncReader, ResponseConcludingAsyncWriter>
18+
typealias Input = RequestResponseMiddlewareBox<RequestContext, RequestConcludingAsyncReader, ResponseConcludingAsyncWriter>
1819
typealias NextInput = RequestResponseMiddlewareBox<
20+
RequestContext,
1921
HTTPRequestLoggingConcludingAsyncReader<RequestConcludingAsyncReader>,
2022
HTTPResponseLoggingConcludingAsyncWriter<ResponseConcludingAsyncWriter>
2123
>
@@ -34,7 +36,7 @@ where
3436
input: consuming Input,
3537
next: (consuming NextInput) async throws -> Void
3638
) async throws {
37-
try await input.withContents { request, requestReader, responseSender in
39+
try await input.withContents { request, context, requestReader, responseSender in
3840
self.logger.info("Received request \(request.path ?? "unknown" ) \(request.method.rawValue)")
3941
defer {
4042
self.logger.info("Finished request \(request.path ?? "unknown" ) \(request.method.rawValue)")
@@ -47,6 +49,7 @@ where
4749
var maybeSender = Optional(responseSender)
4850
let requestResponseBox = RequestResponseMiddlewareBox(
4951
request: request,
52+
requestContext: context,
5053
requestReader: wrappedReader,
5154
responseSender: HTTPResponseSender { [logger] response in
5255
if let sender = maybeSender.take() {

Sources/Example/Middlewares/RouteHandlerMiddleware.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Middleware
44

55
@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, visionOS 26.0, *)
66
struct RouteHandlerMiddleware<
7+
RequestContext: HTTPRequestContext,
78
RequestConcludingAsyncReader: ConcludingAsyncReader & ~Copyable,
89
ResponseConcludingAsyncWriter: ConcludingAsyncWriter & ~Copyable,
910
>: Middleware, Sendable
@@ -13,14 +14,14 @@ where
1314
ResponseConcludingAsyncWriter.Underlying: AsyncWriter<Span<UInt8>, any Error>,
1415
ResponseConcludingAsyncWriter.FinalElement == HTTPFields?
1516
{
16-
typealias Input = RequestResponseMiddlewareBox<RequestConcludingAsyncReader, ResponseConcludingAsyncWriter>
17+
typealias Input = RequestResponseMiddlewareBox<RequestContext, RequestConcludingAsyncReader, ResponseConcludingAsyncWriter>
1718
typealias NextInput = Never
1819

1920
func intercept(
2021
input: consuming Input,
2122
next: (consuming NextInput) async throws -> Void
2223
) async throws {
23-
try await input.withContents { request, requestReader, responseSender in
24+
try await input.withContents { request, _, requestReader, responseSender in
2425
var maybeReader = Optional(requestReader)
2526
try await responseSender.send(HTTPResponse(status: .accepted))
2627
.produceAndConclude { responseBodyAsyncWriter in
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
public protocol HTTPRequestContext: Sendable {
2+
3+
}
4+
5+
6+
public struct HTTPServerRequestContext: HTTPRequestContext {
7+
public init() {}
8+
}

Sources/HTTPServer/HTTPServerClosureRequestHandler.swift

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,20 @@ public import HTTPTypes
2424
/// }
2525
/// ```
2626
@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, visionOS 26.0, *)
27-
public struct HTTPServerClosureRequestHandler<ConcludingRequestReader: ~Copyable, ConcludingResponseWriter: ~Copyable>: HTTPServerRequestHandler {
27+
public struct HTTPServerClosureRequestHandler<
28+
RequestContext: HTTPRequestContext,
29+
ConcludingRequestReader: ConcludingAsyncReader<RequestReader, HTTPFields?> & ~Copyable,
30+
RequestReader: AsyncReader<Span<UInt8>, any Error> & ~Copyable,
31+
ConcludingResponseWriter: ConcludingAsyncWriter<RequestWriter, HTTPFields?> & ~Copyable,
32+
RequestWriter: AsyncWriter<Span<UInt8>, any Error> & ~Copyable
33+
>: HTTPServerRequestHandler {
2834
/// The underlying closure that handles HTTP requests
2935
private let _handler:
3036
nonisolated(nonsending) @Sendable (
3137
HTTPRequest,
32-
consuming HTTPRequestConcludingAsyncReader,
33-
consuming HTTPResponseSender<HTTPResponseConcludingAsyncWriter>
38+
RequestContext,
39+
consuming ConcludingRequestReader,
40+
consuming HTTPResponseSender<ConcludingResponseWriter>
3441
) async throws -> Void
3542

3643
/// Creates a new closure-based HTTP request handler.
@@ -40,8 +47,9 @@ public struct HTTPServerClosureRequestHandler<ConcludingRequestReader: ~Copyable
4047
public init(
4148
handler: nonisolated(nonsending) @Sendable @escaping (
4249
HTTPRequest,
43-
consuming HTTPRequestConcludingAsyncReader,
44-
consuming HTTPResponseSender<HTTPResponseConcludingAsyncWriter>
50+
RequestContext,
51+
consuming ConcludingRequestReader,
52+
consuming HTTPResponseSender<ConcludingResponseWriter>
4553
) async throws -> Void
4654
) {
4755
self._handler = handler
@@ -57,9 +65,10 @@ public struct HTTPServerClosureRequestHandler<ConcludingRequestReader: ~Copyable
5765
/// - responseSender: An ``HTTPResponseSender`` to send the HTTP response.
5866
public func handle(
5967
request: HTTPRequest,
60-
requestBodyAndTrailers: consuming HTTPRequestConcludingAsyncReader,
61-
responseSender: consuming HTTPResponseSender<HTTPResponseConcludingAsyncWriter>
68+
requestContext: RequestContext,
69+
requestBodyAndTrailers: consuming ConcludingRequestReader,
70+
responseSender: consuming HTTPResponseSender<ConcludingResponseWriter>
6271
) async throws {
63-
try await self._handler(request, requestBodyAndTrailers, responseSender)
72+
try await self._handler(request, requestContext, requestBodyAndTrailers, responseSender)
6473
}
6574
}

Sources/HTTPServer/HTTPServerProtocol.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,14 @@ public protocol HTTPServerProtocol: Sendable, ~Copyable, ~Escapable {
4343
}
4444

4545
@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, visionOS 26.0, *)
46-
extension HTTPServerProtocol where RequestHandler == HTTPServerClosureRequestHandler<HTTPRequestConcludingAsyncReader, HTTPResponseConcludingAsyncWriter> {
46+
extension HTTPServerProtocol
47+
where RequestHandler == HTTPServerClosureRequestHandler<
48+
HTTPServerRequestContext,
49+
HTTPRequestConcludingAsyncReader,
50+
HTTPRequestConcludingAsyncReader.Underlying,
51+
HTTPResponseConcludingAsyncWriter,
52+
HTTPResponseConcludingAsyncWriter.Underlying
53+
> {
4754
/// Starts an HTTP server with a closure-based request handler.
4855
///
4956
/// This method provides a convenient way to start an HTTP server using a closure to handle incoming requests.
@@ -70,6 +77,7 @@ extension HTTPServerProtocol where RequestHandler == HTTPServerClosureRequestHan
7077
public func serve(
7178
handler: @Sendable @escaping (
7279
_ request: HTTPRequest,
80+
_ requestContext: HTTPServerRequestContext,
7381
_ requestBodyAndTrailers: consuming HTTPRequestConcludingAsyncReader,
7482
_ responseSender: consuming HTTPResponseSender<HTTPResponseConcludingAsyncWriter>
7583
) async throws -> Void

Sources/HTTPServer/HTTPServerRequestHandler.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ public protocol HTTPServerRequestHandler: Sendable {
7575
/// be `Span<UInt8>`.
7676
associatedtype RequestWriter: AsyncWriter<Span<UInt8>, any Error> & ~Copyable
7777

78+
associatedtype RequestContext: HTTPRequestContext
79+
7880
/// Handles an incoming HTTP request and generates a response.
7981
///
8082
/// This method is called by the HTTP server for each incoming client request. Implementations should:
@@ -95,8 +97,8 @@ public protocol HTTPServerRequestHandler: Sendable {
9597
///
9698
/// - Throws: Any error encountered during request processing or response generation.
9799
func handle(
98-
// TODO: add request context parameter
99100
request: HTTPRequest,
101+
requestContext: RequestContext,
100102
requestBodyAndTrailers: consuming ConcludingRequestReader,
101103
responseSender: consuming HTTPResponseSender<ConcludingResponseWriter>
102104
) async throws

Sources/HTTPServer/NIOHTTPServer.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ import Synchronization
6464
@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, visionOS 26.0, *)
6565
public struct NIOHTTPServer<RequestHandler: HTTPServerRequestHandler>: HTTPServerProtocol
6666
where RequestHandler.ConcludingRequestReader == HTTPRequestConcludingAsyncReader,
67-
RequestHandler.ConcludingResponseWriter == HTTPResponseConcludingAsyncWriter {
67+
RequestHandler.ConcludingResponseWriter == HTTPResponseConcludingAsyncWriter,
68+
RequestHandler.RequestContext == HTTPServerRequestContext {
6869
private let logger: Logger
6970
private let configuration: HTTPServerConfiguration
7071

@@ -424,6 +425,7 @@ where RequestHandler.ConcludingRequestReader == HTTPRequestConcludingAsyncReader
424425
do {
425426
try await handler.handle(
426427
request: httpRequest,
428+
requestContext: HTTPServerRequestContext(),
427429
requestBodyAndTrailers: HTTPRequestConcludingAsyncReader(
428430
iterator: iterator,
429431
readerState: readerState

Sources/HTTPServer/RequestResponseMiddlewareBox.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ public import HTTPTypes
44
/// It is necessary to box them together so that they can be used with `Middlewares`, as this will be the `Middleware.Input`.
55
@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, visionOS 26.0, *)
66
public struct RequestResponseMiddlewareBox<
7+
RequestContext: HTTPRequestContext,
78
RequestReader: ConcludingAsyncReader & ~Copyable,
89
ResponseWriter: ConcludingAsyncWriter & ~Copyable
910
>: ~Copyable {
1011
private let request: HTTPRequest
12+
private let requestContext: RequestContext
1113
private let requestReader: RequestReader
1214
private let responseSender: HTTPResponseSender<ResponseWriter>
1315

@@ -18,10 +20,12 @@ public struct RequestResponseMiddlewareBox<
1820
/// - responseSender: The ``HTTPResponseSender``.
1921
public init(
2022
request: HTTPRequest,
23+
requestContext: RequestContext,
2124
requestReader: consuming RequestReader,
2225
responseSender: consuming HTTPResponseSender<ResponseWriter>
2326
) {
2427
self.request = request
28+
self.requestContext = requestContext
2529
self.requestReader = requestReader
2630
self.responseSender = responseSender
2731
}
@@ -32,11 +36,17 @@ public struct RequestResponseMiddlewareBox<
3236
public consuming func withContents<T>(
3337
_ handler: nonisolated(nonsending) (
3438
HTTPRequest,
39+
RequestContext,
3540
consuming RequestReader,
3641
consuming HTTPResponseSender<ResponseWriter>
3742
) async throws -> T
3843
) async throws -> T {
39-
try await handler(self.request, self.requestReader, self.responseSender)
44+
try await handler(
45+
self.request,
46+
self.requestContext,
47+
self.requestReader,
48+
self.responseSender
49+
)
4050
}
4151
}
4252

Tests/HTTPServerTests/HTTPServerTests.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,17 @@ struct HTTPServerTests {
88
@Test
99
@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, visionOS 26.0, *)
1010
func testConsumingServe() async throws {
11-
let server = NIOHTTPServer<HTTPServerClosureRequestHandler<HTTPRequestConcludingAsyncReader, HTTPResponseConcludingAsyncWriter>>(
11+
let server = NIOHTTPServer<HTTPServerClosureRequestHandler<
12+
HTTPServerRequestContext,
13+
HTTPRequestConcludingAsyncReader,
14+
HTTPRequestConcludingAsyncReader.RequestBodyAsyncReader,
15+
HTTPResponseConcludingAsyncWriter,
16+
HTTPResponseConcludingAsyncWriter.ResponseBodyAsyncWriter
17+
>>(
1218
logger: Logger(label: "Test"),
1319
configuration: .init(bindTarget: .hostAndPort(host: "127.0.0.1", port: 0))
1420
)
15-
try await server.serve { request, requestBodyAndTrailers, responseSender in
21+
try await server.serve { request, context, requestBodyAndTrailers, responseSender in
1622
_ = try await requestBodyAndTrailers.collect(upTo: 100) { _ in }
1723
// Uncommenting this would cause a "requestReader consumed more than once" error.
1824
//_ = try await requestReader.collect(upTo: 100) { _ in }

0 commit comments

Comments
 (0)