diff --git a/Docs/0.目录结构.ipynb b/Docs/0.目录结构.ipynb
index e71881a..6c4f041 100644
--- a/Docs/0.目录结构.ipynb
+++ b/Docs/0.目录结构.ipynb
@@ -91,8 +91,7 @@
"+ [使用管道](./1.4.4.高级使用.使用管道.ipynb)\n",
"+ [类型化客户端](./1.4.5.高级使用.类型化客户端.ipynb)\n",
"+ [工厂模式](./1.4.6.高级使用.工厂模式.ipynb)\n",
- "+ [Polly库](./1.4.7.高级使用.Polly.ipynb)\n",
- "+ [线程限流](./1.4.8.高级使用.线程限流.ipynb)"
+ "+ [Polly库](./1.4.7.高级使用.Polly.ipynb)"
]
},
{
diff --git a/Docs/1.3.6.基础使用.使用代理.ipynb b/Docs/1.3.6.基础使用.使用代理.ipynb
index 7656438..fa64b94 100644
--- a/Docs/1.3.6.基础使用.使用代理.ipynb
+++ b/Docs/1.3.6.基础使用.使用代理.ipynb
@@ -11,7 +11,18 @@
}
},
"source": [
- "# HttpClient 使用原则"
+ "# HttpClient 使用代理功能"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "实际开发中,HttpClient 通过代理访问目标服务器是常见的需求。\n",
+ "\n",
+ "本文将全面介绍如何在 .NET 中配置 HttpClient 使用代理(Proxy)功能,包括基础使用方式、代码示例、以及与依赖注入结合的最佳实践。\n",
+ "\n",
+ "> 注意:运行代码之前,先开启`Fiddler Classic`及其代理功能,充当代理服务器。"
]
},
{
@@ -28,7 +39,560 @@
}
},
"source": [
- "## 1、复用"
+ "## 初始化"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### 开启`Fiddler Classic`及其代理功能,充当代理服务器"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ ""
+ ]
+ },
+ {
+ "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": [
+ "
Installed Packages- Microsoft.Extensions.DependencyInjection, 9.0.5
- Microsoft.Extensions.Http, 9.0.5
- Microsoft.Extensions.Http.Polly, 9.0.5
- Microsoft.Extensions.Logging, 9.0.5
- Microsoft.Extensions.Logging.Console, 9.0.5
- Microsoft.Net.Http.Headers, 9.0.5
- Polly, 8.5.2
- Refit, 8.0.0
- Refit.HttpClientFactory, 8.0.0
- System.Net.Http.Json, 9.0.5
"
+ ]
+ },
+ "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",
+ "\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(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();\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": [
+ "///\n",
+ "/// 代理服务选择器中间件\n",
+ "/// 注意:此中间件会短路,必须设置为最后一个中间件\n",
+ "///\n",
+ "public class ProxySelectorLastHandler : DelegatingHandler\n",
+ "{\n",
+ " /// \n",
+ " /// 拦截请求,并动态设置代理\n",
+ " /// 注意:会短路其它中间件,要放最后\n",
+ " /// \n",
+ " protected override async Task 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 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",
+ "+ 动态选择及证书信任"
]
}
],
diff --git a/Docs/1.3.7.基础使用.使用Cookie.ipynb b/Docs/1.3.7.基础使用.使用Cookie.ipynb
index 7656438..f38ffff 100644
--- a/Docs/1.3.7.基础使用.使用Cookie.ipynb
+++ b/Docs/1.3.7.基础使用.使用Cookie.ipynb
@@ -1,58 +1,759 @@
{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {
- "dotnet_interactive": {
- "language": "csharp"
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "dotnet_interactive": {
+ "language": "csharp"
+ },
+ "polyglot_notebook": {
+ "kernelName": "csharp"
+ }
+ },
+ "source": [
+ "在 HttpClient 中使用 Cookie\n",
+ "============================="
+ ]
},
- "polyglot_notebook": {
- "kernelName": "csharp"
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Cookie 是服务器存储在客户端的小型数据片段,可用于身份验证、会话跟踪等。\n",
+ "\n",
+ ".Net HttpClient 支持 Cookie 功能,本教程详细介绍了Cookie 的管理与使用。"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 初始化"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 44,
+ "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": [
+ "Installed Packages- Microsoft.Extensions.DependencyInjection, 9.0.5
- Microsoft.Extensions.Http, 9.0.5
- Microsoft.Extensions.Http.Polly, 9.0.5
- Microsoft.Extensions.Logging, 9.0.5
- Microsoft.Extensions.Logging.Console, 9.0.5
- Microsoft.Net.Http.Headers, 9.0.5
- Polly, 8.5.2
- Refit, 8.0.0
- Refit.HttpClientFactory, 8.0.0
- System.Net.Http.Json, 9.0.5
"
+ ]
+ },
+ "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\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 什么是 Cookie"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "> Cookie 是服务器发送到用户浏览器并存储在本地的一小段数据,用来进行会话管理(如登录状态)、个性化设置(如主题偏好)、跟踪用户行为等功能。"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## HttClient 手动管理 Cookie"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "如果只在简单的使用Cookie,想保持简洁、灵活。可以手管理。"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### 发送带Cookie的请求"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "polyglot_notebook": {
+ "kernelName": "csharp"
+ },
+ "vscode": {
+ "languageId": "polyglot-notebook"
+ }
+ },
+ "source": [
+ "+ 不使用Cookie"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 45,
+ "metadata": {
+ "polyglot_notebook": {
+ "kernelName": "csharp"
+ },
+ "vscode": {
+ "languageId": "polyglot-notebook"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "{\"data\":\"\",\"code\":1,\"message\":\"没有Cookie\"}\r\n"
+ ]
+ }
+ ],
+ "source": [
+ "{ //不使用Cookie\n",
+ " using (var client = new HttpClient(){BaseAddress=new Uri(webApiBaseUrl)})\n",
+ " {\n",
+ " var response = await client.GetAsync(\"/api/Cookie/GetRequestCookie\"); \n",
+ "\n",
+ " //确保请求成功\n",
+ " response.EnsureSuccessStatusCode();\n",
+ "\n",
+ " //读取响应内容\n",
+ " var content = await response.Content.ReadAsStringAsync();\n",
+ "\n",
+ " //输出 响应内容\n",
+ " Console.WriteLine(content);\n",
+ " }\n",
+ "}"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "+ 手动设置Cookie: 在默认请求头中添加Cookie,适合快捷请求方法(Get,Post,Put,Delete等)\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 46,
+ "metadata": {
+ "polyglot_notebook": {
+ "kernelName": "csharp"
+ },
+ "vscode": {
+ "languageId": "polyglot-notebook"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "{\"data\":[{\"key\":\"Client\",\"value\":\"PolyglotNotebook\"},{\"key\":\"User\",\"value\":\"andy\"}],\"code\":1,\"message\":\"成功\"}\n",
+ "{\"data\":[{\"key\":\"Client\",\"value\":\"PolyglotNotebook\"},{\"key\":\"User\",\"value\":\"andy\"}],\"code\":1,\"message\":\"成功\"}\n"
+ ]
+ }
+ ],
+ "source": [
+ "{ //多次请求时,都自动携带默认请求头Cookie\n",
+ "\n",
+ " var client = new HttpClient()\n",
+ " {\n",
+ " BaseAddress = new Uri(webApiBaseUrl),\n",
+ " };\n",
+ "\n",
+ " //全局设置Cookie,所有快捷请求(Send方法的快捷方法:Get、Post、Put等)都会带上这个Cookie\n",
+ " //快捷方法,不能单独设置Cookie; 只有Send方法才可以单独设置Cookie\n",
+ " client.DefaultRequestHeaders.Add(\"Cookie\", \"Client=PolyglotNotebook,User=andy\");\n",
+ "\n",
+ " //请求1\n",
+ " var response = await client.GetAsync(\"/api/Cookie/GetRequestCookie\"); \n",
+ "\n",
+ " //确保请求成功\n",
+ " response.EnsureSuccessStatusCode();\n",
+ "\n",
+ " //读取响应内容\n",
+ " var content = await response.Content.ReadAsStringAsync();\n",
+ "\n",
+ " //输出 响应内容\n",
+ " Console.WriteLine(content);\n",
+ "\n",
+ " //再次快捷请求,不用重新设置\n",
+ " //请求2\n",
+ " var response2 = await client.GetAsync(\"/api/Cookie/GetRequestCookie\"); \n",
+ "\n",
+ " //确保请求成功\n",
+ " response2.EnsureSuccessStatusCode();\n",
+ "\n",
+ " //读取响应内容\n",
+ " var content2 = await response2.Content.ReadAsStringAsync();\n",
+ "\n",
+ " //输出 响应内容\n",
+ " Console.WriteLine(content2);\n",
+ "\n",
+ "}"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "+ 手动设置Cookie:每次请求设置 HttpRequestMessage,适合Send通用方法。当然可以合并默认请求头"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 47,
+ "metadata": {
+ "polyglot_notebook": {
+ "kernelName": "csharp"
+ },
+ "vscode": {
+ "languageId": "polyglot-notebook"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "{\"data\":[{\"key\":\"Password\",\"value\":\"MyPassword\"},{\"key\":\"Role\",\"value\":\"Admin\"},{\"key\":\"Client\",\"value\":\"PolyglotNotebook\"},{\"key\":\"User\",\"value\":\"andy\"}],\"code\":1,\"message\":\"成功\"}\n",
+ "响应头中没有Cookie\n"
+ ]
+ }
+ ],
+ "source": [
+ "{ //手动设置Cookie, 单次HttpRequestMessage合并默认请求头Cookie\n",
+ "\n",
+ " var client = new HttpClient()\n",
+ " {\n",
+ " BaseAddress = new Uri(webApiBaseUrl),\n",
+ " };\n",
+ "\n",
+ " //全局设置Cookie,所有请求都会带上这个Cookie\n",
+ " client.DefaultRequestHeaders.Add(\"Cookie\", \"Client=PolyglotNotebook,User=andy\");\n",
+ "\n",
+ " //单请求设置\n",
+ " var requestMessage = new HttpRequestMessage(HttpMethod.Get, \"/api/Cookie/GetRequestCookie\");\n",
+ " \n",
+ " //设置Cookie,会覆盖HttpClient设置的默认Cookie\n",
+ " requestMessage.Headers.Add(\"Cookie\", \"Password=MyPassword,Role=Admin\");\n",
+ " //添加默认\n",
+ " if(client.DefaultRequestHeaders.Contains(\"Cookie\"))\n",
+ " {\n",
+ " requestMessage.Headers.Add(\"Cookie\", client.DefaultRequestHeaders.GetValues(\"Cookie\"));\n",
+ " }\n",
+ "\n",
+ " var response = await client.SendAsync(requestMessage);\n",
+ " var content = await response.Content.ReadAsStringAsync();\n",
+ " Console.WriteLine(content);\n",
+ "\n",
+ " //响应头也会自动带上Cookie\n",
+ " if(response.Headers.Contains(\"cookie\"))\n",
+ " {\n",
+ " Console.Write($\"响应头中Cookie为:\");\n",
+ " var cookies = response.Headers.GetValues(\"cookie\");\n",
+ " foreach (var cookie in cookies)\n",
+ " {\n",
+ " Console.WriteLine($\"{cookie}\");\n",
+ " }\n",
+ " }\n",
+ " else\n",
+ " {\n",
+ " Console.WriteLine($\"响应头中没有Cookie\");\n",
+ " }\n",
+ "}"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### 查看响应头中的Cookie"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 48,
+ "metadata": {
+ "polyglot_notebook": {
+ "kernelName": "csharp"
+ },
+ "vscode": {
+ "languageId": "polyglot-notebook"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "响应头中没有Cookie\r\n"
+ ]
+ }
+ ],
+ "source": [
+ "{ //获取响应头中的Cookie\n",
+ "\n",
+ " var client = new HttpClient()\n",
+ " {\n",
+ " BaseAddress = new Uri(webApiBaseUrl),\n",
+ " };\n",
+ "\n",
+ " var response = await client.GetAsync(\"/api/Cookie/GetResponseCookie\");\n",
+ "\n",
+ " //获取响应头中的Cookie\n",
+ " if(response.Headers.Contains(\"cookie\"))\n",
+ " {\n",
+ " Console.Write($\"响应头中Cookie为:\");\n",
+ " var cookies = response.Headers.GetValues(\"cookie\");\n",
+ " foreach (var cookie in cookies)\n",
+ " {\n",
+ " Console.WriteLine($\"{cookie}\");\n",
+ " }\n",
+ " }\n",
+ " else\n",
+ " {\n",
+ " Console.WriteLine($\"响应头中没有Cookie\");\n",
+ " }\n",
+ "}"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## HttClient 使用 CookieContainer 自动管理 Cookie"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ ".NET 提供了 HttpClientHandler + CookieContainer 来自动管理 Cookie 生命周期和持久化。"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### 使用 CookieContainer"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 49,
+ "metadata": {
+ "polyglot_notebook": {
+ "kernelName": "csharp"
+ },
+ "vscode": {
+ "languageId": "polyglot-notebook"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "{\"data\":[{\"key\":\"ProjectName\",\"value\":\"WebApp\"},{\"key\":\"Version\",\"value\":\"Dotnet9\"}],\"code\":1,\"message\":\"成功\"}\r\n"
+ ]
+ }
+ ],
+ "source": [
+ "{\n",
+ " var handler = new HttpClientHandler()\n",
+ " {\n",
+ " UseCookies = true,\n",
+ " CookieContainer = new CookieContainer(),\n",
+ " };\n",
+ "\n",
+ " using var client = new HttpClient(handler)\n",
+ " {\n",
+ " BaseAddress = new Uri(webApiBaseUrl),\n",
+ " };\n",
+ "\n",
+ " // 第一次请求,服务端设置 Cookie\n",
+ " var response = await client.GetAsync(\"/api/Cookie/GetResponseCookie\");\n",
+ " response.EnsureSuccessStatusCode();\n",
+ "\n",
+ " // 第二次请求,自动携带之前设置的 Cookie\n",
+ " var response2 = await client.GetAsync(\"/api/Cookie/GetRequestCookie\");\n",
+ " var content2 = await response2.Content.ReadAsStringAsync();\n",
+ " Console.WriteLine(content2);\n",
+ "}"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Cookie 持久化(保存与恢复)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "若需要在程序重启后继续使用 Cookie,可将其序列化保存至文件或数据库。"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "polyglot_notebook": {
+ "kernelName": "csharp"
+ },
+ "vscode": {
+ "languageId": "polyglot-notebook"
+ }
+ },
+ "source": [
+ "+ 保存 Cookie 到文件"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 50,
+ "metadata": {
+ "polyglot_notebook": {
+ "kernelName": "csharp"
+ },
+ "vscode": {
+ "languageId": "polyglot-notebook"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "///\n",
+ "public void SaveCookies(CookieContainer container, string filePath)\n",
+ "{\n",
+ " using (var writer = new StreamWriter(filePath))\n",
+ " {\n",
+ " foreach (Cookie cookie in container.GetCookies(new Uri(webApiBaseUrl)))\n",
+ " {\n",
+ " writer.WriteLine($\"{cookie.Name}={cookie.Value};Domain={cookie.Domain};Path={cookie.Path};Expires={cookie.Expires}\");\n",
+ " }\n",
+ " }\n",
+ "}\n",
+ "\n",
+ "//应用\n",
+ "{\n",
+ " var handler = new HttpClientHandler()\n",
+ " {\n",
+ " UseCookies = true,\n",
+ " CookieContainer = new CookieContainer(),\n",
+ " };\n",
+ "\n",
+ " using (var client = new HttpClient(handler))\n",
+ " {\n",
+ " client.BaseAddress = new Uri(webApiBaseUrl);\n",
+ "\n",
+ " // 第一次请求,服务端设置 Cookie\n",
+ " var response = await client.GetAsync(\"/api/Cookie/GetResponseCookie\");\n",
+ " response.EnsureSuccessStatusCode();\n",
+ "\n",
+ " SaveCookies(handler.CookieContainer, \"cookies.txt\");\n",
+ " }\n",
+ "}"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "+ 从文件中加载 Cookie"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 51,
+ "metadata": {
+ "polyglot_notebook": {
+ "kernelName": "csharp"
+ },
+ "vscode": {
+ "languageId": "polyglot-notebook"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "{\"data\":[{\"key\":\"ProjectName\",\"value\":\"WebApp\"},{\"key\":\"Version\",\"value\":\"Dotnet9\"}],\"code\":1,\"message\":\"成功\"}\r\n"
+ ]
+ }
+ ],
+ "source": [
+ "//// \n",
+ "/// 从文件中加载 Cookie\n",
+ "/// \n",
+ "public CookieContainer LoadCookies(string filePath)\n",
+ "{\n",
+ " var container = new CookieContainer();\n",
+ " if (!File.Exists(filePath)) return container;\n",
+ "\n",
+ " foreach (var line in File.ReadAllLines(filePath))\n",
+ " {\n",
+ " var parts = line.Split(';');\n",
+ " var nameValue = parts[0].Split('=');\n",
+ " var cookie = new Cookie(nameValue[0], nameValue[1])\n",
+ " {\n",
+ " Domain = parts[1].Replace(\"Domain=\", \"\").Trim(),\n",
+ " Path = parts[2].Replace(\"Path=\", \"\").Trim(),\n",
+ " Expires = DateTime.Parse(parts[3].Replace(\"Expires=\", \"\").Trim())\n",
+ " };\n",
+ " container.Add(cookie);\n",
+ " }\n",
+ "\n",
+ " return container;\n",
+ "}\n",
+ "\n",
+ "//发送请求:从文件中加载 Cookie, 在请求中携带\n",
+ "{\n",
+ " var cookieBox = LoadCookies(\"cookies.txt\");\n",
+ "\n",
+ " var handler = new HttpClientHandler()\n",
+ " {\n",
+ " UseCookies = true,\n",
+ " CookieContainer = cookieBox,\n",
+ " };\n",
+ "\n",
+ " var client = new HttpClient(handler)\n",
+ " {\n",
+ " BaseAddress = new Uri(webApiBaseUrl)\n",
+ " };\n",
+ "\n",
+ " // 第一次请求,服务端设置 Cookie\n",
+ " var response = await client.GetAsync(\"/api/Cookie/GetRequestCookie\");\n",
+ " response.EnsureSuccessStatusCode();\n",
+ "\n",
+ " var content = await response.Content.ReadAsStringAsync();\n",
+ "\n",
+ " Console.WriteLine(content);\n",
+ "}"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 跨域 Cookie 处理"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "默认情况下,CookieContainer 会根据域名自动隔离 Cookie。若需跨域共享 Cookie,可通过以下方式实现:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "+ 手动复制 Cookie"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 52,
+ "metadata": {
+ "polyglot_notebook": {
+ "kernelName": "csharp"
+ },
+ "vscode": {
+ "languageId": "polyglot-notebook"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "//示例代码,无实际请求\n",
+ "var sourceUri = new Uri(\"https://source.com\");\n",
+ "var targetUri = new Uri(\"https://target.com\");\n",
+ "\n",
+ "foreach (Cookie cookie in handler.CookieContainer.GetCookies(sourceUri))\n",
+ "{\n",
+ " var crossDomainCookie = new Cookie(cookie.Name, cookie.Value, cookie.Path, targetUri.Host);\n",
+ " handler.CookieContainer.Add(targetUri, crossDomainCookie);\n",
+ "}"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "+ 自定义 CookieContainer(高级)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "可以继承 CookieContainer 并重写相关方法以实现自定义 Cookie 共享策略。"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 进阶功能:Cookie 过期、安全标志、SameSite 设置等"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "+ 设置 Cookie 高级属性"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 53,
+ "metadata": {
+ "polyglot_notebook": {
+ "kernelName": "csharp"
+ },
+ "vscode": {
+ "languageId": "polyglot-notebook"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "{\"data\":\"\",\"code\":1,\"message\":\"没有Cookie\"}\r\n"
+ ]
+ }
+ ],
+ "source": [
+ "{\n",
+ " //发送请求\n",
+ " {\n",
+ " var handler = new HttpClientHandler()\n",
+ " {\n",
+ " UseCookies = true,\n",
+ " CookieContainer = new CookieContainer(),\n",
+ " };\n",
+ "\n",
+ " var client = new HttpClient(handler)\n",
+ " {\n",
+ " BaseAddress = new Uri(webApiBaseUrl)\n",
+ " };\n",
+ "\n",
+ " //设置Cookie\n",
+ " var cookie = new Cookie(\"jwt_token\", \"a.b.c\")\n",
+ " {\n",
+ " Expires = DateTime.Now.AddDays(7),\n",
+ " Domain = new Uri(webApiBaseUrl).Host,\n",
+ " Path = \"/\",\n",
+ " Secure = true, // 仅 HTTPS 传输\n",
+ " HttpOnly = true, // 防止 XSS 攻击\n",
+ " };\n",
+ "\n",
+ " handler.CookieContainer.Add(new Uri(webApiBaseUrl), cookie);\n",
+ "\n",
+ " // 第一次请求,服务端设置 Cookie\n",
+ " var response = await client.GetAsync(\"/api/Cookie/GetRequestCookie\");\n",
+ " response.EnsureSuccessStatusCode();\n",
+ "\n",
+ " var content = await response.Content.ReadAsStringAsync();\n",
+ "\n",
+ " Console.WriteLine(content);\n",
+ " }\n",
+ "}\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 总结"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "| 功能 | 描述 | 适用场景 |\n",
+ "|--------------------|--------------------------------------|--------------------------------------------|\n",
+ "| 手动设置 Cookie | 灵活、简单 | 单次请求或固定 Cookie |\n",
+ "| 使用 HttpRequestMessage | 更精细控制 Cookie 行为 | 需要合并默认与自定义 Cookie |\n",
+ "| 使用 CookieContainer | 自动管理 Cookie 生命周期 | 多次请求、需要保持会话状态 |\n",
+ "| Cookie 持久化 | 保存 Cookie 至文件或数据库 | 程序重启后仍需保持登录状态 |\n",
+ "| 跨域 Cookie | 手动复制或自定义容器 | 需要在多个域名之间共享 Cookie |\n",
+ "| 安全 Cookie 设置 | `Secure`、`HttpOnly`、`SameSite` | 增强 Cookie 安全性 |\n",
+ "| 获取 Cookie 属性 | 查看 Cookie 的有效期、路径等信息 | 调试和日志记录 |"
+ ]
}
- },
- "source": [
- "# HttpClient 使用原则"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "dotnet_interactive": {
- "language": "csharp"
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": ".NET (C#)",
+ "language": "C#",
+ "name": ".net-csharp"
},
- "polyglot_notebook": {
- "kernelName": "csharp"
+ "language_info": {
+ "name": "python"
},
- "vscode": {
- "languageId": "polyglot-notebook"
+ "polyglot_notebook": {
+ "kernelInfo": {
+ "defaultKernelName": "csharp",
+ "items": [
+ {
+ "aliases": [],
+ "name": "csharp"
+ }
+ ]
+ }
}
- },
- "source": [
- "## 1、复用"
- ]
- }
- ],
- "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
+ "nbformat": 4,
+ "nbformat_minor": 2
}
diff --git a/Docs/1.4.8.高级使用.线程限流.ipynb b/Docs/1.4.8.高级使用.线程限流.ipynb
deleted file mode 100644
index 72e34d0..0000000
--- a/Docs/1.4.8.高级使用.线程限流.ipynb
+++ /dev/null
@@ -1,70 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {
- "dotnet_interactive": {
- "language": "csharp"
- },
- "polyglot_notebook": {
- "kernelName": "csharp"
- }
- },
- "source": [
- "# HttpClient 限流(System.Threading.RateLimiting)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "dotnet_interactive": {
- "language": "csharp"
- },
- "polyglot_notebook": {
- "kernelName": "csharp"
- },
- "vscode": {
- "languageId": "polyglot-notebook"
- }
- },
- "source": [
- "## 1、System.Threading.RateLimiting 概述"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## 2、结合HttpClient 实现限流"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": []
- }
- ],
- "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
-}
diff --git a/Docs/1.5.总结.ipynb b/Docs/1.5.总结.ipynb
deleted file mode 100644
index f46bfad..0000000
--- a/Docs/1.5.总结.ipynb
+++ /dev/null
@@ -1,78 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {
- "dotnet_interactive": {
- "language": "csharp"
- },
- "polyglot_notebook": {
- "kernelName": "csharp"
- }
- },
- "source": [
- "# HttpClient 总结"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "dotnet_interactive": {
- "language": "csharp"
- },
- "polyglot_notebook": {
- "kernelName": "csharp"
- },
- "vscode": {
- "languageId": "polyglot-notebook"
- }
- },
- "source": [
- "## 1、系统结束,也是新的开始"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "dotnet_interactive": {
- "language": "csharp"
- },
- "polyglot_notebook": {
- "kernelName": "csharp"
- },
- "vscode": {
- "languageId": "polyglot-notebook"
- }
- },
- "outputs": [],
- "source": [
- "#! csharp\n",
- "Console.WriteLine(\"Hello World!\");"
- ]
- }
- ],
- "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
-}
diff --git a/Docs/1.6.测试.ipynb b/Docs/1.6.测试.ipynb
deleted file mode 100644
index 6ba2831..0000000
--- a/Docs/1.6.测试.ipynb
+++ /dev/null
@@ -1,222 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {
- "dotnet_interactive": {
- "language": "csharp"
- },
- "polyglot_notebook": {
- "kernelName": "csharp"
- }
- },
- "source": [
- "# HttpClient 测试"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "dotnet_interactive": {
- "language": "csharp"
- },
- "polyglot_notebook": {
- "kernelName": "csharp"
- },
- "vscode": {
- "languageId": "polyglot-notebook"
- }
- },
- "source": [
- "## 1、引用项目Dll"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "dotnet_interactive": {
- "language": "csharp"
- },
- "polyglot_notebook": {
- "kernelName": "csharp"
- },
- "vscode": {
- "languageId": "polyglot-notebook"
- }
- },
- "outputs": [],
- "source": [
- "//引用项目\n",
- "#r \"./Publish/HttpClientStudy.Core/HttpClientStudy.Core.dll\"\n",
- "\n",
- "//执行C#工具方法\n",
- "using HttpClientStudy.Core.Utilities;\n",
- "\n",
- "\n",
- "var result = AppUtility.RunCmd(\"ls\");\n",
- "Console.WriteLine(result);"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## 2、Powershell 管理项目"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 26,
- "metadata": {
- "dotnet_interactive": {
- "language": "pwsh"
- },
- "polyglot_notebook": {
- "kernelName": "pwsh"
- },
- "vscode": {
- "languageId": "polyglot-notebook"
- }
- },
- "outputs": [],
- "source": [
- "#启动已发布的WebApi项目\n",
- "Start-Process -FilePath dotnet -ArgumentList \".\\Publish\\HttpClientStudy.WebApp\\HttpClientStudy.WebApp.dll\""
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 31,
- "metadata": {
- "dotnet_interactive": {
- "language": "pwsh"
- },
- "polyglot_notebook": {
- "kernelName": "pwsh"
- },
- "vscode": {
- "languageId": "polyglot-notebook"
- }
- },
- "outputs": [],
- "source": [
- "# 编译并启动WebApi项目\n",
- "Start-Process -FilePath dotnet -ArgumentList \"run --project ..\\HttpClientStudy.WebApp\\HttpClientStudy.WebApp.csproj\""
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "dotnet_interactive": {
- "language": "pwsh"
- },
- "polyglot_notebook": {
- "kernelName": "pwsh"
- },
- "vscode": {
- "languageId": "polyglot-notebook"
- }
- },
- "outputs": [],
- "source": [
- "# 关闭项目进程\n",
- "$WebAppProcName =\"HttpClientStudy.WebApp\";\n",
- "$WebAppProc = Get-Process $WebAppProcName -ErrorAction Ignore\n",
- "if($null -eq $WebAppProc)\n",
- "{\n",
- " Write-Host \"进程没有找到,可能已经关闭\"\n",
- "}\n",
- "else {\n",
- " $WebAppProc.Kill();\n",
- " Write-Host \"$WebAppProcName 进程已退出\"\n",
- "}"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## 3、C#类库管理项目"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "dotnet_interactive": {
- "language": "csharp"
- },
- "polyglot_notebook": {
- "kernelName": "csharp"
- },
- "vscode": {
- "languageId": "polyglot-notebook"
- }
- },
- "outputs": [],
- "source": [
- "//启动已发布的WebApi项目\n",
- "#r \"./Publish/HttpClientStudy.Core/HttpClientStudy.Core.dll\"\n",
- "\n",
- "{\n",
- " var file = System.IO.Path.GetFullPath(\"./Publish/HttpClientStudy.WebApp/HttpClientStudy.WebApp.exe\",System.Environment.CurrentDirectory);\n",
- "\n",
- " file.Display();\n",
- " var message = HttpClientStudy.Core.Utilities.AppUtility.RunWebApiExeFile(file);\n",
- "\n",
- " Console.WriteLine(message);\n",
- "}"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "dotnet_interactive": {
- "language": "csharp"
- },
- "polyglot_notebook": {
- "kernelName": "csharp"
- },
- "vscode": {
- "languageId": "polyglot-notebook"
- }
- },
- "outputs": [],
- "source": [
- "//关闭项目进程\n",
- "#r \"./Publish/HttpClientStudy.Core/HttpClientStudy.Core.dll\"\n",
- "\n",
- "{\n",
- " var message = HttpClientStudy.Core.Utilities.AppUtility.StopWebApiExeFile();\n",
- " Console.WriteLine(message);\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
-}
diff --git a/Docs/2.1.内核中的各种路径.ipynb b/Docs/2.1.内核中的各种路径.ipynb
deleted file mode 100644
index b655651..0000000
--- a/Docs/2.1.内核中的各种路径.ipynb
+++ /dev/null
@@ -1,125 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {
- "dotnet_interactive": {
- "language": "csharp"
- },
- "polyglot_notebook": {
- "kernelName": "csharp"
- }
- },
- "source": [
- "# 多语言笔记:内核中的各种路径"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "搞清楚,内核执行时的各种默认目录、路径,方便解决一些引用、文件等跟路径有关的问题。"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "dotnet_interactive": {
- "language": "csharp"
- },
- "polyglot_notebook": {
- "kernelName": "csharp"
- },
- "vscode": {
- "languageId": "polyglot-notebook"
- }
- },
- "outputs": [],
- "source": [
- "// C#中,内核执行时,各种默认路径如下:\n",
- "\n",
- "using System;\n",
- "using System.IO;\n",
- "using System.Threading;\n",
- "using System.Threading.Tasks;\n",
- "using System.Diagnostics;\n",
- "using System.Reflection;\n",
- "using Microsoft.Extensions.DependencyInjection;\n",
- "\n",
- " var pathDic = new Dictionary() \n",
- " {\n",
- " //当前运行的exe的完整路径,包含exe文件名,只用于WinForm\n",
- " {\"Application.ExecutablePath\",(\"程序集基完整路径(仅WinForm)\", \"Application.ExecutablePath 只适用于WinForm\") },\n",
- "\n",
- " //程序的启动路径:只用于WinForm\n",
- " {\"Application.StartupPath\",(\"程序集启动路径(仅WinForm)\", \"Application.StartupPath 只适用于WinForm\") },\n",
- "\n",
- " //当前执行的exe或启动项目的路径,通过AppContext\n",
- " {\"AppContext.BaseDirectory\",(\"执行或启动路径\",AppContext.BaseDirectory) }, \n",
- "\n",
- " //当前执行的exe的目录,不包含exe名,使用AppDomain\n",
- " {\"AppDomain.CurrentDomain.BaseDirectory\",(\"程序集解析程序用于探测程序集的基目录\",AppDomain.CurrentDomain.BaseDirectory) }, \n",
- "\n",
- " //程序安装或启动基目录 包含应用程序的目录的名称\n",
- " {\"AppDomain.CurrentDomain.SetupInformation.ApplicationBase\",(\"程序安装或启动基目录\",AppDomain.CurrentDomain.SetupInformation.ApplicationBase) }, \n",
- "\n",
- " //当前进程的主模块路径,包含exe名\n",
- " {\"Process.GetCurrentProcess().MainModule.FileName\",(\"当前进程的主模块路径\",Process.GetCurrentProcess()?.MainModule?.FileName) }, \n",
- "\n",
- " //环境变量:用户当前工作目录的完整限定路径\n",
- " {\"Environment.CurrentDirectory\",(\"用户当前工作目录的完整限定路径\",Environment.CurrentDirectory) }, \n",
- "\n",
- " //环境变量:当前exe的完整路径,包含exe名,通过命令行参数\n",
- " {\"Environment.GetCommandLineArgs()[0]\",(\"当前exe的完整路径\",Environment.GetCommandLineArgs()[0]) }, \n",
- "\n",
- " //当前工作目录的路径(可变)\n",
- " {\"Directory.GetCurrentDirectory\",(\"当前工作目录的路径(可变)\",Directory.GetCurrentDirectory()) },\n",
- " \n",
- " //当前Assembly的加载路径,包含dll或exe名\n",
- " {\"Assembly.GetExecutingAssembly().Location\",(\"当前Assembly的加载路径\",Assembly.GetExecutingAssembly().Location) },\n",
- "\n",
- " //入口程序集的路径\n",
- " {\"Assembly.GetEntryAssembly().Location\",(\"入口程序集的路径\",Assembly.GetEntryAssembly()?.Location) },\n",
- "\n",
- " //已过时:当前程序集的CodeBase路径,可能为file URI格式\n",
- " {\"Assembly.GetExecutingAssembly().CodeBase\",(\"当前程序集的CodeBase路径\",Assembly.GetExecutingAssembly()?.CodeBase) },\n",
- "\n",
- " //已过时:入口程序集的CodeBase路径,可能为file URI格式\n",
- " {\"Assembly.GetEntryAssembly().CodeBase\",(\"入口程序集的CodeBase路径\",Assembly.GetEntryAssembly()?.CodeBase) },\n",
- " };\n",
- "\n",
- " var message = string.Empty;\n",
- " foreach (var item in pathDic)\n",
- " {\n",
- " message += $\"{item.Key} => {item.Value.path}{Environment.NewLine}\";\n",
- " }\n",
- "\n",
- " Console.WriteLine(message);"
- ]
- }
- ],
- "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
-}
diff --git a/Docs/Assets/FiddlerRoot.cer b/Docs/Assets/FiddlerRoot.cer
new file mode 100644
index 0000000..ce5e2f3
Binary files /dev/null and b/Docs/Assets/FiddlerRoot.cer differ
diff --git a/Docs/Assets/HttpClient-代理-抓包.jpg b/Docs/Assets/HttpClient-代理-抓包.jpg
new file mode 100644
index 0000000..0158d8f
Binary files /dev/null and b/Docs/Assets/HttpClient-代理-抓包.jpg differ
diff --git a/Docs/Assets/HttpClient-代理服务器设置.jpg b/Docs/Assets/HttpClient-代理服务器设置.jpg
new file mode 100644
index 0000000..99387e8
Binary files /dev/null and b/Docs/Assets/HttpClient-代理服务器设置.jpg differ
diff --git a/Docs/Assets/HttpClient-使用 Cookie.jpg b/Docs/Assets/HttpClient-使用 Cookie.jpg
new file mode 100644
index 0000000..0049b8b
Binary files /dev/null and b/Docs/Assets/HttpClient-使用 Cookie.jpg differ
diff --git a/Docs/Assets/HttpClient-使用代理功能.jpg b/Docs/Assets/HttpClient-使用代理功能.jpg
new file mode 100644
index 0000000..85deee5
Binary files /dev/null and b/Docs/Assets/HttpClient-使用代理功能.jpg differ
diff --git a/HttpClientStudy.WebApp/Controllers/AdvancedGetController.cs b/HttpClientStudy.WebApp/Controllers/AdvancedGetController.cs
index 321c456..73c5e6d 100644
--- a/HttpClientStudy.WebApp/Controllers/AdvancedGetController.cs
+++ b/HttpClientStudy.WebApp/Controllers/AdvancedGetController.cs
@@ -1,10 +1,6 @@
-using System.ComponentModel.DataAnnotations;
-using System.Text;
+using Microsoft.AspNetCore.Mvc;
-using HttpClientStudy.WebApp.Models;
-
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc;
+using System.ComponentModel.DataAnnotations;
namespace HttpClientStudy.WebApp.Controllers
{
@@ -21,7 +17,7 @@ namespace HttpClientStudy.WebApp.Controllers
/// 构造
///
public AdvancedGetController(ILogger logger)
- {
+ {
_logger = logger;
}
@@ -33,7 +29,7 @@ namespace HttpClientStudy.WebApp.Controllers
public async Task GetWithBody()
{
if (Request.Body == null)
- {
+ {
return new JsonResult(BaseResultUtil.Fail("没有请求体"));
}
@@ -58,7 +54,7 @@ namespace HttpClientStudy.WebApp.Controllers
/// 携带请求正文:使用模型绑定
///
[HttpGet]
- public async Task GetWithFormBody([FromForm] int Id,[FromForm] string name)
+ public async Task GetWithFormBody([FromForm] int Id, [FromForm] string name)
{
AdvancedGetModel vm = new AdvancedGetModel() { Id = Id, Name = name };
var result = BaseResultUtil.Success(vm);
@@ -170,7 +166,7 @@ namespace HttpClientStudy.WebApp.Controllers
///
///
[HttpPost]
- public IActionResult PostFormData([FromForm,Required]int id, [FromForm,Required]string name)
+ public IActionResult PostFormData([FromForm, Required] int id, [FromForm, Required] string name)
{
var paras = $"{nameof(id)}={id}&{nameof(name)}={name}";
var result = BaseResultUtil.Success(paras);
@@ -183,7 +179,7 @@ namespace HttpClientStudy.WebApp.Controllers
///
///
[HttpPost]
- public IActionResult PostJsonData([FromBody]AdvancedGetModel? vm)
+ public IActionResult PostJsonData([FromBody] AdvancedGetModel? vm)
{
var result = BaseResultUtil.Success(vm);
return Ok(result);
diff --git a/HttpClientStudy.WebApp/Controllers/AdvancedPostController.cs b/HttpClientStudy.WebApp/Controllers/AdvancedPostController.cs
index 9cc622a..58eb553 100644
--- a/HttpClientStudy.WebApp/Controllers/AdvancedPostController.cs
+++ b/HttpClientStudy.WebApp/Controllers/AdvancedPostController.cs
@@ -1,10 +1,6 @@
-using System.ComponentModel.DataAnnotations;
-using System.Text;
+using Microsoft.AspNetCore.Mvc;
-using HttpClientStudy.WebApp.Models;
-
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc;
+using System.ComponentModel.DataAnnotations;
namespace HttpClientStudy.WebApp.Controllers
{
@@ -20,8 +16,8 @@ namespace HttpClientStudy.WebApp.Controllers
///
/// 构造
///
- public AdvancedPostController(ILogger logger)
- {
+ public AdvancedPostController(ILogger logger)
+ {
_logger = logger;
}
@@ -39,18 +35,18 @@ namespace HttpClientStudy.WebApp.Controllers
{
string content = "请求体没有数据";
- if (Request.ContentLength>0)
+ if (Request.ContentLength > 0)
{
byte[] bytes = new byte[(int)Request.ContentLength];
- await Request.Body.ReadAsync(bytes);
+ await Request.Body.ReadAsync(bytes).ConfigureAwait(false);
content = UnicodeEncoding.UTF8.GetString(bytes);
}
var result = BaseResultUtil.Success(content);
return Ok(result);
- }
+ }
///
/// 接收请求体数据
@@ -58,7 +54,7 @@ namespace HttpClientStudy.WebApp.Controllers
///
///
[HttpPost]
- public IActionResult FormData([FromForm,Required]int id, [FromForm,Required]string name)
+ public IActionResult FormData([FromForm, Required] int id, [FromForm, Required] string name)
{
var paras = $"{nameof(id)}={id}&{nameof(name)}={name}";
var result = BaseResultUtil.Success(paras);
@@ -84,7 +80,7 @@ namespace HttpClientStudy.WebApp.Controllers
///
///
[HttpPost]
- public IActionResult JsonData([FromBody]AdvancedGetModel? vm)
+ public IActionResult JsonData([FromBody] AdvancedGetModel? vm)
{
var result = BaseResultUtil.Success(vm);
return Ok(result);
diff --git a/HttpClientStudy.WebApp/Controllers/CookieController.cs b/HttpClientStudy.WebApp/Controllers/CookieController.cs
new file mode 100644
index 0000000..924cda3
--- /dev/null
+++ b/HttpClientStudy.WebApp/Controllers/CookieController.cs
@@ -0,0 +1,133 @@
+using Microsoft.AspNetCore.Mvc;
+
+namespace HttpClientStudy.WebApp.Controllers
+{
+ ///
+ /// 普通(简单) 控制器
+ ///
+ [Route("api/[controller]/[action]")]
+ [ApiController]
+ public class CookieController : ControllerBase
+ {
+ private ILogger _logger;
+ private AccountService _accountService;
+
+ ///
+ /// 构造
+ ///
+ public CookieController(ILogger logger, AccountService accountService)
+ {
+ _logger = logger;
+ _accountService = accountService;
+ }
+
+
+ ///
+ /// 获取请求中的Cookie
+ ///
+ ///
+ [HttpGet]
+ public IActionResult GetRequestCookie()
+ {
+ var cookies = Request.Cookies;
+ if (cookies == null || cookies.Count == 0)
+ {
+ return Ok(BaseResultUtil.Success("", "没有Cookie"));
+ }
+
+
+ var result = BaseResultUtil.Success(cookies);
+
+ return Ok(result);
+ }
+
+ ///
+ /// 设置响应中的Cookie
+ ///
+ ///
+ [HttpGet]
+ public IActionResult GetResponseCookie()
+ {
+ //Cookie选项
+ var cookieOptions = new CookieOptions
+ {
+ // 设置过期时间(如1天后过期)
+ Expires = DateTimeOffset.Now.AddDays(1),
+
+ // 设置Cookie路径
+ Path = "/",
+
+ // 设置HttpOnly(防止XSS攻击)
+ HttpOnly = false,
+
+ // 设置Secure(仅HTTPS传输)
+ Secure = false,
+
+ // 设置SameSite策略
+ SameSite = SameSiteMode.Unspecified
+ };
+
+
+ var cookieData = new KeyValuePair[]
+ {
+ new KeyValuePair("ProjectName","WebApp"),
+ new KeyValuePair("Version","Dotnet9"),
+ };
+
+
+ HttpContext.Response.Cookies.Append(cookieData, cookieOptions);
+
+
+ var result = BaseResultUtil.Success("响应头中已设置Cookie");
+
+ return Ok(result);
+ }
+
+ ///
+ /// 设置Cookie
+ ///
+ ///
+ [HttpGet]
+ public IActionResult SetResponseCookie(string cookieName, string cookieValue)
+ {
+ Response.Headers.TryAdd("Cookie", $"{cookieName}={cookieValue ?? string.Empty}");
+
+ var result = BaseResultUtil.Success($"响应头{cookieName}中已设置Cookie值{cookieValue}");
+
+ return Ok(result);
+ }
+
+ ///
+ /// 检测Cookie
+ ///
+ ///
+ [HttpGet]
+ public IActionResult CheckCookie(string? cookieName)
+ {
+ var cookies = Request.Cookies;
+
+ if (cookies == null || cookies.Count == 0)
+ {
+ return Ok(BaseResultUtil.Success("", "没有Cookie"));
+ }
+
+ if (string.IsNullOrWhiteSpace(cookieName))
+ {
+
+ var allCookie = BaseResultUtil.Success(cookies, "返回所有Cookie");
+
+ return Ok(allCookie);
+ }
+
+ KeyValuePair findCookie = cookies.FirstOrDefault(c => c.Key == cookieName);
+ if (findCookie.Key == null)
+ {
+ return Ok(BaseResultUtil.Success($"请求头中没有找到名为{cookieName}的Cookie", $"没找到Cookie"));
+ }
+ else
+ {
+ return Ok(BaseResultUtil.Success($"请求头中名为{findCookie.Key}的Cookie,其值为{findCookie.Value}", $"找到Cookie"));
+ }
+ }
+ }
+}