在 .NET 要呼叫 REST API 時,最常使用的就屬 HttpClient 類別莫屬,但有時候真的想看他到底送出了什麼 HTTP 封包,或是收到了什麼 HTTP 回應 (原始內容),這時候就需要一個方法來攔截並記錄實際 HTTP 傳送的 Request 與 Response 封包內容。其實是有方法的,這篇文章就來介紹如何實作。
使用 DelegatingHandler
類別
在 .NET 中,我們可以使用 DelegatingHandler
類別來攔截 HttpClient
發出的所有 HTTP Request 與 Response 資料,這個類別是一個抽象類別,我們可以繼承這個類別並實作 SendAsync
方法,這個方法會在每次 HttpClient
發出 Request 時被呼叫,因此我們可以在這個方法中攔截 Request 與 Response 的內容。
以下是一個完整的實作範例:
public class DebugHttpMessageHandler : DelegatingHandler
{
public DebugHttpMessageHandler() : base(new HttpClientHandler())
{
}
public DebugHttpMessageHandler(HttpMessageHandler handler) : base(handler)
{
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// Log request method and URL
Console.WriteLine("Request: {0} {1}", request.Method, request.RequestUri);
// Log headers
foreach (var header in request.Headers)
{
Console.WriteLine("Default Headers: {0}: {1}", header.Key, string.Join(", ", header.Value));
}
foreach (var header in request.Content.Headers)
{
Console.WriteLine("Content Headers: {0}: {1}", header.Key, string.Join(", ", header.Value));
}
// Show request payload
if (request.Content != null)
{
string payload = await request.Content.ReadAsStringAsync();
Console.WriteLine("Payload: {0}", payload);
}
Console.WriteLine();
// Send the request and get the response
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
// Log response status
Console.WriteLine("Response: {0} ({1})", response.StatusCode, ((int)response.StatusCode));
// Log headers
foreach (var header in response.Headers)
{
Console.WriteLine("Headers: {0}: {1}", header.Key, string.Join(", ", header.Value));
}
string jsonText = await response.Content.ReadAsStringAsync();
Console.WriteLine("Response Body:\n{0}", jsonText);
return response;
}
}
使用範例:以 Console 應用程式為例
要使用上述 DebugHttpMessageHandler
類別,只要在建立 HttpClient
時指定 DebugHttpMessageHandler
類別即可,但要記得傳入一個 HttpClientHandler
實例,以下是一個使用範例:
// v1
// HttpClientHandler handler = new HttpClientHandler {
// UseDefaultCredentials = true,
// PreAuthenticate = true
// };
//HttpClient client = new HttpClient(new DebugHttpMessageHandler(handler));
// v2
//HttpClient client = new HttpClient(new DebugHttpMessageHandler(new HttpClientHandler()));
// v3
HttpClient client = new HttpClient(new DebugHttpMessageHandler());
client.DefaultRequestHeaders.UserAgent.ParseAdd("Duotify/1.0");
HttpResponseMessage response = await client.GetAsync("https://postman-echo.com/get");
string jsonText = await response.Content.ReadAsStringAsync();
這段程式在執行時,會在 Console 輸出類似以下的訊息:
Request: GET https://postman-echo.com/get
Headers: User-Agent: Duotify/1.0
Response: OK (200)
Headers: Date: Fri, 26 Apr 2024 15:58:41 GMT
Headers: Connection: keep-alive
Headers: ETag: W/"111-PmkfQe2oac4nQoCIljhYp5s+hVo"
Headers: Set-Cookie: sails.sid=s%3ASV_p307ADVhZpm0bAG0SVPu3HUrQbLHK.a0oT6kjU3jmleYeL5Fd5OJ1R7gIwLm6LnFz8O6NkmOw; Path=/; HttpOnly
Response Body:
{
"args": {},
"headers": {
"x-forwarded-proto": "https",
"x-forwarded-port": "443",
"host": "postman-echo.com",
"x-amzn-trace-id": "Root=1-662bcf31-6f8cc63d23fe53441c7c8d3d",
"user-agent": "Duotify/1.0"
},
"url": "https://postman-echo.com/get"
}
這樣就可以在程式執行時,看到 HttpClient
發出的所有 HTTP Request 與 Response 資料了,是不是相當方便!😊
使用範例:以 ASP.NET Core 應用程式為例
在 ASP.NET Core 裡面設定稍微有點不太一樣,因為要搭配 ASP.NET Core 的 DI 進行實作:
public class DebugHttpMessageHandler : DelegatingHandler
{
private ILogger<DebugHttpMessageHandler> _logger;
public DebugHttpMessageHandler(ILogger<DebugHttpMessageHandler> logger) : base(new HttpClientHandler())
{
this._logger = logger;
}
public DebugHttpMessageHandler(HttpMessageHandler handler, ILogger<DebugHttpMessageHandler> logger) : base(handler)
{
this._logger = logger;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// Log request method and URL
_logger.LogInformation("Request: {0} {1}", request.Method, request.RequestUri);
// Log headers
foreach (var header in request.Headers)
{
Console.WriteLine("Default Headers: {0}: {1}", header.Key, string.Join(", ", header.Value));
}
foreach (var header in request.Content.Headers)
{
Console.WriteLine("Content Headers: {0}: {1}", header.Key, string.Join(", ", header.Value));
}
// Show request payload
if (request.Content != null)
{
string payload = await request.Content.ReadAsStringAsync();
_logger.LogInformation("Payload: {0}", payload);
}
//_logger.LogInformation();
// Send the request and get the response
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
// Log response status
_logger.LogInformation("Response: {0} ({1})", response.StatusCode, ((int)response.StatusCode));
// Log headers
foreach (var header in response.Headers)
{
_logger.LogInformation("Headers: {0}: {1}", header.Key, string.Join(", ", header.Value));
}
string jsonText = await response.Content.ReadAsStringAsync();
_logger.LogInformation("Response Body:\n{0}", jsonText);
return response;
}
}
然後在 Program.cs
中註冊 DebugHttpMessageHandler
與設定 IHttpClientFactory
:
// 一定要設定 Scoped,否則會有問題
builder.Services.AddScoped<DebugHttpMessageHandler>();
builder.Services.AddHttpClient("Debug")
.ConfigurePrimaryHttpMessageHandler<DebugHttpMessageHandler>();
最後在 Controller 中使用 IHttpClientFactory
來取得 HttpClient
實例:
private HttpClient http;
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger, IHttpClientFactory hf)
{
http = hf.CreateClient("Debug");
_logger = logger;
}
總結
這篇文章介紹了如何使用 DelegatingHandler
類別來攔截 HttpClient
發出的所有 HTTP Request 與 Response 資料,這個方法可以讓我們在開發時更容易了解 HttpClient
的實際傳輸內容,這對於除錯與開發都是相當有幫助的。
實務上,其實 DelegatingHandler
的應用還蠻廣的,針對一些攔截或追蹤的目的,都可以非常方便的透過這個方法來實作。例如:你可以透過自訂的 HttpMessageHandler
來實作一個簡單的 Retry Policy、實作一個簡單的 Cache Policy、自動加入 Authentication
或其他自訂的 HTTP Header 等等,都是非常方便的。
相關連結