dotnet/yarp · error · ArgumentException

The http client must be of type HttpMessageInvoker, not Http

Error message

The http client must be of type HttpMessageInvoker, not HttpClient

What it means

Thrown by HttpForwarder.SendAsync when the supplied httpClient is an HttpClient rather than an HttpMessageInvoker. The HttpClient.SendAsync overload fully buffers the response, which hurts streaming/performance and breaks gRPC. YARP requires HttpMessageInvoker to preserve streaming semantics (see dotnet/yarp#458).

Source

Thrown at src/ReverseProxy/Forwarder/HttpForwarder.cs:119

        HttpTransformer transformer,
        CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(context);
        ArgumentNullException.ThrowIfNull(destinationPrefix);
        ArgumentNullException.ThrowIfNull(httpClient);
        ArgumentNullException.ThrowIfNull(requestConfig);
        ArgumentNullException.ThrowIfNull(transformer);

        if (RequestUtilities.IsResponseSet(context.Response))
        {
            throw new InvalidOperationException("The request cannot be forwarded, the response has already started");
        }

        // HttpClient overload for SendAsync changes response behavior to fully buffered which impacts performance
        // See discussion in https://github.com/dotnet/yarp/issues/458
        if (httpClient is HttpClient)
        {
            throw new ArgumentException($"The http client must be of type HttpMessageInvoker, not HttpClient", nameof(httpClient));
        }

        // "http://a".Length = 8
        if (destinationPrefix is null || destinationPrefix.Length < 8)
        {
            throw new ArgumentException("Invalid destination prefix.", nameof(destinationPrefix));
        }

        ForwarderTelemetry.Log.ForwarderStart(destinationPrefix);

        var activityCancellationSource = ActivityCancellationTokenSource.Rent(requestConfig?.ActivityTimeout ?? DefaultTimeout, context.RequestAborted, cancellationToken);
        try
        {
            var isClientHttp2OrGreater = ProtocolHelper.IsHttp2OrGreater(context.Request.Protocol);

            // NOTE: We heuristically assume gRPC-looking requests may require streaming semantics.
            // See https://github.com/dotnet/yarp/issues/118 for design discussion.
            var isStreamingRequest = isClientHttp2OrGreater && ProtocolHelper.IsGrpcContentType(context.Request.ContentType);

View on GitHub (pinned to bd11867bee)

Solutions

  1. Pass an HttpMessageInvoker (or the cluster's configured HttpClient, which YARP wraps appropriately) instead of HttpClient.
  2. If using IHttpClientFactory, create an HttpMessageInvoker from the handler: new HttpMessageInvoker(handler, disposeHandler: false).
  3. Use the cluster's pre-configured HttpClient via IReverseProxyFeature.Cluster.HttpClient which is already an HttpMessageInvoker.

Example fix

// before
var client = new HttpClient();
await forwarder.SendAsync(context, address, client, reqConfig, transformer, ct);
// after
var handler = new SocketsHttpHandler();
var invoker = new HttpMessageInvoker(handler, disposeHandler: true);
await forwarder.SendAsync(context, address, invoker, reqConfig, transformer, ct);
Defensive patterns

Strategy: type-guard

Validate before calling

if (httpClient is HttpClient)
    throw new ArgumentException("Use HttpMessageInvoker, not HttpClient.", nameof(httpClient));
await forwarder.SendAsync(context, address, (HttpMessageInvoker)httpClient, reqConfig, transformer, ct);

Type guard

static bool IsMessageInvoker(object client) => client is HttpMessageInvoker and not HttpClient;

Try / catch

try { await forwarder.SendAsync(context, address, client, reqConfig, transformer, ct); }
catch (ArgumentException ex) when (ex.Message.Contains("HttpMessageInvoker"))
{ var invoker = new HttpMessageInvoker(handler, disposeHandler: true); await forwarder.SendAsync(context, address, invoker, reqConfig, transformer, ct); }

Prevention

When it happens

Trigger: Passing a new HttpClient() or an HttpClient-derived instance as the httpClient argument to IHttpForwarder.SendAsync (or IForwarder.SendAsync). The runtime type check (httpClient is HttpClient) trips and rejects it.

Common situations: Creating an HttpClient from IHttpClientFactory and passing it directly to the forwarder. Reusing a typed HttpClient for forwarding. Migrating code that used HttpClient generically.

Related errors


AI-assisted analysis of dotnet/yarp@bd11867bee (2026-08-13). Data as JSON: /api/errors/e20d67e4f60ebff2. Report an issue: GitHub.