Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions Examples/ExampleMiddleware/HTTPServerLoggingMiddleware.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public import Middleware
/// This middleware is useful for debugging and monitoring HTTP traffic.
@available(anyAppleOS 26.0, *)
public struct HTTPServerLoggingMiddleware<
RequestContext: HTTPServerCapability.RequestContext,
RequestConcludingAsyncReader: ConcludingAsyncReader & ~Copyable,
ResponseConcludingAsyncWriter: ConcludingAsyncWriter & ~Copyable
>: Middleware
Expand All @@ -36,8 +37,9 @@ where
ResponseConcludingAsyncWriter.Underlying.WriteElement == UInt8,
ResponseConcludingAsyncWriter.FinalElement == HTTPFields?
{
public typealias Input = HTTPServerMiddlewareInput<RequestConcludingAsyncReader, ResponseConcludingAsyncWriter>
public typealias Input = HTTPServerMiddlewareInput<RequestContext, RequestConcludingAsyncReader, ResponseConcludingAsyncWriter>
public typealias NextInput = HTTPServerMiddlewareInput<
RequestContext,
HTTPRequestLoggingConcludingAsyncReader<RequestConcludingAsyncReader>,
HTTPResponseLoggingConcludingAsyncWriter<ResponseConcludingAsyncWriter>
>
Expand Down Expand Up @@ -117,11 +119,12 @@ extension Middleware where Input: ~Copyable, NextInput: ~Copyable {
/// .requestHandler()
/// }
/// ```
public func logging<RequestReader, ResponseWriter>(
public func logging<RequestContext, RequestReader, ResponseWriter>(
logger: Logger
) -> HTTPServerLoggingMiddleware<RequestReader, ResponseWriter>
) -> HTTPServerLoggingMiddleware<RequestContext, RequestReader, ResponseWriter>
where
Input == HTTPServerMiddlewareInput<RequestReader, ResponseWriter>,
Input == HTTPServerMiddlewareInput<RequestContext, RequestReader, ResponseWriter>,
RequestContext: HTTPServerCapability.RequestContext,
RequestReader: ConcludingAsyncReader & ~Copyable & Escapable,
RequestReader.Underlying: ~Copyable & Escapable,
RequestReader.Underlying.ReadElement == UInt8,
Expand Down
7 changes: 4 additions & 3 deletions Examples/ExampleMiddleware/HTTPServerMiddlewareInput.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ public import HTTPAPIs
/// convenient way to pass all request-handling components through the middleware chain.
@available(anyAppleOS 26.0, *)
public struct HTTPServerMiddlewareInput<
RequestContext: HTTPServerCapability.RequestContext,
RequestReader: ConcludingAsyncReader & ~Copyable,
ResponseWriter: ConcludingAsyncWriter & ~Copyable
>: ~Copyable where RequestReader.Underlying: ~Copyable, ResponseWriter.Underlying: ~Copyable {
private let request: HTTPRequest
private let requestContext: HTTPRequestContext
private let requestContext: RequestContext
private let requestReader: RequestReader
private let responseSender: HTTPResponseSender<ResponseWriter>

Expand All @@ -38,7 +39,7 @@ public struct HTTPServerMiddlewareInput<
/// - responseSender: A sender for transmitting the HTTP response and response body.
public init(
request: HTTPRequest,
requestContext: HTTPRequestContext,
requestContext: RequestContext,
requestReader: consuming RequestReader,
responseSender: consuming HTTPResponseSender<ResponseWriter>
) {
Expand All @@ -63,7 +64,7 @@ public struct HTTPServerMiddlewareInput<
_ handler:
(
HTTPRequest,
HTTPRequestContext,
RequestContext,
consuming RequestReader,
consuming HTTPResponseSender<ResponseWriter>
) async throws -> Return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public import Middleware
/// This middleware has `Never` as its `NextInput` type, indicating it's the end of the chain.
@available(anyAppleOS 26.0, *)
public struct HTTPServerRequestHandlerMiddleware<
RequestContext: HTTPServerCapability.RequestContext,
RequestConcludingAsyncReader: ConcludingAsyncReader & ~Copyable,
ResponseConcludingAsyncWriter: ConcludingAsyncWriter & ~Copyable,
>: Middleware, Sendable
Expand All @@ -32,7 +33,7 @@ where
ResponseConcludingAsyncWriter.Underlying.WriteElement == UInt8,
ResponseConcludingAsyncWriter.FinalElement == HTTPFields?
{
public typealias Input = HTTPServerMiddlewareInput<RequestConcludingAsyncReader, ResponseConcludingAsyncWriter>
public typealias Input = HTTPServerMiddlewareInput<RequestContext, RequestConcludingAsyncReader, ResponseConcludingAsyncWriter>
public typealias NextInput = Void

/// Creates a new request handler middleware.
Expand Down Expand Up @@ -82,9 +83,10 @@ extension Middleware where Input: ~Copyable, NextInput: ~Copyable {
/// .requestHandler()
/// }
/// ```
public func requestHandler<RequestReader, ResponseWriter>() -> HTTPServerRequestHandlerMiddleware<RequestReader, ResponseWriter>
public func requestHandler<RequestContext, RequestReader, ResponseWriter>() -> HTTPServerRequestHandlerMiddleware<RequestContext, RequestReader, ResponseWriter>
where
Input == HTTPServerMiddlewareInput<RequestReader, ResponseWriter>,
Input == HTTPServerMiddlewareInput<RequestContext, RequestReader, ResponseWriter>,
RequestContext: HTTPServerCapability.RequestContext,
RequestReader: ConcludingAsyncReader & ~Copyable,
RequestReader.Underlying: ~Copyable,
RequestReader.Underlying.ReadElement == UInt8,
Expand Down
4 changes: 2 additions & 2 deletions Examples/MiddlewareServer/ExampleMiddlewareServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ where
Server.ResponseConcludingWriter.Underlying: ~Copyable,
ServerMiddleware.Input: ~Copyable,
ServerMiddleware.NextInput: ~Copyable,
ServerMiddleware.Input == HTTPServerMiddlewareInput<Server.RequestConcludingReader, Server.ResponseConcludingWriter>
ServerMiddleware.Input == HTTPServerMiddlewareInput<Server.RequestContext, Server.RequestConcludingReader, Server.ResponseConcludingWriter>
{
typealias RequestConcludingReader = Server.RequestConcludingReader
typealias ResponseConcludingWriter = Server.ResponseConcludingWriter
Expand Down Expand Up @@ -70,7 +70,7 @@ where
Server.ResponseConcludingWriter: ~Copyable,
Server.ResponseConcludingWriter.Underlying: ~Copyable
{
typealias Input = HTTPServerMiddlewareInput<Server.RequestConcludingReader, Server.ResponseConcludingWriter>
typealias Input = HTTPServerMiddlewareInput<Server.RequestContext, Server.RequestConcludingReader, Server.ResponseConcludingWriter>
typealias NextInput = Input

func intercept<Return: ~Copyable>(
Expand Down
6 changes: 5 additions & 1 deletion Sources/HTTPAPIs/Server/HTTPServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
///
/// ``HTTPServer`` provides the contract for server implementations that accept
/// incoming HTTP connections and process requests using a ``HTTPServerRequestHandler``.
public protocol HTTPServer<RequestConcludingReader, ResponseConcludingWriter>: Sendable, ~Copyable, ~Escapable {
public protocol HTTPServer<RequestContext, RequestConcludingReader, ResponseConcludingWriter>: Sendable, ~Copyable, ~Escapable {
associatedtype RequestContext: HTTPServerCapability.RequestContext, ~Copyable, ~Escapable

/// The type used to read request body data and trailers.
// TODO: Check if we should allow ~Escapable readers https://github.com/apple/swift-http-api-proposal/issues/13
associatedtype RequestConcludingReader: ConcludingAsyncReader, ~Copyable, SendableMetatype
Expand Down Expand Up @@ -52,6 +54,8 @@ public protocol HTTPServer<RequestConcludingReader, ResponseConcludingWriter>: S
/// ```
func serve<Handler: HTTPServerRequestHandler>(handler: Handler) async throws
where
Handler.RequestContext: ~Copyable & ~Escapable,
Handler.RequestContext == RequestContext,
Handler.RequestReader == RequestConcludingReader,
Handler.RequestReader: ~Copyable,
Handler.ResponseWriter == ResponseConcludingWriter,
Expand Down
23 changes: 23 additions & 0 deletions Sources/HTTPAPIs/Server/HTTPServerCapability+RequestContext.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

/// The namespace for all protocols defining HTTP server capabilities.
@available(macOS 26.2, iOS 26.2, watchOS 26.2, tvOS 26.2, visionOS 26.2, *)
public enum HTTPServerCapability {
/// The request context protocol.
///
/// Child protocols define additional context that a subset of servers provide,
/// allowing libraries to depend on specific capabilities.
public protocol RequestContext: ~Copyable, ~Escapable {
}
}
16 changes: 9 additions & 7 deletions Sources/HTTPAPIs/Server/HTTPServerClosureRequestHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
/// ```
@available(anyAppleOS 26.0, *)
public struct HTTPServerClosureRequestHandler<
RequestContext: HTTPServerCapability.RequestContext & ~Copyable & ~Escapable,
RequestReader: ConcludingAsyncReader & ~Copyable,
ResponseWriter: ConcludingAsyncWriter & ~Copyable,
>: HTTPServerRequestHandler
Expand All @@ -47,11 +48,12 @@ where
RequestReader.FinalElement == HTTPFields?,
ResponseWriter.FinalElement == HTTPFields?
{

/// The underlying closure that handles HTTP requests.
private let _handler:
@Sendable (
HTTPRequest,
HTTPRequestContext,
consuming RequestContext,
consuming sending RequestReader,
consuming sending HTTPResponseSender<ResponseWriter>
) async throws -> Void
Expand All @@ -65,7 +67,7 @@ where
handler:
@Sendable @escaping (
HTTPRequest,
HTTPRequestContext,
consuming RequestContext,
consuming sending RequestReader,
consuming sending HTTPResponseSender<ResponseWriter>
) async throws -> Void
Expand All @@ -79,12 +81,12 @@ where
///
/// - Parameters:
/// - request: The HTTP request headers and metadata.
/// - requestContext: A ``HTTPRequestContext``.
/// - requestContext: The request context provided by the server.
/// - requestBodyAndTrailers: A reader for accessing the request body data and trailing headers.
/// - responseSender: An ``HTTPResponseSender`` to send the HTTP response.
public func handle(
request: HTTPRequest,
requestContext: HTTPRequestContext,
requestContext: consuming RequestContext,
requestBodyAndTrailers: consuming sending RequestReader,
responseSender: consuming sending HTTPResponseSender<ResponseWriter>
) async throws {
Expand All @@ -109,14 +111,14 @@ where
/// - Parameters:
/// - handler: An async closure that processes HTTP requests. The closure receives:
/// - `HTTPRequest`: The incoming HTTP request with headers and metadata.
/// - ``HTTPRequestContext``: The request's context.
/// - `RequestContext`: The request context provided by the server.
/// - ``HTTPRequestConcludingAsyncReader``: An async reader for consuming the request body and trailers.
/// - ``HTTPResponseSender``: A non-copyable wrapper for a function that accepts an `HTTPResponse` and provides access to an ``HTTPResponseConcludingAsyncWriter``.
///
/// ## Example
///
/// ```swift
/// try await server.serve { request, bodyReader, responseSender in
/// try await server.serve { request, requestContext, bodyReader, responseSender in
/// // Process the request
/// let response = HTTPResponse(status: .ok)
/// let writer = try await responseSender.send(response)
Expand All @@ -130,7 +132,7 @@ where
handler:
@Sendable @escaping (
_ request: HTTPRequest,
_ requestContext: HTTPRequestContext,
_ requestContext: consuming RequestContext,
_ requestBodyAndTrailers: consuming sending RequestConcludingReader,
_ responseSender: consuming sending HTTPResponseSender<ResponseConcludingWriter>
) async throws -> Void
Expand Down
12 changes: 8 additions & 4 deletions Sources/HTTPAPIs/Server/HTTPServerRequestHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,15 @@
///
/// ```swift
/// struct EchoHandler<
/// Context: HTTPServerCapability.RequestContext,
/// ConcludingRequestReader: ConcludingAsyncReader<RequestReader, HTTPFields?> & ~Copyable,
/// RequestReader: AsyncReader<UInt8, any Error> & ~Copyable,
/// ConcludingResponseWriter: ConcludingAsyncWriter<ResponseWriter, HTTPFields?> & ~Copyable,
/// ResponseWriter: AsyncWriter<UInt8, any Error> & ~Copyable
/// >: HTTPServerRequestHandler {
/// func handle(
/// request: HTTPRequest,
/// requestContext: HTTPRequestContext,
/// requestContext: Context,
/// requestBodyAndTrailers: consuming sending ConcludingRequestReader,
/// responseSender: consuming sending HTTPResponseSender<ConcludingResponseWriter>
/// ) async throws {
Expand All @@ -55,7 +56,10 @@
/// }
/// ```
@available(anyAppleOS 26.0, *)
public protocol HTTPServerRequestHandler<RequestReader, ResponseWriter>: Sendable {
public protocol HTTPServerRequestHandler<RequestContext, RequestReader, ResponseWriter>: Sendable {
/// The type of the request context provided by the server.
associatedtype RequestContext: HTTPServerCapability.RequestContext, ~Copyable, ~Escapable

/// The type used to read request body data and trailers.
associatedtype RequestReader: ConcludingAsyncReader, ~Copyable
where RequestReader.Underlying: ~Copyable, RequestReader.Underlying.ReadElement == UInt8, RequestReader.FinalElement == HTTPFields?
Expand All @@ -76,15 +80,15 @@ public protocol HTTPServerRequestHandler<RequestReader, ResponseWriter>: Sendabl
///
/// - Parameters:
/// - request: The HTTP request headers and metadata.
/// - requestContext: A ``HTTPRequestContext`` carrying additional request information.
/// - requestContext: A context carrying additional request information provided by the server.
/// - requestBodyAndTrailers: A reader for accessing the request body data and trailing headers.
/// - responseSender: An ``HTTPResponseSender`` that accepts an HTTP response and returns a writer for the
/// response body. The returned writer allows for incremental writing of the response body and supports trailers.
///
/// - Throws: Any error encountered during request processing or response generation.
func handle(
request: HTTPRequest,
requestContext: HTTPRequestContext,
requestContext: consuming RequestContext,
requestBodyAndTrailers: consuming sending RequestReader,
responseSender: consuming sending HTTPResponseSender<ResponseWriter>
) async throws
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
//
//===----------------------------------------------------------------------===//

/// A context object that carries additional information about an HTTP request.
///
/// `HTTPRequestContext` provides a way to pass metadata through the HTTP request pipeline.
public struct HTTPRequestContext: Sendable {
public import HTTPAPIs

@available(macOS 26.2, iOS 26.2, watchOS 26.2, tvOS 26.2, visionOS 26.2, *)
public struct HTTPRequestContext: HTTPServerCapability.RequestContext {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This type should be moved to swift-http-server?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to HTTPServerForTesting

public init() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import NIOPosix
extension NIOHTTPServer {
func serveInsecureHTTP1_1(
bindTarget: NIOHTTPServerConfiguration.BindTarget,
handler: some HTTPServerRequestHandler<RequestConcludingReader, ResponseConcludingWriter>,
handler: some HTTPServerRequestHandler<HTTPRequestContext, RequestConcludingReader, ResponseConcludingWriter>,
asyncChannelConfiguration: NIOAsyncChannel<HTTPRequestPart, HTTPResponsePart>.Configuration
) async throws {
let serverChannel = try await self.setupHTTP1_1ServerChannel(
Expand Down Expand Up @@ -71,7 +71,7 @@ extension NIOHTTPServer {

func _serveInsecureHTTP1_1(
serverChannel: NIOAsyncChannel<NIOAsyncChannel<HTTPRequestPart, HTTPResponsePart>, Never>,
handler: some HTTPServerRequestHandler<RequestConcludingReader, ResponseConcludingWriter>
handler: some HTTPServerRequestHandler<HTTPRequestContext, RequestConcludingReader, ResponseConcludingWriter>
) async throws {
try await withThrowingDiscardingTaskGroup { group in
try await serverChannel.executeThenClose { inbound in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ extension NIOHTTPServer {
func serveSecureUpgrade(
bindTarget: NIOHTTPServerConfiguration.BindTarget,
tlsConfiguration: TLSConfiguration,
handler: some HTTPServerRequestHandler<RequestConcludingReader, ResponseConcludingWriter>,
handler: some HTTPServerRequestHandler<HTTPRequestContext, RequestConcludingReader, ResponseConcludingWriter>,
asyncChannelConfiguration: NIOAsyncChannel<HTTPRequestPart, HTTPResponsePart>.Configuration,
http2Configuration: NIOHTTP2Handler.Configuration,
verificationCallback: (@Sendable ([X509.Certificate]) async throws -> CertificateVerificationResult)? = nil
Expand Down Expand Up @@ -120,7 +120,7 @@ extension NIOHTTPServer {

func _serveSecureUpgrade(
serverChannel: NIOAsyncChannel<EventLoopFuture<NegotiatedChannel>, Never>,
handler: some HTTPServerRequestHandler<RequestConcludingReader, ResponseConcludingWriter>
handler: some HTTPServerRequestHandler<HTTPRequestContext, RequestConcludingReader, ResponseConcludingWriter>
) async throws {
try await withThrowingDiscardingTaskGroup { group in
try await serverChannel.executeThenClose { inbound in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ import X509
/// ```
@available(anyAppleOS 26.0, *)
public struct NIOHTTPServer: HTTPServer {
public typealias RequestContext = HTTPRequestContext
public typealias RequestConcludingReader = HTTPRequestConcludingAsyncReader
public typealias ResponseConcludingWriter = HTTPResponseConcludingAsyncWriter

Expand Down Expand Up @@ -145,7 +146,7 @@ public struct NIOHTTPServer: HTTPServer {
/// handler: EchoHandler()
/// )
/// ```
public func serve(handler: some HTTPServerRequestHandler<RequestConcludingReader, ResponseConcludingWriter>) async throws {
public func serve(handler: some HTTPServerRequestHandler<HTTPRequestContext, RequestConcludingReader, ResponseConcludingWriter>) async throws {
defer {
switch self.listeningAddressState.withLockedValue({ $0.close() }) {
case .failPromise(let promise, let error):
Expand Down Expand Up @@ -265,7 +266,7 @@ public struct NIOHTTPServer: HTTPServer {

func handleRequestChannel(
channel: NIOAsyncChannel<HTTPRequestPart, HTTPResponsePart>,
handler: some HTTPServerRequestHandler<RequestConcludingReader, ResponseConcludingWriter>
handler: some HTTPServerRequestHandler<HTTPRequestContext, RequestConcludingReader, ResponseConcludingWriter>
) async throws {
do {
try await channel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,24 @@ public import HTTPTypes
/// It is necessary to box them together so that they can be used with `Middlewares`, as this will be the `Middleware.Input`.
@available(anyAppleOS 26.0, *)
public struct RequestResponseMiddlewareBox<
RequestContext: HTTPServerCapability.RequestContext,
RequestReader: ConcludingAsyncReader & ~Copyable,
ResponseWriter: ConcludingAsyncWriter & ~Copyable
>: ~Copyable {
private let request: HTTPRequest
private let requestContext: HTTPRequestContext
private let requestContext: RequestContext
private let requestReader: RequestReader
private let responseSender: HTTPResponseSender<ResponseWriter>

/// Create a new ``RequestResponseMiddlewareBox``.
/// - Parameters:
/// - request: The `HTTPRequest`.
/// - requestContext: The request context.
/// - requestReader: The `RequestReader`.
/// - responseSender: The ``HTTPResponseSender``.
public init(
request: HTTPRequest,
requestContext: HTTPRequestContext,
requestContext: RequestContext,
requestReader: consuming RequestReader,
responseSender: consuming HTTPResponseSender<ResponseWriter>
) {
Expand All @@ -51,7 +53,7 @@ public struct RequestResponseMiddlewareBox<
_ handler:
nonisolated(nonsending) (
HTTPRequest,
HTTPRequestContext,
RequestContext,
consuming RequestReader,
consuming HTTPResponseSender<ResponseWriter>
) async throws -> T
Expand Down
Loading