Esempio n. 1
0
        private async Task <HttpResponse> ReadResponseAsync(Stream responseStream)
        {
            var  headers        = new HttpHeaders();
            char currentChar    = char.MinValue;
            int  streamPosition = 0;

            // https://tools.ietf.org/html/rfc2616#section-4.2
            // HTTP/1.1 200 OK
            // HTTP/1.1 XXX MESSAGE

            const int statusCodeStart     = 9;
            const int statusCodeEnd       = 12;
            const int startOfReasonPhrase = 13;
            const int bufferSize          = 10;

            // TODO: Get this from StringBuilderCache after we determine safe maximum capacity
            var stringBuilder = new StringBuilder();

            var chArray = new byte[bufferSize];

            async Task GoNextChar()
            {
                var bytesRead = await responseStream.ReadAsync(chArray, offset : 0, count : 1).ConfigureAwait(false);

                if (bytesRead == 0)
                {
                    ThrowHelper.ThrowInvalidOperationException($"Unexpected end of stream at position {streamPosition}");
                }

                currentChar = Encoding.ASCII.GetChars(chArray)[0];
                streamPosition++;
            }

            async Task SkipUntil(int requiredStreamPosition)
            {
                var requiredBytes = requiredStreamPosition - streamPosition;

                if (requiredBytes < 0)
                {
                    ThrowHelper.ThrowArgumentOutOfRangeException("StreamPosition already exceeds requiredStreamPosition", nameof(requiredStreamPosition));
                }

                var bytesRemaining = requiredBytes;
                var lastBytesRead  = 0;

                while (bytesRemaining > 0)
                {
                    var bytesToRead = Math.Min(bytesRemaining, bufferSize);
                    lastBytesRead = await responseStream.ReadAsync(chArray, offset : 0, count : bytesToRead).ConfigureAwait(false);

                    if (lastBytesRead == 0)
                    {
                        ThrowHelper.ThrowInvalidOperationException($"Unexpected end of stream at position {streamPosition}");
                    }

                    bytesRemaining -= lastBytesRead;
                }

                currentChar     = Encoding.ASCII.GetChars(chArray)[lastBytesRead - 1];
                streamPosition += requiredBytes;
            }

            async Task ReadUntil(StringBuilder builder, char stopChar)
            {
                while (!currentChar.Equals(stopChar))
                {
                    builder.Append(currentChar);
                    await GoNextChar().ConfigureAwait(false);
                }
            }

            async Task ReadUntilNewLine(StringBuilder builder)
            {
                do
                {
                    if (await IsNewLine().ConfigureAwait(false))
                    {
                        break;
                    }

                    await ReadUntil(builder, DatadogHttpValues.CarriageReturn).ConfigureAwait(false);
                }while (true);
            }

            async Task <bool> IsNewLine()
            {
                if (currentChar.Equals(DatadogHttpValues.CarriageReturn))
                {
                    // end of headers
                    // Next character should be a LineFeed, regardless of Linux/Windows
                    // Skip the newline indicator
                    await GoNextChar().ConfigureAwait(false);

                    if (!currentChar.Equals(DatadogHttpValues.LineFeed))
                    {
                        ThrowHelper.ThrowException($"Unexpected character {currentChar} in headers: CR must be followed by LF");
                    }

                    return(true);
                }

                return(false);
            }

            // Skip to status code
            await SkipUntil(statusCodeStart).ConfigureAwait(false);

            // Read status code
            while (streamPosition < statusCodeEnd)
            {
                await GoNextChar().ConfigureAwait(false);

                stringBuilder.Append(currentChar);
            }

            var potentialStatusCode = stringBuilder.ToString();

            stringBuilder.Clear();

            if (!int.TryParse(potentialStatusCode, out var statusCode))
            {
                DatadogHttpRequestException.Throw("Invalid response, can't parse status code. Line was:" + potentialStatusCode);
            }

            // Skip to reason
            await SkipUntil(startOfReasonPhrase).ConfigureAwait(false);

            // Read reason
            await GoNextChar().ConfigureAwait(false);
            await ReadUntilNewLine(stringBuilder).ConfigureAwait(false);

            var reasonPhrase = stringBuilder.ToString();

            stringBuilder.Clear();

            // Read headers
            do
            {
                await GoNextChar().ConfigureAwait(false);

                // Check for end of headers
                if (await IsNewLine().ConfigureAwait(false))
                {
                    // Empty line, content starts next
                    break;
                }

                // Read key
                await ReadUntil(stringBuilder, stopChar : ':').ConfigureAwait(false);

                var name = stringBuilder.ToString().Trim();
                stringBuilder.Clear();

                // skip separator
                await GoNextChar().ConfigureAwait(false);

                // Read value
                await ReadUntilNewLine(stringBuilder).ConfigureAwait(false);

                var value = stringBuilder.ToString().Trim();
                stringBuilder.Clear();

                headers.Add(name, value);
            }while (true);

            var length = long.TryParse(headers.GetValue(ContentLengthHeaderKey), out var headerValue) ? headerValue : (long?)null;

            return(new HttpResponse(statusCode, reasonPhrase, headers, new StreamContent(responseStream, length)));
        }
Esempio n. 2
0
        private HttpResponse ReadResponse(Stream responseStream)
        {
            var  headers = new HttpHeaders();
            char currentChar;
            int  streamPosition = 0;

            // https://tools.ietf.org/html/rfc2616#section-4.2
            // HTTP/1.1 200 OK
            // HTTP/1.1 XXX MESSAGE

            const int statusCodeStart     = 9;
            const int statusCodeEnd       = 12;
            const int startOfReasonPhrase = 13;

            // TODO: Get this from StringBuilderCache after we determine safe maximum capacity
            var stringBuilder = new StringBuilder();

            var chArray = new byte[1];

            void GoNextChar()
            {
                streamPosition++;
                chArray[0]  = (byte)responseStream.ReadByte();
                currentChar = Encoding.ASCII.GetChars(chArray)[0];
            }

            bool IsNewLine()
            {
                if (currentChar.Equals(DatadogHttpValues.CarriageReturn))
                {
                    // end of headers
                    if (DatadogHttpValues.CrLfLength > 1)
                    {
                        // Skip the newline indicator
                        GoNextChar();
                    }

                    return(true);
                }

                return(false);
            }

            // Skip to status code
            while (streamPosition < statusCodeStart)
            {
                GoNextChar();
            }

            // Read status code
            while (streamPosition < statusCodeEnd)
            {
                GoNextChar();
                stringBuilder.Append(currentChar);
            }

            var potentialStatusCode = stringBuilder.ToString();

            stringBuilder.Clear();

            if (!int.TryParse(potentialStatusCode, out var statusCode))
            {
                throw new DatadogHttpRequestException("Invalid response, can't parse status code. Line was:" + potentialStatusCode);
            }

            // Skip to reason
            while (streamPosition < startOfReasonPhrase)
            {
                GoNextChar();
            }

            // Read reason
            do
            {
                GoNextChar();
                if (IsNewLine())
                {
                    break;
                }

                stringBuilder.Append(currentChar);
            }while (true);

            var reasonPhrase = stringBuilder.ToString();

            stringBuilder.Clear();

            // Read headers
            do
            {
                GoNextChar();

                // Check for end of headers
                if (IsNewLine())
                {
                    // Empty line, content starts next
                    break;
                }

                // Read key
                do
                {
                    if (currentChar.Equals(':'))
                    {
                        // Value portion starts
                        break;
                    }

                    stringBuilder.Append(currentChar);
                    GoNextChar();
                }while (true);

                var name = stringBuilder.ToString().Trim();
                stringBuilder.Clear();

                // Read value
                do
                {
                    GoNextChar();

                    if (IsNewLine())
                    {
                        // Next header pair starts
                        break;
                    }

                    stringBuilder.Append(currentChar);
                }while (true);

                var value = stringBuilder.ToString().Trim();
                stringBuilder.Clear();

                headers.Add(name, value);
            }while (true);

            var length = long.TryParse(headers.GetValue(ContentLengthHeaderKey), out var headerValue) ? headerValue : (long?)null;

            return(new HttpResponse(statusCode, reasonPhrase, headers, new StreamContent(responseStream, length)));
        }