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);
        }
예제 #3
0
        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);
        }
예제 #4
0
        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()));
        }