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; }
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."); } }