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

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

{
"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`及其代理功能,充当代理服务器。"
]
},
{
"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",
"+ 动态选择及证书信任"
]
}
],
"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
}