private HttpActionStatus HandleWebException(Request request, HttpWebRequestState state, WebException error) { switch (error.Status) { case WebExceptionStatus.ConnectFailure: case WebExceptionStatus.KeepAliveFailure: case WebExceptionStatus.ConnectionClosed: case WebExceptionStatus.PipelineFailure: case WebExceptionStatus.NameResolutionFailure: case WebExceptionStatus.ProxyNameResolutionFailure: case WebExceptionStatus.SecureChannelFailure: LogConnectionFailure(request, error, state.ConnectionAttempt); return(HttpActionStatus.ConnectionFailure); case WebExceptionStatus.SendFailure: LogWebException(error); return(HttpActionStatus.SendFailure); case WebExceptionStatus.ReceiveFailure: LogWebException(error); return(HttpActionStatus.ReceiveFailure); case WebExceptionStatus.RequestCanceled: return(HttpActionStatus.RequestCanceled); case WebExceptionStatus.Timeout: return(HttpActionStatus.Timeout); case WebExceptionStatus.ProtocolError: return(HttpActionStatus.ProtocolError); default: LogWebException(error); return(HttpActionStatus.UnknownFailure); } }
private async Task <HttpActionStatus> SendRequestBodyAsync(Request request, HttpWebRequestState state) { try { state.RequestStream = await state.Request.GetRequestStreamAsync().ConfigureAwait(false); } catch (WebException error) { return(HandleWebException(request, state, error)); } catch (Exception error) { LogUnknownException(error); return(HttpActionStatus.UnknownFailure); } try { await state.RequestStream.WriteAsync(request.Content.Buffer, request.Content.Offset, request.Content.Length); state.CloseRequestStream(); } catch (Exception error) { if (IsCancellationException(error)) { return(HttpActionStatus.RequestCanceled); } LogSendBodyFailure(request, error); return(HttpActionStatus.SendFailure); } return(HttpActionStatus.Success); }
private async Task <HttpActionStatus> GetResponseAsync(Request request, HttpWebRequestState state) { try { state.Response = (HttpWebResponse)await state.Request.GetResponseAsync().ConfigureAwait(false); state.ResponseStream = state.Response.GetResponseStream(); return(HttpActionStatus.Success); } catch (WebException error) { var status = HandleWebException(request, state, error); // (iloktionov): HttpWebRequest реагирует на коды ответа вроде 404 или 500 исключением со статусом ProtocolError. if (status == HttpActionStatus.ProtocolError) { state.Response = (HttpWebResponse)error.Response; state.ResponseStream = state.Response.GetResponseStream(); return(HttpActionStatus.Success); } return(status); } catch (Exception error) { LogUnknownException(error); return(HttpActionStatus.UnknownFailure); } }
private static bool NeedToReadResponseBody(Request request, HttpWebRequestState state) { if (request.Method == RequestMethods.Head) { return(false); } // (iloktionov): ContentLength может быть равен -1, если сервер не укажет заголовок, но вернет контент. Это умолчание на уровне HttpWebRequest. return(state.Response.ContentLength != 0); }
private static Content CreateResponseContent(HttpWebRequestState state) { if (state.BodyBuffer != null) { return(new Content(state.BodyBuffer)); } if (state.BodyStream != null) { return(new Content(state.BodyStream.GetBuffer(), 0, (int)state.BodyStream.Position)); } return(null); }
private static Headers CreateResponseHeaders(HttpWebRequestState state) { var headers = Headers.Empty; if (state.Response == null) { return(headers); } foreach (var key in state.Response.Headers.AllKeys) { headers = headers.Set(key, state.Response.Headers[key]); } return(headers); }
public static Response BuildFailureResponse(HttpActionStatus status, HttpWebRequestState state) { switch (status) { case HttpActionStatus.SendFailure: return(BuildResponse(ResponseCode.SendFailure, state)); case HttpActionStatus.ReceiveFailure: return(BuildResponse(ResponseCode.ReceiveFailure, state)); case HttpActionStatus.Timeout: return(BuildResponse(ResponseCode.RequestTimeout, state)); case HttpActionStatus.RequestCanceled: return(BuildResponse(ResponseCode.Canceled, state)); default: return(BuildResponse(ResponseCode.UnknownFailure, state)); } }
public async Task <Response> SendAsync(Request request, TimeSpan timeout, CancellationToken cancellationToken) { if (timeout.TotalMilliseconds < 1) { LogRequestTimeout(request, timeout); return(new Response(ResponseCode.RequestTimeout)); } var state = new HttpWebRequestState(timeout); using (var timeoutCancellation = new CancellationTokenSource()) { var timeoutTask = Task.Delay(state.TimeRemaining, timeoutCancellation.Token); var senderTask = SendInternalAsync(request, state, cancellationToken); var completedTask = await Task.WhenAny(timeoutTask, senderTask).ConfigureAwait(false); if (completedTask is Task <Response> taskWithResponse) { timeoutCancellation.Cancel(); return(taskWithResponse.GetAwaiter().GetResult()); } // (iloktionov): Если выполнившееся задание не кастуется к Task<Response>, сработал таймаут. state.CancelRequest(); LogRequestTimeout(request, timeout); threadPoolMonitor.ReportAndFixIfNeeded(log); // (iloktionov): Попытаемся дождаться завершения задания по отправке запроса перед тем, как возвращать результат: await Task.WhenAny(senderTask.ContinueWith(_ => { }), Task.Delay(requestAbortTimeout)).ConfigureAwait(false); if (!senderTask.IsCompleted) { LogFailedToWaitForRequestAbort(); } return(ResponseFactory.BuildResponse(ResponseCode.RequestTimeout, state)); } }
private async Task <HttpActionStatus> LimitConnectTimeInternal(Task <HttpActionStatus> mainTask, Request request, HttpWebRequestState state, TimeSpan?connectTimeout) { using (var timeoutCancellation = new CancellationTokenSource()) { var completedTask = await Task.WhenAny(mainTask, Task.Delay(connectTimeout.Value, timeoutCancellation.Token)).ConfigureAwait(false); if (completedTask is Task <HttpActionStatus> taskWithResult) { timeoutCancellation.Cancel(); return(taskWithResult.GetAwaiter().GetResult()); } if (!ConnectTimeoutHelper.IsSocketConnected(state.Request, log)) { state.CancelRequestAttempt(); LogConnectionFailure(request, new WebException($"Connection attempt timed out. Timeout = {connectTimeout.Value}.", WebExceptionStatus.ConnectFailure), state.ConnectionAttempt); return(HttpActionStatus.ConnectionFailure); } return(await mainTask.ConfigureAwait(false)); } }
public Task <HttpActionStatus> LimitConnectTime(Task <HttpActionStatus> mainTask, Request request, HttpWebRequestState state, TimeSpan?connectTimeout) { if (connectTimeout == null) { return(mainTask); } if (!ConnectTimeoutHelper.CanCheckSocket) { return(mainTask); } if (state.TimeRemaining < connectTimeout.Value) { return(mainTask); } if (request.Url.IsLoopback) { return(mainTask); } if (ConnectTimeoutHelper.IsSocketConnected(state.Request, log)) { return(mainTask); } return(LimitConnectTimeInternal(mainTask, request, state, connectTimeout)); }
private async Task <Response> SendInternalAsync(Request request, HttpWebRequestState state, CancellationToken cancellationToken) { using (cancellationToken.Register(state.CancelRequest)) { for (state.ConnectionAttempt = 1; state.ConnectionAttempt <= ConnectionAttempts; state.ConnectionAttempt++) { using (state) { if (state.RequestCancelled) { return(new Response(ResponseCode.Canceled)); } state.Reset(); state.Request = HttpWebRequestFactory.Create(request, state.TimeRemaining); HttpActionStatus status; // (iloktionov): Шаг 1 - отправить тело запроса, если оно имеется. if (state.RequestCancelled) { return(new Response(ResponseCode.Canceled)); } if (request.Content != null) { status = await connectTimeLimiter.LimitConnectTime(SendRequestBodyAsync(request, state), request, state, ConnectionTimeout).ConfigureAwait(false); if (status == HttpActionStatus.ConnectionFailure) { continue; } if (status != HttpActionStatus.Success) { return(ResponseFactory.BuildFailureResponse(status, state)); } } // (iloktionov): Шаг 2 - получить ответ от сервера. if (state.RequestCancelled) { return(new Response(ResponseCode.Canceled)); } status = request.Content != null ? await GetResponseAsync(request, state).ConfigureAwait(false) : await connectTimeLimiter.LimitConnectTime(GetResponseAsync(request, state), request, state, ConnectionTimeout).ConfigureAwait(false); if (status == HttpActionStatus.ConnectionFailure) { continue; } if (status != HttpActionStatus.Success) { return(ResponseFactory.BuildFailureResponse(status, state)); } // (iloktionov): Шаг 3 - скачать тело ответа, если оно имеется. if (!NeedToReadResponseBody(request, state)) { return(ResponseFactory.BuildSuccessResponse(state)); } if (state.RequestCancelled) { return(new Response(ResponseCode.Canceled)); } status = await ReadResponseBodyAsync(request, state).ConfigureAwait(false); return(status == HttpActionStatus.Success ? ResponseFactory.BuildSuccessResponse(state) : ResponseFactory.BuildFailureResponse(status, state)); } } return(new Response(ResponseCode.ConnectFailure)); } }
private async Task <HttpActionStatus> ReadResponseBodyAsync(Request request, HttpWebRequestState state) { try { var contentLength = (int)state.Response.ContentLength; if (contentLength > 0) { state.BodyBuffer = new byte[contentLength]; var totalBytesRead = 0; // (iloktionov): Если буфер размером contentLength не попадет в LOH, можно передать его напрямую для работы с сокетом. // В противном случае лучше использовать небольшой промежуточный буфер из пула, т.к. ссылка на переданный сохранится надолго из-за Keep-Alive. if (contentLength < lohObjectSizeThreshold) { while (totalBytesRead < contentLength) { var bytesToRead = Math.Min(contentLength - totalBytesRead, preferredReadSize); var bytesRead = await state.ResponseStream.ReadAsync(state.BodyBuffer, totalBytesRead, bytesToRead).ConfigureAwait(false); if (bytesRead == 0) { break; } totalBytesRead += bytesRead; } } else { using (var bufferHandle = readBuffersPool.AcquireHandle()) { var buffer = bufferHandle.Resource; 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}."); } } else { state.BodyStream = new MemoryStream(); using (var bufferHandle = readBuffersPool.AcquireHandle()) { var buffer = bufferHandle.Resource; while (true) { var bytesRead = await state.ResponseStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false); if (bytesRead == 0) { break; } state.BodyStream.Write(buffer, 0, bytesRead); } } } return(HttpActionStatus.Success); } catch (Exception error) { if (IsCancellationException(error)) { return(HttpActionStatus.RequestCanceled); } LogReceiveBodyFailure(request, error); return(HttpActionStatus.ReceiveFailure); } }
public static Response BuildSuccessResponse(HttpWebRequestState state) { return(BuildResponse((ResponseCode)(int)state.Response.StatusCode, state)); }
public static Response BuildResponse(ResponseCode code, HttpWebRequestState state) { return(new Response(code, CreateResponseContent(state), CreateResponseHeaders(state))); }