HttpWebRequest 到 HttpClient 迁移指南
本文旨在指导开发人员完成从 HttpWebRequest、ServicePoint 和 ServicePointManager迁移到 HttpClient 的过程。 迁移是必要的,因为旧的 API 已经过时,而且 HttpClient 提供了许多好处,包括改进的性能、更好的资源管理以及更现代和灵活的 API 设计。 按照本文档中所述的步骤,开发人员将能够顺利转换其代码库,并充分利用 HttpClient 提供的功能。
警告
从 HttpWebRequest
、ServicePoint
和 ServicePointManager
迁移到 HttpClient
不仅仅是一种“锦上添花”的性能改进。 至关重要的是要明白,一旦迁移到 .NET (Core),现有的 WebRequest
逻辑的性能可能会显著下降。 这是因为 WebRequest
维护为最低兼容性层,这意味着它缺少许多优化,例如在许多情况下的连接重用。 因此,过渡到 HttpClient
对于确保应用程序的性能和资源管理达到现代标准至关重要。
从 HttpWebRequest 迁移到 HttpClient
我们从几个示例开始:
使用 HttpWebRequest 的简单 GET 请求
下面是一个代码的示例:
HttpWebRequest request = WebRequest.CreateHttp(uri);
using WebResponse response = await request.GetResponseAsync();
使用 HttpClient 的简单 GET 请求
下面是一个代码的示例:
HttpClient client = new();
using HttpResponseMessage message = await client.GetAsync(uri);
使用 HttpWebRequest 的简单 POST 请求
下面是一个代码的示例:
HttpWebRequest request = WebRequest.CreateHttp(uri);
request.Method = "POST";
request.ContentType = "text/plain";
await using Stream stream = await request.GetRequestStreamAsync();
await stream.WriteAsync("Hello World!"u8.ToArray());
using WebResponse response = await request.GetResponseAsync();
使用 HttpClient 的简单 POST 请求
下面是一个代码的示例:
HttpClient client = new();
using HttpResponseMessage responseMessage = await client.PostAsync(uri, new StringContent("Hello World!"));
HttpWebRequest 到 HttpClient、SocketsHttpHandler 迁移指南
HttpWebRequest旧 API | 新 API | 备注 |
---|---|---|
Accept |
Accept | 示例:设置请求标头。 |
Address |
RequestUri | 示例:提取重定向 URI。 |
AllowAutoRedirect |
AllowAutoRedirect | 示例:设置 SocketsHttpHandler 属性。 |
AllowReadStreamBuffering |
无直接等效 API | 缓冲属性的使用。 |
AllowWriteStreamBuffering |
无直接等效 API | 缓冲属性的使用。 |
AuthenticationLevel |
无直接等效 API | 示例:启用相互身份验证。 |
AutomaticDecompression |
AutomaticDecompression | 示例:设置 SocketsHttpHandler 属性。 |
CachePolicy |
无直接等效 API | 示例:应用 CachePolicy 标头。 |
ClientCertificates |
SslOptions"ClientCertificates | HttpClient 中证书相关属性的用法。 |
Connection |
Connection | 示例:设置请求标头。 |
ConnectionGroupName |
无等效 API | 无解决方法 |
ContentLength |
ContentLength | 示例:设置内容标头。 |
ContentType |
ContentType | 示例:设置内容标头。 |
ContinueDelegate |
无等效 API | 无解决方法。 |
ContinueTimeout |
Expect100ContinueTimeout | 示例:设置 SocketsHttpHandler 属性。 |
CookieContainer |
CookieContainer | 示例:设置 SocketsHttpHandler 属性。 |
Credentials |
Credentials | 示例:设置 SocketsHttpHandler 属性。 |
Date |
Date | 示例:设置请求标头。 |
DefaultCachePolicy |
无直接等效 API | 示例:应用 CachePolicy 标头。 |
DefaultMaximumErrorResponseLength |
无直接等效 API | 示例:在 HttpClient 中设置 MaximumErrorResponseLength。 |
DefaultMaximumResponseHeadersLength |
无等效 API | 可以改用 MaxResponseHeadersLength。 |
DefaultWebProxy |
无等效 API | 可以改用 Proxy。 |
Expect |
Expect | 示例:设置请求标头。 |
HaveResponse |
无等效 API | 通过具有 HttpResponseMessage 实例来暗示。 |
Headers |
Headers | 示例:设置请求标头。 |
Host |
Host | 示例:设置请求标头。 |
IfModifiedSince |
IfModifiedSince | 示例:设置请求标头。 |
ImpersonationLevel |
无直接等效 API | 示例:更改 ImpersonationLevel。 |
KeepAlive |
无直接等效 API | 示例:设置请求标头。 |
MaximumAutomaticRedirections |
MaxAutomaticRedirections | 示例:设置 SocketsHttpHandler 属性。 |
MaximumResponseHeadersLength |
MaxResponseHeadersLength | 示例:设置 SocketsHttpHandler 属性。 |
MediaType |
无直接等效 API | 示例:设置内容标头。 |
Method |
Method | 示例:HttpRequestMessage 属性的使用。 |
Pipelined |
无等效 API | HttpClient 不支持管道传送。 |
PreAuthenticate |
PreAuthenticate | |
ProtocolVersion |
HttpRequestMessage.Version |
示例:HttpRequestMessage 属性的使用。 |
Proxy |
Proxy | 示例:设置 SocketsHttpHandler 属性。 |
ReadWriteTimeout |
无直接等效 API | SocketsHttpHandler 和 ConnectCallback 的使用。 |
Referer |
Referrer | 示例:设置请求标头。 |
RequestUri |
RequestUri | 示例:HttpRequestMessage 属性的使用。 |
SendChunked |
TransferEncodingChunked | 示例:设置请求标头。 |
ServerCertificateValidationCallback |
SslOptions"RemoteCertificateValidationCallback | 示例:设置 SocketsHttpHandler 属性。 |
ServicePoint |
无等效 API | ServicePoint 不是 HttpClient 的一部分。 |
SupportsCookieContainer |
无等效 API | 对于 HttpClient ,这总是 true 。 |
Timeout |
Timeout | |
TransferEncoding |
TransferEncoding | 示例:设置请求标头。 |
UnsafeAuthenticatedConnectionSharing |
无等效 API | 无解决方法 |
UseDefaultCredentials |
无直接等效 API | 示例:设置 SocketsHttpHandler 属性。 |
UserAgent |
UserAgent | 示例:设置请求标头。 |
迁移 ServicePoint(Manager) 使用情况
你应该知道,ServicePointManager
是一个静态类,这意味着对其属性所做的任何更改都会对应用程序中所有新创建的 ServicePoint
对象产生全局影响。 例如,修改 ConnectionLimit
或 Expect100Continue
这样的属性时,影响每个新的 ServicePoint 实例的属性。
警告
在新式 .NET 中,HttpClient
不考虑在 ServicePointManager
上设置的任何配置。
ServicePointManager 属性映射
ServicePointManager旧 API | 新 API | 备注 |
---|---|---|
CheckCertificateRevocationList |
SslOptions"CertificateRevocationCheckMode | 示例:使用 SocketsHttpHandler 启用 CRL 检查。 |
DefaultConnectionLimit |
MaxConnectionsPerServer | 示例:设置 SocketsHttpHandler 属性。 |
DnsRefreshTimeout |
无等效 API | 示例:启用 Dns 轮循机制。 |
EnableDnsRoundRobin |
无等效 API | 示例:启用 Dns 轮循机制。 |
EncryptionPolicy |
SslOptions"EncryptionPolicy | 示例:设置 SocketsHttpHandler 属性。 |
Expect100Continue |
ExpectContinue | 示例:设置请求标头。 |
MaxServicePointIdleTime |
PooledConnectionIdleTimeout | 示例:设置 SocketsHttpHandler 属性。 |
MaxServicePoints |
无等效 API | ServicePoint 不是 HttpClient 的一部分。 |
ReusePort |
无直接等效 API | SocketsHttpHandler 和 ConnectCallback 的使用。 |
SecurityProtocol |
SslOptions"EnabledSslProtocols | 示例:设置 SocketsHttpHandler 属性。 |
ServerCertificateValidationCallback |
SslOptions"RemoteCertificateValidationCallback | 两者都是 RemoteCertificateValidationCallback |
UseNagleAlgorithm |
无直接等效 API | SocketsHttpHandler 和 ConnectCallback 的使用。 |
警告
在新式 .NET 中,UseNagleAlgorithm
和 Expect100Continue
属性的默认值设置为 false
。 这些值在 .NET Framework 中默认为 true
。
ServicePointManager 方法映射
ServicePointManager旧 API | 新 API | 备注 |
---|---|---|
FindServicePoint |
无等效 API | 无解决方法 |
SetTcpKeepAlive |
无直接等效 API | SocketsHttpHandler 和 ConnectCallback 的使用。 |
ServicePoint 属性映射
ServicePoint旧 API | 新 API | 备注 |
---|---|---|
Address |
HttpRequestMessage.RequestUri |
这是请求 URI,可在 HttpRequestMessage 下找到此信息。 |
BindIPEndPointDelegate |
无直接等效 API | SocketsHttpHandler 和 ConnectCallback 的使用。 |
Certificate |
无直接等效 API | 可以从 RemoteCertificateValidationCallback 中提取此信息。 示例:提取证书。 |
ClientCertificate |
无等效 API | 示例:启用相互身份验证。 |
ConnectionLeaseTimeout |
SocketsHttpHandler.PooledConnectionLifetime |
HttpClient 中的等效设置 |
ConnectionLimit |
MaxConnectionsPerServer | 示例:设置 SocketsHttpHandler 属性。 |
ConnectionName |
无等效 API | 无解决方法 |
CurrentConnections |
无等效 API | 请参阅 .NET 中的网络遥测。 |
Expect100Continue |
ExpectContinue | 示例:设置请求标头。 |
IdleSince |
无等效 API | 无解决方法 |
MaxIdleTime |
PooledConnectionIdleTimeout | 示例:设置 SocketsHttpHandler 属性。 |
ProtocolVersion |
HttpRequestMessage.Version |
示例:HttpRequestMessage 属性的使用。 |
ReceiveBufferSize |
无直接等效 API | SocketsHttpHandler 和 ConnectCallback 的使用。 |
SupportsPipelining |
无等效 API | HttpClient 不支持管道传送。 |
UseNagleAlgorithm |
无直接等效 API | SocketsHttpHandler 和 ConnectCallback 的使用。 |
ServicePoint 方法映射
ServicePoint旧 API | 新 API | 备注 |
---|---|---|
CloseConnectionGroup |
无等效项 | 无解决方法 |
SetTcpKeepAlive |
无直接等效 API | SocketsHttpHandler 和 ConnectCallback 的使用。 |
HttpClient 和 HttpRequestMessage 属性的用法
在 .NET 中使用 HttpClient 时,可以访问各种属性,以便配置和自定义 HTTP 请求和响应。 了解这些属性有助于充分利用 HttpClient,并确保应用程序与 Web 服务高效安全地通信。
示例:HttpRequestMessage 属性的使用
下面是如何将 HttpClient 和 HttpRequestMessage 一起使用的示例:
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://example.com"); // Method and RequestUri usage
var request = new HttpRequestMessage() // Alternative way to set RequestUri and Method
{
RequestUri = new Uri("https://example.com"),
Method = HttpMethod.Post
};
request.Headers.Add("Custom-Header", "value");
request.Content = new StringContent("somestring");
using var response = await client.SendAsync(request);
var protocolVersion = response.RequestMessage.Version; // Fetch `ProtocolVersion`.
示例:提取重定向 URI
下面是如何提取重定向 URI 的示例(与 HttpWebRequest.Address
相同):
var client = new HttpClient();
using var response = await client.GetAsync(uri);
var redirectedUri = response.RequestMessage.RequestUri;
SocketsHttpHandler 和 ConnectCallback 的使用
SocketsHttpHandler
中的 ConnectCallback
属性允许开发人员自定义建立 TCP 连接的过程。 这对于需要控制 DNS 解析或在连接上应用特定套接字选项的方案非常有用。 通过使用 ConnectCallback
,可以在连接过程被 HttpClient
使用之前对其进行拦截和修改。
示例:将 IP 地址绑定到套接字
在使用 HttpWebRequest
的旧方法中,你可能使用自定义逻辑将特定的 IP 地址绑定到套接字。 下面介绍使用 HttpClient
和 ConnectCallback
实现类似功能的方法:
旧代码使用 HttpWebRequest
:
HttpWebRequest request = WebRequest.CreateHttp(uri);
request.ServicePoint.BindIPEndPointDelegate = (servicePoint, remoteEndPoint, retryCount) =>
{
// Bind to a specific IP address
IPAddress localAddress = IPAddress.Parse("192.168.1.100");
return new IPEndPoint(localAddress, 0);
};
using HttpWebResponse response = (HttpWebResponse)request.GetResponse();
新代码使用 HttpClient
和 ConnectCallback
:
var handler = new SocketsHttpHandler
{
ConnectCallback = async (context, cancellationToken) =>
{
// Bind to a specific IP address
IPAddress localAddress = IPAddress.Parse("192.168.1.100");
var socket = new Socket(localAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
try
{
socket.Bind(new IPEndPoint(localAddress, 0));
await socket.ConnectAsync(context.DnsEndPoint, cancellationToken);
return new NetworkStream(socket, ownsSocket: true);
}
catch
{
socket.Dispose();
throw;
}
}
};
var client = new HttpClient(handler);
using var response = await client.GetAsync(uri);
示例:应用特定套接字选项
如果需要应用特定的套接字选项,例如启用“保持 TCP 连接”,则可以在 HttpClient
使用套接字之前使用 ConnectCallback
配置套接字。 事实上,ConnectCallback
在配置套接字选项方面更灵活。
旧代码使用 HttpWebRequest
:
ServicePointManager.ReusePort = true;
HttpWebRequest request = WebRequest.CreateHttp(uri);
request.ServicePoint.SetTcpKeepAlive(true, 60000, 1000);
request.ServicePoint.ReceiveBufferSize = 8192;
request.ServicePoint.UseNagleAlgorithm = false;
using HttpWebResponse response = (HttpWebResponse)request.GetResponse();
新代码使用 HttpClient
和 ConnectCallback
:
var handler = new SocketsHttpHandler
{
ConnectCallback = async (context, cancellationToken) =>
{
var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
try
{
// Setting TCP Keep Alive
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, 60);
socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, 1);
// Setting ReceiveBufferSize
socket.ReceiveBufferSize = 8192;
// Enabling ReusePort
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseUnicastPort, true);
// Disabling Nagle Algorithm
socket.NoDelay = true;
await socket.ConnectAsync(context.DnsEndPoint, cancellationToken);
return new NetworkStream(socket, ownsSocket: true);
}
catch
{
socket.Dispose();
throw;
}
}
};
var client = new HttpClient(handler);
using var response = await client.GetAsync(uri);
示例:启用 DNS 轮循机制
DNS 轮循机制是一种通过在与单个域名相关的 IP 地址列表中轮换来在多个服务器上分配网络流量的技术。 这有助于实现负载均衡并提高服务的可用性。 使用 HttpClient 时,可以通过手动处理 DNS 解析并使用 SocketsHttpHandler 的 ConnectCallback 属性在 IP 地址之间轮换来实现 DNS 轮循机制。
若要使用 HttpClient 启用 DNS 轮循机制,可以使用 ConnectCallback 属性手动解析 DNS 条目并通过 IP 地址轮循。 下面是 HttpWebRequest
和 HttpClient
的示例:
旧代码使用 HttpWebRequest
:
ServicePointManager.DnsRefreshTimeout = 60000;
ServicePointManager.EnableDnsRoundRobin = true;
HttpWebRequest request = WebRequest.CreateHttp(uri);
using HttpWebResponse response = (HttpWebResponse)request.GetResponse();
在较旧的 HttpWebRequest
API 中,由于内置了对该功能的支持,因此启用 DNS 轮循机制非常简单。 但是,较新的 HttpClient
API 不提供相同的内置功能。 尽管如此,可以通过实现一个手动轮换 DNS 解析返回的 IP 地址的 DnsRoundRobinConnector
来实现类似的行为。
使用 HttpClient
的新代码:
// This is available as NuGet Package: https://www.nuget.org/packages/DnsRoundRobin/
// The original source code can be found also here: https://github.com/MihaZupan/DnsRoundRobin
public sealed class DnsRoundRobinConnector : IDisposable
可以在此处找到 DnsRoundRobinConnector
的实现。
DnsRoundRobinConnector
使用情况:
private static readonly DnsRoundRobinConnector s_roundRobinConnector = new(
dnsRefreshInterval: TimeSpan.FromSeconds(10),
endpointConnectTimeout: TimeSpan.FromSeconds(5));
static async Task DnsRoundRobinConnectAsync()
{
var handler = new SocketsHttpHandler
{
ConnectCallback = async (context, cancellation) =>
{
Socket socket = await DnsRoundRobinConnector.Shared.ConnectAsync(context.DnsEndPoint, cancellation);
// Or you can create and use your custom DnsRoundRobinConnector instance
// Socket socket = await s_roundRobinConnector.ConnectAsync(context.DnsEndPoint, cancellation);
return new NetworkStream(socket, ownsSocket: true);
}
};
var client = new HttpClient(handler);
HttpResponseMessage response = await client.GetAsync(Uri);
}
示例:设置 SocketsHttpHandler 属性
SocketsHttpHandler 是 .NET 中的一个强大且灵活的处理程序,提供用于管理 HTTP 连接的高级配置选项。 通过设置 SocketsHttpHandler 的各种属性,可以微调 HTTP 客户端的行为以满足特定要求,例如性能优化、安全增强和自定义连接处理。
下面是有关如何使用各种属性配置 SocketsHttpHandler 并将其用于 HttpClient 的示例:
var cookieContainer = new CookieContainer();
cookieContainer.Add(new Cookie("cookieName", "cookieValue"));
var handler = new SocketsHttpHandler
{
AllowAutoRedirect = true,
AutomaticDecompression = DecompressionMethods.All,
Expect100ContinueTimeout = TimeSpan.FromSeconds(1),
CookieContainer = cookieContainer,
Credentials = new NetworkCredential("user", "pass"),
MaxAutomaticRedirections = 10,
MaxResponseHeadersLength = 1,
Proxy = new WebProxy("http://proxyserver:8080"), // Don't forget to set UseProxy
UseProxy = true,
};
var client = new HttpClient(handler);
using var response = await client.GetAsync(uri);
示例:更改 ImpersonationLevel
此功能特定于某些平台,有些过时。 如果需要解决方法,可以参考代码的此部分。
HttpClient 中证书和 TLS 相关属性的用法
使用 HttpClient
时,可能需要出于各种目的处理客户端证书,例如自定义验证服务器证书或获取服务器证书。 HttpClient
提供了几个属性和选项来有效地管理证书。
示例:使用 SocketsHttpHandler 检查证书吊销列表
SocketsHttpHandler.SslOptions
中的 CheckCertificateRevocationList
属性允许开发人员在 SSL/TLS 握手期间启用或禁用对证书吊销列表 (CRL) 的检查。 启用此属性可确保客户端验证服务器证书是否已吊销,从而提高连接的安全性。
旧代码使用 HttpWebRequest
:
ServicePointManager.CheckCertificateRevocationList = true;
HttpWebRequest request = WebRequest.CreateHttp(uri);
using HttpWebResponse response = (HttpWebResponse)request.GetResponse();
使用 HttpClient
的新代码:
bool checkCertificateRevocationList = true;
var handler = new SocketsHttpHandler
{
SslOptions =
{
CertificateRevocationCheckMode = checkCertificateRevocationList ? X509RevocationMode.Online : X509RevocationMode.NoCheck,
}
};
var client = new HttpClient(handler);
using var response = await client.GetAsync(uri);
示例:提取证书
若要从 HttpClient
中的 RemoteCertificateValidationCallback
提取证书,可以使用 HttpClientHandler
或 SocketsHttpHandler.SslOptions
的 ServerCertificateCustomValidationCallback
属性。 此回调允许在 SSL/TLS 握手期间检查服务器的证书。
旧代码使用 HttpWebRequest
:
HttpWebRequest request = WebRequest.CreateHttp(uri);
using HttpWebResponse response = (HttpWebResponse)request.GetResponse();
X509Certificate? serverCertificate = request.ServicePoint.Certificate;
使用 HttpClient
的新代码:
X509Certificate? serverCertificate = null;
var handler = new SocketsHttpHandler
{
SslOptions = new SslClientAuthenticationOptions
{
RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
{
serverCertificate = certificate;
// Leave the validation as-is.
return sslPolicyErrors == SslPolicyErrors.None;
}
}
};
var client = new HttpClient(handler);
using var response = await client.GetAsync("https://example.com");
示例:启用相互身份验证
相互身份验证(也称为双向 SSL 或客户端证书身份验证)是一种客户端和服务器相互进行身份验证的安全过程。 这确保了双方都是他们声称的人,为敏感通信提供了额外的安全层。 在 HttpClient
中,可以通过配置 HttpClientHandler
或 SocketsHttpHandler
来启用相互身份验证,以包含客户端证书并验证服务器的证书。
要启用相互身份验证,请执行以下步骤:
- 加载客户端证书。
- 配置 HttpClientHandler 或 SocketsHttpHandler 以包含客户端证书。
- 如果需要自定义验证,请设置服务器证书验证回调。
下面是使用 SocketsHttpHandler 的示例:
var handler = new SocketsHttpHandler
{
SslOptions = new SslClientAuthenticationOptions
{
ClientCertificates = new X509CertificateCollection
{
// Load the client certificate from a file
new X509Certificate2("path_to_certificate.pfx", "certificate_password")
},
RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
{
// Custom validation logic for the server certificate
return sslPolicyErrors == SslPolicyErrors.None;
}
}
};
var client = new HttpClient(handler);
using var response = await client.GetAsync(uri);
标头属性的用法
标头在 HTTP 通信中起着关键作用,提供有关请求和响应的基本元数据。 在 .NET 中使用 HttpClient
时,可以设置和管理各种标头属性,来控制 HTTP 请求和响应的行为。 了解如何有效地使用这些标头属性有助于确保应用程序与 Web 服务高效安全地通信。
设置请求标头
请求标头用于向服务器提供有关所发出请求的其他信息。 常见用例包括指定内容类型、设置身份验证令牌和添加自定义标头。 可以使用 HttpClient
的 DefaultRequestHeaders
属性或 HttpRequestMessage
的 Headers 属性设置请求标头。
示例:设置自定义请求标头
在 HttpClient 中设置默认自定义请求标头
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Custom-Header", "value");
在 HttpRequestMessage 中设置自定义请求标头
var request = new HttpRequestMessage(HttpMethod.Get, uri);
request.Headers.Add("Custom-Header", "value");
示例:设置通用请求标头
在 .NET 中使用 HttpRequestMessage
时,设置通用请求标头对于向服务器提供有关所发出请求的其他信息至关重要。 这些标头可以包括身份验证令牌等。 正确配置这些标头可确保服务器正确处理 HTTP 请求。
有关 HttpRequestHeaders 中可用的通用属性的完整列表,请参阅属性。
若要在 HttpRequestMessage
中设置通用请求标头,可以使用 HttpRequestMessage
对象的 Headers
属性。 此属性提供对 HttpRequestHeaders
集合的访问权限,可在其中根据需要添加或修改标头。
在 HttpClient 中设置通用默认请求标头
using System.Net.Http.Headers;
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "token");
在 HttpRequestMessage 中设置通用请求标头
using System.Net.Http.Headers;
var request = new HttpRequestMessage(HttpMethod.Get, uri);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "token");
示例:设置内容标头
内容标头用于提供有关 HTTP 请求或响应正文的其他信息。 在 .NET 中使用 HttpClient
时,可以设置内容标头,以指定与发送或接收的内容相关的媒体类型、编码和其他元数据。 正确配置内容标头可确保服务器和客户端能够正确解释和处理内容。
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, uri);
// Create the content and set the content headers
var jsonData = "{\"key\":\"value\"}";
var content = new StringContent(jsonData, Encoding.UTF8, "application/json");
// The following headers are set automatically by `StringContent`. If you wish to override their values, you can do it like so:
// content.Headers.ContentType = new MediaTypeHeaderValue("application/json; charset=utf-8");
// content.Headers.ContentLength = Encoding.UTF8.GetByteCount(jsonData);
// Assign the content to the request
request.Content = content;
using var response = await client.SendAsync(request);
示例:在 HttpClient 中设置 MaximumErrorResponseLength
利用 MaximumErrorResponseLength
用法,开发人员可以指定处理程序将缓冲的错误响应内容的最大长度。 这对于控制从服务器收到错误响应时读取和存储到内存中的数据量非常有用。 通过使用此技术,可以防止过度使用内存,并在处理大型错误响应时提高应用程序的性能。
有几种方法可以做到这一点,我们将在此示例中研究 TruncatedReadStream
技术:
internal sealed class TruncatedReadStream(Stream innerStream, long maxSize) : Stream
{
private long _maxRemainingLength = maxSize;
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => false;
public override long Length => throw new NotSupportedException();
public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
public override void Flush() => throw new NotSupportedException();
public override int Read(byte[] buffer, int offset, int count)
{
return Read(new Span<byte>(buffer, offset, count));
}
public override int Read(Span<byte> buffer)
{
int readBytes = innerStream.Read(buffer.Slice(0, (int)Math.Min(buffer.Length, _maxRemainingLength)));
_maxRemainingLength -= readBytes;
return readBytes;
}
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return ReadAsync(new Memory<byte>(buffer, offset, count), cancellationToken).AsTask();
}
public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
{
int readBytes = await innerStream.ReadAsync(buffer.Slice(0, (int)Math.Min(buffer.Length, _maxRemainingLength)), cancellationToken)
.ConfigureAwait(false);
_maxRemainingLength -= readBytes;
return readBytes;
}
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
public override void SetLength(long value) => throw new NotSupportedException();
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
public override ValueTask DisposeAsync() => innerStream.DisposeAsync();
protected override void Dispose(bool disposing)
{
if (disposing)
{
innerStream.Dispose();
}
}
}
TruncatedReadStream
的用法示例:
int maxErrorResponseLength = 1 * 1024; // 1 KB
HttpClient client = new HttpClient();
using HttpResponseMessage response = await client.GetAsync(Uri);
if (response.Content is not null)
{
Stream responseReadStream = await response.Content.ReadAsStreamAsync();
// If MaxErrorResponseLength is set and the response status code is an error code, then wrap the response stream in a TruncatedReadStream
if (maxErrorResponseLength >= 0 && !response.IsSuccessStatusCode)
{
responseReadStream = new TruncatedReadStream(responseReadStream, maxErrorResponseLength);
}
// Read the response stream
Memory<byte> buffer = new byte[1024];
int readValue = await responseReadStream.ReadAsync(buffer);
}
示例:应用 CachePolicy 标头
警告
HttpClient
没有内置的逻辑来缓存响应。 除了自行实现所有缓存之外,没有解决方法。 仅仅设置标头并不能实现缓存。
从 HttpWebRequest
迁移到 HttpClient
时,必须正确处理与缓存相关的标头,例如 pragma
和 cache-control
。 这些标头控制如何缓存和检索响应,确保应用程序在性能和数据新鲜度方面按预期方式运行。
在 HttpWebRequest
中,你可能已使用 CachePolicy
属性来设置这些标头。 但是,在 HttpClient
中,需要在请求中手动设置这些标头。
旧代码使用 HttpWebRequest
:
HttpWebRequest request = WebRequest.CreateHttp(uri);
request.CachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.NoCacheNoStore);
using HttpWebResponse response = (HttpWebResponse)request.GetResponse();
在较旧的 HttpWebRequest
API 中,由于内置了对该功能的支持,因此应用 CachePolicy
非常简单。 但是,较新的 HttpClient
API 不提供相同的内置功能。 尽管如此,可以通过实现手动添加缓存相关标头的 AddCacheControlHeaders
来实现类似的行为。
使用 HttpClient
的新代码:
public static class CachePolicy
{
public static void AddCacheControlHeaders(HttpRequestMessage request, RequestCachePolicy policy)
可以在此处找到 AddCacheControlHeaders
的实现。
AddCacheControlHeaders
使用情况:
static async Task AddCacheControlHeaders()
{
HttpClient client = new HttpClient();
HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Get, Uri);
CachePolicy.AddCacheControlHeaders(requestMessage, new HttpRequestCachePolicy(HttpRequestCacheLevel.NoCacheNoStore));
HttpResponseMessage response = await client.SendAsync(requestMessage);
}
缓冲属性的使用
从 HttpWebRequest 迁移到 HttpClient
时,请务必了解这两个 API 处理缓冲的方式的差异。
旧代码使用 HttpWebRequest
:
在 HttpWebRequest
中,可以通过 AllowWriteStreamBuffering
和 AllowReadStreamBuffering
属性直接控制缓冲属性。 这些属性启用或禁用发送到服务器和从服务器接收的数据的缓冲。
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
request.AllowReadStreamBuffering = true; // Default is `false`.
request.AllowWriteStreamBuffering = false; // Default is `true`.
使用 HttpClient
的新代码:
在 HttpClient
中,没有与 AllowWriteStreamBuffering
和 AllowReadStreamBuffering
属性直接等效的属性。
HttpClient 本身不缓冲请求正文,而是将责任委派给 HttpContent
所使用的机构。 像 StringContent
或 ByteArrayContent
这样的内容在逻辑上已经缓冲在内存中,而使用 StreamContent
默认情况下不会产生任何缓冲。 若要强制缓冲内容,可以在发送请求之前调用 HttpContent.LoadIntoBufferAsync
。 下面是一个示例:
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, uri);
request.Content = new StreamContent(yourStream);
await request.Content.LoadIntoBufferAsync();
HttpResponseMessage response = await client.SendAsync(request);
在 HttpClient
中,默认情况下启用读取缓冲。 若要避免此问题,可以指定 HttpCompletionOption.ResponseHeadersRead
标志或使用 GetStreamAsync
帮助程序。
HttpClient client = new HttpClient();
using HttpResponseMessage response = await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead);
await using Stream responseStream = await response.Content.ReadAsStreamAsync();
// Or simply
await using Stream responseStream = await client.GetStreamAsync(uri);