#451 started implementing querystring support in templates (#459)

* #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:
Tom Pallister 2018-07-10 18:00:17 +01:00 committed by GitHub
parent 89c3887d36
commit 75f9a8f9be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 1005 additions and 836 deletions

View File

@ -185,3 +185,34 @@ 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.

View File

@ -28,14 +28,14 @@ namespace Ocelot.DownstreamUrlCreator.Middleware
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;
} }
@ -43,13 +43,26 @@ namespace Ocelot.DownstreamUrlCreator.Middleware
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))
{
context.DownstreamRequest.AbsolutePath = GetPath(dsPath);
context.DownstreamRequest.Query = GetQueryString(dsPath);
// todo - do we need to add anything from the request query string onto the query from the
// templae?
}
else
{
context.DownstreamRequest.AbsolutePath = dsPath.Value;
}
} }
Logger.LogDebug($"Downstream url is {context.DownstreamRequest}"); Logger.LogDebug($"Downstream url is {context.DownstreamRequest}");
@ -57,6 +70,21 @@ namespace Ocelot.DownstreamUrlCreator.Middleware
await _next.Invoke(context); await _next.Invoke(context);
} }
private string GetPath(DownstreamPath dsPath)
{
return dsPath.Value.Substring(0, dsPath.Value.IndexOf("?"));
}
private string GetQueryString(DownstreamPath dsPath)
{
return dsPath.Value.Substring(dsPath.Value.IndexOf("?"));
}
private bool ContainsQueryString(DownstreamPath dsPath)
{
return dsPath.Value.Contains("?");
}
private (string path, string query) CreateServiceFabricUri(DownstreamContext context, Response<DownstreamPath> dsPath) private (string path, string query) CreateServiceFabricUri(DownstreamContext context, Response<DownstreamPath> dsPath)
{ {
var query = context.DownstreamRequest.Query; var query = context.DownstreamRequest.Query;

View 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();
}
}
}

View File

@ -68,6 +68,7 @@
.And(x => x.GivenTheUrlReplacerWillReturn("/api/products/1")) .And(x => x.GivenTheUrlReplacerWillReturn("/api/products/1"))
.When(x => x.WhenICallTheMiddleware()) .When(x => x.WhenICallTheMiddleware())
.Then(x => x.ThenTheDownstreamRequestUriIs("https://my.url:80/api/products/1?q=123")) .Then(x => x.ThenTheDownstreamRequestUriIs("https://my.url:80/api/products/1?q=123"))
.And(x => ThenTheQueryStringIs("?q=123"))
.BDDfy(); .BDDfy();
} }
@ -227,5 +228,10 @@
{ {
_downstreamContext.DownstreamRequest.ToHttpRequestMessage().RequestUri.OriginalString.ShouldBe(expectedUri); _downstreamContext.DownstreamRequest.ToHttpRequestMessage().RequestUri.OriginalString.ShouldBe(expectedUri);
} }
private void ThenTheQueryStringIs(string queryString)
{
_downstreamContext.DownstreamRequest.Query.ShouldBe(queryString);
}
} }
} }