dotnet/yarp · error · IOException

An error occurred when reading the request body from the cli

Error message

An error occurred when reading the request body from the client.

What it means

YARP streams the client's request body to the destination using `StreamCopier.CopyAsync`. When the source stream (the client connection) encounters an I/O error — a socket failure, malformed chunked encoding, a broken pipe, or the client disconnecting mid-body — the copy returns `StreamCopyResult.InputError`. YARP wraps the underlying exception in an IOException so the HttpClient transport layer recognizes the body as incomplete and aborts the outgoing request cleanly.

Source

Thrown at src/ReverseProxy/Forwarder/StreamCopyHttpContent.cs:202

            // The.NET Core HttpClient stack keeps its own buffers on top of the underlying outgoing connection socket.
            // We flush those buffers down to the socket on every write when this is set,
            // but it does NOT result in calls to flush on the underlying socket.
            // This is necessary because we proxy http2 transparently,
            // and we are deliberately unaware of packet structure used e.g. in gRPC duplex channels.
            // Because the sockets aren't flushed, the perf impact of this choice is expected to be small.
            // Future: It may be wise to set this to true for *all* http2 incoming requests,
            // but for now, out of an abundance of caution, we only do it for requests that look like gRPC.
            var (result, error) = await StreamCopier.CopyAsync(isRequest: true, _context.Request.Body, stream,
                Headers.ContentLength ?? StreamCopier.UnknownLength, _timeProvider, _activityToken, _isStreamingRequest, cancellationToken);
            _tcs.TrySetResult((result, error));

            // Check for errors that weren't the result of the destination failing.
            // We have to throw something here so the transport knows the body is incomplete.
            // We can't re-throw the original exception since that would cause concurrency issues.
            // We need to wrap it.
            if (result == StreamCopyResult.InputError)
            {
                throw new IOException("An error occurred when reading the request body from the client.", error);
            }
            if (result == StreamCopyResult.Canceled)
            {
                throw new OperationCanceledException("The request body copy was canceled.", error);
            }
        }
        finally
        {
            linkedCts?.Dispose();
        }
    }

    // this is used internally by HttpContent.ReadAsStreamAsync(...)
    protected override Task<Stream> CreateContentReadStreamAsync()
    {
        // Nobody should be calling this...
        throw new NotImplementedException();
    }

View on GitHub (pinned to bd11867bee)

Solutions

  1. Treat this as a transient client-side failure — log it and return an appropriate error response; no server-side fix is needed unless it is systematic.
  2. If systematic, investigate network stability between clients and YARP (check for load balancer timeouts, proxy chains, or MTU/MSS issues causing RSTs).
  3. Verify the client is not sending a Content-Length that mismatches the actual body size, which causes stream reader failures.
  4. If clients are timing out during large uploads, increase client-side timeout settings or switch to chunked transfer encoding.
  5. In the forwarder error handler, catch the IOException and distinguish client-read failures (return 400) from other forwarding errors.

Example fix

// In your IForwarderErrorFeature handling or exception filter
try
{
    await forwarder.SendAsync(context, destinationPrefix, httpClient,
        ForwarderRequestConfig.Empty, HttpTransformer.Default, ct);
}
catch (IOException ex) when (ex.Message.Contains("reading the request body"))
{
    // Client disconnected or sent bad data; respond cleanly
    context.Response.StatusCode = StatusCodes.Status400BadRequest;
    logger.LogWarning("Client body read failed: {Message}", ex.Message);
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Check client connection health before forwarding (limited effectiveness)
if (context.Request.Headers.ContentLength > 0 && !context.Request.Body.CanRead)
{
    context.Response.StatusCode = 400;
    return;
}

Type guard

// No type guard — this is a runtime I/O failure, not a type issue.

Try / catch

try { await forwarder.SendAsync(context, prefix, client, config, transformer, ct); }
catch (IOException ex) when (ex.Message.Contains("reading the request body"))
{
    // Client disconnected or sent corrupt body data
    if (!context.Response.HasStarted)
        context.Response.StatusCode = StatusCodes.Status400BadRequest;
    logger.LogDebug("Client body read failed: {Message}", ex.InnerException?.Message);
}

Prevention

When it happens

Trigger: The client starts sending a request body (POST/PUT with a body) and then disconnects abnormally, the network connection drops, or the client sends malformed data that the Kestrel request body parser rejects. The `StreamCopier.CopyAsync` call returns `InputError` with the original exception attached, and line 202 throws the wrapped IOException. This happens inside `StreamCopyHttpContent.SerializeToStreamAsync`, which runs when the destination begins reading the request body.

Common situations: A mobile client on an unstable network sends a large file upload and loses connectivity partway through. A client times out and closes the connection while streaming. A proxy or load balancer in front of YARP terminates the connection abruptly. A client sends a body with an incorrect Content-Length, causing the stream reader to fail.

Related errors


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