mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-20 08:22:50 +08:00
[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:
parent
fe3cf44c45
commit
4476f8273e
@ -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) -->
|
||||||
|
@ -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");
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
11
src/Ocelot/Requester/RequestCanceledError.cs
Normal file
11
src/Ocelot/Requester/RequestCanceledError.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
using Ocelot.Errors;
|
||||||
|
|
||||||
|
namespace Ocelot.Requester
|
||||||
|
{
|
||||||
|
public class RequestCanceledError : Error
|
||||||
|
{
|
||||||
|
public RequestCanceledError(string message) : base(message, OcelotErrorCode.RequestCanceled)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
@ -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()
|
||||||
{
|
{
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user