Skip to content

Commit

Permalink
ThreeMammals#438 removed singleton delegating handlers as you cannot …
Browse files Browse the repository at this point in the history
…have these (ThreeMammals#456)
  • Loading branch information
TomPallister authored Jul 9, 2018
1 parent a419ed6 commit c8b72f3
Show file tree
Hide file tree
Showing 8 changed files with 643 additions and 601 deletions.
145 changes: 66 additions & 79 deletions docs/features/delegatinghandlers.rst
Original file line number Diff line number Diff line change
@@ -1,79 +1,66 @@
Delegating Handers
==================

Ocelot allows the user to add delegating handlers to the HttpClient transport. This feature was requested `GitHub #208 <https://github.com/TomPallister/Ocelot/issues/208>`_
and I decided that it was going to be useful in various ways. Since then we extended it in `GitHub #264 <https://github.com/TomPallister/Ocelot/issues/264>`_.

Usage
^^^^^

In order to add delegating handlers to the HttpClient transport you need to do two main things.

First in order to create a class that can be used a delegating handler it must look as follows. We are going to register these handlers in the
asp.net core container so you can inject any other services you have registered into the constructor of your handler.

.. code-block:: csharp
public class FakeHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
//do stuff and optionally call the base handler..
return await base.SendAsync(request, cancellationToken);
}
}
Next you must add the handlers to Ocelot's container either as singleton like follows..

.. code-block:: csharp
services.AddOcelot()
.AddSingletonDelegatingHandler<FakeHandler>()
.AddSingletonDelegatingHandler<FakeHandlerTwo>()
Or transient as below...

.. code-block:: csharp
services.AddOcelot()
.AddTransientDelegatingHandler<FakeHandler>()
.AddTransientDelegatingHandler<FakeHandlerTwo>()
Both of these Add methods have a default parameter called global which is set to false. If it is false then the intent of
the DelegatingHandler is to be applied to specific ReRoutes via ocelot.json (more on that later). If it is set to true
then it becomes a global handler and will be applied to all ReRoutes.

e.g.

.. code-block:: csharp
services.AddOcelot()
.AddSingletonDelegatingHandler<FakeHandler>(true)
Or transient as below...

.. code-block:: csharp
services.AddOcelot()
.AddTransientDelegatingHandler<FakeHandler>(true)
Finally if you want ReRoute specific DelegatingHandlers or to order your specific and / or global (more on this later) DelegatingHandlers
then you must add the following json to the specific ReRoute in ocelot.json. The names in the array must match the class names of your
DelegatingHandlers for Ocelot to match them together.

.. code-block:: json
"DelegatingHandlers": [
"FakeHandlerTwo",
"FakeHandler"
]
You can have as many DelegatingHandlers as you want and they are run in the following order:

1. Any globals that are left in the order they were added to services and are not in the DelegatingHandlers array from ocelot.json.
2. Any non global DelegatingHandlers plus any globals that were in the DelegatingHandlers array from ocelot.json ordered as they are in the DelegatingHandlers array.
3. Tracing DelegatingHandler if enabled (see tracing docs).
4. QoS DelegatingHandler if enabled (see QoS docs).
5. The HttpClient sends the HttpRequestMessage.

Hopefully other people will find this feature useful!
Delegating Handlers
===================

Ocelot allows the user to add delegating handlers to the HttpClient transport. This feature was requested `GitHub #208 <https://github.com/TomPallister/Ocelot/issues/208>`_
and I decided that it was going to be useful in various ways. Since then we extended it in `GitHub #264 <https://github.com/TomPallister/Ocelot/issues/264>`_.

Usage
^^^^^

In order to add delegating handlers to the HttpClient transport you need to do two main things.

First in order to create a class that can be used a delegating handler it must look as follows. We are going to register these handlers in the
asp.net core container so you can inject any other services you have registered into the constructor of your handler.

.. code-block:: csharp
public class FakeHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
//do stuff and optionally call the base handler..
return await base.SendAsync(request, cancellationToken);
}
}
Next you must add the handlers to Ocelot's container like below...

.. code-block:: csharp
services.AddOcelot()
.AddDelegatingHandler<FakeHandler>()
.AddDelegatingHandler<FakeHandlerTwo>()
Both of these Add methods have a default parameter called global which is set to false. If it is false then the intent of
the DelegatingHandler is to be applied to specific ReRoutes via ocelot.json (more on that later). If it is set to true
then it becomes a global handler and will be applied to all ReRoutes.

e.g.

As below...

.. code-block:: csharp
services.AddOcelot()
.AddDelegatingHandler<FakeHandler>(true)
Finally if you want ReRoute specific DelegatingHandlers or to order your specific and / or global (more on this later) DelegatingHandlers
then you must add the following json to the specific ReRoute in ocelot.json. The names in the array must match the class names of your
DelegatingHandlers for Ocelot to match them together.

.. code-block:: json
"DelegatingHandlers": [
"FakeHandlerTwo",
"FakeHandler"
]
You can have as many DelegatingHandlers as you want and they are run in the following order:

1. Any globals that are left in the order they were added to services and are not in the DelegatingHandlers array from ocelot.json.
2. Any non global DelegatingHandlers plus any globals that were in the DelegatingHandlers array from ocelot.json ordered as they are in the DelegatingHandlers array.
3. Tracing DelegatingHandler if enabled (see tracing docs).
4. QoS DelegatingHandler if enabled (see QoS docs).
5. The HttpClient sends the HttpRequestMessage.

Hopefully other people will find this feature useful!
12 changes: 6 additions & 6 deletions global.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"projects": [ "src", "test" ],
"sdk": {
"version": "2.1.300"
}
}
{
"projects": [ "src", "test" ],
"sdk": {
"version": "2.1.301"
}
}
5 changes: 1 addition & 4 deletions src/Ocelot/DependencyInjection/IOcelotBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,7 @@ public interface IOcelotBuilder

IOcelotAdministrationBuilder AddAdministration(string path, Action<IdentityServerAuthenticationOptions> configOptions);

IOcelotBuilder AddSingletonDelegatingHandler<T>(bool global = false)
where T : DelegatingHandler;

IOcelotBuilder AddTransientDelegatingHandler<T>(bool global = false)
IOcelotBuilder AddDelegatingHandler<T>(bool global = false)
where T : DelegatingHandler;

IOcelotBuilder AddSingletonDefinedAggregator<T>()
Expand Down
21 changes: 1 addition & 20 deletions src/Ocelot/DependencyInjection/OcelotBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -212,26 +212,7 @@ public IOcelotBuilder AddTransientDefinedAggregator<T>()
return this;
}

public IOcelotBuilder AddSingletonDelegatingHandler<THandler>(bool global = false)
where THandler : DelegatingHandler
{
if(global)
{
_services.AddSingleton<THandler>();
_services.AddSingleton<GlobalDelegatingHandler>(s => {
var service = s.GetService<THandler>();
return new GlobalDelegatingHandler(service);
});
}
else
{
_services.AddSingleton<DelegatingHandler, THandler>();
}

return this;
}

public IOcelotBuilder AddTransientDelegatingHandler<THandler>(bool global = false)
public IOcelotBuilder AddDelegatingHandler<THandler>(bool global = false)
where THandler : DelegatingHandler
{
if(global)
Expand Down
62 changes: 62 additions & 0 deletions test/Ocelot.AcceptanceTests/HttpDelegatingHandlersTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,57 @@ public void should_call_global_di_handlers()
.BDDfy();
}

[Fact]
public void should_call_global_di_handlers_multiple_times()
{
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 9187,
}
},
UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" },
}
}
};

this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:9187", "/", 200, "Hello from Laura"))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunningWithGlobalHandlerRegisteredInDi<FakeHandlerAgain>())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.BDDfy();
}

[Fact]
public void should_call_global_di_handlers_with_dependency()
{
Expand Down Expand Up @@ -198,6 +249,17 @@ protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage reques
return base.SendAsync(request, cancellationToken);
}
}
// ReSharper disable once ClassNeverInstantiated.Local
private class FakeHandlerAgain : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
Console.WriteLine(request.RequestUri);

//do stuff and optionally call the base handler..
return await base.SendAsync(request, cancellationToken);
}
}

private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody)
{
Expand Down
41 changes: 36 additions & 5 deletions test/Ocelot.AcceptanceTests/Steps.cs
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,8 @@ public void GivenOcelotIsRunningWithSpecficHandlersRegisteredInDi<TOne, TWo>()
{
s.AddSingleton(_webHostBuilder);
s.AddOcelot()
.AddSingletonDelegatingHandler<TOne>()
.AddSingletonDelegatingHandler<TWo>();
.AddDelegatingHandler<TOne>()
.AddDelegatingHandler<TWo>();
})
.Configure(a =>
{
Expand Down Expand Up @@ -303,8 +303,39 @@ public void GivenOcelotIsRunningWithGlobalHandlersRegisteredInDi<TOne, TWo>()
{
s.AddSingleton(_webHostBuilder);
s.AddOcelot()
.AddSingletonDelegatingHandler<TOne>(true)
.AddSingletonDelegatingHandler<TWo>(true);
.AddDelegatingHandler<TOne>(true)
.AddDelegatingHandler<TWo>(true);
})
.Configure(a =>
{
a.UseOcelot().Wait();
});

_ocelotServer = new TestServer(_webHostBuilder);

_ocelotClient = _ocelotServer.CreateClient();
}

public void GivenOcelotIsRunningWithGlobalHandlerRegisteredInDi<TOne>()
where TOne : DelegatingHandler
{
_webHostBuilder = new WebHostBuilder();

_webHostBuilder
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath);
var env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
config.AddJsonFile("ocelot.json");
config.AddEnvironmentVariables();
})
.ConfigureServices(s =>
{
s.AddSingleton(_webHostBuilder);
s.AddOcelot()
.AddDelegatingHandler<TOne>(true);
})
.Configure(a =>
{
Expand Down Expand Up @@ -336,7 +367,7 @@ public void GivenOcelotIsRunningWithGlobalHandlersRegisteredInDi<TOne>(FakeDepen
s.AddSingleton(_webHostBuilder);
s.AddSingleton<FakeDependency>(dependency);
s.AddOcelot()
.AddSingletonDelegatingHandler<TOne>(true);
.AddDelegatingHandler<TOne>(true);
})
.Configure(a =>
{
Expand Down
23 changes: 19 additions & 4 deletions test/Ocelot.ManualTest/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
using Ocelot.Middleware;
using System;
using IdentityServer4.AccessTokenValidation;
using System.Net.Http;
using System.Threading.Tasks;
using System.Threading;

public class Program
{
Expand All @@ -35,10 +38,11 @@ public static void Main(string[] args)
});

s.AddOcelot()
.AddCacheManager(x =>
{
x.WithDictionaryHandle();
})
.AddDelegatingHandler<FakeHandler>(true)
// .AddCacheManager(x =>
// {
// x.WithDictionaryHandle();
// })
/*.AddOpenTracing(option =>
{
option.CollectorUrl = "http://localhost:9618";
Expand All @@ -60,4 +64,15 @@ public static void Main(string[] args)
.Run();
}
}

public class FakeHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
Console.WriteLine(request.RequestUri);

//do stuff and optionally call the base handler..
return await base.SendAsync(request, cancellationToken);
}
}
}
Loading

0 comments on commit c8b72f3

Please sign in to comment.