You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
HttpClientStudy/Docs/1.3.6.基础使用.使用代理.ipynb

623 lines
18 KiB
Plaintext

12 months ago
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
}
},
"source": [
"# HttpClient 使用代理功能"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"实际开发中HttpClient 通过代理访问目标服务器是常见的需求。\n",
"\n",
"本文将全面介绍如何在 .NET 中配置 HttpClient 使用代理Proxy功能包括基础使用方式、代码示例、以及与依赖注入结合的最佳实践。\n",
"\n",
"> 注意:运行代码之前,先开启`Fiddler Classic`及其代理功能,充当代理服务器。"
12 months ago
]
},
{
"cell_type": "markdown",
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"source": [
"## 初始化"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 开启`Fiddler Classic`及其代理功能,充当代理服务器"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"![代理服务器](./Assets/HttpClient-代理服务器设置.jpg)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 导入初始终化笔记文件,并且执行一次"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [
{
"data": {
"text/markdown": [
"## 初始化\n",
"这是全局共用文件包括Nuget包引用、全局类库引用、全局文件引用、全局命名空间引用、全局变量、全局方法、全局类定义等功能。\n",
"\n",
"在业务笔记中引用,执行其它单元格之前先执行一次。"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"<div><div></div><div></div><div><strong>Installed Packages</strong><ul><li><span>Microsoft.Extensions.DependencyInjection, 9.0.5</span></li><li><span>Microsoft.Extensions.Http, 9.0.5</span></li><li><span>Microsoft.Extensions.Http.Polly, 9.0.5</span></li><li><span>Microsoft.Extensions.Logging, 9.0.5</span></li><li><span>Microsoft.Extensions.Logging.Console, 9.0.5</span></li><li><span>Microsoft.Net.Http.Headers, 9.0.5</span></li><li><span>Polly, 8.5.2</span></li><li><span>Refit, 8.0.0</span></li><li><span>Refit.HttpClientFactory, 8.0.0</span></li><li><span>System.Net.Http.Json, 9.0.5</span></li></ul></div></div>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"配置文件根目录c:\\Users\\ruyu\\Desktop\\HttpClientStudy\\Docs\\Publish\\HttpClientStudy.Core\n",
"配置文件根目录c:\\Users\\ruyu\\Desktop\\HttpClientStudy\\Docs\\Publish\\HttpClientStudy.Core\n",
"启动WebApi项目...\n",
"程序[c:\\Users\\ruyu\\Desktop\\HttpClientStudy\\Docs\\Publish\\HttpClientStudy.WebApp\\HttpClientStudy.WebApp.exe]已在新的命令行窗口执行。如果未出现新命令行窗口,可能是程序错误造成窗口闪现!\n",
"已启动WebApi项目,保持窗口打开状态!\n",
"初始化完成!\n"
]
}
],
"source": [
"#!import \"./Ini.ipynb\"\n",
"\n",
"//共享变量\n",
"var fiddlerProxyAddress = \"127.0.0.1:8888\";\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 🧩 什么是代理?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"代理Proxy是一种中间服务器用于转发客户端请求到目标服务器。它常用于以下目的\n",
"\n",
"- 访问受限资源:企业内网中,通过代理服务器访问外部资源;\n",
"- 提高安全性和隐私保护:代理可隐藏真实 IP 地址,保护目标服务器的隐私和数据安全;\n",
"- 提高性能:代理可使用请求缓存和负载均衡等,减少目标服务器的压力,提高性能;\n",
"- 方便调试、测试\n",
" - 代理服务器可记录请求和响应信息,方便调试和测试;\n",
" - `Fiddler Classic`等软件,默认是抓不到 HttpClient 的请求的,需要将其设置为代理服务器,才能抓取到 HttpClient 的请求;\n",
"\n",
"在 .NET HttpClient 中,可以通过多种方式来设置代理服务器。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 🛠️ 设置 HttpClient 代理"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### ✅ 基本方式使用(无用户名密码)"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"响应状态OK\r\n"
]
}
],
"source": [
"{ \n",
" // 设置 SocketsHttpHandler 使用代理\n",
" var handler = new SocketsHttpHandler()\n",
" {\n",
" UseProxy = true,\n",
" Proxy = new WebProxy(fiddlerProxyAddress),\n",
" };\n",
"\n",
" // 创建 HttpClient并且请求\n",
" using (var client = new HttpClient(handler))\n",
" {\n",
" var response = await client.GetAsync(\"https://www.baidu.com\");\n",
" \n",
" Console.WriteLine($\"响应状态:{response.StatusCode}\");\n",
" }\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"执行上面的单元格应该在fiddler classic 中,抓到请求包:可以查看和管理详细信息.\n",
"![Fillder 抓包](./Assets/HttpClient-代理-抓包.jpg)\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### ✅ 带用户名和密码的代理"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"响应状态OK\r\n"
]
}
],
"source": [
"{ \n",
" // 设置 SocketsHttpHandler 使用代理\n",
" var handler = new SocketsHttpHandler()\n",
" {\n",
" UseProxy = true,\n",
" Proxy = new WebProxy(fiddlerProxyAddress)\n",
" {\n",
" //正式项目:机密数据一定要脱敏处理或者使用环境变量、机密管理器等手段\n",
" //因为Fiddler代理服务器没有用户凭据要求所以此处随意填写的。需要的话真实填写正确的用户凭据。\n",
" Credentials = new NetworkCredential(\"username\", \"password\"),\n",
" },\n",
" };\n",
"\n",
" // 创建 HttpClient并且请求\n",
" using (var client = new HttpClient(handler))\n",
" {\n",
" var response = await client.GetAsync(\"https://www.baidu.com\");\n",
" Console.WriteLine($\"响应状态:{response.StatusCode}\");\n",
" }\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"source": [
"### 📦 在IoC和工厂中使用 Proxy [推荐方式]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"在 ASP.NET Core 或基于 IServiceCollection 的项目中,可以通过 UseSocketsHttpHandler 扩展方法,统一管理代理服务器配置。\n",
"\n",
"还可以根据客户端的命名不同,进行不同的代理服务器配置!"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"正常请求,响应内容为: Pong\r\n"
]
}
],
"source": [
"//IoC或工厂中设置代理\n",
"{\n",
" //IoC\n",
" var services = new ServiceCollection();\n",
"\n",
" //默认命名客户端\n",
" services\n",
" .AddHttpClient<HttpClient>(string.Empty)\n",
" .ConfigureHttpClient(client => \n",
" {\n",
" client.BaseAddress = new Uri(webApiBaseUrl);\n",
" client.Timeout = TimeSpan.FromSeconds(10);\n",
" })\n",
"\n",
" //配置代理服务器\n",
" .UseSocketsHttpHandler(handlerBuilder =>\n",
" {\n",
" handlerBuilder.Configure((handler,s) => \n",
" {\n",
" handler.Proxy = new WebProxy(fiddlerProxyAddress);\n",
" }); \n",
" });\n",
"\n",
" //发送请求\n",
" var factory = services.BuildServiceProvider().GetRequiredService<IHttpClientFactory>();\n",
"\n",
" //正常请求\n",
" var defaultClient = factory.CreateClient();\n",
" var defaultContent = await defaultClient.GetStringAsync(\"api/hello/ping\");\n",
" Console.WriteLine($\"正常请求,响应内容为: {defaultContent}\");\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 🔄 动态切换代理服务器\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"要根据请求的不同(请求地址、请求方法、请求头、请求参数等)动态选择使用一同的代理服务器可以使用Pipeline中间件来管理。"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"LoggerDelegatingHandler -> SendAsync -> Before\n",
"ProxySelectorLastHandler -> SendAsync -> Before\n",
"ProxySelectorLastHandler -> SendAsync -> After\n",
"LoggerDelegatingHandler -> SendAsync -> After\n",
"OK\n",
"---------------------------------------------------\n",
"ProxySelectorLastHandler -> SendAsync -> Before\n",
"ProxySelectorLastHandler -> SendAsync -> After\n",
"OK\n"
]
}
],
"source": [
"///<summary>\n",
"/// 代理服务选择器中间件\n",
"/// 注意:此中间件会短路,必须设置为最后一个中间件\n",
"///</summary>\n",
"public class ProxySelectorLastHandler : DelegatingHandler\n",
"{\n",
" /// <summary>\n",
" /// 拦截请求,并动态设置代理\n",
" /// 注意:会短路其它中间件,要放最后\n",
" /// </summary>\n",
" protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken ct)\n",
" {\n",
" Console.WriteLine(\"ProxySelectorLastHandler -> SendAsync -> Before\");\n",
" //动态选择示例\n",
" var proxy = request.RequestUri.Host switch\n",
" {\n",
" string url when url.Contains(\"baidu\") => new WebProxy(\"127.0.0.1:8888\"),\n",
" string url when url.Contains(\"qq\") => new WebProxy(\"127.0.0.1:8888\"),\n",
" _ => null\n",
" };\n",
"\n",
" InnerHandler = new SocketsHttpHandler\n",
" {\n",
" Proxy = proxy,\n",
" UseProxy = proxy != null\n",
" };\n",
"\n",
"\n",
" //请求\n",
" HttpResponseMessage response = await base.SendAsync(request, ct);\n",
"\n",
" Console.WriteLine(\"ProxySelectorLastHandler -> SendAsync -> After\");\n",
"\n",
" return response;\n",
" }\n",
"}\n",
"\n",
"//日志中间件(管道类)\n",
"public class LoggerDelegatingHandler : DelegatingHandler\n",
"{\n",
" protected override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken)\n",
" {\n",
" Console.WriteLine(\"LoggerDelegatingHandler -> Send -> Before\");\n",
"\n",
" HttpResponseMessage response = base.Send(request, cancellationToken);\n",
"\n",
" Console.WriteLine(\"LoggerDelegatingHandler -> Send -> After\");\n",
"\n",
" return response;\n",
" }\n",
"\n",
" protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)\n",
" {\n",
" Console.WriteLine(\"LoggerDelegatingHandler -> SendAsync -> Before\");\n",
"\n",
" HttpResponseMessage response = await base.SendAsync(request, cancellationToken);\n",
"\n",
" Console.WriteLine(\"LoggerDelegatingHandler -> SendAsync -> After\");\n",
"\n",
" return response;\n",
" }\n",
"}\n",
"\n",
"//使用ProxySelectorLastHandler必须设置为最后一个中间件\n",
"{\n",
" var handlerLink = new LoggerDelegatingHandler()\n",
" {\n",
" InnerHandler = new ProxySelectorLastHandler(),\n",
" };\n",
"\n",
" var myClient = new HttpClient(handlerLink);\n",
" var response = await myClient.GetAsync(\"https://www.qq.com\");\n",
"\n",
" Console.WriteLine(response.StatusCode);\n",
" Console.WriteLine(\"---------------------------------------------------\");\n",
"}\n",
"\n",
"\n",
"// ProxySelectorLastHandler 不是最后一个的话,其它中间件无效(被短路)\n",
"{\n",
" var handlerLink = new ProxySelectorLastHandler()\n",
" {\n",
" InnerHandler = new LoggerDelegatingHandler (),\n",
" };\n",
"\n",
" var myClient = new HttpClient(handlerLink);\n",
" var response = await myClient.GetAsync(\"https://www.qq.com\");\n",
"\n",
" Console.WriteLine(response.StatusCode);\n",
"}\n",
"\n",
"//注意看输出:后面的没有日志中间件的任何输出"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 🔐 HTTPS 代理信任问题"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"当使用 HTTPS 代理时,可能会遇到 SSL/TLS 证书不被信任的问题,尤其是在测试环境中使用自签名证书。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### ✅解决方法一:忽略证书验证(⚠️ 注意:仅用于开发环境)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"OK\r\n"
]
}
],
"source": [
"var handler = new HttpClientHandler\n",
"{\n",
" Proxy = new WebProxy(fiddlerProxyAddress),\n",
" UseProxy = true,\n",
" ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true\n",
"};\n",
"\n",
"using (var client = new HttpClient(handler))\n",
"{ \n",
" var response = await client.GetAsync(\"https://www.baidu.com\");\n",
" Console.WriteLine(response.StatusCode);\n",
"};\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### ✅ 解决方法二:手动添加根证书信任"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"OK\r\n"
]
}
],
"source": [
"using System.Security;\n",
"using System.Security.Cryptography;\n",
"using System.Security.Cryptography.X509Certificates;\n",
"\n",
"var file = Environment.CurrentDirectory + \"\\\\Assets\\\\FiddlerRoot.cer\";\n",
"//Console.WriteLine(file);\n",
"var rootCert = System.Security.Cryptography.X509Certificates.X509CertificateLoader.LoadCertificateFromFile(file);\n",
"\n",
"var handler = new HttpClientHandler\n",
"{\n",
" Proxy = new WebProxy(fiddlerProxyAddress),\n",
" UseProxy = true,\n",
" ServerCertificateCustomValidationCallback = (request, cert, chain, errors) =>\n",
" {\n",
" //return true;\n",
" return chain.ChainElements.Any(x => x.Certificate.Thumbprint == cert.Thumbprint); // 验证证书链包含指定根证书\n",
" }\n",
"};\n",
"\n",
"using (var client = new HttpClient(handler))\n",
"{ \n",
" var response = await client.GetAsync(\"https://www.baidu.com\");\n",
" Console.WriteLine(response.StatusCode);\n",
"};"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 📌 总结"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"通过本文,你应该掌握了以下内容:\n",
"\n",
"+ 如何在 HttpClient 中直接设置代理\n",
"+ 如何在依赖注入系统中配置全局代理\n",
"+ 如何通过环境变量设置代理\n",
"+ 如何验证代理是否生效\n",
"+ 实际使用中的注意事项\n",
"+ 动态选择及证书信任"
12 months ago
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".NET (C#)",
"language": "C#",
"name": ".net-csharp"
},
"language_info": {
"name": "python"
},
"polyglot_notebook": {
"kernelInfo": {
"defaultKernelName": "csharp",
"items": [
{
"aliases": [],
"name": "csharp"
}
]
}
}
},
"nbformat": 4,
"nbformat_minor": 2
}