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或工厂中设置代理
{
//IoC
var 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 中直接设置代理
- 如何在依赖注入系统中配置全局代理
- 如何通过环境变量设置代理
- 如何验证代理是否生效
- 实际使用中的注意事项
- 动态选择及证书信任