Summary

Class:CBAM.HTTP.HTTPResponseContentFromStream_Chunked
Assembly:CBAM.HTTP
File(s):/repo-dir/contents/Source/Code/CBAM.HTTP/HTTP.Implementation.cs
Covered lines:0
Uncovered lines:71
Coverable lines:71
Total lines:387
Line coverage:0%
Branch coverage:0%

Coverage History

Metrics

MethodCyclomatic complexity NPath complexity Sequence coverage Branch coverage
.cctor()100%0%
.ctor(...)100%0%
ReadToBuffer()1400%0%
ReadNextChunk()1200%0%
ExtractASCIIHexValue(...)1200%0%

File(s)

/repo-dir/contents/Source/Code/CBAM.HTTP/HTTP.Implementation.cs

#LineLine coverage
 1/*
 2 * Copyright 2017 Stanislav Muhametsin. All rights Reserved.
 3 *
 4 * Licensed  under the  Apache License,  Version 2.0  (the "License");
 5 * you may not use  this file  except in  compliance with the License.
 6 * You may obtain a copy of the License at
 7 *
 8 *   http://www.apache.org/licenses/LICENSE-2.0
 9 *
 10 * Unless required by applicable law or agreed to in writing, software
 11 * distributed  under the  License is distributed on an "AS IS" BASIS,
 12 * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
 13 * implied.
 14 *
 15 * See the License for the specific language governing permissions and
 16 * limitations under the License.
 17 */
 18using System;
 19using System.Collections.Generic;
 20using System.IO;
 21using System.Linq;
 22using System.Text;
 23using System.Threading;
 24using System.Threading.Tasks;
 25using UtilPack;
 26
 27using TRequestHeaderDictionary =
 28#if NET40
 29   CBAM.HTTP.DictionaryWithReadOnlyAPI<System.String, CBAM.HTTP.ListWithReadOnlyAPI<System.String>>
 30#else
 31   System.Collections.Generic.Dictionary<System.String, System.Collections.Generic.List<string>>
 32#endif
 33   ;
 34
 35
 36namespace CBAM.HTTP
 37{
 38   internal sealed class HTTPRequestImpl : HTTPRequest
 39   {
 40      public HTTPRequestImpl()
 41      {
 42         this.Headers = new TRequestHeaderDictionary();
 43      }
 44
 45      public TRequestHeaderDictionary Headers { get; }
 46
 47      public String Method { get; set; }
 48      public String Path { get; set; }
 49
 50      public String Version { get; set; }
 51
 52      public HTTPRequestContent Content { get; set; }
 53
 54
 55   }
 56
 57   internal sealed class HTTPResponseImpl : HTTPResponse
 58   {
 59      public HTTPResponseImpl(
 60         Int32 statusCode,
 61         String statusCodeMessage,
 62         String version,
 63         IDictionary<String, List<String>> headers,
 64         HTTPResponseContent content
 65         )
 66      {
 67         this.Headers = new System.Collections.ObjectModel.ReadOnlyDictionary<String, IReadOnlyList<String>>( headers.To
 68            kvp => kvp.Key,
 69            kvp =>
 70#if NET40
 71            new ListWithReadOnlyAPI<String>
 72#else
 73            new System.Collections.ObjectModel.ReadOnlyCollection<String>
 74#endif
 75            ( kvp.Value )
 76            ) );
 77         this.StatusCode = statusCode;
 78         this.StatusCodeMessage = statusCodeMessage;
 79         this.Version = ArgumentValidator.ValidateNotEmpty( nameof( version ), version );
 80         this.Content = ArgumentValidator.ValidateNotNull( nameof( content ), content );
 81      }
 82
 83      public IReadOnlyDictionary<String, IReadOnlyList<String>> Headers { get; }
 84
 85      public Int32 StatusCode { get; }
 86      public String StatusCodeMessage { get; }
 87
 88      public String Version { get; }
 89
 90      public HTTPResponseContent Content { get; }
 91
 92
 93   }
 94
 95   internal sealed class HTTPRequestContentFromString : HTTPRequestContent
 96   {
 97      public HTTPRequestContentFromString(
 98         String str,
 99         Encoding encoding
 100         )
 101      {
 102         this.StringContent = str ?? String.Empty;
 103         this.Encoding = encoding ?? System.Text.Encoding.UTF8;
 104      }
 105
 106      public Int64? ByteCount => this.Encoding.GetByteCount( this.StringContent );
 107
 108      public ValueTask<Int64> WriteToStream( HTTPWriter writer, Int64? seenByteCount )
 109      {
 110         return writer.WriteToStreamAsync( this.Encoding, this.StringContent, seenByteCount.HasValue ? (Int32?) seenByte
 111      }
 112
 113      public Encoding Encoding { get; set; }
 114
 115      public String StringContent { get; }
 116
 117      public Boolean ContentEndIsKnown => true;
 118   }
 119
 120   internal sealed class HTTPResponseContentFromStream_KnownLength : HTTPResponseContent
 121   {
 122      private const Int32 INITIAL = 0;
 123      private const Int32 READING = 1;
 124
 125      private readonly Stream _stream;
 126      private readonly Byte[] _preReadData;
 127      private readonly BufferAdvanceState _bufferAdvanceState;
 128      private readonly CancellationToken _token;
 129      private readonly Int64 _byteCount;
 130      private Int64 _bytesRemaining;
 131      private Int32 _state;
 132
 133
 134      public HTTPResponseContentFromStream_KnownLength(
 135         Stream stream,
 136         Byte[] buffer,
 137         BufferAdvanceState bufferAdvanceState,
 138         Int64 byteCount,
 139         CancellationToken token
 140         )
 141      {
 142         this._stream = ArgumentValidator.ValidateNotNull( nameof( stream ), stream );
 143         this._preReadData = ArgumentValidator.ValidateNotNull( nameof( buffer ), buffer );
 144         this._bufferAdvanceState = ArgumentValidator.ValidateNotNull( nameof( BufferAdvanceState ), bufferAdvanceState 
 145         this._byteCount = byteCount;
 146         this._bytesRemaining = byteCount;
 147         this._token = token;
 148      }
 149
 150      public Boolean ContentEndIsKnown => this.ByteCount.HasValue;
 151
 152      public Int64? ByteCount => this._byteCount;
 153
 154      public Int64? BytesRemaining => this._bytesRemaining < 0 ? default : Interlocked.Read( ref this._bytesRemaining );
 155
 156      public async ValueTask<Int32> ReadToBuffer( Byte[] array, Int32 offset, Int32 count )
 157      {
 158         array.CheckArrayArguments( offset, count, true );
 159         Int32 bytesRead;
 160         if ( Interlocked.CompareExchange( ref this._state, READING, INITIAL ) == INITIAL )
 161         {
 162            // TODO support for multi-part form stuff
 163            try
 164            {
 165               var remaining = this._bytesRemaining;
 166
 167               if ( remaining == 0 )
 168               {
 169                  bytesRead = 0;
 170               }
 171               else
 172               {
 173                  if ( remaining > 0 )
 174                  {
 175                     count = (Int32) Math.Min( count, remaining );
 176                  }
 177
 178                  var aState = this._bufferAdvanceState;
 179                  var bufferRemaining = aState.BufferTotal - aState.BufferOffset;
 180                  bytesRead = 0;
 181                  if ( bufferRemaining > 0 )
 182                  {
 183                     // We have some data in buffer
 184                     var bufferReadCount = Math.Min( bufferRemaining, count );
 185                     Array.Copy( this._preReadData, aState.BufferOffset, array, offset, bufferReadCount );
 186                     aState.Advance( bufferReadCount );
 187                     if ( remaining > 0 )
 188                     {
 189                        this._bytesRemaining -= bufferReadCount;
 190                     }
 191                     count -= bufferReadCount;
 192                     offset += bufferReadCount;
 193                     bytesRead = bufferReadCount;
 194
 195                  }
 196
 197                  if ( count > 0 )
 198                  {
 199                     var streamRead = await this._stream.ReadAsync( array, offset, count, this._token );
 200                     bytesRead += streamRead;
 201                     if ( remaining > 0 )
 202                     {
 203                        this._bytesRemaining -= streamRead;
 204                     }
 205                  }
 206
 207               }
 208            }
 209            finally
 210            {
 211               Interlocked.Exchange( ref this._state, INITIAL );
 212            }
 213         }
 214         else
 215         {
 216            throw new InvalidOperationException( "Concurrent access" );
 217         }
 218
 219         return bytesRead;
 220      }
 221   }
 222
 223   internal sealed class HTTPResponseContentFromStream_Chunked : HTTPResponseContent
 224   {
 0225      private static readonly Byte[] CRLF = new[] { (Byte) '\r', (Byte) '\n' };
 0226      private static readonly Byte[] TerminatingChunk = new[] { (Byte) '0', (Byte) '\r', (Byte) '\n' }; //, (Byte) '\r',
 227
 228      private const Int32 INITIAL = 0;
 229      private const Int32 READING = 1;
 230
 231      private readonly Stream _stream;
 232      private readonly ResizableArray<Byte> _buffer;
 233      private readonly BufferAdvanceState _advanceState;
 234      private readonly Int32 _streamReadCount;
 235      private readonly CancellationToken _token;
 236
 237
 238      private Int32 _state;
 239      private Int32 _chunkRemaining;
 240
 0241      public HTTPResponseContentFromStream_Chunked(
 0242         Stream stream,
 0243         ResizableArray<Byte> buffer,
 0244         BufferAdvanceState advanceState,
 0245         Int32 firstChunkCount,
 0246         Int32 streamReadCount,
 0247         CancellationToken token
 0248         )
 249      {
 0250         this._state = INITIAL;
 0251         this._stream = ArgumentValidator.ValidateNotNull( nameof( stream ), stream );
 0252         this._buffer = ArgumentValidator.ValidateNotNull( nameof( buffer ), buffer );
 0253         this._advanceState = ArgumentValidator.ValidateNotNull( nameof( advanceState ), advanceState );
 0254         this._token = token;
 0255         this._streamReadCount = streamReadCount;
 0256         this._chunkRemaining = Math.Max( 0, firstChunkCount );
 0257      }
 258
 259
 0260      public Int64? BytesRemaining => Math.Max( this._chunkRemaining, 0 );
 261
 0262      public Int64? ByteCount => null;
 263
 0264      public Boolean ContentEndIsKnown => true;
 265
 266      public async ValueTask<Int32> ReadToBuffer( Byte[] array, Int32 offset, Int32 count )
 267      {
 0268         array.CheckArrayArguments( offset, count, true );
 269         Int32 retVal;
 0270         if ( Interlocked.CompareExchange( ref this._state, READING, INITIAL ) == INITIAL )
 271         {
 272            try
 273            {
 0274               if ( this._chunkRemaining < 0 )
 275               {
 276                  // Ended
 0277                  retVal = 0;
 0278               }
 279               else
 280               {
 281                  // Our whole chunk data has already been read to buffer.
 0282                  retVal = Math.Min( this._chunkRemaining, count );
 0283                  Array.Copy( this._buffer.Array, this._advanceState.BufferOffset, array, offset, retVal );
 0284                  this._advanceState.Advance( retVal );
 0285                  this._chunkRemaining -= retVal;
 286
 0287                  if ( this._chunkRemaining == 0 )
 288                  {
 289                     // We must read next chunk
 0290                     HTTPUtils.EraseReadData( this._advanceState, this._buffer );
 0291                     if ( ( this._chunkRemaining = await ReadNextChunk( this._stream, this._buffer, this._advanceState, 
 292                     {
 293                        // Clear data
 0294                        HTTPUtils.EraseReadData( this._advanceState, this._buffer );
 295                     }
 296
 297                  }
 298               }
 0299            }
 300            finally
 301            {
 0302               Interlocked.Exchange( ref this._state, INITIAL );
 303            }
 304         }
 305         else
 306         {
 0307            throw new InvalidOperationException( "Concurrent access" );
 308         }
 309
 0310         return retVal;
 0311      }
 312
 313
 314
 315
 316      // When this method is done, the buffer will have header + chunk (including terminating CRLF) in its contents
 317      // It assumes that chunk headers starts at advanceState.BufferOffset
 318      // Returns the chunk length (>0) or -1 if last chunk. Will be -1 if last chunk. The advanceState.BufferOffset will
 319      public static async Task<Int32> ReadNextChunk(
 320         Stream stream,
 321         ResizableArray<Byte> buffer,
 322         BufferAdvanceState advanceState,
 323         Int32 streamReadCount,
 324         CancellationToken token
 325         )
 326      {
 0327         var start = advanceState.BufferOffset;
 0328         await stream.ReadUntilMaybeAsync( buffer, advanceState, CRLF, streamReadCount );
 0329         var array = buffer.Array;
 330         const Int32 LAST_CHUNK_SIZE = 3;
 0331         var isLastChunk = ArrayEqualityComparer<Byte>.RangeEquality( TerminatingChunk, 0, LAST_CHUNK_SIZE, array, start
 0332         advanceState.Advance( 2 );
 333         Int32 retVal;
 0334         if ( isLastChunk )
 335         {
 336            // TODO trailers!
 0337            await stream.ReadUntilMaybeAsync( buffer, advanceState, CRLF, streamReadCount );
 0338            retVal = -1;
 0339         }
 340         else
 341         {
 0342            var idx = start;
 0343            retVal = 0;
 344            Int32 curHex;
 0345            while ( ( curHex = ExtractASCIIHexValue( array, idx ) ) >= 0 )
 346            {
 0347               retVal = 0x10 * retVal + curHex;
 0348               ++idx;
 349            }
 350            // TODO extensions!
 351            // Now read content.
 0352            streamReadCount = retVal + 2 - ( advanceState.BufferTotal - advanceState.BufferOffset );
 0353            if ( streamReadCount > 0 )
 354            {
 0355               await stream.ReadSpecificAmountAsync( buffer, advanceState.BufferTotal, streamReadCount, token );
 0356               advanceState.ReadMore( streamReadCount );
 357            }
 358         }
 359
 0360         return retVal;
 361
 0362      }
 363
 364      private static Int32 ExtractASCIIHexValue( Byte[] array, Int32 idx )
 365      {
 0366         var cur = array[idx];
 367         Int32 retVal;
 0368         if ( cur >= '0' && cur <= '9' )
 369         {
 0370            retVal = cur - 0x30;
 0371         }
 0372         else if ( ( cur >= 'A' && cur <= 'F' ) )
 373         {
 0374            retVal = cur - 0x37;
 0375         }
 0376         else if ( ( cur >= 'a' && cur <= 'f' ) )
 377         {
 0378            retVal = cur - 0x57;
 0379         }
 380         else
 381         {
 0382            retVal = -1;
 383         }
 0384         return retVal;
 385      }
 386   }
 387}