Protocol
ClientMiddleware
A type that intercepts HTTP requests and responses.
protocol ClientMiddleware : Sendable
Overview
It allows you to read and modify the request before it is received by the transport and the response after it is returned by the transport.
Appropriate for handling authentication, logging, metrics, tracing, injecting custom headers such as “user-agent”, and more.
Choose between a transport and a middleware
The ClientTransport and ClientMiddleware protocols look similar, however each serves a different purpose.
A transport abstracts over the underlying HTTP library that actually performs the HTTP operation by using the network. A generated Client requires an exactly one client transport.
A middleware intercepts the HTTP request and response, without being responsible for performing the HTTP operation itself. That’s why middlewares take the extra next parameter, to delegate making the HTTP call to the transport at the top of the middleware stack.
Use an existing client 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 ClientTransport, provide the middleware to the initializer of the generated Client type:
let client = Client(
serverURL: URL(string: "https://example.com")!,
transport: transport,
middlewares: [
loggingMiddleware,
]
)
Then make a call to one of the generated client methods:
let response = try await client.checkHealth()
As part of the invocation of checkHealth, the client first invokes the middlewares in the order you provided them, and then passes the request to the transport. When a response is received, the last middleware handles it first, in the reverse order of the middlewares array.
Implement a custom client middleware
If a client middleware implementation with your desired behavior doesn’t yet exist, or you need to simulate rare network conditions your tests, consider implementing a custom client middleware.
For example, to implement a middleware that injects the “Authorization” header to every outgoing request, define a new struct that conforms to the ClientMiddleware protocol:
/// Injects an authorization header to every request.
struct AuthenticationMiddleware: ClientMiddleware {
/// The token value.
var bearerToken: String
func intercept(
_ request: HTTPRequest,
body: HTTPBody?,
baseURL: URL,
operationID: String,
next: (HTTPRequest, HTTPBody?, URL) async throws -> (HTTPResponse, HTTPBody?)
) async throws -> (HTTPResponse, HTTPBody?) {
var request = request
request.headerFields[.authorization] = "Bearer \(bearerToken)"
return try await next(request, body, baseURL)
}
}
An alternative use case for a middleware is to inject random failures when calling a real server, to test your retry and error-handling logic.
Implementing a test client middleware is just one way to help test your code that integrates with a generated client. Another is to implement a type conforming to the generated protocol APIProtocol, and to implement a custom ClientTransport.
Topics
Instance Methods