{"record":{"id":"4676a1477ddaa66f","repo":"dotnet/yarp","slug":"an-error-occurred-when-reading-the-request-body-fr","errorCode":null,"errorMessage":"An error occurred when reading the request body from the client.","messagePattern":"An error occurred when reading the request body from the client\\.","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"src/ReverseProxy/Forwarder/StreamCopyHttpContent.cs","lineNumber":202,"sourceCode":"            // The.NET Core HttpClient stack keeps its own buffers on top of the underlying outgoing connection socket.\n            // We flush those buffers down to the socket on every write when this is set,\n            // but it does NOT result in calls to flush on the underlying socket.\n            // This is necessary because we proxy http2 transparently,\n            // and we are deliberately unaware of packet structure used e.g. in gRPC duplex channels.\n            // Because the sockets aren't flushed, the perf impact of this choice is expected to be small.\n            // Future: It may be wise to set this to true for *all* http2 incoming requests,\n            // but for now, out of an abundance of caution, we only do it for requests that look like gRPC.\n            var (result, error) = await StreamCopier.CopyAsync(isRequest: true, _context.Request.Body, stream,\n                Headers.ContentLength ?? StreamCopier.UnknownLength, _timeProvider, _activityToken, _isStreamingRequest, cancellationToken);\n            _tcs.TrySetResult((result, error));\n\n            // Check for errors that weren't the result of the destination failing.\n            // We have to throw something here so the transport knows the body is incomplete.\n            // We can't re-throw the original exception since that would cause concurrency issues.\n            // We need to wrap it.\n            if (result == StreamCopyResult.InputError)\n            {\n                throw new IOException(\"An error occurred when reading the request body from the client.\", error);\n            }\n            if (result == StreamCopyResult.Canceled)\n            {\n                throw new OperationCanceledException(\"The request body copy was canceled.\", error);\n            }\n        }\n        finally\n        {\n            linkedCts?.Dispose();\n        }\n    }\n\n    // this is used internally by HttpContent.ReadAsStreamAsync(...)\n    protected override Task<Stream> CreateContentReadStreamAsync()\n    {\n        // Nobody should be calling this...\n        throw new NotImplementedException();\n    }","sourceCodeStart":184,"sourceCodeEnd":220,"githubUrl":"https://github.com/dotnet/yarp/blob/bd11867bee7df522e7fd3effb08a9c85fd616908/src/ReverseProxy/Forwarder/StreamCopyHttpContent.cs#L184-L220","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","If systematic, investigate network stability between clients and YARP (check for load balancer timeouts, proxy chains, or MTU/MSS issues causing RSTs).","Verify the client is not sending a Content-Length that mismatches the actual body size, which causes stream reader failures.","If clients are timing out during large uploads, increase client-side timeout settings or switch to chunked transfer encoding.","In the forwarder error handler, catch the IOException and distinguish client-read failures (return 400) from other forwarding errors."],"exampleFix":"// In your IForwarderErrorFeature handling or exception filter\ntry\n{\n    await forwarder.SendAsync(context, destinationPrefix, httpClient,\n        ForwarderRequestConfig.Empty, HttpTransformer.Default, ct);\n}\ncatch (IOException ex) when (ex.Message.Contains(\"reading the request body\"))\n{\n    // Client disconnected or sent bad data; respond cleanly\n    context.Response.StatusCode = StatusCodes.Status400BadRequest;\n    logger.LogWarning(\"Client body read failed: {Message}\", ex.Message);\n}","handlingStrategy":"try-catch","validationCode":"// Check client connection health before forwarding (limited effectiveness)\nif (context.Request.Headers.ContentLength > 0 && !context.Request.Body.CanRead)\n{\n    context.Response.StatusCode = 400;\n    return;\n}","typeGuard":"// No type guard — this is a runtime I/O failure, not a type issue.","tryCatchPattern":"try { await forwarder.SendAsync(context, prefix, client, config, transformer, ct); }\ncatch (IOException ex) when (ex.Message.Contains(\"reading the request body\"))\n{\n    // Client disconnected or sent corrupt body data\n    if (!context.Response.HasStarted)\n        context.Response.StatusCode = StatusCodes.Status400BadRequest;\n    logger.LogDebug(\"Client body read failed: {Message}\", ex.InnerException?.Message);\n}","preventionTips":["Log IOException with 'reading the request body' at debug level — it's typically client-side, not a server bug.","Check `context.RequestAborted.IsCancellationRequested` in error handlers to distinguish client disconnects.","Monitor the rate of these errors to detect systematic network issues between clients and YARP."],"tags":["network","request-body","client-disconnect","io-error","streaming"],"backgroundTag":null,"analyzedSha":"bd11867bee7df522e7fd3effb08a9c85fd616908","analyzedAt":"2026-08-13T21:29:49.359Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}