Skip to content
Merged
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
92 changes: 43 additions & 49 deletions Sources/Example/Example.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,58 +43,52 @@ struct Example {
],
privateKey: Certificate.PrivateKey(privateKey)
)
),
withMiddleware: {
HTTPRequestLoggingMiddleware<
HTTPRequestConcludingAsyncReader,
HTTPResponseConcludingAsyncWriter
>(logger: logger)
TracingMiddleware<
(
HTTPRequest,
HTTPRequestLoggingConcludingAsyncReader<HTTPRequestConcludingAsyncReader>,
(
HTTPResponse
) async throws -> HTTPResponseLoggingConcludingAsyncWriter<HTTPResponseConcludingAsyncWriter>
)
>()
RouteHandlerMiddleware<
HTTPRequestLoggingConcludingAsyncReader<HTTPRequestConcludingAsyncReader>,
HTTPResponseLoggingConcludingAsyncWriter<HTTPResponseConcludingAsyncWriter>
>()
}
)
), handler: handler(request:requestConcludingAsyncReader:responseSender:))
}

// This is a workaround for a current bug with the compiler.
@Sendable
nonisolated(nonsending) private static func handler(
request: HTTPRequest,
requestConcludingAsyncReader: consuming HTTPRequestConcludingAsyncReader,
responseSender: consuming HTTPResponseSender<HTTPResponseConcludingAsyncWriter>
) async throws {
let writer = try await responseSender.sendResponse(HTTPResponse(status: .ok))
try await writer.writeAndConclude(element: "Well, hello!".utf8.span, finalElement: nil)
}
}

// MARK: - Server Extensions

@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, visionOS 26.0, *)
extension Server {
/// Serve HTTP requests using a middleware chain built with the provided builder
/// This method handles the type inference for HTTP middleware components
static func serve(
logger: Logger,
configuration: HTTPServerConfiguration,
@MiddlewareChainBuilder
withMiddleware middlewareBuilder: () -> some Middleware<
(
HTTPRequest,
HTTPRequestConcludingAsyncReader,
(
HTTPResponse
) async throws -> HTTPResponseConcludingAsyncWriter
),
Never
> & Sendable
) async throws where RequestHandler == HTTPServerClosureRequestHandler {
let chain = middlewareBuilder()
// This has to be commented out because of the compiler bug above. Workaround doesn't apply here.

try await serve(
logger: logger,
configuration: configuration
) { request, requestReader, sendResponse in
try await chain.intercept(input: (request, requestReader, sendResponse)) { _ in }
}
}
}
//@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, visionOS 26.0, *)
//extension Server {
// /// Serve HTTP requests using a middleware chain built with the provided builder
// /// This method handles the type inference for HTTP middleware components
// static func serve(
// logger: Logger,
// configuration: HTTPServerConfiguration,
// @MiddlewareChainBuilder
// withMiddleware middlewareBuilder: () -> some Middleware<
// RequestResponseContext<
// HTTPRequestConcludingAsyncReader,
// HTTPResponseConcludingAsyncWriter
// >,
// Never
// > & Sendable
// ) async throws where RequestHandler == HTTPServerClosureRequestHandler {
// let chain = middlewareBuilder()
//
// try await serve(
// logger: logger,
// configuration: configuration
// ) { request, reader, responseSender in
// try await chain.intercept(input: RequestResponseContext(
// request: request,
// requestReader: reader,
// responseSender: responseSender
// )) { _ in }
// }
// }
//}
107 changes: 55 additions & 52 deletions Sources/Example/Middlewares/HTTPRequestLoggingMiddleware.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,90 +5,92 @@ import Middleware

@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, visionOS 26.0, *)
struct HTTPRequestLoggingMiddleware<
RequestConludingAsyncReader: ConcludingAsyncReader,
RequestConcludingAsyncReader: ConcludingAsyncReader & ~Copyable,
ResponseConcludingAsyncWriter: ConcludingAsyncWriter & ~Copyable
>: Middleware
where
RequestConludingAsyncReader.Underlying: AsyncReader<Span<UInt8>, any Error>,
RequestConludingAsyncReader.FinalElement == HTTPFields?,
ResponseConcludingAsyncWriter.Underlying: AsyncWriter<Span<UInt8>, any Error>,
RequestConcludingAsyncReader.Underlying.ReadElement == Span<UInt8>,
RequestConcludingAsyncReader.FinalElement == HTTPFields?,
ResponseConcludingAsyncWriter.Underlying.WriteElement == Span<UInt8>,
ResponseConcludingAsyncWriter.FinalElement == HTTPFields?
{
typealias Input = (
HTTPRequest, RequestConludingAsyncReader, (HTTPResponse) async throws -> ResponseConcludingAsyncWriter
)
typealias NextInput = (
HTTPRequest,
HTTPRequestLoggingConcludingAsyncReader<RequestConludingAsyncReader>,
(
HTTPResponse
) async throws -> HTTPResponseLoggingConcludingAsyncWriter<ResponseConcludingAsyncWriter>
)
typealias Input = RequestResponseMiddlewareBox<RequestConcludingAsyncReader, ResponseConcludingAsyncWriter>
typealias NextInput = RequestResponseMiddlewareBox<
HTTPRequestLoggingConcludingAsyncReader<RequestConcludingAsyncReader>,
HTTPResponseLoggingConcludingAsyncWriter<ResponseConcludingAsyncWriter>
>

let logger: Logger

init(
requestConludingAsyncReaderType: RequestConludingAsyncReader.Type = RequestConludingAsyncReader.self,
requestConcludingAsyncReaderType: RequestConcludingAsyncReader.Type = RequestConcludingAsyncReader.self,
responseConcludingAsyncWriterType: ResponseConcludingAsyncWriter.Type = ResponseConcludingAsyncWriter.self,
logger: Logger
) {
self.logger = logger
}

func intercept(
input: Input,
next: (NextInput) async throws -> Void
input: consuming Input,
next: (consuming NextInput) async throws -> Void
) async throws {
let request = input.0
let requestAsyncReader = input.1
let respond = input.2
self.logger.info("Received request \(request.path ?? "unknown" ) \(request.method.rawValue)")
defer {
self.logger.info("Finished request \(request.path ?? "unknown" ) \(request.method.rawValue)")
}
let wrappedReader = HTTPRequestLoggingConcludingAsyncReader(
base: requestAsyncReader,
logger: self.logger
)
try await next(
(request, wrappedReader, { httpResponse in
let writer = try await respond(httpResponse)
return HTTPResponseLoggingConcludingAsyncWriter(
base: writer,
logger: self.logger
)
try await input.withContents { request, requestReader, responseSender in
self.logger.info("Received request \(request.path ?? "unknown" ) \(request.method.rawValue)")
defer {
self.logger.info("Finished request \(request.path ?? "unknown" ) \(request.method.rawValue)")
}
let wrappedReader = HTTPRequestLoggingConcludingAsyncReader(
base: requestReader,
logger: self.logger
)

var maybeSender = Optional(responseSender)
let requestResponseBox = RequestResponseMiddlewareBox(
request: request,
requestReader: wrappedReader,
responseSender: HTTPResponseSender { [logger] response in
if let sender = maybeSender.take() {
let writer = try await sender.sendResponse(response)
return HTTPResponseLoggingConcludingAsyncWriter(
base: writer,
logger: logger
)
} else {
fatalError("Called closure more than once")
}
}
)
)
try await next(requestResponseBox)
}
}
}

@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, visionOS 26.0, *)
struct HTTPRequestLoggingConcludingAsyncReader<
Base: ConcludingAsyncReader
>: ConcludingAsyncReader
Base: ConcludingAsyncReader & ~Copyable
>: ConcludingAsyncReader, ~Copyable
where
Base.Underlying: AsyncReader<Span<UInt8>, any Error>,
Base.Underlying.ReadElement == Span<UInt8>,
Base.FinalElement == HTTPFields?
{
typealias Underlying = RequestBodyAsyncReader
typealias FinalElement = HTTPFields?

struct RequestBodyAsyncReader: AsyncReader {
struct RequestBodyAsyncReader: AsyncReader, ~Copyable {
typealias ReadElement = Span<UInt8>
typealias ReadFailure = any Error

private var underlying: Base.Underlying
private let logger: Logger

init(underlying: Base.Underlying, logger: Logger) {
init(underlying: consuming Base.Underlying, logger: Logger) {
self.underlying = underlying
self.logger = logger
}

mutating func read<Return>(
body: (consuming Span<UInt8>?) async throws -> Return
) async throws(any Error) -> Return {
) async throws -> Return {
let logger = self.logger
return try await self.underlying.read { span in
logger.info("Received next chunk \(span?.count ?? 0)")
Expand All @@ -100,27 +102,28 @@ where
private var base: Base
private let logger: Logger

init(base: Base, logger: Logger) {
init(base: consuming Base, logger: Logger) {
self.base = base
self.logger = logger
}

func consumeAndConclude<Return>(
body: (inout RequestBodyAsyncReader) async throws -> Return
) async throws -> (Return, HTTPFields?) {
let (result, trailers) = try await self.base.consumeAndConclude { bodyAsyncReader in
var wrappedReader = RequestBodyAsyncReader(
underlying: bodyAsyncReader,
logger: self.logger
consuming func consumeAndConclude<Return>(
body: (consuming Underlying) async throws -> Return
) async throws -> (Return, FinalElement) {
let (result, trailers) = try await self.base.consumeAndConclude { [logger] reader in
let wrappedReader = RequestBodyAsyncReader(
underlying: reader,
logger: logger
)
return try await body(&wrappedReader)
return try await body(wrappedReader)
}

if let trailers {
self.logger.info("Received request trailers \(trailers)")
} else {
self.logger.info("Received no request trailers")
}

return (result, trailers)
}
}
Expand All @@ -130,7 +133,7 @@ struct HTTPResponseLoggingConcludingAsyncWriter<
Base: ConcludingAsyncWriter & ~Copyable
>: ConcludingAsyncWriter, ~Copyable
where
Base.Underlying: AsyncWriter<Span<UInt8>, any Error>,
Base.Underlying.WriteElement == Span<UInt8>,
Base.FinalElement == HTTPFields?
{
typealias Underlying = ResponseBodyAsyncWriter
Expand Down
51 changes: 25 additions & 26 deletions Sources/Example/Middlewares/RouteHandlerMiddleware.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Middleware

@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, visionOS 26.0, *)
struct RouteHandlerMiddleware<
RequestConcludingAsyncReader: ConcludingAsyncReader & Copyable,
RequestConcludingAsyncReader: ConcludingAsyncReader & ~Copyable,
ResponseConcludingAsyncWriter: ConcludingAsyncWriter & ~Copyable,
>: Middleware, Sendable
where
Expand All @@ -13,38 +13,37 @@ where
ResponseConcludingAsyncWriter.Underlying: AsyncWriter<Span<UInt8>, any Error>,
ResponseConcludingAsyncWriter.FinalElement == HTTPFields?
{
typealias Input = (
HTTPRequest,
RequestConcludingAsyncReader,
(HTTPResponse) async throws -> ResponseConcludingAsyncWriter
)
typealias Input = RequestResponseMiddlewareBox<RequestConcludingAsyncReader, ResponseConcludingAsyncWriter>
typealias NextInput = Never

init(
requestConcludingAsyncReaderType: RequestConcludingAsyncReader.Type = RequestConcludingAsyncReader.self,
responseConcludingAsyncWriterType: ResponseConcludingAsyncWriter.Type = ResponseConcludingAsyncWriter.self
) {
}

func intercept(
input: Input,
next: (NextInput) async throws -> Void
input: consuming Input,
next: (consuming NextInput) async throws -> Void
) async throws {
try await input.2(HTTPResponse(status: .accepted)).produceAndConclude { responseBodyAsyncWriter in
var responseBodyAsyncWriter = responseBodyAsyncWriter
_ = try await input.1.consumeAndConclude { bodyAsyncReader in
var shouldContinue = true
while shouldContinue {
try await bodyAsyncReader.read { span in
guard let span else {
shouldContinue = false
return
try await input.withContents { request, requestReader, responseSender in
var maybeReader = Optional(requestReader)
try await responseSender.sendResponse(HTTPResponse(status: .accepted))
.produceAndConclude { responseBodyAsyncWriter in
var responseBodyAsyncWriter = responseBodyAsyncWriter
if let reader = maybeReader.take() {
_ = try await reader.consumeAndConclude { bodyAsyncReader in
var shouldContinue = true
var bodyAsyncReader = bodyAsyncReader
while shouldContinue {
try await bodyAsyncReader.read { span in
guard let span else {
shouldContinue = false
return
}
try await responseBodyAsyncWriter.write(span)
}
}
}
try await responseBodyAsyncWriter.write(span)
return HTTPFields(dictionaryLiteral: (HTTPField.Name.acceptEncoding, "encoding"))
} else {
fatalError("Closure run more than once")
}
}
}
return HTTPFields(dictionaryLiteral: (HTTPField.Name.acceptEncoding, "encoding"))
}
}
}
Loading
Loading