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": [ + "![代理服务器](./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": [ + "
Installed Packages
" + ] + }, + "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(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")); + } + } + } +}