You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
HttpClientStudy/Docs/1.3.1.基础使用.发送请求.ipynb

723 lines
23 KiB
Plaintext

This file contains ambiguous Unicode characters!

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

{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
}
},
"source": [
"# 基础使用-发送请求"
]
},
{
"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",
"<br>\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<T> 的 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<HttpClientStudy.Model.Account>(\"/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<HttpClientStudy.Model.Account>();\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<HttpClientStudy.Model.Account>(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<BaseResult<Account>>();\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
}