private static async Task <bool> TryReadDataAsync(Stream responseStream, Memory <byte> buffer, CancellationToken cancellationToken)
        {
            int read;
            var received = 0;

            while ((read = await StreamHelpers.ReadAsync(responseStream, buffer.Slice(received, buffer.Length - received), cancellationToken).ConfigureAwait(false)) > 0)
            {
                received += read;

                if (received == buffer.Length)
                {
                    return(true);
                }
            }

            if (received == 0)
            {
                return(false);
            }

            throw new InvalidDataException("Unexpected end of content while reading response stream.");
        }
        private async Task ReadTrailersAsync(int trailerLength, Memory <byte> data, CancellationToken cancellationToken)
        {
            if (trailerLength > 0)
            {
                // Read trailers into memory. Attempt to reuse existing buffer, otherwise allocate
                // a new buffer of the trailer size.
                if (trailerLength > data.Length)
                {
                    data = new byte[trailerLength];
                }
                else if (trailerLength < data.Length)
                {
                    data = data.Slice(0, trailerLength);
                }

                var success = await TryReadDataAsync(_inner, data, cancellationToken).ConfigureAwait(false);

                if (!success)
                {
                    throw new InvalidOperationException("Could not read trailing headers.");
                }

                ParseTrailers(data.Span);
            }

            // Final read to ensure there is no additional data. This is important:
            // 1. Double checks there is no unexpected additional data after trailers.
            // 2. The response stream is read to completion. HttpClient may not recognize the
            //    request as completing successfully if the request and response aren't completely
            //    consumed.
            var count = await StreamHelpers.ReadAsync(_inner, data, cancellationToken).ConfigureAwait(false);

            if (count > 0)
            {
                throw new InvalidOperationException("Unexpected data after trailers.");
            }

            _state = ResponseState.Complete;
        }
Пример #3
0
        public override async ValueTask <int> ReadAsync(Memory <byte> data, CancellationToken cancellationToken = default)
#endif
        {
#if NETSTANDARD2_0
            var data = buffer.AsMemory(offset, count);
#endif

            // There is enough remaining data to fill passed in data
            if (data.Length <= _remainder)
            {
                return(CopyRemainderToData(data));
            }

            var underlyingReadData    = data;
            var copyFromMinimumBuffer = false;
            if (data.Length < 6)
            {
                // If the requested data is very small, increase it to 6.
                // 4 bytes for base64, and 2 bytes for potential remaining content.
                if (_minimumBuffer == null)
                {
                    _minimumBuffer = new byte[4 + 2];
                }

                underlyingReadData    = _minimumBuffer;
                copyFromMinimumBuffer = true;
            }
            underlyingReadData = SetRemainder(underlyingReadData);

            // We want to read base64 data in multiples of 4
            underlyingReadData = underlyingReadData.Slice(0, (underlyingReadData.Length / 4) * 4);

            var availableReadData = underlyingReadData;

            var totalRead = 0;
            // Minimum valid base64 length is 4. Read until we have at least that much content
            do
            {
                var read = await StreamHelpers.ReadAsync(_inner, availableReadData, cancellationToken).ConfigureAwait(false);

                if (read == 0)
                {
                    if (_remainder > 0)
                    {
                        return(ReturnData(data, copyFromMinimumBuffer, bytesWritten: 0));
                    }
                    else if (totalRead == 0)
                    {
                        return(0);
                    }

                    throw new InvalidOperationException("Invalid base64 data.");
                }

                availableReadData = availableReadData.Slice(read);
                totalRead        += read;

                // The underlying stream may not have a complete 4 byte segment yet
                // so read again until we have the right data length.
            } while (totalRead % 4 != 0);

            var base64Data   = underlyingReadData.Slice(0, totalRead);
            int bytesWritten = DecodeBase64DataFragments(base64Data.Span);

            return(ReturnData(data, copyFromMinimumBuffer, bytesWritten));
        }
        public override async ValueTask <int> ReadAsync(Memory <byte> data, CancellationToken cancellationToken = default)
#endif
        {
#if NETSTANDARD2_0
            var data = buffer.AsMemory(offset, count);
#endif

            switch (_state)
            {
            case ResponseState.Ready:
                // Read the header first
                // - 1 byte flag for compression
                // - 4 bytes for the content length
                Memory <byte> headerBuffer;

                if (data.Length >= 5)
                {
                    headerBuffer = data.Slice(0, 5);
                }
                else
                {
                    // Should never get here. Client always passes 5 to read the header.
                    throw new InvalidOperationException("Buffer is not large enough for header");
                }

                var success = await TryReadDataAsync(_inner, headerBuffer, cancellationToken).ConfigureAwait(false);

                if (!success)
                {
                    return(0);
                }

                var compressed = headerBuffer.Span[0];
                var length     = (int)BinaryPrimitives.ReadUInt32BigEndian(headerBuffer.Span.Slice(1));

                var isTrailer = IsBitSet(compressed, pos: 7);
                if (isTrailer)
                {
                    await ReadTrailersAsync(length, data, cancellationToken).ConfigureAwait(false);

                    return(0);
                }

                _contentRemaining = length;
                // If there is no content then state is still ready
                _state = _contentRemaining > 0 ? ResponseState.Content : ResponseState.Ready;
                return(5);

            case ResponseState.Content:
                if (data.Length >= _contentRemaining)
                {
                    data = data.Slice(0, _contentRemaining);
                }

                var read = await StreamHelpers.ReadAsync(_inner, data, cancellationToken).ConfigureAwait(false);

                _contentRemaining -= read;
                if (_contentRemaining == 0)
                {
                    _state = ResponseState.Ready;
                }

                return(read);

            default:
                throw new InvalidOperationException("Unexpected state.");
            }
        }