• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ThreeMammals / Ocelot / 23302213965

19 Mar 2026 03:19PM UTC coverage: 91.219% (-1.3%) from 92.49%
23302213965

Pull #2369

github

web-flow
Merge 7f6f01146 into 086c7b15c
Pull Request #2369: Pre-Release 25.0 aka Beta 2

8404 of 9213 relevant lines covered (91.22%)

2131.48 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.58
src/Ocelot/Request/Mapper/StreamHttpContent.cs
1
using Microsoft.AspNetCore.Http;
2
using System.Buffers;
3

4
namespace Ocelot.Request.Mapper;
5

6
public class StreamHttpContent : HttpContent
7
{
8
    private const int DefaultBufferSize = 65536;
9
    public const long UnknownLength = -1;
10
    private readonly HttpContext _context;
11
    private readonly long _contentLength;
12

13
    public StreamHttpContent(HttpContext context)
10✔
14
    {
10✔
15
        _context = context ?? throw new ArgumentNullException(nameof(context));
10✔
16
        _contentLength = context.Request.ContentLength ?? UnknownLength;
10✔
17
    }
10✔
18

19
    protected override Task SerializeToStreamAsync(Stream stream, TransportContext context, CancellationToken cancellationToken)
20
        => CopyAsync(_context.Request.Body, stream, _contentLength, false, cancellationToken);
5✔
21

22
    protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
23
        => CopyAsync(_context.Request.Body, stream, _contentLength, false, CancellationToken.None);
×
24

25
    protected override bool TryComputeLength(out long length)
26
    {
2✔
27
        length = _contentLength;
2✔
28
        return length >= 0;
2✔
29
    }
2✔
30

31
    // This is used internally by HttpContent.ReadAsStreamAsync(...)
32
    protected override Task<Stream> CreateContentReadStreamAsync()
33
    {
×
34
        // Nobody should be calling this...
35
        throw new NotImplementedException();
×
36
    }
37

38
    private static async Task CopyAsync(Stream input, Stream output, long announcedContentLength,
39
        bool autoFlush, CancellationToken cancellation)
40
    {
8✔
41
        // For smaller payloads, avoid allocating a buffer that is larger than the announced content length
42
        var minBufferSize = announcedContentLength != UnknownLength && announcedContentLength < DefaultBufferSize
8✔
43
            ? (int)announcedContentLength
8✔
44
            : DefaultBufferSize;
8✔
45

46
        var buffer = ArrayPool<byte>.Shared.Rent(minBufferSize);
8✔
47
        long contentLength = 0;
8✔
48
        try
49
        {
8✔
50
            while (true)
14✔
51
            {
14✔
52
                // Issue a zero-byte read to the input stream to defer buffer allocation until data is available.
53
                // Note that if the underlying stream does not supporting blocking on zero byte reads, then this will
54
                // complete immediately and won't save any memory, but will still function correctly.
55
                var zeroByteReadTask = input.ReadAsync(Memory<byte>.Empty, cancellation);
14✔
56
                if (zeroByteReadTask.IsCompletedSuccessfully)
14✔
57
                {
14✔
58
                    // Consume the ValueTask's result in case it is backed by an IValueTaskSource
59
                    // It is save to read the Result once after the ValueTask has completed, and we've checked for complition by IsCompletedSuccessfully property
60
                    // See remarks: https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.valuetask-1.result?view=net-8.0#remarks
61
                    _ = zeroByteReadTask.Result; // No need to await the task by .GetAwaiter().GetResult()
14✔
62
                }
14✔
63
                else
64
                {
×
65
                    // Take care not to return the same buffer to the pool twice in case zeroByteReadTask throws
66
                    var bufferToReturn = buffer;
×
67
                    buffer = null;
×
68
                    ArrayPool<byte>.Shared.Return(bufferToReturn);
×
69

70
                    await zeroByteReadTask;
×
71

72
                    buffer = ArrayPool<byte>.Shared.Rent(minBufferSize);
×
73
                }
×
74

75
                var read = await input.ReadAsync(buffer.AsMemory(), cancellation);
14✔
76
                contentLength += read;
14✔
77

78
                // Normally this is enforced by the server, but it could get out of sync if something in the proxy modified the body.
79
                if (announcedContentLength != UnknownLength && contentLength > announcedContentLength)
14✔
80
                {
1✔
81
                    throw new InvalidOperationException($"More data ({contentLength} bytes) received than the specified Content-Length of {announcedContentLength} bytes.");
1✔
82
                }
83

84
                // End of the source stream.
85
                if (read == 0)
13✔
86
                {
7✔
87
                    if (announcedContentLength == UnknownLength || contentLength == announcedContentLength)
7✔
88
                    {
7✔
89
                        return;
7✔
90
                    }
91
                    else
92
                    {
×
93
                        throw new InvalidOperationException($"Sent {contentLength} request content bytes, but Content-Length promised {announcedContentLength}.");
×
94
                    }
95
                }
96

97
                await output.WriteAsync(buffer.AsMemory(0, read), cancellation);
6✔
98
                if (autoFlush)
6✔
99
                {
×
100
                    // HttpClient doesn't always flush outgoing data unless the buffer is full or the caller asks.
101
                    // This is a problem for streaming protocols like WebSockets and gRPC.
102
                    await output.FlushAsync(cancellation);
×
103
                }
×
104
            }
6✔
105
        }
106
        finally
107
        {
8✔
108
            if (buffer != null)
8✔
109
            {
8✔
110
                ArrayPool<byte>.Shared.Return(buffer);
8✔
111
            }
8✔
112
        }
8✔
113
    }
7✔
114
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc