{ "cells": [ { "cell_type": "markdown", "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" } }, "source": [ "# 基础使用-发送请求" ] }, { "cell_type": "markdown", "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" }, "vscode": { "languageId": "polyglot-notebook" } }, "source": [ "## 0、初始化与全局设置" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" }, "vscode": { "languageId": "polyglot-notebook" } }, "outputs": [], "source": [ "//全局设置,行运行一次,为后续准备\n", "#r \"nuget:System.Net.Http.Json\"\n", "#r \"nuget:Microsoft.Net.Http.Headers\"\n", "#r \"nuget:Microsoft.Extensions.Http\"\n", "#r \"nuget:Microsoft.Extensions.DependencyInjection\"\n", "#r \"./Publish/HttpClientStudy.Model/HttpClientStudy.Model.dll\"\n", "#r \"./Publish/HttpClientStudy.Core/HttpClientStudy.Core.dll\"\n", "\n", "global using System;\n", "global using System.Collections;\n", "global using System.Collections.Concurrent;\n", "global using System.Linq;\n", "global using System.Linq.Expressions;\n", "global using System.Threading;\n", "global using System.Threading.Tasks;\n", "global using System.Net.Http;\n", "//System.Net.Http.Json 包含处理json的扩展方法,方便处理请求和影响中的json数据\n", "global using System.Net.Http.Json;\n", "global using Microsoft.Extensions.DependencyInjection;\n", "global using Microsoft.Extensions.DependencyInjection.Extensions;\n", "\n", "global using HttpClientStudy.Config;\n", "global using HttpClientStudy.Model;\n", "global using HttpClientStudy.Core;\n", "global using HttpClientStudy.Core.Utilities;\n", "\n", "//全局变量\n", "var webApiBaseUrl = WebApiConfigManager.GetWebApiConfig().BaseUrl;\n", "var workDir = Environment.CurrentDirectory;\n", "var fullPath = System.IO.Path.GetFullPath(\"./Publish/HttpClientStudy.WebApp/HttpClientStudy.WebApp.exe\", workDir);\n", "\n", "//全局共享静态 HttpClient 对象\n", "public static HttpClient SharedClient = new HttpClient(new SocketsHttpHandler(){ PooledConnectionIdleTimeout = TimeSpan.FromSeconds(30)})\n", "{\n", " BaseAddress = new Uri(WebApiConfigManager.GetWebApiConfig().BaseUrl),\n", "};\n", "\n", "//启动已发布的WebApi项目\n", "{\n", " Console.WriteLine(\"启动WebApi项目\");\n", " var startMessage = AppUtility.RunWebApiExeFile(fullPath);\n", " Console.WriteLine(startMessage);\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1、创建HttpClient" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "根据 系列教程的 `管理客户端` 一章, 创建的HttpClient对象,有非常多的方式。\n", "为方便演示,本节使用两种:一种是全局共享对象(SharedClient, 在初始化时创建), 符合共享的使用原则; 一种是临时实例化对象。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2、发出 Http请求" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Http 简介" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "http是无状态的请求/响应模式。\n", "\n", "客户端请求: 请求行(方法 URL 协议版本 回车换行符)、请求头、空行及可选的请求体;\n", "\n", "![客户端请求消息](./Assets/RequestMessage.png)\n", "\n", "服务器响应:状态行(协议版本 状态码 状态码描述 回车换行符)、响应头及可选的响应体。\n", "\n", "![客户端请求消息](./Assets/ResponseMessage.jpg)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Http 请求方法" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Http请求方法可通过多种特性区分:\n", "\n", "1. 谓词:也就是请求方法,Post、Get、Put、Delete、Options、Head等;\n", "2. 幂等性:如果可多次成功处理某个请求方法而不改变结果,则该请求方法是幂等的;\n", "3. 可缓存性:如果可以存储请求方法的相应响应以供重复使用,则该请求方法是可缓存的;\n", "4. 安全性:如果请求方法不会修改资源的状态,则被视为安全方法。所有安全方法也都是幂等方法,但并非所有幂等方法都是安全方法;\n", "\n", " | **HTTP 方法** | **是否是幂等的** | **是否可缓存** | **是否安全** |\n", " | ------------- | ---------------- | -------------- | ------------ |\n", " | `GET` | ✔️ 是 | ✔️ 是 | ✔️ 是 |\n", " | `POST` | ❌ 否 | ⚠️ †很少 | ❌ 否 |\n", " | `PUT` | ✔️ 是 | ❌ 否 | ❌ 否 |\n", " | `PATCH` | ❌ 否 | ❌ 否 | ❌ 否 |\n", " | `DELETE` | ✔️ 是 | ❌ 否 | ❌ 否 |\n", " | `HEAD` | ✔️ 是 | ✔️ 是 | ✔️ 是 |\n", " | `OPTIONS` | ✔️ 是 | ❌ 否 | ✔️ 是 |\n", " | `TRACE` | ✔️ 是 | ❌ 否 | ✔️ 是 |\n", " | `CONNECT` | ❌ 否 | ❌ 否 | ❌ 否 |\n", "> \n", ">†仅当存在相应的 Cache-Control 或 Expires 响应标头时,POST 方法才可缓存。 这在实践中非常罕见。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Http 状态码" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Http 状态码是响应的首要标识,指定响应的不同结果,分为以下几类:\n", "\n", "1. 1xx:信息,表示临时响应,大多数临时响应 (例如 HttpStatusCode.Continue) 使用 HttpClient 在内部处理,并且永远不会显示给用户;\n", "2. 2xx:成功,表示请求已被成功接收、理解、并接受;\n", "3. 3xx:重定向,表示需要完成一个或多个附加步骤才能完成请求,自动重定向默认处于打开状态,可以使用 HttpClientHandler.AllowAutoRedirect 或 SocketsHttpHandler.AllowAutoRedirect 进行更改;\n", "4. 4xx:客户端错误,表示客户端的请求无效(请求包含语法错误或无法完成请求);\n", "5. 5xx:服务器错误,表示服务器在处理请求的过程中发生了错误。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 使用 HttpClient 发出请求" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "要发出 HTTP 请求,请调用以下任一 API:\n", "\n", "| HTTP 方法 | API |\n", "| ----------------- | ---------------------------- |\n", "| `GET` | HttpClient.GetAsync |\n", "| `GET` | HttpClient.GetByteArrayAsync |\n", "| `GET` | HttpClient.GetStreamAsync |\n", "| `GET` | HttpClient.GetStringAsync |\n", "| `POST` | HttpClient.PostAsync |\n", "| `PUT` | HttpClient.PutAsync |\n", "| `PATCH` | HttpClient.PatchAsync |\n", "| `DELETE` | HttpClient.DeleteAsync |\n", "| †`USER SPECIFIED` | HttpClient.SendAsync |\n", "\n", "
\n", "\n", "> †USER SPECIFIED 请求指示 SendAsync 方法接受任何有效的 HttpMethod。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Http 内容" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "HttpContent 类型用于表示 HTTP 实体正文和相应的内容标头。 对于需要正文的 HTTP 方法(或请求方法)POST、PUT 和 PATCH,可使用 HttpContent 类来指定请求的正文。\n", "大多数示例演示如何使用 JSON 有效负载准备 StringContent 子类,但还有针对其他内容 (MIME) 类型的其他子类。\n", "\n", "- ByteArrayContent:提供基于字节数组的 HTTP 内容;\n", "- FormUrlEncodedContent:为使用 \"application/x-www-form-urlencoded\" MIME 类型编码的名称/值元组提供 HTTP 内容;\n", "- JsonContent:提供基于 JSON 的 HTTP 内容;\n", "- MultipartContent:提供使用 \"multipart/*\" MIME 类型规范进行序列化的 HttpContent 对象的集合;\n", "- MultipartFormDataContent:为使用 \"multipart/form-data\" MIME 类型进行编码的内容提供容器;\n", "- ReadOnlyMemoryContent:提供基于 ReadOnlyMemory 的 HTTP 内容;\n", "- StreamContent:提供基于流的 HTTP 内容;\n", "- StringContent:提供基于字符串的 HTTP 内容\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Http Get" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "GET 请求不应发送正文,而是用于从资源检索数据。 要在给定 HttpClient 和 URI 的情况下发出 HTTP GET 请求,推荐使用 HttpClient.GetAsync 方法。\n", "\n", "> Get 请求,一般不会发送数据(服务器支持的话,也能带),最大特点是:可以缓存请求结果,减少服务器压力。" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" }, "vscode": { "languageId": "polyglot-notebook" } }, "outputs": [], "source": [ "//Get 请求\n", "{\n", " //使用共享客户端发送Get请求\n", " var response = await SharedClient.GetAsync(\"/api/hello/index\");\n", "\n", " //确保请求成功\n", " response.EnsureSuccessStatusCode();\n", "\n", " //读取响应内容\n", " var content = await response.Content.ReadAsStringAsync();\n", "\n", " //输出 响应内容\n", " Console.WriteLine(content);\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Http Get Json" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "System.Net.Http.Json 库,为请求Json格式数据和接受json格式数据提供了很多扩展方法,使用 HttpClient 发送和请求Json格式数据非常方便; \n", "后面单独章节讲解,这里只是一个例子." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" }, "vscode": { "languageId": "polyglot-notebook" } }, "outputs": [], "source": [ "/*\n", " 方式一:使用 System.Net.Http.Json\n", " Get Json\n", " 需要先引入 System.Net.Http.Json 和 命名空间(全局设置里已完成)\n", "*/\n", "{\n", "\n", " //使用共享客户端发送Get请求\n", " var content = await SharedClient.GetFromJsonAsync(\"/api/hello/GetAccount\");\n", "\n", " //框架显示方法\n", " content.Display();\n", "}\n", "\n", "/*\n", " 方式二:ReadFromJsonAsync 方法,读取响应内容(HttpResonseMessage.Content)\n", "*/\n", "{\n", " var response = await SharedClient.GetAsync(\"/api/hello/GetAccount\");\n", " response.EnsureSuccessStatusCode();\n", "\n", " var content = await response.Content.ReadFromJsonAsync();\n", " content.Display();\n", "}\n", "\n", "/*\n", " 方式三:手动序列化读取的响应内容\n", "*/\n", "{\n", " var response = await SharedClient.GetAsync(\"/api/hello/GetAccount\");\n", " response.EnsureSuccessStatusCode();\n", "\n", " var content = await response.Content.ReadAsStringAsync();\n", " \n", " var account = System.Text.Json.JsonSerializer.Deserialize(content);\n", " account.Display();\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## HTTP Post" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "POST 请求将数据发送到服务器进行处理。 请求的 Content-Type 标头表示正文发送的 MIME 类型。 要在给定 HttpClient 和 Uri 的情况下发出 HTTP POST 请求,请使用 HttpClient.PostAsync 方法.\n", "请求数据可选:包括 请求URL、路由、请求头、请求体(包括多种方式,后面详解)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" }, "vscode": { "languageId": "polyglot-notebook" } }, "outputs": [], "source": [ "//不带请求体\n", "{\n", " var response = await SharedClient.PostAsync(\"/api/hello/Post\",null);\n", " response.EnsureSuccessStatusCode();\n", "\n", " var context = await response.Content.ReadAsStringAsync();\n", "\n", " Console.WriteLine(context);\n", "}\n", "\n", "//带请求体\n", "{\n", " var account = new Account()\n", " {\n", " Id =0,\n", " Name = \"小王\",\n", " };\n", "\n", " //请求体有很多:后面专门章节讲解\n", " var requestContent = new StringContent(System.Text.Json.JsonSerializer.Serialize(account), System.Text.Encoding.UTF8, System.Net.Mime.MediaTypeNames.Application.Json);\n", "\n", " var response = await SharedClient.PostAsync(\"/api/hello/AddAccount\", requestContent);\n", " response.EnsureSuccessStatusCode();\n", "\n", " var context = await response.Content.ReadAsStringAsync();\n", "\n", " Console.WriteLine(context);\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## HTTP Put" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "PUT 请求方法会替换现有资源,或使用请求正文有效负载创建一个新资源。 要在给定 HttpClient 和 URI 的情况下发出 HTTP PUT 请求,请使用 HttpClient.PutAsync 方法。" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" }, "vscode": { "languageId": "polyglot-notebook" } }, "outputs": [], "source": [ "/* Put请求, 一般是添加或修改资源:需要数据。\n", " 这里故意使用查询,没有资源:\n", " 意在说明:Get、Post、Put等谓词的使用,只是行业规范(有些服务器做了特殊处理),不具有强制性。并且,从http协议上说,没有本质区别。 \n", "*/\n", "{\n", " var response = await SharedClient.PutAsync(\"/api/Normal/PutDemo?id=2\", null);\n", "\n", " response.EnsureSuccessStatusCode();\n", "\n", " var content = await response.Content.ReadAsStringAsync();\n", " Console.WriteLine(content);\n", "\n", " //或者直接获取对象\n", " var result = await response.Content.ReadFromJsonAsync>();\n", " result.Display();\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## HTTP Head" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "HEAD 请求类似于请求 GET。 它只返回与资源关联的标头,而不返回资源。 对 HEAD 请求的响应不会返回正文。 \n", "要在给定 HttpClient 和 URI 的情况下发出 HTTP HEAD 请求,请使用 HttpClient.SendAsync 方法并将 HttpMethod 设置为 HttpMethod.Head \n", "注意:系统没有提供单独的Head方法,要使用通用的Send或者SendAsync" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" }, "vscode": { "languageId": "polyglot-notebook" } }, "outputs": [], "source": [ "//Head请求,借助SendAsync方法\n", "{\n", " var requestMessage = new HttpRequestMessage(HttpMethod.Head, \"/api/Normal/HeadDemo?id=2\");\n", "\n", " var response = await SharedClient.SendAsync(requestMessage);\n", "\n", " response.EnsureSuccessStatusCode();\n", "\n", " //Head请求,没有响应内容。\n", " var content = await response.Content.ReadAsStringAsync();\n", " Console.WriteLine(response.Content.Headers.ContentLength > 0 ? \"有响应体\":\"无响应体\");\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## HTTP Patch" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "PATCH 请求是对现有资源执行部分更新。 它不会创建新资源,也不会替换现有资源。 而是只更新部分资源。 要在给定 HttpClient 和 URI 的情况下发出 HTTP PATCH 请求,请使用 HttpClient.PatchAsync 方法" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" }, "vscode": { "languageId": "polyglot-notebook" } }, "outputs": [], "source": [ "//Patch请求\n", "{\n", " var account = new Account()\n", " {\n", " Id = 1,\n", " Name = \"Patch更新\"\n", " };\n", "\n", " StringContent jsonContent = new(System.Text.Json.JsonSerializer.Serialize(account), Encoding.UTF8, System.Net.Mime.MediaTypeNames.Application.JsonPatch);\n", "\n", " HttpResponseMessage response = await SharedClient.PatchAsync( \"api/Normal/PatchDemo\", jsonContent);\n", "\n", " response.EnsureSuccessStatusCode();\n", "\n", " var context = await response.Content.ReadAsStringAsync();\n", " Console.WriteLine($\"{context}\");\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## HTTP Delete" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "DELETE 请求会删除现有资源。 DELETE 请求是幂等的但不是安全的,这意味着对同一资源发出的多个 DELETE 请求会产生相同的结果,但该请求会影响资源的状态。 要在给定 HttpClient 和 URI 的情况下发出 HTTP DELETE 请求,请使用 HttpClient.DeleteAsync 方法." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" }, "vscode": { "languageId": "polyglot-notebook" } }, "outputs": [], "source": [ "//Delete 删除资源\n", "{\n", " HttpResponseMessage response = await SharedClient.DeleteAsync( \"api/Normal/Delete?id=1\");\n", "\n", " response.EnsureSuccessStatusCode();\n", "\n", " var context = await response.Content.ReadAsStringAsync();\n", " Console.WriteLine($\"{context}\");\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## HTTP Options" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "OPTIONS 请求用于标识服务器或终结点支持哪些 HTTP 方法。 要在给定 HttpClient 和 URI 的情况下发出 HTTP OPTIONS 请求,请使用 HttpClient.SendAsync 方法并将 HttpMethod 设置为 HttpMethod.Options" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" }, "vscode": { "languageId": "polyglot-notebook" } }, "outputs": [], "source": [ "//Options 预检请求\n", "{\n", " var requestMessage = new HttpRequestMessage(HttpMethod.Options, \"api/Normal/OptionsSimple\");\n", "\n", " HttpResponseMessage response = await SharedClient.SendAsync(requestMessage);\n", "\n", " response.EnsureSuccessStatusCode();\n", "\n", " var context = await response.Content.ReadAsStringAsync();\n", " Console.WriteLine($\"{context}\");\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## HTTP Trace" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "TRACE 请求可用于调试,因为它提供请求消息的应用程序级环回。 要发出 HTTP TRACE 请求,请使用 HttpMethod.Trace 创建 HttpRequestMessage\n", "特别注意:ASP.NET WebApi 并不直接支持 Trace 请求;一个简单方法是使用一个专用的中间件,来支持所有接口的Trace请求.(需要服务器支持)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" }, "vscode": { "languageId": "polyglot-notebook" } }, "outputs": [], "source": [ "//trace 服务器已设置专门中间件支持所有接口的Trace请求\n", "{\n", " var requestMessage = new HttpRequestMessage(HttpMethod.Trace,\"api/Normal/TraceDemo?accountId=1\");\n", " requestMessage.Content = new StringContent(\"我是请求内容\",Encoding.UTF8,System.Net.Mime.MediaTypeNames.Text.Plain);\n", "\n", " var response = await SharedClient.SendAsync(requestMessage);\n", "\n", " response.EnsureSuccessStatusCode();\n", "\n", " var context = await response.Content.ReadAsStringAsync();\n", "\n", " Console.WriteLine(context);\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 }