mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-22 02:42:52 +08:00
* #451 started implementing querystring support in templates * #451 ocelot.json back to normal and specified in docs query string wont work in upstream template * Revert "#451 ocelot.json back to normal and specified in docs query string wont work in upstream template" This reverts commit 563193f7b2f78bad6109484fe77f3c87de831005. * #451 ocelot.json back to normal and specified in docs query string wont work in upstream template
This commit is contained in:
parent
89c3887d36
commit
75f9a8f9be
@ -1,187 +1,218 @@
|
|||||||
Routing
|
Routing
|
||||||
=======
|
=======
|
||||||
|
|
||||||
Ocelot's primary functionality is to take incomeing http requests and forward them on
|
Ocelot's primary functionality is to take incomeing http requests and forward them on
|
||||||
to a downstream service. At the moment in the form of another http request (in the future
|
to a downstream service. At the moment in the form of another http request (in the future
|
||||||
this could be any transport mechanism).
|
this could be any transport mechanism).
|
||||||
|
|
||||||
Ocelot's describes the routing of one request to another as a ReRoute. In order to get
|
Ocelot's describes the routing of one request to another as a ReRoute. In order to get
|
||||||
anything working in Ocelot you need to set up a ReRoute in the configuration.
|
anything working in Ocelot you need to set up a ReRoute in the configuration.
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
{
|
{
|
||||||
"ReRoutes": [
|
"ReRoutes": [
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
In order to set up a ReRoute you need to add one to the json array called ReRoutes like
|
In order to set up a ReRoute you need to add one to the json array called ReRoutes like
|
||||||
the following.
|
the following.
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
{
|
{
|
||||||
"DownstreamPathTemplate": "/api/posts/{postId}",
|
"DownstreamPathTemplate": "/api/posts/{postId}",
|
||||||
"DownstreamScheme": "https",
|
"DownstreamScheme": "https",
|
||||||
"DownstreamHostAndPorts": [
|
"DownstreamHostAndPorts": [
|
||||||
{
|
{
|
||||||
"Host": "localhost",
|
"Host": "localhost",
|
||||||
"Port": 80,
|
"Port": 80,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"UpstreamPathTemplate": "/posts/{postId}",
|
"UpstreamPathTemplate": "/posts/{postId}",
|
||||||
"UpstreamHttpMethod": [ "Put", "Delete" ]
|
"UpstreamHttpMethod": [ "Put", "Delete" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
The DownstreamPathTemplate, Scheme and DownstreamHostAndPorts make the URL that this request will be forwarded to.
|
The DownstreamPathTemplate, Scheme and DownstreamHostAndPorts make the URL that this request will be forwarded to.
|
||||||
|
|
||||||
DownstreamHostAndPorts is an array that contains the host and port of any downstream services that you wish to forward requests to. Usually this will just contain one entry but sometimes you might want to load balance
|
DownstreamHostAndPorts is an array that contains the host and port of any downstream services that you wish to forward requests to. Usually this will just contain one entry but sometimes you might want to load balance
|
||||||
requests to your downstream services and Ocelot let's you add more than one entry and then select a load balancer.
|
requests to your downstream services and Ocelot let's you add more than one entry and then select a load balancer.
|
||||||
|
|
||||||
The UpstreamPathTemplate is the URL that Ocelot will use to identity which DownstreamPathTemplate to use for a given request. Finally the UpstreamHttpMethod is used so
|
The UpstreamPathTemplate is the URL that Ocelot will use to identity which DownstreamPathTemplate to use for a given request. Finally the UpstreamHttpMethod is used so
|
||||||
Ocelot can distinguish between requests to the same URL and is obviously needed to work :)
|
Ocelot can distinguish between requests to the same URL and is obviously needed to work :)
|
||||||
|
|
||||||
You can set a specific list of HTTP Methods or set an empty list to allow any of them. In Ocelot you can add placeholders for variables to your Templates in the form of {something}.
|
You can set a specific list of HTTP Methods or set an empty list to allow any of them. In Ocelot you can add placeholders for variables to your Templates in the form of {something}.
|
||||||
The placeholder needs to be in both the DownstreamPathTemplate and UpstreamPathTemplate. If it is Ocelot will attempt to replace the placeholder with the correct variable value from the Upstream URL when the request comes in.
|
The placeholder needs to be in both the DownstreamPathTemplate and UpstreamPathTemplate. If it is Ocelot will attempt to replace the placeholder with the correct variable value from the Upstream URL when the request comes in.
|
||||||
|
|
||||||
You can also do a catch all type of ReRoute e.g.
|
You can also do a catch all type of ReRoute e.g.
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
{
|
{
|
||||||
"DownstreamPathTemplate": "/api/{everything}",
|
"DownstreamPathTemplate": "/api/{everything}",
|
||||||
"DownstreamScheme": "https",
|
"DownstreamScheme": "https",
|
||||||
"DownstreamHostAndPorts": [
|
"DownstreamHostAndPorts": [
|
||||||
{
|
{
|
||||||
"Host": "localhost",
|
"Host": "localhost",
|
||||||
"Port": 80,
|
"Port": 80,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"UpstreamPathTemplate": "/{everything}",
|
"UpstreamPathTemplate": "/{everything}",
|
||||||
"UpstreamHttpMethod": [ "Get", "Post" ]
|
"UpstreamHttpMethod": [ "Get", "Post" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
This will forward any path + query string combinations to the downstream service after the path /api.
|
This will forward any path + query string combinations to the downstream service after the path /api.
|
||||||
|
|
||||||
At the moment without any configuration Ocelot will default to all ReRoutes being case insensitive.
|
At the moment without any configuration Ocelot will default to all ReRoutes being case insensitive.
|
||||||
In order to change this you can specify on a per ReRoute basis the following setting.
|
In order to change this you can specify on a per ReRoute basis the following setting.
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
"ReRouteIsCaseSensitive": true
|
"ReRouteIsCaseSensitive": true
|
||||||
|
|
||||||
This means that when Ocelot tries to match the incoming upstream url with an upstream template the
|
This means that when Ocelot tries to match the incoming upstream url with an upstream template the
|
||||||
evaluation will be case sensitive. This setting defaults to false so only set it if you want
|
evaluation will be case sensitive. This setting defaults to false so only set it if you want
|
||||||
the ReRoute to be case sensitive is my advice!
|
the ReRoute to be case sensitive is my advice!
|
||||||
|
|
||||||
Catch All
|
Catch All
|
||||||
^^^^^^^^^
|
^^^^^^^^^
|
||||||
|
|
||||||
Ocelot's routing also supports a catch all style routing where the user can specify that they want to match all traffic if you set up your config like below the request will be proxied straight through (it doesnt have to be url any placeholder name will work).
|
Ocelot's routing also supports a catch all style routing where the user can specify that they want to match all traffic if you set up your config like below the request will be proxied straight through (it doesnt have to be url any placeholder name will work).
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
{
|
{
|
||||||
"DownstreamPathTemplate": "/{url}",
|
"DownstreamPathTemplate": "/{url}",
|
||||||
"DownstreamScheme": "https",
|
"DownstreamScheme": "https",
|
||||||
"DownstreamHostAndPorts": [
|
"DownstreamHostAndPorts": [
|
||||||
{
|
{
|
||||||
"Host": "localhost",
|
"Host": "localhost",
|
||||||
"Port": 80,
|
"Port": 80,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"UpstreamPathTemplate": "/{url}",
|
"UpstreamPathTemplate": "/{url}",
|
||||||
"UpstreamHttpMethod": [ "Get" ]
|
"UpstreamHttpMethod": [ "Get" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
The catch all has a lower priority than any other ReRoute. If you also have the ReRoute below in your config then Ocelot would match it before the catch all.
|
The catch all has a lower priority than any other ReRoute. If you also have the ReRoute below in your config then Ocelot would match it before the catch all.
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
{
|
{
|
||||||
"DownstreamPathTemplate": "/",
|
"DownstreamPathTemplate": "/",
|
||||||
"DownstreamScheme": "https",
|
"DownstreamScheme": "https",
|
||||||
"DownstreamHostAndPorts": [
|
"DownstreamHostAndPorts": [
|
||||||
{
|
{
|
||||||
"Host": "10.0.10.1",
|
"Host": "10.0.10.1",
|
||||||
"Port": 80,
|
"Port": 80,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"UpstreamPathTemplate": "/",
|
"UpstreamPathTemplate": "/",
|
||||||
"UpstreamHttpMethod": [ "Get" ]
|
"UpstreamHttpMethod": [ "Get" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
Upstream Host
|
Upstream Host
|
||||||
^^^^^^^^^^^^^
|
^^^^^^^^^^^^^
|
||||||
|
|
||||||
This feature allows you to have ReRoutes based on the upstream host. This works by looking at the host header the client has used and then using this as part of the information we use to identify a ReRoute.
|
This feature allows you to have ReRoutes based on the upstream host. This works by looking at the host header the client has used and then using this as part of the information we use to identify a ReRoute.
|
||||||
|
|
||||||
In order to use this feature please add the following to your config.
|
In order to use this feature please add the following to your config.
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
{
|
{
|
||||||
"DownstreamPathTemplate": "/",
|
"DownstreamPathTemplate": "/",
|
||||||
"DownstreamScheme": "https",
|
"DownstreamScheme": "https",
|
||||||
"DownstreamHostAndPorts": [
|
"DownstreamHostAndPorts": [
|
||||||
{
|
{
|
||||||
"Host": "10.0.10.1",
|
"Host": "10.0.10.1",
|
||||||
"Port": 80,
|
"Port": 80,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"UpstreamPathTemplate": "/",
|
"UpstreamPathTemplate": "/",
|
||||||
"UpstreamHttpMethod": [ "Get" ],
|
"UpstreamHttpMethod": [ "Get" ],
|
||||||
"UpstreamHost": "somedomain.com"
|
"UpstreamHost": "somedomain.com"
|
||||||
}
|
}
|
||||||
|
|
||||||
The ReRoute above will only be matched when the host header value is somedomain.com.
|
The ReRoute above will only be matched when the host header value is somedomain.com.
|
||||||
|
|
||||||
If you do not set UpstreamHost on a ReRoue then any host header can match it. This is basically a catch all and
|
If you do not set UpstreamHost on a ReRoue then any host header can match it. This is basically a catch all and
|
||||||
preservers existing functionality at the time of building the feature. This means that if you have two ReRoutes that are the same apart from the UpstreamHost where one is null and the other set. Ocelot will favour the one that has been set.
|
preservers existing functionality at the time of building the feature. This means that if you have two ReRoutes that are the same apart from the UpstreamHost where one is null and the other set. Ocelot will favour the one that has been set.
|
||||||
|
|
||||||
This feature was requested as part of `Issue 216 <https://github.com/TomPallister/Ocelot/pull/216>`_ .
|
This feature was requested as part of `Issue 216 <https://github.com/TomPallister/Ocelot/pull/216>`_ .
|
||||||
|
|
||||||
Priority
|
Priority
|
||||||
^^^^^^^^
|
^^^^^^^^
|
||||||
|
|
||||||
In `Issue 270 <https://github.com/TomPallister/Ocelot/pull/270>`_ I finally decided to expose the ReRoute priority in
|
In `Issue 270 <https://github.com/TomPallister/Ocelot/pull/270>`_ I finally decided to expose the ReRoute priority in
|
||||||
ocelot.json. This means you can decide in what order you want your ReRoutes to match the Upstream HttpRequest.
|
ocelot.json. This means you can decide in what order you want your ReRoutes to match the Upstream HttpRequest.
|
||||||
|
|
||||||
In order to get this working add the following to a ReRoute in ocelot.json, 0 is just an example value here but will explain below.
|
In order to get this working add the following to a ReRoute in ocelot.json, 0 is just an example value here but will explain below.
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
{
|
{
|
||||||
"Priority": 0
|
"Priority": 0
|
||||||
}
|
}
|
||||||
|
|
||||||
0 is the lowest priority, Ocelot will always use 0 for /{catchAll} ReRoutes and this is still hardcoded. After that you are free
|
0 is the lowest priority, Ocelot will always use 0 for /{catchAll} ReRoutes and this is still hardcoded. After that you are free
|
||||||
to set any priority you wish.
|
to set any priority you wish.
|
||||||
|
|
||||||
e.g. you could have
|
e.g. you could have
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
{
|
{
|
||||||
"UpstreamPathTemplate": "/goods/{catchAll}"
|
"UpstreamPathTemplate": "/goods/{catchAll}"
|
||||||
"Priority": 0
|
"Priority": 0
|
||||||
}
|
}
|
||||||
|
|
||||||
and
|
and
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
{
|
{
|
||||||
"UpstreamPathTemplate": "/goods/delete"
|
"UpstreamPathTemplate": "/goods/delete"
|
||||||
"Priority": 1
|
"Priority": 1
|
||||||
}
|
}
|
||||||
|
|
||||||
In the example above if you make a request into Ocelot on /goods/delete Ocelot will match /goods/delete ReRoute. Previously it would have
|
In the example above if you make a request into Ocelot on /goods/delete Ocelot will match /goods/delete ReRoute. Previously it would have
|
||||||
matched /goods/{catchAll} (because this is the first ReRoute in the list!).
|
matched /goods/{catchAll} (because this is the first ReRoute in the list!).
|
||||||
|
|
||||||
Dynamic Routing
|
Dynamic Routing
|
||||||
^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
This feature was requested in `issue 340 <https://github.com/TomPallister/Ocelot/issue/340>`_. The idea is to enable dynamic routing
|
This feature was requested in `issue 340 <https://github.com/TomPallister/Ocelot/issue/340>`_. The idea is to enable dynamic routing
|
||||||
when using a service discovery provider so you don't have to provide the ReRoute config. See the docs :ref:`service-discovery` if
|
when using a service discovery provider so you don't have to provide the ReRoute config. See the docs :ref:`service-discovery` if
|
||||||
this sounds interesting to you.
|
this sounds interesting to you.
|
||||||
|
|
||||||
|
Query Strings
|
||||||
|
^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Ocelot allow's you to specify a querystring as part of the DownstreamPathTemplate like the example below.
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
{
|
||||||
|
"ReRoutes": [
|
||||||
|
{
|
||||||
|
"DownstreamPathTemplate": "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}",
|
||||||
|
"UpstreamPathTemplate": "/api/units/{subscriptionId}/{unitId}/updates",
|
||||||
|
"UpstreamHttpMethod": [
|
||||||
|
"Get"
|
||||||
|
],
|
||||||
|
"DownstreamScheme": "http",
|
||||||
|
"DownstreamHostAndPorts": [
|
||||||
|
{
|
||||||
|
"Host": "localhost",
|
||||||
|
"Port": 50110
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"GlobalConfiguration": {
|
||||||
|
"UseServiceDiscovery": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
In this example Ocelot will use the value from the {unitId} in the upstream path template and add it to the downstream request as a query string parameter called unitId! Please note you cannot use query string parameters to match routes in the UpstreamPathTemplate.
|
||||||
|
@ -1,84 +1,112 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer;
|
using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer;
|
||||||
using Ocelot.Infrastructure.RequestData;
|
using Ocelot.Infrastructure.RequestData;
|
||||||
using Ocelot.Logging;
|
using Ocelot.Logging;
|
||||||
using Ocelot.Middleware;
|
using Ocelot.Middleware;
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Ocelot.DownstreamRouteFinder.Middleware;
|
using Ocelot.DownstreamRouteFinder.Middleware;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
using Ocelot.Values;
|
using Ocelot.Values;
|
||||||
|
|
||||||
namespace Ocelot.DownstreamUrlCreator.Middleware
|
namespace Ocelot.DownstreamUrlCreator.Middleware
|
||||||
{
|
{
|
||||||
public class DownstreamUrlCreatorMiddleware : OcelotMiddleware
|
public class DownstreamUrlCreatorMiddleware : OcelotMiddleware
|
||||||
{
|
{
|
||||||
private readonly OcelotRequestDelegate _next;
|
private readonly OcelotRequestDelegate _next;
|
||||||
private readonly IDownstreamPathPlaceholderReplacer _replacer;
|
private readonly IDownstreamPathPlaceholderReplacer _replacer;
|
||||||
|
|
||||||
public DownstreamUrlCreatorMiddleware(OcelotRequestDelegate next,
|
public DownstreamUrlCreatorMiddleware(OcelotRequestDelegate next,
|
||||||
IOcelotLoggerFactory loggerFactory,
|
IOcelotLoggerFactory loggerFactory,
|
||||||
IDownstreamPathPlaceholderReplacer replacer)
|
IDownstreamPathPlaceholderReplacer replacer)
|
||||||
:base(loggerFactory.CreateLogger<DownstreamUrlCreatorMiddleware>())
|
:base(loggerFactory.CreateLogger<DownstreamUrlCreatorMiddleware>())
|
||||||
{
|
{
|
||||||
_next = next;
|
_next = next;
|
||||||
_replacer = replacer;
|
_replacer = replacer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Invoke(DownstreamContext context)
|
public async Task Invoke(DownstreamContext context)
|
||||||
{
|
{
|
||||||
var dsPath = _replacer
|
var response = _replacer
|
||||||
.Replace(context.DownstreamReRoute.DownstreamPathTemplate, context.TemplatePlaceholderNameAndValues);
|
.Replace(context.DownstreamReRoute.DownstreamPathTemplate, context.TemplatePlaceholderNameAndValues);
|
||||||
|
|
||||||
if (dsPath.IsError)
|
if (response.IsError)
|
||||||
{
|
{
|
||||||
Logger.LogDebug("IDownstreamPathPlaceholderReplacer returned an error, setting pipeline error");
|
Logger.LogDebug("IDownstreamPathPlaceholderReplacer returned an error, setting pipeline error");
|
||||||
|
|
||||||
SetPipelineError(context, dsPath.Errors);
|
SetPipelineError(context, response.Errors);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
context.DownstreamRequest.Scheme = context.DownstreamReRoute.DownstreamScheme;
|
context.DownstreamRequest.Scheme = context.DownstreamReRoute.DownstreamScheme;
|
||||||
|
|
||||||
if (ServiceFabricRequest(context))
|
if (ServiceFabricRequest(context))
|
||||||
{
|
{
|
||||||
var pathAndQuery = CreateServiceFabricUri(context, dsPath);
|
var pathAndQuery = CreateServiceFabricUri(context, response);
|
||||||
context.DownstreamRequest.AbsolutePath = pathAndQuery.path;
|
context.DownstreamRequest.AbsolutePath = pathAndQuery.path;
|
||||||
context.DownstreamRequest.Query = pathAndQuery.query;
|
context.DownstreamRequest.Query = pathAndQuery.query;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
context.DownstreamRequest.AbsolutePath = dsPath.Data.Value;
|
var dsPath = response.Data;
|
||||||
}
|
|
||||||
|
if(ContainsQueryString(dsPath))
|
||||||
Logger.LogDebug($"Downstream url is {context.DownstreamRequest}");
|
{
|
||||||
|
context.DownstreamRequest.AbsolutePath = GetPath(dsPath);
|
||||||
await _next.Invoke(context);
|
context.DownstreamRequest.Query = GetQueryString(dsPath);
|
||||||
}
|
|
||||||
|
// todo - do we need to add anything from the request query string onto the query from the
|
||||||
private (string path, string query) CreateServiceFabricUri(DownstreamContext context, Response<DownstreamPath> dsPath)
|
// templae?
|
||||||
{
|
}
|
||||||
var query = context.DownstreamRequest.Query;
|
else
|
||||||
var serviceFabricPath = $"/{context.DownstreamReRoute.ServiceName + dsPath.Data.Value}";
|
{
|
||||||
|
context.DownstreamRequest.AbsolutePath = dsPath.Value;
|
||||||
if (RequestForStatefullService(query))
|
}
|
||||||
{
|
}
|
||||||
return (serviceFabricPath, query);
|
|
||||||
}
|
Logger.LogDebug($"Downstream url is {context.DownstreamRequest}");
|
||||||
|
|
||||||
var split = string.IsNullOrEmpty(query) ? "?" : "&";
|
await _next.Invoke(context);
|
||||||
return (serviceFabricPath, $"{query}{split}cmd=instance");
|
}
|
||||||
}
|
|
||||||
|
private string GetPath(DownstreamPath dsPath)
|
||||||
private static bool ServiceFabricRequest(DownstreamContext context)
|
{
|
||||||
{
|
return dsPath.Value.Substring(0, dsPath.Value.IndexOf("?"));
|
||||||
return context.Configuration.ServiceProviderConfiguration.Type == "ServiceFabric" && context.DownstreamReRoute.UseServiceDiscovery;
|
}
|
||||||
}
|
|
||||||
|
private string GetQueryString(DownstreamPath dsPath)
|
||||||
private static bool RequestForStatefullService(string query)
|
{
|
||||||
{
|
return dsPath.Value.Substring(dsPath.Value.IndexOf("?"));
|
||||||
return query.Contains("PartitionKind") && query.Contains("PartitionKey");
|
}
|
||||||
}
|
|
||||||
}
|
private bool ContainsQueryString(DownstreamPath dsPath)
|
||||||
}
|
{
|
||||||
|
return dsPath.Value.Contains("?");
|
||||||
|
}
|
||||||
|
|
||||||
|
private (string path, string query) CreateServiceFabricUri(DownstreamContext context, Response<DownstreamPath> dsPath)
|
||||||
|
{
|
||||||
|
var query = context.DownstreamRequest.Query;
|
||||||
|
var serviceFabricPath = $"/{context.DownstreamReRoute.ServiceName + dsPath.Data.Value}";
|
||||||
|
|
||||||
|
if (RequestForStatefullService(query))
|
||||||
|
{
|
||||||
|
return (serviceFabricPath, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
var split = string.IsNullOrEmpty(query) ? "?" : "&";
|
||||||
|
return (serviceFabricPath, $"{query}{split}cmd=instance");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ServiceFabricRequest(DownstreamContext context)
|
||||||
|
{
|
||||||
|
return context.Configuration.ServiceProviderConfiguration.Type == "ServiceFabric" && context.DownstreamReRoute.UseServiceDiscovery;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool RequestForStatefullService(string query)
|
||||||
|
{
|
||||||
|
return query.Contains("PartitionKind") && query.Contains("PartitionKey");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -984,7 +984,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
app.Run(async context =>
|
app.Run(async context =>
|
||||||
{
|
{
|
||||||
_downstreamPath = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value;
|
_downstreamPath = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value;
|
||||||
|
|
||||||
if(_downstreamPath != basePath)
|
if(_downstreamPath != basePath)
|
||||||
{
|
{
|
||||||
context.Response.StatusCode = statusCode;
|
context.Response.StatusCode = statusCode;
|
||||||
|
104
test/Ocelot.AcceptanceTests/RoutingWithQueryStringTests.cs
Normal file
104
test/Ocelot.AcceptanceTests/RoutingWithQueryStringTests.cs
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Ocelot.Configuration.File;
|
||||||
|
using Shouldly;
|
||||||
|
using TestStack.BDDfy;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Ocelot.AcceptanceTests
|
||||||
|
{
|
||||||
|
public class RoutingWithQueryStringTests : IDisposable
|
||||||
|
{
|
||||||
|
private IWebHost _builder;
|
||||||
|
private readonly Steps _steps;
|
||||||
|
private string _downstreamPath;
|
||||||
|
|
||||||
|
public RoutingWithQueryStringTests()
|
||||||
|
{
|
||||||
|
_steps = new Steps();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_return_response_200_with_query_string_template()
|
||||||
|
{
|
||||||
|
var subscriptionId = Guid.NewGuid().ToString();
|
||||||
|
var unitId = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
var configuration = new FileConfiguration
|
||||||
|
{
|
||||||
|
ReRoutes = new List<FileReRoute>
|
||||||
|
{
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamPathTemplate = "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}",
|
||||||
|
DownstreamScheme = "http",
|
||||||
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
|
{
|
||||||
|
new FileHostAndPort
|
||||||
|
{
|
||||||
|
Host = "localhost",
|
||||||
|
Port = 61879,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
UpstreamPathTemplate = "/api/units/{subscriptionId}/{unitId}/updates",
|
||||||
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:61879", $"/api/subscriptions/{subscriptionId}/updates", $"?unitId={unitId}", 200, "Hello from Laura"))
|
||||||
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway($"/api/units/{subscriptionId}/{unitId}/updates"))
|
||||||
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
|
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, string queryString, int statusCode, string responseBody)
|
||||||
|
{
|
||||||
|
_builder = new WebHostBuilder()
|
||||||
|
.UseUrls(baseUrl)
|
||||||
|
.UseKestrel()
|
||||||
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
|
.UseIISIntegration()
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.UsePathBase(basePath);
|
||||||
|
app.Run(async context =>
|
||||||
|
{
|
||||||
|
|
||||||
|
if(context.Request.PathBase.Value != basePath || context.Request.QueryString.Value != queryString)
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = 404;
|
||||||
|
await context.Response.WriteAsync("downstream path didnt match base path");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = statusCode;
|
||||||
|
await context.Response.WriteAsync(responseBody);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_builder.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ThenTheDownstreamUrlPathShouldBe(string expectedDownstreamPath)
|
||||||
|
{
|
||||||
|
_downstreamPath.ShouldBe(expectedDownstreamPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_builder?.Dispose();
|
||||||
|
_steps.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,345 +1,345 @@
|
|||||||
{
|
{
|
||||||
"ReRoutes": [
|
"ReRoutes": [
|
||||||
{
|
{
|
||||||
"DownstreamPathTemplate": "/profile",
|
"DownstreamPathTemplate": "/profile",
|
||||||
"DownstreamScheme": "http",
|
"DownstreamScheme": "http",
|
||||||
"UpstreamPathTemplate": "/profile",
|
"UpstreamPathTemplate": "/profile",
|
||||||
"UpstreamHttpMethod": [ "Get" ],
|
"UpstreamHttpMethod": [ "Get" ],
|
||||||
"DownstreamHostAndPorts": [
|
"DownstreamHostAndPorts": [
|
||||||
{
|
{
|
||||||
"Host": "localhost",
|
"Host": "localhost",
|
||||||
"Port": 3000
|
"Port": 3000
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"QoSOptions": {
|
||||||
|
"TimeoutValue": 360000
|
||||||
}
|
}
|
||||||
],
|
|
||||||
"QoSOptions": {
|
|
||||||
"TimeoutValue": 360000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"DownstreamPathTemplate": "/api/v1/todo/",
|
|
||||||
"DownstreamScheme": "http",
|
|
||||||
"UpstreamPathTemplate": "/api/v1/todo/",
|
|
||||||
"UpstreamHttpMethod": [ "Get", "Post" ],
|
|
||||||
"DownstreamHostAndPorts": [
|
|
||||||
{
|
|
||||||
"Host": "lxtodo.azurewebsites.net",
|
|
||||||
"Port": 80
|
|
||||||
}
|
|
||||||
|
|
||||||
],
|
|
||||||
"DownstreamHeaderTransform": {
|
|
||||||
"Location": "{DownstreamBaseUrl}, {BaseUrl}"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"DownstreamPathTemplate": "/api/values",
|
|
||||||
"DownstreamScheme": "https",
|
|
||||||
"UpstreamPathTemplate": "/api/values",
|
|
||||||
"UpstreamHttpMethod": [ "Get" ],
|
|
||||||
"DownstreamHostAndPorts": [
|
|
||||||
{
|
|
||||||
"Host": "testapivalues.azurewebsites.net",
|
|
||||||
"Port": 443
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"DownstreamPathTemplate": "/",
|
|
||||||
"DownstreamScheme": "http",
|
|
||||||
"DownstreamHostAndPorts": [
|
|
||||||
{
|
|
||||||
"Host": "localhost",
|
|
||||||
"Port": 52876
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"UpstreamPathTemplate": "/identityserverexample",
|
|
||||||
"UpstreamHttpMethod": [ "Get" ],
|
|
||||||
"QoSOptions": {
|
|
||||||
"ExceptionsAllowedBeforeBreaking": 3,
|
|
||||||
"DurationOfBreak": 10,
|
|
||||||
"TimeoutValue": 5000
|
|
||||||
},
|
},
|
||||||
"AuthenticationOptions": {
|
{
|
||||||
"AuthenticationProviderKey": "TestKey",
|
"DownstreamPathTemplate": "/api/v1/todo/",
|
||||||
"AllowedScopes": [
|
"DownstreamScheme": "http",
|
||||||
"openid",
|
"UpstreamPathTemplate": "/api/v1/todo/",
|
||||||
"offline_access"
|
"UpstreamHttpMethod": [ "Get", "Post" ],
|
||||||
|
"DownstreamHostAndPorts": [
|
||||||
|
{
|
||||||
|
"Host": "lxtodo.azurewebsites.net",
|
||||||
|
"Port": 80
|
||||||
|
}
|
||||||
|
|
||||||
|
],
|
||||||
|
"DownstreamHeaderTransform": {
|
||||||
|
"Location": "{DownstreamBaseUrl}, {BaseUrl}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"DownstreamPathTemplate": "/api/values",
|
||||||
|
"DownstreamScheme": "https",
|
||||||
|
"UpstreamPathTemplate": "/api/values",
|
||||||
|
"UpstreamHttpMethod": [ "Get" ],
|
||||||
|
"DownstreamHostAndPorts": [
|
||||||
|
{
|
||||||
|
"Host": "testapivalues.azurewebsites.net",
|
||||||
|
"Port": 443
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"AddHeadersToRequest": {
|
{
|
||||||
"CustomerId": "Claims[CustomerId] > value",
|
"DownstreamPathTemplate": "/",
|
||||||
"LocationId": "Claims[LocationId] > value",
|
"DownstreamScheme": "http",
|
||||||
"UserType": "Claims[sub] > value[0] > |",
|
"DownstreamHostAndPorts": [
|
||||||
"UserId": "Claims[sub] > value[1] > |"
|
{
|
||||||
|
"Host": "localhost",
|
||||||
|
"Port": 52876
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"UpstreamPathTemplate": "/identityserverexample",
|
||||||
|
"UpstreamHttpMethod": [ "Get" ],
|
||||||
|
"QoSOptions": {
|
||||||
|
"ExceptionsAllowedBeforeBreaking": 3,
|
||||||
|
"DurationOfBreak": 10,
|
||||||
|
"TimeoutValue": 5000
|
||||||
|
},
|
||||||
|
"AuthenticationOptions": {
|
||||||
|
"AuthenticationProviderKey": "TestKey",
|
||||||
|
"AllowedScopes": [
|
||||||
|
"openid",
|
||||||
|
"offline_access"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"AddHeadersToRequest": {
|
||||||
|
"CustomerId": "Claims[CustomerId] > value",
|
||||||
|
"LocationId": "Claims[LocationId] > value",
|
||||||
|
"UserType": "Claims[sub] > value[0] > |",
|
||||||
|
"UserId": "Claims[sub] > value[1] > |"
|
||||||
|
},
|
||||||
|
"AddClaimsToRequest": {
|
||||||
|
"CustomerId": "Claims[CustomerId] > value",
|
||||||
|
"LocationId": "Claims[LocationId] > value",
|
||||||
|
"UserType": "Claims[sub] > value[0] > |",
|
||||||
|
"UserId": "Claims[sub] > value[1] > |"
|
||||||
|
},
|
||||||
|
"AddQueriesToRequest": {
|
||||||
|
"CustomerId": "Claims[CustomerId] > value",
|
||||||
|
"LocationId": "Claims[LocationId] > value",
|
||||||
|
"UserType": "Claims[sub] > value[0] > |",
|
||||||
|
"UserId": "Claims[sub] > value[1] > |"
|
||||||
|
},
|
||||||
|
"RouteClaimsRequirement": {
|
||||||
|
"UserType": "registered"
|
||||||
|
},
|
||||||
|
"RequestIdKey": "OcRequestId"
|
||||||
},
|
},
|
||||||
"AddClaimsToRequest": {
|
{
|
||||||
"CustomerId": "Claims[CustomerId] > value",
|
"DownstreamPathTemplate": "/posts",
|
||||||
"LocationId": "Claims[LocationId] > value",
|
"DownstreamScheme": "https",
|
||||||
"UserType": "Claims[sub] > value[0] > |",
|
"DownstreamHostAndPorts": [
|
||||||
"UserId": "Claims[sub] > value[1] > |"
|
{
|
||||||
},
|
"Host": "jsonplaceholder.typicode.com",
|
||||||
"AddQueriesToRequest": {
|
"Port": 443
|
||||||
"CustomerId": "Claims[CustomerId] > value",
|
}
|
||||||
"LocationId": "Claims[LocationId] > value",
|
],
|
||||||
"UserType": "Claims[sub] > value[0] > |",
|
"UpstreamPathTemplate": "/posts",
|
||||||
"UserId": "Claims[sub] > value[1] > |"
|
"UpstreamHttpMethod": [ "Get" ],
|
||||||
},
|
"HttpHandlerOptions": {
|
||||||
"RouteClaimsRequirement": {
|
"AllowAutoRedirect": true,
|
||||||
"UserType": "registered"
|
"UseCookieContainer": true
|
||||||
},
|
},
|
||||||
"RequestIdKey": "OcRequestId"
|
"QoSOptions": {
|
||||||
},
|
"ExceptionsAllowedBeforeBreaking": 3,
|
||||||
{
|
"DurationOfBreak": 10,
|
||||||
"DownstreamPathTemplate": "/posts",
|
"TimeoutValue": 5000
|
||||||
"DownstreamScheme": "https",
|
|
||||||
"DownstreamHostAndPorts": [
|
|
||||||
{
|
|
||||||
"Host": "jsonplaceholder.typicode.com",
|
|
||||||
"Port": 443
|
|
||||||
}
|
}
|
||||||
],
|
|
||||||
"UpstreamPathTemplate": "/posts",
|
|
||||||
"UpstreamHttpMethod": [ "Get" ],
|
|
||||||
"HttpHandlerOptions": {
|
|
||||||
"AllowAutoRedirect": true,
|
|
||||||
"UseCookieContainer": true
|
|
||||||
},
|
},
|
||||||
"QoSOptions": {
|
{
|
||||||
"ExceptionsAllowedBeforeBreaking": 3,
|
"DownstreamPathTemplate": "/posts/{postId}",
|
||||||
"DurationOfBreak": 10,
|
"DownstreamScheme": "http",
|
||||||
"TimeoutValue": 5000
|
"DownstreamHostAndPorts": [
|
||||||
|
{
|
||||||
|
"Host": "jsonplaceholder.typicode.com",
|
||||||
|
"Port": 80
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"UpstreamPathTemplate": "/posts/{postId}",
|
||||||
|
"UpstreamHttpMethod": [ "Get" ],
|
||||||
|
"RequestIdKey": "ReRouteRequestId",
|
||||||
|
"HttpHandlerOptions": {
|
||||||
|
"AllowAutoRedirect": true,
|
||||||
|
"UseCookieContainer": true,
|
||||||
|
"UseTracing": true,
|
||||||
|
"UseProxy": true
|
||||||
|
},
|
||||||
|
"QoSOptions": {
|
||||||
|
"ExceptionsAllowedBeforeBreaking": 3,
|
||||||
|
"DurationOfBreak": 10,
|
||||||
|
"TimeoutValue": 5000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"DownstreamPathTemplate": "/posts/{postId}/comments",
|
||||||
|
"DownstreamScheme": "http",
|
||||||
|
"DownstreamHostAndPorts": [
|
||||||
|
{
|
||||||
|
"Host": "jsonplaceholder.typicode.com",
|
||||||
|
"Port": 80
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"UpstreamPathTemplate": "/posts/{postId}/comments",
|
||||||
|
"UpstreamHttpMethod": [ "Get" ],
|
||||||
|
"HttpHandlerOptions": {
|
||||||
|
"AllowAutoRedirect": true,
|
||||||
|
"UseCookieContainer": true,
|
||||||
|
"UseTracing": false
|
||||||
|
},
|
||||||
|
"QoSOptions": {
|
||||||
|
"ExceptionsAllowedBeforeBreaking": 3,
|
||||||
|
"DurationOfBreak": 10,
|
||||||
|
"TimeoutValue": 5000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"DownstreamPathTemplate": "/comments",
|
||||||
|
"DownstreamScheme": "http",
|
||||||
|
"DownstreamHostAndPorts": [
|
||||||
|
{
|
||||||
|
"Host": "jsonplaceholder.typicode.com",
|
||||||
|
"Port": 80
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"UpstreamPathTemplate": "/comments",
|
||||||
|
"UpstreamHttpMethod": [ "Get" ],
|
||||||
|
"QoSOptions": {
|
||||||
|
"ExceptionsAllowedBeforeBreaking": 3,
|
||||||
|
"DurationOfBreak": 10,
|
||||||
|
"TimeoutValue": 5000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"DownstreamPathTemplate": "/posts",
|
||||||
|
"DownstreamScheme": "http",
|
||||||
|
"DownstreamHostAndPorts": [
|
||||||
|
{
|
||||||
|
"Host": "jsonplaceholder.typicode.com",
|
||||||
|
"Port": 80
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"UpstreamPathTemplate": "/posts",
|
||||||
|
"UpstreamHttpMethod": [ "Post" ],
|
||||||
|
"QoSOptions": {
|
||||||
|
"ExceptionsAllowedBeforeBreaking": 3,
|
||||||
|
"DurationOfBreak": 10,
|
||||||
|
"TimeoutValue": 5000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"DownstreamPathTemplate": "/posts/{postId}",
|
||||||
|
"DownstreamScheme": "http",
|
||||||
|
"DownstreamHostAndPorts": [
|
||||||
|
{
|
||||||
|
"Host": "jsonplaceholder.typicode.com",
|
||||||
|
"Port": 80
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"UpstreamPathTemplate": "/posts/{postId}",
|
||||||
|
"UpstreamHttpMethod": [ "Put" ],
|
||||||
|
"QoSOptions": {
|
||||||
|
"ExceptionsAllowedBeforeBreaking": 3,
|
||||||
|
"DurationOfBreak": 10,
|
||||||
|
"TimeoutValue": 5000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"DownstreamPathTemplate": "/posts/{postId}",
|
||||||
|
"DownstreamScheme": "http",
|
||||||
|
"DownstreamHostAndPorts": [
|
||||||
|
{
|
||||||
|
"Host": "jsonplaceholder.typicode.com",
|
||||||
|
"Port": 80
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"UpstreamPathTemplate": "/posts/{postId}",
|
||||||
|
"UpstreamHttpMethod": [ "Patch" ],
|
||||||
|
"QoSOptions": {
|
||||||
|
"ExceptionsAllowedBeforeBreaking": 3,
|
||||||
|
"DurationOfBreak": 10,
|
||||||
|
"TimeoutValue": 5000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"DownstreamPathTemplate": "/posts/{postId}",
|
||||||
|
"DownstreamScheme": "http",
|
||||||
|
"DownstreamHostAndPorts": [
|
||||||
|
{
|
||||||
|
"Host": "jsonplaceholder.typicode.com",
|
||||||
|
"Port": 80
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"UpstreamPathTemplate": "/posts/{postId}",
|
||||||
|
"UpstreamHttpMethod": [ "Delete" ],
|
||||||
|
"QoSOptions": {
|
||||||
|
"ExceptionsAllowedBeforeBreaking": 3,
|
||||||
|
"DurationOfBreak": 10,
|
||||||
|
"TimeoutValue": 5000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"DownstreamPathTemplate": "/api/products",
|
||||||
|
"DownstreamScheme": "http",
|
||||||
|
"DownstreamHostAndPorts": [
|
||||||
|
{
|
||||||
|
"Host": "jsonplaceholder.typicode.com",
|
||||||
|
"Port": 80
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"UpstreamPathTemplate": "/products",
|
||||||
|
"UpstreamHttpMethod": [ "Get" ],
|
||||||
|
"QoSOptions": {
|
||||||
|
"ExceptionsAllowedBeforeBreaking": 3,
|
||||||
|
"DurationOfBreak": 10,
|
||||||
|
"TimeoutValue": 5000
|
||||||
|
},
|
||||||
|
"FileCacheOptions": { "TtlSeconds": 15 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"DownstreamPathTemplate": "/api/products/{productId}",
|
||||||
|
"DownstreamScheme": "http",
|
||||||
|
"DownstreamHostAndPorts": [
|
||||||
|
{
|
||||||
|
"Host": "jsonplaceholder.typicode.com",
|
||||||
|
"Port": 80
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"UpstreamPathTemplate": "/products/{productId}",
|
||||||
|
"UpstreamHttpMethod": [ "Get" ],
|
||||||
|
"FileCacheOptions": { "TtlSeconds": 15 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"DownstreamPathTemplate": "/api/products",
|
||||||
|
"DownstreamScheme": "http",
|
||||||
|
"DownstreamHostAndPorts": [
|
||||||
|
{
|
||||||
|
"Host": "jsonplaceholder.typicode.com",
|
||||||
|
"Port": 80
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"UpstreamPathTemplate": "/products",
|
||||||
|
"UpstreamHttpMethod": [ "Post" ],
|
||||||
|
"QoSOptions": {
|
||||||
|
"ExceptionsAllowedBeforeBreaking": 3,
|
||||||
|
"DurationOfBreak": 10,
|
||||||
|
"TimeoutValue": 5000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"DownstreamPathTemplate": "/api/products/{productId}",
|
||||||
|
"DownstreamScheme": "http",
|
||||||
|
"DownstreamHostAndPorts": [
|
||||||
|
{
|
||||||
|
"Host": "jsonplaceholder.typicode.com",
|
||||||
|
"Port": 80
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"UpstreamPathTemplate": "/products/{productId}",
|
||||||
|
"UpstreamHttpMethod": [ "Put" ],
|
||||||
|
"QoSOptions": {
|
||||||
|
"ExceptionsAllowedBeforeBreaking": 3,
|
||||||
|
"DurationOfBreak": 10,
|
||||||
|
"TimeoutValue": 5000
|
||||||
|
},
|
||||||
|
"FileCacheOptions": { "TtlSeconds": 15 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"DownstreamPathTemplate": "/posts",
|
||||||
|
"DownstreamScheme": "http",
|
||||||
|
"DownstreamHostAndPorts": [
|
||||||
|
{
|
||||||
|
"Host": "jsonplaceholder.typicode.com",
|
||||||
|
"Port": 80
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"UpstreamPathTemplate": "/posts/",
|
||||||
|
"UpstreamHttpMethod": [ "Get" ],
|
||||||
|
"QoSOptions": {
|
||||||
|
"ExceptionsAllowedBeforeBreaking": 3,
|
||||||
|
"DurationOfBreak": 10,
|
||||||
|
"TimeoutValue": 5000
|
||||||
|
},
|
||||||
|
"FileCacheOptions": { "TtlSeconds": 15 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"DownstreamPathTemplate": "/",
|
||||||
|
"DownstreamScheme": "http",
|
||||||
|
"DownstreamHostAndPorts": [
|
||||||
|
{
|
||||||
|
"Host": "www.bbc.co.uk",
|
||||||
|
"Port": 80
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"UpstreamPathTemplate": "/bbc/",
|
||||||
|
"UpstreamHttpMethod": [ "Get" ]
|
||||||
}
|
}
|
||||||
},
|
],
|
||||||
{
|
|
||||||
"DownstreamPathTemplate": "/posts/{postId}",
|
"GlobalConfiguration": {
|
||||||
"DownstreamScheme": "http",
|
"RequestIdKey": "ot-traceid"
|
||||||
"DownstreamHostAndPorts": [
|
|
||||||
{
|
|
||||||
"Host": "jsonplaceholder.typicode.com",
|
|
||||||
"Port": 80
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"UpstreamPathTemplate": "/posts/{postId}",
|
|
||||||
"UpstreamHttpMethod": [ "Get" ],
|
|
||||||
"RequestIdKey": "ReRouteRequestId",
|
|
||||||
"HttpHandlerOptions": {
|
|
||||||
"AllowAutoRedirect": true,
|
|
||||||
"UseCookieContainer": true,
|
|
||||||
"UseTracing": true,
|
|
||||||
"UseProxy": true
|
|
||||||
},
|
|
||||||
"QoSOptions": {
|
|
||||||
"ExceptionsAllowedBeforeBreaking": 3,
|
|
||||||
"DurationOfBreak": 10,
|
|
||||||
"TimeoutValue": 5000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"DownstreamPathTemplate": "/posts/{postId}/comments",
|
|
||||||
"DownstreamScheme": "http",
|
|
||||||
"DownstreamHostAndPorts": [
|
|
||||||
{
|
|
||||||
"Host": "jsonplaceholder.typicode.com",
|
|
||||||
"Port": 80
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"UpstreamPathTemplate": "/posts/{postId}/comments",
|
|
||||||
"UpstreamHttpMethod": [ "Get" ],
|
|
||||||
"HttpHandlerOptions": {
|
|
||||||
"AllowAutoRedirect": true,
|
|
||||||
"UseCookieContainer": true,
|
|
||||||
"UseTracing": false
|
|
||||||
},
|
|
||||||
"QoSOptions": {
|
|
||||||
"ExceptionsAllowedBeforeBreaking": 3,
|
|
||||||
"DurationOfBreak": 10,
|
|
||||||
"TimeoutValue": 5000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"DownstreamPathTemplate": "/comments",
|
|
||||||
"DownstreamScheme": "http",
|
|
||||||
"DownstreamHostAndPorts": [
|
|
||||||
{
|
|
||||||
"Host": "jsonplaceholder.typicode.com",
|
|
||||||
"Port": 80
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"UpstreamPathTemplate": "/comments",
|
|
||||||
"UpstreamHttpMethod": [ "Get" ],
|
|
||||||
"QoSOptions": {
|
|
||||||
"ExceptionsAllowedBeforeBreaking": 3,
|
|
||||||
"DurationOfBreak": 10,
|
|
||||||
"TimeoutValue": 5000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"DownstreamPathTemplate": "/posts",
|
|
||||||
"DownstreamScheme": "http",
|
|
||||||
"DownstreamHostAndPorts": [
|
|
||||||
{
|
|
||||||
"Host": "jsonplaceholder.typicode.com",
|
|
||||||
"Port": 80
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"UpstreamPathTemplate": "/posts",
|
|
||||||
"UpstreamHttpMethod": [ "Post" ],
|
|
||||||
"QoSOptions": {
|
|
||||||
"ExceptionsAllowedBeforeBreaking": 3,
|
|
||||||
"DurationOfBreak": 10,
|
|
||||||
"TimeoutValue": 5000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"DownstreamPathTemplate": "/posts/{postId}",
|
|
||||||
"DownstreamScheme": "http",
|
|
||||||
"DownstreamHostAndPorts": [
|
|
||||||
{
|
|
||||||
"Host": "jsonplaceholder.typicode.com",
|
|
||||||
"Port": 80
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"UpstreamPathTemplate": "/posts/{postId}",
|
|
||||||
"UpstreamHttpMethod": [ "Put" ],
|
|
||||||
"QoSOptions": {
|
|
||||||
"ExceptionsAllowedBeforeBreaking": 3,
|
|
||||||
"DurationOfBreak": 10,
|
|
||||||
"TimeoutValue": 5000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"DownstreamPathTemplate": "/posts/{postId}",
|
|
||||||
"DownstreamScheme": "http",
|
|
||||||
"DownstreamHostAndPorts": [
|
|
||||||
{
|
|
||||||
"Host": "jsonplaceholder.typicode.com",
|
|
||||||
"Port": 80
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"UpstreamPathTemplate": "/posts/{postId}",
|
|
||||||
"UpstreamHttpMethod": [ "Patch" ],
|
|
||||||
"QoSOptions": {
|
|
||||||
"ExceptionsAllowedBeforeBreaking": 3,
|
|
||||||
"DurationOfBreak": 10,
|
|
||||||
"TimeoutValue": 5000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"DownstreamPathTemplate": "/posts/{postId}",
|
|
||||||
"DownstreamScheme": "http",
|
|
||||||
"DownstreamHostAndPorts": [
|
|
||||||
{
|
|
||||||
"Host": "jsonplaceholder.typicode.com",
|
|
||||||
"Port": 80
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"UpstreamPathTemplate": "/posts/{postId}",
|
|
||||||
"UpstreamHttpMethod": [ "Delete" ],
|
|
||||||
"QoSOptions": {
|
|
||||||
"ExceptionsAllowedBeforeBreaking": 3,
|
|
||||||
"DurationOfBreak": 10,
|
|
||||||
"TimeoutValue": 5000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"DownstreamPathTemplate": "/api/products",
|
|
||||||
"DownstreamScheme": "http",
|
|
||||||
"DownstreamHostAndPorts": [
|
|
||||||
{
|
|
||||||
"Host": "jsonplaceholder.typicode.com",
|
|
||||||
"Port": 80
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"UpstreamPathTemplate": "/products",
|
|
||||||
"UpstreamHttpMethod": [ "Get" ],
|
|
||||||
"QoSOptions": {
|
|
||||||
"ExceptionsAllowedBeforeBreaking": 3,
|
|
||||||
"DurationOfBreak": 10,
|
|
||||||
"TimeoutValue": 5000
|
|
||||||
},
|
|
||||||
"FileCacheOptions": { "TtlSeconds": 15 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"DownstreamPathTemplate": "/api/products/{productId}",
|
|
||||||
"DownstreamScheme": "http",
|
|
||||||
"DownstreamHostAndPorts": [
|
|
||||||
{
|
|
||||||
"Host": "jsonplaceholder.typicode.com",
|
|
||||||
"Port": 80
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"UpstreamPathTemplate": "/products/{productId}",
|
|
||||||
"UpstreamHttpMethod": [ "Get" ],
|
|
||||||
"FileCacheOptions": { "TtlSeconds": 15 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"DownstreamPathTemplate": "/api/products",
|
|
||||||
"DownstreamScheme": "http",
|
|
||||||
"DownstreamHostAndPorts": [
|
|
||||||
{
|
|
||||||
"Host": "jsonplaceholder.typicode.com",
|
|
||||||
"Port": 80
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"UpstreamPathTemplate": "/products",
|
|
||||||
"UpstreamHttpMethod": [ "Post" ],
|
|
||||||
"QoSOptions": {
|
|
||||||
"ExceptionsAllowedBeforeBreaking": 3,
|
|
||||||
"DurationOfBreak": 10,
|
|
||||||
"TimeoutValue": 5000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"DownstreamPathTemplate": "/api/products/{productId}",
|
|
||||||
"DownstreamScheme": "http",
|
|
||||||
"DownstreamHostAndPorts": [
|
|
||||||
{
|
|
||||||
"Host": "jsonplaceholder.typicode.com",
|
|
||||||
"Port": 80
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"UpstreamPathTemplate": "/products/{productId}",
|
|
||||||
"UpstreamHttpMethod": [ "Put" ],
|
|
||||||
"QoSOptions": {
|
|
||||||
"ExceptionsAllowedBeforeBreaking": 3,
|
|
||||||
"DurationOfBreak": 10,
|
|
||||||
"TimeoutValue": 5000
|
|
||||||
},
|
|
||||||
"FileCacheOptions": { "TtlSeconds": 15 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"DownstreamPathTemplate": "/posts",
|
|
||||||
"DownstreamScheme": "http",
|
|
||||||
"DownstreamHostAndPorts": [
|
|
||||||
{
|
|
||||||
"Host": "jsonplaceholder.typicode.com",
|
|
||||||
"Port": 80
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"UpstreamPathTemplate": "/posts/",
|
|
||||||
"UpstreamHttpMethod": [ "Get" ],
|
|
||||||
"QoSOptions": {
|
|
||||||
"ExceptionsAllowedBeforeBreaking": 3,
|
|
||||||
"DurationOfBreak": 10,
|
|
||||||
"TimeoutValue": 5000
|
|
||||||
},
|
|
||||||
"FileCacheOptions": { "TtlSeconds": 15 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"DownstreamPathTemplate": "/",
|
|
||||||
"DownstreamScheme": "http",
|
|
||||||
"DownstreamHostAndPorts": [
|
|
||||||
{
|
|
||||||
"Host": "www.bbc.co.uk",
|
|
||||||
"Port": 80
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"UpstreamPathTemplate": "/bbc/",
|
|
||||||
"UpstreamHttpMethod": [ "Get" ]
|
|
||||||
}
|
}
|
||||||
],
|
|
||||||
|
|
||||||
"GlobalConfiguration": {
|
|
||||||
"RequestIdKey": "ot-traceid"
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,231 +1,237 @@
|
|||||||
namespace Ocelot.UnitTests.DownstreamUrlCreator
|
namespace Ocelot.UnitTests.DownstreamUrlCreator
|
||||||
{
|
{
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Ocelot.Configuration.Builder;
|
using Ocelot.Configuration.Builder;
|
||||||
using Ocelot.DownstreamRouteFinder;
|
using Ocelot.DownstreamRouteFinder;
|
||||||
using Ocelot.DownstreamRouteFinder.UrlMatcher;
|
using Ocelot.DownstreamRouteFinder.UrlMatcher;
|
||||||
using Ocelot.DownstreamUrlCreator.Middleware;
|
using Ocelot.DownstreamUrlCreator.Middleware;
|
||||||
using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer;
|
using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer;
|
||||||
using Ocelot.Logging;
|
using Ocelot.Logging;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
using Ocelot.Values;
|
using Ocelot.Values;
|
||||||
using TestStack.BDDfy;
|
using TestStack.BDDfy;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Ocelot.Request.Middleware;
|
using Ocelot.Request.Middleware;
|
||||||
using Ocelot.Configuration;
|
using Ocelot.Configuration;
|
||||||
using Ocelot.Middleware;
|
using Ocelot.Middleware;
|
||||||
|
|
||||||
public class DownstreamUrlCreatorMiddlewareTests
|
public class DownstreamUrlCreatorMiddlewareTests
|
||||||
{
|
{
|
||||||
private readonly Mock<IDownstreamPathPlaceholderReplacer> _downstreamUrlTemplateVariableReplacer;
|
private readonly Mock<IDownstreamPathPlaceholderReplacer> _downstreamUrlTemplateVariableReplacer;
|
||||||
private OkResponse<DownstreamPath> _downstreamPath;
|
private OkResponse<DownstreamPath> _downstreamPath;
|
||||||
private readonly Mock<IOcelotLoggerFactory> _loggerFactory;
|
private readonly Mock<IOcelotLoggerFactory> _loggerFactory;
|
||||||
private Mock<IOcelotLogger> _logger;
|
private Mock<IOcelotLogger> _logger;
|
||||||
private DownstreamUrlCreatorMiddleware _middleware;
|
private DownstreamUrlCreatorMiddleware _middleware;
|
||||||
private readonly DownstreamContext _downstreamContext;
|
private readonly DownstreamContext _downstreamContext;
|
||||||
private readonly OcelotRequestDelegate _next;
|
private readonly OcelotRequestDelegate _next;
|
||||||
private readonly HttpRequestMessage _request;
|
private readonly HttpRequestMessage _request;
|
||||||
|
|
||||||
public DownstreamUrlCreatorMiddlewareTests()
|
public DownstreamUrlCreatorMiddlewareTests()
|
||||||
{
|
|
||||||
_downstreamContext = new DownstreamContext(new DefaultHttpContext());
|
|
||||||
_loggerFactory = new Mock<IOcelotLoggerFactory>();
|
|
||||||
_logger = new Mock<IOcelotLogger>();
|
|
||||||
_loggerFactory.Setup(x => x.CreateLogger<DownstreamUrlCreatorMiddleware>()).Returns(_logger.Object);
|
|
||||||
_downstreamUrlTemplateVariableReplacer = new Mock<IDownstreamPathPlaceholderReplacer>();
|
|
||||||
_request = new HttpRequestMessage(HttpMethod.Get, "https://my.url/abc/?q=123");
|
|
||||||
_downstreamContext.DownstreamRequest = new DownstreamRequest(_request);
|
|
||||||
_next = context => Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void should_replace_scheme_and_path()
|
|
||||||
{
|
|
||||||
var downstreamReRoute = new DownstreamReRouteBuilder()
|
|
||||||
.WithDownstreamPathTemplate("any old string")
|
|
||||||
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
|
||||||
.WithDownstreamScheme("https")
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
var config = new ServiceProviderConfigurationBuilder()
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
this.Given(x => x.GivenTheDownStreamRouteIs(
|
|
||||||
new DownstreamRoute(
|
|
||||||
new List<PlaceholderNameAndValue>(),
|
|
||||||
new ReRouteBuilder()
|
|
||||||
.WithDownstreamReRoute(downstreamReRoute)
|
|
||||||
.WithUpstreamHttpMethod(new List<string> { "Get" })
|
|
||||||
.Build())))
|
|
||||||
.And(x => x.GivenTheDownstreamRequestUriIs("http://my.url/abc?q=123"))
|
|
||||||
.And(x => GivenTheServiceProviderConfigIs(config))
|
|
||||||
.And(x => x.GivenTheUrlReplacerWillReturn("/api/products/1"))
|
|
||||||
.When(x => x.WhenICallTheMiddleware())
|
|
||||||
.Then(x => x.ThenTheDownstreamRequestUriIs("https://my.url:80/api/products/1?q=123"))
|
|
||||||
.BDDfy();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void should_not_create_service_fabric_url()
|
|
||||||
{
|
|
||||||
var downstreamReRoute = new DownstreamReRouteBuilder()
|
|
||||||
.WithDownstreamPathTemplate("any old string")
|
|
||||||
.WithUpstreamHttpMethod(new List<string> { "Get" })
|
|
||||||
.WithDownstreamScheme("https")
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
var config = new ServiceProviderConfigurationBuilder()
|
|
||||||
.WithType("ServiceFabric")
|
|
||||||
.WithHost("localhost")
|
|
||||||
.WithPort(19081)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
this.Given(x => x.GivenTheDownStreamRouteIs(
|
|
||||||
new DownstreamRoute(
|
|
||||||
new List<PlaceholderNameAndValue>(),
|
|
||||||
new ReRouteBuilder()
|
|
||||||
.WithDownstreamReRoute(downstreamReRoute)
|
|
||||||
.WithUpstreamHttpMethod(new List<string> { "Get" })
|
|
||||||
.Build())))
|
|
||||||
.And(x => x.GivenTheDownstreamRequestUriIs("http://my.url/abc?q=123"))
|
|
||||||
.And(x => GivenTheServiceProviderConfigIs(config))
|
|
||||||
.And(x => x.GivenTheUrlReplacerWillReturn("/api/products/1"))
|
|
||||||
.When(x => x.WhenICallTheMiddleware())
|
|
||||||
.Then(x => x.ThenTheDownstreamRequestUriIs("https://my.url:80/api/products/1?q=123"))
|
|
||||||
.BDDfy();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void should_create_service_fabric_url()
|
|
||||||
{
|
|
||||||
var downstreamReRoute = new DownstreamReRouteBuilder()
|
|
||||||
.WithDownstreamScheme("http")
|
|
||||||
.WithServiceName("Ocelot/OcelotApp")
|
|
||||||
.WithUseServiceDiscovery(true)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
var downstreamRoute = new DownstreamRoute(
|
|
||||||
new List<PlaceholderNameAndValue>(),
|
|
||||||
new ReRouteBuilder()
|
|
||||||
.WithDownstreamReRoute(downstreamReRoute)
|
|
||||||
.Build());
|
|
||||||
|
|
||||||
var config = new ServiceProviderConfigurationBuilder()
|
|
||||||
.WithType("ServiceFabric")
|
|
||||||
.WithHost("localhost")
|
|
||||||
.WithPort(19081)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute))
|
|
||||||
.And(x => GivenTheServiceProviderConfigIs(config))
|
|
||||||
.And(x => x.GivenTheDownstreamRequestUriIs("http://localhost:19081"))
|
|
||||||
.And(x => x.GivenTheUrlReplacerWillReturn("/api/products/1"))
|
|
||||||
.When(x => x.WhenICallTheMiddleware())
|
|
||||||
.Then(x => x.ThenTheDownstreamRequestUriIs("http://localhost:19081/Ocelot/OcelotApp/api/products/1?cmd=instance"))
|
|
||||||
.BDDfy();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void should_create_service_fabric_url_with_query_string_for_stateless_service()
|
|
||||||
{
|
|
||||||
var downstreamReRoute = new DownstreamReRouteBuilder()
|
|
||||||
.WithDownstreamScheme("http")
|
|
||||||
.WithServiceName("Ocelot/OcelotApp")
|
|
||||||
.WithUseServiceDiscovery(true)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
var downstreamRoute = new DownstreamRoute(
|
|
||||||
new List<PlaceholderNameAndValue>(),
|
|
||||||
new ReRouteBuilder()
|
|
||||||
.WithDownstreamReRoute(downstreamReRoute)
|
|
||||||
.Build());
|
|
||||||
|
|
||||||
var config = new ServiceProviderConfigurationBuilder()
|
|
||||||
.WithType("ServiceFabric")
|
|
||||||
.WithHost("localhost")
|
|
||||||
.WithPort(19081)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute))
|
|
||||||
.And(x => GivenTheServiceProviderConfigIs(config))
|
|
||||||
.And(x => x.GivenTheDownstreamRequestUriIs("http://localhost:19081?Tom=test&laura=1"))
|
|
||||||
.And(x => x.GivenTheUrlReplacerWillReturn("/api/products/1"))
|
|
||||||
.When(x => x.WhenICallTheMiddleware())
|
|
||||||
.Then(x => x.ThenTheDownstreamRequestUriIs("http://localhost:19081/Ocelot/OcelotApp/api/products/1?Tom=test&laura=1&cmd=instance"))
|
|
||||||
.BDDfy();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void should_create_service_fabric_url_with_query_string_for_stateful_service()
|
|
||||||
{
|
|
||||||
var downstreamReRoute = new DownstreamReRouteBuilder()
|
|
||||||
.WithDownstreamScheme("http")
|
|
||||||
.WithServiceName("Ocelot/OcelotApp")
|
|
||||||
.WithUseServiceDiscovery(true)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
var downstreamRoute = new DownstreamRoute(
|
|
||||||
new List<PlaceholderNameAndValue>(),
|
|
||||||
new ReRouteBuilder()
|
|
||||||
.WithDownstreamReRoute(downstreamReRoute)
|
|
||||||
.Build());
|
|
||||||
|
|
||||||
var config = new ServiceProviderConfigurationBuilder()
|
|
||||||
.WithType("ServiceFabric")
|
|
||||||
.WithHost("localhost")
|
|
||||||
.WithPort(19081)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute))
|
|
||||||
.And(x => GivenTheServiceProviderConfigIs(config))
|
|
||||||
.And(x => x.GivenTheDownstreamRequestUriIs("http://localhost:19081?PartitionKind=test&PartitionKey=1"))
|
|
||||||
.And(x => x.GivenTheUrlReplacerWillReturn("/api/products/1"))
|
|
||||||
.When(x => x.WhenICallTheMiddleware())
|
|
||||||
.Then(x => x.ThenTheDownstreamRequestUriIs("http://localhost:19081/Ocelot/OcelotApp/api/products/1?PartitionKind=test&PartitionKey=1"))
|
|
||||||
.BDDfy();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenTheServiceProviderConfigIs(ServiceProviderConfiguration config)
|
|
||||||
{
|
{
|
||||||
var configuration = new InternalConfiguration(null, null, config, null, null, null, null, null);
|
_downstreamContext = new DownstreamContext(new DefaultHttpContext());
|
||||||
_downstreamContext.Configuration = configuration;
|
_loggerFactory = new Mock<IOcelotLoggerFactory>();
|
||||||
}
|
_logger = new Mock<IOcelotLogger>();
|
||||||
|
_loggerFactory.Setup(x => x.CreateLogger<DownstreamUrlCreatorMiddleware>()).Returns(_logger.Object);
|
||||||
private void WhenICallTheMiddleware()
|
_downstreamUrlTemplateVariableReplacer = new Mock<IDownstreamPathPlaceholderReplacer>();
|
||||||
{
|
_request = new HttpRequestMessage(HttpMethod.Get, "https://my.url/abc/?q=123");
|
||||||
_middleware = new DownstreamUrlCreatorMiddleware(_next, _loggerFactory.Object, _downstreamUrlTemplateVariableReplacer.Object);
|
_downstreamContext.DownstreamRequest = new DownstreamRequest(_request);
|
||||||
_middleware.Invoke(_downstreamContext).GetAwaiter().GetResult();
|
_next = context => Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute)
|
[Fact]
|
||||||
{
|
public void should_replace_scheme_and_path()
|
||||||
_downstreamContext.TemplatePlaceholderNameAndValues = downstreamRoute.TemplatePlaceholderNameAndValues;
|
{
|
||||||
_downstreamContext.DownstreamReRoute = downstreamRoute.ReRoute.DownstreamReRoute[0];
|
var downstreamReRoute = new DownstreamReRouteBuilder()
|
||||||
}
|
.WithDownstreamPathTemplate("any old string")
|
||||||
|
.WithUpstreamHttpMethod(new List<string> {"Get"})
|
||||||
private void GivenTheDownstreamRequestUriIs(string uri)
|
.WithDownstreamScheme("https")
|
||||||
{
|
.Build();
|
||||||
_request.RequestUri = new Uri(uri);
|
|
||||||
_downstreamContext.DownstreamRequest = new DownstreamRequest(_request);
|
var config = new ServiceProviderConfigurationBuilder()
|
||||||
}
|
.Build();
|
||||||
|
|
||||||
private void GivenTheUrlReplacerWillReturn(string path)
|
this.Given(x => x.GivenTheDownStreamRouteIs(
|
||||||
{
|
new DownstreamRoute(
|
||||||
_downstreamPath = new OkResponse<DownstreamPath>(new DownstreamPath(path));
|
new List<PlaceholderNameAndValue>(),
|
||||||
_downstreamUrlTemplateVariableReplacer
|
new ReRouteBuilder()
|
||||||
.Setup(x => x.Replace(It.IsAny<PathTemplate>(), It.IsAny<List<PlaceholderNameAndValue>>()))
|
.WithDownstreamReRoute(downstreamReRoute)
|
||||||
.Returns(_downstreamPath);
|
.WithUpstreamHttpMethod(new List<string> { "Get" })
|
||||||
}
|
.Build())))
|
||||||
|
.And(x => x.GivenTheDownstreamRequestUriIs("http://my.url/abc?q=123"))
|
||||||
private void ThenTheDownstreamRequestUriIs(string expectedUri)
|
.And(x => GivenTheServiceProviderConfigIs(config))
|
||||||
{
|
.And(x => x.GivenTheUrlReplacerWillReturn("/api/products/1"))
|
||||||
_downstreamContext.DownstreamRequest.ToHttpRequestMessage().RequestUri.OriginalString.ShouldBe(expectedUri);
|
.When(x => x.WhenICallTheMiddleware())
|
||||||
}
|
.Then(x => x.ThenTheDownstreamRequestUriIs("https://my.url:80/api/products/1?q=123"))
|
||||||
}
|
.And(x => ThenTheQueryStringIs("?q=123"))
|
||||||
}
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_not_create_service_fabric_url()
|
||||||
|
{
|
||||||
|
var downstreamReRoute = new DownstreamReRouteBuilder()
|
||||||
|
.WithDownstreamPathTemplate("any old string")
|
||||||
|
.WithUpstreamHttpMethod(new List<string> { "Get" })
|
||||||
|
.WithDownstreamScheme("https")
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var config = new ServiceProviderConfigurationBuilder()
|
||||||
|
.WithType("ServiceFabric")
|
||||||
|
.WithHost("localhost")
|
||||||
|
.WithPort(19081)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
this.Given(x => x.GivenTheDownStreamRouteIs(
|
||||||
|
new DownstreamRoute(
|
||||||
|
new List<PlaceholderNameAndValue>(),
|
||||||
|
new ReRouteBuilder()
|
||||||
|
.WithDownstreamReRoute(downstreamReRoute)
|
||||||
|
.WithUpstreamHttpMethod(new List<string> { "Get" })
|
||||||
|
.Build())))
|
||||||
|
.And(x => x.GivenTheDownstreamRequestUriIs("http://my.url/abc?q=123"))
|
||||||
|
.And(x => GivenTheServiceProviderConfigIs(config))
|
||||||
|
.And(x => x.GivenTheUrlReplacerWillReturn("/api/products/1"))
|
||||||
|
.When(x => x.WhenICallTheMiddleware())
|
||||||
|
.Then(x => x.ThenTheDownstreamRequestUriIs("https://my.url:80/api/products/1?q=123"))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_create_service_fabric_url()
|
||||||
|
{
|
||||||
|
var downstreamReRoute = new DownstreamReRouteBuilder()
|
||||||
|
.WithDownstreamScheme("http")
|
||||||
|
.WithServiceName("Ocelot/OcelotApp")
|
||||||
|
.WithUseServiceDiscovery(true)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var downstreamRoute = new DownstreamRoute(
|
||||||
|
new List<PlaceholderNameAndValue>(),
|
||||||
|
new ReRouteBuilder()
|
||||||
|
.WithDownstreamReRoute(downstreamReRoute)
|
||||||
|
.Build());
|
||||||
|
|
||||||
|
var config = new ServiceProviderConfigurationBuilder()
|
||||||
|
.WithType("ServiceFabric")
|
||||||
|
.WithHost("localhost")
|
||||||
|
.WithPort(19081)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute))
|
||||||
|
.And(x => GivenTheServiceProviderConfigIs(config))
|
||||||
|
.And(x => x.GivenTheDownstreamRequestUriIs("http://localhost:19081"))
|
||||||
|
.And(x => x.GivenTheUrlReplacerWillReturn("/api/products/1"))
|
||||||
|
.When(x => x.WhenICallTheMiddleware())
|
||||||
|
.Then(x => x.ThenTheDownstreamRequestUriIs("http://localhost:19081/Ocelot/OcelotApp/api/products/1?cmd=instance"))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_create_service_fabric_url_with_query_string_for_stateless_service()
|
||||||
|
{
|
||||||
|
var downstreamReRoute = new DownstreamReRouteBuilder()
|
||||||
|
.WithDownstreamScheme("http")
|
||||||
|
.WithServiceName("Ocelot/OcelotApp")
|
||||||
|
.WithUseServiceDiscovery(true)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var downstreamRoute = new DownstreamRoute(
|
||||||
|
new List<PlaceholderNameAndValue>(),
|
||||||
|
new ReRouteBuilder()
|
||||||
|
.WithDownstreamReRoute(downstreamReRoute)
|
||||||
|
.Build());
|
||||||
|
|
||||||
|
var config = new ServiceProviderConfigurationBuilder()
|
||||||
|
.WithType("ServiceFabric")
|
||||||
|
.WithHost("localhost")
|
||||||
|
.WithPort(19081)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute))
|
||||||
|
.And(x => GivenTheServiceProviderConfigIs(config))
|
||||||
|
.And(x => x.GivenTheDownstreamRequestUriIs("http://localhost:19081?Tom=test&laura=1"))
|
||||||
|
.And(x => x.GivenTheUrlReplacerWillReturn("/api/products/1"))
|
||||||
|
.When(x => x.WhenICallTheMiddleware())
|
||||||
|
.Then(x => x.ThenTheDownstreamRequestUriIs("http://localhost:19081/Ocelot/OcelotApp/api/products/1?Tom=test&laura=1&cmd=instance"))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_create_service_fabric_url_with_query_string_for_stateful_service()
|
||||||
|
{
|
||||||
|
var downstreamReRoute = new DownstreamReRouteBuilder()
|
||||||
|
.WithDownstreamScheme("http")
|
||||||
|
.WithServiceName("Ocelot/OcelotApp")
|
||||||
|
.WithUseServiceDiscovery(true)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var downstreamRoute = new DownstreamRoute(
|
||||||
|
new List<PlaceholderNameAndValue>(),
|
||||||
|
new ReRouteBuilder()
|
||||||
|
.WithDownstreamReRoute(downstreamReRoute)
|
||||||
|
.Build());
|
||||||
|
|
||||||
|
var config = new ServiceProviderConfigurationBuilder()
|
||||||
|
.WithType("ServiceFabric")
|
||||||
|
.WithHost("localhost")
|
||||||
|
.WithPort(19081)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute))
|
||||||
|
.And(x => GivenTheServiceProviderConfigIs(config))
|
||||||
|
.And(x => x.GivenTheDownstreamRequestUriIs("http://localhost:19081?PartitionKind=test&PartitionKey=1"))
|
||||||
|
.And(x => x.GivenTheUrlReplacerWillReturn("/api/products/1"))
|
||||||
|
.When(x => x.WhenICallTheMiddleware())
|
||||||
|
.Then(x => x.ThenTheDownstreamRequestUriIs("http://localhost:19081/Ocelot/OcelotApp/api/products/1?PartitionKind=test&PartitionKey=1"))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheServiceProviderConfigIs(ServiceProviderConfiguration config)
|
||||||
|
{
|
||||||
|
var configuration = new InternalConfiguration(null, null, config, null, null, null, null, null);
|
||||||
|
_downstreamContext.Configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WhenICallTheMiddleware()
|
||||||
|
{
|
||||||
|
_middleware = new DownstreamUrlCreatorMiddleware(_next, _loggerFactory.Object, _downstreamUrlTemplateVariableReplacer.Object);
|
||||||
|
_middleware.Invoke(_downstreamContext).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute)
|
||||||
|
{
|
||||||
|
_downstreamContext.TemplatePlaceholderNameAndValues = downstreamRoute.TemplatePlaceholderNameAndValues;
|
||||||
|
_downstreamContext.DownstreamReRoute = downstreamRoute.ReRoute.DownstreamReRoute[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheDownstreamRequestUriIs(string uri)
|
||||||
|
{
|
||||||
|
_request.RequestUri = new Uri(uri);
|
||||||
|
_downstreamContext.DownstreamRequest = new DownstreamRequest(_request);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheUrlReplacerWillReturn(string path)
|
||||||
|
{
|
||||||
|
_downstreamPath = new OkResponse<DownstreamPath>(new DownstreamPath(path));
|
||||||
|
_downstreamUrlTemplateVariableReplacer
|
||||||
|
.Setup(x => x.Replace(It.IsAny<PathTemplate>(), It.IsAny<List<PlaceholderNameAndValue>>()))
|
||||||
|
.Returns(_downstreamPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheDownstreamRequestUriIs(string expectedUri)
|
||||||
|
{
|
||||||
|
_downstreamContext.DownstreamRequest.ToHttpRequestMessage().RequestUri.OriginalString.ShouldBe(expectedUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheQueryStringIs(string queryString)
|
||||||
|
{
|
||||||
|
_downstreamContext.DownstreamRequest.Query.ShouldBe(queryString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user