Protocol
ServerMiddleware
A type that intercepts HTTP requests and responses.
protocol ServerMiddleware : Sendable
Overview
It allows you to customize the request after it was provided by the transport, but before it was parsed, validated, and provided to the request handler; and the response after it was provided by the request handler, but before it was handed back to the transport.
Appropriate for verifying authentication, performing logging, metrics, tracing, injecting custom headers such as “user-agent”, and more.
Choose between a transport and a middleware
The ServerTransport and ServerMiddleware protocols look similar, however each serves a different purpose.
A transport abstracts over the underlying HTTP library that actually receives the HTTP requests from the network. An implemented handler (a type implemented by you that conforms to the generated APIProtocol protocol) is generally configured with exactly one server transport.
A middleware intercepts the HTTP request and response, without being responsible for receiving the HTTP operations itself. That’s why middlewares take the extra next parameter, to delegate calling the handler to the transport at the top of the middleware stack.
Use an existing server middleware
Instantiate the middleware using the parameters required by the specific implementation. For example, using a hypothetical existing middleware that logs every request and response:
let loggingMiddleware = LoggingMiddleware()
Similarly to the process of using an existing ServerTransport, provide the middleware to the call to register handlers:
try handler.registerHandlers(
on: transport,
serverURL: URL(string: "/api/v1")!,
middlewares: [
loggingMiddleware,
]
)
Then when an HTTP request is received, the server first invokes the middlewares in the order you provided them, and then passes the parsed request to your handler. When a response is received from the handler, the last middleware handles the response first, and it goes back in the reverse order of the middlewares array. At the end, the transport sends the final response back to the client.
Implement a custom server middleware
If a server middleware implementation with your desired behavior doesn’t yet exist, or you need to simulate rare requests in your tests, consider implementing a custom server middleware.
For example, an implementation a middleware that prints only basic information about the incoming request and outgoing response:
/// A middleware that prints request and response metadata.
struct PrintingMiddleware: ServerMiddleware {
func intercept(
_ request: HTTPRequest,
body: HTTPBody?,
metadata: ServerRequestMetadata,
operationID: String,
next: (HTTPRequest, HTTPBody?, ServerRequestMetadata) async throws -> (HTTPResponse, HTTPBody?)
) async throws -> (HTTPResponse, HTTPBody?) {
print(">>>: \(request.method.rawValue) \(request.soar_pathOnly)")
do {
let (response, responseBody) = try await next(request, body, metadata)
print("<<<: \(response.status.code)")
return (response, responseBody)
} catch {
print("!!!: \(error)")
throw error
}
}
}
Implementing a test server middleware is just one way to help test your code that integrates with your handler. Another is to implement a type conforming to the generated protocol APIProtocol, and to implement a custom ServerTransport.
Topics
Instance Methods
Relationships
Conforming Types