Skip to content

fix: replace per-call new HttpClient() with shared static instance#47

Draft
DaraOladapo wants to merge 1 commit into
mainfrom
claude/pensive-mccarthy-kg3mW
Draft

fix: replace per-call new HttpClient() with shared static instance#47
DaraOladapo wants to merge 1 commit into
mainfrom
claude/pensive-mccarthy-kg3mW

Conversation

@DaraOladapo

Copy link
Copy Markdown
Owner

Summary

Closes #36.

Every public method in the library was creating and disposing a fresh HttpClient inside a using block:

// Before — repeated in every method across all 5 files
using (HttpClient httpClient = new HttpClient())
{
    httpClient.DefaultRequestHeaders.Authorization = ...;
    var httpResponse = await httpClient.GetAsync(new Uri(Route));
    return httpResponse;
}

This is the well-documented .NET anti-pattern described in Microsoft's HttpClient guidelines. Each construction opens a new socket; Dispose() does not immediately release it — the OS holds the connection in TIME_WAIT. Under any meaningful request volume this exhausts the available socket pool and causes SocketException.

Fix

Introduced a single private static readonly HttpClient _httpClient in Get.cs (shared across all partial-class files). All active methods now build a per-request HttpRequestMessage and pass it to _httpClient.SendAsync(request):

// After — shared instance, per-request message
private static readonly HttpClient _httpClient = new HttpClient();

public static async Task<HttpResponseMessage> GetAsync(string Route, string Token = null)
{
    var request = new HttpRequestMessage(HttpMethod.Get, new Uri(Route));
    if (!string.IsNullOrEmpty(Token))
        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Token);
    return await _httpClient.SendAsync(request);
}

Using HttpRequestMessage is required here: per-call headers (Authorization, custom) are set on the message, not on DefaultRequestHeaders, so concurrent calls cannot race on shared state.

Files changed

File Change
Get.cs Added _httpClient static field; all 8 active methods use HttpRequestMessage
Post.cs All 8 active methods use HttpRequestMessage
Put.cs All 8 active methods use HttpRequestMessage
Patch.cs All 8 active methods use HttpRequestMessage
Delete.cs All 8 active methods use HttpRequestMessage

Non-breaking

Public API signatures are unchanged. The #region TODO commented-out blocks are preserved (tracked separately in #37).

Test plan

  • Build passes (dotnet build)
  • Existing WireMock integration tests pass (dotnet test)
  • Verify no SocketException under concurrent load (manual or load test)

Generated by Claude Code

Every method was constructing and disposing an HttpClient inside a
using block. Under load this causes socket exhaustion (TIME_WAIT)
because the OS cannot release underlying sockets immediately after
Dispose. Fixes #36.

Introduced a single private static readonly HttpClient _httpClient
in the partial class (Get.cs). All methods now build a per-request
HttpRequestMessage so per-call headers (Authorization, custom) are
isolated and thread-safe — DefaultRequestHeaders on the shared
instance is never mutated.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Replace new HttpClient() per-call with a shared/reusable instance

1 participant