HttpClient 使用代理功能
實際開發中,HttpClient 通過代理訪問目標服務器是常見的需求。
本文將全面介紹如何在 .NET 中配置 HttpClient 使用代理(Proxy)功能,包括基礎使用方式、代碼示例、以及與依賴注入結合的最佳實踐。
注意:運行代碼之前,先開啟
Fiddler Classic
及其代理功能,充當代理服務器。
初始化
開啟Fiddler Classic
及其代理功能,充當代理服務器
導入初始終化筆記文件,并且執行一次
#!import "./Ini.ipynb"//共享變量
var fiddlerProxyAddress = "127.0.0.1:8888";
🧩 什么是代理?
代理(Proxy)是一種中間服務器,用于轉發客戶端請求到目標服務器。它常用于以下目的:
- 訪問受限資源:企業內網中,通過代理服務器訪問外部資源;
- 提高安全性和隱私保護:代理可隱藏真實 IP 地址,保護目標服務器的隱私和數據安全;
- 提高性能:代理可使用請求緩存和負載均衡等,減少目標服務器的壓力,提高性能;
- 方便調試、測試
- 代理服務器可記錄請求和響應信息,方便調試和測試;
Fiddler Classic
等軟件,默認是抓不到 HttpClient 的請求的,需要將其設置為代理服務器,才能抓取到 HttpClient 的請求;
在 .NET HttpClient 中,可以通過多種方式來設置代理服務器。
🛠? 設置 HttpClient 代理
? 基本方式使用(無用戶名密碼)
{ // 設置 SocketsHttpHandler 使用代理var handler = new SocketsHttpHandler(){UseProxy = true,Proxy = new WebProxy(fiddlerProxyAddress),};// 創建 HttpClient,并且請求using (var client = new HttpClient(handler)){var response = await client.GetAsync("https://www.baidu.com");Console.WriteLine($"響應狀態:{response.StatusCode}");}
}
執行上面的單元格,應該在fiddler classic 中,抓到請求包:可以查看和管理詳細信息.
? 帶用戶名和密碼的代理
{ // 設置 SocketsHttpHandler 使用代理var handler = new SocketsHttpHandler(){UseProxy = true,Proxy = new WebProxy(fiddlerProxyAddress){//正式項目:機密數據一定要脫敏處理或者使用環境變量、機密管理器等手段//因為Fiddler代理服務器,沒有用戶憑據要求,所以此處隨意填寫的。需要的話,真實填寫正確的用戶憑據。Credentials = new NetworkCredential("username", "password"),},};// 創建 HttpClient,并且請求using (var client = new HttpClient(handler)){var response = await client.GetAsync("https://www.baidu.com");Console.WriteLine($"響應狀態:{response.StatusCode}");}
}
📦 在IoC和工廠中使用 Proxy [推薦方式]
在 ASP.NET Core 或基于 IServiceCollection 的項目中,可以通過 UseSocketsHttpHandler 擴展方法,統一管理代理服務器配置。
還可以根據客戶端的命名不同,進行不同的代理服務器配置!
//IoC或工廠中設置代理
{//IoCvar services = new ServiceCollection();//默認命名客戶端services.AddHttpClient<HttpClient>(string.Empty).ConfigureHttpClient(client => {client.BaseAddress = new Uri(webApiBaseUrl);client.Timeout = TimeSpan.FromSeconds(10);})//配置代理服務器.UseSocketsHttpHandler(handlerBuilder =>{handlerBuilder.Configure((handler,s) => {handler.Proxy = new WebProxy(fiddlerProxyAddress);}); });//發送請求var factory = services.BuildServiceProvider().GetRequiredService<IHttpClientFactory>();//正常請求var defaultClient = factory.CreateClient();var defaultContent = await defaultClient.GetStringAsync("api/hello/ping");Console.WriteLine($"正常請求,響應內容為: {defaultContent}");
}
🔄 動態切換代理服務器
要根據請求的不同(請求地址、請求方法、請求頭、請求參數等),動態選擇使用一同的代理服務器,可以使用Pipeline中間件,來管理。
///<summary>
/// 代理服務選擇器中間件
/// 注意:此中間件會短路,必須設置為最后一個中間件
///</summary>
public class ProxySelectorLastHandler : DelegatingHandler
{/// <summary>/// 攔截請求,并動態設置代理/// 注意:會短路其它中間件,要放最后/// </summary>protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken ct){Console.WriteLine("ProxySelectorLastHandler -> SendAsync -> Before");//動態選擇示例var proxy = request.RequestUri.Host switch{string url when url.Contains("baidu") => new WebProxy("127.0.0.1:8888"),string url when url.Contains("qq") => new WebProxy("127.0.0.1:8888"),_ => null};InnerHandler = new SocketsHttpHandler{Proxy = proxy,UseProxy = proxy != null};//請求HttpResponseMessage response = await base.SendAsync(request, ct);Console.WriteLine("ProxySelectorLastHandler -> SendAsync -> After");return response;}
}//日志中間件(管道類)
public class LoggerDelegatingHandler : DelegatingHandler
{protected override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken){Console.WriteLine("LoggerDelegatingHandler -> Send -> Before");HttpResponseMessage response = base.Send(request, cancellationToken);Console.WriteLine("LoggerDelegatingHandler -> Send -> After");return response;}protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken){Console.WriteLine("LoggerDelegatingHandler -> SendAsync -> Before");HttpResponseMessage response = await base.SendAsync(request, cancellationToken);Console.WriteLine("LoggerDelegatingHandler -> SendAsync -> After");return response;}
}//使用:ProxySelectorLastHandler必須設置為最后一個中間件
{var handlerLink = new LoggerDelegatingHandler(){InnerHandler = new ProxySelectorLastHandler(),};var myClient = new HttpClient(handlerLink);var response = await myClient.GetAsync("https://www.qq.com");Console.WriteLine(response.StatusCode);Console.WriteLine("---------------------------------------------------");
}// ProxySelectorLastHandler 不是最后一個的話,其它中間件無效(被短路)
{var handlerLink = new ProxySelectorLastHandler(){InnerHandler = new LoggerDelegatingHandler (),};var myClient = new HttpClient(handlerLink);var response = await myClient.GetAsync("https://www.qq.com");Console.WriteLine(response.StatusCode);
}//注意看輸出:后面的沒有日志中間件的任何輸出
🔐 HTTPS 代理信任問題
當使用 HTTPS 代理時,可能會遇到 SSL/TLS 證書不被信任的問題,尤其是在測試環境中使用自簽名證書。
?解決方法一:忽略證書驗證(?? 注意:僅用于開發環境)
var handler = new HttpClientHandler
{Proxy = new WebProxy(fiddlerProxyAddress),UseProxy = true,ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true
};using (var client = new HttpClient(handler))
{ var response = await client.GetAsync("https://www.baidu.com");Console.WriteLine(response.StatusCode);
};
? 解決方法二:手動添加根證書信任
using System.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;var file = Environment.CurrentDirectory + "\\Assets\\FiddlerRoot.cer";
//Console.WriteLine(file);
var rootCert = System.Security.Cryptography.X509Certificates.X509CertificateLoader.LoadCertificateFromFile(file);var handler = new HttpClientHandler
{Proxy = new WebProxy(fiddlerProxyAddress),UseProxy = true,ServerCertificateCustomValidationCallback = (request, cert, chain, errors) =>{//return true;return chain.ChainElements.Any(x => x.Certificate.Thumbprint == cert.Thumbprint); // 驗證證書鏈包含指定根證書}
};using (var client = new HttpClient(handler))
{ var response = await client.GetAsync("https://www.baidu.com");Console.WriteLine(response.StatusCode);
};
📌 總結
通過本文,你應該掌握了以下內容:
- 如何在 HttpClient 中直接設置代理
- 如何在依賴注入系統中配置全局代理
- 如何通過環境變量設置代理
- 如何驗證代理是否生效
- 實際使用中的注意事項
- 動態選擇及證書信任