Merge branch 'master' into jlukawska-feature/1115-find-available-port-in-acceptance-tests

This commit is contained in:
TomPallister 2020-03-11 20:00:55 +00:00
commit c75227d5b8
66 changed files with 2439 additions and 1197 deletions

View File

@ -1,10 +1,10 @@
[<img src="https://threemammals.com/ocelot_logo.png">](https://threemammals.com/ocelot) [<img src="https://threemammals.com/images/ocelot_logo.png">](https://threemammals.com/ocelot)
[![CircleCI](https://circleci.com/gh/ThreeMammals/Ocelot/tree/master.svg?style=svg)](https://circleci.com/gh/ThreeMammals/Ocelot/tree/master) [![CircleCI](https://circleci.com/gh/ThreeMammals/Ocelot/tree/master.svg?style=svg)](https://circleci.com/gh/ThreeMammals/Ocelot/tree/master)
[![Coverage Status](https://coveralls.io/repos/github/ThreeMammals/Ocelot/badge.svg?branch=master)](https://coveralls.io/github/ThreeMammals/Ocelot?branch=master) [![Coverage Status](https://coveralls.io/repos/github/ThreeMammals/Ocelot/badge.svg?branch=master)](https://coveralls.io/github/ThreeMammals/Ocelot?branch=master)
[Slack](threemammals.slack.com) [Slack](https://threemammals.slack.com)
# Ocelot # Ocelot
@ -48,7 +48,7 @@ A quick list of Ocelot's capabilities for more information see the [documentatio
* Retry policies / QoS * Retry policies / QoS
* Load Balancing * Load Balancing
* Logging / Tracing / Correlation * Logging / Tracing / Correlation
* Headers / Query String / Claims Transformation * Headers / Method / Query String / Claims Transformation
* Custom Middleware / Delegating Handlers * Custom Middleware / Delegating Handlers
* Configuration / Administration REST API * Configuration / Administration REST API
* Platform / Cloud Agnostic * Platform / Cloud Agnostic

View File

@ -134,11 +134,12 @@ Task("RunUnitTests")
var coverageSummaryFile = GetSubDirectories(artifactsForUnitTestsDir).First().CombineWithFilePath(File("coverage.opencover.xml")); var coverageSummaryFile = GetSubDirectories(artifactsForUnitTestsDir).First().CombineWithFilePath(File("coverage.opencover.xml"));
Information(coverageSummaryFile); Information(coverageSummaryFile);
Information(artifactsForUnitTestsDir); Information(artifactsForUnitTestsDir);
// todo bring back report generator to get a friendly report // todo bring back report generator to get a friendly report
// ReportGenerator(coverageSummaryFile, artifactsForUnitTestsDir); // ReportGenerator(coverageSummaryFile, artifactsForUnitTestsDir);
// https://github.com/danielpalme/ReportGenerator // https://github.com/danielpalme/ReportGenerator
if (IsRunningOnCircleCI()) if (IsRunningOnCircleCI() && IsMaster())
{ {
var repoToken = EnvironmentVariable(coverallsRepoToken); var repoToken = EnvironmentVariable(coverallsRepoToken);
if (string.IsNullOrEmpty(repoToken)) if (string.IsNullOrEmpty(repoToken))
@ -497,4 +498,9 @@ private string GetResource(string url)
private bool IsRunningOnCircleCI() private bool IsRunningOnCircleCI()
{ {
return !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("CIRCLECI")); return !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("CIRCLECI"));
}
private bool IsMaster()
{
return Environment.GetEnvironmentVariable("CIRCLE_BRANCH").ToLower() == "master";
} }

View File

@ -1,230 +1,287 @@
Configuration Configuration
============ ============
An example configuration can be found `here <https://github.com/ThreeMammals/Ocelot/blob/master/test/Ocelot.ManualTest/ocelot.json>`_. An example configuration can be found `here <https://github.com/ThreeMammals/Ocelot/blob/master/test/Ocelot.ManualTest/ocelot.json>`_.
There are two sections to the configuration. An array of ReRoutes and a GlobalConfiguration. There are two sections to the configuration. An array of ReRoutes and a GlobalConfiguration.
The ReRoutes are the objects that tell Ocelot how to treat an upstream request. The Global The ReRoutes are the objects that tell Ocelot how to treat an upstream request. The Global
configuration is a bit hacky and allows overrides of ReRoute specific settings. It's useful configuration is a bit hacky and allows overrides of ReRoute specific settings. It's useful
if you don't want to manage lots of ReRoute specific settings. if you don't want to manage lots of ReRoute specific settings.
.. code-block:: json .. code-block:: json
{ {
"ReRoutes": [], "ReRoutes": [],
"GlobalConfiguration": {} "GlobalConfiguration": {}
} }
Here is an example ReRoute configuration, You don't need to set all of these things but this is everything that is available at the moment: Here is an example ReRoute configuration, You don't need to set all of these things but this is everything that is available at the moment:
.. code-block:: json .. code-block:: json
{ {
"DownstreamPathTemplate": "/", "DownstreamPathTemplate": "/",
"UpstreamPathTemplate": "/", "UpstreamPathTemplate": "/",
"UpstreamHttpMethod": [ "UpstreamHttpMethod": [
"Get" "Get"
], ],
"AddHeadersToRequest": {}, "DownstreamHttpMethod": "",
"AddClaimsToRequest": {}, "DownstreamHttpVersion": "",
"RouteClaimsRequirement": {}, "AddHeadersToRequest": {},
"AddQueriesToRequest": {}, "AddClaimsToRequest": {},
"RequestIdKey": "", "RouteClaimsRequirement": {},
"FileCacheOptions": { "AddQueriesToRequest": {},
"TtlSeconds": 0, "RequestIdKey": "",
"Region": "" "FileCacheOptions": {
}, "TtlSeconds": 0,
"ReRouteIsCaseSensitive": false, "Region": ""
"ServiceName": "", },
"DownstreamScheme": "http", "ReRouteIsCaseSensitive": false,
"DownstreamHostAndPorts": [ "ServiceName": "",
{ "DownstreamScheme": "http",
"Host": "localhost", "DownstreamHostAndPorts": [
"Port": 51876, {
} "Host": "localhost",
], "Port": 51876,
"QoSOptions": { }
"ExceptionsAllowedBeforeBreaking": 0, ],
"DurationOfBreak": 0, "QoSOptions": {
"TimeoutValue": 0 "ExceptionsAllowedBeforeBreaking": 0,
}, "DurationOfBreak": 0,
"LoadBalancer": "", "TimeoutValue": 0
"RateLimitOptions": { },
"ClientWhitelist": [], "LoadBalancer": "",
"EnableRateLimiting": false, "RateLimitOptions": {
"Period": "", "ClientWhitelist": [],
"PeriodTimespan": 0, "EnableRateLimiting": false,
"Limit": 0 "Period": "",
}, "PeriodTimespan": 0,
"AuthenticationOptions": { "Limit": 0
"AuthenticationProviderKey": "", },
"AllowedScopes": [] "AuthenticationOptions": {
}, "AuthenticationProviderKey": "",
"HttpHandlerOptions": { "AllowedScopes": []
"AllowAutoRedirect": true, },
"UseCookieContainer": true, "HttpHandlerOptions": {
"UseTracing": true, "AllowAutoRedirect": true,
"MaxConnectionsPerServer": 100 "UseCookieContainer": true,
}, "UseTracing": true,
"DangerousAcceptAnyServerCertificateValidator": false "MaxConnectionsPerServer": 100
} },
"DangerousAcceptAnyServerCertificateValidator": false
More information on how to use these options is below.. }
Multiple environments More information on how to use these options is below..
^^^^^^^^^^^^^^^^^^^^^
Multiple environments
Like any other asp.net core project Ocelot supports configuration file names such as configuration.dev.json, configuration.test.json etc. In order to implement this add the following ^^^^^^^^^^^^^^^^^^^^^
to you
Like any other asp.net core project Ocelot supports configuration file names such as configuration.dev.json, configuration.test.json etc. In order to implement this add the following
.. code-block:: csharp to you
.ConfigureAppConfiguration((hostingContext, config) => .. code-block:: csharp
{
config .ConfigureAppConfiguration((hostingContext, config) =>
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) {
.AddJsonFile("appsettings.json", true, true) config
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
.AddJsonFile("ocelot.json") .AddJsonFile("appsettings.json", true, true)
.AddJsonFile($"configuration.{hostingContext.HostingEnvironment.EnvironmentName}.json") .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
.AddEnvironmentVariables(); .AddJsonFile("ocelot.json")
}) .AddJsonFile($"configuration.{hostingContext.HostingEnvironment.EnvironmentName}.json")
.AddEnvironmentVariables();
Ocelot will now use the environment specific configuration and fall back to ocelot.json if there isn't one. })
You also need to set the corresponding environment variable which is ASPNETCORE_ENVIRONMENT. More info on this can be found in the `asp.net core docs <https://docs.microsoft.com/en-us/aspnet/core/fundamentals/environments>`_. Ocelot will now use the environment specific configuration and fall back to ocelot.json if there isn't one.
Merging configuration files You also need to set the corresponding environment variable which is ASPNETCORE_ENVIRONMENT. More info on this can be found in the `asp.net core docs <https://docs.microsoft.com/en-us/aspnet/core/fundamentals/environments>`_.
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Merging configuration files
This feature was requested in `Issue 296 <https://github.com/ThreeMammals/Ocelot/issues/296>`_ and allows users to have multiple configuration files to make managing large configurations easier. ^^^^^^^^^^^^^^^^^^^^^^^^^^^
Instead of adding the configuration directly e.g. AddJsonFile("ocelot.json") you can call AddOcelot() like below. This feature was requested in `Issue 296 <https://github.com/ThreeMammals/Ocelot/issues/296>`_ and allows users to have multiple configuration files to make managing large configurations easier.
.. code-block:: csharp Instead of adding the configuration directly e.g. AddJsonFile("ocelot.json") you can call AddOcelot() like below.
.ConfigureAppConfiguration((hostingContext, config) => .. code-block:: csharp
{
config .ConfigureAppConfiguration((hostingContext, config) =>
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) {
.AddJsonFile("appsettings.json", true, true) config
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
.AddOcelot(hostingContext.HostingEnvironment) .AddJsonFile("appsettings.json", true, true)
.AddEnvironmentVariables(); .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
}) .AddOcelot(hostingContext.HostingEnvironment)
.AddEnvironmentVariables();
In this scenario Ocelot will look for any files that match the pattern (?i)ocelot.([a-zA-Z0-9]*).json and then merge these together. If you want to set the GlobalConfiguration property you must have a file called ocelot.global.json. })
The way Ocelot merges the files is basically load them, loop over them, add any ReRoutes, add any AggregateReRoutes and if the file is called ocelot.global.json add the GlobalConfiguration aswell as any ReRoutes or AggregateReRoutes. Ocelot will then save the merged configuration to a file called ocelot.json and this will be used as the source of truth while ocelot is running. In this scenario Ocelot will look for any files that match the pattern (?i)ocelot.([a-zA-Z0-9]*).json and then merge these together. If you want to set the GlobalConfiguration property you must have a file called ocelot.global.json.
At the moment there is no validation at this stage it only happens when Ocelot validates the final merged configuration. This is something to be aware of when you are investigating problems. I would advise always checking what is in ocelot.json if you have any problems. The way Ocelot merges the files is basically load them, loop over them, add any ReRoutes, add any AggregateReRoutes and if the file is called ocelot.global.json add the GlobalConfiguration aswell as any ReRoutes or AggregateReRoutes. Ocelot will then save the merged configuration to a file called ocelot.json and this will be used as the source of truth while ocelot is running.
You can also give Ocelot a specific path to look in for the configuration files like below. At the moment there is no validation at this stage it only happens when Ocelot validates the final merged configuration. This is something to be aware of when you are investigating problems. I would advise always checking what is in ocelot.json if you have any problems.
.. code-block:: csharp You can also give Ocelot a specific path to look in for the configuration files like below.
.ConfigureAppConfiguration((hostingContext, config) => .. code-block:: csharp
{
config .ConfigureAppConfiguration((hostingContext, config) =>
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) {
.AddJsonFile("appsettings.json", true, true) config
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
.AddOcelot("/foo/bar", hostingContext.HostingEnvironment) .AddJsonFile("appsettings.json", true, true)
.AddEnvironmentVariables(); .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
}) .AddOcelot("/foo/bar", hostingContext.HostingEnvironment)
.AddEnvironmentVariables();
Ocelot needs the HostingEnvironment so it knows to exclude anything environment specific from the algorithm. })
Store configuration in consul Ocelot needs the HostingEnvironment so it knows to exclude anything environment specific from the algorithm.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Store configuration in consul
The first thing you need to do is install the NuGet package that provides Consul support in Ocelot. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
``Install-Package Ocelot.Provider.Consul`` The first thing you need to do is install the NuGet package that provides Consul support in Ocelot.
Then you add the following when you register your services Ocelot will attempt to store and retrieve its configuration in consul KV store. ``Install-Package Ocelot.Provider.Consul``
.. code-block:: csharp Then you add the following when you register your services Ocelot will attempt to store and retrieve its configuration in consul KV store.
services .. code-block:: csharp
.AddOcelot()
.AddConsul() services
.AddConfigStoredInConsul(); .AddOcelot()
.AddConsul()
You also need to add the following to your ocelot.json. This is how Ocelot .AddConfigStoredInConsul();
finds your Consul agent and interacts to load and store the configuration from Consul.
You also need to add the following to your ocelot.json. This is how Ocelot
.. code-block:: json finds your Consul agent and interacts to load and store the configuration from Consul.
"GlobalConfiguration": { .. code-block:: json
"ServiceDiscoveryProvider": {
"Host": "localhost", "GlobalConfiguration": {
"Port": 9500 "ServiceDiscoveryProvider": {
} "Host": "localhost",
} "Port": 9500
}
I decided to create this feature after working on the Raft consensus algorithm and finding out its super hard. Why not take advantage of the fact Consul already gives you this! }
I guess it means if you want to use Ocelot to its fullest you take on Consul as a dependency for now.
I decided to create this feature after working on the Raft consensus algorithm and finding out its super hard. Why not take advantage of the fact Consul already gives you this!
This feature has a 3 second ttl cache before making a new request to your local consul agent. I guess it means if you want to use Ocelot to its fullest you take on Consul as a dependency for now.
Reload JSON config on change This feature has a 3 second ttl cache before making a new request to your local consul agent.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Reload JSON config on change
Ocelot supports reloading the json configuration file on change. e.g. the following will recreate Ocelots internal configuration when the ocelot.json file is updated ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
manually.
Ocelot supports reloading the json configuration file on change. e.g. the following will recreate Ocelots internal configuration when the ocelot.json file is updated
.. code-block:: json manually.
config.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true); .. code-block:: json
Configuration Key config.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true);
-----------------
Configuration Key
If you are using Consul for configuration (or other providers in the future) you might want to key your configurations so you can have multiple configurations :) This feature was requested in `issue 346 <https://github.com/ThreeMammals/Ocelot/issues/346>`_! In order to specify the key you need to set the ConfigurationKey property in the ServiceDiscoveryProvider section of the configuration json file e.g. -----------------
.. code-block:: json If you are using Consul for configuration (or other providers in the future) you might want to key your configurations so you can have multiple configurations :) This feature was requested in `issue 346 <https://github.com/ThreeMammals/Ocelot/issues/346>`_! In order to specify the key you need to set the ConfigurationKey property in the ServiceDiscoveryProvider section of the configuration json file e.g.
"GlobalConfiguration": { .. code-block:: json
"ServiceDiscoveryProvider": {
"Host": "localhost", "GlobalConfiguration": {
"Port": 9500, "ServiceDiscoveryProvider": {
"ConfigurationKey": "Oceolot_A" "Host": "localhost",
} "Port": 9500,
} "ConfigurationKey": "Oceolot_A"
}
In this example Ocelot will use Oceolot_A as the key for your configuration when looking it up in Consul. }
If you do not set the ConfigurationKey Ocelot will use the string InternalConfiguration as the key. In this example Ocelot will use Oceolot_A as the key for your configuration when looking it up in Consul.
Follow Redirects / Use CookieContainer If you do not set the ConfigurationKey Ocelot will use the string InternalConfiguration as the key.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Follow Redirects / Use CookieContainer
Use HttpHandlerOptions in ReRoute configuration to set up HttpHandler behavior: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1. AllowAutoRedirect is a value that indicates whether the request should follow redirection responses. Set it true if the request should automatically Use HttpHandlerOptions in ReRoute configuration to set up HttpHandler behavior:
follow redirection responses from the Downstream resource; otherwise false. The default value is false.
1. AllowAutoRedirect is a value that indicates whether the request should follow redirection responses. Set it true if the request should automatically
2. UseCookieContainer is a value that indicates whether the handler uses the CookieContainer follow redirection responses from the Downstream resource; otherwise false. The default value is false.
property to store server cookies and uses these cookies when sending requests. The default value is false. Please note
that if you are using the CookieContainer Ocelot caches the HttpClient for each downstream service. This means that all requests 2. UseCookieContainer is a value that indicates whether the handler uses the CookieContainer
to that DownstreamService will share the same cookies. `Issue 274 <https://github.com/ThreeMammals/Ocelot/issues/274>`_ was created because a user property to store server cookies and uses these cookies when sending requests. The default value is false. Please note
noticed that the cookies were being shared. I tried to think of a nice way to handle this but I think it is impossible. If you don't cache the clients that if you are using the CookieContainer Ocelot caches the HttpClient for each downstream service. This means that all requests
that means each request gets a new client and therefore a new cookie container. If you clear the cookies from the cached client container you get race conditions due to inflight to that DownstreamService will share the same cookies. `Issue 274 <https://github.com/ThreeMammals/Ocelot/issues/274>`_ was created because a user
requests. This would also mean that subsequent requests don't use the cookies from the previous response! All in all not a great situation. I would avoid setting noticed that the cookies were being shared. I tried to think of a nice way to handle this but I think it is impossible. If you don't cache the clients
UseCookieContainer to true unless you have a really really good reason. Just look at your response headers and forward the cookies back with your next request! that means each request gets a new client and therefore a new cookie container. If you clear the cookies from the cached client container you get race conditions due to inflight
requests. This would also mean that subsequent requests don't use the cookies from the previous response! All in all not a great situation. I would avoid setting
SSL Errors UseCookieContainer to true unless you have a really really good reason. Just look at your response headers and forward the cookies back with your next request!
^^^^^^^^^^
SSL Errors
If you want to ignore SSL warnings / errors set the following in your ReRoute config. ^^^^^^^^^^
.. code-block:: json If you want to ignore SSL warnings / errors set the following in your ReRoute config.
"DangerousAcceptAnyServerCertificateValidator": true .. code-block:: json
I don't recommend doing this, I suggest creating your own certificate and then getting it trusted by your local / remote machine if you can. "DangerousAcceptAnyServerCertificateValidator": true
MaxConnectionsPerServer I don't recommend doing this, I suggest creating your own certificate and then getting it trusted by your local / remote machine if you can.
^^^^^^^^^^^^^^^^^^^^^^^
MaxConnectionsPerServer
This controls how many connections the internal HttpClient will open. This can be set at ReRoute or global level. ^^^^^^^^^^^^^^^^^^^^^^^
This controls how many connections the internal HttpClient will open. This can be set at ReRoute or global level.
React to Configuration Changes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Resolve IOcelotConfigurationChangeTokenSource from the DI container if you wish to react to changes to the Ocelot configuration via the Ocelot.Administration API or ocelot.json being reloaded from the disk. You may either poll the change token's HasChanged property, or register a callback with the RegisterChangeCallback method.
Polling the HasChanged property
-------------------------------
.. code-block:: csharp
public class ConfigurationNotifyingService : BackgroundService
{
private readonly IOcelotConfigurationChangeTokenSource _tokenSource;
private readonly ILogger _logger;
public ConfigurationNotifyingService(IOcelotConfigurationChangeTokenSource tokenSource, ILogger logger)
{
_tokenSource = tokenSource;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
if (_tokenSource.ChangeToken.HasChanged)
{
_logger.LogInformation("Configuration updated");
}
await Task.Delay(1000, stoppingToken);
}
}
}
Registering a callback
----------------------
.. code-block:: csharp
public class MyDependencyInjectedClass : IDisposable
{
private readonly IOcelotConfigurationChangeTokenSource _tokenSource;
private readonly IDisposable _callbackHolder;
public MyClass(IOcelotConfigurationChangeTokenSource tokenSource)
{
_tokenSource = tokenSource;
_callbackHolder = tokenSource.ChangeToken.RegisterChangeCallback(_ => Console.WriteLine("Configuration changed"), null);
}
public void Dispose()
{
_callbackHolder.Dispose();
}
}
DownstreamHttpVersion
---------------------
Ocelot allows you to choose the HTTP version it will use to make the proxy request. It can be set as "1.0", "1.1" or "2.0".

View File

@ -14,7 +14,7 @@ Then add the following to your ConfigureServices method.
s.AddOcelot() s.AddOcelot()
.AddKubernetes(); .AddKubernetes();
If you have services deployed in kubernetes you will normally use the naming service to access them. Default usePodServiceAccount = True, which means that ServiceAccount using Pod to access the service of the k8s cluster needs to be ServiceAccount based on RABC authorization If you have services deployed in kubernetes you will normally use the naming service to access them. Default usePodServiceAccount = True, which means that ServiceAccount using Pod to access the service of the k8s cluster needs to be ServiceAccount based on RBAC authorization
.. code-block::csharp .. code-block::csharp
public static class OcelotBuilderExtensions public static class OcelotBuilderExtensions

View File

@ -0,0 +1,28 @@
HTTP Method Transformation
==========================
Ocelot allows the user to change the HTTP request method that will be used when making a request to a downstream service.
This achieved by setting the following ReRoute configuration:
.. code-block:: json
{
"DownstreamPathTemplate": "/{url}",
"UpstreamPathTemplate": "/{url}",
"UpstreamHttpMethod": [
"Get"
],
"DownstreamHttpMethod": "POST",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 53271
}
],
}
The key property here is DownstreamHttpMethod which is set as POST and the ReRoute will only match on GET as set by UpstreamHttpMethod.
This feature can be useful when interacting with downstream apis that only support POST and you want to present some kind of RESTful interface.

View File

@ -23,7 +23,7 @@ Thanks for taking a look at the Ocelot documentation. Please use the left hand n
features/requestaggregation features/requestaggregation
features/graphql features/graphql
features/servicediscovery features/servicediscovery
features/servicefabric features/servicefabric
features/kubernetes features/kubernetes
features/authentication features/authentication
features/authorisation features/authorisation
@ -33,6 +33,7 @@ Thanks for taking a look at the Ocelot documentation. Please use the left hand n
features/caching features/caching
features/qualityofservice features/qualityofservice
features/headerstransformation features/headerstransformation
features/methodtransformation
features/claimstransformation features/claimstransformation
features/logging features/logging
features/tracing features/tracing

View File

@ -1,13 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Folder Include="wwwroot\"/> <Folder Include="wwwroot\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App"/> <PackageReference Include="Ocelot" Version="14.0.3" />
<PackageReference Include="Ocelot" Version="12.0.1"/> <PackageReference Include="Ocelot.Administration" Version="14.0.3" />
<PackageReference Include="Ocelot.Administration" Version="0.1.0"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -1,19 +1,14 @@
using System; using Microsoft.AspNetCore.Hosting;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Ocelot.Administration;
using Ocelot.DependencyInjection; using Ocelot.DependencyInjection;
using Ocelot.Middleware; using Ocelot.Middleware;
using Ocelot.Administration; using System.IO;
namespace AdministrationApi namespace AdministrationApi
{ {
public class Program public class Program
{ {
public static void Main(string[] args) public static void Main(string[] args)
{ {

View File

@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Ocelot.DependencyInjection; using Ocelot.DependencyInjection;
using Ocelot.Middleware; using Ocelot.Middleware;
using Ocelot.Provider.Kubernetes; using Ocelot.Provider.Kubernetes;
@ -18,7 +19,7 @@ namespace ApiGateway
} }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env) public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{ {
if (env.IsDevelopment()) if (env.IsDevelopment())
{ {

View File

@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@ -28,7 +29,7 @@ namespace DownstreamService
} }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env) public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{ {
if (env.IsDevelopment()) if (env.IsDevelopment())
{ {

View File

@ -27,7 +27,13 @@
} }
app.UseAuthentication(); app.UseAuthentication();
app.UseMvc(); app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
endpoints.MapControllers();
});
}); });
} }

View File

@ -1,5 +1,6 @@
using Ocelot.Configuration.Creator; using Ocelot.Configuration.Creator;
using Ocelot.Values; using Ocelot.Values;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
@ -41,6 +42,8 @@ namespace Ocelot.Configuration.Builder
private List<AddHeader> _addHeadersToUpstream; private List<AddHeader> _addHeadersToUpstream;
private bool _dangerousAcceptAnyServerCertificateValidator; private bool _dangerousAcceptAnyServerCertificateValidator;
private SecurityOptions _securityOptions; private SecurityOptions _securityOptions;
private string _downstreamHttpMethod;
private Version _downstreamHttpVersion;
public DownstreamReRouteBuilder() public DownstreamReRouteBuilder()
{ {
@ -56,6 +59,12 @@ namespace Ocelot.Configuration.Builder
return this; return this;
} }
public DownstreamReRouteBuilder WithDownStreamHttpMethod(string method)
{
_downstreamHttpMethod = method;
return this;
}
public DownstreamReRouteBuilder WithLoadBalancerOptions(LoadBalancerOptions loadBalancerOptions) public DownstreamReRouteBuilder WithLoadBalancerOptions(LoadBalancerOptions loadBalancerOptions)
{ {
_loadBalancerOptions = loadBalancerOptions; _loadBalancerOptions = loadBalancerOptions;
@ -248,6 +257,12 @@ namespace Ocelot.Configuration.Builder
return this; return this;
} }
public DownstreamReRouteBuilder WithDownstreamHttpVersion(Version downstreamHttpVersion)
{
_downstreamHttpVersion = downstreamHttpVersion;
return this;
}
public DownstreamReRoute Build() public DownstreamReRoute Build()
{ {
return new DownstreamReRoute( return new DownstreamReRoute(
@ -282,7 +297,9 @@ namespace Ocelot.Configuration.Builder
_addHeadersToDownstream, _addHeadersToDownstream,
_addHeadersToUpstream, _addHeadersToUpstream,
_dangerousAcceptAnyServerCertificateValidator, _dangerousAcceptAnyServerCertificateValidator,
_securityOptions); _securityOptions,
_downstreamHttpMethod,
_downstreamHttpVersion);
} }
} }
} }

View File

@ -0,0 +1,14 @@
namespace Ocelot.Configuration.ChangeTracking
{
using Microsoft.Extensions.Primitives;
/// <summary>
/// <see cref="IChangeToken" /> source which is activated when Ocelot's configuration is changed.
/// </summary>
public interface IOcelotConfigurationChangeTokenSource
{
IChangeToken ChangeToken { get; }
void Activate();
}
}

View File

@ -0,0 +1,74 @@
namespace Ocelot.Configuration.ChangeTracking
{
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Primitives;
public class OcelotConfigurationChangeToken : IChangeToken
{
public const double PollingIntervalSeconds = 1;
private readonly ICollection<CallbackWrapper> _callbacks = new List<CallbackWrapper>();
private readonly object _lock = new object();
private DateTime? _timeChanged;
public IDisposable RegisterChangeCallback(Action<object> callback, object state)
{
lock (_lock)
{
var wrapper = new CallbackWrapper(callback, state, _callbacks, _lock);
_callbacks.Add(wrapper);
return wrapper;
}
}
public void Activate()
{
lock (_lock)
{
_timeChanged = DateTime.UtcNow;
foreach (var wrapper in _callbacks)
{
wrapper.Invoke();
}
}
}
// Token stays active for PollingIntervalSeconds after a change (could be parameterised) - otherwise HasChanged would be true forever.
// Taking suggestions for better ways to reset HasChanged back to false.
public bool HasChanged => _timeChanged.HasValue && (DateTime.UtcNow - _timeChanged.Value).TotalSeconds < PollingIntervalSeconds;
public bool ActiveChangeCallbacks => true;
private class CallbackWrapper : IDisposable
{
private readonly ICollection<CallbackWrapper> _callbacks;
private readonly object _lock;
public CallbackWrapper(Action<object> callback, object state, ICollection<CallbackWrapper> callbacks, object @lock)
{
_callbacks = callbacks;
_lock = @lock;
Callback = callback;
State = state;
}
public void Invoke()
{
Callback.Invoke(State);
}
public void Dispose()
{
lock (_lock)
{
_callbacks.Remove(this);
}
}
public Action<object> Callback { get; }
public object State { get; }
}
}
}

View File

@ -0,0 +1,16 @@
namespace Ocelot.Configuration.ChangeTracking
{
using Microsoft.Extensions.Primitives;
public class OcelotConfigurationChangeTokenSource : IOcelotConfigurationChangeTokenSource
{
private readonly OcelotConfigurationChangeToken _changeToken = new OcelotConfigurationChangeToken();
public IChangeToken ChangeToken => _changeToken;
public void Activate()
{
_changeToken.Activate();
}
}
}

View File

@ -0,0 +1,30 @@
namespace Ocelot.Configuration.ChangeTracking
{
using System;
using Microsoft.Extensions.Options;
using Ocelot.Configuration.Repository;
public class OcelotConfigurationMonitor : IOptionsMonitor<IInternalConfiguration>
{
private readonly IOcelotConfigurationChangeTokenSource _changeTokenSource;
private readonly IInternalConfigurationRepository _repo;
public OcelotConfigurationMonitor(IInternalConfigurationRepository repo, IOcelotConfigurationChangeTokenSource changeTokenSource)
{
_changeTokenSource = changeTokenSource;
_repo = repo;
}
public IInternalConfiguration Get(string name)
{
return _repo.Get().Data;
}
public IDisposable OnChange(Action<IInternalConfiguration, string> listener)
{
return _changeTokenSource.ChangeToken.RegisterChangeCallback(_ => listener(CurrentValue, ""), null);
}
public IInternalConfiguration CurrentValue => _repo.Get().Data;
}
}

View File

@ -13,13 +13,15 @@ namespace Ocelot.Configuration.Creator
private readonly IHttpHandlerOptionsCreator _httpHandlerOptionsCreator; private readonly IHttpHandlerOptionsCreator _httpHandlerOptionsCreator;
private readonly IAdministrationPath _adminPath; private readonly IAdministrationPath _adminPath;
private readonly ILoadBalancerOptionsCreator _loadBalancerOptionsCreator; private readonly ILoadBalancerOptionsCreator _loadBalancerOptionsCreator;
private readonly IVersionCreator _versionCreator;
public ConfigurationCreator( public ConfigurationCreator(
IServiceProviderConfigurationCreator serviceProviderConfigCreator, IServiceProviderConfigurationCreator serviceProviderConfigCreator,
IQoSOptionsCreator qosOptionsCreator, IQoSOptionsCreator qosOptionsCreator,
IHttpHandlerOptionsCreator httpHandlerOptionsCreator, IHttpHandlerOptionsCreator httpHandlerOptionsCreator,
IServiceProvider serviceProvider, IServiceProvider serviceProvider,
ILoadBalancerOptionsCreator loadBalancerOptionsCreator ILoadBalancerOptionsCreator loadBalancerOptionsCreator,
IVersionCreator versionCreator
) )
{ {
_adminPath = serviceProvider.GetService<IAdministrationPath>(); _adminPath = serviceProvider.GetService<IAdministrationPath>();
@ -27,6 +29,7 @@ namespace Ocelot.Configuration.Creator
_serviceProviderConfigCreator = serviceProviderConfigCreator; _serviceProviderConfigCreator = serviceProviderConfigCreator;
_qosOptionsCreator = qosOptionsCreator; _qosOptionsCreator = qosOptionsCreator;
_httpHandlerOptionsCreator = httpHandlerOptionsCreator; _httpHandlerOptionsCreator = httpHandlerOptionsCreator;
_versionCreator = versionCreator;
} }
public InternalConfiguration Create(FileConfiguration fileConfiguration, List<ReRoute> reRoutes) public InternalConfiguration Create(FileConfiguration fileConfiguration, List<ReRoute> reRoutes)
@ -41,6 +44,8 @@ namespace Ocelot.Configuration.Creator
var adminPath = _adminPath != null ? _adminPath.Path : null; var adminPath = _adminPath != null ? _adminPath.Path : null;
var version = _versionCreator.Create(fileConfiguration.GlobalConfiguration.DownstreamHttpVersion);
return new InternalConfiguration(reRoutes, return new InternalConfiguration(reRoutes,
adminPath, adminPath,
serviceProviderConfiguration, serviceProviderConfiguration,
@ -48,7 +53,8 @@ namespace Ocelot.Configuration.Creator
lbOptions, lbOptions,
fileConfiguration.GlobalConfiguration.DownstreamScheme, fileConfiguration.GlobalConfiguration.DownstreamScheme,
qosOptions, qosOptions,
httpHandlerOptions httpHandlerOptions,
version
); );
} }
} }

View File

@ -8,10 +8,12 @@ namespace Ocelot.Configuration.Creator
public class DynamicsCreator : IDynamicsCreator public class DynamicsCreator : IDynamicsCreator
{ {
private readonly IRateLimitOptionsCreator _rateLimitOptionsCreator; private readonly IRateLimitOptionsCreator _rateLimitOptionsCreator;
private readonly IVersionCreator _versionCreator;
public DynamicsCreator(IRateLimitOptionsCreator rateLimitOptionsCreator) public DynamicsCreator(IRateLimitOptionsCreator rateLimitOptionsCreator, IVersionCreator versionCreator)
{ {
_rateLimitOptionsCreator = rateLimitOptionsCreator; _rateLimitOptionsCreator = rateLimitOptionsCreator;
_versionCreator = versionCreator;
} }
public List<ReRoute> Create(FileConfiguration fileConfiguration) public List<ReRoute> Create(FileConfiguration fileConfiguration)
@ -26,10 +28,13 @@ namespace Ocelot.Configuration.Creator
var rateLimitOption = _rateLimitOptionsCreator var rateLimitOption = _rateLimitOptionsCreator
.Create(fileDynamicReRoute.RateLimitRule, globalConfiguration); .Create(fileDynamicReRoute.RateLimitRule, globalConfiguration);
var version = _versionCreator.Create(fileDynamicReRoute.DownstreamHttpVersion);
var downstreamReRoute = new DownstreamReRouteBuilder() var downstreamReRoute = new DownstreamReRouteBuilder()
.WithEnableRateLimiting(rateLimitOption.EnableRateLimiting) .WithEnableRateLimiting(rateLimitOption.EnableRateLimiting)
.WithRateLimitOptions(rateLimitOption) .WithRateLimitOptions(rateLimitOption)
.WithServiceName(fileDynamicReRoute.ServiceName) .WithServiceName(fileDynamicReRoute.ServiceName)
.WithDownstreamHttpVersion(version)
.Build(); .Build();
var reRoute = new ReRouteBuilder() var reRoute = new ReRouteBuilder()

View File

@ -0,0 +1,17 @@
namespace Ocelot.Configuration.Creator
{
using System;
public class HttpVersionCreator : IVersionCreator
{
public Version Create(string downstreamHttpVersion)
{
if (!Version.TryParse(downstreamHttpVersion, out Version version))
{
version = new Version(1, 1);
}
return version;
}
}
}

View File

@ -0,0 +1,9 @@
namespace Ocelot.Configuration.Creator
{
using System;
public interface IVersionCreator
{
Version Create(string downstreamHttpVersion);
}
}

View File

@ -22,6 +22,7 @@ namespace Ocelot.Configuration.Creator
private readonly IDownstreamAddressesCreator _downstreamAddressesCreator; private readonly IDownstreamAddressesCreator _downstreamAddressesCreator;
private readonly IReRouteKeyCreator _reRouteKeyCreator; private readonly IReRouteKeyCreator _reRouteKeyCreator;
private readonly ISecurityOptionsCreator _securityOptionsCreator; private readonly ISecurityOptionsCreator _securityOptionsCreator;
private readonly IVersionCreator _versionCreator;
public ReRoutesCreator( public ReRoutesCreator(
IClaimsToThingCreator claimsToThingCreator, IClaimsToThingCreator claimsToThingCreator,
@ -37,7 +38,8 @@ namespace Ocelot.Configuration.Creator
IDownstreamAddressesCreator downstreamAddressesCreator, IDownstreamAddressesCreator downstreamAddressesCreator,
ILoadBalancerOptionsCreator loadBalancerOptionsCreator, ILoadBalancerOptionsCreator loadBalancerOptionsCreator,
IReRouteKeyCreator reRouteKeyCreator, IReRouteKeyCreator reRouteKeyCreator,
ISecurityOptionsCreator securityOptionsCreator ISecurityOptionsCreator securityOptionsCreator,
IVersionCreator versionCreator
) )
{ {
_reRouteKeyCreator = reRouteKeyCreator; _reRouteKeyCreator = reRouteKeyCreator;
@ -55,6 +57,7 @@ namespace Ocelot.Configuration.Creator
_httpHandlerOptionsCreator = httpHandlerOptionsCreator; _httpHandlerOptionsCreator = httpHandlerOptionsCreator;
_loadBalancerOptionsCreator = loadBalancerOptionsCreator; _loadBalancerOptionsCreator = loadBalancerOptionsCreator;
_securityOptionsCreator = securityOptionsCreator; _securityOptionsCreator = securityOptionsCreator;
_versionCreator = versionCreator;
} }
public List<ReRoute> Create(FileConfiguration fileConfiguration) public List<ReRoute> Create(FileConfiguration fileConfiguration)
@ -104,6 +107,8 @@ namespace Ocelot.Configuration.Creator
var securityOptions = _securityOptionsCreator.Create(fileReRoute.SecurityOptions); var securityOptions = _securityOptionsCreator.Create(fileReRoute.SecurityOptions);
var downstreamHttpVersion = _versionCreator.Create(fileReRoute.DownstreamHttpVersion);
var reRoute = new DownstreamReRouteBuilder() var reRoute = new DownstreamReRouteBuilder()
.WithKey(fileReRoute.Key) .WithKey(fileReRoute.Key)
.WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate) .WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate)
@ -138,6 +143,8 @@ namespace Ocelot.Configuration.Creator
.WithAddHeadersToUpstream(hAndRs.AddHeadersToUpstream) .WithAddHeadersToUpstream(hAndRs.AddHeadersToUpstream)
.WithDangerousAcceptAnyServerCertificateValidator(fileReRoute.DangerousAcceptAnyServerCertificateValidator) .WithDangerousAcceptAnyServerCertificateValidator(fileReRoute.DangerousAcceptAnyServerCertificateValidator)
.WithSecurityOptions(securityOptions) .WithSecurityOptions(securityOptions)
.WithDownstreamHttpVersion(downstreamHttpVersion)
.WithDownStreamHttpMethod(fileReRoute.DownstreamHttpMethod)
.Build(); .Build();
return reRoute; return reRoute;

View File

@ -1,6 +1,7 @@
namespace Ocelot.Configuration namespace Ocelot.Configuration
{ {
using Creator; using Creator;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using Values; using Values;
@ -38,7 +39,9 @@ namespace Ocelot.Configuration
List<AddHeader> addHeadersToDownstream, List<AddHeader> addHeadersToDownstream,
List<AddHeader> addHeadersToUpstream, List<AddHeader> addHeadersToUpstream,
bool dangerousAcceptAnyServerCertificateValidator, bool dangerousAcceptAnyServerCertificateValidator,
SecurityOptions securityOptions) SecurityOptions securityOptions,
string downstreamHttpMethod,
Version downstreamHttpVersion)
{ {
DangerousAcceptAnyServerCertificateValidator = dangerousAcceptAnyServerCertificateValidator; DangerousAcceptAnyServerCertificateValidator = dangerousAcceptAnyServerCertificateValidator;
AddHeadersToDownstream = addHeadersToDownstream; AddHeadersToDownstream = addHeadersToDownstream;
@ -72,6 +75,8 @@ namespace Ocelot.Configuration
LoadBalancerKey = loadBalancerKey; LoadBalancerKey = loadBalancerKey;
AddHeadersToUpstream = addHeadersToUpstream; AddHeadersToUpstream = addHeadersToUpstream;
SecurityOptions = securityOptions; SecurityOptions = securityOptions;
DownstreamHttpMethod = downstreamHttpMethod;
DownstreamHttpVersion = downstreamHttpVersion;
} }
public string Key { get; } public string Key { get; }
@ -106,5 +111,7 @@ namespace Ocelot.Configuration
public List<AddHeader> AddHeadersToUpstream { get; } public List<AddHeader> AddHeadersToUpstream { get; }
public bool DangerousAcceptAnyServerCertificateValidator { get; } public bool DangerousAcceptAnyServerCertificateValidator { get; }
public SecurityOptions SecurityOptions { get; } public SecurityOptions SecurityOptions { get; }
public string DownstreamHttpMethod { get; }
public Version DownstreamHttpVersion { get; }
} }
} }

View File

@ -4,5 +4,6 @@ namespace Ocelot.Configuration.File
{ {
public string ServiceName { get; set; } public string ServiceName { get; set; }
public FileRateLimitRule RateLimitRule { get; set; } public FileRateLimitRule RateLimitRule { get; set; }
public string DownstreamHttpVersion { get; set; }
} }
} }

View File

@ -26,5 +26,7 @@
public string DownstreamScheme { get; set; } public string DownstreamScheme { get; set; }
public FileHttpHandlerOptions HttpHandlerOptions { get; set; } public FileHttpHandlerOptions HttpHandlerOptions { get; set; }
public string DownstreamHttpVersion { get; set; }
} }
} }

View File

@ -29,6 +29,7 @@ namespace Ocelot.Configuration.File
public string DownstreamPathTemplate { get; set; } public string DownstreamPathTemplate { get; set; }
public string UpstreamPathTemplate { get; set; } public string UpstreamPathTemplate { get; set; }
public List<string> UpstreamHttpMethod { get; set; } public List<string> UpstreamHttpMethod { get; set; }
public string DownstreamHttpMethod { get; set; }
public Dictionary<string, string> AddHeadersToRequest { get; set; } public Dictionary<string, string> AddHeadersToRequest { get; set; }
public Dictionary<string, string> UpstreamHeaderTransform { get; set; } public Dictionary<string, string> UpstreamHeaderTransform { get; set; }
public Dictionary<string, string> DownstreamHeaderTransform { get; set; } public Dictionary<string, string> DownstreamHeaderTransform { get; set; }
@ -55,5 +56,6 @@ namespace Ocelot.Configuration.File
public int Timeout { get; set; } public int Timeout { get; set; }
public bool DangerousAcceptAnyServerCertificateValidator { get; set; } public bool DangerousAcceptAnyServerCertificateValidator { get; set; }
public FileSecurityOptions SecurityOptions { get; set; } public FileSecurityOptions SecurityOptions { get; set; }
public string DownstreamHttpVersion { get; set; }
} }
} }

View File

@ -2,6 +2,8 @@ using System.Collections.Generic;
namespace Ocelot.Configuration namespace Ocelot.Configuration
{ {
using System;
public interface IInternalConfiguration public interface IInternalConfiguration
{ {
List<ReRoute> ReRoutes { get; } List<ReRoute> ReRoutes { get; }
@ -19,5 +21,7 @@ namespace Ocelot.Configuration
QoSOptions QoSOptions { get; } QoSOptions QoSOptions { get; }
HttpHandlerOptions HttpHandlerOptions { get; } HttpHandlerOptions HttpHandlerOptions { get; }
Version DownstreamHttpVersion { get; }
} }
} }

View File

@ -2,6 +2,8 @@ using System.Collections.Generic;
namespace Ocelot.Configuration namespace Ocelot.Configuration
{ {
using System;
public class InternalConfiguration : IInternalConfiguration public class InternalConfiguration : IInternalConfiguration
{ {
public InternalConfiguration( public InternalConfiguration(
@ -12,7 +14,8 @@ namespace Ocelot.Configuration
LoadBalancerOptions loadBalancerOptions, LoadBalancerOptions loadBalancerOptions,
string downstreamScheme, string downstreamScheme,
QoSOptions qoSOptions, QoSOptions qoSOptions,
HttpHandlerOptions httpHandlerOptions) HttpHandlerOptions httpHandlerOptions,
Version downstreamHttpVersion)
{ {
ReRoutes = reRoutes; ReRoutes = reRoutes;
AdministrationPath = administrationPath; AdministrationPath = administrationPath;
@ -22,6 +25,7 @@ namespace Ocelot.Configuration
DownstreamScheme = downstreamScheme; DownstreamScheme = downstreamScheme;
QoSOptions = qoSOptions; QoSOptions = qoSOptions;
HttpHandlerOptions = httpHandlerOptions; HttpHandlerOptions = httpHandlerOptions;
DownstreamHttpVersion = downstreamHttpVersion;
} }
public List<ReRoute> ReRoutes { get; } public List<ReRoute> ReRoutes { get; }
@ -32,5 +36,7 @@ namespace Ocelot.Configuration
public string DownstreamScheme { get; } public string DownstreamScheme { get; }
public QoSOptions QoSOptions { get; } public QoSOptions QoSOptions { get; }
public HttpHandlerOptions HttpHandlerOptions { get; } public HttpHandlerOptions HttpHandlerOptions { get; }
public Version DownstreamHttpVersion { get; }
} }
} }

View File

@ -4,18 +4,21 @@ using Ocelot.Configuration.File;
using Ocelot.Responses; using Ocelot.Responses;
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ocelot.Configuration.ChangeTracking;
namespace Ocelot.Configuration.Repository namespace Ocelot.Configuration.Repository
{ {
public class DiskFileConfigurationRepository : IFileConfigurationRepository public class DiskFileConfigurationRepository : IFileConfigurationRepository
{ {
private readonly IOcelotConfigurationChangeTokenSource _changeTokenSource;
private readonly string _environmentFilePath; private readonly string _environmentFilePath;
private readonly string _ocelotFilePath; private readonly string _ocelotFilePath;
private static readonly object _lock = new object(); private static readonly object _lock = new object();
private const string ConfigurationFileName = "ocelot"; private const string ConfigurationFileName = "ocelot";
public DiskFileConfigurationRepository(IWebHostEnvironment hostingEnvironment) public DiskFileConfigurationRepository(IWebHostEnvironment hostingEnvironment, IOcelotConfigurationChangeTokenSource changeTokenSource)
{ {
_changeTokenSource = changeTokenSource;
_environmentFilePath = $"{AppContext.BaseDirectory}{ConfigurationFileName}{(string.IsNullOrEmpty(hostingEnvironment.EnvironmentName) ? string.Empty : ".")}{hostingEnvironment.EnvironmentName}.json"; _environmentFilePath = $"{AppContext.BaseDirectory}{ConfigurationFileName}{(string.IsNullOrEmpty(hostingEnvironment.EnvironmentName) ? string.Empty : ".")}{hostingEnvironment.EnvironmentName}.json";
_ocelotFilePath = $"{AppContext.BaseDirectory}{ConfigurationFileName}.json"; _ocelotFilePath = $"{AppContext.BaseDirectory}{ConfigurationFileName}.json";
@ -56,6 +59,7 @@ namespace Ocelot.Configuration.Repository
System.IO.File.WriteAllText(_ocelotFilePath, jsonConfiguration); System.IO.File.WriteAllText(_ocelotFilePath, jsonConfiguration);
} }
_changeTokenSource.Activate();
return Task.FromResult<Response>(new OkResponse()); return Task.FromResult<Response>(new OkResponse());
} }
} }

View File

@ -1,4 +1,5 @@
using Ocelot.Responses; using Ocelot.Configuration.ChangeTracking;
using Ocelot.Responses;
namespace Ocelot.Configuration.Repository namespace Ocelot.Configuration.Repository
{ {
@ -10,6 +11,12 @@ namespace Ocelot.Configuration.Repository
private static readonly object LockObject = new object(); private static readonly object LockObject = new object();
private IInternalConfiguration _internalConfiguration; private IInternalConfiguration _internalConfiguration;
private readonly IOcelotConfigurationChangeTokenSource _changeTokenSource;
public InMemoryInternalConfigurationRepository(IOcelotConfigurationChangeTokenSource changeTokenSource)
{
_changeTokenSource = changeTokenSource;
}
public Response<IInternalConfiguration> Get() public Response<IInternalConfiguration> Get()
{ {
@ -23,6 +30,7 @@ namespace Ocelot.Configuration.Repository
_internalConfiguration = internalConfiguration; _internalConfiguration = internalConfiguration;
} }
_changeTokenSource.Activate();
return new OkResponse(); return new OkResponse();
} }
} }

View File

@ -83,6 +83,11 @@
RuleForEach(reRoute => reRoute.DownstreamHostAndPorts) RuleForEach(reRoute => reRoute.DownstreamHostAndPorts)
.SetValidator(hostAndPortValidator); .SetValidator(hostAndPortValidator);
}); });
When(reRoute => !string.IsNullOrEmpty(reRoute.DownstreamHttpVersion), () =>
{
RuleFor(r => r.DownstreamHttpVersion).Matches("^[0-9]([.,][0-9]{1,1})?$");
});
} }
private async Task<bool> IsSupportedAuthenticationProviders(FileAuthenticationOptions authenticationOptions, CancellationToken cancellationToken) private async Task<bool> IsSupportedAuthenticationProviders(FileAuthenticationOptions authenticationOptions, CancellationToken cancellationToken)

View File

@ -1,9 +1,12 @@
using Ocelot.Configuration.ChangeTracking;
namespace Ocelot.DependencyInjection namespace Ocelot.DependencyInjection
{ {
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using Ocelot.Authorisation; using Ocelot.Authorisation;
using Ocelot.Cache; using Ocelot.Cache;
using Ocelot.Claims; using Ocelot.Claims;
@ -112,6 +115,8 @@ namespace Ocelot.DependencyInjection
Services.TryAddSingleton<IDownstreamAddressesCreator, DownstreamAddressesCreator>(); Services.TryAddSingleton<IDownstreamAddressesCreator, DownstreamAddressesCreator>();
Services.TryAddSingleton<IDelegatingHandlerHandlerFactory, DelegatingHandlerHandlerFactory>(); Services.TryAddSingleton<IDelegatingHandlerHandlerFactory, DelegatingHandlerHandlerFactory>();
Services.TryAddSingleton<ICacheKeyGenerator, CacheKeyGenerator>(); Services.TryAddSingleton<ICacheKeyGenerator, CacheKeyGenerator>();
Services.TryAddSingleton<IOcelotConfigurationChangeTokenSource, OcelotConfigurationChangeTokenSource>();
Services.TryAddSingleton<IOptionsMonitor<IInternalConfiguration>, OcelotConfigurationMonitor>();
// see this for why we register this as singleton http://stackoverflow.com/questions/37371264/invalidoperationexception-unable-to-resolve-service-for-type-microsoft-aspnetc // see this for why we register this as singleton http://stackoverflow.com/questions/37371264/invalidoperationexception-unable-to-resolve-service-for-type-microsoft-aspnetc
// could maybe use a scoped data repository // could maybe use a scoped data repository
@ -131,6 +136,7 @@ namespace Ocelot.DependencyInjection
Services.TryAddSingleton<IFrameworkDescription, FrameworkDescription>(); Services.TryAddSingleton<IFrameworkDescription, FrameworkDescription>();
Services.TryAddSingleton<IQoSFactory, QoSFactory>(); Services.TryAddSingleton<IQoSFactory, QoSFactory>();
Services.TryAddSingleton<IExceptionToErrorMapper, HttpExeptionToErrorMapper>(); Services.TryAddSingleton<IExceptionToErrorMapper, HttpExeptionToErrorMapper>();
Services.TryAddSingleton<IVersionCreator, HttpVersionCreator>();
//add security //add security
this.AddSecurity(); this.AddSecurity();
@ -215,7 +221,7 @@ namespace Ocelot.DependencyInjection
{ {
// see: https://greatrexpectations.com/2018/10/25/decorators-in-net-core-with-dependency-injection // see: https://greatrexpectations.com/2018/10/25/decorators-in-net-core-with-dependency-injection
var wrappedDescriptor = Services.First(x => x.ServiceType == typeof(IPlaceholders)); var wrappedDescriptor = Services.First(x => x.ServiceType == typeof(IPlaceholders));
var objectFactory = ActivatorUtilities.CreateFactory( var objectFactory = ActivatorUtilities.CreateFactory(
typeof(ConfigAwarePlaceholders), typeof(ConfigAwarePlaceholders),
new[] { typeof(IPlaceholders) }); new[] { typeof(IPlaceholders) });
@ -229,7 +235,7 @@ namespace Ocelot.DependencyInjection
return this; return this;
} }
private static object CreateInstance(IServiceProvider services, ServiceDescriptor descriptor) private static object CreateInstance(IServiceProvider services, ServiceDescriptor descriptor)
{ {
if (descriptor.ImplementationInstance != null) if (descriptor.ImplementationInstance != null)

View File

@ -1,5 +1,6 @@
namespace Ocelot.DownstreamRouteFinder.Finder namespace Ocelot.DownstreamRouteFinder.Finder
{ {
using System;
using Configuration; using Configuration;
using Configuration.Builder; using Configuration.Builder;
using Configuration.Creator; using Configuration.Creator;
@ -54,6 +55,7 @@
.WithQosOptions(qosOptions) .WithQosOptions(qosOptions)
.WithDownstreamScheme(configuration.DownstreamScheme) .WithDownstreamScheme(configuration.DownstreamScheme)
.WithLoadBalancerOptions(configuration.LoadBalancerOptions) .WithLoadBalancerOptions(configuration.LoadBalancerOptions)
.WithDownstreamHttpVersion(configuration.DownstreamHttpVersion)
.WithUpstreamPathTemplate(upstreamPathTemplate); .WithUpstreamPathTemplate(upstreamPathTemplate);
var rateLimitOptions = configuration.ReRoutes != null var rateLimitOptions = configuration.ReRoutes != null

View File

@ -15,4 +15,4 @@ using System.Runtime.InteropServices;
[assembly: ComVisible(false)] [assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM // The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("d6df4206-0dba-41d8-884d-c3e08290fdbb")] [assembly: Guid("d6df4206-0dba-41d8-884d-c3e08290fdbb")]

View File

@ -1,12 +1,13 @@
namespace Ocelot.Request.Mapper namespace Ocelot.Request.Mapper
{ {
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Ocelot.Configuration;
using Ocelot.Responses; using Ocelot.Responses;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
public interface IRequestMapper public interface IRequestMapper
{ {
Task<Response<HttpRequestMessage>> Map(HttpRequest request); Task<Response<HttpRequestMessage>> Map(HttpRequest request, DownstreamReRoute downstreamReRoute);
} }
} }

View File

@ -3,6 +3,7 @@
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
using Ocelot.Configuration;
using Ocelot.Responses; using Ocelot.Responses;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -15,15 +16,16 @@
{ {
private readonly string[] _unsupportedHeaders = { "host" }; private readonly string[] _unsupportedHeaders = { "host" };
public async Task<Response<HttpRequestMessage>> Map(HttpRequest request) public async Task<Response<HttpRequestMessage>> Map(HttpRequest request, DownstreamReRoute downstreamReRoute)
{ {
try try
{ {
var requestMessage = new HttpRequestMessage() var requestMessage = new HttpRequestMessage()
{ {
Content = await MapContent(request), Content = await MapContent(request),
Method = MapMethod(request), Method = MapMethod(request, downstreamReRoute),
RequestUri = MapUri(request) RequestUri = MapUri(request),
Version = downstreamReRoute.DownstreamHttpVersion,
}; };
MapHeaders(request, requestMessage); MapHeaders(request, requestMessage);
@ -71,8 +73,13 @@
} }
} }
private HttpMethod MapMethod(HttpRequest request) private HttpMethod MapMethod(HttpRequest request, DownstreamReRoute downstreamReRoute)
{ {
if (!string.IsNullOrEmpty(downstreamReRoute?.DownstreamHttpMethod))
{
return new HttpMethod(downstreamReRoute.DownstreamHttpMethod);
}
return new HttpMethod(request.Method); return new HttpMethod(request.Method);
} }

View File

@ -52,6 +52,7 @@ namespace Ocelot.Request.Middleware
}; };
_request.RequestUri = uriBuilder.Uri; _request.RequestUri = uriBuilder.Uri;
_request.Method = new HttpMethod(Method);
return _request; return _request;
} }

View File

@ -24,7 +24,7 @@ namespace Ocelot.Request.Middleware
public async Task Invoke(DownstreamContext context) public async Task Invoke(DownstreamContext context)
{ {
var downstreamRequest = await _requestMapper.Map(context.HttpContext.Request); var downstreamRequest = await _requestMapper.Map(context.HttpContext.Request, context.DownstreamReRoute);
if (downstreamRequest.IsError) if (downstreamRequest.IsError)
{ {

View File

@ -1,4 +1,3 @@
using Microsoft.AspNetCore.Builder;
using Ocelot.Middleware.Pipeline; using Ocelot.Middleware.Pipeline;
namespace Ocelot.Request.Middleware namespace Ocelot.Request.Middleware

View File

@ -1,5 +1,6 @@
using Ocelot.Configuration.File; using Ocelot.Configuration.File;
using System; using System;
using Ocelot.Configuration.ChangeTracking;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
@ -54,6 +55,33 @@ namespace Ocelot.AcceptanceTests
.BDDfy(); .BDDfy();
} }
[Fact]
public void should_trigger_change_token_on_change()
{
this.Given(x => _steps.GivenThereIsAConfiguration(_initialConfig))
.And(x => _steps.GivenOcelotIsRunningReloadingConfig(true))
.And(x => _steps.GivenIHaveAChangeToken())
.And(x => _steps.GivenThereIsAConfiguration(_anotherConfig))
.And(x => _steps.GivenIWait(MillisecondsToWaitForChangeToken))
.Then(x => _steps.TheChangeTokenShouldBeActive(true))
.BDDfy();
}
[Fact]
public void should_not_trigger_change_token_with_no_change()
{
this.Given(x => _steps.GivenThereIsAConfiguration(_initialConfig))
.And(x => _steps.GivenOcelotIsRunningReloadingConfig(false))
.And(x => _steps.GivenIHaveAChangeToken())
.And(x => _steps.GivenIWait(MillisecondsToWaitForChangeToken)) // Wait for prior activation to expire.
.And(x => _steps.GivenThereIsAConfiguration(_anotherConfig))
.And(x => _steps.GivenIWait(MillisecondsToWaitForChangeToken))
.Then(x => _steps.TheChangeTokenShouldBeActive(false))
.BDDfy();
}
private const int MillisecondsToWaitForChangeToken = (int) (OcelotConfigurationChangeToken.PollingIntervalSeconds*1000) - 100;
public void Dispose() public void Dispose()
{ {
_steps.Dispose(); _steps.Dispose();

View File

@ -0,0 +1,243 @@
namespace Ocelot.AcceptanceTests
{
using Microsoft.AspNetCore.Http;
using Ocelot.Configuration.File;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using TestStack.BDDfy;
using Xunit;
public class HttpTests : IDisposable
{
private readonly Steps _steps;
private readonly ServiceHandler _serviceHandler;
public HttpTests()
{
_serviceHandler = new ServiceHandler();
_steps = new Steps();
}
[Fact]
public void should_return_response_200_when_using_http_one()
{
const int port = 53219;
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/{url}",
DownstreamScheme = "https",
UpstreamPathTemplate = "/{url}",
UpstreamHttpMethod = new List<string> { "Get" },
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = port,
},
},
DownstreamHttpMethod = "POST",
DownstreamHttpVersion = "1.0",
DangerousAcceptAnyServerCertificateValidator = true
},
},
};
this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}/", "/", port, HttpProtocols.Http1))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.BDDfy();
}
[Fact]
public void should_return_response_200_when_using_http_one_point_one()
{
const int port = 53279;
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/{url}",
DownstreamScheme = "https",
UpstreamPathTemplate = "/{url}",
UpstreamHttpMethod = new List<string> { "Get" },
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = port,
},
},
DownstreamHttpMethod = "POST",
DownstreamHttpVersion = "1.1",
DangerousAcceptAnyServerCertificateValidator = true
},
},
};
this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}/", "/", port, HttpProtocols.Http1))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.BDDfy();
}
[Fact]
public void should_return_response_200_when_using_http_two_point_zero()
{
const int port = 53675;
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/{url}",
DownstreamScheme = "https",
UpstreamPathTemplate = "/{url}",
UpstreamHttpMethod = new List<string> { "Get" },
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = port,
},
},
DownstreamHttpMethod = "POST",
DownstreamHttpVersion = "2.0",
DangerousAcceptAnyServerCertificateValidator = true
},
},
};
const string expected = "here is some content";
var httpContent = new StringContent(expected);
this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}/", "/", port, HttpProtocols.Http2))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/", httpContent))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(_ => _steps.ThenTheResponseBodyShouldBe(expected))
.BDDfy();
}
[Fact]
public void should_return_response_500_when_using_http_one_to_talk_to_server_running_http_two()
{
const int port = 53677;
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/{url}",
DownstreamScheme = "https",
UpstreamPathTemplate = "/{url}",
UpstreamHttpMethod = new List<string> { "Get" },
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = port,
},
},
DownstreamHttpMethod = "POST",
DownstreamHttpVersion = "1.1",
DangerousAcceptAnyServerCertificateValidator = true
},
},
};
const string expected = "here is some content";
var httpContent = new StringContent(expected);
this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}/", "/", port, HttpProtocols.Http2))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/", httpContent))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.InternalServerError))
.BDDfy();
}
[Fact]
public void should_return_response_200_when_using_http_two_to_talk_to_server_running_http_one_point_one()
{
const int port = 53679;
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/{url}",
DownstreamScheme = "https",
UpstreamPathTemplate = "/{url}",
UpstreamHttpMethod = new List<string> { "Get" },
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = port,
},
},
DownstreamHttpMethod = "POST",
DownstreamHttpVersion = "2.0",
DangerousAcceptAnyServerCertificateValidator = true
},
},
};
const string expected = "here is some content";
var httpContent = new StringContent(expected);
this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}/", "/", port, HttpProtocols.Http1))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/", httpContent))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(_ => _steps.ThenTheResponseBodyShouldBe(expected))
.BDDfy();
}
private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int port, HttpProtocols protocols)
{
_serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context =>
{
context.Response.StatusCode = 200;
var reader = new StreamReader(context.Request.Body);
var body = await reader.ReadToEndAsync();
await context.Response.WriteAsync(body);
}, port, protocols);
}
public void Dispose()
{
_serviceHandler.Dispose();
_steps.Dispose();
}
}
}

View File

@ -0,0 +1,158 @@
namespace Ocelot.AcceptanceTests
{
using Microsoft.AspNetCore.Http;
using Ocelot.Configuration.File;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using TestStack.BDDfy;
using Xunit;
public class MethodTests : IDisposable
{
private readonly Steps _steps;
private readonly ServiceHandler _serviceHandler;
public MethodTests()
{
_serviceHandler = new ServiceHandler();
_steps = new Steps();
}
[Fact]
public void should_return_response_200_when_get_converted_to_post()
{
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/{url}",
DownstreamScheme = "http",
UpstreamPathTemplate = "/{url}",
UpstreamHttpMethod = new List<string> { "Get" },
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 53171,
},
},
DownstreamHttpMethod = "POST",
},
},
};
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:53171/", "/", "POST"))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.BDDfy();
}
[Fact]
public void should_return_response_200_when_get_converted_to_post_with_content()
{
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/{url}",
DownstreamScheme = "http",
UpstreamPathTemplate = "/{url}",
UpstreamHttpMethod = new List<string> { "Get" },
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 53271,
},
},
DownstreamHttpMethod = "POST",
},
},
};
const string expected = "here is some content";
var httpContent = new StringContent(expected);
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:53271/", "/", "POST"))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/", httpContent))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(_ => _steps.ThenTheResponseBodyShouldBe(expected))
.BDDfy();
}
[Fact]
public void should_return_response_200_when_get_converted_to_get_with_content()
{
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/{url}",
DownstreamScheme = "http",
UpstreamPathTemplate = "/{url}",
UpstreamHttpMethod = new List<string> { "Post" },
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 53272,
},
},
DownstreamHttpMethod = "GET",
},
},
};
const string expected = "here is some content";
var httpContent = new StringContent(expected);
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:53272/", "/", "GET"))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIPostUrlOnTheApiGateway("/", httpContent))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(_ => _steps.ThenTheResponseBodyShouldBe(expected))
.BDDfy();
}
private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, string expected)
{
_serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context =>
{
if (context.Request.Method == expected)
{
context.Response.StatusCode = 200;
var reader = new StreamReader(context.Request.Body);
var body = await reader.ReadToEndAsync();
await context.Response.WriteAsync(body);
}
else
{
context.Response.StatusCode = 500;
}
});
}
public void Dispose()
{
_serviceHandler.Dispose();
_steps.Dispose();
}
}
}

View File

@ -6,9 +6,11 @@
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System; using System;
using System.ComponentModel;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Core;
public class ServiceHandler : IDisposable public class ServiceHandler : IDisposable
{ {
@ -47,6 +49,31 @@
_builder.Start(); _builder.Start();
} }
public void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, RequestDelegate del, int port, HttpProtocols protocols)
{
_builder = new WebHostBuilder()
.UseUrls(baseUrl)
.UseKestrel()
.ConfigureKestrel(serverOptions =>
{
serverOptions.Listen(IPAddress.Loopback, port, listenOptions =>
{
listenOptions.UseHttps("idsrv3test.pfx", "idsrv3test");
listenOptions.Protocols = protocols;
});
})
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.Configure(app =>
{
app.UsePathBase(basePath);
app.Run(del);
})
.Build();
_builder.Start();
}
public void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, string fileName, string password, int port, RequestDelegate del) public void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, string fileName, string password, int port, RequestDelegate del)
{ {
_builder = new WebHostBuilder() _builder = new WebHostBuilder()

View File

@ -1,4 +1,6 @@
namespace Ocelot.AcceptanceTests using Ocelot.Configuration.ChangeTracking;
namespace Ocelot.AcceptanceTests
{ {
using Caching; using Caching;
using Configuration.Repository; using Configuration.Repository;
@ -54,6 +56,7 @@
private IWebHostBuilder _webHostBuilder; private IWebHostBuilder _webHostBuilder;
private WebHostBuilder _ocelotBuilder; private WebHostBuilder _ocelotBuilder;
private IWebHost _ocelotHost; private IWebHost _ocelotHost;
private IOcelotConfigurationChangeTokenSource _changeToken;
public Steps() public Steps()
{ {
@ -216,6 +219,11 @@
_ocelotClient = _ocelotServer.CreateClient(); _ocelotClient = _ocelotServer.CreateClient();
} }
public void GivenIHaveAChangeToken()
{
_changeToken = _ocelotServer.Host.Services.GetRequiredService<IOcelotConfigurationChangeTokenSource>();
}
/// <summary> /// <summary>
/// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step. /// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step.
/// </summary> /// </summary>
@ -384,6 +392,7 @@
_ocelotServer = new TestServer(_webHostBuilder); _ocelotServer = new TestServer(_webHostBuilder);
_ocelotClient = _ocelotServer.CreateClient(); _ocelotClient = _ocelotServer.CreateClient();
Thread.Sleep(1000);
} }
public void WhenIGetUrlOnTheApiGatewayWaitingForTheResponseToBeOk(string url) public void WhenIGetUrlOnTheApiGatewayWaitingForTheResponseToBeOk(string url)
@ -901,6 +910,18 @@
_response = _ocelotClient.GetAsync(url).Result; _response = _ocelotClient.GetAsync(url).Result;
} }
public void WhenIGetUrlOnTheApiGateway(string url, HttpContent content)
{
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url) {Content = content};
_response = _ocelotClient.SendAsync(httpRequestMessage).Result;
}
public void WhenIPostUrlOnTheApiGateway(string url, HttpContent content)
{
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, url) { Content = content };
_response = _ocelotClient.SendAsync(httpRequestMessage).Result;
}
public void WhenIGetUrlOnTheApiGateway(string url, string cookie, string value) public void WhenIGetUrlOnTheApiGateway(string url, string cookie, string value)
{ {
var request = _ocelotServer.CreateRequest(url); var request = _ocelotServer.CreateRequest(url);
@ -1123,6 +1144,11 @@
_ocelotClient = _ocelotServer.CreateClient(); _ocelotClient = _ocelotServer.CreateClient();
} }
public void TheChangeTokenShouldBeActive(bool itShouldBeActive)
{
_changeToken.ChangeToken.HasChanged.ShouldBe(itShouldBeActive);
}
public void GivenOcelotIsRunningWithLogger() public void GivenOcelotIsRunningWithLogger()
{ {
_webHostBuilder = new WebHostBuilder(); _webHostBuilder = new WebHostBuilder();

View File

@ -59,7 +59,7 @@ namespace Ocelot.Benchmarks
_downstreamContext = new DownstreamContext(httpContext) _downstreamContext = new DownstreamContext(httpContext)
{ {
Configuration = new InternalConfiguration(new List<ReRoute>(), null, null, null, null, null, null, null) Configuration = new InternalConfiguration(new List<ReRoute>(), null, null, null, null, null, null, null, null)
}; };
} }

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,35 @@
namespace Ocelot.UnitTests.Configuration.ChangeTracking
{
using Ocelot.Configuration.ChangeTracking;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
public class OcelotConfigurationChangeTokenSourceTests
{
private readonly IOcelotConfigurationChangeTokenSource _source;
public OcelotConfigurationChangeTokenSourceTests()
{
_source = new OcelotConfigurationChangeTokenSource();
}
[Fact]
public void should_activate_change_token()
{
this.Given(_ => GivenIActivateTheChangeTokenSource())
.Then(_ => ThenTheChangeTokenShouldBeActivated())
.BDDfy();
}
private void GivenIActivateTheChangeTokenSource()
{
_source.Activate();
}
private void ThenTheChangeTokenShouldBeActivated()
{
_source.ChangeToken.HasChanged.ShouldBeTrue();
}
}
}

View File

@ -0,0 +1,91 @@
using Xunit;
namespace Ocelot.UnitTests.Configuration.ChangeTracking
{
using System;
using Shouldly;
using Ocelot.Configuration.ChangeTracking;
using TestStack.BDDfy;
public class OcelotConfigurationChangeTokenTests
{
[Fact]
public void should_call_callback_with_state()
{
this.Given(_ => GivenIHaveAChangeToken())
.And(_ => AndIRegisterACallback())
.Then(_ => ThenIShouldGetADisposableWrapper())
.Given(_ => GivenIActivateTheToken())
.Then(_ => ThenTheCallbackShouldBeCalled())
.BDDfy();
}
[Fact]
public void should_not_call_callback_if_it_is_disposed()
{
this.Given(_ => GivenIHaveAChangeToken())
.And(_ => AndIRegisterACallback())
.Then(_ => ThenIShouldGetADisposableWrapper())
.And(_ => GivenIActivateTheToken())
.And(_ => AndIDisposeTheCallbackWrapper())
.And(_ => GivenIActivateTheToken())
.Then(_ => ThenTheCallbackShouldNotBeCalled())
.BDDfy();
}
private OcelotConfigurationChangeToken _changeToken;
private IDisposable _callbackWrapper;
private int _callbackCounter;
private readonly object _callbackInitialState = new object();
private object _callbackState;
private void Callback(object state)
{
_callbackCounter++;
_callbackState = state;
_changeToken.HasChanged.ShouldBeTrue();
}
private void GivenIHaveAChangeToken()
{
_changeToken = new OcelotConfigurationChangeToken();
}
private void AndIRegisterACallback()
{
_callbackWrapper = _changeToken.RegisterChangeCallback(Callback, _callbackInitialState);
}
private void ThenIShouldGetADisposableWrapper()
{
_callbackWrapper.ShouldNotBeNull();
}
private void GivenIActivateTheToken()
{
_callbackCounter = 0;
_callbackState = null;
_changeToken.Activate();
}
private void ThenTheCallbackShouldBeCalled()
{
_callbackCounter.ShouldBe(1);
_callbackState.ShouldNotBeNull();
_callbackState.ShouldBeSameAs(_callbackInitialState);
}
private void ThenTheCallbackShouldNotBeCalled()
{
_callbackCounter.ShouldBe(0);
_callbackState.ShouldBeNull();
}
private void AndIDisposeTheCallbackWrapper()
{
_callbackState = null;
_callbackCounter = 0;
_callbackWrapper.Dispose();
}
}
}

View File

@ -19,6 +19,7 @@ namespace Ocelot.UnitTests.Configuration
private readonly Mock<IQoSOptionsCreator> _qosCreator; private readonly Mock<IQoSOptionsCreator> _qosCreator;
private readonly Mock<IHttpHandlerOptionsCreator> _hhoCreator; private readonly Mock<IHttpHandlerOptionsCreator> _hhoCreator;
private readonly Mock<ILoadBalancerOptionsCreator> _lboCreator; private readonly Mock<ILoadBalancerOptionsCreator> _lboCreator;
private readonly Mock<IVersionCreator> _vCreator;
private FileConfiguration _fileConfig; private FileConfiguration _fileConfig;
private List<ReRoute> _reRoutes; private List<ReRoute> _reRoutes;
private ServiceProviderConfiguration _spc; private ServiceProviderConfiguration _spc;
@ -30,6 +31,7 @@ namespace Ocelot.UnitTests.Configuration
public ConfigurationCreatorTests() public ConfigurationCreatorTests()
{ {
_vCreator = new Mock<IVersionCreator>();
_lboCreator = new Mock<ILoadBalancerOptionsCreator>(); _lboCreator = new Mock<ILoadBalancerOptionsCreator>();
_hhoCreator = new Mock<IHttpHandlerOptionsCreator>(); _hhoCreator = new Mock<IHttpHandlerOptionsCreator>();
_qosCreator = new Mock<IQoSOptionsCreator>(); _qosCreator = new Mock<IQoSOptionsCreator>();
@ -117,7 +119,7 @@ namespace Ocelot.UnitTests.Configuration
private void WhenICreate() private void WhenICreate()
{ {
var serviceProvider = _serviceCollection.BuildServiceProvider(); var serviceProvider = _serviceCollection.BuildServiceProvider();
_creator = new ConfigurationCreator(_spcCreator.Object, _qosCreator.Object, _hhoCreator.Object, serviceProvider, _lboCreator.Object); _creator = new ConfigurationCreator(_spcCreator.Object, _qosCreator.Object, _hhoCreator.Object, serviceProvider, _lboCreator.Object, _vCreator.Object);
_result = _creator.Create(_fileConfig, _reRoutes); _result = _creator.Create(_fileConfig, _reRoutes);
} }
} }

View File

@ -3,6 +3,7 @@ namespace Ocelot.UnitTests.Configuration
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Moq; using Moq;
using Newtonsoft.Json; using Newtonsoft.Json;
using Ocelot.Configuration.ChangeTracking;
using Ocelot.Configuration.File; using Ocelot.Configuration.File;
using Ocelot.Configuration.Repository; using Ocelot.Configuration.Repository;
using Shouldly; using Shouldly;
@ -16,6 +17,7 @@ namespace Ocelot.UnitTests.Configuration
public class DiskFileConfigurationRepositoryTests : IDisposable public class DiskFileConfigurationRepositoryTests : IDisposable
{ {
private readonly Mock<IWebHostEnvironment> _hostingEnvironment; private readonly Mock<IWebHostEnvironment> _hostingEnvironment;
private readonly Mock<IOcelotConfigurationChangeTokenSource> _changeTokenSource;
private IFileConfigurationRepository _repo; private IFileConfigurationRepository _repo;
private string _environmentSpecificPath; private string _environmentSpecificPath;
private string _ocelotJsonPath; private string _ocelotJsonPath;
@ -35,7 +37,9 @@ namespace Ocelot.UnitTests.Configuration
_semaphore.Wait(); _semaphore.Wait();
_hostingEnvironment = new Mock<IWebHostEnvironment>(); _hostingEnvironment = new Mock<IWebHostEnvironment>();
_hostingEnvironment.Setup(he => he.EnvironmentName).Returns(_environmentName); _hostingEnvironment.Setup(he => he.EnvironmentName).Returns(_environmentName);
_repo = new DiskFileConfigurationRepository(_hostingEnvironment.Object); _changeTokenSource = new Mock<IOcelotConfigurationChangeTokenSource>(MockBehavior.Strict);
_changeTokenSource.Setup(m => m.Activate());
_repo = new DiskFileConfigurationRepository(_hostingEnvironment.Object, _changeTokenSource.Object);
} }
[Fact] [Fact]
@ -70,6 +74,7 @@ namespace Ocelot.UnitTests.Configuration
.When(_ => WhenISetTheConfiguration()) .When(_ => WhenISetTheConfiguration())
.Then(_ => ThenTheConfigurationIsStoredAs(config)) .Then(_ => ThenTheConfigurationIsStoredAs(config))
.And(_ => ThenTheConfigurationJsonIsIndented(config)) .And(_ => ThenTheConfigurationJsonIsIndented(config))
.And(x => AndTheChangeTokenIsActivated())
.BDDfy(); .BDDfy();
} }
@ -117,7 +122,7 @@ namespace Ocelot.UnitTests.Configuration
{ {
_environmentName = null; _environmentName = null;
_hostingEnvironment.Setup(he => he.EnvironmentName).Returns(_environmentName); _hostingEnvironment.Setup(he => he.EnvironmentName).Returns(_environmentName);
_repo = new DiskFileConfigurationRepository(_hostingEnvironment.Object); _repo = new DiskFileConfigurationRepository(_hostingEnvironment.Object, _changeTokenSource.Object);
} }
private void GivenIHaveAConfiguration(FileConfiguration fileConfiguration) private void GivenIHaveAConfiguration(FileConfiguration fileConfiguration)
@ -210,6 +215,11 @@ namespace Ocelot.UnitTests.Configuration
} }
} }
private void AndTheChangeTokenIsActivated()
{
_changeTokenSource.Verify(m => m.Activate(), Times.Once);
}
private FileConfiguration FakeFileConfigurationForSet() private FileConfiguration FakeFileConfigurationForSet()
{ {
var reRoutes = new List<FileReRoute> var reRoutes = new List<FileReRoute>
@ -222,11 +232,11 @@ namespace Ocelot.UnitTests.Configuration
{ {
Host = "123.12.12.12", Host = "123.12.12.12",
Port = 80, Port = 80,
} },
}, },
DownstreamScheme = "https", DownstreamScheme = "https",
DownstreamPathTemplate = "/asdfs/test/{test}" DownstreamPathTemplate = "/asdfs/test/{test}",
} },
}; };
var globalConfiguration = new FileGlobalConfiguration var globalConfiguration = new FileGlobalConfiguration

View File

@ -1,5 +1,6 @@
namespace Ocelot.UnitTests.Configuration namespace Ocelot.UnitTests.Configuration
{ {
using System;
using Moq; using Moq;
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.Configuration.Builder; using Ocelot.Configuration.Builder;
@ -14,15 +15,18 @@
{ {
private readonly DynamicsCreator _creator; private readonly DynamicsCreator _creator;
private readonly Mock<IRateLimitOptionsCreator> _rloCreator; private readonly Mock<IRateLimitOptionsCreator> _rloCreator;
private readonly Mock<IVersionCreator> _versionCreator;
private List<ReRoute> _result; private List<ReRoute> _result;
private FileConfiguration _fileConfig; private FileConfiguration _fileConfig;
private RateLimitOptions _rlo1; private RateLimitOptions _rlo1;
private RateLimitOptions _rlo2; private RateLimitOptions _rlo2;
private Version _version;
public DynamicsCreatorTests() public DynamicsCreatorTests()
{ {
_versionCreator = new Mock<IVersionCreator>();
_rloCreator = new Mock<IRateLimitOptionsCreator>(); _rloCreator = new Mock<IRateLimitOptionsCreator>();
_creator = new DynamicsCreator(_rloCreator.Object); _creator = new DynamicsCreator(_rloCreator.Object, _versionCreator.Object);
} }
[Fact] [Fact]
@ -50,7 +54,8 @@
RateLimitRule = new FileRateLimitRule RateLimitRule = new FileRateLimitRule
{ {
EnableRateLimiting = false EnableRateLimiting = false
} },
DownstreamHttpVersion = "1.1"
}, },
new FileDynamicReRoute new FileDynamicReRoute
{ {
@ -58,16 +63,19 @@
RateLimitRule = new FileRateLimitRule RateLimitRule = new FileRateLimitRule
{ {
EnableRateLimiting = true EnableRateLimiting = true
} },
DownstreamHttpVersion = "2.0"
} }
} }
}; };
this.Given(_ => GivenThe(fileConfig)) this.Given(_ => GivenThe(fileConfig))
.And(_ => GivenTheRloCreatorReturns()) .And(_ => GivenTheRloCreatorReturns())
.And(_ => GivenTheVersionCreatorReturns())
.When(_ => WhenICreate()) .When(_ => WhenICreate())
.Then(_ => ThenTheReRoutesAreReturned()) .Then(_ => ThenTheReRoutesAreReturned())
.And(_ => ThenTheRloCreatorIsCalledCorrectly()) .And(_ => ThenTheRloCreatorIsCalledCorrectly())
.And(_ => ThenTheVersionCreatorIsCalledCorrectly())
.BDDfy(); .BDDfy();
} }
@ -80,18 +88,32 @@
_fileConfig.GlobalConfiguration), Times.Once); _fileConfig.GlobalConfiguration), Times.Once);
} }
private void ThenTheVersionCreatorIsCalledCorrectly()
{
_versionCreator.Verify(x => x.Create(_fileConfig.DynamicReRoutes[0].DownstreamHttpVersion), Times.Once);
_versionCreator.Verify(x => x.Create(_fileConfig.DynamicReRoutes[1].DownstreamHttpVersion), Times.Once);
}
private void ThenTheReRoutesAreReturned() private void ThenTheReRoutesAreReturned()
{ {
_result.Count.ShouldBe(2); _result.Count.ShouldBe(2);
_result[0].DownstreamReRoute[0].EnableEndpointEndpointRateLimiting.ShouldBeFalse(); _result[0].DownstreamReRoute[0].EnableEndpointEndpointRateLimiting.ShouldBeFalse();
_result[0].DownstreamReRoute[0].RateLimitOptions.ShouldBe(_rlo1); _result[0].DownstreamReRoute[0].RateLimitOptions.ShouldBe(_rlo1);
_result[0].DownstreamReRoute[0].DownstreamHttpVersion.ShouldBe(_version);
_result[0].DownstreamReRoute[0].ServiceName.ShouldBe(_fileConfig.DynamicReRoutes[0].ServiceName); _result[0].DownstreamReRoute[0].ServiceName.ShouldBe(_fileConfig.DynamicReRoutes[0].ServiceName);
_result[1].DownstreamReRoute[0].EnableEndpointEndpointRateLimiting.ShouldBeTrue(); _result[1].DownstreamReRoute[0].EnableEndpointEndpointRateLimiting.ShouldBeTrue();
_result[1].DownstreamReRoute[0].RateLimitOptions.ShouldBe(_rlo2); _result[1].DownstreamReRoute[0].RateLimitOptions.ShouldBe(_rlo2);
_result[1].DownstreamReRoute[0].DownstreamHttpVersion.ShouldBe(_version);
_result[1].DownstreamReRoute[0].ServiceName.ShouldBe(_fileConfig.DynamicReRoutes[1].ServiceName); _result[1].DownstreamReRoute[0].ServiceName.ShouldBe(_fileConfig.DynamicReRoutes[1].ServiceName);
} }
private void GivenTheVersionCreatorReturns()
{
_version = new Version("1.1");
_versionCreator.Setup(x => x.Create(It.IsAny<string>())).Returns(_version);
}
private void GivenTheRloCreatorReturns() private void GivenTheRloCreatorReturns()
{ {
_rlo1 = new RateLimitOptionsBuilder().Build(); _rlo1 = new RateLimitOptionsBuilder().Build();

View File

@ -9,11 +9,14 @@ using Ocelot.Errors;
using Ocelot.Responses; using Ocelot.Responses;
using Shouldly; using Shouldly;
using System.Collections.Generic; using System.Collections.Generic;
using Ocelot.Configuration.ChangeTracking;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
namespace Ocelot.UnitTests.Configuration namespace Ocelot.UnitTests.Configuration
{ {
using System;
public class FileConfigurationSetterTests public class FileConfigurationSetterTests
{ {
private FileConfiguration _fileConfiguration; private FileConfiguration _fileConfiguration;
@ -21,7 +24,7 @@ namespace Ocelot.UnitTests.Configuration
private Mock<IInternalConfigurationRepository> _configRepo; private Mock<IInternalConfigurationRepository> _configRepo;
private Mock<IInternalConfigurationCreator> _configCreator; private Mock<IInternalConfigurationCreator> _configCreator;
private Response<IInternalConfiguration> _configuration; private Response<IInternalConfiguration> _configuration;
private object _result; private object _result;
private Mock<IFileConfigurationRepository> _repo; private Mock<IFileConfigurationRepository> _repo;
public FileConfigurationSetterTests() public FileConfigurationSetterTests()
@ -37,7 +40,7 @@ namespace Ocelot.UnitTests.Configuration
{ {
var fileConfig = new FileConfiguration(); var fileConfig = new FileConfiguration();
var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build();
var config = new InternalConfiguration(new List<ReRoute>(), string.Empty, serviceProviderConfig, "asdf", new LoadBalancerOptionsBuilder().Build(), "", new QoSOptionsBuilder().Build(), new HttpHandlerOptionsBuilder().Build()); var config = new InternalConfiguration(new List<ReRoute>(), string.Empty, serviceProviderConfig, "asdf", new LoadBalancerOptionsBuilder().Build(), "", new QoSOptionsBuilder().Build(), new HttpHandlerOptionsBuilder().Build(), new Version("1.1"));
this.Given(x => GivenTheFollowingConfiguration(fileConfig)) this.Given(x => GivenTheFollowingConfiguration(fileConfig))
.And(x => GivenTheRepoReturns(new OkResponse())) .And(x => GivenTheRepoReturns(new OkResponse()))
@ -104,8 +107,7 @@ namespace Ocelot.UnitTests.Configuration
private void ThenTheConfigurationRepositoryIsCalledCorrectly() private void ThenTheConfigurationRepositoryIsCalledCorrectly()
{ {
_configRepo _configRepo.Verify(x => x.AddOrReplace(_configuration.Data), Times.Once);
.Verify(x => x.AddOrReplace(_configuration.Data), Times.Once);
} }
} }
} }

View File

@ -87,7 +87,7 @@
_reRoutes = new List<ReRoute> { new ReRouteBuilder().Build() }; _reRoutes = new List<ReRoute> { new ReRouteBuilder().Build() };
_aggregates = new List<ReRoute> { new ReRouteBuilder().Build() }; _aggregates = new List<ReRoute> { new ReRouteBuilder().Build() };
_dynamics = new List<ReRoute> { new ReRouteBuilder().Build() }; _dynamics = new List<ReRoute> { new ReRouteBuilder().Build() };
_internalConfig = new InternalConfiguration(null, "", null, "", null, "", null, null); _internalConfig = new InternalConfiguration(null, "", null, "", null, "", null, null, null);
_reRoutesCreator.Setup(x => x.Create(It.IsAny<FileConfiguration>())).Returns(_reRoutes); _reRoutesCreator.Setup(x => x.Create(It.IsAny<FileConfiguration>())).Returns(_reRoutes);
_aggregatesCreator.Setup(x => x.Create(It.IsAny<FileConfiguration>(), It.IsAny<List<ReRoute>>())).Returns(_aggregates); _aggregatesCreator.Setup(x => x.Create(It.IsAny<FileConfiguration>(), It.IsAny<List<ReRoute>>())).Returns(_aggregates);

View File

@ -5,6 +5,8 @@ using Ocelot.Responses;
using Shouldly; using Shouldly;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Moq;
using Ocelot.Configuration.ChangeTracking;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
@ -16,10 +18,13 @@ namespace Ocelot.UnitTests.Configuration
private IInternalConfiguration _config; private IInternalConfiguration _config;
private Response _result; private Response _result;
private Response<IInternalConfiguration> _getResult; private Response<IInternalConfiguration> _getResult;
private readonly Mock<IOcelotConfigurationChangeTokenSource> _changeTokenSource;
public InMemoryConfigurationRepositoryTests() public InMemoryConfigurationRepositoryTests()
{ {
_repo = new InMemoryInternalConfigurationRepository(); _changeTokenSource = new Mock<IOcelotConfigurationChangeTokenSource>(MockBehavior.Strict);
_changeTokenSource.Setup(m => m.Activate());
_repo = new InMemoryInternalConfigurationRepository(_changeTokenSource.Object);
} }
[Fact] [Fact]
@ -28,6 +33,7 @@ namespace Ocelot.UnitTests.Configuration
this.Given(x => x.GivenTheConfigurationIs(new FakeConfig("initial", "adminath"))) this.Given(x => x.GivenTheConfigurationIs(new FakeConfig("initial", "adminath")))
.When(x => x.WhenIAddOrReplaceTheConfig()) .When(x => x.WhenIAddOrReplaceTheConfig())
.Then(x => x.ThenNoErrorsAreReturned()) .Then(x => x.ThenNoErrorsAreReturned())
.And(x => AndTheChangeTokenIsActivated())
.BDDfy(); .BDDfy();
} }
@ -71,6 +77,11 @@ namespace Ocelot.UnitTests.Configuration
_result.IsError.ShouldBeFalse(); _result.IsError.ShouldBeFalse();
} }
private void AndTheChangeTokenIsActivated()
{
_changeTokenSource.Verify(m => m.Activate(), Times.Once);
}
private class FakeConfig : IInternalConfiguration private class FakeConfig : IInternalConfiguration
{ {
private readonly string _downstreamTemplatePath; private readonly string _downstreamTemplatePath;
@ -109,6 +120,7 @@ namespace Ocelot.UnitTests.Configuration
public string DownstreamScheme { get; } public string DownstreamScheme { get; }
public QoSOptions QoSOptions { get; } public QoSOptions QoSOptions { get; }
public HttpHandlerOptions HttpHandlerOptions { get; } public HttpHandlerOptions HttpHandlerOptions { get; }
public Version DownstreamHttpVersion { get; }
} }
} }
} }

View File

@ -1,5 +1,6 @@
namespace Ocelot.UnitTests.Configuration namespace Ocelot.UnitTests.Configuration
{ {
using System;
using Moq; using Moq;
using Ocelot.Cache; using Ocelot.Cache;
using Ocelot.Configuration; using Ocelot.Configuration;
@ -30,6 +31,7 @@
private Mock<ILoadBalancerOptionsCreator> _lboCreator; private Mock<ILoadBalancerOptionsCreator> _lboCreator;
private Mock<IReRouteKeyCreator> _rrkCreator; private Mock<IReRouteKeyCreator> _rrkCreator;
private Mock<ISecurityOptionsCreator> _soCreator; private Mock<ISecurityOptionsCreator> _soCreator;
private Mock<IVersionCreator> _versionCreator;
private FileConfiguration _fileConfig; private FileConfiguration _fileConfig;
private ReRouteOptions _rro; private ReRouteOptions _rro;
private string _requestId; private string _requestId;
@ -46,6 +48,7 @@
private LoadBalancerOptions _lbo; private LoadBalancerOptions _lbo;
private List<ReRoute> _result; private List<ReRoute> _result;
private SecurityOptions _securityOptions; private SecurityOptions _securityOptions;
private Version _expectedVersion;
public ReRoutesCreatorTests() public ReRoutesCreatorTests()
{ {
@ -63,6 +66,7 @@
_lboCreator = new Mock<ILoadBalancerOptionsCreator>(); _lboCreator = new Mock<ILoadBalancerOptionsCreator>();
_rrkCreator = new Mock<IReRouteKeyCreator>(); _rrkCreator = new Mock<IReRouteKeyCreator>();
_soCreator = new Mock<ISecurityOptionsCreator>(); _soCreator = new Mock<ISecurityOptionsCreator>();
_versionCreator = new Mock<IVersionCreator>();
_creator = new ReRoutesCreator( _creator = new ReRoutesCreator(
_cthCreator.Object, _cthCreator.Object,
@ -78,7 +82,8 @@
_daCreator.Object, _daCreator.Object,
_lboCreator.Object, _lboCreator.Object,
_rrkCreator.Object, _rrkCreator.Object,
_soCreator.Object _soCreator.Object,
_versionCreator.Object
); );
} }
@ -155,6 +160,7 @@
private void GivenTheDependenciesAreSetUpCorrectly() private void GivenTheDependenciesAreSetUpCorrectly()
{ {
_expectedVersion = new Version("1.1");
_rro = new ReRouteOptions(false, false, false, false, false); _rro = new ReRouteOptions(false, false, false, false, false);
_requestId = "testy"; _requestId = "testy";
_rrk = "besty"; _rrk = "besty";
@ -182,6 +188,7 @@
_hfarCreator.Setup(x => x.Create(It.IsAny<FileReRoute>())).Returns(_ht); _hfarCreator.Setup(x => x.Create(It.IsAny<FileReRoute>())).Returns(_ht);
_daCreator.Setup(x => x.Create(It.IsAny<FileReRoute>())).Returns(_dhp); _daCreator.Setup(x => x.Create(It.IsAny<FileReRoute>())).Returns(_dhp);
_lboCreator.Setup(x => x.Create(It.IsAny<FileLoadBalancerOptions>())).Returns(_lbo); _lboCreator.Setup(x => x.Create(It.IsAny<FileLoadBalancerOptions>())).Returns(_lbo);
_versionCreator.Setup(x => x.Create(It.IsAny<string>())).Returns(_expectedVersion);
} }
private void ThenTheReRoutesAreCreated() private void ThenTheReRoutesAreCreated()
@ -209,6 +216,7 @@
private void ThenTheReRouteIsSet(FileReRoute expected, int reRouteIndex) private void ThenTheReRouteIsSet(FileReRoute expected, int reRouteIndex)
{ {
_result[reRouteIndex].DownstreamReRoute[0].DownstreamHttpVersion.ShouldBe(_expectedVersion);
_result[reRouteIndex].DownstreamReRoute[0].IsAuthenticated.ShouldBe(_rro.IsAuthenticated); _result[reRouteIndex].DownstreamReRoute[0].IsAuthenticated.ShouldBe(_rro.IsAuthenticated);
_result[reRouteIndex].DownstreamReRoute[0].IsAuthorised.ShouldBe(_rro.IsAuthorised); _result[reRouteIndex].DownstreamReRoute[0].IsAuthorised.ShouldBe(_rro.IsAuthorised);
_result[reRouteIndex].DownstreamReRoute[0].IsCached.ShouldBe(_rro.IsCached); _result[reRouteIndex].DownstreamReRoute[0].IsCached.ShouldBe(_rro.IsCached);

View File

@ -305,6 +305,71 @@
.BDDfy(); .BDDfy();
} }
[Theory]
[InlineData("1.0")]
[InlineData("1.1")]
[InlineData("2.0")]
[InlineData("1,0")]
[InlineData("1,1")]
[InlineData("2,0")]
[InlineData("1")]
[InlineData("2")]
[InlineData("")]
[InlineData(null)]
public void should_be_valid_re_route_using_downstream_http_version(string version)
{
var fileReRoute = new FileReRoute
{
DownstreamPathTemplate = "/test",
UpstreamPathTemplate = "/test",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 5000,
},
},
DownstreamHttpVersion = version,
};
this.Given(_ => GivenThe(fileReRoute))
.When(_ => WhenIValidate())
.Then(_ => ThenTheResultIsValid())
.BDDfy();
}
[Theory]
[InlineData("retg1.1")]
[InlineData("re2.0")]
[InlineData("1,0a")]
[InlineData("a1,1")]
[InlineData("12,0")]
[InlineData("asdf")]
public void should_be_invalid_re_route_using_downstream_http_version(string version)
{
var fileReRoute = new FileReRoute
{
DownstreamPathTemplate = "/test",
UpstreamPathTemplate = "/test",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 5000,
},
},
DownstreamHttpVersion = version,
};
this.Given(_ => GivenThe(fileReRoute))
.When(_ => WhenIValidate())
.Then(_ => ThenTheResultIsInvalid())
.And(_ => ThenTheErrorsContains("'Downstream Http Version' is not in the correct format."))
.BDDfy();
}
private void GivenAnAuthProvider(string key) private void GivenAnAuthProvider(string key)
{ {
var schemes = new List<AuthenticationScheme> var schemes = new List<AuthenticationScheme>

View File

@ -0,0 +1,54 @@
namespace Ocelot.UnitTests.Configuration
{
using System;
using Ocelot.Configuration.Creator;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
public class VersionCreatorTests
{
private readonly HttpVersionCreator _creator;
private string _input;
private Version _result;
public VersionCreatorTests()
{
_creator = new HttpVersionCreator();
}
[Fact]
public void should_create_version_based_on_input()
{
this.Given(_ => GivenTheInput("2.0"))
.When(_ => WhenICreate())
.Then(_ => ThenTheResultIs(2, 0))
.BDDfy();
}
[Fact]
public void should_default_to_version_one_point_one()
{
this.Given(_ => GivenTheInput(""))
.When(_ => WhenICreate())
.Then(_ => ThenTheResultIs(1, 1))
.BDDfy();
}
private void GivenTheInput(string input)
{
_input = input;
}
private void WhenICreate()
{
_result = _creator.Create(_input);
}
private void ThenTheResultIs(int major, int minor)
{
_result.Major.ShouldBe(major);
_result.Minor.ShouldBe(minor);
}
}
}

View File

@ -1,5 +1,6 @@
namespace Ocelot.UnitTests.DownstreamRouteFinder namespace Ocelot.UnitTests.DownstreamRouteFinder
{ {
using System;
using Moq; using Moq;
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.Configuration.Builder; using Ocelot.Configuration.Builder;
@ -44,7 +45,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
[Fact] [Fact]
public void should_create_downstream_route() public void should_create_downstream_route()
{ {
var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions); var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions, new Version("1.1"));
this.Given(_ => GivenTheConfiguration(configuration)) this.Given(_ => GivenTheConfiguration(configuration))
.When(_ => WhenICreate()) .When(_ => WhenICreate())
@ -71,7 +72,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
var reRoutes = new List<ReRoute> { reRoute }; var reRoutes = new List<ReRoute> { reRoute };
var configuration = new InternalConfiguration(reRoutes, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions); var configuration = new InternalConfiguration(reRoutes, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions, new Version("1.1"));
this.Given(_ => GivenTheConfiguration(configuration)) this.Given(_ => GivenTheConfiguration(configuration))
.When(_ => WhenICreate()) .When(_ => WhenICreate())
@ -83,7 +84,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
[Fact] [Fact]
public void should_cache_downstream_route() public void should_cache_downstream_route()
{ {
var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions); var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions, new Version("1.1"));
this.Given(_ => GivenTheConfiguration(configuration, "/geoffisthebest/")) this.Given(_ => GivenTheConfiguration(configuration, "/geoffisthebest/"))
.When(_ => WhenICreate()) .When(_ => WhenICreate())
@ -96,7 +97,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
[Fact] [Fact]
public void should_not_cache_downstream_route() public void should_not_cache_downstream_route()
{ {
var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions); var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions, new Version("1.1"));
this.Given(_ => GivenTheConfiguration(configuration, "/geoffistheworst/")) this.Given(_ => GivenTheConfiguration(configuration, "/geoffistheworst/"))
.When(_ => WhenICreate()) .When(_ => WhenICreate())
@ -110,7 +111,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
public void should_create_downstream_route_with_no_path() public void should_create_downstream_route_with_no_path()
{ {
var upstreamUrlPath = "/auth/"; var upstreamUrlPath = "/auth/";
var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions); var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions, new Version("1.1"));
this.Given(_ => GivenTheConfiguration(configuration, upstreamUrlPath)) this.Given(_ => GivenTheConfiguration(configuration, upstreamUrlPath))
.When(_ => WhenICreate()) .When(_ => WhenICreate())
@ -122,7 +123,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
public void should_create_downstream_route_with_only_first_segment_no_traling_slash() public void should_create_downstream_route_with_only_first_segment_no_traling_slash()
{ {
var upstreamUrlPath = "/auth"; var upstreamUrlPath = "/auth";
var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions); var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions, new Version("1.1"));
this.Given(_ => GivenTheConfiguration(configuration, upstreamUrlPath)) this.Given(_ => GivenTheConfiguration(configuration, upstreamUrlPath))
.When(_ => WhenICreate()) .When(_ => WhenICreate())
@ -134,7 +135,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
public void should_create_downstream_route_with_segments_no_traling_slash() public void should_create_downstream_route_with_segments_no_traling_slash()
{ {
var upstreamUrlPath = "/auth/test"; var upstreamUrlPath = "/auth/test";
var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions); var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions, new Version("1.1"));
this.Given(_ => GivenTheConfiguration(configuration, upstreamUrlPath)) this.Given(_ => GivenTheConfiguration(configuration, upstreamUrlPath))
.When(_ => WhenICreate()) .When(_ => WhenICreate())
@ -146,7 +147,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
public void should_create_downstream_route_and_remove_query_string() public void should_create_downstream_route_and_remove_query_string()
{ {
var upstreamUrlPath = "/auth/test?test=1&best=2"; var upstreamUrlPath = "/auth/test?test=1&best=2";
var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions); var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions, new Version("1.1"));
this.Given(_ => GivenTheConfiguration(configuration, upstreamUrlPath)) this.Given(_ => GivenTheConfiguration(configuration, upstreamUrlPath))
.When(_ => WhenICreate()) .When(_ => WhenICreate())
@ -158,7 +159,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
public void should_create_downstream_route_for_sticky_sessions() public void should_create_downstream_route_for_sticky_sessions()
{ {
var loadBalancerOptions = new LoadBalancerOptionsBuilder().WithType(nameof(CookieStickySessions)).WithKey("boom").WithExpiryInMs(1).Build(); var loadBalancerOptions = new LoadBalancerOptionsBuilder().WithType(nameof(CookieStickySessions)).WithKey("boom").WithExpiryInMs(1).Build();
var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", loadBalancerOptions, "http", _qoSOptions, _handlerOptions); var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", loadBalancerOptions, "http", _qoSOptions, _handlerOptions, new Version("1.1"));
this.Given(_ => GivenTheConfiguration(configuration)) this.Given(_ => GivenTheConfiguration(configuration))
.When(_ => WhenICreate()) .When(_ => WhenICreate())
@ -174,7 +175,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
.WithTimeoutValue(1) .WithTimeoutValue(1)
.Build(); .Build();
var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", qoSOptions, _handlerOptions); var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", qoSOptions, _handlerOptions, new Version("1.1"));
this.Given(_ => GivenTheConfiguration(configuration)) this.Given(_ => GivenTheConfiguration(configuration))
.And(_ => GivenTheQosCreatorReturns(qoSOptions)) .And(_ => GivenTheQosCreatorReturns(qoSOptions))
@ -186,7 +187,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
[Fact] [Fact]
public void should_create_downstream_route_with_handler_options() public void should_create_downstream_route_with_handler_options()
{ {
var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions); var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions, new Version("1.1"));
this.Given(_ => GivenTheConfiguration(configuration)) this.Given(_ => GivenTheConfiguration(configuration))
.When(_ => WhenICreate()) .When(_ => WhenICreate())

View File

@ -1,5 +1,6 @@
namespace Ocelot.UnitTests.DownstreamRouteFinder namespace Ocelot.UnitTests.DownstreamRouteFinder
{ {
using System;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Moq; using Moq;
using Ocelot.Configuration; using Ocelot.Configuration;
@ -48,7 +49,7 @@
[Fact] [Fact]
public void should_call_scoped_data_repository_correctly() public void should_call_scoped_data_repository_correctly()
{ {
var config = new InternalConfiguration(null, null, new ServiceProviderConfigurationBuilder().Build(), "", new LoadBalancerOptionsBuilder().Build(), "", new QoSOptionsBuilder().Build(), new HttpHandlerOptionsBuilder().Build()); var config = new InternalConfiguration(null, null, new ServiceProviderConfigurationBuilder().Build(), "", new LoadBalancerOptionsBuilder().Build(), "", new QoSOptionsBuilder().Build(), new HttpHandlerOptionsBuilder().Build(), new Version("1.1"));
var downstreamReRoute = new DownstreamReRouteBuilder() var downstreamReRoute = new DownstreamReRouteBuilder()
.WithDownstreamPathTemplate("any old string") .WithDownstreamPathTemplate("any old string")

View File

@ -13,6 +13,8 @@ using Xunit;
namespace Ocelot.UnitTests.DownstreamRouteFinder namespace Ocelot.UnitTests.DownstreamRouteFinder
{ {
using System;
public class DownstreamRouteFinderTests public class DownstreamRouteFinderTests
{ {
private readonly IDownstreamRouteProvider _downstreamRouteFinder; private readonly IDownstreamRouteProvider _downstreamRouteFinder;
@ -739,7 +741,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
private void GivenTheConfigurationIs(List<ReRoute> reRoutesConfig, string adminPath, ServiceProviderConfiguration serviceProviderConfig) private void GivenTheConfigurationIs(List<ReRoute> reRoutesConfig, string adminPath, ServiceProviderConfiguration serviceProviderConfig)
{ {
_reRoutesConfig = reRoutesConfig; _reRoutesConfig = reRoutesConfig;
_config = new InternalConfiguration(_reRoutesConfig, adminPath, serviceProviderConfig, "", new LoadBalancerOptionsBuilder().Build(), "", new QoSOptionsBuilder().Build(), new HttpHandlerOptionsBuilder().Build()); _config = new InternalConfiguration(_reRoutesConfig, adminPath, serviceProviderConfig, "", new LoadBalancerOptionsBuilder().Build(), "", new QoSOptionsBuilder().Build(), new HttpHandlerOptionsBuilder().Build(), new Version("1.1"));
} }
private void GivenThereIsAnUpstreamUrlPath(string upstreamUrlPath) private void GivenThereIsAnUpstreamUrlPath(string upstreamUrlPath)

View File

@ -1,5 +1,6 @@
namespace Ocelot.UnitTests.DownstreamRouteFinder namespace Ocelot.UnitTests.DownstreamRouteFinder
{ {
using System;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Moq; using Moq;
using Ocelot.Configuration; using Ocelot.Configuration;
@ -140,12 +141,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
private void GivenTheReRoutes(List<ReRoute> reRoutes) private void GivenTheReRoutes(List<ReRoute> reRoutes)
{ {
_config = new InternalConfiguration(reRoutes, "", null, "", new LoadBalancerOptionsBuilder().Build(), "", new QoSOptionsBuilder().Build(), new HttpHandlerOptionsBuilder().Build()); _config = new InternalConfiguration(reRoutes, "", null, "", new LoadBalancerOptionsBuilder().Build(), "", new QoSOptionsBuilder().Build(), new HttpHandlerOptionsBuilder().Build(), new Version("1.1"));
} }
private void GivenTheReRoutes(List<ReRoute> reRoutes, ServiceProviderConfiguration config) private void GivenTheReRoutes(List<ReRoute> reRoutes, ServiceProviderConfiguration config)
{ {
_config = new InternalConfiguration(reRoutes, "", config, "", new LoadBalancerOptionsBuilder().Build(), "", new QoSOptionsBuilder().Build(), new HttpHandlerOptionsBuilder().Build()); _config = new InternalConfiguration(reRoutes, "", config, "", new LoadBalancerOptionsBuilder().Build(), "", new QoSOptionsBuilder().Build(), new HttpHandlerOptionsBuilder().Build(), new Version("1.1"));
} }
} }
} }

View File

@ -382,7 +382,7 @@
private void GivenTheServiceProviderConfigIs(ServiceProviderConfiguration config) private void GivenTheServiceProviderConfigIs(ServiceProviderConfiguration config)
{ {
var configuration = new InternalConfiguration(null, null, config, null, null, null, null, null); var configuration = new InternalConfiguration(null, null, config, null, null, null, null, null, null);
_downstreamContext.Configuration = configuration; _downstreamContext.Configuration = configuration;
} }

View File

@ -52,7 +52,7 @@ namespace Ocelot.UnitTests.Errors
[Fact] [Fact]
public void NoDownstreamException() public void NoDownstreamException()
{ {
var config = new InternalConfiguration(null, null, null, null, null, null, null, null); var config = new InternalConfiguration(null, null, null, null, null, null, null, null, null);
this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream()) this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream())
.And(_ => GivenTheConfigurationIs(config)) .And(_ => GivenTheConfigurationIs(config))
@ -65,7 +65,7 @@ namespace Ocelot.UnitTests.Errors
[Fact] [Fact]
public void DownstreamException() public void DownstreamException()
{ {
var config = new InternalConfiguration(null, null, null, null, null, null, null, null); var config = new InternalConfiguration(null, null, null, null, null, null, null, null, null);
this.Given(_ => GivenAnExceptionWillBeThrownDownstream()) this.Given(_ => GivenAnExceptionWillBeThrownDownstream())
.And(_ => GivenTheConfigurationIs(config)) .And(_ => GivenTheConfigurationIs(config))
@ -77,7 +77,7 @@ namespace Ocelot.UnitTests.Errors
[Fact] [Fact]
public void ShouldSetRequestId() public void ShouldSetRequestId()
{ {
var config = new InternalConfiguration(null, null, null, "requestidkey", null, null, null, null); var config = new InternalConfiguration(null, null, null, "requestidkey", null, null, null, null, null);
this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream()) this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream())
.And(_ => GivenTheConfigurationIs(config)) .And(_ => GivenTheConfigurationIs(config))
@ -90,7 +90,7 @@ namespace Ocelot.UnitTests.Errors
[Fact] [Fact]
public void ShouldSetAspDotNetRequestId() public void ShouldSetAspDotNetRequestId()
{ {
var config = new InternalConfiguration(null, null, null, null, null, null, null, null); var config = new InternalConfiguration(null, null, null, null, null, null, null, null, null);
this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream()) this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream())
.And(_ => GivenTheConfigurationIs(config)) .And(_ => GivenTheConfigurationIs(config))

View File

@ -20,7 +20,7 @@
{ {
var configRepo = new Mock<IInternalConfigurationRepository>(); var configRepo = new Mock<IInternalConfigurationRepository>();
configRepo.Setup(x => x.Get()) configRepo.Setup(x => x.Get())
.Returns(new OkResponse<IInternalConfiguration>(new InternalConfiguration(null, null, null, null, null, null, null, null))); .Returns(new OkResponse<IInternalConfiguration>(new InternalConfiguration(null, null, null, null, null, null, null, null, null)));
var services = new ServiceCollection(); var services = new ServiceCollection();
services.AddSingleton<IInternalConfigurationRepository>(configRepo.Object); services.AddSingleton<IInternalConfigurationRepository>(configRepo.Object);
var sp = services.BuildServiceProvider(); var sp = services.BuildServiceProvider();
@ -35,7 +35,7 @@
var client = new Mock<IDiscoveryClient>(); var client = new Mock<IDiscoveryClient>();
var configRepo = new Mock<IInternalConfigurationRepository>(); var configRepo = new Mock<IInternalConfigurationRepository>();
configRepo.Setup(x => x.Get()) configRepo.Setup(x => x.Get())
.Returns(new OkResponse<IInternalConfiguration>(new InternalConfiguration(null, null, serviceProviderConfig, null, null, null, null, null))); .Returns(new OkResponse<IInternalConfiguration>(new InternalConfiguration(null, null, serviceProviderConfig, null, null, null, null, null, null)));
var services = new ServiceCollection(); var services = new ServiceCollection();
services.AddSingleton<IInternalConfigurationRepository>(configRepo.Object); services.AddSingleton<IInternalConfigurationRepository>(configRepo.Object);
services.AddSingleton<IDiscoveryClient>(client.Object); services.AddSingleton<IDiscoveryClient>(client.Object);

View File

@ -139,7 +139,7 @@ namespace Ocelot.UnitTests.LoadBalancer
private void GivenTheConfigurationIs(ServiceProviderConfiguration config) private void GivenTheConfigurationIs(ServiceProviderConfiguration config)
{ {
_config = config; _config = config;
var configuration = new InternalConfiguration(null, null, config, null, null, null, null, null); var configuration = new InternalConfiguration(null, null, config, null, null, null, null, null, null);
_downstreamContext.Configuration = configuration; _downstreamContext.Configuration = configuration;
} }

View File

@ -1,6 +1,4 @@
using Ocelot.Middleware; namespace Ocelot.UnitTests.Request
namespace Ocelot.UnitTests.Request
{ {
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Moq; using Moq;
@ -9,9 +7,12 @@ namespace Ocelot.UnitTests.Request
using Ocelot.Request.Creator; using Ocelot.Request.Creator;
using Ocelot.Request.Mapper; using Ocelot.Request.Mapper;
using Ocelot.Request.Middleware; using Ocelot.Request.Middleware;
using Ocelot.Configuration.Builder;
using Ocelot.Middleware;
using Ocelot.Responses; using Ocelot.Responses;
using Shouldly; using Shouldly;
using System.Net.Http; using System.Net.Http;
using Ocelot.Configuration;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
@ -65,6 +66,20 @@ namespace Ocelot.UnitTests.Request
.Then(_ => ThenTheContexRequestIsMappedToADownstreamRequest()) .Then(_ => ThenTheContexRequestIsMappedToADownstreamRequest())
.And(_ => ThenTheDownstreamRequestIsStored()) .And(_ => ThenTheDownstreamRequestIsStored())
.And(_ => ThenTheNextMiddlewareIsInvoked()) .And(_ => ThenTheNextMiddlewareIsInvoked())
.And(_ => ThenTheDownstreamRequestMethodIs("GET"))
.BDDfy();
}
[Fact]
public void Should_map_downstream_reroute_method_to_downstream_request()
{
this.Given(_ => GivenTheHttpContextContainsARequest())
.And(_ => GivenTheMapperWillReturnAMappedRequest())
.When(_ => WhenTheMiddlewareIsInvoked())
.Then(_ => ThenTheContexRequestIsMappedToADownstreamRequest())
.And(_ => ThenTheDownstreamRequestIsStored())
.And(_ => ThenTheNextMiddlewareIsInvoked())
.And(_ => ThenTheDownstreamRequestMethodIs("GET"))
.BDDfy(); .BDDfy();
} }
@ -80,6 +95,11 @@ namespace Ocelot.UnitTests.Request
.BDDfy(); .BDDfy();
} }
private void ThenTheDownstreamRequestMethodIs(string expected)
{
_downstreamContext.DownstreamRequest.Method.ShouldBe(expected);
}
private void GivenTheHttpContextContainsARequest() private void GivenTheHttpContextContainsARequest()
{ {
_httpContext _httpContext
@ -92,7 +112,7 @@ namespace Ocelot.UnitTests.Request
_mappedRequest = new OkResponse<HttpRequestMessage>(new HttpRequestMessage(HttpMethod.Get, "http://www.bbc.co.uk")); _mappedRequest = new OkResponse<HttpRequestMessage>(new HttpRequestMessage(HttpMethod.Get, "http://www.bbc.co.uk"));
_requestMapper _requestMapper
.Setup(rm => rm.Map(It.IsAny<HttpRequest>())) .Setup(rm => rm.Map(It.IsAny<HttpRequest>(), It.IsAny<DownstreamReRoute>()))
.ReturnsAsync(_mappedRequest); .ReturnsAsync(_mappedRequest);
} }
@ -101,7 +121,7 @@ namespace Ocelot.UnitTests.Request
_mappedRequest = new ErrorResponse<HttpRequestMessage>(new UnmappableRequestError(new System.Exception("boooom!"))); _mappedRequest = new ErrorResponse<HttpRequestMessage>(new UnmappableRequestError(new System.Exception("boooom!")));
_requestMapper _requestMapper
.Setup(rm => rm.Map(It.IsAny<HttpRequest>())) .Setup(rm => rm.Map(It.IsAny<HttpRequest>(), It.IsAny<DownstreamReRoute>()))
.ReturnsAsync(_mappedRequest); .ReturnsAsync(_mappedRequest);
} }
@ -112,7 +132,7 @@ namespace Ocelot.UnitTests.Request
private void ThenTheContexRequestIsMappedToADownstreamRequest() private void ThenTheContexRequestIsMappedToADownstreamRequest()
{ {
_requestMapper.Verify(rm => rm.Map(_httpRequest.Object), Times.Once); _requestMapper.Verify(rm => rm.Map(_httpRequest.Object, _downstreamContext.DownstreamReRoute), Times.Once);
} }
private void ThenTheDownstreamRequestIsStored() private void ThenTheDownstreamRequestIsStored()

View File

@ -13,6 +13,8 @@
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ocelot.Configuration;
using Ocelot.Configuration.Builder;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
@ -27,6 +29,8 @@
private List<KeyValuePair<string, StringValues>> _inputHeaders = null; private List<KeyValuePair<string, StringValues>> _inputHeaders = null;
private DownstreamReRoute _downstreamReRoute;
public RequestMapperTests() public RequestMapperTests()
{ {
_httpContext = new DefaultHttpContext(); _httpContext = new DefaultHttpContext();
@ -47,6 +51,7 @@
.And(_ => GivenTheInputRequestHasHost(host)) .And(_ => GivenTheInputRequestHasHost(host))
.And(_ => GivenTheInputRequestHasPath(path)) .And(_ => GivenTheInputRequestHasPath(path))
.And(_ => GivenTheInputRequestHasQueryString(queryString)) .And(_ => GivenTheInputRequestHasQueryString(queryString))
.And(_ => GivenTheDownstreamReRoute())
.When(_ => WhenMapped()) .When(_ => WhenMapped())
.Then(_ => ThenNoErrorIsReturned()) .Then(_ => ThenNoErrorIsReturned())
.And(_ => ThenTheMappedRequestHasUri(expectedUri)) .And(_ => ThenTheMappedRequestHasUri(expectedUri))
@ -76,18 +81,35 @@
{ {
this.Given(_ => GivenTheInputRequestHasMethod(method)) this.Given(_ => GivenTheInputRequestHasMethod(method))
.And(_ => GivenTheInputRequestHasAValidUri()) .And(_ => GivenTheInputRequestHasAValidUri())
.And(_ => GivenTheDownstreamReRoute())
.When(_ => WhenMapped()) .When(_ => WhenMapped())
.Then(_ => ThenNoErrorIsReturned()) .Then(_ => ThenNoErrorIsReturned())
.And(_ => ThenTheMappedRequestHasMethod(method)) .And(_ => ThenTheMappedRequestHasMethod(method))
.BDDfy(); .BDDfy();
} }
[Theory]
[InlineData("", "GET")]
[InlineData(null, "GET")]
[InlineData("POST", "POST")]
public void Should_use_downstream_reroute_method_if_set(string input, string expected)
{
this.Given(_ => GivenTheInputRequestHasMethod("GET"))
.And(_ => GivenTheDownstreamReRouteMethodIs(input))
.And(_ => GivenTheInputRequestHasAValidUri())
.When(_ => WhenMapped())
.Then(_ => ThenNoErrorIsReturned())
.And(_ => ThenTheMappedRequestHasMethod(expected))
.BDDfy();
}
[Fact] [Fact]
public void Should_map_all_headers() public void Should_map_all_headers()
{ {
this.Given(_ => GivenTheInputRequestHasHeaders()) this.Given(_ => GivenTheInputRequestHasHeaders())
.And(_ => GivenTheInputRequestHasMethod("GET")) .And(_ => GivenTheInputRequestHasMethod("GET"))
.And(_ => GivenTheInputRequestHasAValidUri()) .And(_ => GivenTheInputRequestHasAValidUri())
.And(_ => GivenTheDownstreamReRoute())
.When(_ => WhenMapped()) .When(_ => WhenMapped())
.Then(_ => ThenNoErrorIsReturned()) .Then(_ => ThenNoErrorIsReturned())
.And(_ => ThenTheMappedRequestHasEachHeader()) .And(_ => ThenTheMappedRequestHasEachHeader())
@ -100,6 +122,7 @@
this.Given(_ => GivenTheInputRequestHasNoHeaders()) this.Given(_ => GivenTheInputRequestHasNoHeaders())
.And(_ => GivenTheInputRequestHasMethod("GET")) .And(_ => GivenTheInputRequestHasMethod("GET"))
.And(_ => GivenTheInputRequestHasAValidUri()) .And(_ => GivenTheInputRequestHasAValidUri())
.And(_ => GivenTheDownstreamReRoute())
.When(_ => WhenMapped()) .When(_ => WhenMapped())
.Then(_ => ThenNoErrorIsReturned()) .Then(_ => ThenNoErrorIsReturned())
.And(_ => ThenTheMappedRequestHasNoHeaders()) .And(_ => ThenTheMappedRequestHasNoHeaders())
@ -112,6 +135,7 @@
this.Given(_ => GivenTheInputRequestHasContent("This is my content")) this.Given(_ => GivenTheInputRequestHasContent("This is my content"))
.And(_ => GivenTheInputRequestHasMethod("GET")) .And(_ => GivenTheInputRequestHasMethod("GET"))
.And(_ => GivenTheInputRequestHasAValidUri()) .And(_ => GivenTheInputRequestHasAValidUri())
.And(_ => GivenTheDownstreamReRoute())
.When(_ => WhenMapped()) .When(_ => WhenMapped())
.Then(_ => ThenNoErrorIsReturned()) .Then(_ => ThenNoErrorIsReturned())
.And(_ => ThenTheMappedRequestHasContent("This is my content")) .And(_ => ThenTheMappedRequestHasContent("This is my content"))
@ -124,6 +148,7 @@
this.Given(_ => GivenTheInputRequestHasNullContent()) this.Given(_ => GivenTheInputRequestHasNullContent())
.And(_ => GivenTheInputRequestHasMethod("GET")) .And(_ => GivenTheInputRequestHasMethod("GET"))
.And(_ => GivenTheInputRequestHasAValidUri()) .And(_ => GivenTheInputRequestHasAValidUri())
.And(_ => GivenTheDownstreamReRoute())
.When(_ => WhenMapped()) .When(_ => WhenMapped())
.Then(_ => ThenNoErrorIsReturned()) .Then(_ => ThenNoErrorIsReturned())
.And(_ => ThenTheMappedRequestHasNoContent()) .And(_ => ThenTheMappedRequestHasNoContent())
@ -136,6 +161,7 @@
this.Given(_ => GivenTheInputRequestHasNoContentType()) this.Given(_ => GivenTheInputRequestHasNoContentType())
.And(_ => GivenTheInputRequestHasMethod("GET")) .And(_ => GivenTheInputRequestHasMethod("GET"))
.And(_ => GivenTheInputRequestHasAValidUri()) .And(_ => GivenTheInputRequestHasAValidUri())
.And(_ => GivenTheDownstreamReRoute())
.When(_ => WhenMapped()) .When(_ => WhenMapped())
.Then(_ => ThenNoErrorIsReturned()) .Then(_ => ThenNoErrorIsReturned())
.And(_ => ThenTheMappedRequestHasNoContent()) .And(_ => ThenTheMappedRequestHasNoContent())
@ -148,22 +174,13 @@
this.Given(_ => GivenTheInputRequestHasNoContentLength()) this.Given(_ => GivenTheInputRequestHasNoContentLength())
.And(_ => GivenTheInputRequestHasMethod("GET")) .And(_ => GivenTheInputRequestHasMethod("GET"))
.And(_ => GivenTheInputRequestHasAValidUri()) .And(_ => GivenTheInputRequestHasAValidUri())
.And(_ => GivenTheDownstreamReRoute())
.When(_ => WhenMapped()) .When(_ => WhenMapped())
.Then(_ => ThenNoErrorIsReturned()) .Then(_ => ThenNoErrorIsReturned())
.And(_ => ThenTheMappedRequestHasNoContent()) .And(_ => ThenTheMappedRequestHasNoContent())
.BDDfy(); .BDDfy();
} }
private void GivenTheInputRequestHasNoContentLength()
{
_inputRequest.ContentLength = null;
}
private void GivenTheInputRequestHasNoContentType()
{
_inputRequest.ContentType = null;
}
[Fact] [Fact]
public void Should_map_content_headers() public void Should_map_content_headers()
{ {
@ -183,6 +200,7 @@
.And(_ => GivenTheContentMD5Is(md5bytes)) .And(_ => GivenTheContentMD5Is(md5bytes))
.And(_ => GivenTheInputRequestHasMethod("GET")) .And(_ => GivenTheInputRequestHasMethod("GET"))
.And(_ => GivenTheInputRequestHasAValidUri()) .And(_ => GivenTheInputRequestHasAValidUri())
.And(_ => GivenTheDownstreamReRoute())
.When(_ => WhenMapped()) .When(_ => WhenMapped())
.Then(_ => ThenNoErrorIsReturned()) .Then(_ => ThenNoErrorIsReturned())
.And(_ => ThenTheMappedRequestHasContentTypeHeader("application/json")) .And(_ => ThenTheMappedRequestHasContentTypeHeader("application/json"))
@ -204,6 +222,7 @@
.And(_ => GivenTheContentTypeIs("application/json")) .And(_ => GivenTheContentTypeIs("application/json"))
.And(_ => GivenTheInputRequestHasMethod("POST")) .And(_ => GivenTheInputRequestHasMethod("POST"))
.And(_ => GivenTheInputRequestHasAValidUri()) .And(_ => GivenTheInputRequestHasAValidUri())
.And(_ => GivenTheDownstreamReRoute())
.When(_ => WhenMapped()) .When(_ => WhenMapped())
.Then(_ => ThenNoErrorIsReturned()) .Then(_ => ThenNoErrorIsReturned())
.And(_ => ThenTheMappedRequestHasContentTypeHeader("application/json")) .And(_ => ThenTheMappedRequestHasContentTypeHeader("application/json"))
@ -212,6 +231,30 @@
.BDDfy(); .BDDfy();
} }
private void GivenTheDownstreamReRouteMethodIs(string input)
{
_downstreamReRoute = new DownstreamReRouteBuilder()
.WithDownStreamHttpMethod(input)
.WithDownstreamHttpVersion(new Version("1.1")).Build();
}
private void GivenTheDownstreamReRoute()
{
_downstreamReRoute = new DownstreamReRouteBuilder()
.WithDownstreamHttpVersion(new Version("1.1")).Build();
}
private void GivenTheInputRequestHasNoContentLength()
{
_inputRequest.ContentLength = null;
}
private void GivenTheInputRequestHasNoContentType()
{
_inputRequest.ContentType = null;
}
private void ThenTheContentHeadersAreNotAddedToNonContentHeaders() private void ThenTheContentHeadersAreNotAddedToNonContentHeaders()
{ {
_mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-Disposition"); _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-Disposition");
@ -380,7 +423,7 @@
private async Task WhenMapped() private async Task WhenMapped()
{ {
_mappedRequest = await _requestMapper.Map(_inputRequest); _mappedRequest = await _requestMapper.Map(_inputRequest, _downstreamReRoute);
} }
private void ThenNoErrorIsReturned() private void ThenNoErrorIsReturned()