HttpClient and HttpMessageHandler in .NET 8 — The Harmony of HTTP Communication

Admir Mujkic
6 min readMar 5, 2024

--

In the world of .NET development, efficient HTTP communication is the ground on which scalable, reliable and high-performance applications are built.

Understanding and optimizing of key parts such as HttpClient and HttpMessageHandler is analogous to performing a symphony orchestra where each instrument should ley perfectly to other instruments to form an unforgettable music performance.

This blog provides in look into how HttpClient and HttpMessageHandler cooperate, best practices, resource management, authentication and how to avoid common mistakes.

HttpClient is like a conductor in an orchestra, handling the request/response with web services. The main recommendation for using HttpClient is to reuse it throughout the entire life cycle of program execution. This optimizes resources and guarantees high speed. In ASP.NET Core, HttpClientFactory acts as a producer that ensures the conductor has everything required, avoiding resource depletion and ensuring a smooth performance.

public class DataService { private readonly IHttpClientFactory _clientFactory; public DataService(IHttpClientFactory clientFactory) { _clientFactory = clientFactory; } public async Task<string> GetExternalDataAsync(string requestUri) { var client = _clientFactory.CreateClient(); HttpResponseMessage response = await client.GetAsync(requestUri); response.EnsureSuccessStatusCode(); return await response.Content.ReadAsStringAsync(); } }

HttpMessageHandler acts like the bottom of the bridge that connects an application with a web service, managing the low-level details of HTTP requests and responses. To implement any such logic like authentication or logging, HttpMessageHandler can extended using DelegatingHandlers.

Authentication with HttpMessageHandler

public class AuthenticationHandler : DelegatingHandler { private readonly string _authToken; public AuthenticationHandler(string authToken, HttpMessageHandler innerHandler) : base(innerHandler) { _authToken = authToken; } protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _authToken); return base.SendAsync(request, cancellationToken); } }

Properly Managing the Lifecycle

Properly releasing (disposing) HttpClient and HttpMessageHandler instances is crucial for preventing memory leaks and ensuring the best use of resources.

using (var httpClient = new HttpClient(new AuthenticationHandler("your-token", new HttpClientHandler()))) { // Using httpClient to send HTTP requests... }

Producing highly performant, robust, and scalable applications that interact with web services is possible, when the advanced opportunities with HttpClient and HttpMessageHandler in the .NET ecosystem are used properly. Nevertheless, such advanced capabilities come with inevitable necessity for caution as to prevent any undesired outcome negatively affecting an application’s reliability and performance. Here is the exploration of the advanced use and how to bypass them.

HttpClientFactory is a vital aspect of ASP.NET Core applications for the creation and management of HttpClient instances. This approach addresses a number of issues which might arise during the lifecycle of HttpClient instances including exhaustion of sockets and problems with reusing TCP connections.

With HttpClientFactory, instances of HttpClient are automatically handled, refreshed and configured via a centralized location. In addition to that, it supports easy DelegatingHandlers adding for custom request and response processing.

services.AddHttpClient("MyClient", client => { client.BaseAddress = new Uri("https://example.com/"); client.DefaultRequestHeaders.Add("Accept", "application/json"); }).AddHttpMessageHandler<AuthenticationHandler>();

This approach simplifies the central management of the configurations and handlers per specific HttpClients.

Expanding HttpMessageHandler

Through the expansion of HttpMessageHandler by DelegatingHandlers, you can implement sophisticated processing scenarios like adding the authentication tokens, logging, error handling, and also retries. This fine-tuning enables the development teams to adhere to the cross-cutting concerns in a clean and modular fashion.

public class LoggingHandler : DelegatingHandler { protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { // Logging requests Console.WriteLine($"Sending request to {request.RequestUri}"); var response = await base.SendAsync(request, cancellationToken); // Logging responses Console.WriteLine($"Received response: {response.StatusCode} from {request.RequestUri}"); return response; } }

The socket exhaustion happens when an application creates many clients without their re-utilization of them or proper disposal. In order to overcome this pitfall, employ HttpClientFactory to regulate HttpClient instances. By this approach connections will be reused efficiently and sockets do not exhaust.

In the lack of proper error handling in HTTP communication results in missed exceptions, resource leaks, and unstable application states. Always check the status codes of the responses and implement sound error handling logic.

var response = await httpClient.GetAsync("https://api.penzle.com/content"); if (!response.IsSuccessStatusCode) { // Error handling logic Console.WriteLine($"Error: {response.StatusCode}"); } else { var content = await response.Content.ReadAsStringAsync(); // Processing a successful response }

By using these advanced practices and carefully managing the pitfalls, .NET applications can reach a high level of efficiency, reliability, and scalability when communicating with web services.

It is advocated to use only one HttpClient instance throughout the whole application under the .NET environment for various reasons concerning resource usage, performance, as well as the network connection handling. This point, frequently mentioned in official documents and by experts, is essentially grounded in how HttpClient handles HTTP connections.

HttpClient is created for reusability and can handle multiple HTTP calls at once. With only one use of it, HTTP connections which are opened to servers can be reused in an efficient manner. This is so since HttpClient has a method of handling them. Reusing connections implies that we do not have to frequently open and close TCP connections, the process that is resource intensive, and it helps ease the work of the Garbage Collector (GC).

For an application’s performance to be improved significantly, a single HttpClient instance can be reused. Creation a new HttpClient instance for each request consumes the time spent in initialization and closing of several connections. This may decrease the application`s response time, particularly when heavy load is applied. Reuse of instances helps leverage network resources, thereby increasing processing speed for requests.

HTTP/*.* protocol that HttpClient uses provides persistent connections or keep-alive. This implies that connections stay open for several uses which results in lower latency for repeat requests. It is especially good for applications which are connecting with the same servers often. To avoid exhausting all sockets and connections the new instance of HttpClient needs to be created for each request that will be issued and the memory could be differently allocated which may lead to SocketException on the client system.

An SDK that needs to communicate with web services, like a Cosmos DB client, can be designed to accept an HttpMessageHandler as part of its setup or constructor. This lets users add their own customized HttpMessageHandler instances, which can be set up for logging, authentication, retries, and other broad tasks.

This is very important when you know to whom SDK actually hands this handler. In particular with respect to the Dispose method. If the SDK is responsible for creating an HttpMessageHandler (therefore the user does not provide their own), then the SDK also needs to look after the handler’s lifecycle, encompassing the disposal of the handler correctly. If the SDK works with resources (a typical example are HttpMessageHandler and HttpClient instances), it should implement the IDisposable interface.

Example from: https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/best-practice-dotnet#best-practices-for-http-connections

// Use a Singleton instance of the SocketsHttpHandler, which you can share across any HttpClient in your application SocketsHttpHandler socketsHttpHandler = new SocketsHttpHandler(); // Customize this value based on desired DNS refresh timer socketsHttpHandler.PooledConnectionLifetime = TimeSpan.FromMinutes(5); CosmosClientOptions cosmosClientOptions = new CosmosClientOptions() { // Pass your customized SocketHttpHandler to be used by the CosmosClient // Make sure `disposeHandler` is `false` HttpClientFactory = () => new HttpClient(socketsHttpHandler, disposeHandler: false) }; // Use a Singleton instance of the CosmosClient return new CosmosClient("<connection-string>", cosmosClientOptions);

An HttpMessageHandler in an SDK, for example the Cosmos DB client has a lot of flexibility and control over HTTP communication and handling. Nevertheless, with this flexibility comes the duty of handling the HttpMessageHandler’s lifecycle. Proper designing, implementing the IDisposable interface, and clear guidance to SDK users help the developers to avoid the SDKs problems, e.g. inefficiency, unsafety and resource leakage.

The core of constructing efficient, scalable, and dependable applications is to reuse HttpClient instances, fine-grained customization of request and response handling via HttpMessageHandlers, and proper resource handling.

By implementing the following best practices, .NET architects can optimize HTTP communication and ensure that applications communicate with web services efficiently and reliably.

  • Creating new HttpClient instances for each request puts the Garbage Collector under load and severely affects application’s performance. Use HttpClientFactory or something similar to handle the life cycle of HttpClient objects.
  • HTTPMessageHandler chains give you the capability to fine-tune how your applications handle incoming and outgoing HTTP messages. For needs like logging, auth or management of the cache information you could also choose to implement your own handlers.
  • Make sure that all HTTP resources are properly closed and disposed after use. This covers appropriately dealing with HttpResponseMessage objects to prevent resource leaks.
  • Asynchronous patterns decrease thread blocking and give programs a chance to be more reactive. HTTP requests executed asynchronously increases application scalability.
  • Properly tune HttpClient settings, specifying timeouts, request and response buffer sizes, and retry policies. Adjust these settings as per requirement of your application to get better performance and reliability.
  • Pay attention to the security aspects of HTTP communication, for instance, the usage of HTTPS, certificate management, and data privacy protection.

By giving credit to these suggestions, .NET architects alongside with development teams can achieve flawless HTTP communication in their applications. In addition to improving the user experience it also contributes to the ease of programming and the creation and maintenance of a software that can interact with various web services in a dynamic technical setting.

Cheers! 👋

Follow me on LinkedIn: www.linkedin.com/comm/mynetwork/discovery-see-all?usecase=PEOPLE_FOLLOWS&followMember=admir-live

Originally published at https://www.admirmujkic.com

--

--

Admir Mujkic

Admir combined engineering expertise with business acumen to make a positive impact & share knowledge. Dedicated to educating the next generation of leaders.