public static async Task <string> ToHttpStringAsync(this HttpResponseMessage me) { var startLine = new StatusLine(new HttpProtocol($"HTTP/{me.Version.Major}.{me.Version.Minor}"), me.StatusCode).ToString(); string headers = ""; if (me.Headers != null && me.Headers.Count() != 0) { var headerSection = HeaderSection.CreateNew(me.Headers); headers += headerSection.ToString(endWithTwoCRLF: false); } string messageBody = ""; if (me.Content != null) { if (me.Content.Headers != null && me.Content.Headers.Count() != 0) { var headerSection = HeaderSection.CreateNew(me.Content.Headers); headers += headerSection.ToString(endWithTwoCRLF: false); } messageBody = await me.Content.ReadAsStringAsync().ConfigureAwait(false); } return(startLine + headers + CRLF + messageBody); }
public static async Task <string> ToHttpStringAsync(this HttpResponseMessage me) { var startLine = new StatusLine(new HttpProtocol($"HTTP/{me.Version.Major}.{me.Version.Minor}"), me.StatusCode).ToString(); var headers = ""; if (me.Headers.NotNullAndNotEmpty()) { var headerSection = HeaderSection.CreateNew(me.Headers); headers += headerSection.ToString(endWithTwoCRLF: false); } var messageBody = ""; if (!(me.Content is null)) { if (me.Content.Headers.NotNullAndNotEmpty()) { var headerSection = HeaderSection.CreateNew(me.Content.Headers); headers += headerSection.ToString(endWithTwoCRLF: false); } messageBody = await me.Content.ReadAsStringAsync(); } return(startLine + headers + CRLF + messageBody); }
public static async Task <string> ToHttpStringAsync(this HttpRequestMessage me, CancellationToken ctsToken = default(CancellationToken)) { // https://tools.ietf.org/html/rfc7230#section-5.4 // The "Host" header field in a request provides the host and port // information from the target URI, enabling the origin server to // distinguish among resources while servicing requests for multiple // host names on a single IP address. // Host = uri - host[":" port] ; Section 2.7.1 // A client MUST send a Host header field in all HTTP/1.1 request messages. if (me.Method != new HttpMethod("CONNECT")) { if (!me.Headers.Contains("Host")) { // https://tools.ietf.org/html/rfc7230#section-5.4 // If the target URI includes an authority component, then a // client MUST send a field-value for Host that is identical to that // authority component, excluding any userinfo subcomponent and its "@" // delimiter(Section 2.7.1).If the authority component is missing or // undefined for the target URI, then a client MUST send a Host header // field with an empty field - value. me.Headers.TryAddWithoutValidation("Host", me.RequestUri.Authority); } } var startLine = new RequestLine(me.Method, me.RequestUri, new HttpProtocol($"HTTP/{me.Version.Major}.{me.Version.Minor}")).ToString(); string headers = ""; if (me.Headers != null && me.Headers.Count() != 0) { var headerSection = HeaderSection.CreateNew(me.Headers); headers += headerSection.ToString(endWithTwoCRLF: false); } string messageBody = ""; if (me.Content != null) { if (me.Content.Headers != null && me.Content.Headers.Count() != 0) { var headerSection = HeaderSection.CreateNew(me.Content.Headers); headers += headerSection.ToString(endWithTwoCRLF: false); } ctsToken.ThrowIfCancellationRequested(); messageBody = await me.Content.ReadAsStringAsync().ConfigureAwait(false); } return(startLine + headers + CRLF + messageBody); }
public static async Task <HttpRequestMessage> CreateNewAsync(this HttpRequestMessage me, Stream requestStream, CancellationToken ctsToken = default) { // https://tools.ietf.org/html/rfc7230#section-3 // The normal procedure for parsing an HTTP message is to read the // start - line into a structure, read each header field into a hash table // by field name until the empty line, and then use the parsed data to // determine if a message body is expected.If a message body has been // indicated, then it is read as a stream until an amount of octets // equal to the message body length is read or the connection is closed. // https://tools.ietf.org/html/rfc7230#section-3 // All HTTP/ 1.1 messages consist of a start - line followed by a sequence // of octets in a format similar to the Internet Message Format // [RFC5322]: zero or more header fields(collectively referred to as // the "headers" or the "header section"), an empty line indicating the // end of the header section, and an optional message body. // HTTP - message = start - line // * (header - field CRLF ) // CRLF // [message - body] var position = 0; string startLine = await HttpMessageHelper.ReadStartLineAsync(requestStream, ctsToken).ConfigureAwait(false); position += startLine.Length; var requestLine = RequestLine.CreateNew(startLine); var request = new HttpRequestMessage(requestLine.Method, requestLine.URI); string headers = await HttpMessageHelper.ReadHeadersAsync(requestStream, ctsToken).ConfigureAwait(false); position += headers.Length + 2; var headerSection = HeaderSection.CreateNew(headers); var headerStruct = headerSection.ToHttpRequestHeaders(); HttpMessageHelper.AssertValidHeaders(headerStruct.RequestHeaders, headerStruct.ContentHeaders); request.Content = await HttpMessageHelper.GetContentAsync(requestStream, headerStruct, ctsToken).ConfigureAwait(false); HttpMessageHelper.CopyHeaders(headerStruct.RequestHeaders, request.Headers); if (request.Content != null) { HttpMessageHelper.CopyHeaders(headerStruct.ContentHeaders, request.Content.Headers); } return(request); }
public static async Task <HttpResponseMessage> CreateNewAsync(Stream responseStream, HttpMethod requestMethod) { // https://tools.ietf.org/html/rfc7230#section-3 // The normal procedure for parsing an HTTP message is to read the // start - line into a structure, read each header field into a hash table // by field name until the empty line, and then use the parsed data to // determine if a message body is expected.If a message body has been // indicated, then it is read as a stream until an amount of octets // equal to the message body length is read or the connection is closed. // https://tools.ietf.org/html/rfc7230#section-3 // All HTTP/ 1.1 messages consist of a start - line followed by a sequence // of octets in a format similar to the Internet Message Format // [RFC5322]: zero or more header fields(collectively referred to as // the "headers" or the "header section"), an empty line indicating the // end of the header section, and an optional message body. // HTTP - message = start - line // * (header - field CRLF ) // CRLF // [message - body] string startLine = await HttpMessageHelper.ReadStartLineAsync(responseStream); var statusLine = StatusLine.CreateNew(startLine); var response = new HttpResponseMessage(statusLine.StatusCode); string headers = await HttpMessageHelper.ReadHeadersAsync(responseStream); var headerSection = HeaderSection.CreateNew(headers); var headerStruct = headerSection.ToHttpResponseHeaders(); HttpMessageHelper.AssertValidHeaders(headerStruct.ResponseHeaders, headerStruct.ContentHeaders); byte[] contentBytes = await HttpMessageHelper.GetContentBytesAsync(responseStream, headerStruct, requestMethod, statusLine); contentBytes = HttpMessageHelper.HandleGzipCompression(headerStruct.ContentHeaders, contentBytes); response.Content = contentBytes is null ? null : new ByteArrayContent(contentBytes); HttpMessageHelper.CopyHeaders(headerStruct.ResponseHeaders, response.Headers); if (response.Content != null) { HttpMessageHelper.CopyHeaders(headerStruct.ContentHeaders, response.Content.Headers); } return(response); }
private static async Task <HttpContent> GetDecodedChunkedContentAsync(Stream stream, HttpRequestContentHeaders requestHeaders, HttpResponseContentHeaders responseHeaders, CancellationToken ctsToken = default) { if (responseHeaders == null && requestHeaders == null) { throw new ArgumentException("Response and request headers cannot be both null."); } if (responseHeaders != null && requestHeaders != null) { throw new ArgumentException("Either response or request headers has to be null."); } // https://tools.ietf.org/html/rfc7230#section-4.1.3 // 4.1.3.Decoding Chunked // A process for decoding the chunked transfer coding can be represented // in pseudo - code as: // length := 0 // read chunk - size, chunk - ext(if any), and CRLF // while (chunk - size > 0) // { // read chunk-data and CRLF // append chunk-data to decoded-body // length:= length + chunk - size // read chunk-size, chunk - ext(if any), and CRLF // } // read trailer field // while (trailer field is not empty) { // if (trailer field is allowed to be sent in a trailer) { // append trailer field to existing header fields // } // read trailer-field // } // Content - Length := length // Remove "chunked" from Transfer-Encoding // Remove Trailer from existing header fields long length = 0; var firstChunkLine = await ReadCRLFLineAsync(stream, Encoding.ASCII, ctsToken : ctsToken); ParseFistChunkLine(firstChunkLine, out long chunkSize, out IEnumerable <string> chunkExtensions); // We will not do anything with the chunk extensions, because: // https://tools.ietf.org/html/rfc7230#section-4.1.1 // A recipient MUST ignore unrecognized chunk extensions. var decodedBody = new List <byte>(); // https://tools.ietf.org/html/rfc7230#section-4.1 // The chunked transfer coding is complete // when a chunk with a chunk-size of zero is received, possibly followed // by a trailer, and finally terminated by an empty line. while (chunkSize > 0) { var chunkData = await ReadBytesTillLengthAsync(stream, chunkSize, ctsToken); if (await ReadCRLFLineAsync(stream, Encoding.ASCII, ctsToken) != "") { throw new FormatException("Chunk does not end with CRLF."); } foreach (var b in chunkData) { decodedBody.Add(b); } length += chunkSize; firstChunkLine = await ReadCRLFLineAsync(stream, Encoding.ASCII, ctsToken : ctsToken); ParseFistChunkLine(firstChunkLine, out long cs, out IEnumerable <string> ces); chunkSize = cs; chunkExtensions = ces; } // https://tools.ietf.org/html/rfc7230#section-4.1.2 // A trailer allows the sender to include additional fields at the end // of a chunked message in order to supply metadata that might be // dynamically generated while the message body is sent string trailerHeaders = await ReadHeadersAsync(stream, ctsToken); var trailerHeaderSection = HeaderSection.CreateNew(trailerHeaders); RemoveInvalidTrailers(trailerHeaderSection); if (responseHeaders != null) { var trailerHeaderStruct = trailerHeaderSection.ToHttpResponseHeaders(); AssertValidHeaders(trailerHeaderStruct.ResponseHeaders, trailerHeaderStruct.ContentHeaders); // https://tools.ietf.org/html/rfc7230#section-4.1.2 // When a chunked message containing a non-empty trailer is received, // the recipient MAY process the fields(aside from those forbidden // above) as if they were appended to the message's header section. CopyHeaders(trailerHeaderStruct.ResponseHeaders, responseHeaders.ResponseHeaders); responseHeaders.ResponseHeaders.Remove("Transfer-Encoding"); responseHeaders.ContentHeaders.TryAddWithoutValidation("Content-Length", length.ToString()); responseHeaders.ResponseHeaders.Remove("Trailer"); } if (requestHeaders != null) { var trailerHeaderStruct = trailerHeaderSection.ToHttpRequestHeaders(); AssertValidHeaders(trailerHeaderStruct.RequestHeaders, trailerHeaderStruct.ContentHeaders); // https://tools.ietf.org/html/rfc7230#section-4.1.2 // When a chunked message containing a non-empty trailer is received, // the recipient MAY process the fields(aside from those forbidden // above) as if they were appended to the message's header section. CopyHeaders(trailerHeaderStruct.RequestHeaders, requestHeaders.RequestHeaders); requestHeaders.RequestHeaders.Remove("Transfer-Encoding"); requestHeaders.ContentHeaders.TryAddWithoutValidation("Content-Length", length.ToString()); requestHeaders.RequestHeaders.Remove("Trailer"); } return(new ByteArrayContent(decodedBody.ToArray())); }