使用 HttpClient 类发出 HTTP 请求

本文介绍如何使用 HttpClient 类发出 HTTP 请求和处理响应。

重要

所有示例 HTTP 请求都以下面 URL 之一为目标:

HTTP 终结点通常返回 JavaScript 对象表示法 (JSON) 数据,但并不总是如此。 为方便起见,可选的 System.Net.Http.Json NuGet 包为使用 System.Text.Json 执行自动序列化和反序列化的 HttpClientHttpContent 提供多个扩展方法。 下面的示例提醒人们注意这些扩展的可用位置。

提示

本文中的所有源代码均可在 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 实例将使用基址。 若要应用其他配置,请考虑:

提示

或者,可使用工厂模式方法创建 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 实体正文和相应的内容标头。 对于需要正文的 HTTP 方法(或请求方法)POSTPUTPATCH,可使用 HttpContent 类来指定请求的正文。 大多数示例演示如何使用 JSON 有效负载准备 StringContent 子类,但还有针对其他内容 (MIME) 类型的其他子类。

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>

例如, GET 请求 https://jsonplaceholder.typicode.com/todos/3 输出以下消息:

GET https://jsonplaceholder.typicode.com/todos/3 HTTP/1.1

HTTP Get from JSON

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 类型,具有可选的 IdTitleCompletedUserId 属性。 有关 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 类型。 要在给定 HttpClientUri 的情况下发出 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 正文的 StringContent 实例(MIME 类型为 "application/json")。
  • "https://jsonplaceholder.typicode.com/todos" 发出 POST 请求。
  • 确保响应成功,并将请求详细信息写入控制台。
  • 将响应正文以字符串形式写入控制台。

HTTP Post as JSON

要自动将 POST 请求参数序列化并将响应反序列化为强类型 C# 对象,请使用 System.Net.Http.Json NuGet 包中的 PostAsJsonAsync 扩展方法。

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 正文的 StringContent 实例(MIME 类型为 "application/json")。
  • "https://jsonplaceholder.typicode.com/todos/1" 发出 PUT 请求。
  • 确保响应成功,并将请求详细信息和 JSON 响应正文写入控制台。

HTTP Put as JSON

要自动将 PUT 请求参数序列化并将响应反序列化为强类型 C# 对象,请使用 System.Net.Http.Json NuGet 包中的 PutAsJsonAsync 扩展方法。

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 正文的 StringContent 实例(MIME 类型为 "application/json")。
  • "https://jsonplaceholder.typicode.com/todos/1" 发出 PATCH 请求。
  • 确保响应成功,并将请求详细信息和 JSON 响应正文写入控制台。

System.Net.Http.Json NuGet 包中没有针对 PATCH 请求的扩展方法。

HTTP Delete

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
}

前面的代码:

  • "https://www.example.com/" 发送 OPTIONS HTTP 请求。
  • 确保响应成功,并将请求详细信息写入控制台。
  • 循环访问所有响应内容标头,将每个标头写入控制台。

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);

要确保 responseOK(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 实例形式提供,可用于访问流、字节数组或字符串形式的正文:

await using Stream responseStream =
    await response.Content.ReadAsStreamAsync();

在前面的代码中,responseStream 可用于读取响应正文。

byte[] responseByteArray = await response.Content.ReadAsByteArrayAsync();

在前面的代码中,responseByteArray 可用于读取响应正文。

string responseString = await response.Content.ReadAsStringAsync();

在前面的代码中,responseString 可用于读取响应正文。

最后,如果知道 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,请考虑以下 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}");
}

在某些情况下,可能需要在代码中引发 HttpRequestExceptionHttpRequestException() 构造函数是公共的,你可以使用它引发异常,自定义消息为:

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 是一个静态属性,如果在通过其构造函数传递的 HttpClientHandler 中未显式设置代理,则该属性将确定所有 HttpClient 实例使用的默认代理。

此属性返回的默认实例将基于平台按照一组不同的规则进行初始化:

  • 对于 Windows:从环境变量读取代理配置,或者,如果未定义这些变量,则从用户的代理设置中进行读取。
  • 对于 macOS:从环境变量读取代理配置,或者,如果未定义这些变量,则从系统的代理设置中进行读取。
  • 对于 Linux:从环境变量读取代理配置,或者,如果未定义这些变量,则此属性将初始化绕过所有地址的非配置实例。

基于 Windows 和 Unix 的平台上用于 DefaultProxy 初始化的环境变量是:

  • HTTP_PROXY:用于 HTTP 请求的代理服务器。
  • HTTPS_PROXY:用于 HTTPS 请求的代理服务器。
  • ALL_PROXY:用于 HTTP 和/或 HTTPS 请求的代理服务器(如果未定义 HTTP_PROXY 和/或 HTTPS_PROXY)。
  • 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 类支持本地代理绕过。 如果满足以下任一条件,该类会将目标视为本地目标:

  1. 目标包含平面名称(URL 中不含点)。
  2. 目标包含环回地址(LoopbackIPv6Loopback),或者目标包含分配给本地计算机的 IPAddress
  3. 目标的域后缀匹配本地计算机的域后缀 (DomainName)。

有关配置代理的详细信息,请参阅:

另请参阅