Skip to content

Commit

Permalink
#893 Problems to cancel a request (CancellationToken) (#1367)
Browse files Browse the repository at this point in the history
* #893 fix request cancellation, add acceptance test

* correct exception message

* correct exception message

* #893 fix request cancellation, add acceptance test

* correct exception message

* correct exception message

* Add more conditions in Then criteria

* Update MultiplexingMiddleware.cs

* Update CancelRequestTests.cs

* Fix StyleCop and IDE analyzers issues

* 'using' workarounds

* Convert to file-scoped namespace

* Fix warnings

* IDE0044 Make field readonly. Add readonly modifier

* review the code

---------

Co-authored-by: Raman Maksimchuk <dotnet044@gmail.com>
Co-authored-by: Raman Maksimchuk <10501504+raman-m@users.noreply.github.com>
  • Loading branch information
3 people committed Sep 23, 2023
1 parent b27761f commit 5ad807d
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 1 deletion.
3 changes: 2 additions & 1 deletion src/Ocelot/Multiplexer/MultiplexingMiddleware.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http;
using Ocelot.Configuration;
using Ocelot.DownstreamRouteFinder.UrlMatcher;
using Ocelot.Logging;
Expand Down Expand Up @@ -184,6 +184,7 @@ private static HttpContext Copy(HttpContext source)
target.Request.RouteValues = source.Request.RouteValues;
target.Connection.RemoteIpAddress = source.Connection.RemoteIpAddress;
target.RequestServices = source.RequestServices;
target.RequestAborted = source.RequestAborted;
return target;
}

Expand Down
134 changes: 134 additions & 0 deletions test/Ocelot.AcceptanceTests/CancelRequestTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
using Microsoft.AspNetCore.Http;
using Ocelot.Configuration.File;
using Shouldly;
using System;
using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;
using TestStack.BDDfy;
using Xunit;

namespace Ocelot.AcceptanceTests;

public class CancelRequestTests : IDisposable
{
private const int SERVICE_WORK_TIME = 5_000;
private const int MAX_WAITING_TIME = 60_000;

private readonly Steps _steps;
private readonly ServiceHandler _serviceHandler;
private readonly Notifier _serviceWorkStartedNotifier;
private readonly Notifier _serviceWorkStoppedNotifier;

private bool _cancelExceptionThrown;

public CancelRequestTests()
{
_steps = new Steps();
_serviceHandler = new ServiceHandler();
_serviceWorkStartedNotifier = new Notifier("service work started notifier");
_serviceWorkStoppedNotifier = new Notifier("service work finished notifier");
}

[Fact]
public void Should_abort_service_work_when_cancelling_the_request()
{
var port = RandomPortFinder.GetRandomPort();

var configuration = new FileConfiguration
{
Routes = new List<FileRoute>
{
new()
{
DownstreamPathTemplate = "/",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new()
{
Host = "localhost",
Port = port,
},
},
DownstreamScheme = "http",
UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" },
},
},
};

this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}"))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIGetUrlOnTheApiGatewayAndDontWait("/"))
.And(x => WhenIWaitForNotification(_serviceWorkStartedNotifier))
.And(x => _steps.WhenICancelTheRequest())
.And(x => WhenIWaitForNotification(_serviceWorkStoppedNotifier))
.Then(x => x.ThenOcelotClientRequestIsCanceled())
.BDDfy();
}

private void GivenThereIsAServiceRunningOn(string baseUrl)
{
_serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, async context =>
{
try
{
var response = string.Empty;
_serviceWorkStartedNotifier.NotificationSent = true;
await Task.Delay(SERVICE_WORK_TIME, context.RequestAborted);
context.Response.StatusCode = (int)HttpStatusCode.OK;
await context.Response.WriteAsync(response);
}
catch (TaskCanceledException)
{
_cancelExceptionThrown = true;
}
finally
{
_serviceWorkStoppedNotifier.NotificationSent = true;
}
});
}

private static async Task WhenIWaitForNotification(Notifier notifier)
{
int waitingTime = 0;
while (!notifier.NotificationSent)
{
var waitingInterval = 50;
await Task.Delay(waitingInterval);
waitingTime += waitingInterval;

if (waitingTime > MAX_WAITING_TIME)
{
throw new TimeoutException(notifier.Name + $" did not sent notification within {MAX_WAITING_TIME / 1000} second(s).");
}
}
}

private void ThenOcelotClientRequestIsCanceled()
{
_serviceWorkStartedNotifier.NotificationSent.ShouldBeTrue();
_serviceWorkStoppedNotifier.NotificationSent.ShouldBeTrue();

_cancelExceptionThrown.ShouldBeTrue();
}

public void Dispose()
{
_serviceHandler?.Dispose();
_steps.Dispose();
GC.SuppressFinalize(this);
}

class Notifier
{
public Notifier(string name) => Name = name;

public bool NotificationSent { get; set; }
public string Name { get; set; }
}
}
10 changes: 10 additions & 0 deletions test/Ocelot.AcceptanceTests/Steps.cs
Original file line number Diff line number Diff line change
Expand Up @@ -966,6 +966,16 @@ public void WhenIGetUrlOnTheApiGateway(string url)
_response = _ocelotClient.GetAsync(url).Result;
}

public void WhenIGetUrlOnTheApiGatewayAndDontWait(string url)
{
_ocelotClient.GetAsync(url);
}

public void WhenICancelTheRequest()
{
_ocelotClient.CancelPendingRequests();
}

public void WhenIGetUrlOnTheApiGateway(string url, HttpContent content)
{
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url) { Content = content };
Expand Down

0 comments on commit 5ad807d

Please sign in to comment.