diff --git a/src/Ocelot/Configuration/Builder/DownstreamRouteBuilder.cs b/src/Ocelot/Configuration/Builder/DownstreamRouteBuilder.cs index e185bf584..2f8d9812a 100644 --- a/src/Ocelot/Configuration/Builder/DownstreamRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/DownstreamRouteBuilder.cs @@ -44,6 +44,7 @@ public class DownstreamRouteBuilder private SecurityOptions _securityOptions; private string _downstreamHttpMethod; private Version _downstreamHttpVersion; + private HttpVersionPolicy _downstreamVersionPolicy; public DownstreamRouteBuilder() { @@ -263,6 +264,12 @@ public DownstreamRouteBuilder WithDownstreamHttpVersion(Version downstreamHttpVe return this; } + public DownstreamRouteBuilder WithDownstreamPolicyBuilder(HttpVersionPolicy downstreamVersionPolicy) + { + _downstreamVersionPolicy = downstreamVersionPolicy; + return this; + } + public DownstreamRoute Build() { return new DownstreamRoute( @@ -299,6 +306,7 @@ public DownstreamRoute Build() _dangerousAcceptAnyServerCertificateValidator, _securityOptions, _downstreamHttpMethod, - _downstreamHttpVersion); + _downstreamHttpVersion, + _downstreamVersionPolicy); } } diff --git a/src/Ocelot/Configuration/Creator/IVersionPolicyCreator.cs b/src/Ocelot/Configuration/Creator/IVersionPolicyCreator.cs new file mode 100644 index 000000000..027b64e34 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/IVersionPolicyCreator.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; + +namespace Ocelot.Configuration.Creator +{ + public interface IVersionPolicyCreator + { + HttpVersionPolicy Create(string downstreamVersionPolicy); + } +} diff --git a/src/Ocelot/Configuration/Creator/RoutesCreator.cs b/src/Ocelot/Configuration/Creator/RoutesCreator.cs index 7161aad78..1a3261d1c 100644 --- a/src/Ocelot/Configuration/Creator/RoutesCreator.cs +++ b/src/Ocelot/Configuration/Creator/RoutesCreator.cs @@ -26,6 +26,7 @@ public class RoutesCreator : IRoutesCreator private readonly IRouteKeyCreator _routeKeyCreator; private readonly ISecurityOptionsCreator _securityOptionsCreator; private readonly IVersionCreator _versionCreator; + private readonly IVersionPolicyCreator _versionPolicyCreator; public RoutesCreator( IClaimsToThingCreator claimsToThingCreator, @@ -42,7 +43,8 @@ public RoutesCreator( ILoadBalancerOptionsCreator loadBalancerOptionsCreator, IRouteKeyCreator routeKeyCreator, ISecurityOptionsCreator securityOptionsCreator, - IVersionCreator versionCreator + IVersionCreator versionCreator, + IVersionPolicyCreator versionPolicyCreator ) { _routeKeyCreator = routeKeyCreator; @@ -61,6 +63,7 @@ IVersionCreator versionCreator _loadBalancerOptionsCreator = loadBalancerOptionsCreator; _securityOptionsCreator = securityOptionsCreator; _versionCreator = versionCreator; + _versionPolicyCreator = versionPolicyCreator; } public List Create(FileConfiguration fileConfiguration) @@ -111,6 +114,8 @@ private DownstreamRoute SetUpDownstreamRoute(FileRoute fileRoute, FileGlobalConf var securityOptions = _securityOptionsCreator.Create(fileRoute.SecurityOptions); var downstreamHttpVersion = _versionCreator.Create(fileRoute.DownstreamHttpVersion); + + var downstreamVersionPolicy = _versionPolicyCreator.Create(fileRoute.DownstreamVersionPolicy); var route = new DownstreamRouteBuilder() .WithKey(fileRoute.Key) @@ -147,6 +152,7 @@ private DownstreamRoute SetUpDownstreamRoute(FileRoute fileRoute, FileGlobalConf .WithDangerousAcceptAnyServerCertificateValidator(fileRoute.DangerousAcceptAnyServerCertificateValidator) .WithSecurityOptions(securityOptions) .WithDownstreamHttpVersion(downstreamHttpVersion) + .WithDownstreamPolicyBuilder(downstreamVersionPolicy) .WithDownStreamHttpMethod(fileRoute.DownstreamHttpMethod) .Build(); diff --git a/src/Ocelot/Configuration/Creator/VersionPolicyCreator.cs b/src/Ocelot/Configuration/Creator/VersionPolicyCreator.cs new file mode 100644 index 000000000..aedcda354 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/VersionPolicyCreator.cs @@ -0,0 +1,18 @@ +using System.Net.Http; + +namespace Ocelot.Configuration.Creator +{ + public class VersionPolicyCreator : IVersionPolicyCreator + { + public HttpVersionPolicy Create(string downstreamVersionPolicy) + { + return downstreamVersionPolicy switch + { + "exact" => HttpVersionPolicy.RequestVersionExact, + "upgradeable" => HttpVersionPolicy.RequestVersionOrHigher, + "downgradeable" => HttpVersionPolicy.RequestVersionOrLower, + _ => HttpVersionPolicy.RequestVersionOrLower, + }; + } + } +} diff --git a/src/Ocelot/Configuration/DownstreamRoute.cs b/src/Ocelot/Configuration/DownstreamRoute.cs index 40f96b42a..54fbebeb3 100644 --- a/src/Ocelot/Configuration/DownstreamRoute.cs +++ b/src/Ocelot/Configuration/DownstreamRoute.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; - +using System.Net.Http; using Ocelot.Configuration.Creator; using Ocelot.Values; @@ -43,7 +43,8 @@ public DownstreamRoute( bool dangerousAcceptAnyServerCertificateValidator, SecurityOptions securityOptions, string downstreamHttpMethod, - Version downstreamHttpVersion) + Version downstreamHttpVersion, + HttpVersionPolicy downstreamVersionPolicy) { DangerousAcceptAnyServerCertificateValidator = dangerousAcceptAnyServerCertificateValidator; AddHeadersToDownstream = addHeadersToDownstream; @@ -79,6 +80,7 @@ public DownstreamRoute( SecurityOptions = securityOptions; DownstreamHttpMethod = downstreamHttpMethod; DownstreamHttpVersion = downstreamHttpVersion; + DownstreamVersionPolicy = downstreamVersionPolicy; } public string Key { get; } @@ -114,6 +116,7 @@ public DownstreamRoute( public bool DangerousAcceptAnyServerCertificateValidator { get; } public SecurityOptions SecurityOptions { get; } public string DownstreamHttpMethod { get; } - public Version DownstreamHttpVersion { get; } + public Version DownstreamHttpVersion { get; } + public HttpVersionPolicy DownstreamVersionPolicy { get; } } } diff --git a/src/Ocelot/Configuration/File/FileRoute.cs b/src/Ocelot/Configuration/File/FileRoute.cs index 6ff9856b2..cc99bc1c0 100644 --- a/src/Ocelot/Configuration/File/FileRoute.cs +++ b/src/Ocelot/Configuration/File/FileRoute.cs @@ -57,5 +57,6 @@ public FileRoute() public bool DangerousAcceptAnyServerCertificateValidator { get; set; } public FileSecurityOptions SecurityOptions { get; set; } public string DownstreamHttpVersion { get; set; } + public string DownstreamVersionPolicy { get; set; } } } diff --git a/src/Ocelot/Configuration/Validator/RouteFluentValidator.cs b/src/Ocelot/Configuration/Validator/RouteFluentValidator.cs index 7992d40f9..e266bbdb2 100644 --- a/src/Ocelot/Configuration/Validator/RouteFluentValidator.cs +++ b/src/Ocelot/Configuration/Validator/RouteFluentValidator.cs @@ -91,6 +91,11 @@ public RouteFluentValidator(IAuthenticationSchemeProvider authenticationSchemePr { RuleFor(r => r.DownstreamHttpVersion).Matches("^[0-9]([.,][0-9]{1,1})?$"); }); + + When(route => !string.IsNullOrEmpty(route.DownstreamVersionPolicy), () => + { + RuleFor(r => r.DownstreamVersionPolicy).Matches("^(exact|upgradeable|downgradeable)$"); + }); } private async Task IsSupportedAuthenticationProviders(FileAuthenticationOptions authenticationOptions, CancellationToken cancellationToken) diff --git a/src/Ocelot/Request/Mapper/RequestMapper.cs b/src/Ocelot/Request/Mapper/RequestMapper.cs index 8a5bba465..8a7589e49 100644 --- a/src/Ocelot/Request/Mapper/RequestMapper.cs +++ b/src/Ocelot/Request/Mapper/RequestMapper.cs @@ -29,6 +29,7 @@ public async Task> Map(HttpRequest request, Downstr Method = MapMethod(request, downstreamRoute), RequestUri = MapUri(request), Version = downstreamRoute.DownstreamHttpVersion, + VersionPolicy = downstreamRoute.DownstreamVersionPolicy, }; MapHeaders(request, requestMessage); diff --git a/src/Ocelot/Requester/HttpClientBuilder.cs b/src/Ocelot/Requester/HttpClientBuilder.cs index 89ee7ef18..726cab700 100644 --- a/src/Ocelot/Requester/HttpClientBuilder.cs +++ b/src/Ocelot/Requester/HttpClientBuilder.cs @@ -63,6 +63,8 @@ public IHttpClient Create(DownstreamRoute downstreamRoute) _httpClient = new HttpClient(CreateHttpMessageHandler(handler, downstreamRoute)) { Timeout = timeout, + DefaultRequestVersion = downstreamRoute.DownstreamHttpVersion, + DefaultVersionPolicy = downstreamRoute.DownstreamVersionPolicy, }; _client = new HttpClientWrapper(_httpClient); diff --git a/test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs index 4b07033ed..e87e4fe91 100644 --- a/test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs @@ -9,6 +9,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net.Http; using TestStack.BDDfy; using Xunit; @@ -32,6 +33,7 @@ public class RoutesCreatorTests private readonly Mock _rrkCreator; private readonly Mock _soCreator; private readonly Mock _versionCreator; + private readonly Mock _versionPolicyCreator; private FileConfiguration _fileConfig; private RouteOptions _rro; private string _requestId; @@ -48,6 +50,7 @@ public class RoutesCreatorTests private LoadBalancerOptions _lbo; private List _result; private Version _expectedVersion; + private HttpVersionPolicy _expectedVersionPolicy; public RoutesCreatorTests() { @@ -66,6 +69,7 @@ public RoutesCreatorTests() _rrkCreator = new Mock(); _soCreator = new Mock(); _versionCreator = new Mock(); + _versionPolicyCreator = new Mock(); _creator = new RoutesCreator( _cthCreator.Object, @@ -82,7 +86,8 @@ public RoutesCreatorTests() _lboCreator.Object, _rrkCreator.Object, _soCreator.Object, - _versionCreator.Object + _versionCreator.Object, + _versionPolicyCreator.Object ); } @@ -160,6 +165,7 @@ private void ThenTheDependenciesAreCalledCorrectly() private void GivenTheDependenciesAreSetUpCorrectly() { _expectedVersion = new Version("1.1"); + _expectedVersionPolicy = HttpVersionPolicy.RequestVersionOrLower; _rro = new RouteOptions(false, false, false, false, false); _requestId = "testy"; _rrk = "besty"; @@ -188,6 +194,7 @@ private void GivenTheDependenciesAreSetUpCorrectly() _daCreator.Setup(x => x.Create(It.IsAny())).Returns(_dhp); _lboCreator.Setup(x => x.Create(It.IsAny())).Returns(_lbo); _versionCreator.Setup(x => x.Create(It.IsAny())).Returns(_expectedVersion); + _versionPolicyCreator.Setup(x => x.Create(It.IsAny())).Returns(_expectedVersionPolicy); } private void ThenTheRoutesAreCreated() @@ -216,6 +223,7 @@ private void WhenICreate() private void ThenTheRouteIsSet(FileRoute expected, int routeIndex) { _result[routeIndex].DownstreamRoute[0].DownstreamHttpVersion.ShouldBe(_expectedVersion); + _result[routeIndex].DownstreamRoute[0].DownstreamVersionPolicy.ShouldBe(_expectedVersionPolicy); _result[routeIndex].DownstreamRoute[0].IsAuthenticated.ShouldBe(_rro.IsAuthenticated); _result[routeIndex].DownstreamRoute[0].IsAuthorized.ShouldBe(_rro.IsAuthorized); _result[routeIndex].DownstreamRoute[0].IsCached.ShouldBe(_rro.IsCached); diff --git a/test/Ocelot.UnitTests/Configuration/VersionPolicyCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/VersionPolicyCreatorTests.cs new file mode 100644 index 000000000..b1a3eebd5 --- /dev/null +++ b/test/Ocelot.UnitTests/Configuration/VersionPolicyCreatorTests.cs @@ -0,0 +1,62 @@ +using Ocelot.Configuration.Creator; +using System.Net.Http; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class VersionPolicyCreatorTests + { + private readonly VersionPolicyCreator _creator; + private string _input; + private HttpVersionPolicy _result; + + public VersionPolicyCreatorTests() + { + _creator = new VersionPolicyCreator(); + } + + [Fact] + public void should_create_version_policy_based_on_input() + { + this.Given(_ => GivenTheInput("upgradeable")) + .When(_ => WhenICreate()) + .Then(_ => ThenTheResultIs(HttpVersionPolicy.RequestVersionOrHigher)) + .BDDfy(); + } + + [Fact] + public void should_default_to_request_version_or_lower() + { + this.Given(_ => GivenTheInput(string.Empty)) + .When(_ => WhenICreate()) + .Then(_ => ThenTheResultIs(HttpVersionPolicy.RequestVersionOrLower)) + .BDDfy(); + } + + [Fact] + public void should_default_to_request_version_or_lower_when_setting_gibberish() + { + this.Given(_ => GivenTheInput("string is gibberish")) + .When(_ => WhenICreate()) + .Then(_ => ThenTheResultIs(HttpVersionPolicy.RequestVersionOrLower)) + .BDDfy(); + } + + private void GivenTheInput(string input) + { + _input = input; + } + + private void WhenICreate() + { + _result = _creator.Create(_input); + } + + private void ThenTheResultIs(HttpVersionPolicy result) + { + _result.ShouldBe(result); + } + } +}