使用 HttpClient 类发出 HTTP 请求
本文介绍如何使用 HttpClient
类发出 HTTP 请求和处理响应。
重要
本文中的所有示例 HTTP 请求都面向以下 URL 之一:
- https://jsonplaceholder.typicode.com:提供免费的虚假 API 平台用于测试和原型制作的网站。
- https://www.example.com:此域用于文档中的说明性示例。
HTTP 终结点通常返回 JavaScript 对象表示法 (JSON) 数据,但并不总是如此。 为方便起见,可选 System.Net.Http.Json NuGet 包为 HttpClient
和 HttpContent
对象提供了几种扩展方法,这些对象通过使用 📦 System.Text.Json NuGet 包执行自动序列化和反序列化。 本文中的示例重点指出了这些扩展可用的位置。
提示
本文引用的所有源代码均在 GitHub:.NET Docs 存储库中提供。
创建 HttpClient 对象
本文中的大多数示例重复使用相同的 HttpClient
实例,因此可以配置实例一次,并将其用于其余示例。 若要创建 HttpClient
对象,请使用 HttpClient
类构造函数。 有关详细信息,请参阅 HttpClient 使用准则。
// HttpClient lifecycle management best practices:
// https://learn.microsoft.com/dotnet/fundamentals/networking/http/httpclient-guidelines#recommended-use
private static HttpClient sharedClient = new()
{
BaseAddress = new Uri("https://jsonplaceholder.typicode.com"),
};
该代码完成以下任务:
- 将新的
HttpClient
实例实例化为static
变量。 根据 准则,建议的方法是在应用程序生命周期内重复使用HttpClient
实例。 - 将 HttpClient.BaseAddress 属性设置为
"https://jsonplaceholder.typicode.com"
。
此 HttpClient
实例使用基址发出后续请求。 若要应用其他配置,请考虑以下 API:
- 设置 HttpClient.DefaultRequestHeaders 属性。
- 应用非默认 HttpClient.Timeout 属性。
- 指定 HttpClient.DefaultRequestVersion 属性。
提示
或者,可以使用工厂模式方法创建 HttpClient
实例,该方法允许配置任意数量的客户端并将其用作依赖项注入服务。 有关详细信息,请参阅使用 .NET 的 HTTP 客户端工厂。
发出 HTTP 请求
若要发出 HTTP 请求,请调用以下任一 API 方法:
HTTP 方法 | API |
---|---|
GET |
HttpClient.GetAsync |
GET |
HttpClient.GetByteArrayAsync |
GET |
HttpClient.GetStreamAsync |
GET |
HttpClient.GetStringAsync |
POST |
HttpClient.PostAsync |
PUT |
HttpClient.PutAsync |
PATCH |
HttpClient.PatchAsync |
DELETE |
HttpClient.DeleteAsync |
†USER SPECIFIED |
HttpClient.SendAsync |
†
USER SPECIFIED
请求指示SendAsync
方法接受任何有效的 HttpMethod 对象。
警告
发出 HTTP 请求被视为是与网络 I/O 相关的工作。 存在同步 HttpClient.Send 方法,但建议改用异步 API,除非有充分理由不这样做。
注意
在针对 Android 设备(例如使用 .NET MAUI 开发)时,必须将 android:usesCleartextTraffic="true"
定义添加到 AndroidManifest.xml 文件中的 <application></application>
部分。 此设置将启用明文流量,例如 HTTP 请求;否则由于 Android 安全策略,默认情况下会禁用。 请考虑以下示例 XML 设置:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:usesCleartextTraffic="true"></application>
<!-- omitted for brevity -->
</manifest>
有关详细信息,请参阅为 localhost 域启用明文网络流量。
了解 HTTP 内容
HttpContent 类型用于表示 HTTP 实体正文和相应的内容标头。 对于需要正文(POST
、PUT
、PATCH
)的 HTTP 方法(或请求方法),请使用 HttpContent 类来指定请求的正文。 大多数示例演示如何使用 JSON 有效负载准备 StringContent 子类,但还有针对其他内容 (MIME) 类型的其他子类。
- ByteArrayContent:提供基于字节数组的 HTTP 内容。
- FormUrlEncodedContent:为使用
"application/x-www-form-urlencoded"
MIME 类型编码的名称/值元组提供 HTTP 内容。 - JsonContent:提供基于 JSON 的 HTTP 内容。
- MultipartContent:提供使用
"multipart/*"
MIME 类型规范进行序列化的 HttpContent 对象的集合。 - MultipartFormDataContent:为使用
"multipart/form-data"
MIME 类型编码的内容提供容器。 - ReadOnlyMemoryContent:基于 ReadOnlyMemory<T> 值提供 HTTP 内容。
- StreamContent:提供基于流的 HTTP 内容。
- StringContent:提供基于字符串的 HTTP 内容。
HttpContent
类还用于表示 HttpResponseMessage 类的响应正文,该响应正文可在 HttpResponseMessage.Content 属性上访问。
使用 HTTP GET 请求
GET
请求不发送正文。 此请求(作为方法名称指示)用于从资源检索(或获取)数据。 若要在给定 HttpClient
实例和 Uri 对象的情况下发出 HTTP GET
请求,请使用 HttpClient.GetAsync 方法:
static async Task GetAsync(HttpClient httpClient)
{
using HttpResponseMessage response = await httpClient.GetAsync("todos/3");
response.EnsureSuccessStatusCode()
.WriteRequestToConsole();
var jsonResponse = await response.Content.ReadAsStringAsync();
Console.WriteLine($"{jsonResponse}\n");
// Expected output:
// GET https://jsonplaceholder.typicode.com/todos/3 HTTP/1.1
// {
// "userId": 1,
// "id": 3,
// "title": "fugiat veniam minus",
// "completed": false
// }
}
该代码完成以下任务:
- 向
"https://jsonplaceholder.typicode.com/todos/3"
终结点发出GET
请求。 - 确保响应成功。
- 将请求详细信息写入控制台。
- 以字符串的形式读取响应正文。
- 将 JSON 响应正文写入控制台。
WriteRequestToConsole
方法是不属于框架的自定义扩展。 如果对实现感兴趣,请考虑以下 C# 代码:
static class HttpResponseMessageExtensions
{
internal static void WriteRequestToConsole(this HttpResponseMessage response)
{
if (response is null)
{
return;
}
var request = response.RequestMessage;
Console.Write($"{request?.Method} ");
Console.Write($"{request?.RequestUri} ");
Console.WriteLine($"HTTP/{request?.Version}");
}
}
此功能用于以以下形式将请求详细信息写入控制台:
<HTTP Request Method> <Request URI> <HTTP/Version>
例如,对 "https://jsonplaceholder.typicode.com/todos/3"
终结点的 GET
请求输出以下消息:
GET https://jsonplaceholder.typicode.com/todos/3 HTTP/1.1
从 JSON 创建 HTTP GET 请求
https://jsonplaceholder.typicode.com/todos 终结点返回 Todo
对象的 JSON 数组。 其 JSON 结构类似于以下形式:
[
{
"userId": 1,
"id": 1,
"title": "example title",
"completed": false
},
{
"userId": 1,
"id": 2,
"title": "another example title",
"completed": true
},
]
C# Todo
对象定义如下:
public record class Todo(
int? UserId = null,
int? Id = null,
string? Title = null,
bool? Completed = null);
它是 record class
类型,具有可选的 Id
、Title
、Completed
和 UserId
属性。 有关 record
类型的详细信息,请参阅 C# 中的记录类型简介。 若要自动将 GET
请求反序列化为强类型 C# 对象,请使用属于 📦 System.Net.Http.Json NuGet 包的 GetFromJsonAsync 扩展方法。
static async Task GetFromJsonAsync(HttpClient httpClient)
{
var todos = await httpClient.GetFromJsonAsync<List<Todo>>(
"todos?userId=1&completed=false");
Console.WriteLine("GET https://jsonplaceholder.typicode.com/todos?userId=1&completed=false HTTP/1.1");
todos?.ForEach(Console.WriteLine);
Console.WriteLine();
// Expected output:
// GET https://jsonplaceholder.typicode.com/todos?userId=1&completed=false HTTP/1.1
// Todo { UserId = 1, Id = 1, Title = delectus aut autem, Completed = False }
// Todo { UserId = 1, Id = 2, Title = quis ut nam facilis et officia qui, Completed = False }
// Todo { UserId = 1, Id = 3, Title = fugiat veniam minus, Completed = False }
// Todo { UserId = 1, Id = 5, Title = laboriosam mollitia et enim quasi adipisci quia provident illum, Completed = False }
// Todo { UserId = 1, Id = 6, Title = qui ullam ratione quibusdam voluptatem quia omnis, Completed = False }
// Todo { UserId = 1, Id = 7, Title = illo expedita consequatur quia in, Completed = False }
// Todo { UserId = 1, Id = 9, Title = molestiae perspiciatis ipsa, Completed = False }
// Todo { UserId = 1, Id = 13, Title = et doloremque nulla, Completed = False }
// Todo { UserId = 1, Id = 18, Title = dolorum est consequatur ea mollitia in culpa, Completed = False }
}
该代码完成以下任务:
向
"https://jsonplaceholder.typicode.com/todos?userId=1&completed=false"
发出GET
请求。查询字符串表示请求的筛选条件。 命令成功后,响应会自动反序列化为
List<Todo>
对象。将请求详细信息与每个
Todo
对象一起写入控制台。
使用 HTTP POST 请求
POST
请求将数据发送到服务器进行处理。 请求的 Content-Type
标头表示正文发送的 MIME 类型。 若要在给定 HttpClient
实例和 Uri 对象的情况下发出 HTTP POST
请求,请使用 HttpClient.PostAsync 方法:
static async Task PostAsync(HttpClient httpClient)
{
using StringContent jsonContent = new(
JsonSerializer.Serialize(new
{
userId = 77,
id = 1,
title = "write code sample",
completed = false
}),
Encoding.UTF8,
"application/json");
using HttpResponseMessage response = await httpClient.PostAsync(
"todos",
jsonContent);
response.EnsureSuccessStatusCode()
.WriteRequestToConsole();
var jsonResponse = await response.Content.ReadAsStringAsync();
Console.WriteLine($"{jsonResponse}\n");
// Expected output:
// POST https://jsonplaceholder.typicode.com/todos HTTP/1.1
// {
// "userId": 77,
// "id": 201,
// "title": "write code sample",
// "completed": false
// }
}
该代码完成以下任务:
- 使用请求的 JSON 正文(
"application/json"
MIME 类型)准备 StringContent 实例。 - 向
"https://jsonplaceholder.typicode.com/todos"
终结点发出POST
请求。 - 确保响应成功,并将请求详细信息写入控制台。
- 将响应正文作为字符串写入控制台。
将 HTTP POST 请求创建为 JSON
要自动将 POST
请求参数序列化并将响应反序列化为强类型 C# 对象,请使用 PostAsJsonAsync NuGet 包中的 扩展方法。
static async Task PostAsJsonAsync(HttpClient httpClient)
{
using HttpResponseMessage response = await httpClient.PostAsJsonAsync(
"todos",
new Todo(UserId: 9, Id: 99, Title: "Show extensions", Completed: false));
response.EnsureSuccessStatusCode()
.WriteRequestToConsole();
var todo = await response.Content.ReadFromJsonAsync<Todo>();
Console.WriteLine($"{todo}\n");
// Expected output:
// POST https://jsonplaceholder.typicode.com/todos HTTP/1.1
// Todo { UserId = 9, Id = 201, Title = Show extensions, Completed = False }
}
该代码完成以下任务:
- 将
Todo
实例序列化为 JSON,并向"https://jsonplaceholder.typicode.com/todos"
终结点发出POST
请求。 - 确保响应成功,并将请求详细信息写入控制台。
- 将响应正文反序列化为
Todo
实例,并将Todo
对象写入控制台。
使用 HTTP PUT 请求
PUT
请求方法替换现有资源,或使用请求正文有效负载创建新的资源。 若要在给定 HttpClient
实例和 Uri 对象的情况下发出 HTTP PUT
请求,请使用 HttpClient.PutAsync 方法:
static async Task PutAsync(HttpClient httpClient)
{
using StringContent jsonContent = new(
JsonSerializer.Serialize(new
{
userId = 1,
id = 1,
title = "foo bar",
completed = false
}),
Encoding.UTF8,
"application/json");
using HttpResponseMessage response = await httpClient.PutAsync(
"todos/1",
jsonContent);
response.EnsureSuccessStatusCode()
.WriteRequestToConsole();
var jsonResponse = await response.Content.ReadAsStringAsync();
Console.WriteLine($"{jsonResponse}\n");
// Expected output:
// PUT https://jsonplaceholder.typicode.com/todos/1 HTTP/1.1
// {
// "userId": 1,
// "id": 1,
// "title": "foo bar",
// "completed": false
// }
}
该代码完成以下任务:
- 使用请求的 JSON 正文(
"application/json"
MIME 类型)准备 StringContent 实例。 - 向
"https://jsonplaceholder.typicode.com/todos/1"
终结点发出PUT
请求。 - 确保响应成功,并使用 JSON 响应正文将请求详细信息写入控制台。
将 HTTP PUT 请求创建为 JSON
要自动将 PUT
请求参数序列化并将响应反序列化为强类型 C# 对象,请使用 PutAsJsonAsync NuGet 包中的 扩展方法。
static async Task PutAsJsonAsync(HttpClient httpClient)
{
using HttpResponseMessage response = await httpClient.PutAsJsonAsync(
"todos/5",
new Todo(Title: "partially update todo", Completed: true));
response.EnsureSuccessStatusCode()
.WriteRequestToConsole();
var todo = await response.Content.ReadFromJsonAsync<Todo>();
Console.WriteLine($"{todo}\n");
// Expected output:
// PUT https://jsonplaceholder.typicode.com/todos/5 HTTP/1.1
// Todo { UserId = , Id = 5, Title = partially update todo, Completed = True }
}
该代码完成以下任务:
- 将
Todo
实例序列化为 JSON,并向"https://jsonplaceholder.typicode.com/todos/5"
终结点发出PUT
请求。 - 确保响应成功,并将请求详细信息写入控制台。
- 将响应正文反序列化为
Todo
实例,并将Todo
对象写入控制台。
使用 HTTP PATCH 请求
PATCH
请求是对现有资源的部分更新。 此请求不会创建新资源,并且不打算替换现有资源。 相反,此方法仅部分更新资源。 若要在给定 HttpClient
实例和 Uri 对象的情况下发出 HTTP PATCH
请求,请使用 HttpClient.PatchAsync 方法:
static async Task PatchAsync(HttpClient httpClient)
{
using StringContent jsonContent = new(
JsonSerializer.Serialize(new
{
completed = true
}),
Encoding.UTF8,
"application/json");
using HttpResponseMessage response = await httpClient.PatchAsync(
"todos/1",
jsonContent);
response.EnsureSuccessStatusCode()
.WriteRequestToConsole();
var jsonResponse = await response.Content.ReadAsStringAsync();
Console.WriteLine($"{jsonResponse}\n");
// Expected output
// PATCH https://jsonplaceholder.typicode.com/todos/1 HTTP/1.1
// {
// "userId": 1,
// "id": 1,
// "title": "delectus aut autem",
// "completed": true
// }
}
该代码完成以下任务:
- 使用请求的 JSON 正文(
"application/json"
MIME 类型)准备 StringContent 实例。 - 向
"https://jsonplaceholder.typicode.com/todos/1"
终结点发出PATCH
请求。 - 确保响应成功,并使用 JSON 响应正文将请求详细信息写入控制台。
PATCH
NuGet 包中没有针对 System.Net.Http.Json
请求的扩展方法。
使用 HTTP DELETE 请求
DELETE
请求会删除现有资源,并且请求幂等,但不安全。 对同一资源的多个 DELETE
请求产生相同的结果,但请求会影响资源的状态。 若要在给定 HttpClient
实例和 Uri 对象的情况下发出 HTTP DELETE
请求,请使用 HttpClient.DeleteAsync 方法:
static async Task DeleteAsync(HttpClient httpClient)
{
using HttpResponseMessage response = await httpClient.DeleteAsync("todos/1");
response.EnsureSuccessStatusCode()
.WriteRequestToConsole();
var jsonResponse = await response.Content.ReadAsStringAsync();
Console.WriteLine($"{jsonResponse}\n");
// Expected output
// DELETE https://jsonplaceholder.typicode.com/todos/1 HTTP/1.1
// {}
}
该代码完成以下任务:
- 向
"https://jsonplaceholder.typicode.com/todos/1"
终结点发出DELETE
请求。 - 确保响应成功,并将请求详细信息写入控制台。
提示
对 DELETE
请求的响应(就像 PUT
请求一样)可能或可能不包含正文。
了解 HTTP HEAD 请求
HEAD
请求类似于请求 GET
。 此请求仅返回与资源关联的标头,而不是返回资源。 对 HEAD
请求的响应不会返回内容。 若要为给定 HttpClient
实例和 Uri 对象发出 HTTP HEAD
请求,请使用 HttpClient.SendAsync 方法,并将 HttpMethod 类型设置为 HttpMethod.Head
:
static async Task HeadAsync(HttpClient httpClient)
{
using HttpRequestMessage request = new(
HttpMethod.Head,
"https://www.example.com");
using HttpResponseMessage response = await httpClient.SendAsync(request);
response.EnsureSuccessStatusCode()
.WriteRequestToConsole();
foreach (var header in response.Headers)
{
Console.WriteLine($"{header.Key}: {string.Join(", ", header.Value)}");
}
Console.WriteLine();
// Expected output:
// HEAD https://www.example.com/ HTTP/1.1
// Accept-Ranges: bytes
// Age: 550374
// Cache-Control: max-age=604800
// Date: Wed, 10 Aug 2022 17:24:55 GMT
// ETag: "3147526947"
// Server: ECS, (cha / 80E2)
// X-Cache: HIT
}
该代码完成以下任务:
- 向
"https://www.example.com/"
终结点发出HEAD
请求。 - 确保响应成功,并将请求详细信息写入控制台。
- 循环访问所有响应标头,并将每个标头写入控制台。
了解 HTTP OPTIONS 请求
OPTIONS
请求用于标识服务器或终结点支持哪些 HTTP 方法。 若要为给定 HttpClient
实例和 Uri 对象发出 HTTP OPTIONS
请求,请使用 HttpClient.SendAsync 方法,并将 HttpMethod 类型设置为 HttpMethod.Options
:
static async Task OptionsAsync(HttpClient httpClient)
{
using HttpRequestMessage request = new(
HttpMethod.Options,
"https://www.example.com");
using HttpResponseMessage response = await httpClient.SendAsync(request);
response.EnsureSuccessStatusCode()
.WriteRequestToConsole();
foreach (var header in response.Content.Headers)
{
Console.WriteLine($"{header.Key}: {string.Join(", ", header.Value)}");
}
Console.WriteLine();
// Expected output
// OPTIONS https://www.example.com/ HTTP/1.1
// Allow: OPTIONS, GET, HEAD, POST
// Content-Type: text/html; charset=utf-8
// Expires: Wed, 17 Aug 2022 17:28:42 GMT
// Content-Length: 0
}
该代码完成以下任务:
- 将
OPTIONS
HTTP 请求发送到"https://www.example.com/"
终结点。 - 确保响应成功,并将请求详细信息写入控制台。
- 循环访问所有响应内容标头,并将每个标头写入控制台。
了解 HTTP TRACE 请求
TRACE
请求可用于调试,因为它提供请求消息的应用程序级环回。 要发出 HTTP TRACE
请求,请使用 HttpMethod.Trace
类型创建 HttpRequestMessage:
using HttpRequestMessage request = new(
HttpMethod.Trace,
"{ValidRequestUri}");
注意
并非所有 HTTP 服务器都支持 TRACE
HTTP 方法。 如果不明智地使用此方法,则可能会公开安全漏洞。 有关详细信息,请参阅 Open Web Application Security Project (OWASP):跨站点跟踪。
处理 HTTP 响应
处理 HTTP 响应时,可以与 HttpResponseMessage 类型进行交互。 为评估响应的有效性,使用了多个成员。 HTTP 状态代码在 HttpResponseMessage.StatusCode 属性中可用。
假设你发送了给定客户端实例的请求:
using HttpResponseMessage response = await httpClient.SendAsync(request);
若要确保 response
是 OK
(HTTP 状态代码 200),可以按照以下示例评估其值:
if (response is { StatusCode: HttpStatusCode.OK })
{
// Omitted for brevity...
}
还有其他 HTTP 状态代码表示成功的响应,例如CREATED
(HTTP 状态代码 201)、ACCEPTED
(HTTP 状态代码 202)、NO CONTENT
(HTTP 状态代码 204)和 RESET CONTENT
(HTTP 状态代码 205)。 也可使用 HttpResponseMessage.IsSuccessStatusCode 属性评估这些代码,这可确保响应状态代码在 200-299 范围内:
if (response.IsSuccessStatusCode)
{
// Omitted for brevity...
}
如果需要让框架引发 HttpRequestException 错误,可以调用 HttpResponseMessage.EnsureSuccessStatusCode() 方法:
response.EnsureSuccessStatusCode();
如果响应状态代码不在 200-299 范围内,则此代码将引发 HttpRequestException
错误。
探索 HTTP 有效内容响应
使用有效的响应,可以使用 Content 属性访问响应正文。 正文作为 HttpContent 实例提供,可用于以流、字节数组或字符串的形式访问正文。
以下代码使用 responseStream
对象读取响应正文:
await using Stream responseStream =
await response.Content.ReadAsStreamAsync();
可以使用不同的对象来读取响应正文。 使用 responseByteArray
对象读取响应正文:
byte[] responseByteArray = await response.Content.ReadAsByteArrayAsync();
使用 responseString
对象读取响应正文:
string responseString = await response.Content.ReadAsStringAsync();
知道 HTTP 终结点返回 JSON 时,可以使用 System.Net.Http.Json NuGet 包将响应正文反序列化为任何有效的 C# 对象:
T? result = await response.Content.ReadFromJsonAsync<T>();
在此代码中,result
值是反序列化为类型 T
的响应正文。
使用 HTTP 错误处理
HTTP 请求失败时,系统将引发 HttpRequestException 对象。 仅捕获异常可能还不够。 可能需要考虑处理其他可能引发的异常。 例如,调用代码可能会使用在请求完成之前取消的取消令牌。 在此方案中,可以捕获 TaskCanceledException 错误:
using var cts = new CancellationTokenSource();
try
{
// Assuming:
// httpClient.Timeout = TimeSpan.FromSeconds(10)
using var response = await httpClient.GetAsync(
"http://localhost:5001/sleepFor?seconds=100", cts.Token);
}
catch (OperationCanceledException ex) when (cts.IsCancellationRequested)
{
// When the token has been canceled, it is not a timeout.
Console.WriteLine($"Canceled: {ex.Message}");
}
同样,在发出 HTTP 请求时,如果服务器在超出 HttpClient.Timeout 值之前没有响应,则会引发相同的异常。 在此方案中,可以通过在捕获 TaskCanceledException 错误时评估 Exception.InnerException 属性来辨别超时的发生:
try
{
// Assuming:
// httpClient.Timeout = TimeSpan.FromSeconds(10)
using var response = await httpClient.GetAsync(
"http://localhost:5001/sleepFor?seconds=100");
}
catch (OperationCanceledException ex) when (ex.InnerException is TimeoutException tex)
{
Console.WriteLine($"Timed out: {ex.Message}, {tex.Message}");
}
在代码中,当内部异常是 TimeoutException 类型时,会发生超时,取消令牌不会取消请求。
若要在捕获 HttpRequestException 对象时评估 HTTP 状态代码,可以评估 HttpRequestException.StatusCode 属性:
try
{
// Assuming:
// httpClient.Timeout = TimeSpan.FromSeconds(10)
using var response = await httpClient.GetAsync(
"http://localhost:5001/doesNotExist");
response.EnsureSuccessStatusCode();
}
catch (HttpRequestException ex) when (ex is { StatusCode: HttpStatusCode.NotFound })
{
// Handle 404
Console.WriteLine($"Not found: {ex.Message}");
}
在代码中,调用 EnsureSuccessStatusCode() 方法以在响应未成功时引发异常。 HttpRequestException.StatusCode 属性随后被评估以确定响应是否是 404
(HTTP 状态代码 404)。 HttpClient
对象上有几个辅助方法,这些方法会自动代你调用 EnsureSuccessStatusCode
方法。
对于 HTTP 错误处理,请考虑以下 API:
提示
用于发出 HTTP 请求且不返回 HttpResponseMessage
类型的所有 HttpClient
方法代表你隐式调用 EnsureSuccessStatusCode
方法。
调用这些方法时,可以处理 HttpRequestException
对象并评估 HttpRequestException.StatusCode 属性以确定响应的 HTTP 状态代码:
try
{
// These extension methods will throw HttpRequestException
// with StatusCode set when the HTTP request status code isn't 2xx:
//
// GetByteArrayAsync
// GetStreamAsync
// GetStringAsync
using var stream = await httpClient.GetStreamAsync(
"https://localhost:5001/doesNotExists");
}
catch (HttpRequestException ex) when (ex is { StatusCode: HttpStatusCode.NotFound })
{
// Handle 404
Console.WriteLine($"Not found: {ex.Message}");
}
在某些情况下,可能需要在代码中抛出 HttpRequestException 对象。 HttpRequestException() 构造函数是公共的,你可以使用它引发包含自定义消息的异常:
try
{
using var response = await httpClient.GetAsync(
"https://localhost:5001/doesNotExists");
// Throw for anything higher than 400.
if (response is { StatusCode: >= HttpStatusCode.BadRequest })
{
throw new HttpRequestException(
"Something went wrong", inner: null, response.StatusCode);
}
}
catch (HttpRequestException ex) when (ex is { StatusCode: HttpStatusCode.NotFound })
{
Console.WriteLine($"Not found: {ex.Message}");
}
配置 HTTP 代理
可以采用以下两种方法之一配置 HTTP 代理。 在 HttpClient.DefaultProxy 属性上指定默认值。 或者,可以在 HttpClientHandler.Proxy 属性上指定代理。
使用全局默认代理
HttpClient.DefaultProxy
属性是一个静态属性,它确定所有 HttpClient
实例使用的默认代理(如果未在通过其构造函数传递的 HttpClientHandler 对象中显式设置代理)。
此属性返回的默认实例根据平台的不同规则集进行初始化:
- Windows:从环境变量读取代理配置;如果未定义变量,请从用户代理设置读取。
- macOS:从环境变量读取代理配置,或者如果未定义变量,请从系统代理设置读取。
- Linux:从环境变量读取代理配置;如果未定义变量,请初始化非配置实例以绕过所有地址。
基于 Windows 和 Unix 的平台上的 DefaultProxy
属性初始化使用以下环境变量:
HTTP_PROXY
:用于 HTTP 请求的代理服务器。HTTPS_PROXY
:用于 HTTPS 请求的代理服务器。ALL_PROXY
:未定义HTTP_PROXY
和/或HTTPS_PROXY
变量时用于 HTTP 和/或 HTTPS 请求的代理服务器。-
NO_PROXY
:要从代理中排除的主机名的逗号分隔列表。 星号不能作为通配符使用。 如果要匹配子域,请使用前导句点(.)。 示例:NO_PROXY=.example.com
(带前导期)匹配www.example.com
,但与example.com
不匹配。NO_PROXY=example.com
(没有前导期)与www.example.com
不匹配。 将来可能会重新审视这一行为,以便更好地匹配其他生态系统。
在环境变量区分大小写的系统上,变量名称可以是小写或全部大写。 首先检查小写名称。
代理服务器可以是主机名或 IP 地址,可以选择后跟冒号和端口号,也可以是 http
URL,(可选)包括用户名和密码进行代理身份验证。 URL 必须以 http
开头,而不是 https
,并且不能在主机名、IP 或端口之后包含任何文本。
为每个客户端配置代理
HttpClientHandler.Proxy 属性标识用于处理 Internet 资源请求的 WebProxy 对象。 若要指定不应使用代理,请将 Proxy
属性设置为 GlobalProxySelection.GetEmptyWebProxy() 方法返回的代理实例。
本地计算机或应用程序配置文件可能指定使用默认代理。 如果指定了 Proxy
属性,则 Proxy
属性中的代理设置将覆盖本地计算机或应用程序配置文件,处理程序使用指定的代理设置。 如果未在配置文件中指定代理,并且未指定 Proxy
属性,处理程序将使用从本地计算机继承的代理设置。 如果没有代理设置,则请求将直接发送到服务器。
HttpClientHandler 类使用从本地计算机设置继承的通配符分析代理绕过列表。 例如,HttpClientHandler
类会将来自浏览器的 "nt*"
绕过列表分析为 "nt.*"
的正则表达式。 因此,http://nt.com
的 URL 使用 HttpClientHandler
类绕过代理。
HttpClientHandler
类支持本地代理绕过。 如果满足以下任一条件,该类会将目标视为本地目标:
- 目标包含平面名称(URL 中不含句点 (.))。
- 目标包含环回地址(Loopback 或 IPv6Loopback),或者目标包含分配给本地计算机的 IPAddress 属性。
- 目标的域后缀与本地计算机的域后缀匹配,如 DomainName 属性中定义。
有关配置代理的详细信息,请参阅以下 API: