Summary

Class:E_CBAM
Assembly:CBAM.HTTP
File(s):/repo-dir/contents/Source/Code/CBAM.HTTP/Connection.cs
/repo-dir/contents/Source/Code/CBAM.HTTP/HTTP.cs
Covered lines:37
Uncovered lines:43
Coverable lines:80
Total lines:1013
Line coverage:46.2%
Branch coverage:34.2%

Coverage History

Metrics

MethodCyclomatic complexity NPath complexity Sequence coverage Branch coverage
ReceiveOneTextualResponseAsync(...)101%0%
CreateTextualResponseInfoAsync()200.857%1%
FlushBufferContents(...)100%0%
WithHeader(...)201%1%
ReadAllContentAsync(...)201%0.5%
ReadAllContentIfKnownSizeAsync()800.765%1%
ReadAllContentGrowBuffer()400%0%
WriteToStreamAsync(...)400%0%
MultiPartWriteToStreamAsync()1600%0%

File(s)

/repo-dir/contents/Source/Code/CBAM.HTTP/Connection.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.Abstractions;
 19using CBAM.HTTP;
 20using System;
 21using System.Collections.Generic;
 22using System.Text;
 23using System.Threading.Tasks;
 24using UtilPack;
 25
 26namespace CBAM.HTTP
 27{
 28   /// <summary>
 29   /// This interface extends <see cref="Connection{TStatement, TStatementInformation, TStatementCreationArgs, TEnumerab
 30   /// </summary>
 31   /// <typeparam name="TRequestMetaData">The type of metadata associated with each request, used in identifying the req
 32   public interface HTTPConnection<TRequestMetaData> : Connection<HTTPStatement<TRequestMetaData>, HTTPStatementInformat
 33   {
 34      /// <summary>
 35      /// Gets the HTTP protocol version used by this <see cref="HTTPConnection{TRequestMetaData}"/>.
 36      /// </summary>
 37      String ProtocolVersion { get; }
 38   }
 39
 40   /// <summary>
 41   /// This interface extends <see cref="ConnectionVendorFunctionality{TStatement, TStatementCreationArgs}"/> to provide
 42   /// Currently, there are none, but there might be some in the future.
 43   /// </summary>
 44   /// <typeparam name="TRequestMetaData">The type of metadata associated with each request, used in identifying the req
 45   public interface HTTPConnectionVendorFunctionality<TRequestMetaData> : ConnectionVendorFunctionality<HTTPStatement<TR
 46   {
 47
 48   }
 49
 50   /// <summary>
 51   /// This is information associated with a single response.
 52   /// </summary>
 53   /// <typeparam name="TRequestMetaData">The type of the metadata of the request that this response is associated with.
 54   public struct HTTPResponseInfo<TRequestMetaData>
 55   {
 56      /// <summary>
 57      /// Creates a new instance of <see cref="HTTPResponseInfo{TRequestMetaData}"/> with given parameters.
 58      /// </summary>
 59      /// <param name="response">The <see cref="HTTPResponse"/>.</param>
 60      /// <param name="metadata">The metadata of the request that <paramref name="response"/> is associated with.</param
 61      /// <exception cref="ArgumentNullException">If <paramref name="response"/> is <c>null</c>.</exception>
 62      public HTTPResponseInfo(
 63         HTTPResponse response,
 64         TRequestMetaData metadata
 65         )
 66      {
 67         this.Response = ArgumentValidator.ValidateNotNull( nameof( response ), response );
 68         this.RequestMetaData = metadata;
 69      }
 70
 71      /// <summary>
 72      /// Gets the <see cref="HTTPResponse"/> of this <see cref="HTTPResponseInfo{TRequestMetaData}"/>.
 73      /// </summary>
 74      /// <value>The <see cref="HTTPResponse"/> of this <see cref="HTTPResponseInfo{TRequestMetaData}"/>.</value>
 75      public HTTPResponse Response { get; }
 76
 77      /// <summary>
 78      /// Gets the metadata of the request that <see cref="Response"/> is associated with.
 79      /// </summary>
 80      /// <value>The metadata of the request that <see cref="Response"/> is associated with.</value>
 81      public TRequestMetaData RequestMetaData { get; }
 82   }
 83
 84   /// <summary>
 85   /// This struct binds together the <see cref="HTTPRequest"/> and metadata.
 86   /// </summary>
 87   /// <typeparam name="TRequestMetaData">The type of metadata of the request. Typically this is <see cref="Guid"/> or <
 88   public struct HTTPRequestInfo<TRequestMetaData>
 89   {
 90      /// <summary>
 91      /// Creates a new instance of <see cref="HTTPRequestInfo{TRequestMetaData}"/> with given parameters.
 92      /// </summary>
 93      /// <param name="request">The <see cref="HTTPRequest"/>. May be <c>null</c>.</param>
 94      /// <param name="metadata">The metadata of the <paramref name="request"/>.</param>
 95      public HTTPRequestInfo(
 96         HTTPRequest request,
 97         TRequestMetaData metadata
 98         )
 99      {
 100         this.Request = request;
 101         this.RequestMetaData = metadata;
 102      }
 103
 104      /// <summary>
 105      /// Gets the <see cref="HTTPRequest"/> of this <see cref="HTTPRequestInfo{TRequestMetaData}"/>.
 106      /// </summary>
 107      /// <value></value>
 108      public HTTPRequest Request { get; }
 109
 110      /// <summary>
 111      /// Gets the metadata of the <see cref="Request"/>.
 112      /// </summary>
 113      /// <value>The metadata of the <see cref="Request"/>.</value>
 114      public TRequestMetaData RequestMetaData { get; }
 115   }
 116
 117   /// <summary>
 118   /// This class is meant to be used in simple situations, when the textual content of the HTTP response is meant to be
 119   /// </summary>
 120   /// <seealso cref="E_CBAM.CreateTextualResponseInfoAsync"/>
 121   public sealed class HTTPTextualResponseInfo
 122   {
 123      private static readonly Encoding DefaultTextEncoding = new UTF8Encoding( false, false );
 124
 125      /// <summary>
 126      /// Creates a new instance of <see cref="HTTPTextualResponseInfo"/> with given parameters.
 127      /// </summary>
 128      /// <param name="response">The <see cref="HTTPResponse"/>.</param>
 129      /// <param name="content">The content of the <see cref="HTTPResponse"/>, as byte array.</param>
 130      /// <param name="defaultEncoding">The default <see cref="Encoding"/> to use if no encoding can be deduced from <se
 131      /// <exception cref="ArgumentNullException">If <paramref name="response"/> is <c>null</c>.</exception>
 132      public HTTPTextualResponseInfo(
 133         HTTPResponse response,
 134         Byte[] content,
 135         Encoding defaultEncoding
 136         )
 137      {
 138         ArgumentValidator.ValidateNotNull( nameof( response ), response );
 139
 140         this.Version = response.Version;
 141         this.StatusCode = response.StatusCode;
 142         this.Message = response.StatusCodeMessage;
 143         this.Headers = response.Headers;
 144         String textualContent;
 145         if ( !content.IsNullOrEmpty() )
 146         {
 147            String cType;
 148            Int32 charsetIndex;
 149            Int32 charsetEndIdx;
 150            var encoding = response.Headers.TryGetValue( "Content-Type", out var cTypes ) && cTypes.Count > 0 && ( chars
 151               Encoding.GetEncoding( cType.Substring( charsetIndex + 8, ( ( charsetEndIdx = cType.IndexOf( ';', charsetI
 152               ( defaultEncoding ?? DefaultTextEncoding );
 153            textualContent = encoding.GetString( content, 0, content.Length );
 154         }
 155         else
 156         {
 157            textualContent = String.Empty;
 158         }
 159         this.TextualContent = textualContent;
 160      }
 161
 162      /// <summary>
 163      /// Gets the <see cref="HTTPMessage{TContent, TDictionary, TList}.Version"/> of the <see cref="HTTPResponse"/> thi
 164      /// </summary>
 165      /// <value>The <see cref="HTTPMessage{TContent, TDictionary, TList}.Version"/> of the <see cref="HTTPResponse"/> t
 166      public String Version { get; }
 167
 168      /// <summary>
 169      /// Gets the <see cref="HTTPResponse.StatusCode"/> of the <see cref="HTTPResponse"/> this <see cref="HTTPTextualRe
 170      /// </summary>
 171      /// <value>The <see cref="HTTPResponse.StatusCode"/> of the <see cref="HTTPResponse"/> this <see cref="HTTPTextual
 172      public Int32 StatusCode { get; }
 173
 174      /// <summary>
 175      /// Gets the <see cref="HTTPResponse.StatusCodeMessage"/> of the <see cref="HTTPResponse"/> this <see cref="HTTPTe
 176      /// </summary>
 177      /// <value>The <see cref="HTTPResponse.StatusCodeMessage"/> of the <see cref="HTTPResponse"/> this <see cref="HTTP
 178      public String Message { get; }
 179
 180      /// <summary>
 181      /// Gets the <see cref="HTTPMessage{TContent, TDictionary, TList}.Headers"/> of the <see cref="HTTPResponse"/> thi
 182      /// </summary>
 183      /// <value>the <see cref="HTTPMessage{TContent, TDictionary, TList}.Headers"/> of the <see cref="HTTPResponse"/> t
 184      public IReadOnlyDictionary<String, IReadOnlyList<String>> Headers { get; }
 185
 186      /// <summary>
 187      /// Gets the deserialized <see cref="HTTPResponseContent"/> as <see cref="String"/>.
 188      /// </summary>
 189      /// <value>The deserialized <see cref="HTTPResponseContent"/> as <see cref="String"/>.</value>
 190      public String TextualContent { get; }
 191
 192
 193   }
 194
 195}
 196
 197/// <summary>
 198/// This class contains extension methods for types defined in this assembly.
 199/// </summary>
 200public static partial class E_CBAM
 201{
 202   /// <summary>
 203   /// This method will asynchronously receive one <see cref="HTTPTextualResponseInfo"/> from this <see cref="HTTPConnec
 204   /// </summary>
 205   /// <typeparam name="TRequestMetaData">The type of metadata associated with given <see cref="HTTPRequest"/>. Typicall
 206   /// <param name="connection">This <see cref="HTTPConnection{TRequestMetaData}"/>.</param>
 207   /// <param name="request">The <see cref="HTTPRequest"/> to send.</param>
 208   /// <param name="metaData">The metadata of the <paramref name="request"/>.</param>
 209   /// <param name="defaultEncoding">The default encoding to use when deserializing response contents to string. By defa
 210   /// <returns>Potentially asynchronously creates constructed <see cref="HTTPTextualResponseInfo"/>.</returns>
 211   /// <exception cref="NullReferenceException">If this <see cref="HTTPConnection{TRequestMetaData}"/> is <c>null</c>.</
 212   public static Task<HTTPTextualResponseInfo> ReceiveOneTextualResponseAsync<TRequestMetaData>(
 213      this HTTPConnection<TRequestMetaData> connection,
 214      HTTPRequest request,
 215      TRequestMetaData metaData = default,
 216      Encoding defaultEncoding = default
 217      )
 218   {
 219
 2220      return connection
 2221         .PrepareStatementForExecution( new HTTPRequestInfo<TRequestMetaData>( request, metaData ) )
 4222         .Select( async responseInfo => await responseInfo.Response.CreateTextualResponseInfoAsync( defaultEncoding ) )
 2223         .FirstAsync();
 224   }
 225
 226   /// <summary>
 227   /// Asynchronously creates <see cref="HTTPTextualResponseInfo"/> from this <see cref="HTTPResponse"/>.
 228   /// </summary>
 229   /// <param name="response">This <see cref="HTTPResponse"/>.</param>
 230   /// <param name="defaultEncoding">The <see cref="Encoding"/> to use if <see cref="HTTPResponse"/> does not contain an
 231   /// <returns>Potentially asynchronously creates constructed <see cref="HTTPTextualResponseInfo"/>.</returns>
 232   /// <exception cref="NullReferenceException">If this <see cref="HTTPResponse"/> is <c>null</c>.</exception>
 233   public static async ValueTask<HTTPTextualResponseInfo> CreateTextualResponseInfoAsync(
 234      this HTTPResponse response,
 235      Encoding defaultEncoding = default
 236      )
 237   {
 2238      var content = response.Content;
 239      Byte[] bytes;
 2240      if ( content != null )
 241      {
 2242         bytes = await content.ReadAllContentAsync();
 2243      }
 244      else
 245      {
 0246         bytes = null;
 247      }
 248
 2249      return new HTTPTextualResponseInfo( response, bytes, defaultEncoding );
 2250   }
 251}

/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
 351         ) => 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
 364         ) => 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
 379         ) => 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      {
 396         HTTPRequest retVal = new HTTPRequestImpl()
 397         {
 398            Method = method,
 399            Path = path
 400         };
 401
 402         retVal.Version = String.IsNullOrEmpty( version ) ? VERSION_HTTP1_1 : version;
 403         retVal.Content = content;
 404         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
 416         ) => 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      {
 436         return new HTTPResponseImpl(
 437            statusCode,
 438            statusMessage,
 439            version,
 440            headers,
 441            content
 442            );
 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      {
 451         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      {
 471         return byteCount <= 0 ? (HTTPResponseContent) EmptyHTTPResponseContent.Instance : new HTTPResponseContentFromSt
 472            stream,
 473            buffer,
 474            bufferAdvanceState,
 475            byteCount,
 476            token
 477            );
 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      {
 497         return new HTTPResponseContentFromStream_Chunked(
 498               stream,
 499               buffer,
 500               bufferAdvanceState,
 501               await HTTPResponseContentFromStream_Chunked.ReadNextChunk( stream, buffer, bufferAdvanceState, singleStre
 502               singleStreamReadCount,
 503               token
 504               );
 505      }
 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   {
 0578      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   {
 2591      message.Headers
 4592         .GetOrAdd_NotThreadSafe( headerName, hn => new
 4593#if NET40
 4594         ListWithReadOnlyAPI
 4595#else
 4596         List
 4597#endif
 4598         <String>() )
 2599         .Add( headerValue );
 600
 2601      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
 2619      var length = content.ByteCount;
 2620      return length.HasValue ?
 2621         content.ReadAllContentIfKnownSizeAsync( content.BytesRemaining.Value ) :
 2622         content.ReadAllContentGrowBuffer();
 623   }
 624
 625   private static async ValueTask<Byte[]> ReadAllContentIfKnownSizeAsync( this HTTPResponseContent content, Int64 length
 626   {
 2627      if ( length64 > Int32.MaxValue )
 628      {
 0629         throw new InvalidOperationException( "The content length is too big: " + length64 );
 630      }
 2631      else if ( length64 < 0 )
 632      {
 0633         throw new InvalidOperationException( "The content length is negative: " + length64 );
 634      }
 635
 2636      var length = (Int32) length64;
 637      Byte[] retVal;
 2638      if ( length > 0 )
 639      {
 2640         retVal = new Byte[length];
 2641         var offset = 0;
 642         do
 643         {
 2644            var readCount = await content.ReadToBuffer( retVal, offset, length - offset );
 2645            offset += readCount;
 2646            if ( offset < length && readCount <= 0 )
 647            {
 0648               throw new EndOfStreamException();
 649            }
 2650         } while ( offset < length );
 2651      }
 652      else
 653      {
 0654         retVal = Empty<Byte>.Array;
 655      }
 656
 2657      return retVal;
 2658   }
 659
 660   private static async ValueTask<Byte[]> ReadAllContentGrowBuffer( this HTTPResponseContent content )
 661   {
 0662      var buffer = new ResizableArray<Byte>( exponentialResize: false );
 0663      var bytesRead = 0;
 664      Int32 bytesRemaining;
 0665      Int32 bytesSeen = 0;
 0666      while ( ( bytesRemaining = (Int32) content.BytesRemaining.Value ) > 0 )
 667      {
 0668         bytesSeen += bytesRemaining;
 0669         bytesRead += await content.ReadToBuffer( buffer.SetCapacityAndReturnArray( bytesRead + bytesRemaining ), bytesR
 670      }
 671      // Since exponentialResize was set to false, the array will never be too big
 0672      return buffer.Array;
 0673   }
 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   {
 0697      ArgumentValidator.ValidateNotNull( nameof( encoding ), encoding );
 0698      ArgumentValidator.ValidateNotNull( nameof( str ), str );
 699
 0700      var buffer = writer.Buffer;
 0701      var bufferLen = buffer.Length;
 702
 0703      var byteCount = strByteCount ?? encoding.GetByteCount( str );
 704
 705      ValueTask<Int64> retVal;
 0706      if ( bufferLen >= byteCount )
 707      {
 708         // Can just write it directly
 0709         retVal = writer.FlushBufferContents( bufferIndex + encoding.GetBytes( str, 0, str.Length, buffer, bufferIndex )
 0710      }
 711      else
 712      {
 0713         retVal = MultiPartWriteToStreamAsync( writer, encoding, str, byteCount, bufferIndex );
 714      }
 715
 0716      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   {
 0727      var buffer = writer.Buffer;
 0728      var bufferLen = buffer.Length;
 729
 730      // Make sure there is always room for max size (4) char
 0731      if ( bufferLen - bufferIndex <= 4 )
 732      {
 0733         throw new InvalidOperationException( "Too small buffer" );
 734      }
 735
 0736      bufferLen -= 4;
 737
 0738      var cur = 0;
 0739      var textLen = text.Length;
 740      do
 741      {
 0742         while ( cur < textLen && bufferIndex < bufferLen )
 743         {
 744            Int32 count;
 0745            if ( Char.IsLowSurrogate( text[cur] ) && cur < textLen - 1 && Char.IsHighSurrogate( text[cur + 1] ) )
 746            {
 0747               count = 2;
 0748            }
 749            else
 750            {
 0751               count = 1;
 752            }
 0753            bufferIndex += encoding.GetBytes( text, cur, count, buffer, bufferIndex );
 0754            cur += count;
 755         }
 0756         await writer.FlushBufferContents( bufferIndex );
 0757         bufferIndex = 0;
 0758      } while ( cur < textLen );
 759
 0760      return seenByteCount;
 0761   }
 762}