1. 概述
在本教程中,我們將演示如何自定義 Spring 的 WebClient —— 一個響應式 HTTP 客户端 —— 以進行請求和響應的日誌記錄。
2. WebClient
WebClient 是一個基於 Spring WebFlux 的反應式、非阻塞的 HTTP 請求接口。它具有功能型、流暢的 API 以及反應式類型,用於聲明式組合。
在幕後,WebClient 調用一個 HTTP 客户端。Reactor Netty 是默認的反應式 HttpClient,Jetty 的 HttpClient 也得到支持。 此外,通過設置 ClientConnector,還可以插入其他 HTTP 客户端實現,用於 WebClient。
3. 記錄請求和響應
<em>WebClient</em> 默認使用的 <em>HttpClient</em> 是 Netty 實現,因此 <strong>在將reactor.netty.http.client的日誌級別設置為DEBUG後,我們可以看到一些請求日誌。但是,如果需要自定義日誌,可以通過WebClient#filters` 配置我們的日誌器:
WebClient
.builder()
.filters(exchangeFilterFunctions -> {
exchangeFilterFunctions.add(logRequest());
exchangeFilterFunctions.add(logResponse());
})
.build()在代碼片段中,我們添加了兩個獨立的過濾器,用於記錄請求和響應。
讓我們通過使用 ExchangeFilterFunction#ofRequestProcessor 實現 logRequest:
ExchangeFilterFunction logRequest() {
return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
if (log.isDebugEnabled()) {
StringBuilder sb = new StringBuilder("Request: \n");
//append clientRequest method and url
clientRequest
.headers()
.forEach((name, values) -> values.forEach(value -> /* append header key/value */));
log.debug(sb.toString());
}
return Mono.just(clientRequest);
});
}logResponse 與其保持不變,但我們需要使用 ExchangeFilterFunction#ofResponseProcessor 代替。
現在,我們可以將 reactor.netty.http.client 的日誌級別設置為 INFO 或 ERROR,以獲得更清晰的輸出。
4. 記錄請求和響應內容
HTTP 客户端具有記錄請求和響應內容的功能。因此,為了實現這一目標,我們將使用具有日誌功能的 HTTP 客户端,並結合我們的 WebClient。
我們可以通過手動設置 WebClient.Builder# clientConnector 來完成,下面以 Jetty 和 Netty HTTP 客户端為例。
4.1. 使用 Jetty HttpClient 進行日誌記錄
首先,將 Maven 依賴項 jetty-reactive-httpclient 添加到我們的 pom 文件中:
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-reactive-httpclient</artifactId>
<version>1.1.6</version>
</dependency>然後,我們將創建一個定製的 Jetty HttpClient:
SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
HttpClient httpClient = new HttpClient(sslContextFactory) {
@Override
public Request newRequest(URI uri) {
Request request = super.newRequest(uri);
return enhance(request);
}
};在這裏,我們覆蓋了 HttpClient#newRequest 方法,然後將 Request 對象包裹在一個日誌增強器中。
接下來,我們需要註冊請求事件,以便在每個請求部分可用時進行日誌記錄:
Request enhance(Request request) {
StringBuilder group = new StringBuilder();
request.onRequestBegin(theRequest -> {
// append request url and method to group
});
request.onRequestHeaders(theRequest -> {
for (HttpField header : theRequest.getHeaders()) {
// append request headers to group
}
});
request.onRequestContent((theRequest, content) -> {
// append content to group
});
request.onRequestSuccess(theRequest -> {
log.debug(group.toString());
group.delete(0, group.length());
});
group.append("\n");
request.onResponseBegin(theResponse -> {
// append response status to group
});
request.onResponseHeaders(theResponse -> {
for (HttpField header : theResponse.getHeaders()) {
// append response headers to group
}
});
request.onResponseContent((theResponse, content) -> {
// append content to group
});
request.onResponseSuccess(theResponse -> {
log.debug(group.toString());
});
return request;
}最後,我們需要構建 WebClient 實例:
WebClient
.builder()
.clientConnector(new JettyClientHttpConnector(httpClient))
.build()當然,正如我們之前所做的那樣,我們需要將 RequestLogEnhancer 的日誌級別設置為 DEBUG。
4.2. 使用 Netty 進行日誌記錄 HttpClient
首先,讓我們創建一個 Netty HttpClient:
HttpClient httpClient = HttpClient
.create()
.wiretap(true)啓用監聽後,每個請求和響應都會以詳細信息進行記錄。
接下來,我們需要將 Netty 客户端包 reactor.netty.http.client 的日誌級別設置為 DEBUG:
logging.level.reactor.netty.http.client=DEBUG現在,讓我們來構建 WebClient:
WebClient
.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build()我們的 WebClient 會記錄每個請求和響應的詳細信息,但 Netty 內置日誌記錄器的默認格式同時包含請求和響應體的數據的十六進制和文本表示,並且包含大量關於請求和響應事件的數據。
因此,如果我們只需要 Netty 的文本日誌記錄器,我們可以配置 HttpClient:
HttpClient httpClient = HttpClient
.create()
.wiretap("reactor.netty.http.client.HttpClient",
LogLevel.DEBUG, AdvancedByteBufFormat.TEXTUAL);5. 結論
在本教程中,我們使用了多種技術來記錄請求和響應數據,同時使用 Spring WebClient。