Summary

Class:CBAM.HTTP.HTTPFactory
Assembly:CBAM.HTTP
File(s):/repo-dir/contents/Source/Code/CBAM.HTTP/HTTP.cs
Covered lines:24
Uncovered lines:12
Coverable lines:36
Total lines:762
Line coverage:66.6%
Branch coverage:33.3%

Coverage History

Metrics

MethodCyclomatic complexity NPath complexity Sequence coverage Branch coverage
CreateGETRequest(...)101%0%
CreatePOSTRequest(...)100%0%
CreatePOSTRequest(...)100%0%
CreateRequest(...)201%0.5%
CreateRequestContentFromString(...)100%0%
CreateResponse(...)101%0%
CreateHeadersDictionary()101%0%
CreateResponseContentWithKnownByteCount(...)201%0.5%
CreateResponseContentWithChunkedEncoding()200%0%

File(s)

/repo-dir/contents/Source/Code/CBAM.HTTP/HTTP.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 CBAM.HTTP;
 19using System;
 20using System.Collections.Generic;
 21using System.IO;
 22using System.Text;
 23using System.Threading;
 24using System.Threading.Tasks;
 25using UtilPack;
 26
 27namespace CBAM.HTTP
 28{
 29
 30#if NET40
 31
 32   /// <summary>
 33   /// This class exists only in .NET 4 version, and provides easy implementation for mutable <see cref="Dictionary{TKey
 34   /// </summary>
 35   /// <typeparam name="TKey">The type of keys in this dictionary.</typeparam>
 36   /// <typeparam name="TValue">The type of values in this dictionary.</typeparam>
 37   public sealed class DictionaryWithReadOnlyAPI<TKey, TValue> : Dictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TVa
 38   {
 39
 40      IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => this.Keys;
 41
 42      IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => this.Values;
 43   }
 44
 45   /// <summary>
 46   /// This class exists only in .NET 4 version, and provides easy implementation for mutable <see cref="List{T}"/>, whi
 47   /// </summary>
 48   /// <typeparam name="TValue">The type of items in this list.</typeparam>
 49   public sealed class ListWithReadOnlyAPI<TValue> : List<TValue>, IReadOnlyList<TValue>
 50   {
 51      /// <summary>
 52      /// Creates a new instance of <see cref="ListWithReadOnlyAPI{TValue}"/> with no values.
 53      /// </summary>
 54      public ListWithReadOnlyAPI()
 55         : base()
 56      {
 57      }
 58
 59      /// <summary>
 60      /// Creates a new instance of <see cref="ListWithReadOnlyAPI{TValue}"/> with given values.
 61      /// </summary>
 62      /// <param name="collection">The values for this list to have.</param>
 63      public ListWithReadOnlyAPI(
 64         IEnumerable<TValue> collection
 65         )
 66         : base( collection )
 67      {
 68      }
 69   }
 70#endif
 71
 72   /// <summary>
 73   /// This interface describes a HTTP request, which client sends to server, from client's point of view.
 74   /// </summary>
 75   /// <seealso cref="HTTPFactory"/>
 76   public interface HTTPRequest : HTTPMessage<HTTPRequestContent,
 77#if NET40
 78      DictionaryWithReadOnlyAPI<String, ListWithReadOnlyAPI<String>>
 79#else
 80      Dictionary<String, List<String>>
 81#endif
 82      ,
 83#if NET40
 84      ListWithReadOnlyAPI<String>
 85#else
 86      List<String>
 87#endif
 88      >
 89   {
 90      /// <summary>
 91      /// Gets or sets the HTTP request method, as string.
 92      /// </summary>
 93      /// <value>The HTTP request method, as string.</value>
 94      String Method { get; set; }
 95
 96      /// <summary>
 97      /// Gets or sets the HTTP request path, as string.
 98      /// </summary>
 99      /// <value>The HTTP request path, as string.</value>
 100      String Path { get; set; }
 101
 102      /// <summary>
 103      /// Gets or sets the HTTP version of this HTTP message (request or response).
 104      /// </summary>
 105      /// <value>The HTTP version of this HTTP message (request or response).</value>
 106      new String Version { get; set; }
 107
 108      /// <summary>
 109      /// Gets or sets the content of this HTTP message (request or response).
 110      /// </summary>
 111      /// <value>The content of this HTTP message (request or response).</value>
 112      new HTTPRequestContent Content { get; set; }
 113   }
 114
 115   /// <summary>
 116   /// This interface describes a HTTP response, which server sends to the client, from client's point of view.
 117   /// </summary>
 118   /// <seealso cref="HTTPFactory"/>
 119   public interface HTTPResponse : HTTPMessage<HTTPResponseContent, IReadOnlyDictionary<String, IReadOnlyList<String>>, 
 120   {
 121      /// <summary>
 122      /// Gets the status code returned by the server.
 123      /// </summary>
 124      /// <value>The status code returned by the server.</value>
 125      Int32 StatusCode { get; }
 126
 127      /// <summary>
 128      /// Gets the status code message returned by the server.
 129      /// </summary>
 130      /// <value>The status code message returned by the server.</value>
 131      String StatusCodeMessage { get; }
 132   }
 133
 134   /// <summary>
 135   /// This is common interface for <see cref="HTTPRequest"/> and <see cref="HTTPResponse"/>.
 136   /// </summary>
 137   /// <typeparam name="TContent"></typeparam>
 138   /// <typeparam name="TDictionary">The type of dictionary holding headers.</typeparam>
 139   /// <typeparam name="TList">The type of list holding values for single header.</typeparam>
 140   public interface HTTPMessage<out TContent, out TDictionary, out TList>
 141      where TContent : HTTPMessageContent
 142      where TDictionary : IReadOnlyDictionary<String, TList>
 143      where TList : IReadOnlyList<String>
 144   {
 145      /// <summary>
 146      /// Gets the HTTP headers of this HTTP message (request or response).
 147      /// </summary>
 148      /// <value>The HTTP headers of this HTTP message (request or response).</value>
 149      TDictionary Headers { get; }
 150
 151      /// <summary>
 152      /// Gets the HTTP version of this HTTP message (request or response).
 153      /// </summary>
 154      /// <value>The HTTP version of this HTTP message (request or response).</value>
 155      String Version { get; }
 156
 157      /// <summary>
 158      /// Gets the content of this HTTP message (request or response).
 159      /// </summary>
 160      /// <value>The content of this HTTP message (request or response).</value>
 161      TContent Content { get; }
 162   }
 163
 164   /// <summary>
 165   /// This is common interface for <see cref="HTTPRequestContent"/> and <see cref="HTTPResponseContent"/>.
 166   /// </summary>
 167   public interface HTTPMessageContent
 168   {
 169      /// <summary>
 170      /// Gets the amount of bytes this content takes, if the amount is known.
 171      /// </summary>
 172      /// <value>The amount of bytes this content takes, if the amount is known.</value>
 173      Int64? ByteCount { get; }
 174
 175
 176   }
 177
 178   /// <summary>
 179   /// This is the content object for <see cref="HTTPRequest"/>.
 180   /// </summary>
 181   /// <seealso cref="HTTPFactory"/>
 182   public interface HTTPRequestContent : HTTPMessageContent
 183   {
 184      /// <summary>
 185      /// Writes the content bytes of this <see cref="HTTPRequestContent"/> to given <see cref="HTTPWriter"/>.
 186      /// </summary>
 187      /// <param name="writer">The <see cref="HTTPWriter"/>.</param>
 188      /// <param name="seenByteCount">The byte count as returned by <see cref="HTTPMessageContent.ByteCount"/> property.
 189      /// <returns>Potentially asynchronously returns the amount of bytes written to <see cref="HTTPWriter"/>.</returns>
 190      ValueTask<Int64> WriteToStream( HTTPWriter writer, Int64? seenByteCount );
 191   }
 192
 193   /// <summary>
 194   /// This is content object for <see cref="HTTPResponse"/>.
 195   /// </summary>
 196   /// <seealso cref="HTTPFactory"/>
 197   public interface HTTPResponseContent : HTTPMessageContent
 198   {
 199      /// <summary>
 200      /// Gets the amount of bytes remaining in this content, if the content byte count is known.
 201      /// </summary>
 202      /// <value>The amount of bytes remaining in this content, if the content byte count is known.</value>
 203      Int64? BytesRemaining { get; }
 204
 205      /// <summary>
 206      /// Potentially asynchronously reads the given amount of bytes to given array.
 207      /// </summary>
 208      /// <param name="array">The byte array to read to.</param>
 209      /// <param name="offset">The offset in <paramref name="array"/> where to start writing bytes.</param>
 210      /// <param name="count">The maximum amount of bytes to write.</param>
 211      /// <returns>Potentially asynchronously returns amount of bytes read. The return value of <c>0</c> means that end 
 212      ValueTask<Int32> ReadToBuffer( Byte[] array, Int32 offset, Int32 count );
 213
 214      //Boolean ContentEndIsKnown { get; } // TODO not sure if we should even allow situation when this is true??
 215   }
 216
 217   /// <summary>
 218   /// This interface is used by <see cref="HTTPRequestContent.WriteToStream"/> method to write the content to HTTP serv
 219   /// </summary>
 220   public interface HTTPWriter
 221   {
 222      /// <summary>
 223      /// The buffer to use.
 224      /// It may be large enough to fit the whole contents.
 225      /// </summary>
 226      /// <value>The buffer to write content to.</value>
 227      Byte[] Buffer { get; }
 228
 229      /// <summary>
 230      /// This method will flush whatever is written to <see cref="Buffer"/> of this <see cref="HTTPWriter"/> to underly
 231      /// </summary>
 232      /// <param name="offset">The offset in <see cref="Buffer"/> where to start reading data.</param>
 233      /// <param name="count">The amount of bytes in <see cref="Buffer"/> to read.</param>
 234      /// <returns>Potentially asynchronously returns amount of bytes flushed.</returns>
 235      ValueTask<Int64> FlushBufferContents( Int32 offset, Int32 count );
 236   }
 237
 238
 239
 240   /// <summary>
 241   /// This class implements <see cref="HTTPResponseContent"/> when the content is of size <c>0</c>.
 242   /// </summary>
 243   /// <seealso cref="Instance"/>
 244   public class EmptyHTTPResponseContent : HTTPResponseContent
 245   {
 246      /// <summary>
 247      /// Gets the instance of <see cref="EmptyHTTPResponseContent"/>.
 248      /// </summary>
 249      /// <value>The instance of <see cref="EmptyHTTPResponseContent"/>.</value>
 250      public static EmptyHTTPResponseContent Instance { get; } = new EmptyHTTPResponseContent();
 251
 252      private EmptyHTTPResponseContent()
 253      {
 254
 255      }
 256
 257      //public Boolean ContentEndIsKnown => true;
 258
 259      /// <summary>
 260      /// Implements <see cref="HTTPMessageContent.ByteCount"/> and always returns <c>0</c>.
 261      /// </summary>
 262      /// <value>Always returns <c>0</c>.</value>
 263      public Int64? ByteCount => 0;
 264
 265      /// <summary>
 266      /// Implements <see cref="HTTPResponseContent.BytesRemaining"/> and always returns <c>0</c>.
 267      /// </summary>
 268      /// <value>Always returns <c>0</c>.</value>
 269      public Int64? BytesRemaining => 0;
 270
 271      /// <summary>
 272      /// Implements <see cref="HTTPResponseContent.ReadToBuffer"/> and always returns synchronously <c>0</c>.
 273      /// </summary>
 274      /// <param name="array">The byte array.</param>
 275      /// <param name="offset">The offset in byte array, ignored.</param>
 276      /// <param name="count">The maximum amount of bytes to read, ignored.</param>
 277      /// <returns>Always returns <c>0</c> synchronously.</returns>
 278      public ValueTask<Int32> ReadToBuffer( Byte[] array, Int32 offset, Int32 count )
 279      {
 280         array.CheckArrayArguments( offset, count, false );
 281         return new ValueTask<Int32>( 0 );
 282      }
 283   }
 284
 285
 286   /// <summary>
 287   /// This static class provides methods to create instances of <see cref="HTTPRequest"/>, <see cref="HTTPResponse"/>, 
 288   /// </summary>
 289   public static class HTTPFactory
 290   {
 291
 292      /// <summary>
 293      /// This is string constant for HTTP version 1.1.
 294      /// </summary>
 295      public const String VERSION_HTTP1_1 = "HTTP/1.1";
 296
 297      /// <summary>
 298      /// This is string constant for HTTP method GET.
 299      /// </summary>
 300      public const String METHOD_GET = "GET";
 301
 302      /// <summary>
 303      /// This is string constant for HTTP method POST.
 304      /// </summary>
 305      public const String METHOD_POST = "POST";
 306
 307      /// <summary>
 308      /// This is string constant for HTTP method HEAD.
 309      /// </summary>
 310      public const String METHOD_HEAD = "HEAD";
 311
 312      /// <summary>
 313      /// This is string constant for HTTP method PUT.
 314      /// </summary>
 315      public const String METHOD_PUT = "PUT";
 316
 317      /// <summary>
 318      /// This is string constant for HTTP method DELETE.
 319      /// </summary>
 320      public const String METHOD_DELETE = "DELETE";
 321
 322      /// <summary>
 323      /// This is string constant for HTTP method CONNECT.
 324      /// </summary>
 325      public const String METHOD_CONNECT = "CONNECT";
 326
 327      /// <summary>
 328      /// This is string constant for HTTP method OPTIONS.
 329      /// </summary>
 330      public const String METHOD_OPTIONS = "OPTIONS";
 331
 332      /// <summary>
 333      /// This is string constant for HTTP method TRACE.
 334      /// </summary>
 335      public const String METHOD_TRACE = "TRACE";
 336
 337      /// <summary>
 338      /// This is string constant for HTTP method PATCH.
 339      /// </summary>
 340      public const String METHOD_PATCH = "PATCH";
 341
 342      /// <summary>
 343      /// Creates a <see cref="HTTPRequest"/> with <c>"GET"</c> method and given path and version.
 344      /// </summary>
 345      /// <param name="path">The value for <see cref="HTTPRequest.Path"/>.</param>
 346      /// <param name="version">The optional value for <see cref="HTTPMessage{TContent, TDictionary, TList}.Version"/>, 
 347      /// <returns>A new instance of <see cref="HTTPRequest"/> with no headers and properties set to given values.</retu
 348      public static HTTPRequest CreateGETRequest(
 349         String path,
 350         String version = VERSION_HTTP1_1
 2351         ) => CreateRequest( path, method: METHOD_GET, version: version, content: null );
 352
 353      /// <summary>
 354      /// Creates a <see cref="HTTPRequest"/> with <c>"POST"</c> method and given path, content, and version.
 355      /// </summary>
 356      /// <param name="path">The value for <see cref="HTTPRequest.Path"/>.</param>
 357      /// <param name="content">The value for <see cref="HTTPMessage{TContent, TDictionary, TList}.Content"/>.</param>
 358      /// <param name="version">The optional value for <see cref="HTTPMessage{TContent, TDictionary, TList}.Version"/>, 
 359      /// <returns>A new instance of <see cref="HTTPRequest"/> with no headers and properties set to given values.</retu
 360      public static HTTPRequest CreatePOSTRequest(
 361         String path,
 362         HTTPRequestContent content,
 363         String version = VERSION_HTTP1_1
 0364         ) => CreateRequest( path, method: METHOD_POST, version: version, content: content );
 365
 366      /// <summary>
 367      /// Creates a <see cref="HTTPRequest"/> with <c>"POST"</c> method and given path, textual content, and version.
 368      /// </summary>
 369      ///<param name="path">The value for <see cref="HTTPRequest.Path"/>.</param>
 370      /// <param name="textualContent">The content for <see cref="HTTPMessage{TContent, TDictionary, TList}.Content"/> a
 371      /// <param name="version">The optional value for <see cref="HTTPMessage{TContent, TDictionary, TList}.Version"/>, 
 372      /// <param name="encoding">The optional <see cref="Encoding"/> to use when sending <paramref name="textualContent"
 373      /// <returns>A new instance of <see cref="HTTPRequest"/> with no headers and properties set to given values.</retu
 374      public static HTTPRequest CreatePOSTRequest(
 375         String path,
 376         String textualContent,
 377         String version = VERSION_HTTP1_1,
 378         Encoding encoding = null
 0379         ) => CreateRequest( path, METHOD_POST, CreateRequestContentFromString( textualContent, encoding ), version: ver
 380
 381      /// <summary>
 382      /// Generic method to create <see cref="HTTPRequest"/> with given properties.
 383      /// </summary>
 384      /// <param name="path">The value for <see cref="HTTPRequest.Path"/>.</param>
 385      /// <param name="method">The value for <see cref="HTTPRequest.Method"/>.</param>
 386      /// <param name="content">The value for <see cref="HTTPMessage{TContent, TDictionary, TList}.Content"/>.</param>
 387      /// <param name="version">The optional value for <see cref="HTTPMessage{TContent, TDictionary, TList}.Version"/>, 
 388      /// <returns>A new instance of <see cref="HTTPRequest"/> with no headers and properties set to given values.</retu
 389      public static HTTPRequest CreateRequest(
 390         String path,
 391         String method,
 392         HTTPRequestContent content,
 393         String version = VERSION_HTTP1_1
 394         )
 395      {
 2396         HTTPRequest retVal = new HTTPRequestImpl()
 2397         {
 2398            Method = method,
 2399            Path = path
 2400         };
 401
 2402         retVal.Version = String.IsNullOrEmpty( version ) ? VERSION_HTTP1_1 : version;
 2403         retVal.Content = content;
 2404         return retVal;
 405      }
 406
 407      /// <summary>
 408      /// Creates a new instance of <see cref="HTTPRequestContent"/> which has given <see cref="String"/> as content.
 409      /// </summary>
 410      /// <param name="textualContent">The string for the content.</param>
 411      /// <param name="encoding">The optional <see cref="Encoding"/> to use when sending <paramref name="textualContent"
 412      /// <returns>A new instance of <see cref="HTTPRequestContent"/> which will use <paramref name="textualContent"/> a
 413      public static HTTPRequestContent CreateRequestContentFromString(
 414         String textualContent,
 415         Encoding encoding = null
 0416         ) => new HTTPRequestContentFromString( textualContent, encoding );
 417
 418
 419      /// <summary>
 420      /// Creates a new instance of <see cref="HTTPResponse"/> with given parameters.
 421      /// </summary>
 422      /// <param name="version">The value for <see cref="HTTPMessage{TContent, TDictionary, TList}.Version"/> property.<
 423      /// <param name="statusCode">The value for <see cref="HTTPResponse.StatusCode"/> property.</param>
 424      /// <param name="statusMessage">The value for <see cref="HTTPResponse.StatusCodeMessage"/> property.</param>
 425      /// <param name="headers">The value for <see cref="HTTPMessage{TContent, TDictionary, TList}.Headers"/>.</param>
 426      /// <param name="content">The value for <see cref="HTTPMessage{TContent, TDictionary, TList}.Content"/> property.<
 427      /// <returns>A new instance of <see cref="HTTPResponse"/> with no headers and properties set to given values.</ret
 428      public static HTTPResponse CreateResponse(
 429         String version,
 430         Int32 statusCode,
 431         String statusMessage,
 432         IDictionary<String, List<String>> headers,
 433         HTTPResponseContent content
 434         )
 435      {
 2436         return new HTTPResponseImpl(
 2437            statusCode,
 2438            statusMessage,
 2439            version,
 2440            headers,
 2441            content
 2442            );
 443      }
 444
 445      /// <summary>
 446      /// Helper method to create mutable dictionary to hold headers.
 447      /// </summary>
 448      /// <returns>A new instance of headers dictionary</returns>
 449      public static IDictionary<String, List<String>> CreateHeadersDictionary()
 450      {
 2451         return new Dictionary<String, List<String>>( StringComparer.OrdinalIgnoreCase );
 452      }
 453
 454      /// <summary>
 455      /// Creates a new instance of <see cref="HTTPResponseContent"/> reading its contents from <see cref="Stream"/> whe
 456      /// </summary>
 457      /// <param name="stream">The <see cref="Stream"/> where to read contents from.</param>
 458      /// <param name="buffer">The buffer containing pre-read data, this will be used before <paramref name="stream"/> u
 459      /// <param name="bufferAdvanceState">The advance state of the buffer.</param>
 460      /// <param name="byteCount">The content size in bytes.</param>
 461      /// <param name="token">The <see cref="CancellationToken"/> to use in asynchronous method calls.</param>
 462      /// <returns>A <see cref="HTTPResponseContent"/> which has pre-determined content length and reads its contents fr
 463      public static HTTPResponseContent CreateResponseContentWithKnownByteCount(
 464         Stream stream,
 465         Byte[] buffer,
 466         BufferAdvanceState bufferAdvanceState,
 467         Int64 byteCount,
 468         CancellationToken token
 469         )
 470      {
 2471         return byteCount <= 0 ? (HTTPResponseContent) EmptyHTTPResponseContent.Instance : new HTTPResponseContentFromSt
 2472            stream,
 2473            buffer,
 2474            bufferAdvanceState,
 2475            byteCount,
 2476            token
 2477            );
 478      }
 479
 480      /// <summary>
 481      /// Creates a new instance of <see cref="HTTPResponseContent"/> that utilizes chunked transfer encoding when it re
 482      /// </summary>
 483      /// <param name="stream">The <see cref="Stream"/> where to read contents from.</param>
 484      /// <param name="buffer">The buffer containing pre-read data, this will be used before <paramref name="stream"/> u
 485      /// <param name="bufferAdvanceState">The advance state of the buffer.</param>
 486      /// <param name="singleStreamReadCount">The amount of bytes to read from underlying stream at once.</param>
 487      /// <param name="token">The <see cref="CancellationToken"/> to use in asynchronous method calls.</param>
 488      /// <returns>A <see cref="HTTPResponseContent"/> which reads data using chunked transfer encoding.</returns>
 489      public static async ValueTask<HTTPResponseContent> CreateResponseContentWithChunkedEncoding(
 490         Stream stream,
 491         ResizableArray<Byte> buffer,
 492         BufferAdvanceState bufferAdvanceState,
 493         Int32 singleStreamReadCount,
 494         CancellationToken token
 495         )
 496      {
 0497         return new HTTPResponseContentFromStream_Chunked(
 0498               stream,
 0499               buffer,
 0500               bufferAdvanceState,
 0501               await HTTPResponseContentFromStream_Chunked.ReadNextChunk( stream, buffer, bufferAdvanceState, singleStre
 0502               singleStreamReadCount,
 0503               token
 0504               );
 0505      }
 506
 507      ///// <summary>
 508      ///// Creates a new instance of <see cref="HTTPResponseContent"/> which operates on <see cref="Stream"/> to read d
 509      ///// </summary>
 510      ///// <param name="stream">The stream to read data from.</param>
 511      ///// <param name="byteCount">The amount of data, if known.</param>
 512      ///// <param name="onEnd">The callback to run when end of data is encountered.</param>
 513      ///// <returns>A new instance of<see cref="HTTPResponseContent"/> which redirects read actions to underlying <see 
 514      ///// <exception cref="ArgumentNullException">If <paramref name="stream"/> is <c>null</c>.</exception>
 515      //public static HTTPResponseContent CreateResponseContentFromStream(
 516      //   Stream stream,
 517      //   Int64? byteCount,
 518      //   Func<ValueTask<Boolean>> onEnd
 519      //   ) => new HTTPResponseContentFromStream( stream, byteCount, onEnd );
 520
 521      //public static HTTPResponseContent CreateResponseContentFromStreamChunked(
 522      //   Stream stream,
 523      //   Int64? byteCount,
 524      //   Func<ValueTask<Boolean>> onEnd
 525      //   ) => new HTTPResponseContentFromStream_Chunked(  new HTTPResponseContentFromStream( stream, byteCount, onEnd 
 526   }
 527
 528   /// <summary>
 529   /// This class contains various utility methods.
 530   /// </summary>
 531   public static class HTTPUtils
 532   {
 533      /// <summary>
 534      /// Helper method to erase data that has been read (starting from index 0 and having <see cref="BufferAdvanceState
 535      /// </summary>
 536      /// <param name="aState">The <see cref="BufferAdvanceState"/>.</param>
 537      /// <param name="buffer">The <see cref="ResizableArray{T}"/> buffer.</param>
 538      /// <param name="isFirstRead">Whether this is first read (the status line of HTTP message).</param>
 539      public static void EraseReadData(
 540         BufferAdvanceState aState,
 541         ResizableArray<Byte> buffer,
 542         Boolean isFirstRead = false
 543         )
 544      {
 545         var end = aState.BufferOffset;
 546         var preReadLength = aState.BufferTotal;
 547         // Message parts end with CRLF
 548         if ( !isFirstRead )
 549         {
 550            end += 2;
 551         }
 552         var remainingData = preReadLength - end;
 553         if ( remainingData > 0 )
 554         {
 555            var array = buffer.Array;
 556            Array.Copy( array, end, array, 0, remainingData );
 557         }
 558         aState.Reset();
 559         aState.ReadMore( remainingData );
 560      }
 561   }
 562}
 563
 564/// <summary>
 565/// This class contains extensions methods for types defined in this assembly.
 566/// </summary>
 567public static partial class E_CBAM
 568{
 569   /// <summary>
 570   /// Helper method to invoke <see cref="HTTPWriter.FlushBufferContents"/> with <c>0</c> as first argument to offset.
 571   /// </summary>
 572   /// <param name="writer">This <see cref="HTTPWriter"/>.</param>
 573   /// <param name="count">The amount of bytes from beginning of the <see cref="HTTPWriter.Buffer"/> to flush.</param>
 574   /// <returns>The amount of bytes written.</returns>
 575   /// <exception cref="NullReferenceException">If this <see cref="HTTPWriter"/> is <c>null</c>.</exception>
 576   public static ValueTask<Int64> FlushBufferContents( this HTTPWriter writer, Int32 count )
 577   {
 578      return writer.FlushBufferContents( 0, count );
 579   }
 580
 581   /// <summary>
 582   /// Helper method to add header with given name and value to this <see cref="HTTPMessage{TContent, TDictionary, TList
 583   /// </summary>
 584   /// <param name="message">This <see cref="HTTPMessage{TContent, TDictionary, TList}"/>.</param>
 585   /// <param name="headerName">The name of the header.</param>
 586   /// <param name="headerValue">The value of the header.</param>
 587   /// <returns>This <see cref="HTTPMessage{TContent, TDictionary, TList}"/>.</returns>
 588   /// <exception cref="NullReferenceException">If this <see cref="HTTPMessage{TContent, TDictionary, TList}"/> is <c>nu
 589   public static HTTPRequest WithHeader( this HTTPRequest message, String headerName, String headerValue )
 590   {
 591      message.Headers
 592         .GetOrAdd_NotThreadSafe( headerName, hn => new
 593#if NET40
 594         ListWithReadOnlyAPI
 595#else
 596         List
 597#endif
 598         <String>() )
 599         .Add( headerValue );
 600
 601      return message;
 602   }
 603
 604   /// <summary>
 605   /// Helper method to read all content of this <see cref="HTTPResponseContent"/> into single byte array, if the byte s
 606   /// </summary>
 607   /// <param name="content">This <see cref="HTTPResponseContent"/>.</param>
 608   /// <returns>Potentially asynchronously returns a new byte array with the contents read from this <see cref="HTTPResp
 609   /// <exception cref="NullReferenceException">If this <see cref="HTTPResponseContent"/> is <c>null</c>.</exception>
 610   ///// <exception cref="InvalidOperationException">If this <see cref="HTTPResponseContent"/> does not know its byte si
 611   public static ValueTask<Byte[]> ReadAllContentAsync( this HTTPResponseContent content )
 612   {
 613      //ArgumentValidator.ValidateNotNullReference( content );
 614      //if ( !content.ContentEndIsKnown )
 615      //{
 616      //   throw new InvalidOperationException( "The response content does not know where the data ends." );
 617      //}
 618
 619      var length = content.ByteCount;
 620      return length.HasValue ?
 621         content.ReadAllContentIfKnownSizeAsync( content.BytesRemaining.Value ) :
 622         content.ReadAllContentGrowBuffer();
 623   }
 624
 625   private static async ValueTask<Byte[]> ReadAllContentIfKnownSizeAsync( this HTTPResponseContent content, Int64 length
 626   {
 627      if ( length64 > Int32.MaxValue )
 628      {
 629         throw new InvalidOperationException( "The content length is too big: " + length64 );
 630      }
 631      else if ( length64 < 0 )
 632      {
 633         throw new InvalidOperationException( "The content length is negative: " + length64 );
 634      }
 635
 636      var length = (Int32) length64;
 637      Byte[] retVal;
 638      if ( length > 0 )
 639      {
 640         retVal = new Byte[length];
 641         var offset = 0;
 642         do
 643         {
 644            var readCount = await content.ReadToBuffer( retVal, offset, length - offset );
 645            offset += readCount;
 646            if ( offset < length && readCount <= 0 )
 647            {
 648               throw new EndOfStreamException();
 649            }
 650         } while ( offset < length );
 651      }
 652      else
 653      {
 654         retVal = Empty<Byte>.Array;
 655      }
 656
 657      return retVal;
 658   }
 659
 660   private static async ValueTask<Byte[]> ReadAllContentGrowBuffer( this HTTPResponseContent content )
 661   {
 662      var buffer = new ResizableArray<Byte>( exponentialResize: false );
 663      var bytesRead = 0;
 664      Int32 bytesRemaining;
 665      Int32 bytesSeen = 0;
 666      while ( ( bytesRemaining = (Int32) content.BytesRemaining.Value ) > 0 )
 667      {
 668         bytesSeen += bytesRemaining;
 669         bytesRead += await content.ReadToBuffer( buffer.SetCapacityAndReturnArray( bytesRead + bytesRemaining ), bytesR
 670      }
 671      // Since exponentialResize was set to false, the array will never be too big
 672      return buffer.Array;
 673   }
 674
 675   /// <summary>
 676   /// This is helper method to write a <see cref="String"/> to this <see cref="HTTPWriter"/> using given <see cref="Enc
 677   /// </summary>
 678   /// <param name="writer">This <see cref="HTTPWriter"/>.</param>
 679   /// <param name="encoding">The <see cref="Encoding"/> to use.</param>
 680   /// <param name="str">The <see cref="String"/> to write.</param>
 681   /// <param name="strByteCount">The string byte count, as returned by <see cref="Encoding.GetByteCount(string)"/>. May
 682   /// <param name="bufferIndex">The index in <see cref="HTTPWriter.Buffer"/> where to start writing.</param>
 683   /// <returns>Potentially asynchronously returns amount of bytes written.</returns>
 684   /// <exception cref="NullReferenceException">If this <see cref="HTTPWriter"/> is <c>null</c>.</exception>
 685   /// <exception cref="ArgumentNullException">If either of <paramref name="encoding"/> or <paramref name="str"/> is <c>
 686   /// <remarks>
 687   /// This method also takes care of situation when the <paramref name="str"/> does not fit into <see cref="HTTPWriter.
 688   /// </remarks>
 689   public static ValueTask<Int64> WriteToStreamAsync(
 690      this HTTPWriter writer,
 691      Encoding encoding,
 692      String str,
 693      Int32? strByteCount,
 694      Int32 bufferIndex = 0
 695      )
 696   {
 697      ArgumentValidator.ValidateNotNull( nameof( encoding ), encoding );
 698      ArgumentValidator.ValidateNotNull( nameof( str ), str );
 699
 700      var buffer = writer.Buffer;
 701      var bufferLen = buffer.Length;
 702
 703      var byteCount = strByteCount ?? encoding.GetByteCount( str );
 704
 705      ValueTask<Int64> retVal;
 706      if ( bufferLen >= byteCount )
 707      {
 708         // Can just write it directly
 709         retVal = writer.FlushBufferContents( bufferIndex + encoding.GetBytes( str, 0, str.Length, buffer, bufferIndex )
 710      }
 711      else
 712      {
 713         retVal = MultiPartWriteToStreamAsync( writer, encoding, str, byteCount, bufferIndex );
 714      }
 715
 716      return retVal;
 717   }
 718
 719   private static async ValueTask<Int64> MultiPartWriteToStreamAsync(
 720      HTTPWriter writer,
 721      Encoding encoding,
 722      String text,
 723      Int32 seenByteCount,
 724      Int32 bufferIndex
 725      )
 726   {
 727      var buffer = writer.Buffer;
 728      var bufferLen = buffer.Length;
 729
 730      // Make sure there is always room for max size (4) char
 731      if ( bufferLen - bufferIndex <= 4 )
 732      {
 733         throw new InvalidOperationException( "Too small buffer" );
 734      }
 735
 736      bufferLen -= 4;
 737
 738      var cur = 0;
 739      var textLen = text.Length;
 740      do
 741      {
 742         while ( cur < textLen && bufferIndex < bufferLen )
 743         {
 744            Int32 count;
 745            if ( Char.IsLowSurrogate( text[cur] ) && cur < textLen - 1 && Char.IsHighSurrogate( text[cur + 1] ) )
 746            {
 747               count = 2;
 748            }
 749            else
 750            {
 751               count = 1;
 752            }
 753            bufferIndex += encoding.GetBytes( text, cur, count, buffer, bufferIndex );
 754            cur += count;
 755         }
 756         await writer.FlushBufferContents( bufferIndex );
 757         bufferIndex = 0;
 758      } while ( cur < textLen );
 759
 760      return seenByteCount;
 761   }
 762}