diff --git a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinder.cs b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinder.cs index 18a662c7e..bb5b87dfd 100644 --- a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinder.cs @@ -17,7 +17,9 @@ public Response> Find(string path, string query, s for (var counterForTemplate = 0; counterForTemplate < pathTemplate.Length; counterForTemplate++) { - if (IsPlaceholder(pathTemplate[counterForTemplate]) && CharactersDontMatch(pathTemplate[counterForTemplate], path[counterForPath]) && ContinueScanningUrl(counterForPath, path.Length)) + if (IsPlaceholder(pathTemplate[counterForTemplate]) + && CharactersDontMatch(pathTemplate[counterForTemplate], path[counterForPath]) + && ContinueScanningUrl(counterForPath, path.Length)) { //should_find_multiple_query_string make test pass if (PassedQueryString(pathTemplate, counterForTemplate)) diff --git a/test/Ocelot.AcceptanceTests/RoutingTests.cs b/test/Ocelot.AcceptanceTests/RoutingTests.cs index bc19dd34f..c955fdd31 100644 --- a/test/Ocelot.AcceptanceTests/RoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingTests.cs @@ -14,6 +14,12 @@ public RoutingTests() { _serviceHandler = new ServiceHandler(); _steps = new Steps(); + } + + public void Dispose() + { + _serviceHandler.Dispose(); + _steps.Dispose(); } [Fact] @@ -43,7 +49,7 @@ public void should_not_match_forward_slash_in_pattern_before_next_forward_slash( }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}/", "/api/v1/aaaaaaaaa/cards", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}/", "/api/v1/aaaaaaaaa/cards", HttpStatusCode.OK, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/api/v1/aaaaaaaaa/cards")) @@ -88,7 +94,7 @@ public void should_return_response_200_with_forward_slash_and_placeholder_only() }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}/", "/", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}/", "/", HttpStatusCode.OK, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) @@ -139,7 +145,7 @@ public void should_return_response_200_favouring_forward_slash_with_path_route() }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}/", "/test", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}/", "/test", HttpStatusCode.OK, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/test")) @@ -189,7 +195,7 @@ public void should_return_response_200_favouring_forward_slash() }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}/", "/", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}/", "/", HttpStatusCode.OK, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) @@ -240,7 +246,7 @@ public void should_return_response_200_favouring_forward_slash_route_because_it_ }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}/", "/", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}/", "/", HttpStatusCode.OK, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) @@ -276,7 +282,7 @@ public void should_return_response_200_with_nothing_and_placeholder_only() }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", HttpStatusCode.OK, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway(string.Empty)) @@ -312,7 +318,7 @@ public void should_return_response_200_with_simple_url() }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", HttpStatusCode.OK, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) @@ -365,7 +371,7 @@ public void Bug() }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/api/v1/vacancy/1", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/api/v1/vacancy/1", HttpStatusCode.OK, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/vacancy/1")) @@ -401,7 +407,7 @@ public void should_return_response_200_when_path_missing_forward_slash_as_first_ }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/api/products", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/api/products", HttpStatusCode.OK, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) @@ -437,7 +443,7 @@ public void should_return_response_200_when_host_has_trailing_slash() }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/api/products", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/api/products", HttpStatusCode.OK, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) @@ -475,7 +481,7 @@ public void should_return_ok_when_upstream_url_ends_with_forward_slash_but_templ }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", downstreamBasePath, 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", downstreamBasePath, HttpStatusCode.OK, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway(url)) @@ -514,7 +520,7 @@ public void should_fix_issue_649(string url) }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn(baseUrl, "/authenticate", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn(baseUrl, "/authenticate", HttpStatusCode.OK, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway(url)) @@ -550,7 +556,7 @@ public void should_return_not_found_when_upstream_url_ends_with_forward_slash_bu }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/products", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/products", HttpStatusCode.OK, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products/")) @@ -587,7 +593,7 @@ public void should_return_200_found(string downstreamPathTemplate, string upstre }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", downstreamPathTemplate, 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", downstreamPathTemplate, HttpStatusCode.OK, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway(requestURL)) @@ -623,7 +629,7 @@ public void should_return_response_200_with_complex_url() }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/api/products/1", 200, "Some Product")) + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/api/products/1", HttpStatusCode.OK, "Some Product")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products/1")) @@ -659,7 +665,7 @@ public void should_return_response_200_with_complex_url_that_starts_with_placeho }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/api/23/products/1", 200, "Some Product")) + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/api/23/products/1", HttpStatusCode.OK, "Some Product")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("23/products/1")) @@ -695,7 +701,7 @@ public void should_not_add_trailing_slash_to_downstream_url() }, }; - this.Given(x => GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/api/products/1", 200, "Some Product")) + this.Given(x => GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/api/products/1", HttpStatusCode.OK, "Some Product")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products/1")) @@ -730,7 +736,7 @@ public void should_return_response_201_with_simple_url() }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 201, string.Empty)) + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", HttpStatusCode.Created, string.Empty)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .And(x => _steps.GivenThePostHasContent("postContent")) @@ -766,7 +772,7 @@ public void should_return_response_201_with_complex_query_string() }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/newThing", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/newThing", HttpStatusCode.OK, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/newThing?DeviceType=IphoneApp&Browser=moonpigIphone&BrowserString=-&CountryCode=123&DeviceName=iPhone 5 (GSM+CDMA)&OperatingSystem=iPhone OS 7.1.2&BrowserVersion=3708AdHoc&ipAddress=-")) @@ -802,7 +808,7 @@ public void should_return_response_200_with_placeholder_for_final_url_path() }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/api/products/1", 200, "Some Product")) + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/api/products/1", HttpStatusCode.OK, "Some Product")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/myApp1Name/api/products/1")) @@ -823,25 +829,8 @@ public void should_return_response_200_with_placeholder_for_final_url_path() public void should_return_correct_downstream_when_omitting_ending_placeholder(string downstreamPathTemplate, string upstreamPathTemplate, string requestURL, string downstreamURL, string queryString) { var port = PortFinder.GetRandomPort(); - - var configuration = new FileConfiguration - { - Routes = new List - { - new() - { - DownstreamPathTemplate = downstreamPathTemplate, - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new("localhost", port), - }, - UpstreamPathTemplate = upstreamPathTemplate, - UpstreamHttpMethod = new List { "Get" }, - }, - }, - }; - this.Given(x => GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Test Body")) + var configuration = GivenDefaultConfiguration(port, upstreamPathTemplate, downstreamPathTemplate); + this.Given(x => GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", HttpStatusCode.OK, "Hello from Aly")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway(requestURL)) @@ -856,6 +845,28 @@ public void should_return_correct_downstream_when_omitting_ending_placeholder(st .BDDfy(); } + [Theory(DisplayName = "Catch All Query String should be forwarded with all query string parameters with(out) last slash")] + [Trait("Link", "https://ocelot.readthedocs.io/en/latest/features/routing.html#catch-all-query-string")] + [InlineData("/apipath/contracts?{everything}", "/contracts?{everything}", "/contracts?", "/apipath/contracts", "?")] + [InlineData("/apipath/contracts?{everything}", "/contracts?{everything}", "/contracts?p1=v1&p2=v2", "/apipath/contracts", "?p1=v1&p2=v2")] + [InlineData("/apipath/contracts/?{everything}", "/contracts/?{everything}", "/contracts/?", "/apipath/contracts/", "?")] + [InlineData("/apipath/contracts/?{everything}", "/contracts/?{everything}", "/contracts/?p3=v3&p4=v4", "/apipath/contracts/", "?p3=v3&p4=v4")] + [InlineData("/apipath/contracts?{everything}", "/contracts?{everything}", "/contracts?filter=(-something+123+else)", "/apipath/contracts", "?filter=(-something+123+else)")] + public void Should_forward_Catch_All_query_string_when_last_slash(string downstream, string upstream, string requestURL, string downstreamPath, string queryString) + { + var port = PortFinder.GetRandomPort(); + var configuration = GivenDefaultConfiguration(port, upstream, downstream); + this.Given(x => GivenThereIsAServiceRunningOn($"http://localhost:{port}", downstreamPath, HttpStatusCode.OK, "Hello from Raman")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway(requestURL)) + .Then(x => ThenTheDownstreamUrlPathShouldBe(downstreamPath)) // ! + .And(x => x.ThenTheDownstreamUrlQueryStringShouldBe(queryString)) // !! + .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Raman")) + .BDDfy(); + } + [Fact] public void should_return_response_201_with_simple_url_and_multiple_upstream_http_method() { @@ -883,7 +894,7 @@ public void should_return_response_201_with_simple_url_and_multiple_upstream_htt }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", string.Empty, 201, string.Empty)) + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", string.Empty, HttpStatusCode.Created, string.Empty)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .And(x => _steps.GivenThePostHasContent("postContent")) @@ -919,7 +930,7 @@ public void should_return_response_200_with_simple_url_and_any_upstream_http_met }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", HttpStatusCode.OK, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) @@ -972,7 +983,7 @@ public void should_return_404_when_calling_upstream_route_with_no_matching_downs }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/api/v1/vacancy/1", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/api/v1/vacancy/1", HttpStatusCode.OK, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("api/vacancy/1")) @@ -1007,7 +1018,7 @@ public void should_not_set_trailing_slash_on_url_template() }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/api/swagger/lib/backbone-min.js", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/api/swagger/lib/backbone-min.js", HttpStatusCode.OK, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/platform/swagger/lib/backbone-min.js")) @@ -1060,7 +1071,7 @@ public void should_use_priority() }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}/", "/goods/delete", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}/", "/goods/delete", HttpStatusCode.OK, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/goods/delete")) @@ -1096,7 +1107,7 @@ public void should_match_multiple_paths_with_catch_all() }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}/", "/test/toot", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}/", "/test/toot", HttpStatusCode.OK, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/test/toot")) @@ -1147,7 +1158,7 @@ public void should_fix_issue_271() }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}/", "/api/v1/modules/Test", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}/", "/api/v1/modules/Test", HttpStatusCode.OK, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/api/v1/modules/Test")) @@ -1156,7 +1167,7 @@ public void should_fix_issue_271() .BDDfy(); } - private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody) + private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, HttpStatusCode statusCode, string responseBody) { _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context => { @@ -1165,12 +1176,12 @@ private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int if (_downstreamPath != basePath) { - context.Response.StatusCode = statusCode; + context.Response.StatusCode = (int)statusCode; await context.Response.WriteAsync("downstream path didnt match base path"); } else { - context.Response.StatusCode = statusCode; + context.Response.StatusCode = (int)statusCode; await context.Response.WriteAsync(responseBody); } }); @@ -1184,12 +1195,24 @@ internal void ThenTheDownstreamUrlPathShouldBe(string expectedDownstreamPath) internal void ThenTheDownstreamUrlQueryStringShouldBe(string expectedQueryString) { _downstreamQuery.ShouldBe(expectedQueryString); - } - - public void Dispose() - { - _serviceHandler.Dispose(); - _steps.Dispose(); - } + } + + private FileConfiguration GivenDefaultConfiguration(int port, string upstream, string downstream) => new() + { + Routes = new() + { + new() + { + DownstreamPathTemplate = downstream, + DownstreamScheme = Uri.UriSchemeHttp, + DownstreamHostAndPorts = + [ + new("localhost", port), + ], + UpstreamPathTemplate = upstream, + UpstreamHttpMethod =[HttpMethods.Get], + }, + }, + }; } }