Skip to content

ABP HTTP client should drain or dispose non-ABP error responses when using ResponseHeadersRead #25475

@HMBSbige

Description

@HMBSbige

Is there an existing issue for this?

  • I have searched the existing issues

Description

The ABP dynamic HTTP client uses HttpCompletionOption.ResponseHeadersRead.

With ResponseHeadersRead, HttpClient returns after receiving response headers. The response content stream remains open until it is consumed or the HttpResponseMessage is disposed.

For ABP-formatted error responses, the response body is consumed by the ABP error handling pipeline, so the connection is released normally.

For non-ABP error responses without the _AbpErrorFormat header, the response body may not be consumed before the client throws. In that case, the underlying connection can remain occupied until the server closes it or the client times out.

This is easy to observe on .NET Framework because the default per-host connection limit is 2, but the same response lifetime rule applies on modern .NET versions as well.

Reproduction Steps

  1. Create a .NET Framework 4.8 console application.
  2. Configure an ABP dynamic HTTP client proxy to call an endpoint.
  3. Make the endpoint, or an intermediary such as a gateway or WAF, return a non-success response without the _AbpErrorFormat header while keeping the connection alive.
  4. Keep ServicePointManager.DefaultConnectionLimit unchanged.
  5. Call the same ABP client method repeatedly.

Observed on .NET Framework:

  • Request 1 receives the error response.
  • Request 2 receives the error response.
  • Request 3 waits because both per-host connections are still occupied.
  • The pattern repeats after the connections are eventually released.

Expected behavior

When ResponseHeadersRead is used, the ABP HTTP client should release the connection on every error path.

For non-success responses without _AbpErrorFormat, the client should drain the response content or dispose the HttpResponseMessage before throwing.

Actual behavior

For non-ABP error responses, the response body may remain unconsumed and the response may not be disposed before the exception leaves the ABP HTTP client pipeline.

The underlying connection can remain occupied until the remote side closes it or a timeout occurs. On .NET Framework this can quickly exhaust the default two per-host connections and cause subsequent requests to block.

Regression?

Unknown.

Known Workarounds

Add a custom DelegatingHandler to dispose non-ABP error responses:

protected override async Task<HttpResponseMessage> SendAsync(
    HttpRequestMessage request,
    CancellationToken cancellationToken)
{
    var response = await base.SendAsync(request, cancellationToken);

    if (response.StatusCode >= HttpStatusCode.BadRequest &&
        response.Content is { } content &&
        !response.Headers.Contains(AbpHttpConsts.AbpErrorFormat))
    {
        response.Dispose();
    }

    return response;
}

Alternatively, drain the response content before throwing for non-ABP error responses.

Version

10.4.0

User Interface

Common (Default)

Database Provider

None/Others

Tiered or separate authentication server

None (Default)

Operation System

Windows (Default)

Other information

The issue was reproduced on .NET Framework 4.8 with the default per-host connection limit. The behavior is consistent with ResponseHeadersRead: the response must be consumed or disposed to release the connection deterministically. Modern .NET versions usually make this harder to reproduce because their connection limits are higher, but disposing or draining the response is still the preferred way to release the connection promptly and avoid holding resources longer than necessary.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions