[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> <Project>
<PropertyGroup> <PropertyGroup>
<LangVersion>latest</LangVersion>
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/ThreeMammals/Ocelot</RepositoryUrl> <RepositoryUrl>https://github.com/ThreeMammals/Ocelot</RepositoryUrl>
<!-- Optional: Publish the repository URL in the built .nupkg (in the NuSpec <Repository> element) --> <!-- Optional: Publish the repository URL in the built .nupkg (in the NuSpec <Repository> element) -->

View File

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

View File

@ -2,42 +2,43 @@
{ {
public enum OcelotErrorCode public enum OcelotErrorCode
{ {
UnauthenticatedError, UnauthenticatedError = 0,
UnknownError, UnknownError = 1,
DownstreampathTemplateAlreadyUsedError, DownstreampathTemplateAlreadyUsedError = 2,
UnableToFindDownstreamRouteError, UnableToFindDownstreamRouteError = 3,
CannotAddDataError, CannotAddDataError = 4,
CannotFindDataError, CannotFindDataError = 5,
UnableToCompleteRequestError, UnableToCompleteRequestError = 6,
UnableToCreateAuthenticationHandlerError, UnableToCreateAuthenticationHandlerError = 7,
UnsupportedAuthenticationProviderError, UnsupportedAuthenticationProviderError = 8,
CannotFindClaimError, CannotFindClaimError = 9,
ParsingConfigurationHeaderError, ParsingConfigurationHeaderError = 10,
NoInstructionsError, NoInstructionsError = 11,
InstructionNotForClaimsError, InstructionNotForClaimsError = 12,
UnauthorizedError, UnauthorizedError = 13,
ClaimValueNotAuthorisedError, ClaimValueNotAuthorisedError = 14,
ScopeNotAuthorisedError, ScopeNotAuthorisedError = 15,
UserDoesNotHaveClaimError, UserDoesNotHaveClaimError = 16,
DownstreamPathTemplateContainsSchemeError, DownstreamPathTemplateContainsSchemeError = 17,
DownstreamPathNullOrEmptyError, DownstreamPathNullOrEmptyError = 18,
DownstreamSchemeNullOrEmptyError, DownstreamSchemeNullOrEmptyError = 19,
DownstreamHostNullOrEmptyError, DownstreamHostNullOrEmptyError = 20,
ServicesAreNullError, ServicesAreNullError = 21,
ServicesAreEmptyError, ServicesAreEmptyError = 22,
UnableToFindServiceDiscoveryProviderError, UnableToFindServiceDiscoveryProviderError = 23,
UnableToFindLoadBalancerError, UnableToFindLoadBalancerError = 24,
RequestTimedOutError, RequestTimedOutError = 25,
UnableToFindQoSProviderError, UnableToFindQoSProviderError = 26,
UnmappableRequestError, UnmappableRequestError = 27,
RateLimitOptionsError, RateLimitOptionsError = 28,
PathTemplateDoesntStartWithForwardSlash, PathTemplateDoesntStartWithForwardSlash = 29,
FileValidationFailedError, FileValidationFailedError = 30,
UnableToFindDelegatingHandlerProviderError, UnableToFindDelegatingHandlerProviderError = 31,
CouldNotFindPlaceholderError, CouldNotFindPlaceholderError = 32,
CouldNotFindAggregatorError, CouldNotFindAggregatorError = 33,
CannotAddPlaceholderError, CannotAddPlaceholderError = 34,
CannotRemovePlaceholderError, CannotRemovePlaceholderError = 35,
QuotaExceededError 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.Logging;
using Ocelot.Middleware; using Ocelot.Middleware;
using Ocelot.Responses; using Ocelot.Responses;
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace Ocelot.Requester namespace Ocelot.Requester
{ {
@ -33,7 +33,7 @@ namespace Ocelot.Requester
try 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); return new OkResponse<HttpResponseMessage>(response);
} }
catch (Exception exception) catch (Exception exception)

View File

@ -1,4 +1,5 @@
using System.Net.Http; using System.Net.Http;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Ocelot.Requester namespace Ocelot.Requester
@ -15,9 +16,9 @@ namespace Ocelot.Requester
Client = client; 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 namespace Ocelot.Requester
{ {
using System;
using System.Collections.Generic;
using Errors; using Errors;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
public class HttpExeptionToErrorMapper : IExceptionToErrorMapper public class HttpExeptionToErrorMapper : IExceptionToErrorMapper
{ {
@ -23,6 +23,11 @@ namespace Ocelot.Requester
return _mappers[type](exception); return _mappers[type](exception);
} }
if (type == typeof(OperationCanceledException))
{
return new RequestCanceledError(exception.Message);
}
return new UnableToCompleteRequestError(exception); 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; using System.Threading.Tasks;
namespace Ocelot.Requester namespace Ocelot.Requester
@ -8,6 +8,6 @@ namespace Ocelot.Requester
{ {
HttpClient Client { get; } 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.Collections.Generic;
using System.Linq; using System.Linq;
using Ocelot.Errors;
namespace Ocelot.Responder namespace Ocelot.Responder
{ {
@ -27,6 +27,14 @@ namespace Ocelot.Responder
return 503; 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)) if (errors.Any(e => e.Code == OcelotErrorCode.UnableToFindDownstreamRouteError))
{ {
return 404; return 404;

View File

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

View File

@ -1,9 +1,9 @@
using System; using Ocelot.Errors;
using System.Collections.Generic;
using System.Net;
using Ocelot.Errors;
using Ocelot.Responder; using Ocelot.Responder;
using Shouldly; using Shouldly;
using System;
using System.Collections.Generic;
using System.Net;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; 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. // 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 // You should make the appropriate changes to the test cases here to ensure
// they cover all the error codes, and then modify this assertion. // 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) private void ShouldMapErrorToStatusCode(OcelotErrorCode errorCode, HttpStatusCode expectedHttpStatusCode)
@ -137,7 +137,7 @@ namespace Ocelot.UnitTests.Responder
{ {
var errors = new List<Error>(); var errors = new List<Error>();
foreach(var errorCode in errorCodes) foreach (var errorCode in errorCodes)
{ {
errors.Add(new AnyError(errorCode)); errors.Add(new AnyError(errorCode));
} }