private async Task <HttpActionStatus> ReadResponseBodyAsync(Request request, WebRequestState state, CancellationToken cancellationToken)
        {
            try
            {
                var contentLength = (int)state.Response.ContentLength;
                if (contentLength > 0)
                {
                    state.BodyBuffer = Settings.BufferFactory(contentLength);

                    var totalBytesRead = 0;

                    // If a contentLength-sized buffer won't end up in LOH, it can be used directly to work with socket.
                    // Otherwise, it's better to use a small temporary buffer from a pool because the passed reference will be stored for a long time due to Keep-Alive.
                    if (contentLength < Constants.LOHObjectSizeThreshold)
                    {
                        while (totalBytesRead < contentLength)
                        {
                            var bytesToRead = Math.Min(contentLength - totalBytesRead, Constants.BufferSize);
                            var bytesRead   = await state.ResponseStream.ReadAsync(state.BodyBuffer, totalBytesRead, bytesToRead, cancellationToken)
                                              .ConfigureAwait(false);

                            if (bytesRead == 0)
                            {
                                break;
                            }

                            totalBytesRead += bytesRead;
                        }
                    }
                    else
                    {
                        using (BufferPool.Default.Rent(Constants.BufferSize, out var buffer))
                        {
                            while (totalBytesRead < contentLength)
                            {
                                var bytesToRead = Math.Min(contentLength - totalBytesRead, buffer.Length);
                                var bytesRead   = await state.ResponseStream.ReadAsync(buffer, 0, bytesToRead).ConfigureAwait(false);

                                if (bytesRead == 0)
                                {
                                    break;
                                }

                                Buffer.BlockCopy(buffer, 0, state.BodyBuffer, totalBytesRead, bytesRead);

                                totalBytesRead += bytesRead;
                            }
                        }
                    }

                    if (totalBytesRead < contentLength)
                    {
                        throw new EndOfStreamException($"Response stream ended prematurely. Read only {totalBytesRead} byte(s), but Content-Length specified {contentLength}.");
                    }

                    state.BodyBufferLength = totalBytesRead;
                }
                else
                {
                    state.BodyStream = new MemoryStream();

                    using (BufferPool.Default.Rent(Constants.BufferSize, out var buffer))
                    {
                        while (true)
                        {
                            var bytesRead = await state.ResponseStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);

                            if (bytesRead == 0)
                            {
                                break;
                            }

                            state.BodyStream.Write(buffer, 0, bytesRead);

                            if (ResponseBodyIsTooLarge(state))
                            {
                                state.CancelRequestAttempt();
                                state.BodyStream = null;
                                return(HttpActionStatus.InsufficientStorage);
                            }
                        }
                    }
                }

                return(HttpActionStatus.Success);
            }
            catch (Exception error)
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    return(HttpActionStatus.RequestCanceled);
                }

                LogReceiveBodyFailure(request, error);
                return(HttpActionStatus.ReceiveFailure);
            }
        }
        private async Task <Response> SendInternalAsync(Request request, WebRequestState state, TimeSpan?connectionTimeout, CancellationToken cancellationToken)
        {
            CancellationTokenRegistration registration;

            try
            {
                registration = cancellationToken.Register(state.CancelRequest);
            }
            catch (ObjectDisposedException)
            {
                return(Responses.Canceled);
            }

            using (registration)
                using (state)
                {
                    if (state.RequestCanceled)
                    {
                        return(Responses.Canceled);
                    }

                    state.Request = WebRequestFactory.Create(request, state.TimeRemaining, Settings, log);

                    HttpActionStatus status;

                    // Step 1: send request body if it was supplied in request.
                    if (state.RequestCanceled)
                    {
                        return(Responses.Canceled);
                    }

                    if (request.HasBody)
                    {
                        status = await connectTimeLimiter
                                 .LimitConnectTime(SendRequestBodyAsync(request, state, cancellationToken), request, state, connectionTimeout)
                                 .ConfigureAwait(false);

                        if (status != HttpActionStatus.Success)
                        {
                            return(responseFactory.BuildFailureResponse(status, state));
                        }
                    }

                    // Step 2: receive response from server.
                    if (state.RequestCanceled)
                    {
                        return(Responses.Canceled);
                    }

                    status = request.HasBody
                    ? await GetResponseAsync(request, state).ConfigureAwait(false)
                    : await connectTimeLimiter.LimitConnectTime(GetResponseAsync(request, state), request, state, connectionTimeout).ConfigureAwait(false);

                    if (status != HttpActionStatus.Success)
                    {
                        return(responseFactory.BuildFailureResponse(status, state));
                    }

                    // Step 3 - download request body if it exists.
                    if (!NeedToReadResponseBody(request, state))
                    {
                        return(responseFactory.BuildSuccessResponse(state));
                    }

                    if (ResponseBodyIsTooLarge(state))
                    {
                        state.CancelRequestAttempt();
                        return(responseFactory.BuildResponse(ResponseCode.InsufficientStorage, state));
                    }

                    if (state.RequestCanceled)
                    {
                        return(Responses.Canceled);
                    }

                    if (NeedToStreamResponseBody(state))
                    {
                        state.ReturnStreamDirectly = true;
                        state.PreventNextDispose();
                        return(responseFactory.BuildSuccessResponse(state));
                    }

                    status = await ReadResponseBodyAsync(request, state, cancellationToken)
                             .ConfigureAwait(false);

                    return(status == HttpActionStatus.Success
                    ? responseFactory.BuildSuccessResponse(state)
                    : responseFactory.BuildFailureResponse(status, state));
                }
        }