{ "cells": [ { "cell_type": "markdown", "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" } }, "source": [ "在 HttpClient 中使用 Cookie\n", "=============================" ] }, { "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
" ] }, "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 的有效期、路径等信息 | 调试和日志记录 |" ] } ], "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 }