在.NET开发中,HttpClient是一个常用的类,用于发送HTTP请求。然而,许多开发者在使用HttpClient时存在误解,常常在每次请求时创建新的实例,这不仅效率低下,还可能导致连接池耗尽。本文将探讨HttpClient的正确使用方式,以帮助开发者避免常见的错误,并提高应用程序的性能。
HttpClient设计为可重用。应该在整个应用程序的生命周期中使用单个HttpClient实例,或者至少在请求签名变化时尽可能少地使用。这样做的主要原因是,HttpClient的每个实例都会打开一个新的套接字连接,而在高流量网站上,可能会耗尽可用的连接池,并收到System.Net.Sockets.SocketException异常。此外,通过重用实例,可以避免建立新的TCP连接的显著开销,因为HttpClient可以以线程安全的方式重用其现有连接。
在控制台或桌面应用程序中,可以在任何地方公开这个实例。在ASP.NETWebAPI应用程序中,可以在Global.asax.cs文件中创建它。以下是设置HttpClient实例的示例:
public class WebApiApplication : System.Web.HttpApplication
{
internal static HttpClient httpClientInstance;
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
Uri baseUri = new Uri("https://someresource.com");
httpClientInstance = new HttpClient();
httpClientInstance.BaseAddress = baseUri;
httpClientInstance.DefaultRequestHeaders.Clear();
httpClientInstance.DefaultRequestHeaders.ConnectionClose = false;
httpClientInstance.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
ServicePointManager.FindServicePoint(baseUri).ConnectionLeaseTimeout = 60 * 1000;
}
}
在这个示例中,一次性设置了客户端。所有的使用都将:
虽然HttpClient是线程安全的,但它并不是所有的属性都是线程安全的。如果在每次调用之前更改URL和/或标头,可能会导致一些非常难以识别的错误。如果这种配置会变化,那么不能使用第一个配方。如果有一个可管理的配置数量,为每个配置创建一个HttpClient实例。将比单实例稍微低效一些,但比每次都创建和处置要高效得多。
在这种情况下,可以为所有HTTP调用创建单独的客户端实例,这可能会变得难以管理。仍然可以通过变化HttpRequestMessage而不是HttpClient来获得单实例的所有优势。以下是新的Global.asax.cs文件的示例:
public class WebApiApplication : System.Web.HttpApplication
{
internal static HttpClient httpClientInstance;
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
httpClientInstance = new HttpClient();
httpClientInstance.DefaultRequestHeaders.ConnectionClose = false;
ServicePointManager.FindServicePoint("some uri").ConnectionLeaseTimeout = 60 * 1000;
ServicePointManager.FindServicePoint("some other uri").ConnectionLeaseTimeout = 60 * 1000;
ServicePointManager.FindServicePoint("some other other uri").ConnectionLeaseTimeout = 60 * 1000;
// etc....
}
}
在这个示例中,已经消除了内容类型和基础URI,因为在客户端使用中,这将经常变化。此外,已经为所有知道将要使用的URI配置了ConnectionLeaseTimeout。如果在设计时不知道所有URI,那么在运行时这样做是可以的——至少在运行时这样做比根本不做要好。