[WIP] use HttpContext.RequestAborted to support cancel a request (#902)

* use HttpContext.RequestAborted to support cancel a request

* register context.HttpContext.RequestAborted.ThrowIfCancellationRequested() in ExceptionHandlerMiddleware

* add LangVersion

* add default number to OcelotErrorCode
fix unit test

* revert back to one line
This commit is contained in:
liweihan 2019-05-27 16:23:30 +08:00 committed by Thiago Loureiro
parent fe3cf44c45
commit 4476f8273e
11 changed files with 114 additions and 68 deletions

View File

@ -1,5 +1,7 @@
<Project>
<PropertyGroup>
<LangVersion>latest</LangVersion>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/ThreeMammals/Ocelot</RepositoryUrl>
<!-- Optional: Publish the repository URL in the built .nupkg (in the NuSpec <Repository> element) -->

View File

@ -1,14 +1,14 @@
namespace Ocelot.Errors.Middleware
{
using Configuration;
using System;
using System.Linq;
using System.Threading.Tasks;
using Ocelot.Configuration.Repository;
using Ocelot.Infrastructure.Extensions;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Logging;
using Ocelot.Middleware;
using System;
using System.Linq;
using System.Threading.Tasks;
/// <summary>
/// Catches all unhandled exceptions thrown by middleware, logs and returns a 500
@ -34,6 +34,8 @@ namespace Ocelot.Errors.Middleware
{
try
{
context.HttpContext.RequestAborted.ThrowIfCancellationRequested();
//try and get the global request id and set it for logs...
//should this basically be immutable per request...i guess it should!
//first thing is get config
@ -52,6 +54,14 @@ namespace Ocelot.Errors.Middleware
await _next.Invoke(context);
}
catch (OperationCanceledException e) when (context.HttpContext.RequestAborted.IsCancellationRequested)
{
Logger.LogDebug("operation canceled");
if (!context.HttpContext.Response.HasStarted)
{
context.HttpContext.Response.StatusCode = 499;
}
}
catch (Exception e)
{
Logger.LogDebug("error calling middleware");

View File

@ -2,42 +2,43 @@
{
public enum OcelotErrorCode
{
UnauthenticatedError,
UnknownError,
DownstreampathTemplateAlreadyUsedError,
UnableToFindDownstreamRouteError,
CannotAddDataError,
CannotFindDataError,
UnableToCompleteRequestError,
UnableToCreateAuthenticationHandlerError,
UnsupportedAuthenticationProviderError,
CannotFindClaimError,
ParsingConfigurationHeaderError,
NoInstructionsError,
InstructionNotForClaimsError,
UnauthorizedError,
ClaimValueNotAuthorisedError,
ScopeNotAuthorisedError,
UserDoesNotHaveClaimError,
DownstreamPathTemplateContainsSchemeError,
DownstreamPathNullOrEmptyError,
DownstreamSchemeNullOrEmptyError,
DownstreamHostNullOrEmptyError,
ServicesAreNullError,
ServicesAreEmptyError,
UnableToFindServiceDiscoveryProviderError,
UnableToFindLoadBalancerError,
RequestTimedOutError,
UnableToFindQoSProviderError,
UnmappableRequestError,
RateLimitOptionsError,
PathTemplateDoesntStartWithForwardSlash,
FileValidationFailedError,
UnableToFindDelegatingHandlerProviderError,
CouldNotFindPlaceholderError,
CouldNotFindAggregatorError,
CannotAddPlaceholderError,
CannotRemovePlaceholderError,
QuotaExceededError
UnauthenticatedError = 0,
UnknownError = 1,
DownstreampathTemplateAlreadyUsedError = 2,
UnableToFindDownstreamRouteError = 3,
CannotAddDataError = 4,
CannotFindDataError = 5,
UnableToCompleteRequestError = 6,
UnableToCreateAuthenticationHandlerError = 7,
UnsupportedAuthenticationProviderError = 8,
CannotFindClaimError = 9,
ParsingConfigurationHeaderError = 10,
NoInstructionsError = 11,
InstructionNotForClaimsError = 12,
UnauthorizedError = 13,
ClaimValueNotAuthorisedError = 14,
ScopeNotAuthorisedError = 15,
UserDoesNotHaveClaimError = 16,
DownstreamPathTemplateContainsSchemeError = 17,
DownstreamPathNullOrEmptyError = 18,
DownstreamSchemeNullOrEmptyError = 19,
DownstreamHostNullOrEmptyError = 20,
ServicesAreNullError = 21,
ServicesAreEmptyError = 22,
UnableToFindServiceDiscoveryProviderError = 23,
UnableToFindLoadBalancerError = 24,
RequestTimedOutError = 25,
UnableToFindQoSProviderError = 26,
UnmappableRequestError = 27,
RateLimitOptionsError = 28,
PathTemplateDoesntStartWithForwardSlash = 29,
FileValidationFailedError = 30,
UnableToFindDelegatingHandlerProviderError = 31,
CouldNotFindPlaceholderError = 32,
CouldNotFindAggregatorError = 33,
CannotAddPlaceholderError = 34,
CannotRemovePlaceholderError = 35,
QuotaExceededError = 36,
RequestCanceled = 37,
}
}

View File

@ -1,9 +1,9 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Ocelot.Logging;
using Ocelot.Middleware;
using Ocelot.Responses;
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace Ocelot.Requester
{
@ -33,7 +33,7 @@ namespace Ocelot.Requester
try
{
var response = await httpClient.SendAsync(context.DownstreamRequest.ToHttpRequestMessage());
var response = await httpClient.SendAsync(context.DownstreamRequest.ToHttpRequestMessage(), context.HttpContext.RequestAborted);
return new OkResponse<HttpResponseMessage>(response);
}
catch (Exception exception)

View File

@ -1,4 +1,5 @@
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace Ocelot.Requester
@ -15,9 +16,9 @@ namespace Ocelot.Requester
Client = client;
}
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken = default)
{
return Client.SendAsync(request);
return Client.SendAsync(request, cancellationToken);
}
}
}

View File

@ -1,9 +1,9 @@
namespace Ocelot.Requester
{
using System;
using System.Collections.Generic;
using Errors;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
public class HttpExeptionToErrorMapper : IExceptionToErrorMapper
{
@ -23,6 +23,11 @@ namespace Ocelot.Requester
return _mappers[type](exception);
}
if (type == typeof(OperationCanceledException))
{
return new RequestCanceledError(exception.Message);
}
return new UnableToCompleteRequestError(exception);
}
}

View File

@ -1,5 +1,5 @@
using System;
using System.Net.Http;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace Ocelot.Requester
@ -8,6 +8,6 @@ namespace Ocelot.Requester
{
HttpClient Client { get; }
Task<HttpResponseMessage> SendAsync(HttpRequestMessage request);
Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken = default);
}
}

View File

@ -0,0 +1,11 @@
using Ocelot.Errors;
namespace Ocelot.Requester
{
public class RequestCanceledError : Error
{
public RequestCanceledError(string message) : base(message, OcelotErrorCode.RequestCanceled)
{
}
}
}

View File

@ -1,6 +1,6 @@
using Ocelot.Errors;
using System.Collections.Generic;
using System.Linq;
using Ocelot.Errors;
namespace Ocelot.Responder
{
@ -27,6 +27,14 @@ namespace Ocelot.Responder
return 503;
}
if (errors.Any(e => e.Code == OcelotErrorCode.RequestCanceled))
{
// status code refer to
// https://stackoverflow.com/questions/46234679/what-is-the-correct-http-status-code-for-a-cancelled-request?answertab=votes#tab-top
// https://httpstatuses.com/499
return 499;
}
if (errors.Any(e => e.Code == OcelotErrorCode.UnableToFindDownstreamRouteError))
{
return 404;

View File

@ -30,6 +30,14 @@
error.ShouldBeOfType<UnableToCompleteRequestError>();
}
[Fact]
public void should_return_request_canceled()
{
var error = _mapper.Map(new OperationCanceledException());
error.ShouldBeOfType<RequestCanceledError>();
}
[Fact]
public void should_return_error_from_mapper()
{

View File

@ -1,9 +1,9 @@
using System;
using System.Collections.Generic;
using System.Net;
using Ocelot.Errors;
using Ocelot.Errors;
using Ocelot.Responder;
using Shouldly;
using System;
using System.Collections.Generic;
using System.Net;
using TestStack.BDDfy;
using Xunit;
@ -125,7 +125,7 @@ namespace Ocelot.UnitTests.Responder
// If this test fails then it's because the number of error codes has changed.
// You should make the appropriate changes to the test cases here to ensure
// they cover all the error codes, and then modify this assertion.
Enum.GetNames(typeof(OcelotErrorCode)).Length.ShouldBe(37, "Looks like the number of error codes has changed. Do you need to modify ErrorsToHttpStatusCodeMapper?");
Enum.GetNames(typeof(OcelotErrorCode)).Length.ShouldBe(38, "Looks like the number of error codes has changed. Do you need to modify ErrorsToHttpStatusCodeMapper?");
}
private void ShouldMapErrorToStatusCode(OcelotErrorCode errorCode, HttpStatusCode expectedHttpStatusCode)