Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
16 changes: 3 additions & 13 deletions Sources/Example/Example.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,7 @@ struct Example {

// Using the new extension method that doesn't require type hints
let privateKey = P256.Signing.PrivateKey()
let server = NIOHTTPServer<HTTPServerClosureRequestHandler<
HTTPRequestConcludingAsyncReader,
HTTPRequestConcludingAsyncReader.Underlying,
HTTPResponseConcludingAsyncWriter,
HTTPResponseConcludingAsyncWriter.Underlying
>>(
let server = NIOHTTPServer(
logger: logger,
configuration: .init(
bindTarget: .hostAndPort(host: "127.0.0.1", port: 12345),
Expand All @@ -50,6 +45,7 @@ struct Example {
)
)
)

try await server.serve { request, requestContext, requestBodyAndTrailers, responseSender in
let writer = try await responseSender.send(HTTPResponse(status: .ok))
try await writer.writeAndConclude(element: "Well, hello!".utf8.span, finalElement: nil)
Expand All @@ -60,13 +56,7 @@ struct Example {
// MARK: - Server Extensions

@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, visionOS 26.0, *)
extension NIOHTTPServer
where RequestHandler == HTTPServerClosureRequestHandler<
HTTPRequestConcludingAsyncReader,
HTTPRequestConcludingAsyncReader.Underlying,
HTTPResponseConcludingAsyncWriter,
HTTPResponseConcludingAsyncWriter.Underlying
> {
extension NIOHTTPServer {
/// Serve HTTP requests using a middleware chain built with the provided builder
/// This method handles the type inference for HTTP middleware components
func serve(
Expand Down
31 changes: 13 additions & 18 deletions Sources/HTTPServer/HTTPServerClosureRequestHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,18 @@ public import HTTPTypes
/// ```
@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, visionOS 26.0, *)
public struct HTTPServerClosureRequestHandler<
ConcludingRequestReader: ConcludingAsyncReader<RequestReader, HTTPFields?> & ~Copyable,
ConcludingRequestReader: ConcludingAsyncReader<RequestReader, HTTPFields?> & ~Copyable & SendableMetatype,
RequestReader: AsyncReader<Span<UInt8>, any Error> & ~Copyable,
ConcludingResponseWriter: ConcludingAsyncWriter<RequestWriter, HTTPFields?> & ~Copyable,
ConcludingResponseWriter: ConcludingAsyncWriter<RequestWriter, HTTPFields?> & ~Copyable & SendableMetatype,
RequestWriter: AsyncWriter<Span<UInt8>, any Error> & ~Copyable
>: HTTPServerRequestHandler {
/// The underlying closure that handles HTTP requests
private let _handler:
nonisolated(nonsending) @Sendable (
HTTPRequest,
HTTPRequestContext,
consuming sending HTTPRequestConcludingAsyncReader,
consuming sending HTTPResponseSender<HTTPResponseConcludingAsyncWriter>
consuming sending ConcludingRequestReader,
consuming sending HTTPResponseSender<ConcludingResponseWriter>
) async throws -> Void

/// Creates a new closure-based HTTP request handler.
Expand All @@ -47,8 +47,8 @@ public struct HTTPServerClosureRequestHandler<
handler: nonisolated(nonsending) @Sendable @escaping (
HTTPRequest,
HTTPRequestContext,
consuming sending HTTPRequestConcludingAsyncReader,
consuming sending HTTPResponseSender<HTTPResponseConcludingAsyncWriter>
consuming sending ConcludingRequestReader,
consuming sending HTTPResponseSender<ConcludingResponseWriter>
) async throws -> Void
) {
self._handler = handler
Expand All @@ -66,20 +66,15 @@ public struct HTTPServerClosureRequestHandler<
public func handle(
request: HTTPRequest,
requestContext: HTTPRequestContext,
requestBodyAndTrailers: consuming sending HTTPRequestConcludingAsyncReader,
responseSender: consuming sending HTTPResponseSender<HTTPResponseConcludingAsyncWriter>
requestBodyAndTrailers: consuming sending ConcludingRequestReader,
responseSender: consuming sending HTTPResponseSender<ConcludingResponseWriter>
) async throws {
try await self._handler(request, requestContext, requestBodyAndTrailers, responseSender)
}
}

@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, visionOS 26.0, *)
extension HTTPServerProtocol where RequestHandler == HTTPServerClosureRequestHandler<
HTTPRequestConcludingAsyncReader,
HTTPRequestConcludingAsyncReader.Underlying,
HTTPResponseConcludingAsyncWriter,
HTTPResponseConcludingAsyncWriter.Underlying
> {
extension HTTPServerProtocol {
/// Starts an HTTP server with a closure-based request handler.
///
/// This method provides a convenient way to start an HTTP server using a closure to handle incoming requests.
Expand All @@ -105,13 +100,13 @@ extension HTTPServerProtocol where RequestHandler == HTTPServerClosureRequestHan
/// }
/// ```
public func serve(
handler: @Sendable @escaping (
body: @Sendable @escaping (
_ request: HTTPRequest,
_ requestContext: HTTPRequestContext,
_ requestBodyAndTrailers: consuming sending HTTPRequestConcludingAsyncReader,
_ responseSender: consuming sending HTTPResponseSender<HTTPResponseConcludingAsyncWriter>
_ requestBodyAndTrailers: consuming sending ConcludingRequestReader,
_ responseSender: consuming sending HTTPResponseSender<ConcludingResponseWriter>
) async throws -> Void
) async throws {
try await self.serve(handler: HTTPServerClosureRequestHandler(handler: handler))
try await self.serve(handler: HTTPServerClosureRequestHandler(handler: body))
}
}
20 changes: 15 additions & 5 deletions Sources/HTTPServer/HTTPServerProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,21 @@ public import HTTPTypes
@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, visionOS 26.0, *)
/// A generic HTTP server protocol that can handle incoming HTTP requests.
public protocol HTTPServerProtocol: Sendable, ~Copyable, ~Escapable {
// TODO: write down in the proposal we can't make the serve method generic over the handler
// because otherwise, closure-based APIs can't be implemented.
/// The ``ConcludingAsyncReader`` to use when reading requests. ``ConcludingAsyncReader/FinalElement``
/// must be an optional `HTTPFields`, and ``ConcludingAsyncReader/Underlying`` must use `Span<UInt8>` as its
/// `ReadElement`.
associatedtype ConcludingRequestReader: ConcludingAsyncReader & ~Copyable & SendableMetatype
where ConcludingRequestReader.Underlying.ReadElement == Span<UInt8>,
ConcludingRequestReader.Underlying.ReadFailure == any Error,
ConcludingRequestReader.FinalElement == HTTPFields?

/// The ``HTTPServerRequestHandler`` to use when handling requests.
associatedtype RequestHandler: HTTPServerRequestHandler
/// The ``ConcludingAsyncWriter`` to use when reading requests. ``ConcludingAsyncWriter/FinalElement``
/// must be an optional `HTTPFields`, and ``ConcludingAsyncWriter/Underlying`` must use `Span<UInt8>` as its
/// `WriteElement`.
associatedtype ConcludingResponseWriter: ConcludingAsyncWriter & ~Copyable & SendableMetatype
where ConcludingResponseWriter.Underlying.WriteElement == Span<UInt8>,
ConcludingResponseWriter.Underlying.WriteFailure == any Error,
ConcludingResponseWriter.FinalElement == HTTPFields?

/// Starts an HTTP server with the specified request handler.
///
Expand Down Expand Up @@ -39,5 +49,5 @@ public protocol HTTPServerProtocol: Sendable, ~Copyable, ~Escapable {
///
/// try await server.serve(handler: EchoHandler())
/// ```
func serve(handler: RequestHandler) async throws
func serve(handler: some HTTPServerRequestHandler<ConcludingRequestReader, ConcludingResponseWriter>) async throws
}
16 changes: 5 additions & 11 deletions Sources/HTTPServer/HTTPServerRequestHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,24 +57,18 @@ public import HTTPTypes
/// }
/// ```
@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, visionOS 26.0, *)
public protocol HTTPServerRequestHandler: Sendable {
public protocol HTTPServerRequestHandler<ConcludingRequestReader, ConcludingResponseWriter>: Sendable {
/// The ``ConcludingAsyncReader`` to use when reading requests. ``ConcludingAsyncReader/FinalElement``
/// must be an optional `HTTPFields`, and ``ConcludingAsyncReader/Underlying`` must use `Span<UInt8>` as its
/// `ReadElement`.
associatedtype ConcludingRequestReader: ConcludingAsyncReader<RequestReader, HTTPFields?> & ~Copyable

/// The underlying ``AsyncReader`` for ``ConcludingRequestReader``. Its ``AsyncReader/ReadElement`` must
/// be `Span<UInt8>`.
associatedtype RequestReader: AsyncReader<Span<UInt8>, any Error> & ~Copyable
associatedtype ConcludingRequestReader: ConcludingAsyncReader & ~Copyable & SendableMetatype
where ConcludingRequestReader.Underlying.ReadElement == Span<UInt8>, ConcludingRequestReader.FinalElement == HTTPFields?

/// The ``ConcludingAsyncWriter`` to use when reading requests. ``ConcludingAsyncWriter/FinalElement``
/// must be an optional `HTTPFields`, and ``ConcludingAsyncWriter/Underlying`` must use `Span<UInt8>` as its
/// `WriteElement`.
associatedtype ConcludingResponseWriter: ConcludingAsyncWriter<RequestWriter, HTTPFields?> & ~Copyable

/// The underlying ``AsyncWriter`` for ``ConcludingResponseWriter``. Its ``AsyncWriter/WriteElement`` must
/// be `Span<UInt8>`.
associatedtype RequestWriter: AsyncWriter<Span<UInt8>, any Error> & ~Copyable
associatedtype ConcludingResponseWriter: ConcludingAsyncWriter & ~Copyable & SendableMetatype
where ConcludingResponseWriter.Underlying.WriteElement == Span<UInt8>, ConcludingResponseWriter.FinalElement == HTTPFields?

/// Handles an incoming HTTP request and generates a response.
///
Expand Down
15 changes: 8 additions & 7 deletions Sources/HTTPServer/NIOHTTPServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,10 @@ import Synchronization
/// }
/// ```
@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, visionOS 26.0, *)
public struct NIOHTTPServer<RequestHandler: HTTPServerRequestHandler>: HTTPServerProtocol
where RequestHandler.ConcludingRequestReader == HTTPRequestConcludingAsyncReader,
RequestHandler.ConcludingResponseWriter == HTTPResponseConcludingAsyncWriter {
public struct NIOHTTPServer: HTTPServerProtocol {
public typealias ConcludingRequestReader = HTTPRequestConcludingAsyncReader
public typealias ConcludingResponseWriter = HTTPResponseConcludingAsyncWriter

private let logger: Logger
private let configuration: HTTPServerConfiguration

Expand Down Expand Up @@ -118,7 +119,7 @@ where RequestHandler.ConcludingRequestReader == HTTPRequestConcludingAsyncReader
/// handler: EchoHandler()
/// )
/// ```
public func serve(handler: RequestHandler) async throws {
public func serve(handler: some HTTPServerRequestHandler<ConcludingRequestReader, ConcludingResponseWriter>) async throws {
let asyncChannelConfiguration: NIOAsyncChannel<HTTPRequestPart, HTTPResponsePart>.Configuration
switch self.configuration.backpressureStrategy.backing {
case .watermark(let low, let high):
Expand Down Expand Up @@ -274,7 +275,7 @@ where RequestHandler.ConcludingRequestReader == HTTPRequestConcludingAsyncReader

private func serveInsecureHTTP1_1(
bindTarget: HTTPServerConfiguration.BindTarget,
handler: RequestHandler,
handler: some HTTPServerRequestHandler<ConcludingRequestReader, ConcludingResponseWriter>,
asyncChannelConfiguration: NIOAsyncChannel<HTTPRequestPart, HTTPResponsePart>.Configuration
) async throws {
switch bindTarget.backing {
Expand Down Expand Up @@ -309,7 +310,7 @@ where RequestHandler.ConcludingRequestReader == HTTPRequestConcludingAsyncReader
private func serveSecureUpgrade(
bindTarget: HTTPServerConfiguration.BindTarget,
tlsConfiguration: TLSConfiguration,
handler: RequestHandler,
handler: some HTTPServerRequestHandler<ConcludingRequestReader, ConcludingResponseWriter>,
asyncChannelConfiguration: NIOAsyncChannel<HTTPRequestPart, HTTPResponsePart>.Configuration,
http2Configuration: NIOHTTP2Handler.Configuration
) async throws {
Expand Down Expand Up @@ -394,7 +395,7 @@ where RequestHandler.ConcludingRequestReader == HTTPRequestConcludingAsyncReader

private func handleRequestChannel(
channel: NIOAsyncChannel<HTTPRequestPart, HTTPResponsePart>,
handler: RequestHandler
handler: some HTTPServerRequestHandler<ConcludingRequestReader, ConcludingResponseWriter>
) async throws {
do {
try await channel
Expand Down
7 changes: 1 addition & 6 deletions Tests/HTTPServerTests/HTTPServerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,7 @@ struct HTTPServerTests {
@Test
@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, visionOS 26.0, *)
func testConsumingServe() async throws {
let server = NIOHTTPServer<HTTPServerClosureRequestHandler<
HTTPRequestConcludingAsyncReader,
HTTPRequestConcludingAsyncReader.RequestBodyAsyncReader,
HTTPResponseConcludingAsyncWriter,
HTTPResponseConcludingAsyncWriter.ResponseBodyAsyncWriter
>>(
let server = NIOHTTPServer(
logger: Logger(label: "Test"),
configuration: .init(bindTarget: .hostAndPort(host: "127.0.0.1", port: 0))
)
Expand Down
Loading