From 3ac9b3bd87424c1829e5ee480d38ee29d40b2b75 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Wed, 31 Jan 2018 20:34:55 +0000 Subject: [PATCH] hacked together load balancing reroutes in fileconfig (#211) * hacked together load balancing reroutes in fileconfig * some renaming and refactoring * more renames * hacked away the old config json * test for issue 213 * renamed key * dont share ports * oops * updated docs * mvoed docs around * port being used --- .editorconfig | 18 +- .gitignore | 506 ++--- GitVersion.yml | 8 +- LICENSE.md | 14 +- Ocelot.sln | 174 +- README.md | 130 +- build-and-release-unstable.ps1 | 2 +- build-and-run-tests.ps1 | 2 +- build.cake | 952 +++++----- build.ps1 | 468 ++--- build.sh | 200 +- docs/Makefile | 450 ++--- docs/building/building.rst | 22 +- docs/building/overview.rst | 6 +- docs/building/releaseprocess.rst | 46 +- docs/building/tests.rst | 48 +- docs/conf.py | 718 +++---- docs/features/administration.rst | 162 +- docs/features/authentication.rst | 262 +-- docs/features/authorisation.rst | 36 +- docs/features/caching.rst | 42 +- docs/features/claimstransformation.rst | 142 +- docs/features/configuration.rst | 206 +- docs/features/headerstransformation.rst | 192 +- docs/features/loadbalancer.rst | 60 + docs/features/logging.rst | 26 +- docs/features/middlewareinjection.rst | 80 +- docs/features/qualityofservice.rst | 42 +- docs/features/raft.rst | 90 +- docs/features/requestid.rst | 120 +- docs/features/routing.rst | 206 +- docs/features/servicediscovery.rst | 74 +- docs/index.rst | 95 +- docs/introduction/bigpicture.rst | 76 +- docs/introduction/contributing.rst | 8 +- docs/introduction/gettingstarted.rst | 342 ++-- docs/introduction/notsupported.rst | 14 +- docs/make.bat | 562 +++--- docs/make.sh | 562 +++--- docs/readme.md | 28 +- global.json | 10 +- ocelot.postman_collection.json | 626 +++--- release.ps1 | 2 +- run-acceptance-tests.ps1 | 2 +- run-acceptance-tests.sh | 4 +- run-benchmarks.ps1 | 2 +- run-unit-tests.ps1 | 2 +- run-unit-tests.sh | 4 +- src/Ocelot/Authentication/BearerToken.cs | 30 +- .../SupportedAuthenticationProviders.cs | 16 +- ...nticationMiddlewareMiddlewareExtensions.cs | 22 +- .../ClaimValueNotAuthorisedError.cs | 24 +- src/Ocelot/Authorisation/ClaimsAuthoriser.cs | 104 +- src/Ocelot/Authorisation/IClaimsAuthoriser.cs | 24 +- src/Ocelot/Authorisation/IScopesAuthoriser.cs | 24 +- .../Middleware/AuthorisationMiddleware.cs | 234 +-- ...orisationMiddlewareMiddlewareExtensions.cs | 22 +- .../Authorisation/ScopeNotAuthorisedError.cs | 24 +- src/Ocelot/Authorisation/ScopesAuthoriser.cs | 100 +- src/Ocelot/Authorisation/UnauthorisedError.cs | 24 +- .../UserDoesNotHaveClaimError.cs | 24 +- src/Ocelot/Cache/CachedResponse.cs | 50 +- src/Ocelot/Cache/IOcelotCache.cs | 26 +- src/Ocelot/Cache/IRegionCreator.cs | 16 +- .../Cache/Middleware/OutputCacheMiddleware.cs | 232 +-- .../OutputCacheMiddlewareExtensions.cs | 24 +- src/Ocelot/Cache/OcelotCacheManagerCache.cs | 86 +- src/Ocelot/Cache/OutputCacheController.cs | 56 +- src/Ocelot/Cache/RegionCreator.cs | 46 +- src/Ocelot/Cache/Regions.cs | 24 +- src/Ocelot/Claims/AddClaimsToRequest.cs | 90 +- src/Ocelot/Claims/IAddClaimsToRequest.cs | 26 +- .../Middleware/ClaimsBuilderMiddleware.cs | 92 +- .../ClaimsBuilderMiddlewareExtensions.cs | 22 +- .../Authentication/HashMatcher.cs | 42 +- .../Authentication/IHashMatcher.cs | 12 +- .../Configuration/AuthenticationOptions.cs | 32 +- .../Builder/AuthenticationOptionsBuilder.cs | 52 +- .../Builder/QoSOptionsBuilder.cs | 68 +- .../Builder/RateLimitOptionsBuilder.cs | 142 +- .../Configuration/Builder/ReRouteBuilder.cs | 488 +++-- .../Builder/ReRouteOptionsBuilder.cs | 90 +- .../ServiceProviderConfigurationBuilder.cs | 48 +- src/Ocelot/Configuration/CacheOptions.cs | 28 +- src/Ocelot/Configuration/ClaimToThing.cs | 36 +- .../Creator/AuthenticationOptionsCreator.cs | 22 +- .../Creator/ClaimsToThingCreator.cs | 80 +- .../Creator/DownstreamAddressesCreator.cs | 14 + .../Creator/FileOcelotConfigurationCreator.cs | 10 +- .../Creator/HeaderFindAndReplaceCreator.cs | 136 +- .../Creator/HeaderTransformations.cs | 34 +- .../Creator/IAuthenticationOptionsCreator.cs | 18 +- .../Creator/IClaimsToThingCreator.cs | 16 +- .../Creator/IDownstreamAddressesCreator.cs | 10 + .../Creator/IHeaderFindAndReplaceCreator.cs | 20 +- .../Creator/IOcelotConfigurationCreator.cs | 20 +- .../Creator/IQoSOptionsCreator.cs | 16 +- .../Creator/IRateLimitOptionsCreator.cs | 16 +- .../Creator/IReRouteOptionsCreator.cs | 16 +- .../Creator/IRequestIdKeyCreator.cs | 16 +- .../IServiceProviderConfigurationCreator.cs | 16 +- .../IUpstreamTemplatePatternCreator.cs | 18 +- .../IdentityServerConfigurationCreator.cs | 52 +- .../Creator/QoSOptionsCreator.cs | 32 +- .../Creator/RateLimitOptionsCreator.cs | 64 +- .../Creator/ReRouteOptionsCreator.cs | 102 +- .../Creator/RequestIdKeyCreator.cs | 34 +- .../ServiceProviderConfigurationCreator.cs | 34 +- .../Creator/UpstreamTemplatePatternCreator.cs | 150 +- .../Configuration/DownstreamHostAndPort.cs | 13 + .../File/FileAuthenticationOptions.cs | 50 +- .../Configuration/File/FileCacheOptions.cs | 16 +- .../Configuration/File/FileConfiguration.cs | 32 +- .../File/FileGlobalConfiguration.cs | 36 +- .../Configuration/File/FileHostAndPort.cs | 8 + .../File/FileIdentityServerConfig.cs | 18 +- .../Configuration/File/FileJwtConfig.cs | 16 +- .../Configuration/File/FileQoSOptions.cs | 22 +- .../File/FileRateLimitOptions.cs | 78 +- .../Configuration/File/FileRateLimitRule.cs | 100 +- src/Ocelot/Configuration/File/FileReRoute.cs | 92 +- .../File/FileServiceDiscoveryProvider.cs | 14 +- .../FileConfigurationController.cs | 138 +- .../Configuration/HeaderFindAndReplace.cs | 38 +- .../Configuration/IOcelotConfiguration.cs | 22 +- .../Configuration/OcelotConfiguration.cs | 38 +- .../Parser/ClaimToThingConfigurationParser.cs | 146 +- .../IClaimToThingConfigurationParser.cs | 16 +- .../Parser/InstructionNotForClaimsError.cs | 24 +- .../Parser/NoInstructionsError.cs | 24 +- .../Parser/ParsingConfigurationHeaderError.cs | 26 +- .../Provider/FileConfigurationProvider.cs | 50 +- .../Provider/IFileConfigurationProvider.cs | 20 +- .../Provider/IIdentityServerConfiguration.cs | 30 +- .../Provider/IOcelotConfigurationProvider.cs | 22 +- .../Provider/IdentityServerConfiguration.cs | 62 +- .../Provider/OcelotConfigurationProvider.cs | 62 +- src/Ocelot/Configuration/QoSOptions.cs | 58 +- src/Ocelot/Configuration/RateLimitOptions.cs | 122 +- src/Ocelot/Configuration/RateLimitRule.cs | 48 +- src/Ocelot/Configuration/ReRoute.cs | 201 +- src/Ocelot/Configuration/ReRouteOptions.cs | 38 +- .../ConsulFileConfigurationPoller.cs | 158 +- .../ConsulFileConfigurationRepository.cs | 156 +- .../Repository/FileConfigurationRepository.cs | 102 +- .../IFileConfigurationRepository.cs | 22 +- .../IOcelotConfigurationRepository.cs | 22 +- .../InMemoryOcelotConfigurationRepository.cs | 58 +- .../UnableToSetConfigInConsulError.cs | 22 +- .../ServiceProviderConfiguraion.cs | 26 +- .../Setter/FileConfigurationSetter.cs | 82 +- .../Setter/IFileConfigurationSetter.cs | 20 +- .../ConfigurationValidationResult.cs | 48 +- .../FileConfigurationFluentValidator.cs | 130 +- .../Validator/FileValidationFailedError.cs | 30 +- .../Validator/HostAndPortValidator.cs | 13 + .../Validator/IConfigurationValidator.cs | 22 +- .../Validator/ReRouteFluentValidator.cs | 163 +- .../DependencyInjection/IOcelotBuilder.cs | 24 +- .../DependencyInjection/OcelotBuilder.cs | 663 +++---- .../ServiceCollectionExtensions.cs | 44 +- .../DownstreamRouteFinder/DownstreamRoute.cs | 34 +- .../Finder/DownstreamRouteFinder.cs | 110 +- .../Finder/IDownstreamRouteFinder.cs | 22 +- .../UnableToFindDownstreamRouteError.cs | 22 +- .../DownstreamRouteFinderMiddleware.cs | 136 +- ...wnstreamRouteFinderMiddlewareExtensions.cs | 22 +- .../IUrlPathPlaceholderNameAndValueFinder.cs | 20 +- .../IUrlPathToUrlTemplateMatcher.cs | 16 +- .../UrlMatcher/RegExUrlMatcher.cs | 34 +- .../UrlMatcher/UrlMatch.cs | 20 +- .../UrlPathPlaceholderNameAndValue.cs | 24 +- .../UrlPathPlaceholderNameAndValueFinder.cs | 226 +-- .../DownstreamHostNullOrEmptyError.cs | 22 +- .../DownstreamPathNullOrEmptyError.cs | 22 +- .../DownstreamSchemeNullOrEmptyError.cs | 22 +- .../DownstreamUrlCreator/IUrlBuilder.cs | 20 +- .../DownstreamUrlCreatorMiddleware.cs | 112 +- ...ownstreamUrlCreatorMiddlewareExtensions.cs | 22 +- src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs | 92 +- .../DownstreamUrlTemplateVariableReplacer.cs | 48 +- ...wnstreamUrlPathTemplateVariableReplacer.cs | 22 +- src/Ocelot/Errors/Error.cs | 36 +- .../ExceptionHandlerMiddlewareExtensions.cs | 24 +- src/Ocelot/Errors/OcelotErrorCode.cs | 76 +- src/Ocelot/Headers/AddHeadersToRequest.cs | 84 +- .../HttpContextRequestHeaderReplacer.cs | 48 +- .../Headers/HttpResponseHeaderReplacer.cs | 112 +- src/Ocelot/Headers/IAddHeadersToRequest.cs | 26 +- .../IHttpContextRequestHeaderReplacer.cs | 22 +- .../Headers/IHttpResponseHeaderReplacer.cs | 22 +- src/Ocelot/Headers/IRemoveOutputHeaders.cs | 20 +- .../HttpHeadersTransformationMiddleware.cs | 82 +- ...adersTransformationMiddlewareExtensions.cs | 22 +- .../HttpRequestHeadersBuilderMiddleware.cs | 98 +- ...questHeadersBuilderMiddlewareExtensions.cs | 22 +- src/Ocelot/Headers/RemoveOutputHeaders.cs | 52 +- .../Claims/Parser/CannotFindClaimError.cs | 24 +- .../Claims/Parser/ClaimsParser.cs | 132 +- .../Claims/Parser/IClaimsParser.cs | 22 +- .../Extensions/StringExtensions.cs | 64 +- .../RequestData/CannotAddDataError.cs | 22 +- .../RequestData/CannotFindDataError.cs | 22 +- .../RequestData/HttpDataRepository.cs | 148 +- .../IRequestScopedDataRepository.cs | 20 +- .../LoadBalancers/ILoadBalancer.cs | 24 +- .../LoadBalancers/ILoadBalancerFactory.cs | 18 +- .../LoadBalancers/ILoadBalancerHouse.cs | 20 +- .../LoadBalancer/LoadBalancers/Lease.cs | 30 +- .../LoadBalancers/LeastConnection.cs | 290 +-- .../LoadBalancers/LoadBalancerFactory.cs | 60 +- .../LoadBalancers/LoadBalancerHouse.cs | 112 +- .../LoadBalancers/NoLoadBalancer.cs | 56 +- .../LoadBalancer/LoadBalancers/RoundRobin.cs | 76 +- .../LoadBalancers/ServicesAreEmptyError.cs | 22 +- .../LoadBalancers/ServicesAreNullError.cs | 22 +- .../UnableToFindLoadBalancerError.cs | 22 +- .../Middleware/LoadBalancingMiddleware.cs | 142 +- .../LoadBalancingMiddlewareExtensions.cs | 22 +- src/Ocelot/Logging/IOcelotLoggerFactory.cs | 52 +- src/Ocelot/Middleware/BaseUrlFinder.cs | 42 +- src/Ocelot/Middleware/IBaseUrlFinder.cs | 12 +- src/Ocelot/Middleware/OcelotMiddleware.cs | 134 +- .../OcelotMiddlewareConfiguration.cs | 86 +- .../Middleware/OcelotMiddlewareExtensions.cs | 668 +++---- src/Ocelot/Middleware/UnauthenticatedError.cs | 22 +- src/Ocelot/Ocelot.csproj | 90 +- src/Ocelot/Properties/AssemblyInfo.cs | 36 +- .../QueryStrings/AddQueriesToRequest.cs | 132 +- .../QueryStrings/IAddQueriesToRequest.cs | 28 +- .../QueryStringBuilderMiddleware.cs | 94 +- .../QueryStringBuilderMiddlewareExtensions.cs | 22 +- src/Ocelot/Raft/ExcludeFromCoverage.cs | 12 +- src/Ocelot/Raft/FakeCommand.cs | 30 +- src/Ocelot/Raft/FileFsm.cs | 66 +- src/Ocelot/Raft/FilePeer.cs | 16 +- src/Ocelot/Raft/FilePeers.cs | 30 +- src/Ocelot/Raft/FilePeersProvider.cs | 88 +- src/Ocelot/Raft/HttpPeer.cs | 256 +-- src/Ocelot/Raft/OcelotFiniteStateMachine.cs | 48 +- src/Ocelot/Raft/RaftController.cs | 166 +- src/Ocelot/Raft/SqlLiteLog.cs | 556 +++--- src/Ocelot/Raft/UpdateFileConfiguration.cs | 28 +- .../RateLimit/ClientRateLimitProcessor.cs | 90 +- src/Ocelot/RateLimit/ClientRequestIdentity.cs | 34 +- ...DistributedCacheRateLimitCounterHanlder.cs | 90 +- .../RateLimit/IRateLimitCounterHandler.cs | 30 +- .../MemoryCacheRateLimitCounterHandler.cs | 90 +- .../RateLimitMiddlewareExtensions.cs | 32 +- src/Ocelot/RateLimit/RateLimitCore.cs | 294 +-- src/Ocelot/RateLimit/RateLimitCounter.cs | 46 +- src/Ocelot/RateLimit/RateLimitHeaders.cs | 54 +- .../Request/Builder/HttpRequestCreator.cs | 38 +- src/Ocelot/Request/Builder/IRequestCreator.cs | 36 +- src/Ocelot/Request/Mapper/IRequestMapper.cs | 24 +- src/Ocelot/Request/Mapper/RequestMapper.cs | 190 +- .../Request/Mapper/UnmappableRequestError.cs | 24 +- .../DownstreamRequestInitialiserMiddleware.cs | 80 +- .../HttpRequestBuilderMiddleware.cs | 130 +- .../HttpRequestBuilderMiddlewareExtensions.cs | 32 +- src/Ocelot/Request/Request.cs | 56 +- src/Ocelot/RequestId/DefaultRequestIdKey.cs | 18 +- .../Middleware/ReRouteRequestIdMiddleware.cs | 176 +- .../RequestIdMiddlewareExtensions.cs | 22 +- src/Ocelot/RequestId/RequestId.cs | 28 +- src/Ocelot/Requester/HttpClientBuilder.cs | 132 +- .../Requester/HttpClientHttpRequester.cs | 6 +- src/Ocelot/Requester/IHttpClient.cs | 26 +- src/Ocelot/Requester/IHttpClientCache.cs | 32 +- src/Ocelot/Requester/IHttpRequester.cs | 26 +- src/Ocelot/Requester/MemoryHttpClientCache.cs | 106 +- .../Middleware/HttpRequesterMiddleware.cs | 86 +- .../HttpRequesterMiddlewareExtensions.cs | 22 +- .../PollyCircuitBreakingDelegatingHandler.cs | 90 +- src/Ocelot/Requester/QoS/CircuitBreaker.cs | 32 +- src/Ocelot/Requester/QoS/IQoSProvider.cs | 12 +- .../Requester/QoS/IQoSProviderFactory.cs | 20 +- src/Ocelot/Requester/QoS/IQosProviderHouse.cs | 18 +- src/Ocelot/Requester/QoS/NoQoSProvider.cs | 12 +- src/Ocelot/Requester/QoS/PollyQoSProvider.cs | 100 +- .../Requester/QoS/QoSProviderFactory.cs | 48 +- src/Ocelot/Requester/QoS/QosProviderHouse.cs | 104 +- .../QoS/UnableToFindQoSProviderError.cs | 32 +- src/Ocelot/Requester/RequestTimedOutError.cs | 26 +- .../Requester/UnableToCompleteRequestError.cs | 26 +- src/Ocelot/Responder/HttpContextResponder.cs | 162 +- src/Ocelot/Responder/IHttpResponder.cs | 24 +- .../ResponderMiddlewareExtensions.cs | 22 +- src/Ocelot/Responses/ErrorResponse.cs | 28 +- src/Ocelot/Responses/ErrorResponseGeneric.cs | 34 +- src/Ocelot/Responses/OkResponse.cs | 16 +- src/Ocelot/Responses/OkResponseGeneric.cs | 16 +- src/Ocelot/Responses/Response.cs | 54 +- src/Ocelot/Responses/ResponseGeneric.cs | 38 +- .../ConfigurationServiceProvider.cs | 40 +- .../ConsulRegistryConfiguration.cs | 30 +- .../ConsulServiceDiscoveryProvider.cs | 110 +- .../IServiceDiscoveryProvider.cs | 20 +- .../IServiceDiscoveryProviderFactory.cs | 16 +- .../ServiceDiscoveryProviderFactory.cs | 68 +- ...ableToFindServiceDiscoveryProviderError.cs | 22 +- src/Ocelot/Values/DownstreamPath.cs | 24 +- src/Ocelot/Values/DownstreamUrl.cs | 22 +- src/Ocelot/Values/PathTemplate.cs | 24 +- src/Ocelot/Values/Service.cs | 58 +- .../{HostAndPort.cs => ServiceHostAndPort.cs} | 28 +- src/Ocelot/Values/UpstreamPathTemplate.cs | 26 +- test/Ocelot.AcceptanceTests/.gitignore | 468 ++--- .../AcceptanceTestsStartup.cs | 78 +- .../AuthenticationTests.cs | 732 +++---- .../AuthorisationTests.cs | 672 +++---- test/Ocelot.AcceptanceTests/BearerToken.cs | 30 +- .../Caching/InMemoryJsonHandle.cs | 274 +-- test/Ocelot.AcceptanceTests/CachingTests.cs | 362 ++-- .../CannotStartOcelotTests.cs | 114 +- .../CaseSensitiveRoutingTests.cs | 474 ++--- .../ClaimsToHeadersForwardingTests.cs | 416 ++-- .../ClaimsToQueryStringForwardingTests.cs | 430 ++--- .../ClientRateLimitTests.cs | 344 ++-- .../ConfigurationInConsulTests.cs | 736 ++++---- test/Ocelot.AcceptanceTests/ConsulStartup.cs | 80 +- .../CustomMiddlewareTests.cs | 604 +++--- test/Ocelot.AcceptanceTests/HeaderTests.cs | 476 ++--- .../LoadBalancerTests.cs | 178 ++ .../Ocelot.AcceptanceTests.csproj | 106 +- .../Properties/AssemblyInfo.cs | 38 +- test/Ocelot.AcceptanceTests/QoSTests.cs | 432 +++-- test/Ocelot.AcceptanceTests/RequestIdTests.cs | 310 +-- .../ReturnsErrorTests.cs | 158 +- test/Ocelot.AcceptanceTests/RoutingTests.cs | 1678 +++++++++-------- .../ServiceDiscoveryTests.cs | 99 +- .../StartupWithConsulAndCustomCacheHandle.cs | 58 +- .../StartupWithCustomCacheHandle.cs | 56 +- test/Ocelot.AcceptanceTests/Steps.cs | 852 ++++----- .../TestConfiguration.cs | 20 +- .../TwoDownstreamServicesTests.cs | 421 ++--- test/Ocelot.AcceptanceTests/appsettings.json | 20 +- .../Ocelot.AcceptanceTests/configuration.json | 60 +- .../Ocelot.Benchmarks.csproj | 48 +- test/Ocelot.Benchmarks/Program.cs | 34 +- .../Properties/AssemblyInfo.cs | 38 +- ...lPathToUrlPathTemplateMatcherBenchmarks.cs | 78 +- .../AdministrationTests.cs | 884 +++++---- test/Ocelot.IntegrationTests/BearerToken.cs | 32 +- .../IntegrationTestsStartup.cs | 100 +- .../Ocelot.IntegrationTests.csproj | 90 +- .../Properties/AssemblyInfo.cs | 38 +- test/Ocelot.IntegrationTests/RaftStartup.cs | 104 +- test/Ocelot.IntegrationTests/RaftTests.cs | 803 ++++---- .../ThreadSafeHeadersTests.cs | 374 ++-- test/Ocelot.IntegrationTests/appsettings.json | 20 +- test/Ocelot.IntegrationTests/peers.json | 34 +- test/Ocelot.ManualTest/ManualTestStartup.cs | 80 +- .../Ocelot.ManualTest.csproj | 86 +- .../Ocelot.postman_collection.json | 740 ++++---- test/Ocelot.ManualTest/Program.cs | 78 +- .../Properties/launchSettings.json | 50 +- test/Ocelot.ManualTest/appsettings.json | 20 +- test/Ocelot.ManualTest/configuration.json | 620 +++--- test/Ocelot.ManualTest/web.config | 28 +- .../AuthenticationMiddlewareTests.cs | 140 +- .../AuthorisationMiddlewareTests.cs | 164 +- .../Authorization/ClaimsAuthoriserTests.cs | 158 +- .../Cache/CacheManagerCacheTests.cs | 206 +- .../Cache/OutputCacheMiddlewareTests.cs | 260 +-- .../Cache/RegionCreatorTests.cs | 128 +- .../Claims/AddClaimsToRequestTests.cs | 288 +-- .../Claims/ClaimsBuilderMiddlewareTests.cs | 174 +- .../AuthenticationOptionsCreatorTests.cs | 122 +- .../ClaimToThingConfigurationParserTests.cs | 234 +-- .../ClaimsToThingCreatorTests.cs | 218 +-- .../ConfigurationFluentValidationTests.cs | 1156 +++++++----- .../ConsulFileConfigurationPollerTests.cs | 184 +- .../DownstreamAddressesCreatorTests.cs | 122 ++ .../FileConfigurationCreatorTests.cs | 67 +- .../FileConfigurationProviderTests.cs | 122 +- .../FileConfigurationRepositoryTests.cs | 424 +++-- .../FileConfigurationSetterTests.cs | 224 +-- .../Configuration/HashCreationTests.cs | 64 +- .../Configuration/HashMatcherTests.cs | 150 +- .../HeaderFindAndReplaceCreatorTests.cs | 314 +-- ...IdentityServerConfigurationCreatorTests.cs | 30 +- .../InMemoryConfigurationRepositoryTests.cs | 202 +- .../OcelotConfigurationProviderTests.cs | 160 +- .../Configuration/QoSOptionsCreatorTests.cs | 124 +- .../RateLimitOptionsCreatorTests.cs | 216 +-- .../ReRouteOptionsCreatorTests.cs | 166 +- .../Configuration/RequestIdKeyCreatorTests.cs | 182 +- .../ServiceProviderCreatorTests.cs | 138 +- .../UpstreamTemplatePatternCreatorTests.cs | 366 ++-- .../FileConfigurationControllerTests.cs | 390 ++-- .../Controllers/OutputCacheControllerTests.cs | 88 +- .../DependencyInjection/OcelotBuilderTests.cs | 450 ++--- .../DownstreamRouteFinderMiddlewareTests.cs | 182 +- .../DownstreamRouteFinderTests.cs | 904 ++++----- .../UrlMatcher/RegExUrlMatcherTests.cs | 434 ++--- ...lPathPlaceholderNameAndValueFinderTests.cs | 532 +++--- .../DownstreamUrlCreatorMiddlewareTests.cs | 206 +- .../DownstreamUrlCreator/UrlBuilderTests.cs | 244 +-- ...eamUrlPathTemplateVariableReplacerTests.cs | 366 ++-- test/Ocelot.UnitTests/Errors/ErrorTests.cs | 34 +- .../Errors/ExceptionHandlerMiddlewareTests.cs | 286 +-- .../Headers/AddHeadersToRequestTests.cs | 302 +-- .../HttpContextRequestHeaderReplacerTests.cs | 180 +- ...ttpHeadersTransformationMiddlewareTests.cs | 200 +- ...ttpRequestHeadersBuilderMiddlewareTests.cs | 194 +- .../HttpResponseHeaderReplacerTests.cs | 468 ++--- .../Headers/RemoveHeadersTests.cs | 104 +- .../Infrastructure/ClaimParserTests.cs | 248 +-- .../Infrastructure/ScopesAuthoriserTests.cs | 246 +-- .../LoadBalancer/LeastConnectionTests.cs | 570 +++--- .../LoadBalancer/LoadBalancerFactoryTests.cs | 248 +-- .../LoadBalancer/LoadBalancerHouseTests.cs | 330 ++-- .../LoadBalancerMiddlewareTests.cs | 418 ++-- .../LoadBalancer/NoLoadBalancerTests.cs | 96 +- .../LoadBalancer/RoundRobinTests.cs | 138 +- .../Middleware/BaseUrlFinderTests.cs | 122 +- test/Ocelot.UnitTests/Ocelot.UnitTests.csproj | 118 +- .../Properties/AssemblyInfo.cs | 38 +- .../QueryStrings/AddQueriesToRequestTests.cs | 314 +-- .../QueryStringBuilderMiddlewareTests.cs | 194 +- .../Raft/OcelotFiniteStateMachineTests.cs | 88 +- .../ClientRateLimitMiddlewareTests.cs | 256 +-- .../ScopedRequestDataRepositoryTests.cs | 156 +- ...streamRequestInitialiserMiddlewareTests.cs | 284 +-- .../Request/Mapper/RequestMapperTests.cs | 574 +++--- .../ReRouteRequestIdMiddlewareTests.cs | 364 ++-- .../Requester/HttpRequesterMiddlewareTests.cs | 162 +- .../Requester/QoSProviderFactoryTests.cs | 162 +- .../Requester/QosProviderHouseTests.cs | 292 +-- test/Ocelot.UnitTests/Responder/AnyError.cs | 30 +- .../ErrorsToHttpStatusCodeMapperTests.cs | 334 ++-- .../Responder/ResponderMiddlewareTests.cs | 144 +- .../ServerHostedMiddlewareTest.cs | 136 +- .../ConfigurationServiceProviderTests.cs | 104 +- .../ServiceProviderFactoryTests.cs | 174 +- .../ServiceDiscovery/ServiceRegistryTests.cs | 276 +-- .../TestData/AuthenticationConfigTestData.cs | 126 +- test/Ocelot.UnitTests/Waiter.cs | 108 +- tools/packages.config | 8 +- 440 files changed, 29740 insertions(+), 28464 deletions(-) create mode 100644 docs/features/loadbalancer.rst create mode 100644 src/Ocelot/Configuration/Creator/DownstreamAddressesCreator.cs create mode 100644 src/Ocelot/Configuration/Creator/IDownstreamAddressesCreator.cs create mode 100644 src/Ocelot/Configuration/DownstreamHostAndPort.cs create mode 100644 src/Ocelot/Configuration/File/FileHostAndPort.cs create mode 100644 src/Ocelot/Configuration/Validator/HostAndPortValidator.cs rename src/Ocelot/Values/{HostAndPort.cs => ServiceHostAndPort.cs} (68%) create mode 100644 test/Ocelot.AcceptanceTests/LoadBalancerTests.cs create mode 100644 test/Ocelot.UnitTests/Configuration/DownstreamAddressesCreatorTests.cs diff --git a/.editorconfig b/.editorconfig index beb5aacf4..054123c42 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,9 +1,9 @@ -root = true - -[*] -end_of_line = crlf -insert_final_newline = true - -[*.cs] -indent_style = space -indent_size = 4 +root = true + +[*] +end_of_line = crlf +insert_final_newline = true + +[*.cs] +indent_style = space +indent_size = 4 diff --git a/.gitignore b/.gitignore index 0f60ca3d5..836e0bfe5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,254 +1,254 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. - -# User-specific files -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -build/ -bld/ -[Bb]in/ -[Oo]bj/ -results/ - -# Visual Studio 2015 cache/options directory -.vs/ -.vscode/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ -site/wwwroot/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUNIT -*.VisualState.xml -TestResult.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# DNX -project.lock.json -artifacts/ - -*_i.c -*_p.c -*_i.h -*.ilk -*.meta -*.obj -*.pch -*.pdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# JustCode is a .NET coding add-in -.JustCode - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# TODO: Comment the next line if you want to checkin your web deploy settings -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# NuGet Packages -*.nupkg -# The packages folder can be ignored because of Package Restore -**/packages/* -# except build/, which is used as an MSBuild target. -!**/packages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/packages/repositories.config - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Microsoft Azure ApplicationInsights config file -ApplicationInsights.config - -# Windows Store app package directory -AppPackages/ -BundleArtifacts/ - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.pfx -!idsrv3test.pfx -*.publishsettings -node_modules/ -orleans.codegen.cs - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm - -# SQL Server files -*.mdf -*.ldf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe - -# FAKE - F# Make -.fake/ -!tools/packages.config -tools/ - -# MacOS -.DS_Store - -# Ocelot acceptance test config -test/Ocelot.AcceptanceTests/configuration.json - -# Read the docstates -_build/ -_static/ -_templates/ - -# JetBrains Rider +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ +results/ + +# Visual Studio 2015 cache/options directory +.vs/ +.vscode/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ +site/wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Microsoft Azure ApplicationInsights config file +ApplicationInsights.config + +# Windows Store app package directory +AppPackages/ +BundleArtifacts/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +!idsrv3test.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe + +# FAKE - F# Make +.fake/ +!tools/packages.config +tools/ + +# MacOS +.DS_Store + +# Ocelot acceptance test config +test/Ocelot.AcceptanceTests/configuration.json + +# Read the docstates +_build/ +_static/ +_templates/ + +# JetBrains Rider .idea/ \ No newline at end of file diff --git a/GitVersion.yml b/GitVersion.yml index 05e9ac418..09e2e362c 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -1,4 +1,4 @@ -mode: ContinuousDelivery -branches: {} -ignore: - sha: [] +mode: ContinuousDelivery +branches: {} +ignore: + sha: [] diff --git a/LICENSE.md b/LICENSE.md index a7e39d094..d47ecafdf 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,8 +1,8 @@ -The MIT License (MIT) -Copyright (c) 2016 Tom Pallister - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - +The MIT License (MIT) +Copyright (c) 2016 Tom Pallister + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/Ocelot.sln b/Ocelot.sln index 8557f70af..82a17db98 100644 --- a/Ocelot.sln +++ b/Ocelot.sln @@ -1,87 +1,87 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26730.15 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5CFB79B7-C9DC-45A4-9A75-625D92471702}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3FA7C349-DBE8-4904-A2CE-015B8869CE6C}" - ProjectSection(SolutionItems) = preProject - .gitignore = .gitignore - build-and-release-unstable.ps1 = build-and-release-unstable.ps1 - build-and-run-tests.ps1 = build-and-run-tests.ps1 - build.cake = build.cake - build.ps1 = build.ps1 - GitVersion.yml = GitVersion.yml - global.json = global.json - LICENSE.md = LICENSE.md - ocelot.postman_collection.json = ocelot.postman_collection.json - README.md = README.md - release.ps1 = release.ps1 - ReleaseNotes.md = ReleaseNotes.md - run-acceptance-tests.ps1 = run-acceptance-tests.ps1 - run-benchmarks.ps1 = run-benchmarks.ps1 - run-unit-tests.ps1 = run-unit-tests.ps1 - version.ps1 = version.ps1 - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{5B401523-36DA-4491-B73A-7590A26E420B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot", "src\Ocelot\Ocelot.csproj", "{D6DF4206-0DBA-41D8-884D-C3E08290FDBB}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.UnitTests", "test\Ocelot.UnitTests\Ocelot.UnitTests.csproj", "{54E84F1A-E525-4443-96EC-039CBD50C263}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.AcceptanceTests", "test\Ocelot.AcceptanceTests\Ocelot.AcceptanceTests.csproj", "{F8C224FE-36BE-45F5-9B0E-666D8F4A9B52}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.ManualTest", "test\Ocelot.ManualTest\Ocelot.ManualTest.csproj", "{02BBF4C5-517E-4157-8D21-4B8B9E118B7A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Benchmarks", "test\Ocelot.Benchmarks\Ocelot.Benchmarks.csproj", "{106B49E6-95F6-4A7B-B81C-96BFA74AF035}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.IntegrationTests", "test\Ocelot.IntegrationTests\Ocelot.IntegrationTests.csproj", "{D4575572-99CA-4530-8737-C296EDA326F8}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {D6DF4206-0DBA-41D8-884D-C3E08290FDBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D6DF4206-0DBA-41D8-884D-C3E08290FDBB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D6DF4206-0DBA-41D8-884D-C3E08290FDBB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D6DF4206-0DBA-41D8-884D-C3E08290FDBB}.Release|Any CPU.Build.0 = Release|Any CPU - {54E84F1A-E525-4443-96EC-039CBD50C263}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {54E84F1A-E525-4443-96EC-039CBD50C263}.Debug|Any CPU.Build.0 = Debug|Any CPU - {54E84F1A-E525-4443-96EC-039CBD50C263}.Release|Any CPU.ActiveCfg = Release|Any CPU - {54E84F1A-E525-4443-96EC-039CBD50C263}.Release|Any CPU.Build.0 = Release|Any CPU - {F8C224FE-36BE-45F5-9B0E-666D8F4A9B52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F8C224FE-36BE-45F5-9B0E-666D8F4A9B52}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F8C224FE-36BE-45F5-9B0E-666D8F4A9B52}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F8C224FE-36BE-45F5-9B0E-666D8F4A9B52}.Release|Any CPU.Build.0 = Release|Any CPU - {02BBF4C5-517E-4157-8D21-4B8B9E118B7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {02BBF4C5-517E-4157-8D21-4B8B9E118B7A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {02BBF4C5-517E-4157-8D21-4B8B9E118B7A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {02BBF4C5-517E-4157-8D21-4B8B9E118B7A}.Release|Any CPU.Build.0 = Release|Any CPU - {106B49E6-95F6-4A7B-B81C-96BFA74AF035}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {106B49E6-95F6-4A7B-B81C-96BFA74AF035}.Debug|Any CPU.Build.0 = Debug|Any CPU - {106B49E6-95F6-4A7B-B81C-96BFA74AF035}.Release|Any CPU.ActiveCfg = Release|Any CPU - {106B49E6-95F6-4A7B-B81C-96BFA74AF035}.Release|Any CPU.Build.0 = Release|Any CPU - {D4575572-99CA-4530-8737-C296EDA326F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D4575572-99CA-4530-8737-C296EDA326F8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D4575572-99CA-4530-8737-C296EDA326F8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D4575572-99CA-4530-8737-C296EDA326F8}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {D6DF4206-0DBA-41D8-884D-C3E08290FDBB} = {5CFB79B7-C9DC-45A4-9A75-625D92471702} - {54E84F1A-E525-4443-96EC-039CBD50C263} = {5B401523-36DA-4491-B73A-7590A26E420B} - {F8C224FE-36BE-45F5-9B0E-666D8F4A9B52} = {5B401523-36DA-4491-B73A-7590A26E420B} - {02BBF4C5-517E-4157-8D21-4B8B9E118B7A} = {5B401523-36DA-4491-B73A-7590A26E420B} - {106B49E6-95F6-4A7B-B81C-96BFA74AF035} = {5B401523-36DA-4491-B73A-7590A26E420B} - {D4575572-99CA-4530-8737-C296EDA326F8} = {5B401523-36DA-4491-B73A-7590A26E420B} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {21476EFF-778A-4F97-8A56-D1AF1CEC0C48} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26730.15 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5CFB79B7-C9DC-45A4-9A75-625D92471702}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3FA7C349-DBE8-4904-A2CE-015B8869CE6C}" + ProjectSection(SolutionItems) = preProject + .gitignore = .gitignore + build-and-release-unstable.ps1 = build-and-release-unstable.ps1 + build-and-run-tests.ps1 = build-and-run-tests.ps1 + build.cake = build.cake + build.ps1 = build.ps1 + GitVersion.yml = GitVersion.yml + global.json = global.json + LICENSE.md = LICENSE.md + ocelot.postman_collection.json = ocelot.postman_collection.json + README.md = README.md + release.ps1 = release.ps1 + ReleaseNotes.md = ReleaseNotes.md + run-acceptance-tests.ps1 = run-acceptance-tests.ps1 + run-benchmarks.ps1 = run-benchmarks.ps1 + run-unit-tests.ps1 = run-unit-tests.ps1 + version.ps1 = version.ps1 + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{5B401523-36DA-4491-B73A-7590A26E420B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot", "src\Ocelot\Ocelot.csproj", "{D6DF4206-0DBA-41D8-884D-C3E08290FDBB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.UnitTests", "test\Ocelot.UnitTests\Ocelot.UnitTests.csproj", "{54E84F1A-E525-4443-96EC-039CBD50C263}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.AcceptanceTests", "test\Ocelot.AcceptanceTests\Ocelot.AcceptanceTests.csproj", "{F8C224FE-36BE-45F5-9B0E-666D8F4A9B52}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.ManualTest", "test\Ocelot.ManualTest\Ocelot.ManualTest.csproj", "{02BBF4C5-517E-4157-8D21-4B8B9E118B7A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Benchmarks", "test\Ocelot.Benchmarks\Ocelot.Benchmarks.csproj", "{106B49E6-95F6-4A7B-B81C-96BFA74AF035}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.IntegrationTests", "test\Ocelot.IntegrationTests\Ocelot.IntegrationTests.csproj", "{D4575572-99CA-4530-8737-C296EDA326F8}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D6DF4206-0DBA-41D8-884D-C3E08290FDBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D6DF4206-0DBA-41D8-884D-C3E08290FDBB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D6DF4206-0DBA-41D8-884D-C3E08290FDBB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D6DF4206-0DBA-41D8-884D-C3E08290FDBB}.Release|Any CPU.Build.0 = Release|Any CPU + {54E84F1A-E525-4443-96EC-039CBD50C263}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {54E84F1A-E525-4443-96EC-039CBD50C263}.Debug|Any CPU.Build.0 = Debug|Any CPU + {54E84F1A-E525-4443-96EC-039CBD50C263}.Release|Any CPU.ActiveCfg = Release|Any CPU + {54E84F1A-E525-4443-96EC-039CBD50C263}.Release|Any CPU.Build.0 = Release|Any CPU + {F8C224FE-36BE-45F5-9B0E-666D8F4A9B52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F8C224FE-36BE-45F5-9B0E-666D8F4A9B52}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F8C224FE-36BE-45F5-9B0E-666D8F4A9B52}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F8C224FE-36BE-45F5-9B0E-666D8F4A9B52}.Release|Any CPU.Build.0 = Release|Any CPU + {02BBF4C5-517E-4157-8D21-4B8B9E118B7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {02BBF4C5-517E-4157-8D21-4B8B9E118B7A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {02BBF4C5-517E-4157-8D21-4B8B9E118B7A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {02BBF4C5-517E-4157-8D21-4B8B9E118B7A}.Release|Any CPU.Build.0 = Release|Any CPU + {106B49E6-95F6-4A7B-B81C-96BFA74AF035}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {106B49E6-95F6-4A7B-B81C-96BFA74AF035}.Debug|Any CPU.Build.0 = Debug|Any CPU + {106B49E6-95F6-4A7B-B81C-96BFA74AF035}.Release|Any CPU.ActiveCfg = Release|Any CPU + {106B49E6-95F6-4A7B-B81C-96BFA74AF035}.Release|Any CPU.Build.0 = Release|Any CPU + {D4575572-99CA-4530-8737-C296EDA326F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D4575572-99CA-4530-8737-C296EDA326F8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D4575572-99CA-4530-8737-C296EDA326F8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D4575572-99CA-4530-8737-C296EDA326F8}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {D6DF4206-0DBA-41D8-884D-C3E08290FDBB} = {5CFB79B7-C9DC-45A4-9A75-625D92471702} + {54E84F1A-E525-4443-96EC-039CBD50C263} = {5B401523-36DA-4491-B73A-7590A26E420B} + {F8C224FE-36BE-45F5-9B0E-666D8F4A9B52} = {5B401523-36DA-4491-B73A-7590A26E420B} + {02BBF4C5-517E-4157-8D21-4B8B9E118B7A} = {5B401523-36DA-4491-B73A-7590A26E420B} + {106B49E6-95F6-4A7B-B81C-96BFA74AF035} = {5B401523-36DA-4491-B73A-7590A26E420B} + {D4575572-99CA-4530-8737-C296EDA326F8} = {5B401523-36DA-4491-B73A-7590A26E420B} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {21476EFF-778A-4F97-8A56-D1AF1CEC0C48} + EndGlobalSection +EndGlobal diff --git a/README.md b/README.md index 43f176064..dd2569d46 100644 --- a/README.md +++ b/README.md @@ -1,65 +1,65 @@ -# Ocelot - -[![Build status](https://ci.appveyor.com/api/projects/status/r6sv51qx36sis1je?svg=true)](https://ci.appveyor.com/project/TomPallister/ocelot-fcfpb) - -[![Coverage Status](https://coveralls.io/repos/github/TomPallister/Ocelot/badge.svg?branch=develop)](https://coveralls.io/github/TomPallister/Ocelot?branch=develop) - -Ocelot is a .NET Api Gateway. This project is aimed at people using .NET running -a micro services / service orientated architecture -that need a unified point of entry into their system. - -In particular I want easy integration with -IdentityServer reference and bearer tokens. - -We have been unable to find this in my current workplace -without having to write our own Javascript middlewares -to handle the IdentityServer reference tokens. We would -rather use the IdentityServer code that already exists -to do this. - -Ocelot is a bunch of middlewares in a specific order. - -Ocelot manipulates the HttpRequest object into a state specified by its configuration until -it reaches a request builder middleware where it creates a HttpRequestMessage object which is -used to make a request to a downstream service. The middleware that makes the request is -the last thing in the Ocelot pipeline. It does not call the next middleware. -The response from the downstream service is stored in a per request scoped repository -and retrieved as the requests goes back up the Ocelot pipeline. There is a piece of middleware -that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client. -That is basically it with a bunch of other features. - -## How to install - -Ocelot is designed to work with ASP.NET core only and is currently -built to netcoreapp2.0 [this](https://docs.microsoft.com/en-us/dotnet/articles/standard/library) documentation may prove helpful when working out if Ocelot would be suitable for you. - -Install Ocelot and it's dependencies using NuGet. - -`Install-Package Ocelot` - -All versions can be found [here](https://www.nuget.org/packages/Ocelot/) - -## Documentation - -Please click [here](http://ocelot.readthedocs.io/en/latest/) for the Ocleot documentation. This includes lots of information and will be helpful if you want to understand the features Ocelot currently offers. - -## Coming up - -You can see what we are working on [here](https://github.com/TomPallister/Ocelot/projects/1) - -## Contributing - -Pull requests, issues and commentary welcome! No special process just create a request and get in -touch either via gitter or create an issue. - - -## Things that are currently annoying me - -+ The base OcelotMiddleware lets you access things that are going to be null -and doesnt check the response is OK. I think the fact you can even call stuff -that isnt available is annoying. Let alone it be null. - -[![](https://codescene.io/projects/697/status.svg) Get more details at **codescene.io**.](https://codescene.io/projects/697/jobs/latest-successful/results) - - - +# Ocelot + +[![Build status](https://ci.appveyor.com/api/projects/status/r6sv51qx36sis1je?svg=true)](https://ci.appveyor.com/project/TomPallister/ocelot-fcfpb) + +[![Coverage Status](https://coveralls.io/repos/github/TomPallister/Ocelot/badge.svg?branch=develop)](https://coveralls.io/github/TomPallister/Ocelot?branch=develop) + +Ocelot is a .NET Api Gateway. This project is aimed at people using .NET running +a micro services / service orientated architecture +that need a unified point of entry into their system. + +In particular I want easy integration with +IdentityServer reference and bearer tokens. + +We have been unable to find this in my current workplace +without having to write our own Javascript middlewares +to handle the IdentityServer reference tokens. We would +rather use the IdentityServer code that already exists +to do this. + +Ocelot is a bunch of middlewares in a specific order. + +Ocelot manipulates the HttpRequest object into a state specified by its configuration until +it reaches a request builder middleware where it creates a HttpRequestMessage object which is +used to make a request to a downstream service. The middleware that makes the request is +the last thing in the Ocelot pipeline. It does not call the next middleware. +The response from the downstream service is stored in a per request scoped repository +and retrieved as the requests goes back up the Ocelot pipeline. There is a piece of middleware +that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client. +That is basically it with a bunch of other features. + +## How to install + +Ocelot is designed to work with ASP.NET core only and is currently +built to netcoreapp2.0 [this](https://docs.microsoft.com/en-us/dotnet/articles/standard/library) documentation may prove helpful when working out if Ocelot would be suitable for you. + +Install Ocelot and it's dependencies using NuGet. + +`Install-Package Ocelot` + +All versions can be found [here](https://www.nuget.org/packages/Ocelot/) + +## Documentation + +Please click [here](http://ocelot.readthedocs.io/en/latest/) for the Ocleot documentation. This includes lots of information and will be helpful if you want to understand the features Ocelot currently offers. + +## Coming up + +You can see what we are working on [here](https://github.com/TomPallister/Ocelot/projects/1) + +## Contributing + +Pull requests, issues and commentary welcome! No special process just create a request and get in +touch either via gitter or create an issue. + + +## Things that are currently annoying me + ++ The base OcelotMiddleware lets you access things that are going to be null +and doesnt check the response is OK. I think the fact you can even call stuff +that isnt available is annoying. Let alone it be null. + +[![](https://codescene.io/projects/697/status.svg) Get more details at **codescene.io**.](https://codescene.io/projects/697/jobs/latest-successful/results) + + + diff --git a/build-and-release-unstable.ps1 b/build-and-release-unstable.ps1 index d6849dc83..269a8689f 100644 --- a/build-and-release-unstable.ps1 +++ b/build-and-release-unstable.ps1 @@ -1,2 +1,2 @@ -./build.ps1 -target BuildAndReleaseUnstable +./build.ps1 -target BuildAndReleaseUnstable exit $LASTEXITCODE \ No newline at end of file diff --git a/build-and-run-tests.ps1 b/build-and-run-tests.ps1 index cc9fd4bbe..6ff61c0a1 100644 --- a/build-and-run-tests.ps1 +++ b/build-and-run-tests.ps1 @@ -1,2 +1,2 @@ -./build.ps1 -target RunTests +./build.ps1 -target RunTests exit $LASTEXITCODE \ No newline at end of file diff --git a/build.cake b/build.cake index 7281eb3a4..db4f168ca 100644 --- a/build.cake +++ b/build.cake @@ -1,476 +1,476 @@ -#tool "nuget:?package=GitVersion.CommandLine" -#tool "nuget:?package=GitReleaseNotes" -#addin nuget:?package=Cake.Json -#addin nuget:?package=Newtonsoft.Json&version=9.0.1 -#tool "nuget:?package=OpenCover" -#tool "nuget:?package=ReportGenerator" -#tool coveralls.net -#addin Cake.Coveralls - -// compile -var compileConfig = Argument("configuration", "Release"); -var slnFile = "./Ocelot.sln"; - -// build artifacts -var artifactsDir = Directory("artifacts"); - -// unit testing -var artifactsForUnitTestsDir = artifactsDir + Directory("UnitTests"); -var unitTestAssemblies = @"./test/Ocelot.UnitTests/Ocelot.UnitTests.csproj"; -var minCodeCoverage = 76.4d; -var coverallsRepoToken = "coveralls-repo-token-ocelot"; -var coverallsRepo = "https://coveralls.io/github/TomPallister/Ocelot"; - -// acceptance testing -var artifactsForAcceptanceTestsDir = artifactsDir + Directory("AcceptanceTests"); -var acceptanceTestAssemblies = @"./test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj"; - -// integration testing -var artifactsForIntegrationTestsDir = artifactsDir + Directory("IntegrationTests"); -var integrationTestAssemblies = @"./test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj"; - -// benchmark testing -var artifactsForBenchmarkTestsDir = artifactsDir + Directory("BenchmarkTests"); -var benchmarkTestAssemblies = @"./test/Ocelot.Benchmarks"; - -// packaging -var packagesDir = artifactsDir + Directory("Packages"); -var releaseNotesFile = packagesDir + File("releasenotes.md"); -var artifactsFile = packagesDir + File("artifacts.txt"); - -// unstable releases -var nugetFeedUnstableKey = EnvironmentVariable("nuget-apikey-unstable"); -var nugetFeedUnstableUploadUrl = "https://www.nuget.org/api/v2/package"; -var nugetFeedUnstableSymbolsUploadUrl = "https://www.nuget.org/api/v2/package"; - -// stable releases -var tagsUrl = "https://api.github.com/repos/tompallister/ocelot/releases/tags/"; -var nugetFeedStableKey = EnvironmentVariable("nuget-apikey-stable"); -var nugetFeedStableUploadUrl = "https://www.nuget.org/api/v2/package"; -var nugetFeedStableSymbolsUploadUrl = "https://www.nuget.org/api/v2/package"; - -// internal build variables - don't change these. -var releaseTag = ""; -string committedVersion = "0.0.0-dev"; -var buildVersion = committedVersion; -GitVersion versioning = null; -var nugetFeedUnstableBranchFilter = "^(develop)$|^(PullRequest/)"; - -var target = Argument("target", "Default"); - - -Information("target is " +target); -Information("Build configuration is " + compileConfig); - -Task("Default") - .IsDependentOn("Build"); - -Task("Build") - .IsDependentOn("RunTests") - .IsDependentOn("CreatePackages"); - -Task("BuildAndReleaseUnstable") - .IsDependentOn("Build") - .IsDependentOn("ReleasePackagesToUnstableFeed"); - -Task("Clean") - .Does(() => - { - if (DirectoryExists(artifactsDir)) - { - DeleteDirectory(artifactsDir, recursive:true); - } - CreateDirectory(artifactsDir); - }); - -Task("Version") - .Does(() => - { - versioning = GetNuGetVersionForCommit(); - var nugetVersion = versioning.NuGetVersion; - Information("SemVer version number: " + nugetVersion); - - if (AppVeyor.IsRunningOnAppVeyor) - { - Information("Persisting version number..."); - PersistVersion(committedVersion, nugetVersion); - buildVersion = nugetVersion; - } - else - { - Information("We are not running on build server, so we won't persist the version number."); - } - }); - -Task("Compile") - .IsDependentOn("Clean") - .IsDependentOn("Version") - .Does(() => - { - var settings = new DotNetCoreBuildSettings - { - Configuration = compileConfig, - }; - - DotNetCoreBuild(slnFile, settings); - }); - -Task("RunUnitTests") - .IsDependentOn("Compile") - .Does(() => - { - if (IsRunningOnWindows()) - { - var coverageSummaryFile = artifactsForUnitTestsDir + File("coverage.xml"); - - EnsureDirectoryExists(artifactsForUnitTestsDir); - - OpenCover(tool => - { - tool.DotNetCoreTest(unitTestAssemblies); - }, - new FilePath(coverageSummaryFile), - new OpenCoverSettings() - { - Register="user", - ArgumentCustomization=args=>args.Append(@"-oldstyle -returntargetcode -excludebyattribute:*.ExcludeFromCoverage*") - } - .WithFilter("+[Ocelot*]*") - .WithFilter("-[xunit*]*") - .WithFilter("-[Ocelot*Tests]*") - ); - - ReportGenerator(coverageSummaryFile, artifactsForUnitTestsDir); - - if (AppVeyor.IsRunningOnAppVeyor) - { - var repoToken = EnvironmentVariable(coverallsRepoToken); - if (string.IsNullOrEmpty(repoToken)) - { - throw new Exception(string.Format("Coveralls repo token not found. Set environment variable '{0}'", coverallsRepoToken)); - } - - Information(string.Format("Uploading test coverage to {0}", coverallsRepo)); - CoverallsNet(coverageSummaryFile, CoverallsNetReportType.OpenCover, new CoverallsNetSettings() - { - RepoToken = repoToken - }); - } - else - { - Information("We are not running on the build server so we won't publish the coverage report to coveralls.io"); - } - - var sequenceCoverage = XmlPeek(coverageSummaryFile, "//CoverageSession/Summary/@sequenceCoverage"); - var branchCoverage = XmlPeek(coverageSummaryFile, "//CoverageSession/Summary/@branchCoverage"); - - Information("Sequence Coverage: " + sequenceCoverage); - - if(double.Parse(sequenceCoverage) < minCodeCoverage) - { - var whereToCheck = !AppVeyor.IsRunningOnAppVeyor ? coverallsRepo : artifactsForUnitTestsDir; - throw new Exception(string.Format("Code coverage fell below the threshold of {0}%. You can find the code coverage report at {1}", minCodeCoverage, whereToCheck)); - }; - - } - else - { - var settings = new DotNetCoreTestSettings - { - Configuration = compileConfig, - }; - - EnsureDirectoryExists(artifactsForUnitTestsDir); - DotNetCoreTest(unitTestAssemblies, settings); - } - }); - -Task("RunAcceptanceTests") - .IsDependentOn("Compile") - .Does(() => - { - var settings = new DotNetCoreTestSettings - { - Configuration = compileConfig, - ArgumentCustomization = args => args - .Append("--no-restore") - .Append("--no-build") - }; - - EnsureDirectoryExists(artifactsForAcceptanceTestsDir); - DotNetCoreTest(acceptanceTestAssemblies, settings); - }); - -Task("RunIntegrationTests") - .IsDependentOn("Compile") - .Does(() => - { - var settings = new DotNetCoreTestSettings - { - Configuration = compileConfig, - ArgumentCustomization = args => args - .Append("--no-restore") - .Append("--no-build") - }; - - EnsureDirectoryExists(artifactsForIntegrationTestsDir); - DotNetCoreTest(integrationTestAssemblies, settings); - }); - -Task("RunTests") - .IsDependentOn("RunUnitTests") - .IsDependentOn("RunAcceptanceTests") - .IsDependentOn("RunIntegrationTests"); - -Task("CreatePackages") - .IsDependentOn("Compile") - .Does(() => - { - EnsureDirectoryExists(packagesDir); - CopyFiles("./src/**/Ocelot.*.nupkg", packagesDir); - - //GenerateReleaseNotes(releaseNotesFile); - - System.IO.File.WriteAllLines(artifactsFile, new[]{ - "nuget:Ocelot." + buildVersion + ".nupkg", - //"releaseNotes:releasenotes.md" - }); - - if (AppVeyor.IsRunningOnAppVeyor) - { - var path = packagesDir.ToString() + @"/**/*"; - - foreach (var file in GetFiles(path)) - { - AppVeyor.UploadArtifact(file.FullPath); - } - } - }); - -Task("ReleasePackagesToUnstableFeed") - .IsDependentOn("CreatePackages") - .Does(() => - { - if (ShouldPublishToUnstableFeed(nugetFeedUnstableBranchFilter, versioning.BranchName)) - { - PublishPackages(packagesDir, artifactsFile, nugetFeedUnstableKey, nugetFeedUnstableUploadUrl, nugetFeedUnstableSymbolsUploadUrl); - } - }); - -Task("EnsureStableReleaseRequirements") - .Does(() => - { - Information("Check if stable release..."); - - if (!AppVeyor.IsRunningOnAppVeyor) - { - throw new Exception("Stable release should happen via appveyor"); - } - - Information("Running on AppVeyor..."); - - Information("IsTag = " + AppVeyor.Environment.Repository.Tag.IsTag); - - Information("Name = " + AppVeyor.Environment.Repository.Tag.Name); - - var isTag = - AppVeyor.Environment.Repository.Tag.IsTag && - !string.IsNullOrWhiteSpace(AppVeyor.Environment.Repository.Tag.Name); - - if (!isTag) - { - throw new Exception("Stable release should happen from a published GitHub release"); - } - - Information("Release is stable..."); - }); - -Task("UpdateVersionInfo") - .IsDependentOn("EnsureStableReleaseRequirements") - .Does(() => - { - releaseTag = AppVeyor.Environment.Repository.Tag.Name; - AppVeyor.UpdateBuildVersion(releaseTag); - }); - -Task("DownloadGitHubReleaseArtifacts") - .IsDependentOn("UpdateVersionInfo") - .Does(() => - { - try - { - Information("DownloadGitHubReleaseArtifacts"); - - EnsureDirectoryExists(packagesDir); - - Information("Directory exists..."); - - var releaseUrl = tagsUrl + releaseTag; - - Information("Release url " + releaseUrl); - - //var releaseJson = Newtonsoft.Json.Linq.JObject.Parse(GetResource(releaseUrl)); - - var assets_url = Newtonsoft.Json.Linq.JObject.Parse(GetResource(releaseUrl)) - .GetValue("assets_url") - .Value(); - - Information("Assets url " + assets_url); - - var assets = GetResource(assets_url); - - Information("Assets " + assets_url); - - foreach(var asset in Newtonsoft.Json.JsonConvert.DeserializeObject(assets)) - { - Information("In the loop.."); - - var file = packagesDir + File(asset.Value("name")); - - Information("Downloading " + file); - - DownloadFile(asset.Value("browser_download_url"), file); - } - - Information("Out of the loop..."); - } - catch(Exception exception) - { - Information("There was an exception " + exception); - throw; - } - }); - -Task("ReleasePackagesToStableFeed") - .IsDependentOn("DownloadGitHubReleaseArtifacts") - .Does(() => - { - PublishPackages(packagesDir, artifactsFile, nugetFeedStableKey, nugetFeedStableUploadUrl, nugetFeedStableSymbolsUploadUrl); - }); - -Task("Release") - .IsDependentOn("ReleasePackagesToStableFeed"); - -RunTarget(target); - -/// Gets nuique nuget version for this commit -private GitVersion GetNuGetVersionForCommit() -{ - GitVersion(new GitVersionSettings{ - UpdateAssemblyInfo = false, - OutputType = GitVersionOutput.BuildServer - }); - - return GitVersion(new GitVersionSettings{ OutputType = GitVersionOutput.Json }); -} - -/// Updates project version in all of our projects -private void PersistVersion(string committedVersion, string newVersion) -{ - Information(string.Format("We'll search all csproj files for {0} and replace with {1}...", committedVersion, newVersion)); - - var projectFiles = GetFiles("./**/*.csproj"); - - foreach(var projectFile in projectFiles) - { - var file = projectFile.ToString(); - - Information(string.Format("Updating {0}...", file)); - - var updatedProjectFile = System.IO.File.ReadAllText(file) - .Replace(committedVersion, newVersion); - - System.IO.File.WriteAllText(file, updatedProjectFile); - } -} - -/// generates release notes based on issues closed in GitHub since the last release -private void GenerateReleaseNotes(ConvertableFilePath file) -{ - if(!IsRunningOnWindows()) - { - Warning("We are not running on Windows so we cannot generate release notes."); - return; - } - - Information("Generating release notes at " + file); - - var releaseNotesExitCode = StartProcess( - @"tools/GitReleaseNotes/tools/gitreleasenotes.exe", - new ProcessSettings { Arguments = ". /o " + file }); - - if (string.IsNullOrEmpty(System.IO.File.ReadAllText(file))) - { - System.IO.File.WriteAllText(file, "No issues closed since last release"); - } - - if (releaseNotesExitCode != 0) - { - throw new Exception("Failed to generate release notes"); - } -} - -/// Publishes code and symbols packages to nuget feed, based on contents of artifacts file -private void PublishPackages(ConvertableDirectoryPath packagesDir, ConvertableFilePath artifactsFile, string feedApiKey, string codeFeedUrl, string symbolFeedUrl) -{ - var artifacts = System.IO.File - .ReadAllLines(artifactsFile) - .Select(l => l.Split(':')) - .ToDictionary(v => v[0], v => v[1]); - - var codePackage = packagesDir + File(artifacts["nuget"]); - - Information("Pushing package " + codePackage); - - NuGetPush( - codePackage, - new NuGetPushSettings { - ApiKey = feedApiKey, - Source = codeFeedUrl - }); -} - -/// gets the resource from the specified url -private string GetResource(string url) -{ - try - { - Information("Getting resource from " + url); - - var assetsRequest = System.Net.WebRequest.CreateHttp(url); - assetsRequest.Method = "GET"; - assetsRequest.Accept = "application/vnd.github.v3+json"; - assetsRequest.UserAgent = "BuildScript"; - - using (var assetsResponse = assetsRequest.GetResponse()) - { - var assetsStream = assetsResponse.GetResponseStream(); - var assetsReader = new StreamReader(assetsStream); - var response = assetsReader.ReadToEnd(); - - Information("Response is " + response); - - return response; - } - } - catch(Exception exception) - { - Information("There was an exception " + exception); - throw; - } -} - -private bool ShouldPublishToUnstableFeed(string filter, string branchName) -{ - var regex = new System.Text.RegularExpressions.Regex(filter); - var publish = regex.IsMatch(branchName); - if (publish) - { - Information("Branch " + branchName + " will be published to the unstable feed"); - } - else - { - Information("Branch " + branchName + " will not be published to the unstable feed"); - } - return publish; -} +#tool "nuget:?package=GitVersion.CommandLine" +#tool "nuget:?package=GitReleaseNotes" +#addin nuget:?package=Cake.Json +#addin nuget:?package=Newtonsoft.Json&version=9.0.1 +#tool "nuget:?package=OpenCover" +#tool "nuget:?package=ReportGenerator" +#tool coveralls.net +#addin Cake.Coveralls + +// compile +var compileConfig = Argument("configuration", "Release"); +var slnFile = "./Ocelot.sln"; + +// build artifacts +var artifactsDir = Directory("artifacts"); + +// unit testing +var artifactsForUnitTestsDir = artifactsDir + Directory("UnitTests"); +var unitTestAssemblies = @"./test/Ocelot.UnitTests/Ocelot.UnitTests.csproj"; +var minCodeCoverage = 76.4d; +var coverallsRepoToken = "coveralls-repo-token-ocelot"; +var coverallsRepo = "https://coveralls.io/github/TomPallister/Ocelot"; + +// acceptance testing +var artifactsForAcceptanceTestsDir = artifactsDir + Directory("AcceptanceTests"); +var acceptanceTestAssemblies = @"./test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj"; + +// integration testing +var artifactsForIntegrationTestsDir = artifactsDir + Directory("IntegrationTests"); +var integrationTestAssemblies = @"./test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj"; + +// benchmark testing +var artifactsForBenchmarkTestsDir = artifactsDir + Directory("BenchmarkTests"); +var benchmarkTestAssemblies = @"./test/Ocelot.Benchmarks"; + +// packaging +var packagesDir = artifactsDir + Directory("Packages"); +var releaseNotesFile = packagesDir + File("releasenotes.md"); +var artifactsFile = packagesDir + File("artifacts.txt"); + +// unstable releases +var nugetFeedUnstableKey = EnvironmentVariable("nuget-apikey-unstable"); +var nugetFeedUnstableUploadUrl = "https://www.nuget.org/api/v2/package"; +var nugetFeedUnstableSymbolsUploadUrl = "https://www.nuget.org/api/v2/package"; + +// stable releases +var tagsUrl = "https://api.github.com/repos/tompallister/ocelot/releases/tags/"; +var nugetFeedStableKey = EnvironmentVariable("nuget-apikey-stable"); +var nugetFeedStableUploadUrl = "https://www.nuget.org/api/v2/package"; +var nugetFeedStableSymbolsUploadUrl = "https://www.nuget.org/api/v2/package"; + +// internal build variables - don't change these. +var releaseTag = ""; +string committedVersion = "0.0.0-dev"; +var buildVersion = committedVersion; +GitVersion versioning = null; +var nugetFeedUnstableBranchFilter = "^(develop)$|^(PullRequest/)"; + +var target = Argument("target", "Default"); + + +Information("target is " +target); +Information("Build configuration is " + compileConfig); + +Task("Default") + .IsDependentOn("Build"); + +Task("Build") + .IsDependentOn("RunTests") + .IsDependentOn("CreatePackages"); + +Task("BuildAndReleaseUnstable") + .IsDependentOn("Build") + .IsDependentOn("ReleasePackagesToUnstableFeed"); + +Task("Clean") + .Does(() => + { + if (DirectoryExists(artifactsDir)) + { + DeleteDirectory(artifactsDir, recursive:true); + } + CreateDirectory(artifactsDir); + }); + +Task("Version") + .Does(() => + { + versioning = GetNuGetVersionForCommit(); + var nugetVersion = versioning.NuGetVersion; + Information("SemVer version number: " + nugetVersion); + + if (AppVeyor.IsRunningOnAppVeyor) + { + Information("Persisting version number..."); + PersistVersion(committedVersion, nugetVersion); + buildVersion = nugetVersion; + } + else + { + Information("We are not running on build server, so we won't persist the version number."); + } + }); + +Task("Compile") + .IsDependentOn("Clean") + .IsDependentOn("Version") + .Does(() => + { + var settings = new DotNetCoreBuildSettings + { + Configuration = compileConfig, + }; + + DotNetCoreBuild(slnFile, settings); + }); + +Task("RunUnitTests") + .IsDependentOn("Compile") + .Does(() => + { + if (IsRunningOnWindows()) + { + var coverageSummaryFile = artifactsForUnitTestsDir + File("coverage.xml"); + + EnsureDirectoryExists(artifactsForUnitTestsDir); + + OpenCover(tool => + { + tool.DotNetCoreTest(unitTestAssemblies); + }, + new FilePath(coverageSummaryFile), + new OpenCoverSettings() + { + Register="user", + ArgumentCustomization=args=>args.Append(@"-oldstyle -returntargetcode -excludebyattribute:*.ExcludeFromCoverage*") + } + .WithFilter("+[Ocelot*]*") + .WithFilter("-[xunit*]*") + .WithFilter("-[Ocelot*Tests]*") + ); + + ReportGenerator(coverageSummaryFile, artifactsForUnitTestsDir); + + if (AppVeyor.IsRunningOnAppVeyor) + { + var repoToken = EnvironmentVariable(coverallsRepoToken); + if (string.IsNullOrEmpty(repoToken)) + { + throw new Exception(string.Format("Coveralls repo token not found. Set environment variable '{0}'", coverallsRepoToken)); + } + + Information(string.Format("Uploading test coverage to {0}", coverallsRepo)); + CoverallsNet(coverageSummaryFile, CoverallsNetReportType.OpenCover, new CoverallsNetSettings() + { + RepoToken = repoToken + }); + } + else + { + Information("We are not running on the build server so we won't publish the coverage report to coveralls.io"); + } + + var sequenceCoverage = XmlPeek(coverageSummaryFile, "//CoverageSession/Summary/@sequenceCoverage"); + var branchCoverage = XmlPeek(coverageSummaryFile, "//CoverageSession/Summary/@branchCoverage"); + + Information("Sequence Coverage: " + sequenceCoverage); + + if(double.Parse(sequenceCoverage) < minCodeCoverage) + { + var whereToCheck = !AppVeyor.IsRunningOnAppVeyor ? coverallsRepo : artifactsForUnitTestsDir; + throw new Exception(string.Format("Code coverage fell below the threshold of {0}%. You can find the code coverage report at {1}", minCodeCoverage, whereToCheck)); + }; + + } + else + { + var settings = new DotNetCoreTestSettings + { + Configuration = compileConfig, + }; + + EnsureDirectoryExists(artifactsForUnitTestsDir); + DotNetCoreTest(unitTestAssemblies, settings); + } + }); + +Task("RunAcceptanceTests") + .IsDependentOn("Compile") + .Does(() => + { + var settings = new DotNetCoreTestSettings + { + Configuration = compileConfig, + ArgumentCustomization = args => args + .Append("--no-restore") + .Append("--no-build") + }; + + EnsureDirectoryExists(artifactsForAcceptanceTestsDir); + DotNetCoreTest(acceptanceTestAssemblies, settings); + }); + +Task("RunIntegrationTests") + .IsDependentOn("Compile") + .Does(() => + { + var settings = new DotNetCoreTestSettings + { + Configuration = compileConfig, + ArgumentCustomization = args => args + .Append("--no-restore") + .Append("--no-build") + }; + + EnsureDirectoryExists(artifactsForIntegrationTestsDir); + DotNetCoreTest(integrationTestAssemblies, settings); + }); + +Task("RunTests") + .IsDependentOn("RunUnitTests") + .IsDependentOn("RunAcceptanceTests") + .IsDependentOn("RunIntegrationTests"); + +Task("CreatePackages") + .IsDependentOn("Compile") + .Does(() => + { + EnsureDirectoryExists(packagesDir); + CopyFiles("./src/**/Ocelot.*.nupkg", packagesDir); + + //GenerateReleaseNotes(releaseNotesFile); + + System.IO.File.WriteAllLines(artifactsFile, new[]{ + "nuget:Ocelot." + buildVersion + ".nupkg", + //"releaseNotes:releasenotes.md" + }); + + if (AppVeyor.IsRunningOnAppVeyor) + { + var path = packagesDir.ToString() + @"/**/*"; + + foreach (var file in GetFiles(path)) + { + AppVeyor.UploadArtifact(file.FullPath); + } + } + }); + +Task("ReleasePackagesToUnstableFeed") + .IsDependentOn("CreatePackages") + .Does(() => + { + if (ShouldPublishToUnstableFeed(nugetFeedUnstableBranchFilter, versioning.BranchName)) + { + PublishPackages(packagesDir, artifactsFile, nugetFeedUnstableKey, nugetFeedUnstableUploadUrl, nugetFeedUnstableSymbolsUploadUrl); + } + }); + +Task("EnsureStableReleaseRequirements") + .Does(() => + { + Information("Check if stable release..."); + + if (!AppVeyor.IsRunningOnAppVeyor) + { + throw new Exception("Stable release should happen via appveyor"); + } + + Information("Running on AppVeyor..."); + + Information("IsTag = " + AppVeyor.Environment.Repository.Tag.IsTag); + + Information("Name = " + AppVeyor.Environment.Repository.Tag.Name); + + var isTag = + AppVeyor.Environment.Repository.Tag.IsTag && + !string.IsNullOrWhiteSpace(AppVeyor.Environment.Repository.Tag.Name); + + if (!isTag) + { + throw new Exception("Stable release should happen from a published GitHub release"); + } + + Information("Release is stable..."); + }); + +Task("UpdateVersionInfo") + .IsDependentOn("EnsureStableReleaseRequirements") + .Does(() => + { + releaseTag = AppVeyor.Environment.Repository.Tag.Name; + AppVeyor.UpdateBuildVersion(releaseTag); + }); + +Task("DownloadGitHubReleaseArtifacts") + .IsDependentOn("UpdateVersionInfo") + .Does(() => + { + try + { + Information("DownloadGitHubReleaseArtifacts"); + + EnsureDirectoryExists(packagesDir); + + Information("Directory exists..."); + + var releaseUrl = tagsUrl + releaseTag; + + Information("Release url " + releaseUrl); + + //var releaseJson = Newtonsoft.Json.Linq.JObject.Parse(GetResource(releaseUrl)); + + var assets_url = Newtonsoft.Json.Linq.JObject.Parse(GetResource(releaseUrl)) + .GetValue("assets_url") + .Value(); + + Information("Assets url " + assets_url); + + var assets = GetResource(assets_url); + + Information("Assets " + assets_url); + + foreach(var asset in Newtonsoft.Json.JsonConvert.DeserializeObject(assets)) + { + Information("In the loop.."); + + var file = packagesDir + File(asset.Value("name")); + + Information("Downloading " + file); + + DownloadFile(asset.Value("browser_download_url"), file); + } + + Information("Out of the loop..."); + } + catch(Exception exception) + { + Information("There was an exception " + exception); + throw; + } + }); + +Task("ReleasePackagesToStableFeed") + .IsDependentOn("DownloadGitHubReleaseArtifacts") + .Does(() => + { + PublishPackages(packagesDir, artifactsFile, nugetFeedStableKey, nugetFeedStableUploadUrl, nugetFeedStableSymbolsUploadUrl); + }); + +Task("Release") + .IsDependentOn("ReleasePackagesToStableFeed"); + +RunTarget(target); + +/// Gets nuique nuget version for this commit +private GitVersion GetNuGetVersionForCommit() +{ + GitVersion(new GitVersionSettings{ + UpdateAssemblyInfo = false, + OutputType = GitVersionOutput.BuildServer + }); + + return GitVersion(new GitVersionSettings{ OutputType = GitVersionOutput.Json }); +} + +/// Updates project version in all of our projects +private void PersistVersion(string committedVersion, string newVersion) +{ + Information(string.Format("We'll search all csproj files for {0} and replace with {1}...", committedVersion, newVersion)); + + var projectFiles = GetFiles("./**/*.csproj"); + + foreach(var projectFile in projectFiles) + { + var file = projectFile.ToString(); + + Information(string.Format("Updating {0}...", file)); + + var updatedProjectFile = System.IO.File.ReadAllText(file) + .Replace(committedVersion, newVersion); + + System.IO.File.WriteAllText(file, updatedProjectFile); + } +} + +/// generates release notes based on issues closed in GitHub since the last release +private void GenerateReleaseNotes(ConvertableFilePath file) +{ + if(!IsRunningOnWindows()) + { + Warning("We are not running on Windows so we cannot generate release notes."); + return; + } + + Information("Generating release notes at " + file); + + var releaseNotesExitCode = StartProcess( + @"tools/GitReleaseNotes/tools/gitreleasenotes.exe", + new ProcessSettings { Arguments = ". /o " + file }); + + if (string.IsNullOrEmpty(System.IO.File.ReadAllText(file))) + { + System.IO.File.WriteAllText(file, "No issues closed since last release"); + } + + if (releaseNotesExitCode != 0) + { + throw new Exception("Failed to generate release notes"); + } +} + +/// Publishes code and symbols packages to nuget feed, based on contents of artifacts file +private void PublishPackages(ConvertableDirectoryPath packagesDir, ConvertableFilePath artifactsFile, string feedApiKey, string codeFeedUrl, string symbolFeedUrl) +{ + var artifacts = System.IO.File + .ReadAllLines(artifactsFile) + .Select(l => l.Split(':')) + .ToDictionary(v => v[0], v => v[1]); + + var codePackage = packagesDir + File(artifacts["nuget"]); + + Information("Pushing package " + codePackage); + + NuGetPush( + codePackage, + new NuGetPushSettings { + ApiKey = feedApiKey, + Source = codeFeedUrl + }); +} + +/// gets the resource from the specified url +private string GetResource(string url) +{ + try + { + Information("Getting resource from " + url); + + var assetsRequest = System.Net.WebRequest.CreateHttp(url); + assetsRequest.Method = "GET"; + assetsRequest.Accept = "application/vnd.github.v3+json"; + assetsRequest.UserAgent = "BuildScript"; + + using (var assetsResponse = assetsRequest.GetResponse()) + { + var assetsStream = assetsResponse.GetResponseStream(); + var assetsReader = new StreamReader(assetsStream); + var response = assetsReader.ReadToEnd(); + + Information("Response is " + response); + + return response; + } + } + catch(Exception exception) + { + Information("There was an exception " + exception); + throw; + } +} + +private bool ShouldPublishToUnstableFeed(string filter, string branchName) +{ + var regex = new System.Text.RegularExpressions.Regex(filter); + var publish = regex.IsMatch(branchName); + if (publish) + { + Information("Branch " + branchName + " will be published to the unstable feed"); + } + else + { + Information("Branch " + branchName + " will not be published to the unstable feed"); + } + return publish; +} diff --git a/build.ps1 b/build.ps1 index 265bd1238..40b4d7407 100644 --- a/build.ps1 +++ b/build.ps1 @@ -1,234 +1,234 @@ -########################################################################## -# This is the Cake bootstrapper script for PowerShell. -# This file was downloaded from https://github.com/cake-build/resources -# Feel free to change this file to fit your needs. -########################################################################## - -<# - -.SYNOPSIS -This is a Powershell script to bootstrap a Cake build. - -.DESCRIPTION -This Powershell script will download NuGet if missing, restore NuGet tools (including Cake) -and execute your Cake build script with the parameters you provide. - -.PARAMETER Script -The build script to execute. -.PARAMETER Target -The build script target to run. -.PARAMETER Configuration -The build configuration to use. -.PARAMETER Verbosity -Specifies the amount of information to be displayed. -.PARAMETER ShowDescription -Shows description about tasks. -.PARAMETER DryRun -Performs a dry run. -.PARAMETER Experimental -Uses the nightly builds of the Roslyn script engine. -.PARAMETER Mono -Uses the Mono Compiler rather than the Roslyn script engine. -.PARAMETER SkipToolPackageRestore -Skips restoring of packages. -.PARAMETER ScriptArgs -Remaining arguments are added here. - -.LINK -https://cakebuild.net - -#> - -[CmdletBinding()] -Param( - [string]$Script = "build.cake", - [string]$Target, - [string]$Configuration, - [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] - [string]$Verbosity, - [switch]$ShowDescription, - [Alias("WhatIf", "Noop")] - [switch]$DryRun, - [switch]$Experimental, - [switch]$Mono, - [switch]$SkipToolPackageRestore, - [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] - [string[]]$ScriptArgs -) - -[Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null -function MD5HashFile([string] $filePath) -{ - if ([string]::IsNullOrEmpty($filePath) -or !(Test-Path $filePath -PathType Leaf)) - { - return $null - } - - [System.IO.Stream] $file = $null; - [System.Security.Cryptography.MD5] $md5 = $null; - try - { - $md5 = [System.Security.Cryptography.MD5]::Create() - $file = [System.IO.File]::OpenRead($filePath) - return [System.BitConverter]::ToString($md5.ComputeHash($file)) - } - finally - { - if ($file -ne $null) - { - $file.Dispose() - } - } -} - -function GetProxyEnabledWebClient -{ - $wc = New-Object System.Net.WebClient - $proxy = [System.Net.WebRequest]::GetSystemWebProxy() - $proxy.Credentials = [System.Net.CredentialCache]::DefaultCredentials - $wc.Proxy = $proxy - return $wc -} - -Write-Host "Preparing to run build script..." - -if(!$PSScriptRoot){ - $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent -} - -$TOOLS_DIR = Join-Path $PSScriptRoot "tools" -$ADDINS_DIR = Join-Path $TOOLS_DIR "Addins" -$MODULES_DIR = Join-Path $TOOLS_DIR "Modules" -$NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe" -$CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe" -$NUGET_URL = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" -$PACKAGES_CONFIG = Join-Path $TOOLS_DIR "packages.config" -$PACKAGES_CONFIG_MD5 = Join-Path $TOOLS_DIR "packages.config.md5sum" -$ADDINS_PACKAGES_CONFIG = Join-Path $ADDINS_DIR "packages.config" -$MODULES_PACKAGES_CONFIG = Join-Path $MODULES_DIR "packages.config" - -# Make sure tools folder exists -if ((Test-Path $PSScriptRoot) -and !(Test-Path $TOOLS_DIR)) { - Write-Verbose -Message "Creating tools directory..." - New-Item -Path $TOOLS_DIR -Type directory | out-null -} - -# Make sure that packages.config exist. -if (!(Test-Path $PACKAGES_CONFIG)) { - Write-Verbose -Message "Downloading packages.config..." - try { - $wc = GetProxyEnabledWebClient - $wc.DownloadFile("https://cakebuild.net/download/bootstrapper/packages", $PACKAGES_CONFIG) } catch { - Throw "Could not download packages.config." - } -} - -# Try find NuGet.exe in path if not exists -if (!(Test-Path $NUGET_EXE)) { - Write-Verbose -Message "Trying to find nuget.exe in PATH..." - $existingPaths = $Env:Path -Split ';' | Where-Object { (![string]::IsNullOrEmpty($_)) -and (Test-Path $_ -PathType Container) } - $NUGET_EXE_IN_PATH = Get-ChildItem -Path $existingPaths -Filter "nuget.exe" | Select -First 1 - if ($NUGET_EXE_IN_PATH -ne $null -and (Test-Path $NUGET_EXE_IN_PATH.FullName)) { - Write-Verbose -Message "Found in PATH at $($NUGET_EXE_IN_PATH.FullName)." - $NUGET_EXE = $NUGET_EXE_IN_PATH.FullName - } -} - -# Try download NuGet.exe if not exists -if (!(Test-Path $NUGET_EXE)) { - Write-Verbose -Message "Downloading NuGet.exe..." - try { - $wc = GetProxyEnabledWebClient - $wc.DownloadFile($NUGET_URL, $NUGET_EXE) - } catch { - Throw "Could not download NuGet.exe." - } -} - -# Save nuget.exe path to environment to be available to child processed -$ENV:NUGET_EXE = $NUGET_EXE - -# Restore tools from NuGet? -if(-Not $SkipToolPackageRestore.IsPresent) { - Push-Location - Set-Location $TOOLS_DIR - - # Check for changes in packages.config and remove installed tools if true. - [string] $md5Hash = MD5HashFile($PACKAGES_CONFIG) - if((!(Test-Path $PACKAGES_CONFIG_MD5)) -Or - ($md5Hash -ne (Get-Content $PACKAGES_CONFIG_MD5 ))) { - Write-Verbose -Message "Missing or changed package.config hash..." - Remove-Item * -Recurse -Exclude packages.config,nuget.exe - } - - Write-Verbose -Message "Restoring tools from NuGet..." - $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$TOOLS_DIR`"" - - if ($LASTEXITCODE -ne 0) { - Throw "An error occured while restoring NuGet tools." - } - else - { - $md5Hash | Out-File $PACKAGES_CONFIG_MD5 -Encoding "ASCII" - } - Write-Verbose -Message ($NuGetOutput | out-string) - - Pop-Location -} - -# Restore addins from NuGet -if (Test-Path $ADDINS_PACKAGES_CONFIG) { - Push-Location - Set-Location $ADDINS_DIR - - Write-Verbose -Message "Restoring addins from NuGet..." - $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$ADDINS_DIR`"" - - if ($LASTEXITCODE -ne 0) { - Throw "An error occured while restoring NuGet addins." - } - - Write-Verbose -Message ($NuGetOutput | out-string) - - Pop-Location -} - -# Restore modules from NuGet -if (Test-Path $MODULES_PACKAGES_CONFIG) { - Push-Location - Set-Location $MODULES_DIR - - Write-Verbose -Message "Restoring modules from NuGet..." - $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$MODULES_DIR`"" - - if ($LASTEXITCODE -ne 0) { - Throw "An error occured while restoring NuGet modules." - } - - Write-Verbose -Message ($NuGetOutput | out-string) - - Pop-Location -} - -# Make sure that Cake has been installed. -if (!(Test-Path $CAKE_EXE)) { - Throw "Could not find Cake.exe at $CAKE_EXE" -} - - - -# Build Cake arguments -$cakeArguments = @("$Script"); -if ($Target) { $cakeArguments += "-target=$Target" } -if ($Configuration) { $cakeArguments += "-configuration=$Configuration" } -if ($Verbosity) { $cakeArguments += "-verbosity=$Verbosity" } -if ($ShowDescription) { $cakeArguments += "-showdescription" } -if ($DryRun) { $cakeArguments += "-dryrun" } -if ($Experimental) { $cakeArguments += "-experimental" } -if ($Mono) { $cakeArguments += "-mono" } -$cakeArguments += $ScriptArgs - -# Start Cake -Write-Host "Running build script..." -&$CAKE_EXE $cakeArguments -exit $LASTEXITCODE +########################################################################## +# This is the Cake bootstrapper script for PowerShell. +# This file was downloaded from https://github.com/cake-build/resources +# Feel free to change this file to fit your needs. +########################################################################## + +<# + +.SYNOPSIS +This is a Powershell script to bootstrap a Cake build. + +.DESCRIPTION +This Powershell script will download NuGet if missing, restore NuGet tools (including Cake) +and execute your Cake build script with the parameters you provide. + +.PARAMETER Script +The build script to execute. +.PARAMETER Target +The build script target to run. +.PARAMETER Configuration +The build configuration to use. +.PARAMETER Verbosity +Specifies the amount of information to be displayed. +.PARAMETER ShowDescription +Shows description about tasks. +.PARAMETER DryRun +Performs a dry run. +.PARAMETER Experimental +Uses the nightly builds of the Roslyn script engine. +.PARAMETER Mono +Uses the Mono Compiler rather than the Roslyn script engine. +.PARAMETER SkipToolPackageRestore +Skips restoring of packages. +.PARAMETER ScriptArgs +Remaining arguments are added here. + +.LINK +https://cakebuild.net + +#> + +[CmdletBinding()] +Param( + [string]$Script = "build.cake", + [string]$Target, + [string]$Configuration, + [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] + [string]$Verbosity, + [switch]$ShowDescription, + [Alias("WhatIf", "Noop")] + [switch]$DryRun, + [switch]$Experimental, + [switch]$Mono, + [switch]$SkipToolPackageRestore, + [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] + [string[]]$ScriptArgs +) + +[Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null +function MD5HashFile([string] $filePath) +{ + if ([string]::IsNullOrEmpty($filePath) -or !(Test-Path $filePath -PathType Leaf)) + { + return $null + } + + [System.IO.Stream] $file = $null; + [System.Security.Cryptography.MD5] $md5 = $null; + try + { + $md5 = [System.Security.Cryptography.MD5]::Create() + $file = [System.IO.File]::OpenRead($filePath) + return [System.BitConverter]::ToString($md5.ComputeHash($file)) + } + finally + { + if ($file -ne $null) + { + $file.Dispose() + } + } +} + +function GetProxyEnabledWebClient +{ + $wc = New-Object System.Net.WebClient + $proxy = [System.Net.WebRequest]::GetSystemWebProxy() + $proxy.Credentials = [System.Net.CredentialCache]::DefaultCredentials + $wc.Proxy = $proxy + return $wc +} + +Write-Host "Preparing to run build script..." + +if(!$PSScriptRoot){ + $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent +} + +$TOOLS_DIR = Join-Path $PSScriptRoot "tools" +$ADDINS_DIR = Join-Path $TOOLS_DIR "Addins" +$MODULES_DIR = Join-Path $TOOLS_DIR "Modules" +$NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe" +$CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe" +$NUGET_URL = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" +$PACKAGES_CONFIG = Join-Path $TOOLS_DIR "packages.config" +$PACKAGES_CONFIG_MD5 = Join-Path $TOOLS_DIR "packages.config.md5sum" +$ADDINS_PACKAGES_CONFIG = Join-Path $ADDINS_DIR "packages.config" +$MODULES_PACKAGES_CONFIG = Join-Path $MODULES_DIR "packages.config" + +# Make sure tools folder exists +if ((Test-Path $PSScriptRoot) -and !(Test-Path $TOOLS_DIR)) { + Write-Verbose -Message "Creating tools directory..." + New-Item -Path $TOOLS_DIR -Type directory | out-null +} + +# Make sure that packages.config exist. +if (!(Test-Path $PACKAGES_CONFIG)) { + Write-Verbose -Message "Downloading packages.config..." + try { + $wc = GetProxyEnabledWebClient + $wc.DownloadFile("https://cakebuild.net/download/bootstrapper/packages", $PACKAGES_CONFIG) } catch { + Throw "Could not download packages.config." + } +} + +# Try find NuGet.exe in path if not exists +if (!(Test-Path $NUGET_EXE)) { + Write-Verbose -Message "Trying to find nuget.exe in PATH..." + $existingPaths = $Env:Path -Split ';' | Where-Object { (![string]::IsNullOrEmpty($_)) -and (Test-Path $_ -PathType Container) } + $NUGET_EXE_IN_PATH = Get-ChildItem -Path $existingPaths -Filter "nuget.exe" | Select -First 1 + if ($NUGET_EXE_IN_PATH -ne $null -and (Test-Path $NUGET_EXE_IN_PATH.FullName)) { + Write-Verbose -Message "Found in PATH at $($NUGET_EXE_IN_PATH.FullName)." + $NUGET_EXE = $NUGET_EXE_IN_PATH.FullName + } +} + +# Try download NuGet.exe if not exists +if (!(Test-Path $NUGET_EXE)) { + Write-Verbose -Message "Downloading NuGet.exe..." + try { + $wc = GetProxyEnabledWebClient + $wc.DownloadFile($NUGET_URL, $NUGET_EXE) + } catch { + Throw "Could not download NuGet.exe." + } +} + +# Save nuget.exe path to environment to be available to child processed +$ENV:NUGET_EXE = $NUGET_EXE + +# Restore tools from NuGet? +if(-Not $SkipToolPackageRestore.IsPresent) { + Push-Location + Set-Location $TOOLS_DIR + + # Check for changes in packages.config and remove installed tools if true. + [string] $md5Hash = MD5HashFile($PACKAGES_CONFIG) + if((!(Test-Path $PACKAGES_CONFIG_MD5)) -Or + ($md5Hash -ne (Get-Content $PACKAGES_CONFIG_MD5 ))) { + Write-Verbose -Message "Missing or changed package.config hash..." + Remove-Item * -Recurse -Exclude packages.config,nuget.exe + } + + Write-Verbose -Message "Restoring tools from NuGet..." + $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$TOOLS_DIR`"" + + if ($LASTEXITCODE -ne 0) { + Throw "An error occured while restoring NuGet tools." + } + else + { + $md5Hash | Out-File $PACKAGES_CONFIG_MD5 -Encoding "ASCII" + } + Write-Verbose -Message ($NuGetOutput | out-string) + + Pop-Location +} + +# Restore addins from NuGet +if (Test-Path $ADDINS_PACKAGES_CONFIG) { + Push-Location + Set-Location $ADDINS_DIR + + Write-Verbose -Message "Restoring addins from NuGet..." + $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$ADDINS_DIR`"" + + if ($LASTEXITCODE -ne 0) { + Throw "An error occured while restoring NuGet addins." + } + + Write-Verbose -Message ($NuGetOutput | out-string) + + Pop-Location +} + +# Restore modules from NuGet +if (Test-Path $MODULES_PACKAGES_CONFIG) { + Push-Location + Set-Location $MODULES_DIR + + Write-Verbose -Message "Restoring modules from NuGet..." + $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$MODULES_DIR`"" + + if ($LASTEXITCODE -ne 0) { + Throw "An error occured while restoring NuGet modules." + } + + Write-Verbose -Message ($NuGetOutput | out-string) + + Pop-Location +} + +# Make sure that Cake has been installed. +if (!(Test-Path $CAKE_EXE)) { + Throw "Could not find Cake.exe at $CAKE_EXE" +} + + + +# Build Cake arguments +$cakeArguments = @("$Script"); +if ($Target) { $cakeArguments += "-target=$Target" } +if ($Configuration) { $cakeArguments += "-configuration=$Configuration" } +if ($Verbosity) { $cakeArguments += "-verbosity=$Verbosity" } +if ($ShowDescription) { $cakeArguments += "-showdescription" } +if ($DryRun) { $cakeArguments += "-dryrun" } +if ($Experimental) { $cakeArguments += "-experimental" } +if ($Mono) { $cakeArguments += "-mono" } +$cakeArguments += $ScriptArgs + +# Start Cake +Write-Host "Running build script..." +&$CAKE_EXE $cakeArguments +exit $LASTEXITCODE diff --git a/build.sh b/build.sh index 04731adf1..d0eed8a31 100755 --- a/build.sh +++ b/build.sh @@ -1,101 +1,101 @@ -#!/usr/bin/env bash - -########################################################################## -# This is the Cake bootstrapper script for Linux and OS X. -# This file was downloaded from https://github.com/cake-build/resources -# Feel free to change this file to fit your needs. -########################################################################## - -# Define directories. -SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) -TOOLS_DIR=$SCRIPT_DIR/tools -NUGET_EXE=$TOOLS_DIR/nuget.exe -CAKE_EXE=$TOOLS_DIR/Cake/Cake.exe -PACKAGES_CONFIG=$TOOLS_DIR/packages.config -PACKAGES_CONFIG_MD5=$TOOLS_DIR/packages.config.md5sum - -# Define md5sum or md5 depending on Linux/OSX -MD5_EXE= -if [[ "$(uname -s)" == "Darwin" ]]; then - MD5_EXE="md5 -r" -else - MD5_EXE="md5sum" -fi - -# Define default arguments. -SCRIPT="build.cake" -TARGET="Default" -CONFIGURATION="Release" -VERBOSITY="verbose" -DRYRUN= -SHOW_VERSION=false -SCRIPT_ARGUMENTS=() - -# Parse arguments. -for i in "$@"; do - case $1 in - -s|--script) SCRIPT="$2"; shift ;; - -t|--target) TARGET="$2"; shift ;; - -c|--configuration) CONFIGURATION="$2"; shift ;; - -v|--verbosity) VERBOSITY="$2"; shift ;; - -d|--dryrun) DRYRUN="-dryrun" ;; - --version) SHOW_VERSION=true ;; - --) shift; SCRIPT_ARGUMENTS+=("$@"); break ;; - *) SCRIPT_ARGUMENTS+=("$1") ;; - esac - shift -done - -# Make sure the tools folder exist. -if [ ! -d "$TOOLS_DIR" ]; then - mkdir "$TOOLS_DIR" -fi - -# Make sure that packages.config exist. -if [ ! -f "$TOOLS_DIR/packages.config" ]; then - echo "Downloading packages.config..." - curl -Lsfo "$TOOLS_DIR/packages.config" http://cakebuild.net/download/bootstrapper/packages - if [ $? -ne 0 ]; then - echo "An error occured while downloading packages.config." - exit 1 - fi -fi - -# Download NuGet if it does not exist. -if [ ! -f "$NUGET_EXE" ]; then - echo "Downloading NuGet..." - curl -Lsfo "$NUGET_EXE" https://dist.nuget.org/win-x86-commandline/latest/nuget.exe - if [ $? -ne 0 ]; then - echo "An error occured while downloading nuget.exe." - exit 1 - fi -fi - -# Restore tools from NuGet. -pushd "$TOOLS_DIR" >/dev/null -if [ ! -f "$PACKAGES_CONFIG_MD5" ] || [ "$( cat "$PACKAGES_CONFIG_MD5" | sed 's/\r$//' )" != "$( $MD5_EXE "$PACKAGES_CONFIG" | awk '{ print $1 }' )" ]; then - find . -type d ! -name . | xargs rm -rf -fi - -mono "$NUGET_EXE" install -ExcludeVersion -if [ $? -ne 0 ]; then - echo "Could not restore NuGet packages." - exit 1 -fi - -$MD5_EXE "$PACKAGES_CONFIG" | awk '{ print $1 }' >| "$PACKAGES_CONFIG_MD5" - -popd >/dev/null - -# Make sure that Cake has been installed. -if [ ! -f "$CAKE_EXE" ]; then - echo "Could not find Cake.exe at '$CAKE_EXE'." - exit 1 -fi - -# Start Cake -if $SHOW_VERSION; then - exec mono "$CAKE_EXE" -version -else - exec mono "$CAKE_EXE" $SCRIPT -verbosity=$VERBOSITY -configuration=$CONFIGURATION -target=$TARGET $DRYRUN "${SCRIPT_ARGUMENTS[@]}" +#!/usr/bin/env bash + +########################################################################## +# This is the Cake bootstrapper script for Linux and OS X. +# This file was downloaded from https://github.com/cake-build/resources +# Feel free to change this file to fit your needs. +########################################################################## + +# Define directories. +SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +TOOLS_DIR=$SCRIPT_DIR/tools +NUGET_EXE=$TOOLS_DIR/nuget.exe +CAKE_EXE=$TOOLS_DIR/Cake/Cake.exe +PACKAGES_CONFIG=$TOOLS_DIR/packages.config +PACKAGES_CONFIG_MD5=$TOOLS_DIR/packages.config.md5sum + +# Define md5sum or md5 depending on Linux/OSX +MD5_EXE= +if [[ "$(uname -s)" == "Darwin" ]]; then + MD5_EXE="md5 -r" +else + MD5_EXE="md5sum" +fi + +# Define default arguments. +SCRIPT="build.cake" +TARGET="Default" +CONFIGURATION="Release" +VERBOSITY="verbose" +DRYRUN= +SHOW_VERSION=false +SCRIPT_ARGUMENTS=() + +# Parse arguments. +for i in "$@"; do + case $1 in + -s|--script) SCRIPT="$2"; shift ;; + -t|--target) TARGET="$2"; shift ;; + -c|--configuration) CONFIGURATION="$2"; shift ;; + -v|--verbosity) VERBOSITY="$2"; shift ;; + -d|--dryrun) DRYRUN="-dryrun" ;; + --version) SHOW_VERSION=true ;; + --) shift; SCRIPT_ARGUMENTS+=("$@"); break ;; + *) SCRIPT_ARGUMENTS+=("$1") ;; + esac + shift +done + +# Make sure the tools folder exist. +if [ ! -d "$TOOLS_DIR" ]; then + mkdir "$TOOLS_DIR" +fi + +# Make sure that packages.config exist. +if [ ! -f "$TOOLS_DIR/packages.config" ]; then + echo "Downloading packages.config..." + curl -Lsfo "$TOOLS_DIR/packages.config" http://cakebuild.net/download/bootstrapper/packages + if [ $? -ne 0 ]; then + echo "An error occured while downloading packages.config." + exit 1 + fi +fi + +# Download NuGet if it does not exist. +if [ ! -f "$NUGET_EXE" ]; then + echo "Downloading NuGet..." + curl -Lsfo "$NUGET_EXE" https://dist.nuget.org/win-x86-commandline/latest/nuget.exe + if [ $? -ne 0 ]; then + echo "An error occured while downloading nuget.exe." + exit 1 + fi +fi + +# Restore tools from NuGet. +pushd "$TOOLS_DIR" >/dev/null +if [ ! -f "$PACKAGES_CONFIG_MD5" ] || [ "$( cat "$PACKAGES_CONFIG_MD5" | sed 's/\r$//' )" != "$( $MD5_EXE "$PACKAGES_CONFIG" | awk '{ print $1 }' )" ]; then + find . -type d ! -name . | xargs rm -rf +fi + +mono "$NUGET_EXE" install -ExcludeVersion +if [ $? -ne 0 ]; then + echo "Could not restore NuGet packages." + exit 1 +fi + +$MD5_EXE "$PACKAGES_CONFIG" | awk '{ print $1 }' >| "$PACKAGES_CONFIG_MD5" + +popd >/dev/null + +# Make sure that Cake has been installed. +if [ ! -f "$CAKE_EXE" ]; then + echo "Could not find Cake.exe at '$CAKE_EXE'." + exit 1 +fi + +# Start Cake +if $SHOW_VERSION; then + exec mono "$CAKE_EXE" -version +else + exec mono "$CAKE_EXE" $SCRIPT -verbosity=$VERBOSITY -configuration=$CONFIGURATION -target=$TARGET $DRYRUN "${SCRIPT_ARGUMENTS[@]}" fi \ No newline at end of file diff --git a/docs/Makefile b/docs/Makefile index db9838c49..61e3150d1 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,225 +1,225 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = _build - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " applehelp to make an Apple Help Book" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " epub3 to make an epub3" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " xml to make Docutils-native XML files" - @echo " pseudoxml to make pseudoxml-XML files for display purposes" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - @echo " coverage to run coverage check of the documentation (if enabled)" - @echo " dummy to check syntax errors of document sources" - -.PHONY: clean -clean: - rm -rf $(BUILDDIR)/* - -.PHONY: html -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -.PHONY: dirhtml -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -.PHONY: singlehtml -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -.PHONY: pickle -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -.PHONY: json -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -.PHONY: htmlhelp -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -.PHONY: qthelp -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Ocelot.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Ocelot.qhc" - -.PHONY: applehelp -applehelp: - $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp - @echo - @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." - @echo "N.B. You won't be able to view it unless you put it in" \ - "~/Library/Documentation/Help or install it in your application" \ - "bundle." - -.PHONY: devhelp -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/Ocelot" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Ocelot" - @echo "# devhelp" - -.PHONY: epub -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -.PHONY: epub3 -epub3: - $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 - @echo - @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." - -.PHONY: latex -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -.PHONY: latexpdf -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -.PHONY: latexpdfja -latexpdfja: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through platex and dvipdfmx..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -.PHONY: text -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -.PHONY: man -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -.PHONY: texinfo -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -.PHONY: info -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -.PHONY: gettext -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -.PHONY: changes -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -.PHONY: linkcheck -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -.PHONY: doctest -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." - -.PHONY: coverage -coverage: - $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage - @echo "Testing of coverage in the sources finished, look at the " \ - "results in $(BUILDDIR)/coverage/python.txt." - -.PHONY: xml -xml: - $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml - @echo - @echo "Build finished. The XML files are in $(BUILDDIR)/xml." - -.PHONY: pseudoxml -pseudoxml: - $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml - @echo - @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." - -.PHONY: dummy -dummy: - $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy - @echo - @echo "Build finished. Dummy builder generates no files." +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " epub3 to make an epub3" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + @echo " dummy to check syntax errors of document sources" + +.PHONY: clean +clean: + rm -rf $(BUILDDIR)/* + +.PHONY: html +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +.PHONY: dirhtml +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +.PHONY: singlehtml +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +.PHONY: pickle +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +.PHONY: json +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +.PHONY: htmlhelp +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +.PHONY: qthelp +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Ocelot.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Ocelot.qhc" + +.PHONY: applehelp +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +.PHONY: devhelp +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/Ocelot" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Ocelot" + @echo "# devhelp" + +.PHONY: epub +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +.PHONY: epub3 +epub3: + $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 + @echo + @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." + +.PHONY: latex +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +.PHONY: latexpdf +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: latexpdfja +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: text +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +.PHONY: man +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +.PHONY: texinfo +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +.PHONY: info +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +.PHONY: gettext +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +.PHONY: changes +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +.PHONY: linkcheck +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +.PHONY: doctest +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +.PHONY: coverage +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +.PHONY: xml +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +.PHONY: pseudoxml +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." + +.PHONY: dummy +dummy: + $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy + @echo + @echo "Build finished. Dummy builder generates no files." diff --git a/docs/building/building.rst b/docs/building/building.rst index 23a0f5b0a..8fbd37b91 100644 --- a/docs/building/building.rst +++ b/docs/building/building.rst @@ -1,12 +1,12 @@ -Building -======== - -* You'll generally want to run the `./build.ps1` script. This will compile, run unit and acceptance tests and build the output packages locally. Output will got to the `./artifacts` directory. - -* You can view the current commit's `SemVer `_ build information by running `./version.ps1`. - -* The other `./*.ps1` scripts perform subsets of the build process, if you don't want to run the full build. - -* The release process works best with GitFlow branching; this allows us to publish every development commit to an unstable feed with a unique SemVer version, and then choose when to release to a stable feed. - +Building +======== + +* You'll generally want to run the `./build.ps1` script. This will compile, run unit and acceptance tests and build the output packages locally. Output will got to the `./artifacts` directory. + +* You can view the current commit's `SemVer `_ build information by running `./version.ps1`. + +* The other `./*.ps1` scripts perform subsets of the build process, if you don't want to run the full build. + +* The release process works best with GitFlow branching; this allows us to publish every development commit to an unstable feed with a unique SemVer version, and then choose when to release to a stable feed. + * Alternatively you can build the project in VS2017 with the latest .NET Core SDK. \ No newline at end of file diff --git a/docs/building/overview.rst b/docs/building/overview.rst index 5dea8976d..a5977f495 100644 --- a/docs/building/overview.rst +++ b/docs/building/overview.rst @@ -1,4 +1,4 @@ -Overview -======== - +Overview +======== + This document summarises the build and release process for the project. The build scripts are written using `Cake `_, and are defined in `./build.cake`. The scripts have been designed to be run by either developers locally or by a build server (currently `AppVeyor `_), with minimal logic defined in the build server itself. \ No newline at end of file diff --git a/docs/building/releaseprocess.rst b/docs/building/releaseprocess.rst index abe5793de..3cfed2f0d 100644 --- a/docs/building/releaseprocess.rst +++ b/docs/building/releaseprocess.rst @@ -1,23 +1,23 @@ -Release process -=============== - -This section defines the release process for the maintainers of the project. -* Merge pull requests to the `release` branch. - -* Every commit pushed to the Origin repo will kick off the `ocelot-build `_ project in AppVeyor. This performs the same tasks as the command line build, and in addition pushes the packages to the unstable nuget feed. - -* When you're ready for a release, create a release branch. You'll probably want to update the committed `./ReleaseNotes.md` based on the contents of the equivalent file in the `./artifacts` directory. - -* When the `release` branch has built successfully in Appveyor, select the build and then Deploy to the `GitHub Release` environment. This will create a new release in GitHub. - -* In Github, navigate to the `release `_. Modify the release name and tag as desired. - -* When you're ready, publish the release. This will tag the commit with the specified release number. - -* The `ocelot-release `_ project will detect the newly created tag and kick off the release process. This will download the artifacts from GitHub, and publish the packages to the stable nuget feed. - -* When you have a final stable release build, merge the `release` branch into `master` and `develop`. Deploy the master branch to github and following the full release process as described above. Don't forget to uncheck the "This is a pre-release" checkbox in GitHub before publishing. - -* Note - because the release builds are initiated by tagging a commit, if for some reason a release build fails in AppVeyor you'll need to delete the tag from the repo and republish the release in GitHub. - - +Release process +=============== + +This section defines the release process for the maintainers of the project. +* Merge pull requests to the `release` branch. + +* Every commit pushed to the Origin repo will kick off the `ocelot-build `_ project in AppVeyor. This performs the same tasks as the command line build, and in addition pushes the packages to the unstable nuget feed. + +* When you're ready for a release, create a release branch. You'll probably want to update the committed `./ReleaseNotes.md` based on the contents of the equivalent file in the `./artifacts` directory. + +* When the `release` branch has built successfully in Appveyor, select the build and then Deploy to the `GitHub Release` environment. This will create a new release in GitHub. + +* In Github, navigate to the `release `_. Modify the release name and tag as desired. + +* When you're ready, publish the release. This will tag the commit with the specified release number. + +* The `ocelot-release `_ project will detect the newly created tag and kick off the release process. This will download the artifacts from GitHub, and publish the packages to the stable nuget feed. + +* When you have a final stable release build, merge the `release` branch into `master` and `develop`. Deploy the master branch to github and following the full release process as described above. Don't forget to uncheck the "This is a pre-release" checkbox in GitHub before publishing. + +* Note - because the release builds are initiated by tagging a commit, if for some reason a release build fails in AppVeyor you'll need to delete the tag from the repo and republish the release in GitHub. + + diff --git a/docs/building/tests.rst b/docs/building/tests.rst index ab8a86676..d1fc8aba7 100644 --- a/docs/building/tests.rst +++ b/docs/building/tests.rst @@ -1,24 +1,24 @@ -Tests -===== - -The tests should all just run and work apart from the integration tests which need the following -environmental variables setting. This is a manual step at the moment. - - ``OCELOT_USERNAME=admin`` - - ``OCELOT_HASH=kE/mxd1hO9h9Sl2VhGhwJUd9xZEv4NP6qXoN39nIqM4=`` - - ``OCELOT_SALT=zzWITpnDximUNKYLiUam/w==`` - -On windows you can use.. - - ``SETX OCELOT_USERNAME admin`` - -On mac.. - - ``export OCELOT_USERNAME=admin`` - -I need to work out a nicer way of doing this in the future. - - - +Tests +===== + +The tests should all just run and work apart from the integration tests which need the following +environmental variables setting. This is a manual step at the moment. + + ``OCELOT_USERNAME=admin`` + + ``OCELOT_HASH=kE/mxd1hO9h9Sl2VhGhwJUd9xZEv4NP6qXoN39nIqM4=`` + + ``OCELOT_SALT=zzWITpnDximUNKYLiUam/w==`` + +On windows you can use.. + + ``SETX OCELOT_USERNAME admin`` + +On mac.. + + ``export OCELOT_USERNAME=admin`` + +I need to work out a nicer way of doing this in the future. + + + diff --git a/docs/conf.py b/docs/conf.py index 6a92500c8..716ee20b9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,359 +1,359 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# -# Ocelot documentation build configuration file, created by -# sphinx-quickstart on Wed Jul 20 08:57:27 2016. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -# import os -# import sys -# sys.path.insert(0, os.path.abspath('.')) - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -# source_suffix = ['.rst', '.md'] - -# markdown support -#from recommonmark.parser import CommonMarkParser - -#source_parsers = { -# '.md': CommonMarkParser, -#} - -source_suffix = ['.rst'] - - -# The encoding of source files. -# -# source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = 'Ocelot' -copyright = '2016, Tom Pallister' -author = 'Tom Pallister' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '1.0.0' -# The full version, including alpha/beta/rc tags. -release = '1.0.0' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -# -# today = '' -# -# Else, today_fmt is used as the format for a strftime call. -# -# today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -# -# default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -# -# add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -# -# add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -# -# show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' -highlight_language = 'csharp' - -# A list of ignored prefixes for module index sorting. -# modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -# keep_warnings = False - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = False - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# - -# on_rtd is whether we are on readthedocs.org, this line of code grabbed from docs.readthedocs.org -import os -on_rtd = os.environ.get('READTHEDOCS', None) == 'True' - -if not on_rtd: # only import and set the theme if we're building docs locally - import sphinx_rtd_theme - html_theme = 'sphinx_rtd_theme' - html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] - -# otherwise, readthedocs.org uses their theme by default, so no need to specify it - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -# html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -# html_theme_path = [] - -# The name for this set of Sphinx documents. -# " v documentation" by default. -# -# html_title = 'Ocelot v1.0.0' - -# A shorter title for the navigation bar. Default is the same as html_title. -# -# html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -# -# html_logo = None - -# The name of an image file (relative to this directory) to use as a favicon of -# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -# -html_favicon = 'favicon.ico' - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -# -# html_extra_path = [] - -# If not None, a 'Last updated on:' timestamp is inserted at every page -# bottom, using the given strftime format. -# The empty string is equivalent to '%b %d, %Y'. -# -# html_last_updated_fmt = None - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -# -# html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -# -# html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -# -# html_additional_pages = {} - -# If false, no module index is generated. -# -# html_domain_indices = True - -# If false, no index is generated. -# -# html_use_index = True - -# If true, the index is split into individual pages for each letter. -# -# html_split_index = False - -# If true, links to the reST sources are added to the pages. -# -# html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -# -# html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -# -# html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -# -# html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -# html_file_suffix = None - -# Language to be used for generating the HTML full-text search index. -# Sphinx supports the following languages: -# 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' -# 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh' -# -# html_search_language = 'en' - -# A dictionary with options for the search language support, empty by default. -# 'ja' uses this config value. -# 'zh' user can custom change `jieba` dictionary path. -# -# html_search_options = {'type': 'default'} - -# The name of a javascript file (relative to the configuration directory) that -# implements a search results scorer. If empty, the default will be used. -# -# html_search_scorer = 'scorer.js' - -# Output file base name for HTML help builder. -htmlhelp_basename = 'Ocelotdoc' - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'Ocelot.tex', 'Ocelot Documentation', - 'Tom Pallister', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -# -# latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -# -# latex_use_parts = False - -# If true, show page references after internal links. -# -# latex_show_pagerefs = False - -# If true, show URL addresses after external links. -# -# latex_show_urls = False - -# Documents to append as an appendix to all manuals. -# -# latex_appendices = [] - -# It false, will not define \strong, \code, itleref, \crossref ... but only -# \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added -# packages. -# -# latex_keep_old_macro_names = True - -# If false, no module index is generated. -# -# latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'Ocelot', 'Ocelot Documentation', - [author], 1) -] - -# If true, show URL addresses after external links. -# -# man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'Ocelot', 'Ocelot Documentation', - author, 'Ocelot', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -# -# texinfo_appendices = [] - -# If false, no module index is generated. -# -# texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -# -# texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -# -# texinfo_no_detailmenu = False +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Ocelot documentation build configuration file, created by +# sphinx-quickstart on Wed Jul 20 08:57:27 2016. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] + +# markdown support +#from recommonmark.parser import CommonMarkParser + +#source_parsers = { +# '.md': CommonMarkParser, +#} + +source_suffix = ['.rst'] + + +# The encoding of source files. +# +# source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'Ocelot' +copyright = '2016, Tom Pallister' +author = 'Tom Pallister' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '1.0.0' +# The full version, including alpha/beta/rc tags. +release = '1.0.0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +# +# today = '' +# +# Else, today_fmt is used as the format for a strftime call. +# +# today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +# +# default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +# +# add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +# +# add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +# +# show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' +highlight_language = 'csharp' + +# A list of ignored prefixes for module index sorting. +# modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +# keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# + +# on_rtd is whether we are on readthedocs.org, this line of code grabbed from docs.readthedocs.org +import os +on_rtd = os.environ.get('READTHEDOCS', None) == 'True' + +if not on_rtd: # only import and set the theme if we're building docs locally + import sphinx_rtd_theme + html_theme = 'sphinx_rtd_theme' + html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] + +# otherwise, readthedocs.org uses their theme by default, so no need to specify it + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +# html_theme_path = [] + +# The name for this set of Sphinx documents. +# " v documentation" by default. +# +# html_title = 'Ocelot v1.0.0' + +# A shorter title for the navigation bar. Default is the same as html_title. +# +# html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +# +# html_logo = None + +# The name of an image file (relative to this directory) to use as a favicon of +# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +# +html_favicon = 'favicon.ico' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +# +# html_extra_path = [] + +# If not None, a 'Last updated on:' timestamp is inserted at every page +# bottom, using the given strftime format. +# The empty string is equivalent to '%b %d, %Y'. +# +# html_last_updated_fmt = None + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +# +# html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +# +# html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +# +# html_additional_pages = {} + +# If false, no module index is generated. +# +# html_domain_indices = True + +# If false, no index is generated. +# +# html_use_index = True + +# If true, the index is split into individual pages for each letter. +# +# html_split_index = False + +# If true, links to the reST sources are added to the pages. +# +# html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +# +# html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +# +# html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +# +# html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +# html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh' +# +# html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# 'ja' uses this config value. +# 'zh' user can custom change `jieba` dictionary path. +# +# html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +# +# html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'Ocelotdoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'Ocelot.tex', 'Ocelot Documentation', + 'Tom Pallister', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +# +# latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +# +# latex_use_parts = False + +# If true, show page references after internal links. +# +# latex_show_pagerefs = False + +# If true, show URL addresses after external links. +# +# latex_show_urls = False + +# Documents to append as an appendix to all manuals. +# +# latex_appendices = [] + +# It false, will not define \strong, \code, itleref, \crossref ... but only +# \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added +# packages. +# +# latex_keep_old_macro_names = True + +# If false, no module index is generated. +# +# latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'Ocelot', 'Ocelot Documentation', + [author], 1) +] + +# If true, show URL addresses after external links. +# +# man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'Ocelot', 'Ocelot Documentation', + author, 'Ocelot', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +# +# texinfo_appendices = [] + +# If false, no module index is generated. +# +# texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# +# texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +# +# texinfo_no_detailmenu = False diff --git a/docs/features/administration.rst b/docs/features/administration.rst index 162920f6f..298e410e2 100644 --- a/docs/features/administration.rst +++ b/docs/features/administration.rst @@ -1,81 +1,81 @@ -Administration -============== - -Ocelot supports changing configuration during runtime via an authenticated HTTP API. The API is authenticated -using bearer tokens that you request from Ocelot iteself. This is provided by the amazing -`Identity Server `_ project that I have been using for a few years now. Check them out. - -In order to enable the administration section you need to do a few things. First of all add this to your -initial Startup.cs. - -The path can be anything you want and it is obviously reccomended don't use -a url you would like to route through with Ocelot as this will not work. The administration uses the -MapWhen functionality of asp.net core and all requests to {root}/administration will be sent there not -to the Ocelot middleware. - -The secret is the client secret that Ocelot's internal IdentityServer will use to authenticate requests to the administration API. This can be whatever you want it to be! - -.. code-block:: csharp - - public virtual void ConfigureServices(IServiceCollection services) - { - services - .AddOcelot(Configuration) - .AddAdministration("/administration", "secret"); - } - -Now if you went with the configuration options above and want to access the API you can use the postman scripts -called ocelot.postman_collection.json in the solution to change the Ocelot configuration. Obviously these -will need to be changed if you are running Ocelot on a different url to http://localhost:5000. - -The scripts show you how to request a bearer token from ocelot and then use it to GET the existing configuration and POST -a configuration. - -Administration running multiple Ocelot's -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -If you are running multiple Ocelot's in a cluster then you need to use a certificate to sign the bearer tokens used to access the administration API. - -In order to do this you need to add two more environmental variables for each Ocelot in the cluster. - -``OCELOT_CERTIFICATE`` - The path to a certificate that can be used to sign the tokens. The certificate needs to be of the type X509 and obviously Ocelot needs to be able to access it. -``OCELOT_CERTIFICATE_PASSWORD`` - The password for the certificate. - -Normally Ocelot just uses temporary signing credentials but if you set these environmental variables then it will use the certificate. If all the other Ocelots in the cluster have the same certificate then you are good! - -Administration API -^^^^^^^^^^^^^^^^^^ - -**POST {adminPath}/connect/token** - -This gets a token for use with the admin area using the client credentials we talk about setting above. Under the hood this calls into an IdentityServer hosted within Ocelot. - -The body of the request is form-data as follows - -``client_id`` set as admin - -``client_secret`` set as whatever you used when setting up the administration services. - -``scope`` set as admin - -``grant_type`` set as client_credentials - -**GET {adminPath}/configuration** - - -This gets the current Ocelot configuration. It is exactly the same JSON we use to set Ocelot up with in the first place. - -**POST {adminPath}/configuration** - - -This overrwrites the existing configuration (should probably be a put!). I reccomend getting your config from the GET endpoint, making any changes and posting it back...simples. - -The body of the request is JSON and it is the same format as the FileConfiguration.cs that we use to set up -Ocelot on a file system. - -**DELETE {adminPath}/outputcache/{region}** - -This clears a region of the cache. If you are using a backplane it will clear all instances of the cache! Giving your the ability to run a cluster of Ocelots and cache over all of them in memory and clear them all at the same time / just use a distributed cache. - -The region is whatever you set against the Region field in the FileCacheOptions section of the Ocelot configuration. +Administration +============== + +Ocelot supports changing configuration during runtime via an authenticated HTTP API. The API is authenticated +using bearer tokens that you request from Ocelot iteself. This is provided by the amazing +`Identity Server `_ project that I have been using for a few years now. Check them out. + +In order to enable the administration section you need to do a few things. First of all add this to your +initial Startup.cs. + +The path can be anything you want and it is obviously reccomended don't use +a url you would like to route through with Ocelot as this will not work. The administration uses the +MapWhen functionality of asp.net core and all requests to {root}/administration will be sent there not +to the Ocelot middleware. + +The secret is the client secret that Ocelot's internal IdentityServer will use to authenticate requests to the administration API. This can be whatever you want it to be! + +.. code-block:: csharp + + public virtual void ConfigureServices(IServiceCollection services) + { + services + .AddOcelot(Configuration) + .AddAdministration("/administration", "secret"); + } + +Now if you went with the configuration options above and want to access the API you can use the postman scripts +called ocelot.postman_collection.json in the solution to change the Ocelot configuration. Obviously these +will need to be changed if you are running Ocelot on a different url to http://localhost:5000. + +The scripts show you how to request a bearer token from ocelot and then use it to GET the existing configuration and POST +a configuration. + +Administration running multiple Ocelot's +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +If you are running multiple Ocelot's in a cluster then you need to use a certificate to sign the bearer tokens used to access the administration API. + +In order to do this you need to add two more environmental variables for each Ocelot in the cluster. + +``OCELOT_CERTIFICATE`` + The path to a certificate that can be used to sign the tokens. The certificate needs to be of the type X509 and obviously Ocelot needs to be able to access it. +``OCELOT_CERTIFICATE_PASSWORD`` + The password for the certificate. + +Normally Ocelot just uses temporary signing credentials but if you set these environmental variables then it will use the certificate. If all the other Ocelots in the cluster have the same certificate then you are good! + +Administration API +^^^^^^^^^^^^^^^^^^ + +**POST {adminPath}/connect/token** + +This gets a token for use with the admin area using the client credentials we talk about setting above. Under the hood this calls into an IdentityServer hosted within Ocelot. + +The body of the request is form-data as follows + +``client_id`` set as admin + +``client_secret`` set as whatever you used when setting up the administration services. + +``scope`` set as admin + +``grant_type`` set as client_credentials + +**GET {adminPath}/configuration** + + +This gets the current Ocelot configuration. It is exactly the same JSON we use to set Ocelot up with in the first place. + +**POST {adminPath}/configuration** + + +This overrwrites the existing configuration (should probably be a put!). I reccomend getting your config from the GET endpoint, making any changes and posting it back...simples. + +The body of the request is JSON and it is the same format as the FileConfiguration.cs that we use to set up +Ocelot on a file system. + +**DELETE {adminPath}/outputcache/{region}** + +This clears a region of the cache. If you are using a backplane it will clear all instances of the cache! Giving your the ability to run a cluster of Ocelots and cache over all of them in memory and clear them all at the same time / just use a distributed cache. + +The region is whatever you set against the Region field in the FileCacheOptions section of the Ocelot configuration. diff --git a/docs/features/authentication.rst b/docs/features/authentication.rst index d745206e8..ff349bdd9 100644 --- a/docs/features/authentication.rst +++ b/docs/features/authentication.rst @@ -1,125 +1,137 @@ -Authentication -============== - -In order to authenticate ReRoutes and subsequently use any of Ocelot's claims based features such as authorisation or modifying the request with values from the token. Users must register authentication services in their Startup.cs as usual but they provide a scheme (authentication provider key) with each registration e.g. - -.. code-block:: csharp - - public void ConfigureServices(IServiceCollection services) - { - var authenticationProviderKey = "TestKey"; - - services.AddAuthentication() - .AddJwtBearer(authenticationProviderKey, x => - { - }); - } - - -In this example TestKey is the scheme that this provider has been registered with. -We then map this to a ReRoute in the configuration e.g. - -.. code-block:: json - - "ReRoutes": [{ - "DownstreamPathTemplate": "/", - "UpstreamPathTemplate": "/", - "UpstreamHttpMethod": ["Post"], - "ReRouteIsCaseSensitive": false, - "DownstreamScheme": "http", - "DownstreamHost": "localhost", - "DownstreamPort": 51876, - "AuthenticationOptions": { - "AuthenticationProviderKey": "TestKey", - "AllowedScopes": [] - } - }] - -When Ocelot runs it will look at this ReRoutes AuthenticationOptions.AuthenticationProviderKey -and check that there is an Authentication provider registered with the given key. If there isn't then Ocelot -will not start up, if there is then the ReRoute will use that provider when it executes. - -If a ReRoute is authenticated Ocelot will invoke whatever scheme is associated with it while executing the authentication middleware. If the request fails authentication Ocelot returns a http status code 401. - -JWT Tokens -^^^^^^^^^^ - -If you want to authenticate using JWT tokens maybe from a provider like Auth0 you can register your authentication middleware as normal e.g. - -.. code-block:: csharp - - public void ConfigureServices(IServiceCollection services) - { - var authenticationProviderKey = "TestKey"; - - services.AddAuthentication() - .AddJwtBearer(authenticationProviderKey, x => - { - x.Authority = "test"; - x.Audience = "test"; - }); - - services.AddOcelot(Configuration); - } - -Then map the authentication provider key to a ReRoute in your configuration e.g. - -.. code-block:: json - - "ReRoutes": [{ - "DownstreamPathTemplate": "/", - "UpstreamPathTemplate": "/", - "UpstreamHttpMethod": ["Post"], - "ReRouteIsCaseSensitive": false, - "DownstreamScheme": "http", - "DownstreamHost": "localhost", - "DownstreamPort": 51876, - "AuthenticationOptions": { - "AuthenticationProviderKey": "TestKey", - "AllowedScopes": [] - } - }] - - - -Identity Server Bearer Tokens -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -In order to use IdentityServer bearer tokens register your IdentityServer services as usual in ConfigureServices with a scheme (key). If you don't understand how to do this please consul the IdentityServer documentation. - -.. code-block:: csharp - - public void ConfigureServices(IServiceCollection services) - { - var authenticationProviderKey = "TestKey"; - var options = o => - { - o.Authority = "https://whereyouridentityserverlives.com"; - o.ApiName = "api"; - o.SupportedTokens = SupportedTokens.Both; - o.ApiSecret = "secret"; - }; - - services.AddAuthentication() - .AddIdentityServerAuthentication(authenticationProviderKey, options); - - services.AddOcelot(Configuration); - } - -Then map the authentication provider key to a ReRoute in your configuration e.g. - -.. code-block:: json - - "ReRoutes": [{ - "DownstreamPathTemplate": "/", - "UpstreamPathTemplate": "/", - "UpstreamHttpMethod": ["Post"], - "ReRouteIsCaseSensitive": false, - "DownstreamScheme": "http", - "DownstreamHost": "localhost", - "DownstreamPort": 51876, - "AuthenticationOptions": { - "AuthenticationProviderKey": "TestKey", - "AllowedScopes": [] - } - }] +Authentication +============== + +In order to authenticate ReRoutes and subsequently use any of Ocelot's claims based features such as authorisation or modifying the request with values from the token. Users must register authentication services in their Startup.cs as usual but they provide a scheme (authentication provider key) with each registration e.g. + +.. code-block:: csharp + + public void ConfigureServices(IServiceCollection services) + { + var authenticationProviderKey = "TestKey"; + + services.AddAuthentication() + .AddJwtBearer(authenticationProviderKey, x => + { + }); + } + + +In this example TestKey is the scheme that this provider has been registered with. +We then map this to a ReRoute in the configuration e.g. + +.. code-block:: json + + "ReRoutes": [{ + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 51876, + } + ], + "DownstreamPathTemplate": "/", + "UpstreamPathTemplate": "/", + "UpstreamHttpMethod": ["Post"], + "ReRouteIsCaseSensitive": false, + "DownstreamScheme": "http", + "AuthenticationOptions": { + "AuthenticationProviderKey": "TestKey", + "AllowedScopes": [] + } + }] + +When Ocelot runs it will look at this ReRoutes AuthenticationOptions.AuthenticationProviderKey +and check that there is an Authentication provider registered with the given key. If there isn't then Ocelot +will not start up, if there is then the ReRoute will use that provider when it executes. + +If a ReRoute is authenticated Ocelot will invoke whatever scheme is associated with it while executing the authentication middleware. If the request fails authentication Ocelot returns a http status code 401. + +JWT Tokens +^^^^^^^^^^ + +If you want to authenticate using JWT tokens maybe from a provider like Auth0 you can register your authentication middleware as normal e.g. + +.. code-block:: csharp + + public void ConfigureServices(IServiceCollection services) + { + var authenticationProviderKey = "TestKey"; + + services.AddAuthentication() + .AddJwtBearer(authenticationProviderKey, x => + { + x.Authority = "test"; + x.Audience = "test"; + }); + + services.AddOcelot(Configuration); + } + +Then map the authentication provider key to a ReRoute in your configuration e.g. + +.. code-block:: json + + "ReRoutes": [{ + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 51876, + } + ], + "DownstreamPathTemplate": "/", + "UpstreamPathTemplate": "/", + "UpstreamHttpMethod": ["Post"], + "ReRouteIsCaseSensitive": false, + "DownstreamScheme": "http", + "AuthenticationOptions": { + "AuthenticationProviderKey": "TestKey", + "AllowedScopes": [] + } + }] + + + +Identity Server Bearer Tokens +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In order to use IdentityServer bearer tokens register your IdentityServer services as usual in ConfigureServices with a scheme (key). If you don't understand how to do this please consul the IdentityServer documentation. + +.. code-block:: csharp + + public void ConfigureServices(IServiceCollection services) + { + var authenticationProviderKey = "TestKey"; + var options = o => + { + o.Authority = "https://whereyouridentityserverlives.com"; + o.ApiName = "api"; + o.SupportedTokens = SupportedTokens.Both; + o.ApiSecret = "secret"; + }; + + services.AddAuthentication() + .AddIdentityServerAuthentication(authenticationProviderKey, options); + + services.AddOcelot(Configuration); + } + +Then map the authentication provider key to a ReRoute in your configuration e.g. + +.. code-block:: json + + "ReRoutes": [{ + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 51876, + } + ], + "DownstreamPathTemplate": "/", + "UpstreamPathTemplate": "/", + "UpstreamHttpMethod": ["Post"], + "ReRouteIsCaseSensitive": false, + "DownstreamScheme": "http", + "AuthenticationOptions": { + "AuthenticationProviderKey": "TestKey", + "AllowedScopes": [] + } + }] diff --git a/docs/features/authorisation.rst b/docs/features/authorisation.rst index 9a4aecbf3..b7a4ec1ee 100644 --- a/docs/features/authorisation.rst +++ b/docs/features/authorisation.rst @@ -1,18 +1,18 @@ -Authorisation -============= - -Ocelot supports claims based authorisation which is run post authentication. This means if -you have a route you want to authorise you can add the following to you ReRoute configuration. - -.. code-block:: json - - "RouteClaimsRequirement": { - "UserType": "registered" - } - -In this example when the authorisation middleware is called Ocelot will check to see -if the user has the claim type UserType and if the value of that claim is registered. -If it isn't then the user will not be authorised and the response will be 403 forbidden. - - - +Authorisation +============= + +Ocelot supports claims based authorisation which is run post authentication. This means if +you have a route you want to authorise you can add the following to you ReRoute configuration. + +.. code-block:: json + + "RouteClaimsRequirement": { + "UserType": "registered" + } + +In this example when the authorisation middleware is called Ocelot will check to see +if the user has the claim type UserType and if the value of that claim is registered. +If it isn't then the user will not be authorised and the response will be 403 forbidden. + + + diff --git a/docs/features/caching.rst b/docs/features/caching.rst index 8180f9641..8e70bc3a5 100644 --- a/docs/features/caching.rst +++ b/docs/features/caching.rst @@ -1,21 +1,21 @@ -Caching -======= - -Ocelot supports some very rudimentary caching at the moment provider by -the `CacheManager `_ project. This is an amazing project -that is solving a lot of caching problems. I would reccomend using this package to -cache with Ocelot. If you look at the example `here `_ you can see how the cache manager is setup and then passed into the Ocelot -AddOcelotOutputCaching configuration method. You can use any settings supported by -the CacheManager package and just pass them in. - -Anyway Ocelot currently supports caching on the URL of the downstream service -and setting a TTL in seconds to expire the cache. You can also clear the cache for a region -by calling Ocelot's administration API. - -In order to use caching on a route in your ReRoute configuration add this setting. - -.. code-block:: json - - "FileCacheOptions": { "TtlSeconds": 15, "Region": "somename" } - -In this example ttl seconds is set to 15 which means the cache will expire after 15 seconds. +Caching +======= + +Ocelot supports some very rudimentary caching at the moment provider by +the `CacheManager `_ project. This is an amazing project +that is solving a lot of caching problems. I would reccomend using this package to +cache with Ocelot. If you look at the example `here `_ you can see how the cache manager is setup and then passed into the Ocelot +AddOcelotOutputCaching configuration method. You can use any settings supported by +the CacheManager package and just pass them in. + +Anyway Ocelot currently supports caching on the URL of the downstream service +and setting a TTL in seconds to expire the cache. You can also clear the cache for a region +by calling Ocelot's administration API. + +In order to use caching on a route in your ReRoute configuration add this setting. + +.. code-block:: json + + "FileCacheOptions": { "TtlSeconds": 15, "Region": "somename" } + +In this example ttl seconds is set to 15 which means the cache will expire after 15 seconds. diff --git a/docs/features/claimstransformation.rst b/docs/features/claimstransformation.rst index 764012e00..e17e1aa70 100644 --- a/docs/features/claimstransformation.rst +++ b/docs/features/claimstransformation.rst @@ -1,72 +1,72 @@ -Claims Transformation -===================== - -Ocelot allows the user to access claims and transform them into headers, query string -parameters and other claims. This is only available once a user has been authenticated. - -After the user is authenticated we run the claims to claims transformation middleware. -This allows the user to transform claims before the authorisation middleware is called. -After the user is authorised first we call the claims to headers middleware and Finally -the claims to query strig parameters middleware. - -The syntax for performing the transforms is the same for each proces. In the ReRoute -configuration a json dictionary is added with a specific name either AddClaimsToRequest, -AddHeadersToRequest, AddQueriesToRequest. - -Note I'm not a hotshot programmer so have no idea if this syntax is good.. - -Within this dictionary the entries specify how Ocelot should transform things! -The key to the dictionary is going to become the key of either a claim, header -or query parameter. - -The value of the entry is parsed to logic that will perform the transform. First of -all a dictionary accessor is specified e.g. Claims[CustomerId]. This means we want -to access the claims and get the CustomerId claim type. Next is a greater than (>) -symbol which is just used to split the string. The next entry is either value or value with -and indexer. If value is specifed Ocelot will just take the value and add it to the -transform. If the value has an indexer Ocelot will look for a delimiter which is provided -after another greater than symbol. Ocelot will then split the value on the delimiter -and add whatever was at the index requested to the transform. - -Claims to Claims Tranformation -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Below is an example configuration that will transforms claims to claims - -.. code-block:: json - - "AddClaimsToRequest": { - "UserType": "Claims[sub] > value[0] > |", - "UserId": "Claims[sub] > value[1] > |" - } - -This shows a transforms where Ocelot looks at the users sub claim and transforms it into -UserType and UserId claims. Assuming the sub looks like this "usertypevalue|useridvalue". - -Claims to Headers Tranformation -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Below is an example configuration that will transforms claims to headers - -.. code-block:: json - - "AddHeadersToRequest": { - "CustomerId": "Claims[sub] > value[1] > |" - } - -This shows a transform where Ocelot looks at the users sub claim and trasnforms it into a -CustomerId header. Assuming the sub looks like this "usertypevalue|useridvalue". - -Claims to Query String Parameters Tranformation -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Below is an example configuration that will transforms claims to query string parameters - -.. code-block:: json - - "AddQueriesToRequest": { - "LocationId": "Claims[LocationId] > value", - } - -This shows a transform where Ocelot looks at the users LocationId claim and add its as +Claims Transformation +===================== + +Ocelot allows the user to access claims and transform them into headers, query string +parameters and other claims. This is only available once a user has been authenticated. + +After the user is authenticated we run the claims to claims transformation middleware. +This allows the user to transform claims before the authorisation middleware is called. +After the user is authorised first we call the claims to headers middleware and Finally +the claims to query strig parameters middleware. + +The syntax for performing the transforms is the same for each proces. In the ReRoute +configuration a json dictionary is added with a specific name either AddClaimsToRequest, +AddHeadersToRequest, AddQueriesToRequest. + +Note I'm not a hotshot programmer so have no idea if this syntax is good.. + +Within this dictionary the entries specify how Ocelot should transform things! +The key to the dictionary is going to become the key of either a claim, header +or query parameter. + +The value of the entry is parsed to logic that will perform the transform. First of +all a dictionary accessor is specified e.g. Claims[CustomerId]. This means we want +to access the claims and get the CustomerId claim type. Next is a greater than (>) +symbol which is just used to split the string. The next entry is either value or value with +and indexer. If value is specifed Ocelot will just take the value and add it to the +transform. If the value has an indexer Ocelot will look for a delimiter which is provided +after another greater than symbol. Ocelot will then split the value on the delimiter +and add whatever was at the index requested to the transform. + +Claims to Claims Tranformation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Below is an example configuration that will transforms claims to claims + +.. code-block:: json + + "AddClaimsToRequest": { + "UserType": "Claims[sub] > value[0] > |", + "UserId": "Claims[sub] > value[1] > |" + } + +This shows a transforms where Ocelot looks at the users sub claim and transforms it into +UserType and UserId claims. Assuming the sub looks like this "usertypevalue|useridvalue". + +Claims to Headers Tranformation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Below is an example configuration that will transforms claims to headers + +.. code-block:: json + + "AddHeadersToRequest": { + "CustomerId": "Claims[sub] > value[1] > |" + } + +This shows a transform where Ocelot looks at the users sub claim and trasnforms it into a +CustomerId header. Assuming the sub looks like this "usertypevalue|useridvalue". + +Claims to Query String Parameters Tranformation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Below is an example configuration that will transforms claims to query string parameters + +.. code-block:: json + + "AddQueriesToRequest": { + "LocationId": "Claims[LocationId] > value", + } + +This shows a transform where Ocelot looks at the users LocationId claim and add its as a query string parameter to be forwarded onto the downstream service. \ No newline at end of file diff --git a/docs/features/configuration.rst b/docs/features/configuration.rst index 6545c8dfc..29a19af0f 100644 --- a/docs/features/configuration.rst +++ b/docs/features/configuration.rst @@ -1,102 +1,106 @@ -Configuration -============ - -An example configuration can be found `here `_. -There are two sections to the configuration. An array of ReRoutes and a GlobalConfiguration. -The ReRoutes are the objects that tell Ocelot how to treat an upstream request. The Global -configuration is a bit hacky and allows overrides of ReRoute specific settings. It's useful -if you don't want to manage lots of ReRoute specific settings. - -.. code-block:: json - - { - "ReRoutes": [], - "GlobalConfiguration": {} - } - -Follow Redirects / Use CookieContainer -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Use HttpHandlerOptions in ReRoute configuration to set up HttpHandler behavior: -- _AllowAutoRedirect_ is a value that indicates whether the request should follow redirection responses. -Set it true if the request should automatically follow redirection responses from the Downstream resource; otherwise false. The default value is true. -- _UseCookieContainer_ is a value that indicates whether the handler uses the CookieContainer property to store server cookies and uses these cookies when sending requests. -The default value is true. - -Here is an example ReRoute configuration, You don't need to set all of these things but this is everything that is available at the moment: - -.. code-block:: json - - { - "DownstreamPathTemplate": "/", - "UpstreamPathTemplate": "/", - "UpstreamHttpMethod": [ - "Get" - ], - "AddHeadersToRequest": {}, - "AddClaimsToRequest": {}, - "RouteClaimsRequirement": {}, - "AddQueriesToRequest": {}, - "RequestIdKey": "", - "FileCacheOptions": { - "TtlSeconds": 0, - "Region": "" - }, - "ReRouteIsCaseSensitive": false, - "ServiceName": "", - "DownstreamScheme": "http", - "DownstreamHost": "localhost", - "DownstreamPort": 51779, - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 0, - "DurationOfBreak": 0, - "TimeoutValue": 0 - }, - "LoadBalancer": "", - "RateLimitOptions": { - "ClientWhitelist": [], - "EnableRateLimiting": false, - "Period": "", - "PeriodTimespan": 0, - "Limit": 0 - }, - "AuthenticationOptions": { - "AuthenticationProviderKey": "", - "AllowedScopes": [] - }, - "HttpHandlerOptions": { - "AllowAutoRedirect": true, - "UseCookieContainer": true - }, - "UseServiceDiscovery": false - } - -More information on how to use these options is below.. - -Store configuration in consul -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -If you add the following when you register your services Ocelot will attempt to store and retrieve its configuration in consul KV store. - -.. code-block:: csharp - - services - .AddOcelot(Configuration) - .AddStoreOcelotConfigurationInConsul(); - -You also need to add the following to your configuration.json. This is how Ocelot -finds your Consul agent and interacts to load and store the configuration from Consul. - -.. code-block:: json - - "GlobalConfiguration": { - "ServiceDiscoveryProvider": { - "Host": "localhost", - "Port": 9500 - } - } - -I decided to create this feature after working on the raft consensus algorithm and finding out its super hard. Why not take advantage of the fact Consul already gives you this! -I guess it means if you want to use Ocelot to its fullest you take on Consul as a dependency for now. - +Configuration +============ + +An example configuration can be found `here `_. +There are two sections to the configuration. An array of ReRoutes and a GlobalConfiguration. +The ReRoutes are the objects that tell Ocelot how to treat an upstream request. The Global +configuration is a bit hacky and allows overrides of ReRoute specific settings. It's useful +if you don't want to manage lots of ReRoute specific settings. + +.. code-block:: json + + { + "ReRoutes": [], + "GlobalConfiguration": {} + } + +Here is an example ReRoute configuration, You don't need to set all of these things but this is everything that is available at the moment: + +.. code-block:: json + + { + "DownstreamPathTemplate": "/", + "UpstreamPathTemplate": "/", + "UpstreamHttpMethod": [ + "Get" + ], + "AddHeadersToRequest": {}, + "AddClaimsToRequest": {}, + "RouteClaimsRequirement": {}, + "AddQueriesToRequest": {}, + "RequestIdKey": "", + "FileCacheOptions": { + "TtlSeconds": 0, + "Region": "" + }, + "ReRouteIsCaseSensitive": false, + "ServiceName": "", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 51876, + } + ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 0, + "DurationOfBreak": 0, + "TimeoutValue": 0 + }, + "LoadBalancer": "", + "RateLimitOptions": { + "ClientWhitelist": [], + "EnableRateLimiting": false, + "Period": "", + "PeriodTimespan": 0, + "Limit": 0 + }, + "AuthenticationOptions": { + "AuthenticationProviderKey": "", + "AllowedScopes": [] + }, + "HttpHandlerOptions": { + "AllowAutoRedirect": true, + "UseCookieContainer": true + }, + "UseServiceDiscovery": false + } + +More information on how to use these options is below.. + +Follow Redirects / Use CookieContainer +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Use HttpHandlerOptions in ReRoute configuration to set up HttpHandler behavior: +- _AllowAutoRedirect_ is a value that indicates whether the request should follow redirection responses. +Set it true if the request should automatically follow redirection responses from the Downstream resource; otherwise false. The default value is true. +- _UseCookieContainer_ is a value that indicates whether the handler uses the CookieContainer property to store server cookies and uses these cookies when sending requests. +The default value is true. + +Store configuration in consul +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you add the following when you register your services Ocelot will attempt to store and retrieve its configuration in consul KV store. + +.. code-block:: csharp + + services + .AddOcelot(Configuration) + .AddStoreOcelotConfigurationInConsul(); + +You also need to add the following to your configuration.json. This is how Ocelot +finds your Consul agent and interacts to load and store the configuration from Consul. + +.. code-block:: json + + "GlobalConfiguration": { + "ServiceDiscoveryProvider": { + "Host": "localhost", + "Port": 9500 + } + } + +I decided to create this feature after working on the raft consensus algorithm and finding out its super hard. Why not take advantage of the fact Consul already gives you this! +I guess it means if you want to use Ocelot to its fullest you take on Consul as a dependency for now. + This feature has a 3 second ttl cache before making a new request to your local consul agent. \ No newline at end of file diff --git a/docs/features/headerstransformation.rst b/docs/features/headerstransformation.rst index 01436cb97..5063c1b7b 100644 --- a/docs/features/headerstransformation.rst +++ b/docs/features/headerstransformation.rst @@ -1,97 +1,97 @@ -Headers Transformation -===================== - -Ocelot allows the user to transform headers pre and post downstream request. At the moment Ocelot only supports find and replace. This feature was requested `GitHub #190 `_ and I decided that it was going to be useful in various ways. - -Syntax -^^^^^^ - -In order to transform a header first we specify the header key and then the type of transform we want e.g. - -.. code-block:: json - - "Test": "http://www.bbc.co.uk/, http://ocelot.com/" - -The key is "Test" and the value is "http://www.bbc.co.uk/, http://ocelot.com/". The value is saying replace http://www.bbc.co.uk/ with http://ocelot.com/. The syntax is {find}, {replace}. Hopefully pretty simple. There are examples below that explain more. - -Pre Downstream Request -^^^^^^^^^^^^^^^^^^^^^^ - -Add the following to a ReRoute in configuration.json in order to replace http://www.bbc.co.uk/ with http://ocelot.com/. This header will be changed before the request downstream and will be sent to the downstream server. - -.. code-block:: json - - "UpstreamHeaderTransform": { - "Test": "http://www.bbc.co.uk/, http://ocelot.com/" - }, - -Post Downstream Request -^^^^^^^^^^^^^^^^^^^^^^ - -Add the following to a ReRoute in configuration.json in order to replace http://www.bbc.co.uk/ with http://ocelot.com/. This transformation will take place after Ocelot has received the response from the downstream service. - -.. code-block:: json - - "DownstreamHeaderTransform": { - "Test": "http://www.bbc.co.uk/, http://ocelot.com/" - }, - -Placeholders -^^^^^^^^^^^^ - -Ocelot allows placeholders that can be used in header transformation. - -{BaseUrl} - This will use Ocelot's base url e.g. http://localhost:5000 as its value. -{DownstreamBaseUrl} - This will use the downstream services base url e.g. http://localhost:5000 as its value. This only works for DownstreamHeaderTransform at the moment. - -Handling 302 Redirects -^^^^^^^^^^^^^^^^^^^^^^ -Ocelot will by default automatically follow redirects however if you want to return the location header to the client you might want to change the location to be Ocelot not the downstream service. Ocelot allows this with the following configuration. - -.. code-block:: json - - "DownstreamHeaderTransform": { - "Location": "http://www.bbc.co.uk/, http://ocelot.com/" - }, - "HttpHandlerOptions": { - "AllowAutoRedirect": false, - }, - -or you could use the BaseUrl placeholder. - -.. code-block:: json - - "DownstreamHeaderTransform": { - "Location": "http://localhost:6773, {BaseUrl}" - }, - "HttpHandlerOptions": { - "AllowAutoRedirect": false, - }, - -finally if you are using a load balancer with Ocelot you will get multiple downstream base urls so the above would not work. In this case you can do the following. - -.. code-block:: json - - "DownstreamHeaderTransform": { - "Location": "{DownstreamBaseUrl}, {BaseUrl}" - }, - "HttpHandlerOptions": { - "AllowAutoRedirect": false, - }, - -Future -^^^^^^ - -Ideally this feature would be able to support the fact that a header can have multiple values. At the moment it just assumes one. -It would also be nice if it could multi find and replace e.g. - -.. code-block:: json - - "DownstreamHeaderTransform": { - "Location": "[{one,one},{two,two}" - }, - "HttpHandlerOptions": { - "AllowAutoRedirect": false, - }, - +Headers Transformation +===================== + +Ocelot allows the user to transform headers pre and post downstream request. At the moment Ocelot only supports find and replace. This feature was requested `GitHub #190 `_ and I decided that it was going to be useful in various ways. + +Syntax +^^^^^^ + +In order to transform a header first we specify the header key and then the type of transform we want e.g. + +.. code-block:: json + + "Test": "http://www.bbc.co.uk/, http://ocelot.com/" + +The key is "Test" and the value is "http://www.bbc.co.uk/, http://ocelot.com/". The value is saying replace http://www.bbc.co.uk/ with http://ocelot.com/. The syntax is {find}, {replace}. Hopefully pretty simple. There are examples below that explain more. + +Pre Downstream Request +^^^^^^^^^^^^^^^^^^^^^^ + +Add the following to a ReRoute in configuration.json in order to replace http://www.bbc.co.uk/ with http://ocelot.com/. This header will be changed before the request downstream and will be sent to the downstream server. + +.. code-block:: json + + "UpstreamHeaderTransform": { + "Test": "http://www.bbc.co.uk/, http://ocelot.com/" + }, + +Post Downstream Request +^^^^^^^^^^^^^^^^^^^^^^ + +Add the following to a ReRoute in configuration.json in order to replace http://www.bbc.co.uk/ with http://ocelot.com/. This transformation will take place after Ocelot has received the response from the downstream service. + +.. code-block:: json + + "DownstreamHeaderTransform": { + "Test": "http://www.bbc.co.uk/, http://ocelot.com/" + }, + +Placeholders +^^^^^^^^^^^^ + +Ocelot allows placeholders that can be used in header transformation. + +{BaseUrl} - This will use Ocelot's base url e.g. http://localhost:5000 as its value. +{DownstreamBaseUrl} - This will use the downstream services base url e.g. http://localhost:5000 as its value. This only works for DownstreamHeaderTransform at the moment. + +Handling 302 Redirects +^^^^^^^^^^^^^^^^^^^^^^ +Ocelot will by default automatically follow redirects however if you want to return the location header to the client you might want to change the location to be Ocelot not the downstream service. Ocelot allows this with the following configuration. + +.. code-block:: json + + "DownstreamHeaderTransform": { + "Location": "http://www.bbc.co.uk/, http://ocelot.com/" + }, + "HttpHandlerOptions": { + "AllowAutoRedirect": false, + }, + +or you could use the BaseUrl placeholder. + +.. code-block:: json + + "DownstreamHeaderTransform": { + "Location": "http://localhost:6773, {BaseUrl}" + }, + "HttpHandlerOptions": { + "AllowAutoRedirect": false, + }, + +finally if you are using a load balancer with Ocelot you will get multiple downstream base urls so the above would not work. In this case you can do the following. + +.. code-block:: json + + "DownstreamHeaderTransform": { + "Location": "{DownstreamBaseUrl}, {BaseUrl}" + }, + "HttpHandlerOptions": { + "AllowAutoRedirect": false, + }, + +Future +^^^^^^ + +Ideally this feature would be able to support the fact that a header can have multiple values. At the moment it just assumes one. +It would also be nice if it could multi find and replace e.g. + +.. code-block:: json + + "DownstreamHeaderTransform": { + "Location": "[{one,one},{two,two}" + }, + "HttpHandlerOptions": { + "AllowAutoRedirect": false, + }, + If anyone wants to have a go at this please help yourself!! \ No newline at end of file diff --git a/docs/features/loadbalancer.rst b/docs/features/loadbalancer.rst new file mode 100644 index 000000000..f587aa22f --- /dev/null +++ b/docs/features/loadbalancer.rst @@ -0,0 +1,60 @@ +Load Balancer +============= + +Ocelot can load balance across available downstream services for each ReRoute. This means you can scale your downstream services and Ocelot can use them effectively. + +The type of load balancer available are: + + LeastConnection - tracks which services are dealing with requests and sends new requests to service with least existing requests. The algorythm state is not distributed across a cluster of Ocelot's. + + RoundRobin - loops through available services and sends requests. The algorythm state is not distributed across a cluster of Ocelot's. + + NoLoadBalancer - takes the first available service from config or service discovery. + +You must choose in your configuration which load balancer to use. + +Configuration +^^^^^^^^^^^^^ + +The following shows how to set up multiple downstream services for a ReRoute using configuration.json and then select the LeadConnection load balancer. This is the simplest way to get load balancing set up. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { + "Host": "10.0.1.10", + "Port": 5000, + }, + { + "Host": "10.0.1.11", + "Port": 5000, + } + ], + "UpstreamPathTemplate": "/posts/{postId}", + "LoadBalancer": "LeastConnection", + "UpstreamHttpMethod": [ "Put", "Delete" ] + } + + +Service Discovery +^^^^^^^^^^^^^^^^^ + +The following shows how to set up a ReRoute using service discovery then select the LeadConnection load balancer. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "UpstreamPathTemplate": "/posts/{postId}", + "UpstreamHttpMethod": [ "Put" ], + "ServiceName": "product", + "LoadBalancer": "LeastConnection", + "UseServiceDiscovery": true + } + +When this is set up Ocelot will lookup the downstream host and port from the service discover provider and load balance requests across any available services. If you add and remove services from the +service discovery provider (consul) then Ocelot should respect this and stop calling services that have been removed and start calling services that have been added. \ No newline at end of file diff --git a/docs/features/logging.rst b/docs/features/logging.rst index fba8e5dcf..313f62ff1 100644 --- a/docs/features/logging.rst +++ b/docs/features/logging.rst @@ -1,14 +1,14 @@ -Logging -======= - -Ocelot uses the standard logging interfaces ILoggerFactory / ILogger at the moment. -This is encapsulated in IOcelotLogger / IOcelotLoggerFactory with an implementation -for the standard asp.net core logging stuff at the moment. This is because Ocelot add's some extra info to the logs such as request id if it is configured. - -There is a global error handler that should catch any exceptions thrown and log them as errors. - -Finally if logging is set to trace level Ocelot will log starting, finishing and any middlewares that throw an exception which can be quite useful. - -The reason for not just using bog standard framework logging is that I could not -work out how to override the request id that get's logged when setting IncludeScopes +Logging +======= + +Ocelot uses the standard logging interfaces ILoggerFactory / ILogger at the moment. +This is encapsulated in IOcelotLogger / IOcelotLoggerFactory with an implementation +for the standard asp.net core logging stuff at the moment. This is because Ocelot add's some extra info to the logs such as request id if it is configured. + +There is a global error handler that should catch any exceptions thrown and log them as errors. + +Finally if logging is set to trace level Ocelot will log starting, finishing and any middlewares that throw an exception which can be quite useful. + +The reason for not just using bog standard framework logging is that I could not +work out how to override the request id that get's logged when setting IncludeScopes to true for logging settings. Nicely onto the next feature. \ No newline at end of file diff --git a/docs/features/middlewareinjection.rst b/docs/features/middlewareinjection.rst index aedf54652..f25cfa266 100644 --- a/docs/features/middlewareinjection.rst +++ b/docs/features/middlewareinjection.rst @@ -1,41 +1,41 @@ -Middleware Injection and Overrides -================================== - -Warning use with caution. If you are seeing any exceptions or strange behavior in your middleware -pipeline and you are using any of the following. Remove them and try again! - -When setting up Ocelot in your Startup.cs you can provide some additonal middleware -and override middleware. This is done as follos. - -.. code-block:: csharp - - var configuration = new OcelotMiddlewareConfiguration - { - PreErrorResponderMiddleware = async (ctx, next) => - { - await next.Invoke(); - } - }; - - app.UseOcelot(configuration); - -In the example above the provided function will run before the first piece of Ocelot middleware. -This allows a user to supply any behaviours they want before and after the Ocelot pipeline has run. -This means you can break everything so use at your own pleasure! - -The user can set functions against the following. - -* PreErrorResponderMiddleware - Already explained above. - -* PreAuthenticationMiddleware - This allows the user to run pre authentication logic and then call Ocelot's authentication middleware. - -* AuthenticationMiddleware - This overrides Ocelots authentication middleware. - -* PreAuthorisationMiddleware - This allows the user to run pre authorisation logic and then call Ocelot's authorisation middleware. - -* AuthorisationMiddleware - This overrides Ocelots authorisation middleware. - -* PreQueryStringBuilderMiddleware - This alows the user to manipulate the query string on the http request before it is passed to Ocelots request creator. - -Obviously you can just add middleware as normal before the call to app.UseOcelot() It cannot be added +Middleware Injection and Overrides +================================== + +Warning use with caution. If you are seeing any exceptions or strange behavior in your middleware +pipeline and you are using any of the following. Remove them and try again! + +When setting up Ocelot in your Startup.cs you can provide some additonal middleware +and override middleware. This is done as follos. + +.. code-block:: csharp + + var configuration = new OcelotMiddlewareConfiguration + { + PreErrorResponderMiddleware = async (ctx, next) => + { + await next.Invoke(); + } + }; + + app.UseOcelot(configuration); + +In the example above the provided function will run before the first piece of Ocelot middleware. +This allows a user to supply any behaviours they want before and after the Ocelot pipeline has run. +This means you can break everything so use at your own pleasure! + +The user can set functions against the following. + +* PreErrorResponderMiddleware - Already explained above. + +* PreAuthenticationMiddleware - This allows the user to run pre authentication logic and then call Ocelot's authentication middleware. + +* AuthenticationMiddleware - This overrides Ocelots authentication middleware. + +* PreAuthorisationMiddleware - This allows the user to run pre authorisation logic and then call Ocelot's authorisation middleware. + +* AuthorisationMiddleware - This overrides Ocelots authorisation middleware. + +* PreQueryStringBuilderMiddleware - This alows the user to manipulate the query string on the http request before it is passed to Ocelots request creator. + +Obviously you can just add middleware as normal before the call to app.UseOcelot() It cannot be added after as Ocelot does not call the next middleware. \ No newline at end of file diff --git a/docs/features/qualityofservice.rst b/docs/features/qualityofservice.rst index 48db5009a..9eebf9230 100644 --- a/docs/features/qualityofservice.rst +++ b/docs/features/qualityofservice.rst @@ -1,22 +1,22 @@ -Quality of Service -================== - -Ocelot supports one QoS capability at the current time. You can set on a per ReRoute basis if you -want to use a circuit breaker when making requests to a downstream service. This uses the an awesome -.NET library called Polly check them out `here `_. - -Add the following section to a ReRoute configuration. - -.. code-block:: json - - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking":3, - "DurationOfBreak":5, - "TimeoutValue":5000 - } - -You must set a number greater than 0 against ExceptionsAllowedBeforeBreaking for this rule to be -implemented. Duration of break is how long the circuit breaker will stay open for after it is tripped. -TimeoutValue means ff a request takes more than 5 seconds it will automatically be timed out. - +Quality of Service +================== + +Ocelot supports one QoS capability at the current time. You can set on a per ReRoute basis if you +want to use a circuit breaker when making requests to a downstream service. This uses the an awesome +.NET library called Polly check them out `here `_. + +Add the following section to a ReRoute configuration. + +.. code-block:: json + + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking":3, + "DurationOfBreak":5, + "TimeoutValue":5000 + } + +You must set a number greater than 0 against ExceptionsAllowedBeforeBreaking for this rule to be +implemented. Duration of break is how long the circuit breaker will stay open for after it is tripped. +TimeoutValue means ff a request takes more than 5 seconds it will automatically be timed out. + If you do not add a QoS section QoS will not be used. \ No newline at end of file diff --git a/docs/features/raft.rst b/docs/features/raft.rst index a61e2ed18..45ea59f2d 100644 --- a/docs/features/raft.rst +++ b/docs/features/raft.rst @@ -1,45 +1,45 @@ -Raft (EXPERIMENTAL DO NOT USE IN PRODUCTION) -============================================ - -Ocelot has recenely integrated `Rafty `_ which is an implementation of Raft that I have also been working on over the last year. This project is very experimental so please do not use this feature of Ocelot in production until I think it's OK. - -Raft is a distributed concensus algorythm that allows a cluster of servers (Ocelots) to maintain local state without having a centralised database for storing state (e.g. SQL Server). - -In order to enable Rafty in Ocelot you must make the following changes to your Startup.cs. - -.. code-block:: csharp - - public virtual void ConfigureServices(IServiceCollection services) - { - services - .AddOcelot(Configuration) - .AddAdministration("/administration", "secret") - .AddRafty(); - } - -In addition to this you must add a file called peers.json to your main project and it will look as follows - -.. code-block:: json - - { - "Peers": [{ - "HostAndPort": "http://localhost:5000" - }, - { - "HostAndPort": "http://localhost:5002" - }, - { - "HostAndPort": "http://localhost:5003" - }, - { - "HostAndPort": "http://localhost:5004" - }, - { - "HostAndPort": "http://localhost:5001" - } - ] - } - -Each instance of Ocelot must have it's address in the array so that they can communicate using Rafty. - -Once you have made these configuration changes you must deploy and start each instance of Ocelot using the addresses in the peers.json file. The servers should then start communicating with each other! You can test if everything is working by posting a configuration update and checking it has replicated to all servers by getting there configuration. +Raft (EXPERIMENTAL DO NOT USE IN PRODUCTION) +============================================ + +Ocelot has recenely integrated `Rafty `_ which is an implementation of Raft that I have also been working on over the last year. This project is very experimental so please do not use this feature of Ocelot in production until I think it's OK. + +Raft is a distributed concensus algorythm that allows a cluster of servers (Ocelots) to maintain local state without having a centralised database for storing state (e.g. SQL Server). + +In order to enable Rafty in Ocelot you must make the following changes to your Startup.cs. + +.. code-block:: csharp + + public virtual void ConfigureServices(IServiceCollection services) + { + services + .AddOcelot(Configuration) + .AddAdministration("/administration", "secret") + .AddRafty(); + } + +In addition to this you must add a file called peers.json to your main project and it will look as follows + +.. code-block:: json + + { + "Peers": [{ + "HostAndPort": "http://localhost:5000" + }, + { + "HostAndPort": "http://localhost:5002" + }, + { + "HostAndPort": "http://localhost:5003" + }, + { + "HostAndPort": "http://localhost:5004" + }, + { + "HostAndPort": "http://localhost:5001" + } + ] + } + +Each instance of Ocelot must have it's address in the array so that they can communicate using Rafty. + +Once you have made these configuration changes you must deploy and start each instance of Ocelot using the addresses in the peers.json file. The servers should then start communicating with each other! You can test if everything is working by posting a configuration update and checking it has replicated to all servers by getting there configuration. diff --git a/docs/features/requestid.rst b/docs/features/requestid.rst index a9fe4c415..6e4f239b5 100644 --- a/docs/features/requestid.rst +++ b/docs/features/requestid.rst @@ -1,60 +1,60 @@ -Request Id / Correlation Id -=========================== - -Ocelot supports a client sending a request id in the form of a header. If set Ocelot will -use the requestid for logging as soon as it becomes available in the middleware pipeline. -Ocelot will also forward the request id with the specified header to the downstream service. - -You can still get the asp.net core request id in the logs if you set -IncludeScopes true in your logging config. - -In order to use the reques tid feature you have two options. - -*Global* - -In your configuration.json set the following in the GlobalConfiguration section. This will be used for all requests into Ocelot. - -.. code-block:: json - - "GlobalConfiguration": { - "RequestIdKey": "OcRequestId" - } - -I reccomend using the GlobalConfiguration unless you really need it to be ReRoute specific. - -*ReRoute* - -If you want to override this for a specific ReRoute add the following to configuration.json for the specific ReRoute. - -.. code-block:: json - - "RequestIdKey": "OcRequestId" - -Once Ocelot has identified the incoming requests matching ReRoute object it will set the request id based on the ReRoute configuration. - -This can lead to a small gotcha. If you set a GlobalConfiguration it is possible to get one request id until the ReRoute is identified and then another after that because the request id key can change. This is by design and is the best solution I can think of at the moment. In this case the OcelotLogger will show the request id and previous request id in the logs. - -Below is an example of the logging when set at Debug level for a normal request.. - -.. code-block:: bash - - dbug: Ocelot.Errors.Middleware.ExceptionHandlerMiddleware[0] - requestId: asdf, previousRequestId: no previous request id, message: ocelot pipeline started, - dbug: Ocelot.DownstreamRouteFinder.Middleware.DownstreamRouteFinderMiddleware[0] - requestId: asdf, previousRequestId: no previous request id, message: upstream url path is {upstreamUrlPath}, - dbug: Ocelot.DownstreamRouteFinder.Middleware.DownstreamRouteFinderMiddleware[0] - requestId: asdf, previousRequestId: no previous request id, message: downstream template is {downstreamRoute.Data.ReRoute.DownstreamPath}, - dbug: Ocelot.RateLimit.Middleware.ClientRateLimitMiddleware[0] - requestId: asdf, previousRequestId: no previous request id, message: EndpointRateLimiting is not enabled for Ocelot.Values.PathTemplate, - dbug: Ocelot.Authorisation.Middleware.AuthorisationMiddleware[0] - requestId: 1234, previousRequestId: asdf, message: /posts/{postId} route does not require user to be authorised, - dbug: Ocelot.DownstreamUrlCreator.Middleware.DownstreamUrlCreatorMiddleware[0] - requestId: 1234, previousRequestId: asdf, message: downstream url is {downstreamUrl.Data.Value}, - dbug: Ocelot.Request.Middleware.HttpRequestBuilderMiddleware[0] - requestId: 1234, previousRequestId: asdf, message: setting upstream request, - dbug: Ocelot.Requester.Middleware.HttpRequesterMiddleware[0] - requestId: 1234, previousRequestId: asdf, message: setting http response message, - dbug: Ocelot.Responder.Middleware.ResponderMiddleware[0] - requestId: 1234, previousRequestId: asdf, message: no pipeline errors, setting and returning completed response, - dbug: Ocelot.Errors.Middleware.ExceptionHandlerMiddleware[0] - requestId: 1234, previousRequestId: asdf, message: ocelot pipeline finished, +Request Id / Correlation Id +=========================== + +Ocelot supports a client sending a request id in the form of a header. If set Ocelot will +use the requestid for logging as soon as it becomes available in the middleware pipeline. +Ocelot will also forward the request id with the specified header to the downstream service. + +You can still get the asp.net core request id in the logs if you set +IncludeScopes true in your logging config. + +In order to use the reques tid feature you have two options. + +*Global* + +In your configuration.json set the following in the GlobalConfiguration section. This will be used for all requests into Ocelot. + +.. code-block:: json + + "GlobalConfiguration": { + "RequestIdKey": "OcRequestId" + } + +I reccomend using the GlobalConfiguration unless you really need it to be ReRoute specific. + +*ReRoute* + +If you want to override this for a specific ReRoute add the following to configuration.json for the specific ReRoute. + +.. code-block:: json + + "RequestIdKey": "OcRequestId" + +Once Ocelot has identified the incoming requests matching ReRoute object it will set the request id based on the ReRoute configuration. + +This can lead to a small gotcha. If you set a GlobalConfiguration it is possible to get one request id until the ReRoute is identified and then another after that because the request id key can change. This is by design and is the best solution I can think of at the moment. In this case the OcelotLogger will show the request id and previous request id in the logs. + +Below is an example of the logging when set at Debug level for a normal request.. + +.. code-block:: bash + + dbug: Ocelot.Errors.Middleware.ExceptionHandlerMiddleware[0] + requestId: asdf, previousRequestId: no previous request id, message: ocelot pipeline started, + dbug: Ocelot.DownstreamRouteFinder.Middleware.DownstreamRouteFinderMiddleware[0] + requestId: asdf, previousRequestId: no previous request id, message: upstream url path is {upstreamUrlPath}, + dbug: Ocelot.DownstreamRouteFinder.Middleware.DownstreamRouteFinderMiddleware[0] + requestId: asdf, previousRequestId: no previous request id, message: downstream template is {downstreamRoute.Data.ReRoute.DownstreamPath}, + dbug: Ocelot.RateLimit.Middleware.ClientRateLimitMiddleware[0] + requestId: asdf, previousRequestId: no previous request id, message: EndpointRateLimiting is not enabled for Ocelot.Values.PathTemplate, + dbug: Ocelot.Authorisation.Middleware.AuthorisationMiddleware[0] + requestId: 1234, previousRequestId: asdf, message: /posts/{postId} route does not require user to be authorised, + dbug: Ocelot.DownstreamUrlCreator.Middleware.DownstreamUrlCreatorMiddleware[0] + requestId: 1234, previousRequestId: asdf, message: downstream url is {downstreamUrl.Data.Value}, + dbug: Ocelot.Request.Middleware.HttpRequestBuilderMiddleware[0] + requestId: 1234, previousRequestId: asdf, message: setting upstream request, + dbug: Ocelot.Requester.Middleware.HttpRequesterMiddleware[0] + requestId: 1234, previousRequestId: asdf, message: setting http response message, + dbug: Ocelot.Responder.Middleware.ResponderMiddleware[0] + requestId: 1234, previousRequestId: asdf, message: no pipeline errors, setting and returning completed response, + dbug: Ocelot.Errors.Middleware.ExceptionHandlerMiddleware[0] + requestId: 1234, previousRequestId: asdf, message: ocelot pipeline finished, diff --git a/docs/features/routing.rst b/docs/features/routing.rst index 4878b9044..d6dd1926c 100644 --- a/docs/features/routing.rst +++ b/docs/features/routing.rst @@ -1,94 +1,112 @@ -Routing -======= - -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 -this could be any transport mechanism.). - -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. - -.. code-block:: json - - { - "ReRoutes": [ - ] - } - -In order to set up a ReRoute you need to add one to the json array called ReRoutes like -the following. - -.. code-block:: json - - { - "DownstreamPathTemplate": "/api/posts/{postId}", - "DownstreamScheme": "https", - "DownstreamPort": 80, - "DownstreamHost":"localhost", - "UpstreamPathTemplate": "/posts/{postId}", - "UpstreamHttpMethod": [ "Put", "Delete" ] - } - -The DownstreamPathTemplate, Scheme, Port and Host make the URL that this request will be forwarded to. -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 :) -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. - -You can also do a catch all type of ReRoute e.g. - -.. code-block:: json - - { - "DownstreamPathTemplate": "/api/{everything}", - "DownstreamScheme": "https", - "DownstreamPort": 80, - "DownstreamHost":"localhost", - "UpstreamPathTemplate": "/{everything}", - "UpstreamHttpMethod": [ "Get", "Post" ] - } - -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. -In order to change this you can specify on a per ReRoute basis the following setting. - -.. code-block:: json - - "ReRouteIsCaseSensitive": true - -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 -the ReRoute to be case sensitive is my advice! - -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). - -.. code-block:: json - - { - "DownstreamPathTemplate": "/{url}", - "DownstreamScheme": "https", - "DownstreamPort": 80, - "DownstreamHost":"localhost", - "UpstreamPathTemplate": "/{url}", - "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. - -.. code-block:: json - - { - "DownstreamPathTemplate": "/", - "DownstreamScheme": "https", - "DownstreamPort": 80, - "DownstreamHost":"10.0.10.1", - "UpstreamPathTemplate": "/", - "UpstreamHttpMethod": [ "Get" ] - } +Routing +======= + +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 +this could be any transport mechanism.). + +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. + +.. code-block:: json + + { + "ReRoutes": [ + ] + } + +In order to set up a ReRoute you need to add one to the json array called ReRoutes like +the following. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 80, + } + ], + "UpstreamPathTemplate": "/posts/{postId}", + "UpstreamHttpMethod": [ "Put", "Delete" ] + } + +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 +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 +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}. +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. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/api/{everything}", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 80, + } + ], + "UpstreamPathTemplate": "/{everything}", + "UpstreamHttpMethod": [ "Get", "Post" ] + } + +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. +In order to change this you can specify on a per ReRoute basis the following setting. + +.. code-block:: json + + "ReRouteIsCaseSensitive": true + +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 +the ReRoute to be case sensitive is my advice! + +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). + +.. code-block:: json + + { + "DownstreamPathTemplate": "/{url}", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 80, + } + ], + "UpstreamPathTemplate": "/{url}", + "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. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { + "Host": "10.0.10.1", + "Port": 80, + } + ], + "UpstreamPathTemplate": "/", + "UpstreamHttpMethod": [ "Get" ] + } diff --git a/docs/features/servicediscovery.rst b/docs/features/servicediscovery.rst index f0e67766e..28728db3e 100644 --- a/docs/features/servicediscovery.rst +++ b/docs/features/servicediscovery.rst @@ -1,38 +1,38 @@ -Service Discovery -================= - -Ocelot allows you to specify a service discovery provider and will use this to find the host and port -for the downstream service Ocelot is forwarding a request to. At the moment this is only supported in the -GlobalConfiguration section which means the same service discovery provider will be used for all ReRoutes -you specify a ServiceName for at ReRoute level. - -At the moment the only supported service discovery provider is Consul. The following is required in the -GlobalConfiguration. The Provider is required and if you do not specify a host and port the Consul default -will be used. - -.. code-block:: json - - "ServiceDiscoveryProvider": { - "Host": "localhost", - "Port": 9500 - } - -In the future we can add a feature that allows ReRoute specfic configuration. - -In order to tell Ocelot a ReRoute is to use the service discovery provider for its host and port you must add the -ServiceName, UseServiceDiscovery and load balancer you wish to use when making requests downstream. At the moment Ocelot has a RoundRobin -and LeastConnection algorithm you can use. If no load balancer is specified Ocelot will not load balance requests. - -.. code-block:: json - - { - "DownstreamPathTemplate": "/api/posts/{postId}", - "DownstreamScheme": "https", - "UpstreamPathTemplate": "/posts/{postId}", - "UpstreamHttpMethod": [ "Put" ], - "ServiceName": "product", - "LoadBalancer": "LeastConnection", - "UseServiceDiscovery": false - } - +Service Discovery +================= + +Ocelot allows you to specify a service discovery provider and will use this to find the host and port +for the downstream service Ocelot is forwarding a request to. At the moment this is only supported in the +GlobalConfiguration section which means the same service discovery provider will be used for all ReRoutes +you specify a ServiceName for at ReRoute level. + +At the moment the only supported service discovery provider is Consul. The following is required in the +GlobalConfiguration. The Provider is required and if you do not specify a host and port the Consul default +will be used. + +.. code-block:: json + + "ServiceDiscoveryProvider": { + "Host": "localhost", + "Port": 9500 + } + +In the future we can add a feature that allows ReRoute specfic configuration. + +In order to tell Ocelot a ReRoute is to use the service discovery provider for its host and port you must add the +ServiceName, UseServiceDiscovery and load balancer you wish to use when making requests downstream. At the moment Ocelot has a RoundRobin +and LeastConnection algorithm you can use. If no load balancer is specified Ocelot will not load balance requests. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "UpstreamPathTemplate": "/posts/{postId}", + "UpstreamHttpMethod": [ "Put" ], + "ServiceName": "product", + "LoadBalancer": "LeastConnection", + "UseServiceDiscovery": true + } + When this is set up Ocelot will lookup the downstream host and port from the service discover provider and load balance requests across any available services. \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index dbd7c93d6..c2bc7041a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,47 +1,48 @@ -Welcome to Ocelot -================= - -Thanks for taking a look at the Ocelot documentation. Please use the left hand nav to get around. I would suggest taking a look at introduction first. - -.. toctree:: - :maxdepth: 2 - :hidden: - :caption: Introduction - - introduction/bigpicture - introduction/gettingstarted - introduction/contributing - introduction/notsupported - -.. toctree:: - :maxdepth: 2 - :hidden: - :caption: Features - - features/routing - features/configuration - features/servicediscovery - features/authentication - features/authorisation - features/administration - features/raft - features/caching - features/qualityofservice - features/headerstransformation - features/claimstransformation - features/logging - features/requestid - features/middlewareinjection - -.. toctree:: - :maxdepth: 2 - :hidden: - :caption: Building Ocelot - - building/overview - building/building - building/tests - building/releaseprocess - - - +Welcome to Ocelot +================= + +Thanks for taking a look at the Ocelot documentation. Please use the left hand nav to get around. I would suggest taking a look at introduction first. + +.. toctree:: + :maxdepth: 2 + :hidden: + :caption: Introduction + + introduction/bigpicture + introduction/gettingstarted + introduction/contributing + introduction/notsupported + +.. toctree:: + :maxdepth: 2 + :hidden: + :caption: Features + + features/routing + features/configuration + features/servicediscovery + features/authentication + features/authorisation + features/administration + features/raft + features/caching + features/qualityofservice + features/headerstransformation + features/claimstransformation + features/logging + features/requestid + features/middlewareinjection + features/loadbalancer + +.. toctree:: + :maxdepth: 2 + :hidden: + :caption: Building Ocelot + + building/overview + building/building + building/tests + building/releaseprocess + + + diff --git a/docs/introduction/bigpicture.rst b/docs/introduction/bigpicture.rst index 18ea1b126..95e62fdcb 100644 --- a/docs/introduction/bigpicture.rst +++ b/docs/introduction/bigpicture.rst @@ -1,38 +1,38 @@ -Big Picture -=========== - -Ocleot is aimed at people using .NET running -a micro services / service orientated architecture -that need a unified point of entry into their system. - -In particular I want easy integration with -IdentityServer reference and bearer tokens. - -Ocelot is a bunch of middlewares in a specific order. - -Ocelot manipulates the HttpRequest object into a state specified by its configuration until -it reaches a request builder middleware where it creates a HttpRequestMessage object which is -used to make a request to a downstream service. The middleware that makes the request is -the last thing in the Ocelot pipeline. It does not call the next middleware. -The response from the downstream service is stored in a per request scoped repository -and retrived as the requests goes back up the Ocelot pipeline. There is a piece of middleware -that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client. -That is basically it with a bunch of other features. - -The following are configuration that you use when deploying Ocelot. - -Basic Implementation -^^^^^^^^^^^^^^^^^^^^ -.. image:: ../images/OcelotBasic.jpg - -With IdentityServer -^^^^^^^^^^^^^^^^^^^ -.. image:: ../images/OcelotIndentityServer.jpg - -Multiple Instances -^^^^^^^^^^^^^^^^^^ -.. image:: ../images/OcelotMultipleInstances.jpg - -With Consul -^^^^^^^^^^^ -.. image:: ../images/OcelotMultipleInstancesConsul.jpg +Big Picture +=========== + +Ocleot is aimed at people using .NET running +a micro services / service orientated architecture +that need a unified point of entry into their system. + +In particular I want easy integration with +IdentityServer reference and bearer tokens. + +Ocelot is a bunch of middlewares in a specific order. + +Ocelot manipulates the HttpRequest object into a state specified by its configuration until +it reaches a request builder middleware where it creates a HttpRequestMessage object which is +used to make a request to a downstream service. The middleware that makes the request is +the last thing in the Ocelot pipeline. It does not call the next middleware. +The response from the downstream service is stored in a per request scoped repository +and retrived as the requests goes back up the Ocelot pipeline. There is a piece of middleware +that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client. +That is basically it with a bunch of other features. + +The following are configuration that you use when deploying Ocelot. + +Basic Implementation +^^^^^^^^^^^^^^^^^^^^ +.. image:: ../images/OcelotBasic.jpg + +With IdentityServer +^^^^^^^^^^^^^^^^^^^ +.. image:: ../images/OcelotIndentityServer.jpg + +Multiple Instances +^^^^^^^^^^^^^^^^^^ +.. image:: ../images/OcelotMultipleInstances.jpg + +With Consul +^^^^^^^^^^^ +.. image:: ../images/OcelotMultipleInstancesConsul.jpg diff --git a/docs/introduction/contributing.rst b/docs/introduction/contributing.rst index a36c34004..535e22275 100644 --- a/docs/introduction/contributing.rst +++ b/docs/introduction/contributing.rst @@ -1,5 +1,5 @@ -Contributing -============ - -Pull requests, issues and commentary welcome! No special process just create a request and get in +Contributing +============ + +Pull requests, issues and commentary welcome! No special process just create a request and get in touch either via gitter or create an issue. \ No newline at end of file diff --git a/docs/introduction/gettingstarted.rst b/docs/introduction/gettingstarted.rst index 1ac8a2982..65ff432dd 100644 --- a/docs/introduction/gettingstarted.rst +++ b/docs/introduction/gettingstarted.rst @@ -1,172 +1,172 @@ -Getting Started -=============== - -Ocelot is designed to work with .NET Core only and is currently -built to netcoreapp2.0 `this `_ documentation may prove helpful when working out if Ocelot would be suitable for you. - -.NET Core 2.0 -^^^^^^^^^^^^^ - -**Install NuGet package** - -Install Ocelot and it's dependecies using nuget. You will need to create a netcoreapp2.0 projct and bring the package into it. Then follow the Startup below and :doc:`../features/configuration` sections -to get up and running. - - ``Install-Package Ocelot`` - -All versions can be found `here `_. - -**Configuration** - -The following is a very basic configuration.json. It won't do anything but should get Ocelot starting. - -.. code-block:: json - - { - "ReRoutes": [], - "GlobalConfiguration": {} - } - -**Program** - -Then in your Program.cs you will want to have the following. This can be changed if you -don't wan't to use the default url e.g. UseUrls(someUrls) and should work as long as you keep the WebHostBuilder registration. - -.. code-block:: csharp - - public class Program - { - public static void Main(string[] args) - { - IWebHostBuilder builder = new WebHostBuilder(); - builder.ConfigureServices(s => { - s.AddSingleton(builder); - }); - builder.UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); - config.AddEnvironmentVariables(); - }) - .ConfigureLogging((hostingContext, logging) => - { - logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); - logging.AddConsole(); - }) - .UseIISIntegration() - .UseStartup(); - var host = builder.Build(); - host.Run(); - } - } - -Sadly we need to inject the IWebHostBuilder interface to get the applications scheme, url and port later. I cannot find a better way of doing this at the moment without setting this in a static or some kind of config. - -**Startup** - -An example startup using a json file for configuration can be seen below. This is the most basic startup and Ocelot has quite a few more options. Detailed in the rest of these docs! If you get a stuck a good place to look is at the ManualTests project in the source code. - -.. code-block:: csharp - - public class Startup - { - public void ConfigureServices(IServiceCollection services) - { - services.AddOcelot(); - } - - public void Configure(IApplicationBuilder app) - { - app.UseOcelot().Wait(); - } - } - -.NET Core 1.0 -^^^^^^^^^^^^^ - -**Install NuGet package** - -Install Ocelot and it's dependecies using nuget. You will need to create a netcoreapp1.0+ projct and bring the package into it. Then follow the Startup below and :doc:`../features/configuration` sections -to get up and running. Please note you will need to choose one of the Ocelot packages from the NuGet feed. - -All versions can be found `here `_. - -**Configuration** - -The following is a very basic configuration.json. It won't do anything but should get Ocelot starting. - -.. code-block:: json - - { - "ReRoutes": [], - "GlobalConfiguration": {} - } - -**Program** - -Then in your Program.cs you will want to have the following. This can be changed if you -don't wan't to use the default url e.g. UseUrls(someUrls) and should work as long as you keep the WebHostBuilder registration. - -.. code-block:: csharp - - public class Program - { - public static void Main(string[] args) - { - IWebHostBuilder builder = new WebHostBuilder(); - - builder.ConfigureServices(s => { - s.AddSingleton(builder); - }); - - builder.UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseStartup(); - - var host = builder.Build(); - - host.Run(); - } - } - -Sadly we need to inject the IWebHostBuilder interface to get the applications scheme, url and port later. I cannot find a better way of doing this at the moment without setting this in a static or some kind of config. - -**Startup** - -An example startup using a json file for configuration can be seen below. - -.. code-block:: csharp - - public class Startup - { - public Startup(IHostingEnvironment env) - { - var builder = new ConfigurationBuilder() - .SetBasePath(env.ContentRootPath) - .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) - .AddJsonFile("configuration.json") - .AddEnvironmentVariables(); - - Configuration = builder.Build(); - } - - public IConfigurationRoot Configuration { get; } - - public void ConfigureServices(IServiceCollection services) - { - services.AddOcelot(Configuration); - } - - public void Configure(IApplicationBuilder app) - { - app.UseOcelot().Wait(); - } - } - +Getting Started +=============== + +Ocelot is designed to work with .NET Core only and is currently +built to netcoreapp2.0 `this `_ documentation may prove helpful when working out if Ocelot would be suitable for you. + +.NET Core 2.0 +^^^^^^^^^^^^^ + +**Install NuGet package** + +Install Ocelot and it's dependecies using nuget. You will need to create a netcoreapp2.0 projct and bring the package into it. Then follow the Startup below and :doc:`../features/configuration` sections +to get up and running. + + ``Install-Package Ocelot`` + +All versions can be found `here `_. + +**Configuration** + +The following is a very basic configuration.json. It won't do anything but should get Ocelot starting. + +.. code-block:: json + + { + "ReRoutes": [], + "GlobalConfiguration": {} + } + +**Program** + +Then in your Program.cs you will want to have the following. This can be changed if you +don't wan't to use the default url e.g. UseUrls(someUrls) and should work as long as you keep the WebHostBuilder registration. + +.. code-block:: csharp + + public class Program + { + public static void Main(string[] args) + { + IWebHostBuilder builder = new WebHostBuilder(); + builder.ConfigureServices(s => { + s.AddSingleton(builder); + }); + builder.UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); + config.AddJsonFile("configuration.json"); + config.AddEnvironmentVariables(); + }) + .ConfigureLogging((hostingContext, logging) => + { + logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); + logging.AddConsole(); + }) + .UseIISIntegration() + .UseStartup(); + var host = builder.Build(); + host.Run(); + } + } + +Sadly we need to inject the IWebHostBuilder interface to get the applications scheme, url and port later. I cannot find a better way of doing this at the moment without setting this in a static or some kind of config. + +**Startup** + +An example startup using a json file for configuration can be seen below. This is the most basic startup and Ocelot has quite a few more options. Detailed in the rest of these docs! If you get a stuck a good place to look is at the ManualTests project in the source code. + +.. code-block:: csharp + + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + services.AddOcelot(); + } + + public void Configure(IApplicationBuilder app) + { + app.UseOcelot().Wait(); + } + } + +.NET Core 1.0 +^^^^^^^^^^^^^ + +**Install NuGet package** + +Install Ocelot and it's dependecies using nuget. You will need to create a netcoreapp1.0+ projct and bring the package into it. Then follow the Startup below and :doc:`../features/configuration` sections +to get up and running. Please note you will need to choose one of the Ocelot packages from the NuGet feed. + +All versions can be found `here `_. + +**Configuration** + +The following is a very basic configuration.json. It won't do anything but should get Ocelot starting. + +.. code-block:: json + + { + "ReRoutes": [], + "GlobalConfiguration": {} + } + +**Program** + +Then in your Program.cs you will want to have the following. This can be changed if you +don't wan't to use the default url e.g. UseUrls(someUrls) and should work as long as you keep the WebHostBuilder registration. + +.. code-block:: csharp + + public class Program + { + public static void Main(string[] args) + { + IWebHostBuilder builder = new WebHostBuilder(); + + builder.ConfigureServices(s => { + s.AddSingleton(builder); + }); + + builder.UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseStartup(); + + var host = builder.Build(); + + host.Run(); + } + } + +Sadly we need to inject the IWebHostBuilder interface to get the applications scheme, url and port later. I cannot find a better way of doing this at the moment without setting this in a static or some kind of config. + +**Startup** + +An example startup using a json file for configuration can be seen below. + +.. code-block:: csharp + + public class Startup + { + public Startup(IHostingEnvironment env) + { + var builder = new ConfigurationBuilder() + .SetBasePath(env.ContentRootPath) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) + .AddJsonFile("configuration.json") + .AddEnvironmentVariables(); + + Configuration = builder.Build(); + } + + public IConfigurationRoot Configuration { get; } + + public void ConfigureServices(IServiceCollection services) + { + services.AddOcelot(Configuration); + } + + public void Configure(IApplicationBuilder app) + { + app.UseOcelot().Wait(); + } + } + This is pretty much all you need to get going. \ No newline at end of file diff --git a/docs/introduction/notsupported.rst b/docs/introduction/notsupported.rst index f099ba853..8ef9b5e79 100644 --- a/docs/introduction/notsupported.rst +++ b/docs/introduction/notsupported.rst @@ -1,8 +1,8 @@ -Not Supported -============= - -Ocelot does not support... - -* Chunked Encoding - Ocelot will always get the body size and return Content-Length header. Sorry if this doesn't work for your use case! - +Not Supported +============= + +Ocelot does not support... + +* Chunked Encoding - Ocelot will always get the body size and return Content-Length header. Sorry if this doesn't work for your use case! + * Fowarding a host header - The host header that you send to Ocelot will not be forwarded to the downstream service. Obviously this would break everything :( \ No newline at end of file diff --git a/docs/make.bat b/docs/make.bat index 296afae35..cac625705 100755 --- a/docs/make.bat +++ b/docs/make.bat @@ -1,281 +1,281 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set BUILDDIR=_build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . -set I18NSPHINXOPTS=%SPHINXOPTS% . -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% - set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. epub3 to make an epub3 - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. texinfo to make Texinfo files - echo. gettext to make PO message catalogs - echo. changes to make an overview over all changed/added/deprecated items - echo. xml to make Docutils-native XML files - echo. pseudoxml to make pseudoxml-XML files for display purposes - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - echo. coverage to run coverage check of the documentation if enabled - echo. dummy to check syntax errors of document sources - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - - -REM Check if sphinx-build is available and fallback to Python version if any -%SPHINXBUILD% 1>NUL 2>NUL -if errorlevel 9009 goto sphinx_python -goto sphinx_ok - -:sphinx_python - -set SPHINXBUILD=python -m sphinx.__init__ -%SPHINXBUILD% 2> nul -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -:sphinx_ok - - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Ocelot.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Ocelot.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "epub3" ( - %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdf" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdfja" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf-ja - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "texinfo" ( - %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. - goto end -) - -if "%1" == "gettext" ( - %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The message catalogs are in %BUILDDIR%/locale. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -if "%1" == "coverage" ( - %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage - if errorlevel 1 exit /b 1 - echo. - echo.Testing of coverage in the sources finished, look at the ^ -results in %BUILDDIR%/coverage/python.txt. - goto end -) - -if "%1" == "xml" ( - %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The XML files are in %BUILDDIR%/xml. - goto end -) - -if "%1" == "pseudoxml" ( - %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. - goto end -) - -if "%1" == "dummy" ( - %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. Dummy builder generates no files. - goto end -) - -:end +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. epub3 to make an epub3 + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + echo. coverage to run coverage check of the documentation if enabled + echo. dummy to check syntax errors of document sources + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +REM Check if sphinx-build is available and fallback to Python version if any +%SPHINXBUILD% 1>NUL 2>NUL +if errorlevel 9009 goto sphinx_python +goto sphinx_ok + +:sphinx_python + +set SPHINXBUILD=python -m sphinx.__init__ +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +:sphinx_ok + + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Ocelot.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Ocelot.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "epub3" ( + %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "coverage" ( + %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage + if errorlevel 1 exit /b 1 + echo. + echo.Testing of coverage in the sources finished, look at the ^ +results in %BUILDDIR%/coverage/python.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +if "%1" == "dummy" ( + %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. Dummy builder generates no files. + goto end +) + +:end diff --git a/docs/make.sh b/docs/make.sh index 296afae35..cac625705 100755 --- a/docs/make.sh +++ b/docs/make.sh @@ -1,281 +1,281 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set BUILDDIR=_build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . -set I18NSPHINXOPTS=%SPHINXOPTS% . -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% - set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. epub3 to make an epub3 - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. texinfo to make Texinfo files - echo. gettext to make PO message catalogs - echo. changes to make an overview over all changed/added/deprecated items - echo. xml to make Docutils-native XML files - echo. pseudoxml to make pseudoxml-XML files for display purposes - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - echo. coverage to run coverage check of the documentation if enabled - echo. dummy to check syntax errors of document sources - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - - -REM Check if sphinx-build is available and fallback to Python version if any -%SPHINXBUILD% 1>NUL 2>NUL -if errorlevel 9009 goto sphinx_python -goto sphinx_ok - -:sphinx_python - -set SPHINXBUILD=python -m sphinx.__init__ -%SPHINXBUILD% 2> nul -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -:sphinx_ok - - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Ocelot.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Ocelot.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "epub3" ( - %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdf" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdfja" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf-ja - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "texinfo" ( - %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. - goto end -) - -if "%1" == "gettext" ( - %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The message catalogs are in %BUILDDIR%/locale. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -if "%1" == "coverage" ( - %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage - if errorlevel 1 exit /b 1 - echo. - echo.Testing of coverage in the sources finished, look at the ^ -results in %BUILDDIR%/coverage/python.txt. - goto end -) - -if "%1" == "xml" ( - %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The XML files are in %BUILDDIR%/xml. - goto end -) - -if "%1" == "pseudoxml" ( - %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. - goto end -) - -if "%1" == "dummy" ( - %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. Dummy builder generates no files. - goto end -) - -:end +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. epub3 to make an epub3 + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + echo. coverage to run coverage check of the documentation if enabled + echo. dummy to check syntax errors of document sources + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +REM Check if sphinx-build is available and fallback to Python version if any +%SPHINXBUILD% 1>NUL 2>NUL +if errorlevel 9009 goto sphinx_python +goto sphinx_ok + +:sphinx_python + +set SPHINXBUILD=python -m sphinx.__init__ +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +:sphinx_ok + + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Ocelot.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Ocelot.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "epub3" ( + %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "coverage" ( + %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage + if errorlevel 1 exit /b 1 + echo. + echo.Testing of coverage in the sources finished, look at the ^ +results in %BUILDDIR%/coverage/python.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +if "%1" == "dummy" ( + %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. Dummy builder generates no files. + goto end +) + +:end diff --git a/docs/readme.md b/docs/readme.md index 40230566f..1c1f4c4f0 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -1,14 +1,14 @@ -# Ocelot documentation - -The folder contains the documentation for Ocelot. - -We are using [Read the docs](https://readthedocs.org/) to host the documentation and the rendered version -can be found [here](https://ocelot.readthedocs.io). - -Doc pages are authored in ReStructuredText (RST) - you can find a primer [here](http://www.sphinx-doc.org/en/stable/rest.html). - -You can find more information about RTD and Sphinx under the following links: - -* [Read the Docs documentation](https://docs.readthedocs.io/en/latest/index.html) -* [Sphinx](http://www.sphinx-doc.org/) -* [Getting started Screencast](https://www.youtube.com/watch?feature=player_embedded&v=oJsUvBQyHBs) +# Ocelot documentation + +The folder contains the documentation for Ocelot. + +We are using [Read the docs](https://readthedocs.org/) to host the documentation and the rendered version +can be found [here](https://ocelot.readthedocs.io). + +Doc pages are authored in ReStructuredText (RST) - you can find a primer [here](http://www.sphinx-doc.org/en/stable/rest.html). + +You can find more information about RTD and Sphinx under the following links: + +* [Read the Docs documentation](https://docs.readthedocs.io/en/latest/index.html) +* [Sphinx](http://www.sphinx-doc.org/) +* [Getting started Screencast](https://www.youtube.com/watch?feature=player_embedded&v=oJsUvBQyHBs) diff --git a/global.json b/global.json index 9a3e328e2..38106d459 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ -{ - "projects": [ "src", "test" ], - "sdk": { - "version": "2.0.2" - } +{ + "projects": [ "src", "test" ], + "sdk": { + "version": "2.0.2" + } } \ No newline at end of file diff --git a/ocelot.postman_collection.json b/ocelot.postman_collection.json index 28bbeb0c8..2dc33d331 100644 --- a/ocelot.postman_collection.json +++ b/ocelot.postman_collection.json @@ -1,314 +1,314 @@ -{ - "id": "4dbde9fe-89f5-be35-bb9f-d3b438e16375", - "name": "Ocelot", - "description": "", - "order": [ - "a1c95935-ed18-d5dc-bcb8-a3db8ba1934f", - "ea0ed57a-2cb9-8acc-47dd-006b8db2f1b2", - "c4494401-3985-a5bf-71fb-6e4171384ac6", - "09af8dda-a9cb-20d2-5ee3-0a3023773a1a", - "e8825dc3-4137-99a7-0000-ef5786610dc3", - "fddfc4fa-5114-69e3-4744-203ed71a526b", - "c45d30d7-d9c4-fa05-8110-d6e769bb6ff9", - "4684c2fa-f38c-c193-5f55-bf563a1978c6", - "5f308240-79e3-cf74-7a6b-fe462f0d54f1", - "178f16da-c61b-c881-1c33-9d64a56851a4", - "26a08569-85f6-7f9a-726f-61be419c7a34" - ], - "folders": [], - "timestamp": 0, - "owner": "212120", - "public": false, - "requests": [ - { - "folder": null, - "id": "09af8dda-a9cb-20d2-5ee3-0a3023773a1a", - "name": "GET http://localhost:5000/comments?postId=1", - "dataMode": "params", - "data": null, - "rawModeData": null, - "descriptionFormat": "html", - "description": "", - "headers": "", - "method": "GET", - "pathVariables": {}, - "url": "http://localhost:5000/comments?postId=1", - "preRequestScript": null, - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" - }, - { - "id": "178f16da-c61b-c881-1c33-9d64a56851a4", - "headers": "Authorization: Bearer {{AccessToken}}\n", - "url": "http://localhost:5000/administration/configuration", - "preRequestScript": null, - "pathVariables": {}, - "method": "GET", - "data": null, - "dataMode": "params", - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "time": 1508914722969, - "name": "GET http://localhost:5000/admin/configuration", - "description": "", - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" - }, - { - "id": "26a08569-85f6-7f9a-726f-61be419c7a34", - "headers": "", - "url": "http://localhost:5000/administration/connect/token", - "preRequestScript": null, - "pathVariables": {}, - "method": "POST", - "data": [ - { - "key": "client_id", - "value": "raft", - "type": "text", - "enabled": true - }, - { - "key": "client_secret", - "value": "REALLYHARDPASSWORD", - "type": "text", - "enabled": true - }, - { - "key": "scope", - "value": "admin raft ", - "type": "text", - "enabled": true - }, - { - "key": "username", - "value": "admin", - "type": "text", - "enabled": false - }, - { - "key": "password", - "value": "secret", - "type": "text", - "enabled": false - }, - { - "key": "grant_type", - "value": "client_credentials", - "type": "text", - "enabled": true - } - ], - "dataMode": "params", - "tests": "var jsonData = JSON.parse(responseBody);\npostman.setGlobalVariable(\"AccessToken\", jsonData.access_token);\npostman.setGlobalVariable(\"RefreshToken\", jsonData.refresh_token);", - "currentHelper": "normal", - "helperAttributes": {}, - "time": 1513240031907, - "name": "POST http://localhost:5000/admin/connect/token copy copy", - "description": "", - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" - }, - { - "folder": null, - "id": "4684c2fa-f38c-c193-5f55-bf563a1978c6", - "name": "DELETE http://localhost:5000/posts/1", - "dataMode": "params", - "data": null, - "rawModeData": null, - "descriptionFormat": "html", - "description": "", - "headers": "", - "method": "DELETE", - "pathVariables": {}, - "url": "http://localhost:5000/posts/1", - "preRequestScript": null, - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" - }, - { - "id": "5f308240-79e3-cf74-7a6b-fe462f0d54f1", - "headers": "Authorization: Bearer {{AccessToken}}\n", - "url": "http://localhost:5000/administration/.well-known/openid-configuration", - "preRequestScript": null, - "pathVariables": {}, - "method": "GET", - "data": null, - "dataMode": "params", - "tests": null, - "currentHelper": "normal", - "helperAttributes": "{}", - "time": 1488038888813, - "name": "GET http://localhost:5000/admin/.well-known/openid-configuration", - "description": "", - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375", - "folder": null, - "rawModeData": null, - "descriptionFormat": null, - "queryParams": [], - "headerData": [ - { - "key": "Authorization", - "value": "Bearer {{AccessToken}}", - "description": "", - "enabled": true - } - ], - "pathVariableData": [] - }, - { - "id": "a1c95935-ed18-d5dc-bcb8-a3db8ba1934f", - "folder": null, - "name": "GET http://localhost:5000/posts", - "dataMode": "params", - "data": [ - { - "key": "client_id", - "value": "admin", - "type": "text", - "enabled": true - }, - { - "key": "client_secret", - "value": "secret", - "type": "text", - "enabled": true - }, - { - "key": "scope", - "value": "admin", - "type": "text", - "enabled": true - }, - { - "key": "username", - "value": "admin", - "type": "text", - "enabled": true - }, - { - "key": "password", - "value": "admin", - "type": "text", - "enabled": true - }, - { - "key": "grant_type", - "value": "password", - "type": "text", - "enabled": true - } - ], - "rawModeData": null, - "descriptionFormat": "html", - "description": "", - "headers": "", - "method": "POST", - "pathVariables": {}, - "url": "http://localhost:5000/admin/configuration", - "preRequestScript": null, - "tests": null, - "currentHelper": "normal", - "helperAttributes": "{}", - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" - }, - { - "folder": null, - "id": "c4494401-3985-a5bf-71fb-6e4171384ac6", - "name": "GET http://localhost:5000/posts/1/comments", - "dataMode": "params", - "data": null, - "rawModeData": null, - "descriptionFormat": "html", - "description": "", - "headers": "", - "method": "GET", - "pathVariables": {}, - "url": "http://localhost:5000/posts/1/comments", - "preRequestScript": null, - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" - }, - { - "folder": null, - "id": "c45d30d7-d9c4-fa05-8110-d6e769bb6ff9", - "name": "PATCH http://localhost:5000/posts/1", - "dataMode": "raw", - "data": [], - "descriptionFormat": "html", - "description": "", - "headers": "", - "method": "PATCH", - "pathVariables": {}, - "url": "http://localhost:5000/posts/1", - "preRequestScript": null, - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375", - "rawModeData": "{\n \"title\": \"gfdgsgsdgsdfgsdfgdfg\",\n}" - }, - { - "folder": null, - "id": "e8825dc3-4137-99a7-0000-ef5786610dc3", - "name": "POST http://localhost:5000/posts/1", - "dataMode": "raw", - "data": [], - "descriptionFormat": "html", - "description": "", - "headers": "", - "method": "POST", - "pathVariables": {}, - "url": "http://localhost:5000/posts", - "preRequestScript": null, - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375", - "rawModeData": "{\n \"userId\": 1,\n \"title\": \"test\",\n \"body\": \"test\"\n}" - }, - { - "folder": null, - "id": "ea0ed57a-2cb9-8acc-47dd-006b8db2f1b2", - "name": "GET http://localhost:5000/posts/1", - "dataMode": "params", - "data": null, - "rawModeData": null, - "descriptionFormat": "html", - "description": "", - "headers": "", - "method": "GET", - "pathVariables": {}, - "url": "http://localhost:5000/posts/1", - "preRequestScript": null, - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" - }, - { - "folder": null, - "id": "fddfc4fa-5114-69e3-4744-203ed71a526b", - "name": "PUT http://localhost:5000/posts/1", - "dataMode": "raw", - "data": [], - "descriptionFormat": "html", - "description": "", - "headers": "", - "method": "PUT", - "pathVariables": {}, - "url": "http://localhost:5000/posts/1", - "preRequestScript": null, - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375", - "rawModeData": "{\n \"userId\": 1,\n \"title\": \"test\",\n \"body\": \"test\"\n}" - } - ] +{ + "id": "4dbde9fe-89f5-be35-bb9f-d3b438e16375", + "name": "Ocelot", + "description": "", + "order": [ + "a1c95935-ed18-d5dc-bcb8-a3db8ba1934f", + "ea0ed57a-2cb9-8acc-47dd-006b8db2f1b2", + "c4494401-3985-a5bf-71fb-6e4171384ac6", + "09af8dda-a9cb-20d2-5ee3-0a3023773a1a", + "e8825dc3-4137-99a7-0000-ef5786610dc3", + "fddfc4fa-5114-69e3-4744-203ed71a526b", + "c45d30d7-d9c4-fa05-8110-d6e769bb6ff9", + "4684c2fa-f38c-c193-5f55-bf563a1978c6", + "5f308240-79e3-cf74-7a6b-fe462f0d54f1", + "178f16da-c61b-c881-1c33-9d64a56851a4", + "26a08569-85f6-7f9a-726f-61be419c7a34" + ], + "folders": [], + "timestamp": 0, + "owner": "212120", + "public": false, + "requests": [ + { + "folder": null, + "id": "09af8dda-a9cb-20d2-5ee3-0a3023773a1a", + "name": "GET http://localhost:5000/comments?postId=1", + "dataMode": "params", + "data": null, + "rawModeData": null, + "descriptionFormat": "html", + "description": "", + "headers": "", + "method": "GET", + "pathVariables": {}, + "url": "http://localhost:5000/comments?postId=1", + "preRequestScript": null, + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" + }, + { + "id": "178f16da-c61b-c881-1c33-9d64a56851a4", + "headers": "Authorization: Bearer {{AccessToken}}\n", + "url": "http://localhost:5000/administration/configuration", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1508914722969, + "name": "GET http://localhost:5000/admin/configuration", + "description": "", + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" + }, + { + "id": "26a08569-85f6-7f9a-726f-61be419c7a34", + "headers": "", + "url": "http://localhost:5000/administration/connect/token", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [ + { + "key": "client_id", + "value": "raft", + "type": "text", + "enabled": true + }, + { + "key": "client_secret", + "value": "REALLYHARDPASSWORD", + "type": "text", + "enabled": true + }, + { + "key": "scope", + "value": "admin raft ", + "type": "text", + "enabled": true + }, + { + "key": "username", + "value": "admin", + "type": "text", + "enabled": false + }, + { + "key": "password", + "value": "secret", + "type": "text", + "enabled": false + }, + { + "key": "grant_type", + "value": "client_credentials", + "type": "text", + "enabled": true + } + ], + "dataMode": "params", + "tests": "var jsonData = JSON.parse(responseBody);\npostman.setGlobalVariable(\"AccessToken\", jsonData.access_token);\npostman.setGlobalVariable(\"RefreshToken\", jsonData.refresh_token);", + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1513240031907, + "name": "POST http://localhost:5000/admin/connect/token copy copy", + "description": "", + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" + }, + { + "folder": null, + "id": "4684c2fa-f38c-c193-5f55-bf563a1978c6", + "name": "DELETE http://localhost:5000/posts/1", + "dataMode": "params", + "data": null, + "rawModeData": null, + "descriptionFormat": "html", + "description": "", + "headers": "", + "method": "DELETE", + "pathVariables": {}, + "url": "http://localhost:5000/posts/1", + "preRequestScript": null, + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" + }, + { + "id": "5f308240-79e3-cf74-7a6b-fe462f0d54f1", + "headers": "Authorization: Bearer {{AccessToken}}\n", + "url": "http://localhost:5000/administration/.well-known/openid-configuration", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": "{}", + "time": 1488038888813, + "name": "GET http://localhost:5000/admin/.well-known/openid-configuration", + "description": "", + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375", + "folder": null, + "rawModeData": null, + "descriptionFormat": null, + "queryParams": [], + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "description": "", + "enabled": true + } + ], + "pathVariableData": [] + }, + { + "id": "a1c95935-ed18-d5dc-bcb8-a3db8ba1934f", + "folder": null, + "name": "GET http://localhost:5000/posts", + "dataMode": "params", + "data": [ + { + "key": "client_id", + "value": "admin", + "type": "text", + "enabled": true + }, + { + "key": "client_secret", + "value": "secret", + "type": "text", + "enabled": true + }, + { + "key": "scope", + "value": "admin", + "type": "text", + "enabled": true + }, + { + "key": "username", + "value": "admin", + "type": "text", + "enabled": true + }, + { + "key": "password", + "value": "admin", + "type": "text", + "enabled": true + }, + { + "key": "grant_type", + "value": "password", + "type": "text", + "enabled": true + } + ], + "rawModeData": null, + "descriptionFormat": "html", + "description": "", + "headers": "", + "method": "POST", + "pathVariables": {}, + "url": "http://localhost:5000/admin/configuration", + "preRequestScript": null, + "tests": null, + "currentHelper": "normal", + "helperAttributes": "{}", + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" + }, + { + "folder": null, + "id": "c4494401-3985-a5bf-71fb-6e4171384ac6", + "name": "GET http://localhost:5000/posts/1/comments", + "dataMode": "params", + "data": null, + "rawModeData": null, + "descriptionFormat": "html", + "description": "", + "headers": "", + "method": "GET", + "pathVariables": {}, + "url": "http://localhost:5000/posts/1/comments", + "preRequestScript": null, + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" + }, + { + "folder": null, + "id": "c45d30d7-d9c4-fa05-8110-d6e769bb6ff9", + "name": "PATCH http://localhost:5000/posts/1", + "dataMode": "raw", + "data": [], + "descriptionFormat": "html", + "description": "", + "headers": "", + "method": "PATCH", + "pathVariables": {}, + "url": "http://localhost:5000/posts/1", + "preRequestScript": null, + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375", + "rawModeData": "{\n \"title\": \"gfdgsgsdgsdfgsdfgdfg\",\n}" + }, + { + "folder": null, + "id": "e8825dc3-4137-99a7-0000-ef5786610dc3", + "name": "POST http://localhost:5000/posts/1", + "dataMode": "raw", + "data": [], + "descriptionFormat": "html", + "description": "", + "headers": "", + "method": "POST", + "pathVariables": {}, + "url": "http://localhost:5000/posts", + "preRequestScript": null, + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375", + "rawModeData": "{\n \"userId\": 1,\n \"title\": \"test\",\n \"body\": \"test\"\n}" + }, + { + "folder": null, + "id": "ea0ed57a-2cb9-8acc-47dd-006b8db2f1b2", + "name": "GET http://localhost:5000/posts/1", + "dataMode": "params", + "data": null, + "rawModeData": null, + "descriptionFormat": "html", + "description": "", + "headers": "", + "method": "GET", + "pathVariables": {}, + "url": "http://localhost:5000/posts/1", + "preRequestScript": null, + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" + }, + { + "folder": null, + "id": "fddfc4fa-5114-69e3-4744-203ed71a526b", + "name": "PUT http://localhost:5000/posts/1", + "dataMode": "raw", + "data": [], + "descriptionFormat": "html", + "description": "", + "headers": "", + "method": "PUT", + "pathVariables": {}, + "url": "http://localhost:5000/posts/1", + "preRequestScript": null, + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375", + "rawModeData": "{\n \"userId\": 1,\n \"title\": \"test\",\n \"body\": \"test\"\n}" + } + ] } \ No newline at end of file diff --git a/release.ps1 b/release.ps1 index 683cb58c9..c396b5702 100644 --- a/release.ps1 +++ b/release.ps1 @@ -1,2 +1,2 @@ -./build.ps1 -target Release +./build.ps1 -target Release exit $LASTEXITCODE \ No newline at end of file diff --git a/run-acceptance-tests.ps1 b/run-acceptance-tests.ps1 index 6c6ade104..8f6b2dc25 100644 --- a/run-acceptance-tests.ps1 +++ b/run-acceptance-tests.ps1 @@ -1,2 +1,2 @@ -./build -target RunAcceptanceTests +./build -target RunAcceptanceTests exit $LASTEXITCODE \ No newline at end of file diff --git a/run-acceptance-tests.sh b/run-acceptance-tests.sh index e05baea12..6a263bea1 100755 --- a/run-acceptance-tests.sh +++ b/run-acceptance-tests.sh @@ -1,3 +1,3 @@ -#!/usr/bin/env bash - +#!/usr/bin/env bash + ./build.sh --target RunAcceptanceTests \ No newline at end of file diff --git a/run-benchmarks.ps1 b/run-benchmarks.ps1 index cb4e9b610..790ce6c6d 100644 --- a/run-benchmarks.ps1 +++ b/run-benchmarks.ps1 @@ -1,2 +1,2 @@ -./build.ps1 -target RunBenchmarkTests +./build.ps1 -target RunBenchmarkTests exit $LASTEXITCODE \ No newline at end of file diff --git a/run-unit-tests.ps1 b/run-unit-tests.ps1 index c3c368322..444f0c469 100644 --- a/run-unit-tests.ps1 +++ b/run-unit-tests.ps1 @@ -1,2 +1,2 @@ -./build.ps1 -target RunUnitTests +./build.ps1 -target RunUnitTests exit $LASTEXITCODE \ No newline at end of file diff --git a/run-unit-tests.sh b/run-unit-tests.sh index da848514e..8ced6b7f6 100755 --- a/run-unit-tests.sh +++ b/run-unit-tests.sh @@ -1,3 +1,3 @@ -#!/usr/bin/env bash - +#!/usr/bin/env bash + ./build.sh --target RunUnitTests \ No newline at end of file diff --git a/src/Ocelot/Authentication/BearerToken.cs b/src/Ocelot/Authentication/BearerToken.cs index 8ac4e2001..5a52614a3 100644 --- a/src/Ocelot/Authentication/BearerToken.cs +++ b/src/Ocelot/Authentication/BearerToken.cs @@ -1,16 +1,16 @@ -using Newtonsoft.Json; - -namespace Ocelot.Authentication -{ - class BearerToken - { - [JsonProperty("access_token")] - public string AccessToken { get; set; } - - [JsonProperty("expires_in")] - public int ExpiresIn { get; set; } - - [JsonProperty("token_type")] - public string TokenType { get; set; } - } +using Newtonsoft.Json; + +namespace Ocelot.Authentication +{ + class BearerToken + { + [JsonProperty("access_token")] + public string AccessToken { get; set; } + + [JsonProperty("expires_in")] + public int ExpiresIn { get; set; } + + [JsonProperty("token_type")] + public string TokenType { get; set; } + } } \ No newline at end of file diff --git a/src/Ocelot/Authentication/Handler/SupportedAuthenticationProviders.cs b/src/Ocelot/Authentication/Handler/SupportedAuthenticationProviders.cs index 5662fe401..aa60a8b07 100644 --- a/src/Ocelot/Authentication/Handler/SupportedAuthenticationProviders.cs +++ b/src/Ocelot/Authentication/Handler/SupportedAuthenticationProviders.cs @@ -1,8 +1,8 @@ -namespace Ocelot.Authentication.Handler -{ - public enum SupportedAuthenticationProviders - { - IdentityServer, - Jwt - } -} +namespace Ocelot.Authentication.Handler +{ + public enum SupportedAuthenticationProviders + { + IdentityServer, + Jwt + } +} diff --git a/src/Ocelot/Authentication/Middleware/AuthenticationMiddlewareMiddlewareExtensions.cs b/src/Ocelot/Authentication/Middleware/AuthenticationMiddlewareMiddlewareExtensions.cs index 8e1a97e85..4539ba253 100644 --- a/src/Ocelot/Authentication/Middleware/AuthenticationMiddlewareMiddlewareExtensions.cs +++ b/src/Ocelot/Authentication/Middleware/AuthenticationMiddlewareMiddlewareExtensions.cs @@ -1,12 +1,12 @@ -using Microsoft.AspNetCore.Builder; - -namespace Ocelot.Authentication.Middleware -{ - public static class AuthenticationMiddlewareMiddlewareExtensions - { - public static IApplicationBuilder UseAuthenticationMiddleware(this IApplicationBuilder builder) - { - return builder.UseMiddleware(builder); - } - } +using Microsoft.AspNetCore.Builder; + +namespace Ocelot.Authentication.Middleware +{ + public static class AuthenticationMiddlewareMiddlewareExtensions + { + public static IApplicationBuilder UseAuthenticationMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(builder); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Authorisation/ClaimValueNotAuthorisedError.cs b/src/Ocelot/Authorisation/ClaimValueNotAuthorisedError.cs index 166430ed5..9d552205c 100644 --- a/src/Ocelot/Authorisation/ClaimValueNotAuthorisedError.cs +++ b/src/Ocelot/Authorisation/ClaimValueNotAuthorisedError.cs @@ -1,12 +1,12 @@ -using Ocelot.Errors; - -namespace Ocelot.Authorisation -{ - public class ClaimValueNotAuthorisedError : Error - { - public ClaimValueNotAuthorisedError(string message) - : base(message, OcelotErrorCode.ClaimValueNotAuthorisedError) - { - } - } -} +using Ocelot.Errors; + +namespace Ocelot.Authorisation +{ + public class ClaimValueNotAuthorisedError : Error + { + public ClaimValueNotAuthorisedError(string message) + : base(message, OcelotErrorCode.ClaimValueNotAuthorisedError) + { + } + } +} diff --git a/src/Ocelot/Authorisation/ClaimsAuthoriser.cs b/src/Ocelot/Authorisation/ClaimsAuthoriser.cs index 13c18bb3a..29acb685a 100644 --- a/src/Ocelot/Authorisation/ClaimsAuthoriser.cs +++ b/src/Ocelot/Authorisation/ClaimsAuthoriser.cs @@ -1,53 +1,53 @@ -using System.Collections.Generic; -using System.Security.Claims; -using Ocelot.Errors; -using Ocelot.Responses; - -namespace Ocelot.Authorisation -{ - using Infrastructure.Claims.Parser; - - public class ClaimsAuthoriser : IClaimsAuthoriser - { - private readonly IClaimsParser _claimsParser; - - public ClaimsAuthoriser(IClaimsParser claimsParser) - { - _claimsParser = claimsParser; - } - - public Response Authorise(ClaimsPrincipal claimsPrincipal, Dictionary routeClaimsRequirement) - { - foreach (var required in routeClaimsRequirement) - { - var value = _claimsParser.GetValue(claimsPrincipal.Claims, required.Key, string.Empty, 0); - - if (value.IsError) - { - return new ErrorResponse(value.Errors); - } - - if (value.Data != null) - { - var authorised = value.Data == required.Value; - if (!authorised) - { - return new ErrorResponse(new List - { - new ClaimValueNotAuthorisedError( - $"claim value: {value.Data} is not the same as required value: {required.Value} for type: {required.Key}") - }); - } - } - else - { - return new ErrorResponse(new List - { - new UserDoesNotHaveClaimError($"user does not have claim {required.Key}") - }); - } - } - return new OkResponse(true); - } - } +using System.Collections.Generic; +using System.Security.Claims; +using Ocelot.Errors; +using Ocelot.Responses; + +namespace Ocelot.Authorisation +{ + using Infrastructure.Claims.Parser; + + public class ClaimsAuthoriser : IClaimsAuthoriser + { + private readonly IClaimsParser _claimsParser; + + public ClaimsAuthoriser(IClaimsParser claimsParser) + { + _claimsParser = claimsParser; + } + + public Response Authorise(ClaimsPrincipal claimsPrincipal, Dictionary routeClaimsRequirement) + { + foreach (var required in routeClaimsRequirement) + { + var value = _claimsParser.GetValue(claimsPrincipal.Claims, required.Key, string.Empty, 0); + + if (value.IsError) + { + return new ErrorResponse(value.Errors); + } + + if (value.Data != null) + { + var authorised = value.Data == required.Value; + if (!authorised) + { + return new ErrorResponse(new List + { + new ClaimValueNotAuthorisedError( + $"claim value: {value.Data} is not the same as required value: {required.Value} for type: {required.Key}") + }); + } + } + else + { + return new ErrorResponse(new List + { + new UserDoesNotHaveClaimError($"user does not have claim {required.Key}") + }); + } + } + return new OkResponse(true); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Authorisation/IClaimsAuthoriser.cs b/src/Ocelot/Authorisation/IClaimsAuthoriser.cs index 0288403a3..4abd799e9 100644 --- a/src/Ocelot/Authorisation/IClaimsAuthoriser.cs +++ b/src/Ocelot/Authorisation/IClaimsAuthoriser.cs @@ -1,12 +1,12 @@ -using System.Security.Claims; -using Ocelot.Responses; - -namespace Ocelot.Authorisation -{ - using System.Collections.Generic; - - public interface IClaimsAuthoriser - { - Response Authorise(ClaimsPrincipal claimsPrincipal, Dictionary routeClaimsRequirement); - } -} +using System.Security.Claims; +using Ocelot.Responses; + +namespace Ocelot.Authorisation +{ + using System.Collections.Generic; + + public interface IClaimsAuthoriser + { + Response Authorise(ClaimsPrincipal claimsPrincipal, Dictionary routeClaimsRequirement); + } +} diff --git a/src/Ocelot/Authorisation/IScopesAuthoriser.cs b/src/Ocelot/Authorisation/IScopesAuthoriser.cs index 62b7bf93c..a6a1396c2 100644 --- a/src/Ocelot/Authorisation/IScopesAuthoriser.cs +++ b/src/Ocelot/Authorisation/IScopesAuthoriser.cs @@ -1,12 +1,12 @@ -using System.Security.Claims; -using Ocelot.Responses; - -namespace Ocelot.Authorisation -{ - using System.Collections.Generic; - - public interface IScopesAuthoriser - { - Response Authorise(ClaimsPrincipal claimsPrincipal, List routeAllowedScopes); - } -} +using System.Security.Claims; +using Ocelot.Responses; + +namespace Ocelot.Authorisation +{ + using System.Collections.Generic; + + public interface IScopesAuthoriser + { + Response Authorise(ClaimsPrincipal claimsPrincipal, List routeAllowedScopes); + } +} diff --git a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs index 4a707fdc2..f3f55e357 100644 --- a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs +++ b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs @@ -1,117 +1,117 @@ -using Ocelot.Infrastructure.RequestData; -using Ocelot.Logging; -using Ocelot.Responses; -using Ocelot.Configuration; - -namespace Ocelot.Authorisation.Middleware -{ - using System.Collections.Generic; - using System.Threading.Tasks; - using Errors; - using Microsoft.AspNetCore.Http; - using Ocelot.Middleware; - - public class AuthorisationMiddleware : OcelotMiddleware - { - private readonly RequestDelegate _next; - private readonly IClaimsAuthoriser _claimsAuthoriser; - private readonly IScopesAuthoriser _scopesAuthoriser; - private readonly IOcelotLogger _logger; - - public AuthorisationMiddleware(RequestDelegate next, - IRequestScopedDataRepository requestScopedDataRepository, - IClaimsAuthoriser claimsAuthoriser, - IScopesAuthoriser scopesAuthoriser, - IOcelotLoggerFactory loggerFactory) - : base(requestScopedDataRepository) - { - _next = next; - _claimsAuthoriser = claimsAuthoriser; - _scopesAuthoriser = scopesAuthoriser; - _logger = loggerFactory.CreateLogger(); - } - - public async Task Invoke(HttpContext context) - { - if (IsAuthenticatedRoute(DownstreamRoute.ReRoute)) - { - _logger.LogDebug("route is authenticated scopes must be checked"); - - var authorised = _scopesAuthoriser.Authorise(context.User, DownstreamRoute.ReRoute.AuthenticationOptions.AllowedScopes); - - if (authorised.IsError) - { - _logger.LogDebug("error authorising user scopes"); - - SetPipelineError(authorised.Errors); - return; - } - - if (IsAuthorised(authorised)) - { - _logger.LogDebug("user scopes is authorised calling next authorisation checks"); - } - else - { - _logger.LogDebug("user scopes is not authorised setting pipeline error"); - - SetPipelineError(new List - { - new UnauthorisedError( - $"{context.User.Identity.Name} unable to access {DownstreamRoute.ReRoute.UpstreamPathTemplate.Value}") - }); - } - } - - if (IsAuthorisedRoute(DownstreamRoute.ReRoute)) - { - _logger.LogDebug("route is authorised"); - - var authorised = _claimsAuthoriser.Authorise(context.User, DownstreamRoute.ReRoute.RouteClaimsRequirement); - - if (authorised.IsError) - { - _logger.LogDebug($"Error whilst authorising {context.User.Identity.Name} for {context.User.Identity.Name}. Setting pipeline error"); - - SetPipelineError(authorised.Errors); - return; - } - - if (IsAuthorised(authorised)) - { - _logger.LogDebug($"{context.User.Identity.Name} has succesfully been authorised for {DownstreamRoute.ReRoute.UpstreamPathTemplate.Value}. Calling next middleware"); - await _next.Invoke(context); - } - else - { - _logger.LogDebug($"{context.User.Identity.Name} is not authorised to access {DownstreamRoute.ReRoute.UpstreamPathTemplate.Value}. Setting pipeline error"); - - SetPipelineError(new List - { - new UnauthorisedError($"{context.User.Identity.Name} is not authorised to access {DownstreamRoute.ReRoute.UpstreamPathTemplate.Value}") - }); - } - } - else - { - _logger.LogDebug($"{DownstreamRoute.ReRoute.DownstreamPathTemplate.Value} route does not require user to be authorised"); - await _next.Invoke(context); - } - } - - private static bool IsAuthorised(Response authorised) - { - return authorised.Data; - } - - private static bool IsAuthenticatedRoute(ReRoute reRoute) - { - return reRoute.IsAuthenticated; - } - - private static bool IsAuthorisedRoute(ReRoute reRoute) - { - return reRoute.IsAuthorised; - } - } -} +using Ocelot.Infrastructure.RequestData; +using Ocelot.Logging; +using Ocelot.Responses; +using Ocelot.Configuration; + +namespace Ocelot.Authorisation.Middleware +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using Errors; + using Microsoft.AspNetCore.Http; + using Ocelot.Middleware; + + public class AuthorisationMiddleware : OcelotMiddleware + { + private readonly RequestDelegate _next; + private readonly IClaimsAuthoriser _claimsAuthoriser; + private readonly IScopesAuthoriser _scopesAuthoriser; + private readonly IOcelotLogger _logger; + + public AuthorisationMiddleware(RequestDelegate next, + IRequestScopedDataRepository requestScopedDataRepository, + IClaimsAuthoriser claimsAuthoriser, + IScopesAuthoriser scopesAuthoriser, + IOcelotLoggerFactory loggerFactory) + : base(requestScopedDataRepository) + { + _next = next; + _claimsAuthoriser = claimsAuthoriser; + _scopesAuthoriser = scopesAuthoriser; + _logger = loggerFactory.CreateLogger(); + } + + public async Task Invoke(HttpContext context) + { + if (IsAuthenticatedRoute(DownstreamRoute.ReRoute)) + { + _logger.LogDebug("route is authenticated scopes must be checked"); + + var authorised = _scopesAuthoriser.Authorise(context.User, DownstreamRoute.ReRoute.AuthenticationOptions.AllowedScopes); + + if (authorised.IsError) + { + _logger.LogDebug("error authorising user scopes"); + + SetPipelineError(authorised.Errors); + return; + } + + if (IsAuthorised(authorised)) + { + _logger.LogDebug("user scopes is authorised calling next authorisation checks"); + } + else + { + _logger.LogDebug("user scopes is not authorised setting pipeline error"); + + SetPipelineError(new List + { + new UnauthorisedError( + $"{context.User.Identity.Name} unable to access {DownstreamRoute.ReRoute.UpstreamPathTemplate.Value}") + }); + } + } + + if (IsAuthorisedRoute(DownstreamRoute.ReRoute)) + { + _logger.LogDebug("route is authorised"); + + var authorised = _claimsAuthoriser.Authorise(context.User, DownstreamRoute.ReRoute.RouteClaimsRequirement); + + if (authorised.IsError) + { + _logger.LogDebug($"Error whilst authorising {context.User.Identity.Name} for {context.User.Identity.Name}. Setting pipeline error"); + + SetPipelineError(authorised.Errors); + return; + } + + if (IsAuthorised(authorised)) + { + _logger.LogDebug($"{context.User.Identity.Name} has succesfully been authorised for {DownstreamRoute.ReRoute.UpstreamPathTemplate.Value}. Calling next middleware"); + await _next.Invoke(context); + } + else + { + _logger.LogDebug($"{context.User.Identity.Name} is not authorised to access {DownstreamRoute.ReRoute.UpstreamPathTemplate.Value}. Setting pipeline error"); + + SetPipelineError(new List + { + new UnauthorisedError($"{context.User.Identity.Name} is not authorised to access {DownstreamRoute.ReRoute.UpstreamPathTemplate.Value}") + }); + } + } + else + { + _logger.LogDebug($"{DownstreamRoute.ReRoute.DownstreamPathTemplate.Value} route does not require user to be authorised"); + await _next.Invoke(context); + } + } + + private static bool IsAuthorised(Response authorised) + { + return authorised.Data; + } + + private static bool IsAuthenticatedRoute(ReRoute reRoute) + { + return reRoute.IsAuthenticated; + } + + private static bool IsAuthorisedRoute(ReRoute reRoute) + { + return reRoute.IsAuthorised; + } + } +} diff --git a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddlewareMiddlewareExtensions.cs b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddlewareMiddlewareExtensions.cs index e2c8af2d9..ca1fc9456 100644 --- a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddlewareMiddlewareExtensions.cs +++ b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddlewareMiddlewareExtensions.cs @@ -1,12 +1,12 @@ -namespace Ocelot.Authorisation.Middleware -{ - using Microsoft.AspNetCore.Builder; - - public static class AuthorisationMiddlewareMiddlewareExtensions - { - public static IApplicationBuilder UseAuthorisationMiddleware(this IApplicationBuilder builder) - { - return builder.UseMiddleware(); - } - } +namespace Ocelot.Authorisation.Middleware +{ + using Microsoft.AspNetCore.Builder; + + public static class AuthorisationMiddlewareMiddlewareExtensions + { + public static IApplicationBuilder UseAuthorisationMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Authorisation/ScopeNotAuthorisedError.cs b/src/Ocelot/Authorisation/ScopeNotAuthorisedError.cs index 5460cb903..d99e1abdb 100644 --- a/src/Ocelot/Authorisation/ScopeNotAuthorisedError.cs +++ b/src/Ocelot/Authorisation/ScopeNotAuthorisedError.cs @@ -1,12 +1,12 @@ -using Ocelot.Errors; - -namespace Ocelot.Authorisation -{ - public class ScopeNotAuthorisedError : Error - { - public ScopeNotAuthorisedError(string message) - : base(message, OcelotErrorCode.ScopeNotAuthorisedError) - { - } - } -} +using Ocelot.Errors; + +namespace Ocelot.Authorisation +{ + public class ScopeNotAuthorisedError : Error + { + public ScopeNotAuthorisedError(string message) + : base(message, OcelotErrorCode.ScopeNotAuthorisedError) + { + } + } +} diff --git a/src/Ocelot/Authorisation/ScopesAuthoriser.cs b/src/Ocelot/Authorisation/ScopesAuthoriser.cs index 6f32d6c39..ec433915a 100644 --- a/src/Ocelot/Authorisation/ScopesAuthoriser.cs +++ b/src/Ocelot/Authorisation/ScopesAuthoriser.cs @@ -1,51 +1,51 @@ -using IdentityModel; -using Ocelot.Errors; -using Ocelot.Responses; -using System.Collections.Generic; -using System.Security.Claims; -using System.Linq; - -namespace Ocelot.Authorisation -{ - using Infrastructure.Claims.Parser; - - public class ScopesAuthoriser : IScopesAuthoriser - { - private readonly IClaimsParser _claimsParser; - - public ScopesAuthoriser(IClaimsParser claimsParser) - { - _claimsParser = claimsParser; - } - - public Response Authorise(ClaimsPrincipal claimsPrincipal, List routeAllowedScopes) - { - if (routeAllowedScopes == null || routeAllowedScopes.Count == 0) - { - return new OkResponse(true); - } - - var values = _claimsParser.GetValuesByClaimType(claimsPrincipal.Claims, JwtClaimTypes.Scope); - - if (values.IsError) - { - return new ErrorResponse(values.Errors); - } - - var userScopes = values.Data; - - List matchesScopes = routeAllowedScopes.Intersect(userScopes).ToList(); - - if (matchesScopes == null || matchesScopes.Count == 0) - { - return new ErrorResponse(new List - { - new ScopeNotAuthorisedError( - $"no one user scope: '{string.Join(",", userScopes)}' match with some allowed scope: '{string.Join(",", routeAllowedScopes)}'") - }); - } - - return new OkResponse(true); - } - } +using IdentityModel; +using Ocelot.Errors; +using Ocelot.Responses; +using System.Collections.Generic; +using System.Security.Claims; +using System.Linq; + +namespace Ocelot.Authorisation +{ + using Infrastructure.Claims.Parser; + + public class ScopesAuthoriser : IScopesAuthoriser + { + private readonly IClaimsParser _claimsParser; + + public ScopesAuthoriser(IClaimsParser claimsParser) + { + _claimsParser = claimsParser; + } + + public Response Authorise(ClaimsPrincipal claimsPrincipal, List routeAllowedScopes) + { + if (routeAllowedScopes == null || routeAllowedScopes.Count == 0) + { + return new OkResponse(true); + } + + var values = _claimsParser.GetValuesByClaimType(claimsPrincipal.Claims, JwtClaimTypes.Scope); + + if (values.IsError) + { + return new ErrorResponse(values.Errors); + } + + var userScopes = values.Data; + + List matchesScopes = routeAllowedScopes.Intersect(userScopes).ToList(); + + if (matchesScopes == null || matchesScopes.Count == 0) + { + return new ErrorResponse(new List + { + new ScopeNotAuthorisedError( + $"no one user scope: '{string.Join(",", userScopes)}' match with some allowed scope: '{string.Join(",", routeAllowedScopes)}'") + }); + } + + return new OkResponse(true); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Authorisation/UnauthorisedError.cs b/src/Ocelot/Authorisation/UnauthorisedError.cs index 766c1a7bf..2a594d72f 100644 --- a/src/Ocelot/Authorisation/UnauthorisedError.cs +++ b/src/Ocelot/Authorisation/UnauthorisedError.cs @@ -1,12 +1,12 @@ -using Ocelot.Errors; - -namespace Ocelot.Authorisation -{ - public class UnauthorisedError : Error - { - public UnauthorisedError(string message) - : base(message, OcelotErrorCode.UnauthorizedError) - { - } - } -} +using Ocelot.Errors; + +namespace Ocelot.Authorisation +{ + public class UnauthorisedError : Error + { + public UnauthorisedError(string message) + : base(message, OcelotErrorCode.UnauthorizedError) + { + } + } +} diff --git a/src/Ocelot/Authorisation/UserDoesNotHaveClaimError.cs b/src/Ocelot/Authorisation/UserDoesNotHaveClaimError.cs index 38525f003..4752fdefd 100644 --- a/src/Ocelot/Authorisation/UserDoesNotHaveClaimError.cs +++ b/src/Ocelot/Authorisation/UserDoesNotHaveClaimError.cs @@ -1,12 +1,12 @@ -using Ocelot.Errors; - -namespace Ocelot.Authorisation -{ - public class UserDoesNotHaveClaimError : Error - { - public UserDoesNotHaveClaimError(string message) - : base(message, OcelotErrorCode.UserDoesNotHaveClaimError) - { - } - } -} +using Ocelot.Errors; + +namespace Ocelot.Authorisation +{ + public class UserDoesNotHaveClaimError : Error + { + public UserDoesNotHaveClaimError(string message) + : base(message, OcelotErrorCode.UserDoesNotHaveClaimError) + { + } + } +} diff --git a/src/Ocelot/Cache/CachedResponse.cs b/src/Ocelot/Cache/CachedResponse.cs index 122841fd1..02b327b39 100644 --- a/src/Ocelot/Cache/CachedResponse.cs +++ b/src/Ocelot/Cache/CachedResponse.cs @@ -1,25 +1,25 @@ -using System.Collections.Generic; -using System.Net; - -namespace Ocelot.Cache -{ - public class CachedResponse - { - public CachedResponse( - HttpStatusCode statusCode = HttpStatusCode.OK, - Dictionary> headers = null, - string body = null - ) - { - StatusCode = statusCode; - Headers = headers ?? new Dictionary>(); - Body = body ?? ""; - } - - public HttpStatusCode StatusCode { get; private set; } - - public Dictionary> Headers { get; private set; } - - public string Body { get; private set; } - } -} +using System.Collections.Generic; +using System.Net; + +namespace Ocelot.Cache +{ + public class CachedResponse + { + public CachedResponse( + HttpStatusCode statusCode = HttpStatusCode.OK, + Dictionary> headers = null, + string body = null + ) + { + StatusCode = statusCode; + Headers = headers ?? new Dictionary>(); + Body = body ?? ""; + } + + public HttpStatusCode StatusCode { get; private set; } + + public Dictionary> Headers { get; private set; } + + public string Body { get; private set; } + } +} diff --git a/src/Ocelot/Cache/IOcelotCache.cs b/src/Ocelot/Cache/IOcelotCache.cs index 9abf57610..b445b647e 100644 --- a/src/Ocelot/Cache/IOcelotCache.cs +++ b/src/Ocelot/Cache/IOcelotCache.cs @@ -1,13 +1,13 @@ -using System; -using System.Collections.Generic; - -namespace Ocelot.Cache -{ - public interface IOcelotCache - { - void Add(string key, T value, TimeSpan ttl, string region); - void AddAndDelete(string key, T value, TimeSpan ttl, string region); - T Get(string key, string region); - void ClearRegion(string region); - } -} +using System; +using System.Collections.Generic; + +namespace Ocelot.Cache +{ + public interface IOcelotCache + { + void Add(string key, T value, TimeSpan ttl, string region); + void AddAndDelete(string key, T value, TimeSpan ttl, string region); + T Get(string key, string region); + void ClearRegion(string region); + } +} diff --git a/src/Ocelot/Cache/IRegionCreator.cs b/src/Ocelot/Cache/IRegionCreator.cs index 8ed186dd2..f35c9a250 100644 --- a/src/Ocelot/Cache/IRegionCreator.cs +++ b/src/Ocelot/Cache/IRegionCreator.cs @@ -1,9 +1,9 @@ -using Ocelot.Configuration.File; - -namespace Ocelot.Cache -{ - public interface IRegionCreator - { - string Create(FileReRoute reRoute); - } +using Ocelot.Configuration.File; + +namespace Ocelot.Cache +{ + public interface IRegionCreator + { + string Create(FileReRoute reRoute); + } } \ No newline at end of file diff --git a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs index 513577ff0..717283f54 100644 --- a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs +++ b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs @@ -1,116 +1,116 @@ -using System; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Ocelot.Infrastructure.RequestData; -using Ocelot.Logging; -using Ocelot.Middleware; -using System.IO; - -namespace Ocelot.Cache.Middleware -{ - public class OutputCacheMiddleware : OcelotMiddleware - { - private readonly RequestDelegate _next; - private readonly IOcelotLogger _logger; - private readonly IOcelotCache _outputCache; - private readonly IRegionCreator _regionCreator; - - public OutputCacheMiddleware(RequestDelegate next, - IOcelotLoggerFactory loggerFactory, - IRequestScopedDataRepository scopedDataRepository, - IOcelotCache outputCache, - IRegionCreator regionCreator) - : base(scopedDataRepository) - { - _next = next; - _outputCache = outputCache; - _logger = loggerFactory.CreateLogger(); - _regionCreator = regionCreator; - } - - public async Task Invoke(HttpContext context) - { - if (!DownstreamRoute.ReRoute.IsCached) - { - await _next.Invoke(context); - return; - } - - var downstreamUrlKey = $"{DownstreamRequest.Method.Method}-{DownstreamRequest.RequestUri.OriginalString}"; - - _logger.LogDebug("started checking cache for {downstreamUrlKey}", downstreamUrlKey); - - var cached = _outputCache.Get(downstreamUrlKey, DownstreamRoute.ReRoute.CacheOptions.Region); - - if (cached != null) - { - _logger.LogDebug("cache entry exists for {downstreamUrlKey}", downstreamUrlKey); - - var response = CreateHttpResponseMessage(cached); - SetHttpResponseMessageThisRequest(response); - - _logger.LogDebug("finished returned cached response for {downstreamUrlKey}", downstreamUrlKey); - - return; - } - - _logger.LogDebug("no resonse cached for {downstreamUrlKey}", downstreamUrlKey); - - await _next.Invoke(context); - - if (PipelineError) - { - _logger.LogDebug("there was a pipeline error for {downstreamUrlKey}", downstreamUrlKey); - - return; - } - - cached = await CreateCachedResponse(HttpResponseMessage); - - _outputCache.Add(downstreamUrlKey, cached, TimeSpan.FromSeconds(DownstreamRoute.ReRoute.CacheOptions.TtlSeconds), DownstreamRoute.ReRoute.CacheOptions.Region); - - _logger.LogDebug("finished response added to cache for {downstreamUrlKey}", downstreamUrlKey); - } - - internal HttpResponseMessage CreateHttpResponseMessage(CachedResponse cached) - { - if (cached == null) - { - return null; - } - - var response = new HttpResponseMessage(cached.StatusCode); - foreach (var header in cached.Headers) - { - response.Headers.Add(header.Key, header.Value); - } - var content = new MemoryStream(Convert.FromBase64String(cached.Body)); - response.Content = new StreamContent(content); - - return response; - } - - internal async Task CreateCachedResponse(HttpResponseMessage response) - { - if (response == null) - { - return null; - } - - var statusCode = response.StatusCode; - var headers = response.Headers.ToDictionary(v => v.Key, v => v.Value); - string body = null; - - if (response.Content != null) - { - var content = await response.Content.ReadAsByteArrayAsync(); - body = Convert.ToBase64String(content); - } - - var cached = new CachedResponse(statusCode, headers, body); - return cached; - } - } -} +using System; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Ocelot.Infrastructure.RequestData; +using Ocelot.Logging; +using Ocelot.Middleware; +using System.IO; + +namespace Ocelot.Cache.Middleware +{ + public class OutputCacheMiddleware : OcelotMiddleware + { + private readonly RequestDelegate _next; + private readonly IOcelotLogger _logger; + private readonly IOcelotCache _outputCache; + private readonly IRegionCreator _regionCreator; + + public OutputCacheMiddleware(RequestDelegate next, + IOcelotLoggerFactory loggerFactory, + IRequestScopedDataRepository scopedDataRepository, + IOcelotCache outputCache, + IRegionCreator regionCreator) + : base(scopedDataRepository) + { + _next = next; + _outputCache = outputCache; + _logger = loggerFactory.CreateLogger(); + _regionCreator = regionCreator; + } + + public async Task Invoke(HttpContext context) + { + if (!DownstreamRoute.ReRoute.IsCached) + { + await _next.Invoke(context); + return; + } + + var downstreamUrlKey = $"{DownstreamRequest.Method.Method}-{DownstreamRequest.RequestUri.OriginalString}"; + + _logger.LogDebug("started checking cache for {downstreamUrlKey}", downstreamUrlKey); + + var cached = _outputCache.Get(downstreamUrlKey, DownstreamRoute.ReRoute.CacheOptions.Region); + + if (cached != null) + { + _logger.LogDebug("cache entry exists for {downstreamUrlKey}", downstreamUrlKey); + + var response = CreateHttpResponseMessage(cached); + SetHttpResponseMessageThisRequest(response); + + _logger.LogDebug("finished returned cached response for {downstreamUrlKey}", downstreamUrlKey); + + return; + } + + _logger.LogDebug("no resonse cached for {downstreamUrlKey}", downstreamUrlKey); + + await _next.Invoke(context); + + if (PipelineError) + { + _logger.LogDebug("there was a pipeline error for {downstreamUrlKey}", downstreamUrlKey); + + return; + } + + cached = await CreateCachedResponse(HttpResponseMessage); + + _outputCache.Add(downstreamUrlKey, cached, TimeSpan.FromSeconds(DownstreamRoute.ReRoute.CacheOptions.TtlSeconds), DownstreamRoute.ReRoute.CacheOptions.Region); + + _logger.LogDebug("finished response added to cache for {downstreamUrlKey}", downstreamUrlKey); + } + + internal HttpResponseMessage CreateHttpResponseMessage(CachedResponse cached) + { + if (cached == null) + { + return null; + } + + var response = new HttpResponseMessage(cached.StatusCode); + foreach (var header in cached.Headers) + { + response.Headers.Add(header.Key, header.Value); + } + var content = new MemoryStream(Convert.FromBase64String(cached.Body)); + response.Content = new StreamContent(content); + + return response; + } + + internal async Task CreateCachedResponse(HttpResponseMessage response) + { + if (response == null) + { + return null; + } + + var statusCode = response.StatusCode; + var headers = response.Headers.ToDictionary(v => v.Key, v => v.Value); + string body = null; + + if (response.Content != null) + { + var content = await response.Content.ReadAsByteArrayAsync(); + body = Convert.ToBase64String(content); + } + + var cached = new CachedResponse(statusCode, headers, body); + return cached; + } + } +} diff --git a/src/Ocelot/Cache/Middleware/OutputCacheMiddlewareExtensions.cs b/src/Ocelot/Cache/Middleware/OutputCacheMiddlewareExtensions.cs index 76e406eeb..89e343a09 100644 --- a/src/Ocelot/Cache/Middleware/OutputCacheMiddlewareExtensions.cs +++ b/src/Ocelot/Cache/Middleware/OutputCacheMiddlewareExtensions.cs @@ -1,12 +1,12 @@ -using Microsoft.AspNetCore.Builder; - -namespace Ocelot.Cache.Middleware -{ - public static class OutputCacheMiddlewareExtensions - { - public static IApplicationBuilder UseOutputCacheMiddleware(this IApplicationBuilder builder) - { - return builder.UseMiddleware(); - } - } -} +using Microsoft.AspNetCore.Builder; + +namespace Ocelot.Cache.Middleware +{ + public static class OutputCacheMiddlewareExtensions + { + public static IApplicationBuilder UseOutputCacheMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } +} diff --git a/src/Ocelot/Cache/OcelotCacheManagerCache.cs b/src/Ocelot/Cache/OcelotCacheManagerCache.cs index 33ee15436..29f1ed332 100644 --- a/src/Ocelot/Cache/OcelotCacheManagerCache.cs +++ b/src/Ocelot/Cache/OcelotCacheManagerCache.cs @@ -1,44 +1,44 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using CacheManager.Core; - -namespace Ocelot.Cache -{ - public class OcelotCacheManagerCache : IOcelotCache - { - private readonly ICacheManager _cacheManager; - - public OcelotCacheManagerCache(ICacheManager cacheManager) - { - _cacheManager = cacheManager; - } - - public void Add(string key, T value, TimeSpan ttl, string region) - { - _cacheManager.Add(new CacheItem(key, region, value, ExpirationMode.Absolute, ttl)); - } - - public void AddAndDelete(string key, T value, TimeSpan ttl, string region) - { - var exists = _cacheManager.Get(key); - - if (exists != null) - { - _cacheManager.Remove(key); - } - - Add(key, value, ttl, region); - } - - public T Get(string key, string region) - { - return _cacheManager.Get(key, region); - } - - public void ClearRegion(string region) - { - _cacheManager.ClearRegion(region); - } - } +using System; +using System.Collections.Generic; +using System.Linq; +using CacheManager.Core; + +namespace Ocelot.Cache +{ + public class OcelotCacheManagerCache : IOcelotCache + { + private readonly ICacheManager _cacheManager; + + public OcelotCacheManagerCache(ICacheManager cacheManager) + { + _cacheManager = cacheManager; + } + + public void Add(string key, T value, TimeSpan ttl, string region) + { + _cacheManager.Add(new CacheItem(key, region, value, ExpirationMode.Absolute, ttl)); + } + + public void AddAndDelete(string key, T value, TimeSpan ttl, string region) + { + var exists = _cacheManager.Get(key); + + if (exists != null) + { + _cacheManager.Remove(key); + } + + Add(key, value, ttl, region); + } + + public T Get(string key, string region) + { + return _cacheManager.Get(key, region); + } + + public void ClearRegion(string region) + { + _cacheManager.ClearRegion(region); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Cache/OutputCacheController.cs b/src/Ocelot/Cache/OutputCacheController.cs index 2dafcb660..32b9fa7cf 100644 --- a/src/Ocelot/Cache/OutputCacheController.cs +++ b/src/Ocelot/Cache/OutputCacheController.cs @@ -1,29 +1,29 @@ -using System.Net.Http; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Ocelot.Cache; -using Ocelot.Configuration.Provider; - -namespace Ocelot.Cache -{ - [Authorize] - [Route("outputcache")] - public class OutputCacheController : Controller - { - private IOcelotCache _cache; - - public OutputCacheController(IOcelotCache cache) - { - _cache = cache; - } - - [HttpDelete] - [Route("{region}")] - public IActionResult Delete(string region) - { - _cache.ClearRegion(region); - return new NoContentResult(); - } - } +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Ocelot.Cache; +using Ocelot.Configuration.Provider; + +namespace Ocelot.Cache +{ + [Authorize] + [Route("outputcache")] + public class OutputCacheController : Controller + { + private IOcelotCache _cache; + + public OutputCacheController(IOcelotCache cache) + { + _cache = cache; + } + + [HttpDelete] + [Route("{region}")] + public IActionResult Delete(string region) + { + _cache.ClearRegion(region); + return new NoContentResult(); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Cache/RegionCreator.cs b/src/Ocelot/Cache/RegionCreator.cs index 87de751cc..e2ca874b9 100644 --- a/src/Ocelot/Cache/RegionCreator.cs +++ b/src/Ocelot/Cache/RegionCreator.cs @@ -1,24 +1,24 @@ -using System.Linq; -using Ocelot.Configuration; -using Ocelot.Configuration.File; - -namespace Ocelot.Cache -{ - - public class RegionCreator : IRegionCreator - { - public string Create(FileReRoute reRoute) - { - if(!string.IsNullOrEmpty(reRoute?.FileCacheOptions?.Region)) - { - return reRoute?.FileCacheOptions?.Region; - } - - var methods = string.Join("", reRoute.UpstreamHttpMethod.Select(m => m)); - - var region = $"{methods}{reRoute.UpstreamPathTemplate.Replace("/", "")}"; - - return region; - } - } +using System.Linq; +using Ocelot.Configuration; +using Ocelot.Configuration.File; + +namespace Ocelot.Cache +{ + + public class RegionCreator : IRegionCreator + { + public string Create(FileReRoute reRoute) + { + if(!string.IsNullOrEmpty(reRoute?.FileCacheOptions?.Region)) + { + return reRoute?.FileCacheOptions?.Region; + } + + var methods = string.Join("", reRoute.UpstreamHttpMethod.Select(m => m)); + + var region = $"{methods}{reRoute.UpstreamPathTemplate.Replace("/", "")}"; + + return region; + } + } } \ No newline at end of file diff --git a/src/Ocelot/Cache/Regions.cs b/src/Ocelot/Cache/Regions.cs index 1dbefbd5f..f94cc3064 100644 --- a/src/Ocelot/Cache/Regions.cs +++ b/src/Ocelot/Cache/Regions.cs @@ -1,13 +1,13 @@ -using System.Collections.Generic; - -namespace Ocelot.Cache -{ - public class Regions - { - public Regions(List value) - { - Value = value; - } - public List Value {get;private set;} - } +using System.Collections.Generic; + +namespace Ocelot.Cache +{ + public class Regions + { + public Regions(List value) + { + Value = value; + } + public List Value {get;private set;} + } } \ No newline at end of file diff --git a/src/Ocelot/Claims/AddClaimsToRequest.cs b/src/Ocelot/Claims/AddClaimsToRequest.cs index 120b0003c..aa1237a8f 100644 --- a/src/Ocelot/Claims/AddClaimsToRequest.cs +++ b/src/Ocelot/Claims/AddClaimsToRequest.cs @@ -1,46 +1,46 @@ -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -using Microsoft.AspNetCore.Http; -using Ocelot.Configuration; -using Ocelot.Infrastructure.Claims.Parser; -using Ocelot.Responses; - -namespace Ocelot.Claims -{ - public class AddClaimsToRequest : IAddClaimsToRequest - { - private readonly IClaimsParser _claimsParser; - - public AddClaimsToRequest(IClaimsParser claimsParser) - { - _claimsParser = claimsParser; - } - - public Response SetClaimsOnContext(List claimsToThings, HttpContext context) - { - foreach (var config in claimsToThings) - { - var value = _claimsParser.GetValue(context.User.Claims, config.NewKey, config.Delimiter, config.Index); - - if (value.IsError) - { - return new ErrorResponse(value.Errors); - } - - var exists = context.User.Claims.FirstOrDefault(x => x.Type == config.ExistingKey); - - var identity = context.User.Identity as ClaimsIdentity; - - if (exists != null) - { - identity?.RemoveClaim(exists); - } - - identity?.AddClaim(new System.Security.Claims.Claim(config.ExistingKey, value.Data)); - } - - return new OkResponse(); - } - } +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using Microsoft.AspNetCore.Http; +using Ocelot.Configuration; +using Ocelot.Infrastructure.Claims.Parser; +using Ocelot.Responses; + +namespace Ocelot.Claims +{ + public class AddClaimsToRequest : IAddClaimsToRequest + { + private readonly IClaimsParser _claimsParser; + + public AddClaimsToRequest(IClaimsParser claimsParser) + { + _claimsParser = claimsParser; + } + + public Response SetClaimsOnContext(List claimsToThings, HttpContext context) + { + foreach (var config in claimsToThings) + { + var value = _claimsParser.GetValue(context.User.Claims, config.NewKey, config.Delimiter, config.Index); + + if (value.IsError) + { + return new ErrorResponse(value.Errors); + } + + var exists = context.User.Claims.FirstOrDefault(x => x.Type == config.ExistingKey); + + var identity = context.User.Identity as ClaimsIdentity; + + if (exists != null) + { + identity?.RemoveClaim(exists); + } + + identity?.AddClaim(new System.Security.Claims.Claim(config.ExistingKey, value.Data)); + } + + return new OkResponse(); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Claims/IAddClaimsToRequest.cs b/src/Ocelot/Claims/IAddClaimsToRequest.cs index 02ae85e90..9adbcab40 100644 --- a/src/Ocelot/Claims/IAddClaimsToRequest.cs +++ b/src/Ocelot/Claims/IAddClaimsToRequest.cs @@ -1,13 +1,13 @@ -using System.Collections.Generic; -using Microsoft.AspNetCore.Http; -using Ocelot.Configuration; -using Ocelot.Responses; - -namespace Ocelot.Claims -{ - public interface IAddClaimsToRequest - { - Response SetClaimsOnContext(List claimsToThings, - HttpContext context); - } -} +using System.Collections.Generic; +using Microsoft.AspNetCore.Http; +using Ocelot.Configuration; +using Ocelot.Responses; + +namespace Ocelot.Claims +{ + public interface IAddClaimsToRequest + { + Response SetClaimsOnContext(List claimsToThings, + HttpContext context); + } +} diff --git a/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddleware.cs b/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddleware.cs index 9a2e4239c..afd2fae27 100644 --- a/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddleware.cs +++ b/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddleware.cs @@ -1,46 +1,46 @@ -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Ocelot.Infrastructure.RequestData; -using Ocelot.Logging; -using Ocelot.Middleware; - -namespace Ocelot.Claims.Middleware -{ - public class ClaimsBuilderMiddleware : OcelotMiddleware - { - private readonly RequestDelegate _next; - private readonly IAddClaimsToRequest _addClaimsToRequest; - private readonly IOcelotLogger _logger; - - public ClaimsBuilderMiddleware(RequestDelegate next, - IRequestScopedDataRepository requestScopedDataRepository, - IOcelotLoggerFactory loggerFactory, - IAddClaimsToRequest addClaimsToRequest) - : base(requestScopedDataRepository) - { - _next = next; - _addClaimsToRequest = addClaimsToRequest; - _logger = loggerFactory.CreateLogger(); - } - - public async Task Invoke(HttpContext context) - { - if (DownstreamRoute.ReRoute.ClaimsToClaims.Any()) - { - _logger.LogDebug("this route has instructions to convert claims to other claims"); - - var result = _addClaimsToRequest.SetClaimsOnContext(DownstreamRoute.ReRoute.ClaimsToClaims, context); - - if (result.IsError) - { - _logger.LogDebug("error converting claims to other claims, setting pipeline error"); - - SetPipelineError(result.Errors); - return; - } - } - await _next.Invoke(context); - } - } -} +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Ocelot.Infrastructure.RequestData; +using Ocelot.Logging; +using Ocelot.Middleware; + +namespace Ocelot.Claims.Middleware +{ + public class ClaimsBuilderMiddleware : OcelotMiddleware + { + private readonly RequestDelegate _next; + private readonly IAddClaimsToRequest _addClaimsToRequest; + private readonly IOcelotLogger _logger; + + public ClaimsBuilderMiddleware(RequestDelegate next, + IRequestScopedDataRepository requestScopedDataRepository, + IOcelotLoggerFactory loggerFactory, + IAddClaimsToRequest addClaimsToRequest) + : base(requestScopedDataRepository) + { + _next = next; + _addClaimsToRequest = addClaimsToRequest; + _logger = loggerFactory.CreateLogger(); + } + + public async Task Invoke(HttpContext context) + { + if (DownstreamRoute.ReRoute.ClaimsToClaims.Any()) + { + _logger.LogDebug("this route has instructions to convert claims to other claims"); + + var result = _addClaimsToRequest.SetClaimsOnContext(DownstreamRoute.ReRoute.ClaimsToClaims, context); + + if (result.IsError) + { + _logger.LogDebug("error converting claims to other claims, setting pipeline error"); + + SetPipelineError(result.Errors); + return; + } + } + await _next.Invoke(context); + } + } +} diff --git a/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddlewareExtensions.cs b/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddlewareExtensions.cs index fd3bef396..099a0add9 100644 --- a/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddlewareExtensions.cs +++ b/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddlewareExtensions.cs @@ -1,12 +1,12 @@ -using Microsoft.AspNetCore.Builder; - -namespace Ocelot.Claims.Middleware -{ - public static class ClaimsBuilderMiddlewareExtensions - { - public static IApplicationBuilder UseClaimsBuilderMiddleware(this IApplicationBuilder builder) - { - return builder.UseMiddleware(); - } - } +using Microsoft.AspNetCore.Builder; + +namespace Ocelot.Claims.Middleware +{ + public static class ClaimsBuilderMiddlewareExtensions + { + public static IApplicationBuilder UseClaimsBuilderMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Authentication/HashMatcher.cs b/src/Ocelot/Configuration/Authentication/HashMatcher.cs index 087733327..5f17362f9 100644 --- a/src/Ocelot/Configuration/Authentication/HashMatcher.cs +++ b/src/Ocelot/Configuration/Authentication/HashMatcher.cs @@ -1,22 +1,22 @@ -using System; -using Microsoft.AspNetCore.Cryptography.KeyDerivation; - -namespace Ocelot.Configuration.Authentication -{ - public class HashMatcher : IHashMatcher - { - public bool Match(string password, string salt, string hash) - { - byte[] s = Convert.FromBase64String(salt); - - string hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2( - password: password, - salt: s, - prf: KeyDerivationPrf.HMACSHA256, - iterationCount: 10000, - numBytesRequested: 256 / 8)); - - return hashed == hash; - } - } +using System; +using Microsoft.AspNetCore.Cryptography.KeyDerivation; + +namespace Ocelot.Configuration.Authentication +{ + public class HashMatcher : IHashMatcher + { + public bool Match(string password, string salt, string hash) + { + byte[] s = Convert.FromBase64String(salt); + + string hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2( + password: password, + salt: s, + prf: KeyDerivationPrf.HMACSHA256, + iterationCount: 10000, + numBytesRequested: 256 / 8)); + + return hashed == hash; + } + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Authentication/IHashMatcher.cs b/src/Ocelot/Configuration/Authentication/IHashMatcher.cs index ad0d8e039..629bf0089 100644 --- a/src/Ocelot/Configuration/Authentication/IHashMatcher.cs +++ b/src/Ocelot/Configuration/Authentication/IHashMatcher.cs @@ -1,7 +1,7 @@ -namespace Ocelot.Configuration.Authentication -{ - public interface IHashMatcher - { - bool Match(string password, string salt, string hash); - } +namespace Ocelot.Configuration.Authentication +{ + public interface IHashMatcher + { + bool Match(string password, string salt, string hash); + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/AuthenticationOptions.cs b/src/Ocelot/Configuration/AuthenticationOptions.cs index 21e53ad77..2b911b58e 100644 --- a/src/Ocelot/Configuration/AuthenticationOptions.cs +++ b/src/Ocelot/Configuration/AuthenticationOptions.cs @@ -1,16 +1,16 @@ -using System.Collections.Generic; - -namespace Ocelot.Configuration -{ - public class AuthenticationOptions - { - public AuthenticationOptions(List allowedScopes, string authenticationProviderKey) - { - AllowedScopes = allowedScopes; - AuthenticationProviderKey = authenticationProviderKey; - } - - public List AllowedScopes { get; private set; } - public string AuthenticationProviderKey { get; private set; } - } -} +using System.Collections.Generic; + +namespace Ocelot.Configuration +{ + public class AuthenticationOptions + { + public AuthenticationOptions(List allowedScopes, string authenticationProviderKey) + { + AllowedScopes = allowedScopes; + AuthenticationProviderKey = authenticationProviderKey; + } + + public List AllowedScopes { get; private set; } + public string AuthenticationProviderKey { get; private set; } + } +} diff --git a/src/Ocelot/Configuration/Builder/AuthenticationOptionsBuilder.cs b/src/Ocelot/Configuration/Builder/AuthenticationOptionsBuilder.cs index 8146236ef..352bc8a6b 100644 --- a/src/Ocelot/Configuration/Builder/AuthenticationOptionsBuilder.cs +++ b/src/Ocelot/Configuration/Builder/AuthenticationOptionsBuilder.cs @@ -1,27 +1,27 @@ -using System.Collections.Generic; - -namespace Ocelot.Configuration.Builder -{ - public class AuthenticationOptionsBuilder - { - private List _allowedScopes = new List(); - private string _authenticationProviderKey; - - public AuthenticationOptionsBuilder WithAllowedScopes(List allowedScopes) - { - _allowedScopes = allowedScopes; - return this; - } - - public AuthenticationOptionsBuilder WithAuthenticationProviderKey(string authenticationProviderKey) - { - _authenticationProviderKey = authenticationProviderKey; - return this; - } - - public AuthenticationOptions Build() - { - return new AuthenticationOptions(_allowedScopes, _authenticationProviderKey); - } - } +using System.Collections.Generic; + +namespace Ocelot.Configuration.Builder +{ + public class AuthenticationOptionsBuilder + { + private List _allowedScopes = new List(); + private string _authenticationProviderKey; + + public AuthenticationOptionsBuilder WithAllowedScopes(List allowedScopes) + { + _allowedScopes = allowedScopes; + return this; + } + + public AuthenticationOptionsBuilder WithAuthenticationProviderKey(string authenticationProviderKey) + { + _authenticationProviderKey = authenticationProviderKey; + return this; + } + + public AuthenticationOptions Build() + { + return new AuthenticationOptions(_allowedScopes, _authenticationProviderKey); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Builder/QoSOptionsBuilder.cs b/src/Ocelot/Configuration/Builder/QoSOptionsBuilder.cs index 8d953d5c2..93d18257e 100644 --- a/src/Ocelot/Configuration/Builder/QoSOptionsBuilder.cs +++ b/src/Ocelot/Configuration/Builder/QoSOptionsBuilder.cs @@ -1,34 +1,34 @@ -namespace Ocelot.Configuration.Builder -{ - public class QoSOptionsBuilder - { - private int _exceptionsAllowedBeforeBreaking; - - private int _durationOfBreak; - - private int _timeoutValue; - - public QoSOptionsBuilder WithExceptionsAllowedBeforeBreaking(int exceptionsAllowedBeforeBreaking) - { - _exceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking; - return this; - } - - public QoSOptionsBuilder WithDurationOfBreak(int durationOfBreak) - { - _durationOfBreak = durationOfBreak; - return this; - } - - public QoSOptionsBuilder WithTimeoutValue(int timeoutValue) - { - _timeoutValue = timeoutValue; - return this; - } - - public QoSOptions Build() - { - return new QoSOptions(_exceptionsAllowedBeforeBreaking, _durationOfBreak, _timeoutValue); - } - } -} +namespace Ocelot.Configuration.Builder +{ + public class QoSOptionsBuilder + { + private int _exceptionsAllowedBeforeBreaking; + + private int _durationOfBreak; + + private int _timeoutValue; + + public QoSOptionsBuilder WithExceptionsAllowedBeforeBreaking(int exceptionsAllowedBeforeBreaking) + { + _exceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking; + return this; + } + + public QoSOptionsBuilder WithDurationOfBreak(int durationOfBreak) + { + _durationOfBreak = durationOfBreak; + return this; + } + + public QoSOptionsBuilder WithTimeoutValue(int timeoutValue) + { + _timeoutValue = timeoutValue; + return this; + } + + public QoSOptions Build() + { + return new QoSOptions(_exceptionsAllowedBeforeBreaking, _durationOfBreak, _timeoutValue); + } + } +} diff --git a/src/Ocelot/Configuration/Builder/RateLimitOptionsBuilder.cs b/src/Ocelot/Configuration/Builder/RateLimitOptionsBuilder.cs index 532d990b2..69ad913c4 100644 --- a/src/Ocelot/Configuration/Builder/RateLimitOptionsBuilder.cs +++ b/src/Ocelot/Configuration/Builder/RateLimitOptionsBuilder.cs @@ -1,71 +1,71 @@ -using System.Collections.Generic; - -namespace Ocelot.Configuration.Builder -{ - public class RateLimitOptionsBuilder - { - private bool _enableRateLimiting; - private string _clientIdHeader; - private List _clientWhitelist; - private bool _disableRateLimitHeaders; - private string _quotaExceededMessage; - private string _rateLimitCounterPrefix; - private RateLimitRule _rateLimitRule; - private int _httpStatusCode; - - public RateLimitOptionsBuilder WithEnableRateLimiting(bool enableRateLimiting) - { - _enableRateLimiting = enableRateLimiting; - return this; - } - - public RateLimitOptionsBuilder WithClientIdHeader(string clientIdheader) - { - _clientIdHeader = clientIdheader; - return this; - } - - public RateLimitOptionsBuilder WithClientWhiteList(List clientWhitelist) - { - _clientWhitelist = clientWhitelist; - return this; - } - - public RateLimitOptionsBuilder WithDisableRateLimitHeaders(bool disableRateLimitHeaders) - { - _disableRateLimitHeaders = disableRateLimitHeaders; - return this; - } - - public RateLimitOptionsBuilder WithQuotaExceededMessage(string quotaExceededMessage) - { - _quotaExceededMessage = quotaExceededMessage; - return this; - } - - public RateLimitOptionsBuilder WithRateLimitCounterPrefix(string rateLimitCounterPrefix) - { - _rateLimitCounterPrefix = rateLimitCounterPrefix; - return this; - } - - public RateLimitOptionsBuilder WithRateLimitRule(RateLimitRule rateLimitRule) - { - _rateLimitRule = rateLimitRule; - return this; - } - - public RateLimitOptionsBuilder WithHttpStatusCode(int httpStatusCode) - { - _httpStatusCode = httpStatusCode; - return this; - } - - public RateLimitOptions Build() - { - return new RateLimitOptions(_enableRateLimiting, _clientIdHeader, _clientWhitelist, - _disableRateLimitHeaders, _quotaExceededMessage, _rateLimitCounterPrefix, - _rateLimitRule, _httpStatusCode); - } - } -} +using System.Collections.Generic; + +namespace Ocelot.Configuration.Builder +{ + public class RateLimitOptionsBuilder + { + private bool _enableRateLimiting; + private string _clientIdHeader; + private List _clientWhitelist; + private bool _disableRateLimitHeaders; + private string _quotaExceededMessage; + private string _rateLimitCounterPrefix; + private RateLimitRule _rateLimitRule; + private int _httpStatusCode; + + public RateLimitOptionsBuilder WithEnableRateLimiting(bool enableRateLimiting) + { + _enableRateLimiting = enableRateLimiting; + return this; + } + + public RateLimitOptionsBuilder WithClientIdHeader(string clientIdheader) + { + _clientIdHeader = clientIdheader; + return this; + } + + public RateLimitOptionsBuilder WithClientWhiteList(List clientWhitelist) + { + _clientWhitelist = clientWhitelist; + return this; + } + + public RateLimitOptionsBuilder WithDisableRateLimitHeaders(bool disableRateLimitHeaders) + { + _disableRateLimitHeaders = disableRateLimitHeaders; + return this; + } + + public RateLimitOptionsBuilder WithQuotaExceededMessage(string quotaExceededMessage) + { + _quotaExceededMessage = quotaExceededMessage; + return this; + } + + public RateLimitOptionsBuilder WithRateLimitCounterPrefix(string rateLimitCounterPrefix) + { + _rateLimitCounterPrefix = rateLimitCounterPrefix; + return this; + } + + public RateLimitOptionsBuilder WithRateLimitRule(RateLimitRule rateLimitRule) + { + _rateLimitRule = rateLimitRule; + return this; + } + + public RateLimitOptionsBuilder WithHttpStatusCode(int httpStatusCode) + { + _httpStatusCode = httpStatusCode; + return this; + } + + public RateLimitOptions Build() + { + return new RateLimitOptions(_enableRateLimiting, _clientIdHeader, _clientWhitelist, + _disableRateLimitHeaders, _quotaExceededMessage, _rateLimitCounterPrefix, + _rateLimitRule, _httpStatusCode); + } + } +} diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index d81d3351b..979251d1e 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -1,249 +1,239 @@ -using System.Collections.Generic; -using System.Net.Http; -using Ocelot.Values; -using System.Linq; -using Ocelot.Configuration.Creator; - -namespace Ocelot.Configuration.Builder -{ - public class ReRouteBuilder - { - private AuthenticationOptions _authenticationOptions; - private string _loadBalancerKey; - private string _downstreamPathTemplate; - private string _upstreamTemplate; - private UpstreamPathTemplate _upstreamTemplatePattern; - private List _upstreamHttpMethod; - private bool _isAuthenticated; - private List _configHeaderExtractorProperties; - private List _claimToClaims; - private Dictionary _routeClaimRequirement; - private bool _isAuthorised; - private List _claimToQueries; - private string _requestIdHeaderKey; - private bool _isCached; - private CacheOptions _fileCacheOptions; - private string _downstreamScheme; - private string _downstreamHost; - private int _downstreamPort; - private string _loadBalancer; - private bool _useQos; - private QoSOptions _qosOptions; - private HttpHandlerOptions _httpHandlerOptions; - public bool _enableRateLimiting; - public RateLimitOptions _rateLimitOptions; - private string _authenticationProviderKey; - private bool _useServiceDiscovery; - private string _serviceName; - - private List _upstreamHeaderFindAndReplace; - private List _downstreamHeaderFindAndReplace; - - public ReRouteBuilder WithLoadBalancer(string loadBalancer) - { - _loadBalancer = loadBalancer; - return this; - } - - public ReRouteBuilder WithDownstreamScheme(string downstreamScheme) - { - _downstreamScheme = downstreamScheme; - return this; - } - - public ReRouteBuilder WithDownstreamHost(string downstreamHost) - { - _downstreamHost = downstreamHost; - return this; - } - - public ReRouteBuilder WithDownstreamPathTemplate(string input) - { - _downstreamPathTemplate = input; - return this; - } - - public ReRouteBuilder WithUpstreamPathTemplate(string input) - { - _upstreamTemplate = input; - return this; - } - - public ReRouteBuilder WithUpstreamTemplatePattern(UpstreamPathTemplate input) - { - _upstreamTemplatePattern = input; - return this; - } - - public ReRouteBuilder WithUpstreamHttpMethod(List input) - { - _upstreamHttpMethod = (input.Count == 0) ? new List() : input.Select(x => new HttpMethod(x.Trim())).ToList(); - return this; - } - - public ReRouteBuilder WithIsAuthenticated(bool input) - { - _isAuthenticated = input; - return this; - } - - public ReRouteBuilder WithIsAuthorised(bool input) - { - _isAuthorised = input; - return this; - } - - public ReRouteBuilder WithRequestIdKey(string input) - { - _requestIdHeaderKey = input; - return this; - } - - public ReRouteBuilder WithClaimsToHeaders(List input) - { - _configHeaderExtractorProperties = input; - return this; - } - - public ReRouteBuilder WithClaimsToClaims(List input) - { - _claimToClaims = input; - return this; - } - - public ReRouteBuilder WithRouteClaimsRequirement(Dictionary input) - { - _routeClaimRequirement = input; - return this; - } - - public ReRouteBuilder WithClaimsToQueries(List input) - { - _claimToQueries = input; - return this; - } - - public ReRouteBuilder WithIsCached(bool input) - { - _isCached = input; - return this; - } - - public ReRouteBuilder WithCacheOptions(CacheOptions input) - { - _fileCacheOptions = input; - return this; - } - - public ReRouteBuilder WithDownstreamPort(int port) - { - _downstreamPort = port; - return this; - } - - public ReRouteBuilder WithIsQos(bool input) - { - _useQos = input; - return this; - } - - public ReRouteBuilder WithQosOptions(QoSOptions input) - { - _qosOptions = input; - return this; - } - - public ReRouteBuilder WithReRouteKey(string loadBalancerKey) - { - _loadBalancerKey = loadBalancerKey; - return this; - } - - public ReRouteBuilder WithAuthenticationOptions(AuthenticationOptions authenticationOptions) - { - _authenticationOptions = authenticationOptions; - return this; - } - - public ReRouteBuilder WithEnableRateLimiting(bool input) - { - _enableRateLimiting = input; - return this; - } - - public ReRouteBuilder WithRateLimitOptions(RateLimitOptions input) - { - _rateLimitOptions = input; - return this; - } - - public ReRouteBuilder WithAuthenticationProviderKey(string authenticationProviderKey) - { - _authenticationProviderKey = authenticationProviderKey; - return this; - } - - public ReRouteBuilder WithHttpHandlerOptions(HttpHandlerOptions input) - { - _httpHandlerOptions = input; - return this; - } - - public ReRouteBuilder WithUseServiceDiscovery(bool useServiceDiscovery) - { - _useServiceDiscovery = useServiceDiscovery; - return this; - } - - public ReRouteBuilder WithServiceName(string serviceName) - { - _serviceName = serviceName; - return this; - } - - public ReRouteBuilder WithUpstreamHeaderFindAndReplace(List upstreamHeaderFindAndReplace) - { - _upstreamHeaderFindAndReplace = upstreamHeaderFindAndReplace; - return this; - } - - public ReRouteBuilder WithDownstreamHeaderFindAndReplace(List downstreamHeaderFindAndReplace) - { - _downstreamHeaderFindAndReplace = downstreamHeaderFindAndReplace; - return this; - } - - public ReRoute Build() - { - return new ReRoute( - new PathTemplate(_downstreamPathTemplate), - new PathTemplate(_upstreamTemplate), - _upstreamHttpMethod, - _upstreamTemplatePattern, - _isAuthenticated, - _authenticationOptions, - _configHeaderExtractorProperties, - _claimToClaims, - _routeClaimRequirement, - _isAuthorised, - _claimToQueries, - _requestIdHeaderKey, - _isCached, - _fileCacheOptions, - _downstreamScheme, - _loadBalancer, - _downstreamHost, - _downstreamPort, - _loadBalancerKey, - _useQos, - _qosOptions, - _enableRateLimiting, - _rateLimitOptions, - _httpHandlerOptions, - _useServiceDiscovery, - _serviceName, - _upstreamHeaderFindAndReplace, - _downstreamHeaderFindAndReplace); - } - } -} +using System.Collections.Generic; +using System.Net.Http; +using Ocelot.Values; +using System.Linq; +using Ocelot.Configuration.Creator; +using System; + +namespace Ocelot.Configuration.Builder +{ + public class ReRouteBuilder + { + private AuthenticationOptions _authenticationOptions; + private string _loadBalancerKey; + private string _downstreamPathTemplate; + private string _upstreamTemplate; + private UpstreamPathTemplate _upstreamTemplatePattern; + private List _upstreamHttpMethod; + private bool _isAuthenticated; + private List _configHeaderExtractorProperties; + private List _claimToClaims; + private Dictionary _routeClaimRequirement; + private bool _isAuthorised; + private List _claimToQueries; + private string _requestIdHeaderKey; + private bool _isCached; + private CacheOptions _fileCacheOptions; + private string _downstreamScheme; + private string _loadBalancer; + private bool _useQos; + private QoSOptions _qosOptions; + private HttpHandlerOptions _httpHandlerOptions; + private bool _enableRateLimiting; + private RateLimitOptions _rateLimitOptions; + private bool _useServiceDiscovery; + private string _serviceName; + private List _upstreamHeaderFindAndReplace; + private List _downstreamHeaderFindAndReplace; + private readonly List _downstreamAddresses; + + public ReRouteBuilder() + { + _downstreamAddresses = new List(); + } + + public ReRouteBuilder WithDownstreamAddresses(List downstreamAddresses) + { + _downstreamAddresses.AddRange(downstreamAddresses); + return this; + } + + public ReRouteBuilder WithLoadBalancer(string loadBalancer) + { + _loadBalancer = loadBalancer; + return this; + } + + public ReRouteBuilder WithDownstreamScheme(string downstreamScheme) + { + _downstreamScheme = downstreamScheme; + return this; + } + + public ReRouteBuilder WithDownstreamPathTemplate(string input) + { + _downstreamPathTemplate = input; + return this; + } + + public ReRouteBuilder WithUpstreamPathTemplate(string input) + { + _upstreamTemplate = input; + return this; + } + + public ReRouteBuilder WithUpstreamTemplatePattern(UpstreamPathTemplate input) + { + _upstreamTemplatePattern = input; + return this; + } + + public ReRouteBuilder WithUpstreamHttpMethod(List input) + { + _upstreamHttpMethod = (input.Count == 0) ? new List() : input.Select(x => new HttpMethod(x.Trim())).ToList(); + return this; + } + + public ReRouteBuilder WithIsAuthenticated(bool input) + { + _isAuthenticated = input; + return this; + } + + public ReRouteBuilder WithIsAuthorised(bool input) + { + _isAuthorised = input; + return this; + } + + public ReRouteBuilder WithRequestIdKey(string input) + { + _requestIdHeaderKey = input; + return this; + } + + public ReRouteBuilder WithClaimsToHeaders(List input) + { + _configHeaderExtractorProperties = input; + return this; + } + + public ReRouteBuilder WithClaimsToClaims(List input) + { + _claimToClaims = input; + return this; + } + + public ReRouteBuilder WithRouteClaimsRequirement(Dictionary input) + { + _routeClaimRequirement = input; + return this; + } + + public ReRouteBuilder WithClaimsToQueries(List input) + { + _claimToQueries = input; + return this; + } + + public ReRouteBuilder WithIsCached(bool input) + { + _isCached = input; + return this; + } + + public ReRouteBuilder WithCacheOptions(CacheOptions input) + { + _fileCacheOptions = input; + return this; + } + + public ReRouteBuilder WithIsQos(bool input) + { + _useQos = input; + return this; + } + + public ReRouteBuilder WithQosOptions(QoSOptions input) + { + _qosOptions = input; + return this; + } + + public ReRouteBuilder WithReRouteKey(string loadBalancerKey) + { + _loadBalancerKey = loadBalancerKey; + return this; + } + + public ReRouteBuilder WithAuthenticationOptions(AuthenticationOptions authenticationOptions) + { + _authenticationOptions = authenticationOptions; + return this; + } + + public ReRouteBuilder WithEnableRateLimiting(bool input) + { + _enableRateLimiting = input; + return this; + } + + public ReRouteBuilder WithRateLimitOptions(RateLimitOptions input) + { + _rateLimitOptions = input; + return this; + } + + public ReRouteBuilder WithHttpHandlerOptions(HttpHandlerOptions input) + { + _httpHandlerOptions = input; + return this; + } + + public ReRouteBuilder WithUseServiceDiscovery(bool useServiceDiscovery) + { + _useServiceDiscovery = useServiceDiscovery; + return this; + } + + public ReRouteBuilder WithServiceName(string serviceName) + { + _serviceName = serviceName; + return this; + } + + public ReRouteBuilder WithUpstreamHeaderFindAndReplace(List upstreamHeaderFindAndReplace) + { + _upstreamHeaderFindAndReplace = upstreamHeaderFindAndReplace; + return this; + } + + public ReRouteBuilder WithDownstreamHeaderFindAndReplace(List downstreamHeaderFindAndReplace) + { + _downstreamHeaderFindAndReplace = downstreamHeaderFindAndReplace; + return this; + } + + public ReRoute Build() + { + return new ReRoute( + new PathTemplate(_downstreamPathTemplate), + new PathTemplate(_upstreamTemplate), + _upstreamHttpMethod, + _upstreamTemplatePattern, + _isAuthenticated, + _authenticationOptions, + _configHeaderExtractorProperties, + _claimToClaims, + _routeClaimRequirement, + _isAuthorised, + _claimToQueries, + _requestIdHeaderKey, + _isCached, + _fileCacheOptions, + _downstreamScheme, + _loadBalancer, + _loadBalancerKey, + _useQos, + _qosOptions, + _enableRateLimiting, + _rateLimitOptions, + _httpHandlerOptions, + _useServiceDiscovery, + _serviceName, + _upstreamHeaderFindAndReplace, + _downstreamHeaderFindAndReplace, + _downstreamAddresses); + } + } +} diff --git a/src/Ocelot/Configuration/Builder/ReRouteOptionsBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteOptionsBuilder.cs index b8520a260..e5729823d 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteOptionsBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteOptionsBuilder.cs @@ -1,46 +1,46 @@ -namespace Ocelot.Configuration.Builder -{ - public class ReRouteOptionsBuilder - { - private bool _isAuthenticated; - private bool _isAuthorised; - private bool _isCached; - private bool _isQoS; - private bool _enableRateLimiting; - - public ReRouteOptionsBuilder WithIsCached(bool isCached) - { - _isCached = isCached; - return this; - } - - public ReRouteOptionsBuilder WithIsAuthenticated(bool isAuthenticated) - { - _isAuthenticated = isAuthenticated; - return this; - } - - public ReRouteOptionsBuilder WithIsAuthorised(bool isAuthorised) - { - _isAuthorised = isAuthorised; - return this; - } - - public ReRouteOptionsBuilder WithIsQos(bool isQoS) - { - _isQoS = isQoS; - return this; - } - - public ReRouteOptionsBuilder WithRateLimiting(bool enableRateLimiting) - { - _enableRateLimiting = enableRateLimiting; - return this; - } - - public ReRouteOptions Build() - { - return new ReRouteOptions(_isAuthenticated, _isAuthorised, _isCached, _isQoS, _enableRateLimiting); - } - } +namespace Ocelot.Configuration.Builder +{ + public class ReRouteOptionsBuilder + { + private bool _isAuthenticated; + private bool _isAuthorised; + private bool _isCached; + private bool _isQoS; + private bool _enableRateLimiting; + + public ReRouteOptionsBuilder WithIsCached(bool isCached) + { + _isCached = isCached; + return this; + } + + public ReRouteOptionsBuilder WithIsAuthenticated(bool isAuthenticated) + { + _isAuthenticated = isAuthenticated; + return this; + } + + public ReRouteOptionsBuilder WithIsAuthorised(bool isAuthorised) + { + _isAuthorised = isAuthorised; + return this; + } + + public ReRouteOptionsBuilder WithIsQos(bool isQoS) + { + _isQoS = isQoS; + return this; + } + + public ReRouteOptionsBuilder WithRateLimiting(bool enableRateLimiting) + { + _enableRateLimiting = enableRateLimiting; + return this; + } + + public ReRouteOptions Build() + { + return new ReRouteOptions(_isAuthenticated, _isAuthorised, _isCached, _isQoS, _enableRateLimiting); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Builder/ServiceProviderConfigurationBuilder.cs b/src/Ocelot/Configuration/Builder/ServiceProviderConfigurationBuilder.cs index 3a9f79745..0999a47c5 100644 --- a/src/Ocelot/Configuration/Builder/ServiceProviderConfigurationBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ServiceProviderConfigurationBuilder.cs @@ -1,25 +1,25 @@ -namespace Ocelot.Configuration.Builder -{ - public class ServiceProviderConfigurationBuilder - { - private string _serviceDiscoveryProviderHost; - private int _serviceDiscoveryProviderPort; - - public ServiceProviderConfigurationBuilder WithServiceDiscoveryProviderHost(string serviceDiscoveryProviderHost) - { - _serviceDiscoveryProviderHost = serviceDiscoveryProviderHost; - return this; - } - - public ServiceProviderConfigurationBuilder WithServiceDiscoveryProviderPort(int serviceDiscoveryProviderPort) - { - _serviceDiscoveryProviderPort = serviceDiscoveryProviderPort; - return this; - } - - public ServiceProviderConfiguration Build() - { - return new ServiceProviderConfiguration(_serviceDiscoveryProviderHost,_serviceDiscoveryProviderPort); - } - } +namespace Ocelot.Configuration.Builder +{ + public class ServiceProviderConfigurationBuilder + { + private string _serviceDiscoveryProviderHost; + private int _serviceDiscoveryProviderPort; + + public ServiceProviderConfigurationBuilder WithServiceDiscoveryProviderHost(string serviceDiscoveryProviderHost) + { + _serviceDiscoveryProviderHost = serviceDiscoveryProviderHost; + return this; + } + + public ServiceProviderConfigurationBuilder WithServiceDiscoveryProviderPort(int serviceDiscoveryProviderPort) + { + _serviceDiscoveryProviderPort = serviceDiscoveryProviderPort; + return this; + } + + public ServiceProviderConfiguration Build() + { + return new ServiceProviderConfiguration(_serviceDiscoveryProviderHost,_serviceDiscoveryProviderPort); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/CacheOptions.cs b/src/Ocelot/Configuration/CacheOptions.cs index a8b6a9d32..a3a926a43 100644 --- a/src/Ocelot/Configuration/CacheOptions.cs +++ b/src/Ocelot/Configuration/CacheOptions.cs @@ -1,14 +1,14 @@ -namespace Ocelot.Configuration -{ - public class CacheOptions - { - public CacheOptions(int ttlSeconds, string region) - { - TtlSeconds = ttlSeconds; - Region = region; - } - - public int TtlSeconds { get; private set; } - public string Region {get;private set;} - } -} +namespace Ocelot.Configuration +{ + public class CacheOptions + { + public CacheOptions(int ttlSeconds, string region) + { + TtlSeconds = ttlSeconds; + Region = region; + } + + public int TtlSeconds { get; private set; } + public string Region {get;private set;} + } +} diff --git a/src/Ocelot/Configuration/ClaimToThing.cs b/src/Ocelot/Configuration/ClaimToThing.cs index 5a6776672..0d13f5d0a 100644 --- a/src/Ocelot/Configuration/ClaimToThing.cs +++ b/src/Ocelot/Configuration/ClaimToThing.cs @@ -1,18 +1,18 @@ -namespace Ocelot.Configuration -{ - public class ClaimToThing - { - public ClaimToThing(string existingKey, string newKey, string delimiter, int index) - { - NewKey = newKey; - Delimiter = delimiter; - Index = index; - ExistingKey = existingKey; - } - - public string ExistingKey { get; private set; } - public string NewKey { get; private set; } - public string Delimiter { get; private set; } - public int Index { get; private set; } - } -} +namespace Ocelot.Configuration +{ + public class ClaimToThing + { + public ClaimToThing(string existingKey, string newKey, string delimiter, int index) + { + NewKey = newKey; + Delimiter = delimiter; + Index = index; + ExistingKey = existingKey; + } + + public string ExistingKey { get; private set; } + public string NewKey { get; private set; } + public string Delimiter { get; private set; } + public int Index { get; private set; } + } +} diff --git a/src/Ocelot/Configuration/Creator/AuthenticationOptionsCreator.cs b/src/Ocelot/Configuration/Creator/AuthenticationOptionsCreator.cs index 1e3f9aa02..ce5e0e1ef 100644 --- a/src/Ocelot/Configuration/Creator/AuthenticationOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/AuthenticationOptionsCreator.cs @@ -1,12 +1,12 @@ -using Ocelot.Configuration.File; - -namespace Ocelot.Configuration.Creator -{ - public class AuthenticationOptionsCreator : IAuthenticationOptionsCreator - { - public AuthenticationOptions Create(FileReRoute reRoute) - { - return new AuthenticationOptions(reRoute.AuthenticationOptions.AllowedScopes, reRoute.AuthenticationOptions.AuthenticationProviderKey); - } - } +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public class AuthenticationOptionsCreator : IAuthenticationOptionsCreator + { + public AuthenticationOptions Create(FileReRoute reRoute) + { + return new AuthenticationOptions(reRoute.AuthenticationOptions.AllowedScopes, reRoute.AuthenticationOptions.AuthenticationProviderKey); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/ClaimsToThingCreator.cs b/src/Ocelot/Configuration/Creator/ClaimsToThingCreator.cs index 27f52a5f9..985abae4a 100644 --- a/src/Ocelot/Configuration/Creator/ClaimsToThingCreator.cs +++ b/src/Ocelot/Configuration/Creator/ClaimsToThingCreator.cs @@ -1,41 +1,41 @@ -using System.Collections.Generic; -using Ocelot.Configuration.Parser; -using Ocelot.Logging; - -namespace Ocelot.Configuration.Creator -{ - public class ClaimsToThingCreator : IClaimsToThingCreator - { - private readonly IClaimToThingConfigurationParser _claimToThingConfigParser; - private readonly IOcelotLogger _logger; - - public ClaimsToThingCreator(IClaimToThingConfigurationParser claimToThingConfigurationParser, - IOcelotLoggerFactory loggerFactory) - { - _logger = loggerFactory.CreateLogger(); - _claimToThingConfigParser = claimToThingConfigurationParser; - } - - public List Create(Dictionary inputToBeParsed) - { - var claimsToThings = new List(); - - foreach (var input in inputToBeParsed) - { - var claimToThing = _claimToThingConfigParser.Extract(input.Key, input.Value); - - if (claimToThing.IsError) - { - _logger.LogDebug("ClaimsToThingCreator.BuildAddThingsToRequest", - $"Unable to extract configuration for key: {input.Key} and value: {input.Value} your configuration file is incorrect"); - } - else - { - claimsToThings.Add(claimToThing.Data); - } - } - - return claimsToThings; - } - } +using System.Collections.Generic; +using Ocelot.Configuration.Parser; +using Ocelot.Logging; + +namespace Ocelot.Configuration.Creator +{ + public class ClaimsToThingCreator : IClaimsToThingCreator + { + private readonly IClaimToThingConfigurationParser _claimToThingConfigParser; + private readonly IOcelotLogger _logger; + + public ClaimsToThingCreator(IClaimToThingConfigurationParser claimToThingConfigurationParser, + IOcelotLoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + _claimToThingConfigParser = claimToThingConfigurationParser; + } + + public List Create(Dictionary inputToBeParsed) + { + var claimsToThings = new List(); + + foreach (var input in inputToBeParsed) + { + var claimToThing = _claimToThingConfigParser.Extract(input.Key, input.Value); + + if (claimToThing.IsError) + { + _logger.LogDebug("ClaimsToThingCreator.BuildAddThingsToRequest", + $"Unable to extract configuration for key: {input.Key} and value: {input.Value} your configuration file is incorrect"); + } + else + { + claimsToThings.Add(claimToThing.Data); + } + } + + return claimsToThings; + } + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/DownstreamAddressesCreator.cs b/src/Ocelot/Configuration/Creator/DownstreamAddressesCreator.cs new file mode 100644 index 000000000..777f9dce6 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/DownstreamAddressesCreator.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using System.Linq; +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public class DownstreamAddressesCreator : IDownstreamAddressesCreator + { + public List Create(FileReRoute reRoute) + { + return reRoute.DownstreamHostAndPorts.Select(hostAndPort => new DownstreamHostAndPort(hostAndPort.Host, hostAndPort.Port)).ToList(); + } + } +} diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index 32c5fb47b..2723bac41 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -38,6 +38,7 @@ public class FileOcelotConfigurationCreator : IOcelotConfigurationCreator private readonly IHttpHandlerOptionsCreator _httpHandlerOptionsCreator; private readonly IAdministrationPath _adminPath; private readonly IHeaderFindAndReplaceCreator _headerFAndRCreator; + private readonly IDownstreamAddressesCreator _downstreamAddressesCreator; public FileOcelotConfigurationCreator( @@ -55,9 +56,11 @@ public FileOcelotConfigurationCreator( IRegionCreator regionCreator, IHttpHandlerOptionsCreator httpHandlerOptionsCreator, IAdministrationPath adminPath, - IHeaderFindAndReplaceCreator headerFAndRCreator + IHeaderFindAndReplaceCreator headerFAndRCreator, + IDownstreamAddressesCreator downstreamAddressesCreator ) { + _downstreamAddressesCreator = downstreamAddressesCreator; _headerFAndRCreator = headerFAndRCreator; _adminPath = adminPath; _regionCreator = regionCreator; @@ -133,6 +136,8 @@ private ReRoute SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration gl var hAndRs = _headerFAndRCreator.Create(fileReRoute); + var downstreamAddresses = _downstreamAddressesCreator.Create(fileReRoute); + var reRoute = new ReRouteBuilder() .WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate) .WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate) @@ -150,8 +155,7 @@ private ReRoute SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration gl .WithCacheOptions(new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds, region)) .WithDownstreamScheme(fileReRoute.DownstreamScheme) .WithLoadBalancer(fileReRoute.LoadBalancer) - .WithDownstreamHost(fileReRoute.DownstreamHost) - .WithDownstreamPort(fileReRoute.DownstreamPort) + .WithDownstreamAddresses(downstreamAddresses) .WithReRouteKey(reRouteKey) .WithIsQos(fileReRouteOptions.IsQos) .WithQosOptions(qosOptions) diff --git a/src/Ocelot/Configuration/Creator/HeaderFindAndReplaceCreator.cs b/src/Ocelot/Configuration/Creator/HeaderFindAndReplaceCreator.cs index e7b613835..3820de4e2 100644 --- a/src/Ocelot/Configuration/Creator/HeaderFindAndReplaceCreator.cs +++ b/src/Ocelot/Configuration/Creator/HeaderFindAndReplaceCreator.cs @@ -1,68 +1,68 @@ -using System; -using System.Collections.Generic; -using Ocelot.Configuration.File; -using Ocelot.Middleware; - -namespace Ocelot.Configuration.Creator -{ - public class HeaderFindAndReplaceCreator : IHeaderFindAndReplaceCreator - { - private IBaseUrlFinder _finder; - private Dictionary> _placeholders; - - public HeaderFindAndReplaceCreator(IBaseUrlFinder finder) - { - _finder = finder; - _placeholders = new Dictionary>(); - _placeholders.Add("{BaseUrl}", () => { - return _finder.Find(); - }); - } - - public HeaderTransformations Create(FileReRoute fileReRoute) - { - var upstream = new List(); - - foreach(var input in fileReRoute.UpstreamHeaderTransform) - { - var hAndr = Map(input); - upstream.Add(hAndr); - } - - var downstream = new List(); - - foreach(var input in fileReRoute.DownstreamHeaderTransform) - { - var hAndr = Map(input); - downstream.Add(hAndr); - } - - return new HeaderTransformations(upstream, downstream); - } - - private HeaderFindAndReplace Map(KeyValuePair input) - { - var findAndReplace = input.Value.Split(","); - - var replace = findAndReplace[1].TrimStart(); - - var startOfPlaceholder = replace.IndexOf("{"); - if(startOfPlaceholder > -1) - { - var endOfPlaceholder = replace.IndexOf("}", startOfPlaceholder); - - var placeholder = replace.Substring(startOfPlaceholder, startOfPlaceholder + (endOfPlaceholder + 1)); - - if(_placeholders.ContainsKey(placeholder)) - { - var value = _placeholders[placeholder].Invoke(); - replace = replace.Replace(placeholder, value); - } - } - - var hAndr = new HeaderFindAndReplace(input.Key, findAndReplace[0], replace, 0); - - return hAndr; - } - } -} +using System; +using System.Collections.Generic; +using Ocelot.Configuration.File; +using Ocelot.Middleware; + +namespace Ocelot.Configuration.Creator +{ + public class HeaderFindAndReplaceCreator : IHeaderFindAndReplaceCreator + { + private IBaseUrlFinder _finder; + private Dictionary> _placeholders; + + public HeaderFindAndReplaceCreator(IBaseUrlFinder finder) + { + _finder = finder; + _placeholders = new Dictionary>(); + _placeholders.Add("{BaseUrl}", () => { + return _finder.Find(); + }); + } + + public HeaderTransformations Create(FileReRoute fileReRoute) + { + var upstream = new List(); + + foreach(var input in fileReRoute.UpstreamHeaderTransform) + { + var hAndr = Map(input); + upstream.Add(hAndr); + } + + var downstream = new List(); + + foreach(var input in fileReRoute.DownstreamHeaderTransform) + { + var hAndr = Map(input); + downstream.Add(hAndr); + } + + return new HeaderTransformations(upstream, downstream); + } + + private HeaderFindAndReplace Map(KeyValuePair input) + { + var findAndReplace = input.Value.Split(","); + + var replace = findAndReplace[1].TrimStart(); + + var startOfPlaceholder = replace.IndexOf("{"); + if(startOfPlaceholder > -1) + { + var endOfPlaceholder = replace.IndexOf("}", startOfPlaceholder); + + var placeholder = replace.Substring(startOfPlaceholder, startOfPlaceholder + (endOfPlaceholder + 1)); + + if(_placeholders.ContainsKey(placeholder)) + { + var value = _placeholders[placeholder].Invoke(); + replace = replace.Replace(placeholder, value); + } + } + + var hAndr = new HeaderFindAndReplace(input.Key, findAndReplace[0], replace, 0); + + return hAndr; + } + } +} diff --git a/src/Ocelot/Configuration/Creator/HeaderTransformations.cs b/src/Ocelot/Configuration/Creator/HeaderTransformations.cs index 2fba1c676..55e1e3b97 100644 --- a/src/Ocelot/Configuration/Creator/HeaderTransformations.cs +++ b/src/Ocelot/Configuration/Creator/HeaderTransformations.cs @@ -1,17 +1,17 @@ -using System.Collections.Generic; - -namespace Ocelot.Configuration.Creator -{ - public class HeaderTransformations - { - public HeaderTransformations(List upstream, List downstream) - { - Upstream = upstream; - Downstream = downstream; - } - - public List Upstream {get;private set;} - - public List Downstream {get;private set;} - } -} +using System.Collections.Generic; + +namespace Ocelot.Configuration.Creator +{ + public class HeaderTransformations + { + public HeaderTransformations(List upstream, List downstream) + { + Upstream = upstream; + Downstream = downstream; + } + + public List Upstream {get;private set;} + + public List Downstream {get;private set;} + } +} diff --git a/src/Ocelot/Configuration/Creator/IAuthenticationOptionsCreator.cs b/src/Ocelot/Configuration/Creator/IAuthenticationOptionsCreator.cs index b7ea81418..a987f69a8 100644 --- a/src/Ocelot/Configuration/Creator/IAuthenticationOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/IAuthenticationOptionsCreator.cs @@ -1,10 +1,10 @@ -using System.Collections.Generic; -using Ocelot.Configuration.File; - -namespace Ocelot.Configuration.Creator -{ - public interface IAuthenticationOptionsCreator - { - AuthenticationOptions Create(FileReRoute reRoute); - } +using System.Collections.Generic; +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public interface IAuthenticationOptionsCreator + { + AuthenticationOptions Create(FileReRoute reRoute); + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/IClaimsToThingCreator.cs b/src/Ocelot/Configuration/Creator/IClaimsToThingCreator.cs index 54ff8ddc6..ead3d5376 100644 --- a/src/Ocelot/Configuration/Creator/IClaimsToThingCreator.cs +++ b/src/Ocelot/Configuration/Creator/IClaimsToThingCreator.cs @@ -1,9 +1,9 @@ -using System.Collections.Generic; - -namespace Ocelot.Configuration.Creator -{ - public interface IClaimsToThingCreator - { - List Create(Dictionary thingsBeingAdded); - } +using System.Collections.Generic; + +namespace Ocelot.Configuration.Creator +{ + public interface IClaimsToThingCreator + { + List Create(Dictionary thingsBeingAdded); + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/IDownstreamAddressesCreator.cs b/src/Ocelot/Configuration/Creator/IDownstreamAddressesCreator.cs new file mode 100644 index 000000000..6db1c1bfa --- /dev/null +++ b/src/Ocelot/Configuration/Creator/IDownstreamAddressesCreator.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public interface IDownstreamAddressesCreator + { + List Create(FileReRoute reRoute); + } +} diff --git a/src/Ocelot/Configuration/Creator/IHeaderFindAndReplaceCreator.cs b/src/Ocelot/Configuration/Creator/IHeaderFindAndReplaceCreator.cs index 1423c8cab..9a983a8a1 100644 --- a/src/Ocelot/Configuration/Creator/IHeaderFindAndReplaceCreator.cs +++ b/src/Ocelot/Configuration/Creator/IHeaderFindAndReplaceCreator.cs @@ -1,10 +1,10 @@ -using System.Collections.Generic; -using Ocelot.Configuration.File; - -namespace Ocelot.Configuration.Creator -{ - public interface IHeaderFindAndReplaceCreator - { - HeaderTransformations Create(FileReRoute fileReRoute); - } -} +using System.Collections.Generic; +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public interface IHeaderFindAndReplaceCreator + { + HeaderTransformations Create(FileReRoute fileReRoute); + } +} diff --git a/src/Ocelot/Configuration/Creator/IOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/IOcelotConfigurationCreator.cs index 66a2d8aeb..4b4317017 100644 --- a/src/Ocelot/Configuration/Creator/IOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/IOcelotConfigurationCreator.cs @@ -1,11 +1,11 @@ -using System.Threading.Tasks; -using Ocelot.Configuration.File; -using Ocelot.Responses; - -namespace Ocelot.Configuration.Creator -{ - public interface IOcelotConfigurationCreator - { - Task> Create(FileConfiguration fileConfiguration); - } +using System.Threading.Tasks; +using Ocelot.Configuration.File; +using Ocelot.Responses; + +namespace Ocelot.Configuration.Creator +{ + public interface IOcelotConfigurationCreator + { + Task> Create(FileConfiguration fileConfiguration); + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/IQoSOptionsCreator.cs b/src/Ocelot/Configuration/Creator/IQoSOptionsCreator.cs index a0686ae54..a8c84dc37 100644 --- a/src/Ocelot/Configuration/Creator/IQoSOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/IQoSOptionsCreator.cs @@ -1,9 +1,9 @@ -using Ocelot.Configuration.File; - -namespace Ocelot.Configuration.Creator -{ - public interface IQoSOptionsCreator - { - QoSOptions Create(FileReRoute fileReRoute); - } +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public interface IQoSOptionsCreator + { + QoSOptions Create(FileReRoute fileReRoute); + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/IRateLimitOptionsCreator.cs b/src/Ocelot/Configuration/Creator/IRateLimitOptionsCreator.cs index 42f03a96a..91beea740 100644 --- a/src/Ocelot/Configuration/Creator/IRateLimitOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/IRateLimitOptionsCreator.cs @@ -1,9 +1,9 @@ -using Ocelot.Configuration.File; - -namespace Ocelot.Configuration.Creator -{ - public interface IRateLimitOptionsCreator - { - RateLimitOptions Create(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration, bool enableRateLimiting); - } +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public interface IRateLimitOptionsCreator + { + RateLimitOptions Create(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration, bool enableRateLimiting); + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/IReRouteOptionsCreator.cs b/src/Ocelot/Configuration/Creator/IReRouteOptionsCreator.cs index 9bc4a07a6..f19845a8c 100644 --- a/src/Ocelot/Configuration/Creator/IReRouteOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/IReRouteOptionsCreator.cs @@ -1,9 +1,9 @@ -using Ocelot.Configuration.File; - -namespace Ocelot.Configuration.Creator -{ - public interface IReRouteOptionsCreator - { - ReRouteOptions Create(FileReRoute fileReRoute); - } +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public interface IReRouteOptionsCreator + { + ReRouteOptions Create(FileReRoute fileReRoute); + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/IRequestIdKeyCreator.cs b/src/Ocelot/Configuration/Creator/IRequestIdKeyCreator.cs index 2800f8a59..898a653bb 100644 --- a/src/Ocelot/Configuration/Creator/IRequestIdKeyCreator.cs +++ b/src/Ocelot/Configuration/Creator/IRequestIdKeyCreator.cs @@ -1,9 +1,9 @@ -using Ocelot.Configuration.File; - -namespace Ocelot.Configuration.Creator -{ - public interface IRequestIdKeyCreator - { - string Create(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration); - } +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public interface IRequestIdKeyCreator + { + string Create(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration); + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/IServiceProviderConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/IServiceProviderConfigurationCreator.cs index ea48ba545..f2b750b4e 100644 --- a/src/Ocelot/Configuration/Creator/IServiceProviderConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/IServiceProviderConfigurationCreator.cs @@ -1,9 +1,9 @@ -using Ocelot.Configuration.File; - -namespace Ocelot.Configuration.Creator -{ - public interface IServiceProviderConfigurationCreator - { - ServiceProviderConfiguration Create(FileGlobalConfiguration globalConfiguration); - } +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public interface IServiceProviderConfigurationCreator + { + ServiceProviderConfiguration Create(FileGlobalConfiguration globalConfiguration); + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/IUpstreamTemplatePatternCreator.cs b/src/Ocelot/Configuration/Creator/IUpstreamTemplatePatternCreator.cs index 14de619d8..b40e5e3e4 100644 --- a/src/Ocelot/Configuration/Creator/IUpstreamTemplatePatternCreator.cs +++ b/src/Ocelot/Configuration/Creator/IUpstreamTemplatePatternCreator.cs @@ -1,10 +1,10 @@ -using Ocelot.Configuration.File; -using Ocelot.Values; - -namespace Ocelot.Configuration.Creator -{ - public interface IUpstreamTemplatePatternCreator - { - UpstreamPathTemplate Create(FileReRoute reRoute); - } +using Ocelot.Configuration.File; +using Ocelot.Values; + +namespace Ocelot.Configuration.Creator +{ + public interface IUpstreamTemplatePatternCreator + { + UpstreamPathTemplate Create(FileReRoute reRoute); + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/IdentityServerConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/IdentityServerConfigurationCreator.cs index c414c0a57..5e7fcd37f 100644 --- a/src/Ocelot/Configuration/Creator/IdentityServerConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/IdentityServerConfigurationCreator.cs @@ -1,26 +1,26 @@ -using System; -using System.Collections.Generic; -using IdentityServer4.AccessTokenValidation; -using IdentityServer4.Models; -using Ocelot.Configuration.Provider; - -namespace Ocelot.Configuration.Creator -{ - public static class IdentityServerConfigurationCreator - { - public static IdentityServerConfiguration GetIdentityServerConfiguration(string secret) - { - var credentialsSigningCertificateLocation = Environment.GetEnvironmentVariable("OCELOT_CERTIFICATE"); - var credentialsSigningCertificatePassword = Environment.GetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD"); - - return new IdentityServerConfiguration( - "admin", - false, - secret, - new List { "admin", "openid", "offline_access" }, - credentialsSigningCertificateLocation, - credentialsSigningCertificatePassword - ); - } - } -} +using System; +using System.Collections.Generic; +using IdentityServer4.AccessTokenValidation; +using IdentityServer4.Models; +using Ocelot.Configuration.Provider; + +namespace Ocelot.Configuration.Creator +{ + public static class IdentityServerConfigurationCreator + { + public static IdentityServerConfiguration GetIdentityServerConfiguration(string secret) + { + var credentialsSigningCertificateLocation = Environment.GetEnvironmentVariable("OCELOT_CERTIFICATE"); + var credentialsSigningCertificatePassword = Environment.GetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD"); + + return new IdentityServerConfiguration( + "admin", + false, + secret, + new List { "admin", "openid", "offline_access" }, + credentialsSigningCertificateLocation, + credentialsSigningCertificatePassword + ); + } + } +} diff --git a/src/Ocelot/Configuration/Creator/QoSOptionsCreator.cs b/src/Ocelot/Configuration/Creator/QoSOptionsCreator.cs index ddeef9105..3cf6b1cd1 100644 --- a/src/Ocelot/Configuration/Creator/QoSOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/QoSOptionsCreator.cs @@ -1,17 +1,17 @@ -using Ocelot.Configuration.Builder; -using Ocelot.Configuration.File; - -namespace Ocelot.Configuration.Creator -{ - public class QoSOptionsCreator : IQoSOptionsCreator - { - public QoSOptions Create(FileReRoute fileReRoute) - { - return new QoSOptionsBuilder() - .WithExceptionsAllowedBeforeBreaking(fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking) - .WithDurationOfBreak(fileReRoute.QoSOptions.DurationOfBreak) - .WithTimeoutValue(fileReRoute.QoSOptions.TimeoutValue) - .Build(); - } - } +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public class QoSOptionsCreator : IQoSOptionsCreator + { + public QoSOptions Create(FileReRoute fileReRoute) + { + return new QoSOptionsBuilder() + .WithExceptionsAllowedBeforeBreaking(fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking) + .WithDurationOfBreak(fileReRoute.QoSOptions.DurationOfBreak) + .WithTimeoutValue(fileReRoute.QoSOptions.TimeoutValue) + .Build(); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/RateLimitOptionsCreator.cs b/src/Ocelot/Configuration/Creator/RateLimitOptionsCreator.cs index c9eedcbdf..5d987aeee 100644 --- a/src/Ocelot/Configuration/Creator/RateLimitOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/RateLimitOptionsCreator.cs @@ -1,32 +1,32 @@ -using System; -using Ocelot.Configuration.Builder; -using Ocelot.Configuration.File; - -namespace Ocelot.Configuration.Creator -{ - public class RateLimitOptionsCreator : IRateLimitOptionsCreator - { - public RateLimitOptions Create(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration, bool enableRateLimiting) - { - RateLimitOptions rateLimitOption = null; - - if (enableRateLimiting) - { - rateLimitOption = new RateLimitOptionsBuilder() - .WithClientIdHeader(globalConfiguration.RateLimitOptions.ClientIdHeader) - .WithClientWhiteList(fileReRoute.RateLimitOptions.ClientWhitelist) - .WithDisableRateLimitHeaders(globalConfiguration.RateLimitOptions.DisableRateLimitHeaders) - .WithEnableRateLimiting(fileReRoute.RateLimitOptions.EnableRateLimiting) - .WithHttpStatusCode(globalConfiguration.RateLimitOptions.HttpStatusCode) - .WithQuotaExceededMessage(globalConfiguration.RateLimitOptions.QuotaExceededMessage) - .WithRateLimitCounterPrefix(globalConfiguration.RateLimitOptions.RateLimitCounterPrefix) - .WithRateLimitRule(new RateLimitRule(fileReRoute.RateLimitOptions.Period, - fileReRoute.RateLimitOptions.PeriodTimespan, - fileReRoute.RateLimitOptions.Limit)) - .Build(); - } - - return rateLimitOption; - } - } -} +using System; +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public class RateLimitOptionsCreator : IRateLimitOptionsCreator + { + public RateLimitOptions Create(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration, bool enableRateLimiting) + { + RateLimitOptions rateLimitOption = null; + + if (enableRateLimiting) + { + rateLimitOption = new RateLimitOptionsBuilder() + .WithClientIdHeader(globalConfiguration.RateLimitOptions.ClientIdHeader) + .WithClientWhiteList(fileReRoute.RateLimitOptions.ClientWhitelist) + .WithDisableRateLimitHeaders(globalConfiguration.RateLimitOptions.DisableRateLimitHeaders) + .WithEnableRateLimiting(fileReRoute.RateLimitOptions.EnableRateLimiting) + .WithHttpStatusCode(globalConfiguration.RateLimitOptions.HttpStatusCode) + .WithQuotaExceededMessage(globalConfiguration.RateLimitOptions.QuotaExceededMessage) + .WithRateLimitCounterPrefix(globalConfiguration.RateLimitOptions.RateLimitCounterPrefix) + .WithRateLimitRule(new RateLimitRule(fileReRoute.RateLimitOptions.Period, + fileReRoute.RateLimitOptions.PeriodTimespan, + fileReRoute.RateLimitOptions.Limit)) + .Build(); + } + + return rateLimitOption; + } + } +} diff --git a/src/Ocelot/Configuration/Creator/ReRouteOptionsCreator.cs b/src/Ocelot/Configuration/Creator/ReRouteOptionsCreator.cs index f98a77c6b..8defc420e 100644 --- a/src/Ocelot/Configuration/Creator/ReRouteOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/ReRouteOptionsCreator.cs @@ -1,52 +1,52 @@ -using Ocelot.Configuration.Builder; -using Ocelot.Configuration.File; - -namespace Ocelot.Configuration.Creator -{ - public class ReRouteOptionsCreator : IReRouteOptionsCreator - { - public ReRouteOptions Create(FileReRoute fileReRoute) - { - var isAuthenticated = IsAuthenticated(fileReRoute); - var isAuthorised = IsAuthorised(fileReRoute); - var isCached = IsCached(fileReRoute); - var isQos = IsQoS(fileReRoute); - var enableRateLimiting = IsEnableRateLimiting(fileReRoute); - - var options = new ReRouteOptionsBuilder() - .WithIsAuthenticated(isAuthenticated) - .WithIsAuthorised(isAuthorised) - .WithIsCached(isCached) - .WithIsQos(isQos) - .WithRateLimiting(enableRateLimiting) - .Build(); - - return options; - } - - private static bool IsEnableRateLimiting(FileReRoute fileReRoute) - { - return (fileReRoute.RateLimitOptions != null && fileReRoute.RateLimitOptions.EnableRateLimiting) ? true : false; - } - - private bool IsQoS(FileReRoute fileReRoute) - { - return fileReRoute.QoSOptions?.ExceptionsAllowedBeforeBreaking > 0 && fileReRoute.QoSOptions?.TimeoutValue > 0; - } - - private bool IsAuthenticated(FileReRoute fileReRoute) - { - return !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.AuthenticationProviderKey); - } - - private bool IsAuthorised(FileReRoute fileReRoute) - { - return fileReRoute.RouteClaimsRequirement?.Count > 0; - } - - private bool IsCached(FileReRoute fileReRoute) - { - return fileReRoute.FileCacheOptions.TtlSeconds > 0; - } - } +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public class ReRouteOptionsCreator : IReRouteOptionsCreator + { + public ReRouteOptions Create(FileReRoute fileReRoute) + { + var isAuthenticated = IsAuthenticated(fileReRoute); + var isAuthorised = IsAuthorised(fileReRoute); + var isCached = IsCached(fileReRoute); + var isQos = IsQoS(fileReRoute); + var enableRateLimiting = IsEnableRateLimiting(fileReRoute); + + var options = new ReRouteOptionsBuilder() + .WithIsAuthenticated(isAuthenticated) + .WithIsAuthorised(isAuthorised) + .WithIsCached(isCached) + .WithIsQos(isQos) + .WithRateLimiting(enableRateLimiting) + .Build(); + + return options; + } + + private static bool IsEnableRateLimiting(FileReRoute fileReRoute) + { + return (fileReRoute.RateLimitOptions != null && fileReRoute.RateLimitOptions.EnableRateLimiting) ? true : false; + } + + private bool IsQoS(FileReRoute fileReRoute) + { + return fileReRoute.QoSOptions?.ExceptionsAllowedBeforeBreaking > 0 && fileReRoute.QoSOptions?.TimeoutValue > 0; + } + + private bool IsAuthenticated(FileReRoute fileReRoute) + { + return !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.AuthenticationProviderKey); + } + + private bool IsAuthorised(FileReRoute fileReRoute) + { + return fileReRoute.RouteClaimsRequirement?.Count > 0; + } + + private bool IsCached(FileReRoute fileReRoute) + { + return fileReRoute.FileCacheOptions.TtlSeconds > 0; + } + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/RequestIdKeyCreator.cs b/src/Ocelot/Configuration/Creator/RequestIdKeyCreator.cs index dde171a81..6fad9b28b 100644 --- a/src/Ocelot/Configuration/Creator/RequestIdKeyCreator.cs +++ b/src/Ocelot/Configuration/Creator/RequestIdKeyCreator.cs @@ -1,18 +1,18 @@ -using Ocelot.Configuration.File; - -namespace Ocelot.Configuration.Creator -{ - public class RequestIdKeyCreator : IRequestIdKeyCreator - { - public string Create(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) - { - var reRouteId = !string.IsNullOrEmpty(fileReRoute.RequestIdKey); - - var requestIdKey = reRouteId - ? fileReRoute.RequestIdKey - : globalConfiguration.RequestIdKey; - - return requestIdKey; - } - } +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public class RequestIdKeyCreator : IRequestIdKeyCreator + { + public string Create(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) + { + var reRouteId = !string.IsNullOrEmpty(fileReRoute.RequestIdKey); + + var requestIdKey = reRouteId + ? fileReRoute.RequestIdKey + : globalConfiguration.RequestIdKey; + + return requestIdKey; + } + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/ServiceProviderConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/ServiceProviderConfigurationCreator.cs index 4980ae69f..cfc3e7c77 100644 --- a/src/Ocelot/Configuration/Creator/ServiceProviderConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/ServiceProviderConfigurationCreator.cs @@ -1,18 +1,18 @@ -using Ocelot.Configuration.Builder; -using Ocelot.Configuration.File; - -namespace Ocelot.Configuration.Creator -{ - public class ServiceProviderConfigurationCreator : IServiceProviderConfigurationCreator - { - public ServiceProviderConfiguration Create(FileGlobalConfiguration globalConfiguration) - { - var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0; - - return new ServiceProviderConfigurationBuilder() - .WithServiceDiscoveryProviderHost(globalConfiguration?.ServiceDiscoveryProvider?.Host) - .WithServiceDiscoveryProviderPort(serviceProviderPort) - .Build(); - } - } +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public class ServiceProviderConfigurationCreator : IServiceProviderConfigurationCreator + { + public ServiceProviderConfiguration Create(FileGlobalConfiguration globalConfiguration) + { + var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0; + + return new ServiceProviderConfigurationBuilder() + .WithServiceDiscoveryProviderHost(globalConfiguration?.ServiceDiscoveryProvider?.Host) + .WithServiceDiscoveryProviderPort(serviceProviderPort) + .Build(); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs b/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs index 7816d1188..cc6ebdc7d 100644 --- a/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs +++ b/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs @@ -1,76 +1,76 @@ -using System.Collections.Generic; -using Ocelot.Configuration.File; -using Ocelot.Values; - -namespace Ocelot.Configuration.Creator -{ - public class UpstreamTemplatePatternCreator : IUpstreamTemplatePatternCreator - { - private const string RegExMatchEverything = "[0-9a-zA-Z].*"; - private const string RegExMatchEndString = "$"; - private const string RegExIgnoreCase = "(?i)"; - private const string RegExForwardSlashOnly = "^/$"; - private const string RegExForwardSlashAndOnePlaceHolder = "^/.*"; - - public UpstreamPathTemplate Create(FileReRoute reRoute) - { - var upstreamTemplate = reRoute.UpstreamPathTemplate; - - var placeholders = new List(); - - for (var i = 0; i < upstreamTemplate.Length; i++) - { - if (IsPlaceHolder(upstreamTemplate, i)) - { - var postitionOfPlaceHolderClosingBracket = upstreamTemplate.IndexOf('}', i); - var difference = postitionOfPlaceHolderClosingBracket - i + 1; - var placeHolderName = upstreamTemplate.Substring(i, difference); - placeholders.Add(placeHolderName); - - //hack to handle /{url} case - if(ForwardSlashAndOnePlaceHolder(upstreamTemplate, placeholders, postitionOfPlaceHolderClosingBracket)) - { - return new UpstreamPathTemplate(RegExForwardSlashAndOnePlaceHolder, 0); - } - } - } - - foreach (var placeholder in placeholders) - { - upstreamTemplate = upstreamTemplate.Replace(placeholder, RegExMatchEverything); - } - - if (upstreamTemplate == "/") - { - return new UpstreamPathTemplate(RegExForwardSlashOnly, 1); - } - - if(upstreamTemplate.EndsWith("/")) - { - upstreamTemplate = upstreamTemplate.Remove(upstreamTemplate.Length -1, 1) + "(/|)"; - } - - var route = reRoute.ReRouteIsCaseSensitive - ? $"^{upstreamTemplate}{RegExMatchEndString}" - : $"^{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}"; - - return new UpstreamPathTemplate(route, 1); - } - - private bool ForwardSlashAndOnePlaceHolder(string upstreamTemplate, List placeholders, int postitionOfPlaceHolderClosingBracket) - { - if(upstreamTemplate.Substring(0, 2) == "/{" && placeholders.Count == 1 && upstreamTemplate.Length == postitionOfPlaceHolderClosingBracket + 1) - { - return true; - } - - return false; - } - - - private bool IsPlaceHolder(string upstreamTemplate, int i) - { - return upstreamTemplate[i] == '{'; - } - } +using System.Collections.Generic; +using Ocelot.Configuration.File; +using Ocelot.Values; + +namespace Ocelot.Configuration.Creator +{ + public class UpstreamTemplatePatternCreator : IUpstreamTemplatePatternCreator + { + private const string RegExMatchEverything = "[0-9a-zA-Z].*"; + private const string RegExMatchEndString = "$"; + private const string RegExIgnoreCase = "(?i)"; + private const string RegExForwardSlashOnly = "^/$"; + private const string RegExForwardSlashAndOnePlaceHolder = "^/.*"; + + public UpstreamPathTemplate Create(FileReRoute reRoute) + { + var upstreamTemplate = reRoute.UpstreamPathTemplate; + + var placeholders = new List(); + + for (var i = 0; i < upstreamTemplate.Length; i++) + { + if (IsPlaceHolder(upstreamTemplate, i)) + { + var postitionOfPlaceHolderClosingBracket = upstreamTemplate.IndexOf('}', i); + var difference = postitionOfPlaceHolderClosingBracket - i + 1; + var placeHolderName = upstreamTemplate.Substring(i, difference); + placeholders.Add(placeHolderName); + + //hack to handle /{url} case + if(ForwardSlashAndOnePlaceHolder(upstreamTemplate, placeholders, postitionOfPlaceHolderClosingBracket)) + { + return new UpstreamPathTemplate(RegExForwardSlashAndOnePlaceHolder, 0); + } + } + } + + foreach (var placeholder in placeholders) + { + upstreamTemplate = upstreamTemplate.Replace(placeholder, RegExMatchEverything); + } + + if (upstreamTemplate == "/") + { + return new UpstreamPathTemplate(RegExForwardSlashOnly, 1); + } + + if(upstreamTemplate.EndsWith("/")) + { + upstreamTemplate = upstreamTemplate.Remove(upstreamTemplate.Length -1, 1) + "(/|)"; + } + + var route = reRoute.ReRouteIsCaseSensitive + ? $"^{upstreamTemplate}{RegExMatchEndString}" + : $"^{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}"; + + return new UpstreamPathTemplate(route, 1); + } + + private bool ForwardSlashAndOnePlaceHolder(string upstreamTemplate, List placeholders, int postitionOfPlaceHolderClosingBracket) + { + if(upstreamTemplate.Substring(0, 2) == "/{" && placeholders.Count == 1 && upstreamTemplate.Length == postitionOfPlaceHolderClosingBracket + 1) + { + return true; + } + + return false; + } + + + private bool IsPlaceHolder(string upstreamTemplate, int i) + { + return upstreamTemplate[i] == '{'; + } + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/DownstreamHostAndPort.cs b/src/Ocelot/Configuration/DownstreamHostAndPort.cs new file mode 100644 index 000000000..483e75237 --- /dev/null +++ b/src/Ocelot/Configuration/DownstreamHostAndPort.cs @@ -0,0 +1,13 @@ +namespace Ocelot.Configuration +{ + public class DownstreamHostAndPort + { + public DownstreamHostAndPort(string host, int port) + { + Host = host; + Port = port; + } + public string Host { get; private set; } + public int Port { get; private set; } + } +} diff --git a/src/Ocelot/Configuration/File/FileAuthenticationOptions.cs b/src/Ocelot/Configuration/File/FileAuthenticationOptions.cs index 2b99dc566..70e376906 100644 --- a/src/Ocelot/Configuration/File/FileAuthenticationOptions.cs +++ b/src/Ocelot/Configuration/File/FileAuthenticationOptions.cs @@ -1,25 +1,25 @@ -using System.Collections.Generic; -using System.Text; - -namespace Ocelot.Configuration.File -{ - public class FileAuthenticationOptions - { - public FileAuthenticationOptions() - { - AllowedScopes = new List(); - } - - public string AuthenticationProviderKey {get; set;} - public List AllowedScopes { get; set; } - - public override string ToString() - { - var sb = new StringBuilder(); - sb.Append($"{nameof(AuthenticationProviderKey)}:{AuthenticationProviderKey},{nameof(AllowedScopes)}:["); - sb.AppendJoin(',', AllowedScopes); - sb.Append("]"); - return sb.ToString(); - } - } -} +using System.Collections.Generic; +using System.Text; + +namespace Ocelot.Configuration.File +{ + public class FileAuthenticationOptions + { + public FileAuthenticationOptions() + { + AllowedScopes = new List(); + } + + public string AuthenticationProviderKey {get; set;} + public List AllowedScopes { get; set; } + + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append($"{nameof(AuthenticationProviderKey)}:{AuthenticationProviderKey},{nameof(AllowedScopes)}:["); + sb.AppendJoin(',', AllowedScopes); + sb.Append("]"); + return sb.ToString(); + } + } +} diff --git a/src/Ocelot/Configuration/File/FileCacheOptions.cs b/src/Ocelot/Configuration/File/FileCacheOptions.cs index df9fb631d..9bca1f5b1 100644 --- a/src/Ocelot/Configuration/File/FileCacheOptions.cs +++ b/src/Ocelot/Configuration/File/FileCacheOptions.cs @@ -1,8 +1,8 @@ -namespace Ocelot.Configuration.File -{ - public class FileCacheOptions - { - public int TtlSeconds { get; set; } - public string Region {get; set;} - } -} +namespace Ocelot.Configuration.File +{ + public class FileCacheOptions + { + public int TtlSeconds { get; set; } + public string Region {get; set;} + } +} diff --git a/src/Ocelot/Configuration/File/FileConfiguration.cs b/src/Ocelot/Configuration/File/FileConfiguration.cs index 18938a0e0..2c4120aa2 100644 --- a/src/Ocelot/Configuration/File/FileConfiguration.cs +++ b/src/Ocelot/Configuration/File/FileConfiguration.cs @@ -1,16 +1,16 @@ -using System.Collections.Generic; - -namespace Ocelot.Configuration.File -{ - public class FileConfiguration - { - public FileConfiguration() - { - ReRoutes = new List(); - GlobalConfiguration = new FileGlobalConfiguration(); - } - - public List ReRoutes { get; set; } - public FileGlobalConfiguration GlobalConfiguration { get; set; } - } -} +using System.Collections.Generic; + +namespace Ocelot.Configuration.File +{ + public class FileConfiguration + { + public FileConfiguration() + { + ReRoutes = new List(); + GlobalConfiguration = new FileGlobalConfiguration(); + } + + public List ReRoutes { get; set; } + public FileGlobalConfiguration GlobalConfiguration { get; set; } + } +} diff --git a/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs b/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs index 4bb9e191f..5788d921d 100644 --- a/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs +++ b/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs @@ -1,18 +1,18 @@ - -namespace Ocelot.Configuration.File -{ - public class FileGlobalConfiguration - { - public FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider(); - RateLimitOptions = new FileRateLimitOptions(); - } - - public string RequestIdKey { get; set; } - - public FileServiceDiscoveryProvider ServiceDiscoveryProvider {get;set;} - - public FileRateLimitOptions RateLimitOptions { get; set; } - } -} + +namespace Ocelot.Configuration.File +{ + public class FileGlobalConfiguration + { + public FileGlobalConfiguration() + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider(); + RateLimitOptions = new FileRateLimitOptions(); + } + + public string RequestIdKey { get; set; } + + public FileServiceDiscoveryProvider ServiceDiscoveryProvider {get;set;} + + public FileRateLimitOptions RateLimitOptions { get; set; } + } +} diff --git a/src/Ocelot/Configuration/File/FileHostAndPort.cs b/src/Ocelot/Configuration/File/FileHostAndPort.cs new file mode 100644 index 000000000..67191873d --- /dev/null +++ b/src/Ocelot/Configuration/File/FileHostAndPort.cs @@ -0,0 +1,8 @@ +namespace Ocelot.Configuration.File +{ + public class FileHostAndPort + { + public string Host {get;set;} + public int Port { get; set; } + } +} diff --git a/src/Ocelot/Configuration/File/FileIdentityServerConfig.cs b/src/Ocelot/Configuration/File/FileIdentityServerConfig.cs index dfc023b2d..a07edab13 100644 --- a/src/Ocelot/Configuration/File/FileIdentityServerConfig.cs +++ b/src/Ocelot/Configuration/File/FileIdentityServerConfig.cs @@ -1,10 +1,10 @@ -namespace Ocelot.Configuration.File -{ - public class FileIdentityServerConfig - { - public string ProviderRootUrl { get; set; } - public string ApiName { get; set; } - public bool RequireHttps { get; set; } - public string ApiSecret { get; set; } - } +namespace Ocelot.Configuration.File +{ + public class FileIdentityServerConfig + { + public string ProviderRootUrl { get; set; } + public string ApiName { get; set; } + public bool RequireHttps { get; set; } + public string ApiSecret { get; set; } + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/File/FileJwtConfig.cs b/src/Ocelot/Configuration/File/FileJwtConfig.cs index a24522fc7..a028c6018 100644 --- a/src/Ocelot/Configuration/File/FileJwtConfig.cs +++ b/src/Ocelot/Configuration/File/FileJwtConfig.cs @@ -1,9 +1,9 @@ -namespace Ocelot.Configuration.File -{ - public class FileJwtConfig - { - public string Authority { get; set; } - - public string Audience { get; set; } - } +namespace Ocelot.Configuration.File +{ + public class FileJwtConfig + { + public string Authority { get; set; } + + public string Audience { get; set; } + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/File/FileQoSOptions.cs b/src/Ocelot/Configuration/File/FileQoSOptions.cs index 876a3fd53..aa842ced8 100644 --- a/src/Ocelot/Configuration/File/FileQoSOptions.cs +++ b/src/Ocelot/Configuration/File/FileQoSOptions.cs @@ -1,11 +1,11 @@ -namespace Ocelot.Configuration.File -{ - public class FileQoSOptions - { - public int ExceptionsAllowedBeforeBreaking { get; set; } - - public int DurationOfBreak { get; set; } - - public int TimeoutValue { get; set; } - } -} +namespace Ocelot.Configuration.File +{ + public class FileQoSOptions + { + public int ExceptionsAllowedBeforeBreaking { get; set; } + + public int DurationOfBreak { get; set; } + + public int TimeoutValue { get; set; } + } +} diff --git a/src/Ocelot/Configuration/File/FileRateLimitOptions.cs b/src/Ocelot/Configuration/File/FileRateLimitOptions.cs index afb3fab5e..b9277a3c8 100644 --- a/src/Ocelot/Configuration/File/FileRateLimitOptions.cs +++ b/src/Ocelot/Configuration/File/FileRateLimitOptions.cs @@ -1,39 +1,39 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Ocelot.Configuration.File -{ - public class FileRateLimitOptions - { - /// - /// Gets or sets the HTTP header that holds the client identifier, by default is X-ClientId - /// - public string ClientIdHeader { get; set; } = "ClientId"; - - /// - /// Gets or sets a value that will be used as a formatter for the QuotaExceeded response message. - /// If none specified the default will be: - /// API calls quota exceeded! maximum admitted {0} per {1} - /// - public string QuotaExceededMessage { get; set; } - - /// - /// Gets or sets the counter prefix, used to compose the rate limit counter cache key - /// - public string RateLimitCounterPrefix { get; set; } = "ocelot"; - - /// - /// Disables X-Rate-Limit and Rety-After headers - /// - public bool DisableRateLimitHeaders { get; set; } - - /// - /// Gets or sets the HTTP Status code returned when rate limiting occurs, by default value is set to 429 (Too Many Requests) - /// - public int HttpStatusCode { get; set; } = 429; - } - - -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.Configuration.File +{ + public class FileRateLimitOptions + { + /// + /// Gets or sets the HTTP header that holds the client identifier, by default is X-ClientId + /// + public string ClientIdHeader { get; set; } = "ClientId"; + + /// + /// Gets or sets a value that will be used as a formatter for the QuotaExceeded response message. + /// If none specified the default will be: + /// API calls quota exceeded! maximum admitted {0} per {1} + /// + public string QuotaExceededMessage { get; set; } + + /// + /// Gets or sets the counter prefix, used to compose the rate limit counter cache key + /// + public string RateLimitCounterPrefix { get; set; } = "ocelot"; + + /// + /// Disables X-Rate-Limit and Rety-After headers + /// + public bool DisableRateLimitHeaders { get; set; } + + /// + /// Gets or sets the HTTP Status code returned when rate limiting occurs, by default value is set to 429 (Too Many Requests) + /// + public int HttpStatusCode { get; set; } = 429; + } + + +} diff --git a/src/Ocelot/Configuration/File/FileRateLimitRule.cs b/src/Ocelot/Configuration/File/FileRateLimitRule.cs index 5a79c3c01..55346d2e8 100644 --- a/src/Ocelot/Configuration/File/FileRateLimitRule.cs +++ b/src/Ocelot/Configuration/File/FileRateLimitRule.cs @@ -1,50 +1,50 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Ocelot.Configuration.File -{ - - public class FileRateLimitRule - { - public FileRateLimitRule() - { - ClientWhitelist = new List(); - } - - public List ClientWhitelist { get; set; } - - /// - /// Enables endpoint rate limiting based URL path and HTTP verb - /// - public bool EnableRateLimiting { get; set; } - - /// - /// Rate limit period as in 1s, 1m, 1h - /// - public string Period { get; set; } - - public double PeriodTimespan { get; set; } - /// - /// Maximum number of requests that a client can make in a defined period - /// - public long Limit { get; set; } - - public override string ToString() - { - if (!EnableRateLimiting) - { - return string.Empty; - } - var sb = new StringBuilder(); - sb.Append( - $"{nameof(Period)}:{Period},{nameof(PeriodTimespan)}:{PeriodTimespan:F},{nameof(Limit)}:{Limit},{nameof(ClientWhitelist)}:["); - - sb.AppendJoin(',', ClientWhitelist); - sb.Append(']'); - return sb.ToString(); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ocelot.Configuration.File +{ + + public class FileRateLimitRule + { + public FileRateLimitRule() + { + ClientWhitelist = new List(); + } + + public List ClientWhitelist { get; set; } + + /// + /// Enables endpoint rate limiting based URL path and HTTP verb + /// + public bool EnableRateLimiting { get; set; } + + /// + /// Rate limit period as in 1s, 1m, 1h + /// + public string Period { get; set; } + + public double PeriodTimespan { get; set; } + /// + /// Maximum number of requests that a client can make in a defined period + /// + public long Limit { get; set; } + + public override string ToString() + { + if (!EnableRateLimiting) + { + return string.Empty; + } + var sb = new StringBuilder(); + sb.Append( + $"{nameof(Period)}:{Period},{nameof(PeriodTimespan)}:{PeriodTimespan:F},{nameof(Limit)}:{Limit},{nameof(ClientWhitelist)}:["); + + sb.AppendJoin(',', ClientWhitelist); + sb.Append(']'); + return sb.ToString(); + } + } +} diff --git a/src/Ocelot/Configuration/File/FileReRoute.cs b/src/Ocelot/Configuration/File/FileReRoute.cs index 4a34878e4..de5ad1d8a 100644 --- a/src/Ocelot/Configuration/File/FileReRoute.cs +++ b/src/Ocelot/Configuration/File/FileReRoute.cs @@ -1,46 +1,46 @@ -using System.Collections.Generic; - -namespace Ocelot.Configuration.File -{ - public class FileReRoute - { - public FileReRoute() - { - UpstreamHttpMethod = new List(); - AddHeadersToRequest = new Dictionary(); - AddClaimsToRequest = new Dictionary(); - RouteClaimsRequirement = new Dictionary(); - AddQueriesToRequest = new Dictionary(); - DownstreamHeaderTransform = new Dictionary(); - FileCacheOptions = new FileCacheOptions(); - QoSOptions = new FileQoSOptions(); - RateLimitOptions = new FileRateLimitRule(); - AuthenticationOptions = new FileAuthenticationOptions(); - HttpHandlerOptions = new FileHttpHandlerOptions(); - UpstreamHeaderTransform = new Dictionary(); - } - - public string DownstreamPathTemplate { get; set; } - public string UpstreamPathTemplate { get; set; } - public List UpstreamHttpMethod { get; set; } - public Dictionary AddHeadersToRequest { get; set; } - public Dictionary UpstreamHeaderTransform { get; set; } - public Dictionary DownstreamHeaderTransform { get; set; } - public Dictionary AddClaimsToRequest { get; set; } - public Dictionary RouteClaimsRequirement { get; set; } - public Dictionary AddQueriesToRequest { get; set; } - public string RequestIdKey { get; set; } - public FileCacheOptions FileCacheOptions { get; set; } - public bool ReRouteIsCaseSensitive { get; set; } - public string ServiceName { get; set; } - public string DownstreamScheme {get;set;} - public string DownstreamHost {get;set;} - public int DownstreamPort { get; set; } - public FileQoSOptions QoSOptions { get; set; } - public string LoadBalancer {get;set;} - public FileRateLimitRule RateLimitOptions { get; set; } - public FileAuthenticationOptions AuthenticationOptions { get; set; } - public FileHttpHandlerOptions HttpHandlerOptions { get; set; } - public bool UseServiceDiscovery {get;set;} - } -} \ No newline at end of file +using System.Collections.Generic; + +namespace Ocelot.Configuration.File +{ + public class FileReRoute + { + public FileReRoute() + { + UpstreamHttpMethod = new List(); + AddHeadersToRequest = new Dictionary(); + AddClaimsToRequest = new Dictionary(); + RouteClaimsRequirement = new Dictionary(); + AddQueriesToRequest = new Dictionary(); + DownstreamHeaderTransform = new Dictionary(); + FileCacheOptions = new FileCacheOptions(); + QoSOptions = new FileQoSOptions(); + RateLimitOptions = new FileRateLimitRule(); + AuthenticationOptions = new FileAuthenticationOptions(); + HttpHandlerOptions = new FileHttpHandlerOptions(); + UpstreamHeaderTransform = new Dictionary(); + DownstreamHostAndPorts = new List(); + } + + public string DownstreamPathTemplate { get; set; } + public string UpstreamPathTemplate { get; set; } + public List UpstreamHttpMethod { get; set; } + public Dictionary AddHeadersToRequest { get; set; } + public Dictionary UpstreamHeaderTransform { get; set; } + public Dictionary DownstreamHeaderTransform { get; set; } + public Dictionary AddClaimsToRequest { get; set; } + public Dictionary RouteClaimsRequirement { get; set; } + public Dictionary AddQueriesToRequest { get; set; } + public string RequestIdKey { get; set; } + public FileCacheOptions FileCacheOptions { get; set; } + public bool ReRouteIsCaseSensitive { get; set; } + public string ServiceName { get; set; } + public string DownstreamScheme {get;set;} + public FileQoSOptions QoSOptions { get; set; } + public string LoadBalancer {get;set;} + public FileRateLimitRule RateLimitOptions { get; set; } + public FileAuthenticationOptions AuthenticationOptions { get; set; } + public FileHttpHandlerOptions HttpHandlerOptions { get; set; } + public bool UseServiceDiscovery {get;set;} + public List DownstreamHostAndPorts {get;set;} + } +} diff --git a/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs b/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs index 37cac26f9..f9c370b16 100644 --- a/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs +++ b/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs @@ -1,8 +1,8 @@ -namespace Ocelot.Configuration.File -{ - public class FileServiceDiscoveryProvider - { - public string Host {get;set;} - public int Port { get; set; } - } +namespace Ocelot.Configuration.File +{ + public class FileServiceDiscoveryProvider + { + public string Host {get;set;} + public int Port { get; set; } + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/FileConfigurationController.cs b/src/Ocelot/Configuration/FileConfigurationController.cs index e17d9e5c1..7bdf49268 100644 --- a/src/Ocelot/Configuration/FileConfigurationController.cs +++ b/src/Ocelot/Configuration/FileConfigurationController.cs @@ -1,69 +1,69 @@ -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.DependencyInjection; -using Ocelot.Configuration.File; -using Ocelot.Configuration.Provider; -using Ocelot.Configuration.Setter; -using Ocelot.Raft; -using Rafty.Concensus; - -namespace Ocelot.Configuration -{ - [Authorize] - [Route("configuration")] - public class FileConfigurationController : Controller - { - private readonly IFileConfigurationProvider _configGetter; - private readonly IFileConfigurationSetter _configSetter; - private readonly IServiceProvider _serviceProvider; - - public FileConfigurationController(IFileConfigurationProvider getFileConfig, IFileConfigurationSetter configSetter, IServiceProvider serviceProvider) - { - _configGetter = getFileConfig; - _configSetter = configSetter; - _serviceProvider = serviceProvider; - } - - [HttpGet] - public async Task Get() - { - var response = await _configGetter.Get(); - - if(response.IsError) - { - return new BadRequestObjectResult(response.Errors); - } - - return new OkObjectResult(response.Data); - } - - [HttpPost] - public async Task Post([FromBody]FileConfiguration fileConfiguration) - { - //todo - this code is a bit shit sort it out.. - var test = _serviceProvider.GetService(typeof(INode)); - if (test != null) - { - var node = (INode)test; - var result = node.Accept(new UpdateFileConfiguration(fileConfiguration)); - if (result.GetType() == typeof(Rafty.Concensus.ErrorResponse)) - { - return new BadRequestObjectResult("There was a problem. This error message sucks raise an issue in GitHub."); - } - - return new OkObjectResult(result.Command.Configuration); - } - - var response = await _configSetter.Set(fileConfiguration); - - if (response.IsError) - { - return new BadRequestObjectResult(response.Errors); - } - - return new OkObjectResult(fileConfiguration); - } - } -} +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Ocelot.Configuration.File; +using Ocelot.Configuration.Provider; +using Ocelot.Configuration.Setter; +using Ocelot.Raft; +using Rafty.Concensus; + +namespace Ocelot.Configuration +{ + [Authorize] + [Route("configuration")] + public class FileConfigurationController : Controller + { + private readonly IFileConfigurationProvider _configGetter; + private readonly IFileConfigurationSetter _configSetter; + private readonly IServiceProvider _serviceProvider; + + public FileConfigurationController(IFileConfigurationProvider getFileConfig, IFileConfigurationSetter configSetter, IServiceProvider serviceProvider) + { + _configGetter = getFileConfig; + _configSetter = configSetter; + _serviceProvider = serviceProvider; + } + + [HttpGet] + public async Task Get() + { + var response = await _configGetter.Get(); + + if(response.IsError) + { + return new BadRequestObjectResult(response.Errors); + } + + return new OkObjectResult(response.Data); + } + + [HttpPost] + public async Task Post([FromBody]FileConfiguration fileConfiguration) + { + //todo - this code is a bit shit sort it out.. + var test = _serviceProvider.GetService(typeof(INode)); + if (test != null) + { + var node = (INode)test; + var result = node.Accept(new UpdateFileConfiguration(fileConfiguration)); + if (result.GetType() == typeof(Rafty.Concensus.ErrorResponse)) + { + return new BadRequestObjectResult("There was a problem. This error message sucks raise an issue in GitHub."); + } + + return new OkObjectResult(result.Command.Configuration); + } + + var response = await _configSetter.Set(fileConfiguration); + + if (response.IsError) + { + return new BadRequestObjectResult(response.Errors); + } + + return new OkObjectResult(fileConfiguration); + } + } +} diff --git a/src/Ocelot/Configuration/HeaderFindAndReplace.cs b/src/Ocelot/Configuration/HeaderFindAndReplace.cs index f3835415c..6b1636c20 100644 --- a/src/Ocelot/Configuration/HeaderFindAndReplace.cs +++ b/src/Ocelot/Configuration/HeaderFindAndReplace.cs @@ -1,20 +1,20 @@ -namespace Ocelot.Configuration -{ - public class HeaderFindAndReplace - { - public HeaderFindAndReplace(string key, string find, string replace, int index) - { - Key = key; - Find = find; - Replace = replace; - Index = index; - } - - public string Key {get;} - public string Find {get;} - public string Replace {get;} - - // only index 0 for now.. - public int Index {get;} - } +namespace Ocelot.Configuration +{ + public class HeaderFindAndReplace + { + public HeaderFindAndReplace(string key, string find, string replace, int index) + { + Key = key; + Find = find; + Replace = replace; + Index = index; + } + + public string Key {get;} + public string Find {get;} + public string Replace {get;} + + // only index 0 for now.. + public int Index {get;} + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/IOcelotConfiguration.cs b/src/Ocelot/Configuration/IOcelotConfiguration.cs index 5384630e4..fd808ae46 100644 --- a/src/Ocelot/Configuration/IOcelotConfiguration.cs +++ b/src/Ocelot/Configuration/IOcelotConfiguration.cs @@ -1,12 +1,12 @@ -using System.Collections.Generic; - -namespace Ocelot.Configuration -{ - public interface IOcelotConfiguration - { - List ReRoutes { get; } - string AdministrationPath {get;} - ServiceProviderConfiguration ServiceProviderConfiguration {get;} - string RequestId {get;} - } +using System.Collections.Generic; + +namespace Ocelot.Configuration +{ + public interface IOcelotConfiguration + { + List ReRoutes { get; } + string AdministrationPath {get;} + ServiceProviderConfiguration ServiceProviderConfiguration {get;} + string RequestId {get;} + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/OcelotConfiguration.cs b/src/Ocelot/Configuration/OcelotConfiguration.cs index 2c7e973ac..35f956ecf 100644 --- a/src/Ocelot/Configuration/OcelotConfiguration.cs +++ b/src/Ocelot/Configuration/OcelotConfiguration.cs @@ -1,20 +1,20 @@ -using System.Collections.Generic; - -namespace Ocelot.Configuration -{ - public class OcelotConfiguration : IOcelotConfiguration - { - public OcelotConfiguration(List reRoutes, string administrationPath, ServiceProviderConfiguration serviceProviderConfiguration, string requestId) - { - ReRoutes = reRoutes; - AdministrationPath = administrationPath; - ServiceProviderConfiguration = serviceProviderConfiguration; - RequestId = requestId; - } - - public List ReRoutes { get; } - public string AdministrationPath {get;} - public ServiceProviderConfiguration ServiceProviderConfiguration {get;} - public string RequestId {get;} - } +using System.Collections.Generic; + +namespace Ocelot.Configuration +{ + public class OcelotConfiguration : IOcelotConfiguration + { + public OcelotConfiguration(List reRoutes, string administrationPath, ServiceProviderConfiguration serviceProviderConfiguration, string requestId) + { + ReRoutes = reRoutes; + AdministrationPath = administrationPath; + ServiceProviderConfiguration = serviceProviderConfiguration; + RequestId = requestId; + } + + public List ReRoutes { get; } + public string AdministrationPath {get;} + public ServiceProviderConfiguration ServiceProviderConfiguration {get;} + public string RequestId {get;} + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Parser/ClaimToThingConfigurationParser.cs b/src/Ocelot/Configuration/Parser/ClaimToThingConfigurationParser.cs index 45dfe62d9..4d698e965 100644 --- a/src/Ocelot/Configuration/Parser/ClaimToThingConfigurationParser.cs +++ b/src/Ocelot/Configuration/Parser/ClaimToThingConfigurationParser.cs @@ -1,73 +1,73 @@ -using System; -using System.Collections.Generic; -using System.Text.RegularExpressions; -using Ocelot.Errors; -using Ocelot.Responses; - -namespace Ocelot.Configuration.Parser -{ - public class ClaimToThingConfigurationParser : IClaimToThingConfigurationParser - { - private readonly Regex _claimRegex = new Regex("Claims\\[.*\\]"); - private readonly Regex _indexRegex = new Regex("value\\[.*\\]"); - private const string SplitToken = ">"; - - public Response Extract(string existingKey, string value) - { - try - { - var instructions = value.Split(SplitToken.ToCharArray()); - - if (instructions.Length <= 1) - { - return new ErrorResponse( - new List - { - new NoInstructionsError(SplitToken) - }); - } - - var claimMatch = _claimRegex.IsMatch(instructions[0]); - - if (!claimMatch) - { - return new ErrorResponse( - new List - { - new InstructionNotForClaimsError() - }); - } - - var newKey = GetIndexValue(instructions[0]); - var index = 0; - var delimiter = string.Empty; - - if (instructions.Length > 2 && _indexRegex.IsMatch(instructions[1])) - { - index = int.Parse(GetIndexValue(instructions[1])); - delimiter = instructions[2].Trim(); - } - - return new OkResponse( - new ClaimToThing(existingKey, newKey, delimiter, index)); - } - catch (Exception exception) - { - return new ErrorResponse( - new List - { - new ParsingConfigurationHeaderError(exception) - }); - } - } - - private string GetIndexValue(string instruction) - { - var firstIndexer = instruction.IndexOf("[", StringComparison.Ordinal); - var lastIndexer = instruction.IndexOf("]", StringComparison.Ordinal); - var length = lastIndexer - firstIndexer; - var claimKey = instruction.Substring(firstIndexer + 1, length - 1); - return claimKey; - } - } -} +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using Ocelot.Errors; +using Ocelot.Responses; + +namespace Ocelot.Configuration.Parser +{ + public class ClaimToThingConfigurationParser : IClaimToThingConfigurationParser + { + private readonly Regex _claimRegex = new Regex("Claims\\[.*\\]"); + private readonly Regex _indexRegex = new Regex("value\\[.*\\]"); + private const string SplitToken = ">"; + + public Response Extract(string existingKey, string value) + { + try + { + var instructions = value.Split(SplitToken.ToCharArray()); + + if (instructions.Length <= 1) + { + return new ErrorResponse( + new List + { + new NoInstructionsError(SplitToken) + }); + } + + var claimMatch = _claimRegex.IsMatch(instructions[0]); + + if (!claimMatch) + { + return new ErrorResponse( + new List + { + new InstructionNotForClaimsError() + }); + } + + var newKey = GetIndexValue(instructions[0]); + var index = 0; + var delimiter = string.Empty; + + if (instructions.Length > 2 && _indexRegex.IsMatch(instructions[1])) + { + index = int.Parse(GetIndexValue(instructions[1])); + delimiter = instructions[2].Trim(); + } + + return new OkResponse( + new ClaimToThing(existingKey, newKey, delimiter, index)); + } + catch (Exception exception) + { + return new ErrorResponse( + new List + { + new ParsingConfigurationHeaderError(exception) + }); + } + } + + private string GetIndexValue(string instruction) + { + var firstIndexer = instruction.IndexOf("[", StringComparison.Ordinal); + var lastIndexer = instruction.IndexOf("]", StringComparison.Ordinal); + var length = lastIndexer - firstIndexer; + var claimKey = instruction.Substring(firstIndexer + 1, length - 1); + return claimKey; + } + } +} diff --git a/src/Ocelot/Configuration/Parser/IClaimToThingConfigurationParser.cs b/src/Ocelot/Configuration/Parser/IClaimToThingConfigurationParser.cs index a27129943..3af0a32e9 100644 --- a/src/Ocelot/Configuration/Parser/IClaimToThingConfigurationParser.cs +++ b/src/Ocelot/Configuration/Parser/IClaimToThingConfigurationParser.cs @@ -1,9 +1,9 @@ -using Ocelot.Responses; - -namespace Ocelot.Configuration.Parser -{ - public interface IClaimToThingConfigurationParser - { - Response Extract(string existingKey, string value); - } +using Ocelot.Responses; + +namespace Ocelot.Configuration.Parser +{ + public interface IClaimToThingConfigurationParser + { + Response Extract(string existingKey, string value); + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Parser/InstructionNotForClaimsError.cs b/src/Ocelot/Configuration/Parser/InstructionNotForClaimsError.cs index 62cf87412..50dd88203 100644 --- a/src/Ocelot/Configuration/Parser/InstructionNotForClaimsError.cs +++ b/src/Ocelot/Configuration/Parser/InstructionNotForClaimsError.cs @@ -1,12 +1,12 @@ -using Ocelot.Errors; - -namespace Ocelot.Configuration.Parser -{ - public class InstructionNotForClaimsError : Error - { - public InstructionNotForClaimsError() - : base("instructions did not contain claims, at the moment we only support claims extraction", OcelotErrorCode.InstructionNotForClaimsError) - { - } - } -} +using Ocelot.Errors; + +namespace Ocelot.Configuration.Parser +{ + public class InstructionNotForClaimsError : Error + { + public InstructionNotForClaimsError() + : base("instructions did not contain claims, at the moment we only support claims extraction", OcelotErrorCode.InstructionNotForClaimsError) + { + } + } +} diff --git a/src/Ocelot/Configuration/Parser/NoInstructionsError.cs b/src/Ocelot/Configuration/Parser/NoInstructionsError.cs index 09aa0ccb9..8c7eafc7d 100644 --- a/src/Ocelot/Configuration/Parser/NoInstructionsError.cs +++ b/src/Ocelot/Configuration/Parser/NoInstructionsError.cs @@ -1,12 +1,12 @@ -using Ocelot.Errors; - -namespace Ocelot.Configuration.Parser -{ - public class NoInstructionsError : Error - { - public NoInstructionsError(string splitToken) - : base($"There we no instructions splitting on {splitToken}", OcelotErrorCode.NoInstructionsError) - { - } - } -} +using Ocelot.Errors; + +namespace Ocelot.Configuration.Parser +{ + public class NoInstructionsError : Error + { + public NoInstructionsError(string splitToken) + : base($"There we no instructions splitting on {splitToken}", OcelotErrorCode.NoInstructionsError) + { + } + } +} diff --git a/src/Ocelot/Configuration/Parser/ParsingConfigurationHeaderError.cs b/src/Ocelot/Configuration/Parser/ParsingConfigurationHeaderError.cs index 9b03c95da..1f3eb627d 100644 --- a/src/Ocelot/Configuration/Parser/ParsingConfigurationHeaderError.cs +++ b/src/Ocelot/Configuration/Parser/ParsingConfigurationHeaderError.cs @@ -1,13 +1,13 @@ -using System; -using Ocelot.Errors; - -namespace Ocelot.Configuration.Parser -{ - public class ParsingConfigurationHeaderError : Error - { - public ParsingConfigurationHeaderError(Exception exception) - : base($"error parsing configuration eception is {exception.Message}", OcelotErrorCode.ParsingConfigurationHeaderError) - { - } - } -} +using System; +using Ocelot.Errors; + +namespace Ocelot.Configuration.Parser +{ + public class ParsingConfigurationHeaderError : Error + { + public ParsingConfigurationHeaderError(Exception exception) + : base($"error parsing configuration eception is {exception.Message}", OcelotErrorCode.ParsingConfigurationHeaderError) + { + } + } +} diff --git a/src/Ocelot/Configuration/Provider/FileConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/FileConfigurationProvider.cs index 17f46531c..fdcd949b9 100644 --- a/src/Ocelot/Configuration/Provider/FileConfigurationProvider.cs +++ b/src/Ocelot/Configuration/Provider/FileConfigurationProvider.cs @@ -1,26 +1,26 @@ -using System; -using System.IO; -using System.Threading.Tasks; -using Newtonsoft.Json; -using Ocelot.Configuration.File; -using Ocelot.Configuration.Repository; -using Ocelot.Responses; - -namespace Ocelot.Configuration.Provider -{ - public class FileConfigurationProvider : IFileConfigurationProvider - { - private IFileConfigurationRepository _repo; - - public FileConfigurationProvider(IFileConfigurationRepository repo) - { - _repo = repo; - } - - public async Task> Get() - { - var fileConfig = await _repo.Get(); - return new OkResponse(fileConfig.Data); - } - } +using System; +using System.IO; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Ocelot.Configuration.File; +using Ocelot.Configuration.Repository; +using Ocelot.Responses; + +namespace Ocelot.Configuration.Provider +{ + public class FileConfigurationProvider : IFileConfigurationProvider + { + private IFileConfigurationRepository _repo; + + public FileConfigurationProvider(IFileConfigurationRepository repo) + { + _repo = repo; + } + + public async Task> Get() + { + var fileConfig = await _repo.Get(); + return new OkResponse(fileConfig.Data); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Provider/IFileConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/IFileConfigurationProvider.cs index 39afedba4..c2ab51cbc 100644 --- a/src/Ocelot/Configuration/Provider/IFileConfigurationProvider.cs +++ b/src/Ocelot/Configuration/Provider/IFileConfigurationProvider.cs @@ -1,11 +1,11 @@ -using System.Threading.Tasks; -using Ocelot.Configuration.File; -using Ocelot.Responses; - -namespace Ocelot.Configuration.Provider -{ - public interface IFileConfigurationProvider - { - Task> Get(); - } +using System.Threading.Tasks; +using Ocelot.Configuration.File; +using Ocelot.Responses; + +namespace Ocelot.Configuration.Provider +{ + public interface IFileConfigurationProvider + { + Task> Get(); + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Provider/IIdentityServerConfiguration.cs b/src/Ocelot/Configuration/Provider/IIdentityServerConfiguration.cs index a01ed751b..8a76eb9f2 100644 --- a/src/Ocelot/Configuration/Provider/IIdentityServerConfiguration.cs +++ b/src/Ocelot/Configuration/Provider/IIdentityServerConfiguration.cs @@ -1,16 +1,16 @@ -using System.Collections.Generic; -using IdentityServer4.AccessTokenValidation; -using IdentityServer4.Models; - -namespace Ocelot.Configuration.Provider -{ - public interface IIdentityServerConfiguration - { - string ApiName { get; } - string ApiSecret { get; } - bool RequireHttps { get; } - List AllowedScopes { get; } - string CredentialsSigningCertificateLocation { get; } - string CredentialsSigningCertificatePassword { get; } - } +using System.Collections.Generic; +using IdentityServer4.AccessTokenValidation; +using IdentityServer4.Models; + +namespace Ocelot.Configuration.Provider +{ + public interface IIdentityServerConfiguration + { + string ApiName { get; } + string ApiSecret { get; } + bool RequireHttps { get; } + List AllowedScopes { get; } + string CredentialsSigningCertificateLocation { get; } + string CredentialsSigningCertificatePassword { get; } + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs index 9b89bcea7..80f4583b7 100644 --- a/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs +++ b/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs @@ -1,11 +1,11 @@ -using System.Threading.Tasks; -using Ocelot.Configuration.File; -using Ocelot.Responses; - -namespace Ocelot.Configuration.Provider -{ - public interface IOcelotConfigurationProvider - { - Task> Get(); - } -} +using System.Threading.Tasks; +using Ocelot.Configuration.File; +using Ocelot.Responses; + +namespace Ocelot.Configuration.Provider +{ + public interface IOcelotConfigurationProvider + { + Task> Get(); + } +} diff --git a/src/Ocelot/Configuration/Provider/IdentityServerConfiguration.cs b/src/Ocelot/Configuration/Provider/IdentityServerConfiguration.cs index 6f62e53cf..795e6994e 100644 --- a/src/Ocelot/Configuration/Provider/IdentityServerConfiguration.cs +++ b/src/Ocelot/Configuration/Provider/IdentityServerConfiguration.cs @@ -1,32 +1,32 @@ -using System.Collections.Generic; -using IdentityServer4.AccessTokenValidation; -using IdentityServer4.Models; - -namespace Ocelot.Configuration.Provider -{ - public class IdentityServerConfiguration : IIdentityServerConfiguration - { - public IdentityServerConfiguration( - string apiName, - bool requireHttps, - string apiSecret, - List allowedScopes, - string credentialsSigningCertificateLocation, - string credentialsSigningCertificatePassword) - { - ApiName = apiName; - RequireHttps = requireHttps; - ApiSecret = apiSecret; - AllowedScopes = allowedScopes; - CredentialsSigningCertificateLocation = credentialsSigningCertificateLocation; - CredentialsSigningCertificatePassword = credentialsSigningCertificatePassword; - } - - public string ApiName { get; private set; } - public bool RequireHttps { get; private set; } - public List AllowedScopes { get; private set; } - public string ApiSecret { get; private set; } - public string CredentialsSigningCertificateLocation { get; private set; } - public string CredentialsSigningCertificatePassword { get; private set; } - } +using System.Collections.Generic; +using IdentityServer4.AccessTokenValidation; +using IdentityServer4.Models; + +namespace Ocelot.Configuration.Provider +{ + public class IdentityServerConfiguration : IIdentityServerConfiguration + { + public IdentityServerConfiguration( + string apiName, + bool requireHttps, + string apiSecret, + List allowedScopes, + string credentialsSigningCertificateLocation, + string credentialsSigningCertificatePassword) + { + ApiName = apiName; + RequireHttps = requireHttps; + ApiSecret = apiSecret; + AllowedScopes = allowedScopes; + CredentialsSigningCertificateLocation = credentialsSigningCertificateLocation; + CredentialsSigningCertificatePassword = credentialsSigningCertificatePassword; + } + + public string ApiName { get; private set; } + public bool RequireHttps { get; private set; } + public List AllowedScopes { get; private set; } + public string ApiSecret { get; private set; } + public string CredentialsSigningCertificateLocation { get; private set; } + public string CredentialsSigningCertificatePassword { get; private set; } + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs index 31ecb8ac2..ed72a60e8 100644 --- a/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs +++ b/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs @@ -1,32 +1,32 @@ -using System.Threading.Tasks; -using Ocelot.Configuration.File; -using Ocelot.Configuration.Repository; -using Ocelot.Responses; - -namespace Ocelot.Configuration.Provider -{ - /// - /// Register as singleton - /// - public class OcelotConfigurationProvider : IOcelotConfigurationProvider - { - private readonly IOcelotConfigurationRepository _config; - - public OcelotConfigurationProvider(IOcelotConfigurationRepository repo) - { - _config = repo; - } - - public async Task> Get() - { - var repoConfig = await _config.Get(); - - if (repoConfig.IsError) - { - return new ErrorResponse(repoConfig.Errors); - } - - return new OkResponse(repoConfig.Data); - } - } +using System.Threading.Tasks; +using Ocelot.Configuration.File; +using Ocelot.Configuration.Repository; +using Ocelot.Responses; + +namespace Ocelot.Configuration.Provider +{ + /// + /// Register as singleton + /// + public class OcelotConfigurationProvider : IOcelotConfigurationProvider + { + private readonly IOcelotConfigurationRepository _config; + + public OcelotConfigurationProvider(IOcelotConfigurationRepository repo) + { + _config = repo; + } + + public async Task> Get() + { + var repoConfig = await _config.Get(); + + if (repoConfig.IsError) + { + return new ErrorResponse(repoConfig.Errors); + } + + return new OkResponse(repoConfig.Data); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/QoSOptions.cs b/src/Ocelot/Configuration/QoSOptions.cs index 1c519632a..c518cfecc 100644 --- a/src/Ocelot/Configuration/QoSOptions.cs +++ b/src/Ocelot/Configuration/QoSOptions.cs @@ -1,29 +1,29 @@ -using Polly.Timeout; - -namespace Ocelot.Configuration -{ - public class QoSOptions - { - public QoSOptions( - int exceptionsAllowedBeforeBreaking, - int durationofBreak, - int timeoutValue, - TimeoutStrategy timeoutStrategy = TimeoutStrategy.Pessimistic) - { - ExceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking; - DurationOfBreak = durationofBreak; - TimeoutValue = timeoutValue; - TimeoutStrategy = timeoutStrategy; - } - - - public int ExceptionsAllowedBeforeBreaking { get; private set; } - - public int DurationOfBreak { get; private set; } - - public int TimeoutValue { get; private set; } - - public TimeoutStrategy TimeoutStrategy { get; private set; } - - } -} +using Polly.Timeout; + +namespace Ocelot.Configuration +{ + public class QoSOptions + { + public QoSOptions( + int exceptionsAllowedBeforeBreaking, + int durationofBreak, + int timeoutValue, + TimeoutStrategy timeoutStrategy = TimeoutStrategy.Pessimistic) + { + ExceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking; + DurationOfBreak = durationofBreak; + TimeoutValue = timeoutValue; + TimeoutStrategy = timeoutStrategy; + } + + + public int ExceptionsAllowedBeforeBreaking { get; private set; } + + public int DurationOfBreak { get; private set; } + + public int TimeoutValue { get; private set; } + + public TimeoutStrategy TimeoutStrategy { get; private set; } + + } +} diff --git a/src/Ocelot/Configuration/RateLimitOptions.cs b/src/Ocelot/Configuration/RateLimitOptions.cs index fdf54d526..722af4cd7 100644 --- a/src/Ocelot/Configuration/RateLimitOptions.cs +++ b/src/Ocelot/Configuration/RateLimitOptions.cs @@ -1,61 +1,61 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Ocelot.Configuration -{ - /// - /// RateLimit Options - /// - public class RateLimitOptions - { - public RateLimitOptions(bool enbleRateLimiting, string clientIdHeader, List clientWhitelist,bool disableRateLimitHeaders, - string quotaExceededMessage, string rateLimitCounterPrefix, RateLimitRule rateLimitRule, int httpStatusCode) - { - EnableRateLimiting = enbleRateLimiting; - ClientIdHeader = clientIdHeader; - ClientWhitelist = clientWhitelist?? new List(); - DisableRateLimitHeaders = disableRateLimitHeaders; - QuotaExceededMessage = quotaExceededMessage; - RateLimitCounterPrefix = rateLimitCounterPrefix; - RateLimitRule = rateLimitRule; - HttpStatusCode = httpStatusCode; - } - - public RateLimitRule RateLimitRule { get; private set; } - - public List ClientWhitelist { get; private set; } - - /// - /// Gets or sets the HTTP header that holds the client identifier, by default is X-ClientId - /// - public string ClientIdHeader { get; private set; } - - /// - /// Gets or sets the HTTP Status code returned when rate limiting occurs, by default value is set to 429 (Too Many Requests) - /// - public int HttpStatusCode { get; private set; } - - /// - /// Gets or sets a value that will be used as a formatter for the QuotaExceeded response message. - /// If none specified the default will be: - /// API calls quota exceeded! maximum admitted {0} per {1} - /// - public string QuotaExceededMessage { get; private set; } - - /// - /// Gets or sets the counter prefix, used to compose the rate limit counter cache key - /// - public string RateLimitCounterPrefix { get; private set; } - - /// - /// Enables endpoint rate limiting based URL path and HTTP verb - /// - public bool EnableRateLimiting { get; private set; } - - /// - /// Disables X-Rate-Limit and Rety-After headers - /// - public bool DisableRateLimitHeaders { get; private set; } - } -} +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.Configuration +{ + /// + /// RateLimit Options + /// + public class RateLimitOptions + { + public RateLimitOptions(bool enbleRateLimiting, string clientIdHeader, List clientWhitelist,bool disableRateLimitHeaders, + string quotaExceededMessage, string rateLimitCounterPrefix, RateLimitRule rateLimitRule, int httpStatusCode) + { + EnableRateLimiting = enbleRateLimiting; + ClientIdHeader = clientIdHeader; + ClientWhitelist = clientWhitelist?? new List(); + DisableRateLimitHeaders = disableRateLimitHeaders; + QuotaExceededMessage = quotaExceededMessage; + RateLimitCounterPrefix = rateLimitCounterPrefix; + RateLimitRule = rateLimitRule; + HttpStatusCode = httpStatusCode; + } + + public RateLimitRule RateLimitRule { get; private set; } + + public List ClientWhitelist { get; private set; } + + /// + /// Gets or sets the HTTP header that holds the client identifier, by default is X-ClientId + /// + public string ClientIdHeader { get; private set; } + + /// + /// Gets or sets the HTTP Status code returned when rate limiting occurs, by default value is set to 429 (Too Many Requests) + /// + public int HttpStatusCode { get; private set; } + + /// + /// Gets or sets a value that will be used as a formatter for the QuotaExceeded response message. + /// If none specified the default will be: + /// API calls quota exceeded! maximum admitted {0} per {1} + /// + public string QuotaExceededMessage { get; private set; } + + /// + /// Gets or sets the counter prefix, used to compose the rate limit counter cache key + /// + public string RateLimitCounterPrefix { get; private set; } + + /// + /// Enables endpoint rate limiting based URL path and HTTP verb + /// + public bool EnableRateLimiting { get; private set; } + + /// + /// Disables X-Rate-Limit and Rety-After headers + /// + public bool DisableRateLimitHeaders { get; private set; } + } +} diff --git a/src/Ocelot/Configuration/RateLimitRule.cs b/src/Ocelot/Configuration/RateLimitRule.cs index d4acfa314..d77919aec 100644 --- a/src/Ocelot/Configuration/RateLimitRule.cs +++ b/src/Ocelot/Configuration/RateLimitRule.cs @@ -1,25 +1,25 @@ -using System; - -namespace Ocelot.Configuration -{ - public class RateLimitRule - { - public RateLimitRule(string period, double periodTimespan, long limit) - { - Period = period; - PeriodTimespan = periodTimespan; - Limit = limit; - } - - /// - /// Rate limit period as in 1s, 1m, 1h,1d - /// - public string Period { get; private set; } - - public double PeriodTimespan { get; private set; } - /// - /// Maximum number of requests that a client can make in a defined period - /// - public long Limit { get; private set; } - } +using System; + +namespace Ocelot.Configuration +{ + public class RateLimitRule + { + public RateLimitRule(string period, double periodTimespan, long limit) + { + Period = period; + PeriodTimespan = periodTimespan; + Limit = limit; + } + + /// + /// Rate limit period as in 1s, 1m, 1h,1d + /// + public string Period { get; private set; } + + public double PeriodTimespan { get; private set; } + /// + /// Maximum number of requests that a client can make in a defined period + /// + public long Limit { get; private set; } + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index 9efe6dbc6..be69fd240 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -1,102 +1,99 @@ -using System.Collections.Generic; -using System.Net.Http; -using Ocelot.Configuration.Creator; -using Ocelot.Values; - -namespace Ocelot.Configuration -{ - public class ReRoute - { - public ReRoute(PathTemplate downstreamPathTemplate, - PathTemplate upstreamPathTemplate, - List upstreamHttpMethod, - UpstreamPathTemplate upstreamTemplatePattern, - bool isAuthenticated, - AuthenticationOptions authenticationOptions, - List claimsToHeaders, - List claimsToClaims, - Dictionary routeClaimsRequirement, - bool isAuthorised, - List claimsToQueries, - string requestIdKey, - bool isCached, - CacheOptions cacheOptions, - string downstreamScheme, - string loadBalancer, - string downstreamHost, - int downstreamPort, - string reRouteKey, - bool isQos, - QoSOptions qosOptions, - bool enableEndpointRateLimiting, - RateLimitOptions ratelimitOptions, - HttpHandlerOptions httpHandlerOptions, - bool useServiceDiscovery, - string serviceName, - List upstreamHeadersFindAndReplace, - List downstreamHeadersFindAndReplace) - { - DownstreamHeadersFindAndReplace = downstreamHeadersFindAndReplace; - UpstreamHeadersFindAndReplace = upstreamHeadersFindAndReplace; - ServiceName = serviceName; - UseServiceDiscovery = useServiceDiscovery; - ReRouteKey = reRouteKey; - LoadBalancer = loadBalancer; - DownstreamHost = downstreamHost; - DownstreamPort = downstreamPort; - DownstreamPathTemplate = downstreamPathTemplate; - UpstreamPathTemplate = upstreamPathTemplate; - UpstreamHttpMethod = upstreamHttpMethod; - UpstreamTemplatePattern = upstreamTemplatePattern; - IsAuthenticated = isAuthenticated; - AuthenticationOptions = authenticationOptions; - RouteClaimsRequirement = routeClaimsRequirement; - IsAuthorised = isAuthorised; - RequestIdKey = requestIdKey; - IsCached = isCached; - CacheOptions = cacheOptions; - ClaimsToQueries = claimsToQueries - ?? new List(); - ClaimsToClaims = claimsToClaims - ?? new List(); - ClaimsToHeaders = claimsToHeaders - ?? new List(); - DownstreamScheme = downstreamScheme; - IsQos = isQos; - QosOptionsOptions = qosOptions; - EnableEndpointEndpointRateLimiting = enableEndpointRateLimiting; - RateLimitOptions = ratelimitOptions; - HttpHandlerOptions = httpHandlerOptions; - } - - public string ReRouteKey {get;private set;} - public PathTemplate DownstreamPathTemplate { get; private set; } - public PathTemplate UpstreamPathTemplate { get; private set; } - public UpstreamPathTemplate UpstreamTemplatePattern { get; private set; } - public List UpstreamHttpMethod { get; private set; } - public bool IsAuthenticated { get; private set; } - public bool IsAuthorised { get; private set; } - public AuthenticationOptions AuthenticationOptions { get; private set; } - public List ClaimsToQueries { get; private set; } - public List ClaimsToHeaders { get; private set; } - public List ClaimsToClaims { get; private set; } - public Dictionary RouteClaimsRequirement { get; private set; } - public string RequestIdKey { get; private set; } - public bool IsCached { get; private set; } - public CacheOptions CacheOptions { get; private set; } - public string DownstreamScheme {get;private set;} - public bool IsQos { get; private set; } - public QoSOptions QosOptionsOptions { get; private set; } - public string LoadBalancer {get;private set;} - public string DownstreamHost { get; private set; } - public int DownstreamPort { get; private set; } - public bool EnableEndpointEndpointRateLimiting { get; private set; } - public RateLimitOptions RateLimitOptions { get; private set; } - public HttpHandlerOptions HttpHandlerOptions { get; private set; } - public bool UseServiceDiscovery {get;private set;} - public string ServiceName {get;private set;} - public List UpstreamHeadersFindAndReplace {get;private set;} - public List DownstreamHeadersFindAndReplace {get;private set;} - - } -} \ No newline at end of file +using System.Collections.Generic; +using System.Net.Http; +using Ocelot.Configuration.Creator; +using Ocelot.Values; + +namespace Ocelot.Configuration +{ + public class ReRoute + { + public ReRoute(PathTemplate downstreamPathTemplate, + PathTemplate upstreamPathTemplate, + List upstreamHttpMethod, + UpstreamPathTemplate upstreamTemplatePattern, + bool isAuthenticated, + AuthenticationOptions authenticationOptions, + List claimsToHeaders, + List claimsToClaims, + Dictionary routeClaimsRequirement, + bool isAuthorised, + List claimsToQueries, + string requestIdKey, + bool isCached, + CacheOptions cacheOptions, + string downstreamScheme, + string loadBalancer, + string reRouteKey, + bool isQos, + QoSOptions qosOptions, + bool enableEndpointRateLimiting, + RateLimitOptions ratelimitOptions, + HttpHandlerOptions httpHandlerOptions, + bool useServiceDiscovery, + string serviceName, + List upstreamHeadersFindAndReplace, + List downstreamHeadersFindAndReplace, + List downstreamAddresses) + { + DownstreamHeadersFindAndReplace = downstreamHeadersFindAndReplace; + UpstreamHeadersFindAndReplace = upstreamHeadersFindAndReplace; + ServiceName = serviceName; + UseServiceDiscovery = useServiceDiscovery; + ReRouteKey = reRouteKey; + LoadBalancer = loadBalancer; + DownstreamAddresses = downstreamAddresses; + DownstreamPathTemplate = downstreamPathTemplate; + UpstreamPathTemplate = upstreamPathTemplate; + UpstreamHttpMethod = upstreamHttpMethod; + UpstreamTemplatePattern = upstreamTemplatePattern; + IsAuthenticated = isAuthenticated; + AuthenticationOptions = authenticationOptions; + RouteClaimsRequirement = routeClaimsRequirement; + IsAuthorised = isAuthorised; + RequestIdKey = requestIdKey; + IsCached = isCached; + CacheOptions = cacheOptions; + ClaimsToQueries = claimsToQueries + ?? new List(); + ClaimsToClaims = claimsToClaims + ?? new List(); + ClaimsToHeaders = claimsToHeaders + ?? new List(); + DownstreamScheme = downstreamScheme; + IsQos = isQos; + QosOptionsOptions = qosOptions; + EnableEndpointEndpointRateLimiting = enableEndpointRateLimiting; + RateLimitOptions = ratelimitOptions; + HttpHandlerOptions = httpHandlerOptions; + } + + public string ReRouteKey {get;private set;} + public PathTemplate DownstreamPathTemplate { get; private set; } + public PathTemplate UpstreamPathTemplate { get; private set; } + public UpstreamPathTemplate UpstreamTemplatePattern { get; private set; } + public List UpstreamHttpMethod { get; private set; } + public bool IsAuthenticated { get; private set; } + public bool IsAuthorised { get; private set; } + public AuthenticationOptions AuthenticationOptions { get; private set; } + public List ClaimsToQueries { get; private set; } + public List ClaimsToHeaders { get; private set; } + public List ClaimsToClaims { get; private set; } + public Dictionary RouteClaimsRequirement { get; private set; } + public string RequestIdKey { get; private set; } + public bool IsCached { get; private set; } + public CacheOptions CacheOptions { get; private set; } + public string DownstreamScheme {get;private set;} + public bool IsQos { get; private set; } + public QoSOptions QosOptionsOptions { get; private set; } + public string LoadBalancer {get;private set;} + public bool EnableEndpointEndpointRateLimiting { get; private set; } + public RateLimitOptions RateLimitOptions { get; private set; } + public HttpHandlerOptions HttpHandlerOptions { get; private set; } + public bool UseServiceDiscovery {get;private set;} + public string ServiceName {get;private set;} + public List UpstreamHeadersFindAndReplace {get;private set;} + public List DownstreamHeadersFindAndReplace {get;private set;} + public List DownstreamAddresses {get;private set;} + + } +} diff --git a/src/Ocelot/Configuration/ReRouteOptions.cs b/src/Ocelot/Configuration/ReRouteOptions.cs index 2e42f1615..5ec316c3a 100644 --- a/src/Ocelot/Configuration/ReRouteOptions.cs +++ b/src/Ocelot/Configuration/ReRouteOptions.cs @@ -1,20 +1,20 @@ -namespace Ocelot.Configuration -{ - public class ReRouteOptions - { - public ReRouteOptions(bool isAuthenticated, bool isAuthorised, bool isCached, bool isQos, bool isEnableRateLimiting) - { - IsAuthenticated = isAuthenticated; - IsAuthorised = isAuthorised; - IsCached = isCached; - IsQos = isQos; - EnableRateLimiting = isEnableRateLimiting; - - } - public bool IsAuthenticated { get; private set; } - public bool IsAuthorised { get; private set; } - public bool IsCached { get; private set; } - public bool IsQos { get; private set; } - public bool EnableRateLimiting { get; private set; } - } +namespace Ocelot.Configuration +{ + public class ReRouteOptions + { + public ReRouteOptions(bool isAuthenticated, bool isAuthorised, bool isCached, bool isQos, bool isEnableRateLimiting) + { + IsAuthenticated = isAuthenticated; + IsAuthorised = isAuthorised; + IsCached = isCached; + IsQos = isQos; + EnableRateLimiting = isEnableRateLimiting; + + } + public bool IsAuthenticated { get; private set; } + public bool IsAuthorised { get; private set; } + public bool IsCached { get; private set; } + public bool IsQos { get; private set; } + public bool EnableRateLimiting { get; private set; } + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationPoller.cs b/src/Ocelot/Configuration/Repository/ConsulFileConfigurationPoller.cs index 503148020..8274af2fa 100644 --- a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationPoller.cs +++ b/src/Ocelot/Configuration/Repository/ConsulFileConfigurationPoller.cs @@ -1,80 +1,80 @@ -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Newtonsoft.Json; -using Ocelot.Configuration.File; -using Ocelot.Configuration.Setter; -using Ocelot.Logging; - -namespace Ocelot.Configuration.Repository -{ - public class ConsulFileConfigurationPoller : IDisposable - { - private IOcelotLogger _logger; - private IFileConfigurationRepository _repo; - private IFileConfigurationSetter _setter; - private string _previousAsJson; - private Timer _timer; - private bool _polling; - - public ConsulFileConfigurationPoller(IOcelotLoggerFactory factory, IFileConfigurationRepository repo, IFileConfigurationSetter setter) - { - _setter = setter; - _logger = factory.CreateLogger(); - _repo = repo; - _previousAsJson = ""; - _timer = new Timer(async x => - { - if(_polling) - { - return; - } - - _polling = true; - await Poll(); - _polling = false; - - }, null, 0, 1000); - } - - private async Task Poll() - { - _logger.LogDebug("Started polling consul"); - - var fileConfig = await _repo.Get(); - - if(fileConfig.IsError) - { - _logger.LogDebug($"error geting file config, errors are {string.Join(",", fileConfig.Errors.Select(x => x.Message))}"); - return; - } - - var asJson = ToJson(fileConfig.Data); - - if(!fileConfig.IsError && asJson != _previousAsJson) - { - await _setter.Set(fileConfig.Data); - _previousAsJson = asJson; - } - - _logger.LogDebug("Finished polling consul"); - } - - /// - /// We could do object comparison here but performance isnt really a problem. This might be an issue one day! - /// - /// - /// - private string ToJson(FileConfiguration config) - { - var currentHash = JsonConvert.SerializeObject(config); - return currentHash; - } - - public void Dispose() - { - _timer.Dispose(); - } - } +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Ocelot.Configuration.File; +using Ocelot.Configuration.Setter; +using Ocelot.Logging; + +namespace Ocelot.Configuration.Repository +{ + public class ConsulFileConfigurationPoller : IDisposable + { + private IOcelotLogger _logger; + private IFileConfigurationRepository _repo; + private IFileConfigurationSetter _setter; + private string _previousAsJson; + private Timer _timer; + private bool _polling; + + public ConsulFileConfigurationPoller(IOcelotLoggerFactory factory, IFileConfigurationRepository repo, IFileConfigurationSetter setter) + { + _setter = setter; + _logger = factory.CreateLogger(); + _repo = repo; + _previousAsJson = ""; + _timer = new Timer(async x => + { + if(_polling) + { + return; + } + + _polling = true; + await Poll(); + _polling = false; + + }, null, 0, 1000); + } + + private async Task Poll() + { + _logger.LogDebug("Started polling consul"); + + var fileConfig = await _repo.Get(); + + if(fileConfig.IsError) + { + _logger.LogDebug($"error geting file config, errors are {string.Join(",", fileConfig.Errors.Select(x => x.Message))}"); + return; + } + + var asJson = ToJson(fileConfig.Data); + + if(!fileConfig.IsError && asJson != _previousAsJson) + { + await _setter.Set(fileConfig.Data); + _previousAsJson = asJson; + } + + _logger.LogDebug("Finished polling consul"); + } + + /// + /// We could do object comparison here but performance isnt really a problem. This might be an issue one day! + /// + /// + /// + private string ToJson(FileConfiguration config) + { + var currentHash = JsonConvert.SerializeObject(config); + return currentHash; + } + + public void Dispose() + { + _timer.Dispose(); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs index b89de3d88..91b4bd9dc 100644 --- a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs +++ b/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs @@ -1,79 +1,79 @@ -using System; -using System.Text; -using System.Threading.Tasks; -using Consul; -using Newtonsoft.Json; -using Ocelot.Configuration.File; -using Ocelot.Responses; -using Ocelot.ServiceDiscovery; - -namespace Ocelot.Configuration.Repository -{ - - public class ConsulFileConfigurationRepository : IFileConfigurationRepository - { - private readonly ConsulClient _consul; - private string _ocelotConfiguration = "OcelotConfiguration"; - private readonly Cache.IOcelotCache _cache; - - public ConsulFileConfigurationRepository(Cache.IOcelotCache cache, ServiceProviderConfiguration serviceProviderConfig) - { - var consulHost = string.IsNullOrEmpty(serviceProviderConfig?.ServiceProviderHost) ? "localhost" : serviceProviderConfig?.ServiceProviderHost; - var consulPort = serviceProviderConfig?.ServiceProviderPort ?? 8500; - var configuration = new ConsulRegistryConfiguration(consulHost, consulPort, _ocelotConfiguration); - _cache = cache; - _consul = new ConsulClient(c => - { - c.Address = new Uri($"http://{configuration.HostName}:{configuration.Port}"); - }); - } - - public async Task> Get() - { - var config = _cache.Get(_ocelotConfiguration, _ocelotConfiguration); - - if (config != null) - { - return new OkResponse(config); - } - - var queryResult = await _consul.KV.Get(_ocelotConfiguration); - - if (queryResult.Response == null) - { - return new OkResponse(null); - } - - var bytes = queryResult.Response.Value; - - var json = Encoding.UTF8.GetString(bytes); - - var consulConfig = JsonConvert.DeserializeObject(json); - - return new OkResponse(consulConfig); - } - - public async Task Set(FileConfiguration ocelotConfiguration) - { - var json = JsonConvert.SerializeObject(ocelotConfiguration); - - var bytes = Encoding.UTF8.GetBytes(json); - - var kvPair = new KVPair(_ocelotConfiguration) - { - Value = bytes - }; - - var result = await _consul.KV.Put(kvPair); - - if (result.Response) - { - _cache.AddAndDelete(_ocelotConfiguration, ocelotConfiguration, TimeSpan.FromSeconds(3), _ocelotConfiguration); - - return new OkResponse(); - } - - return new ErrorResponse(new UnableToSetConfigInConsulError($"Unable to set FileConfiguration in consul, response status code from consul was {result.StatusCode}")); - } - } +using System; +using System.Text; +using System.Threading.Tasks; +using Consul; +using Newtonsoft.Json; +using Ocelot.Configuration.File; +using Ocelot.Responses; +using Ocelot.ServiceDiscovery; + +namespace Ocelot.Configuration.Repository +{ + + public class ConsulFileConfigurationRepository : IFileConfigurationRepository + { + private readonly ConsulClient _consul; + private string _ocelotConfiguration = "OcelotConfiguration"; + private readonly Cache.IOcelotCache _cache; + + public ConsulFileConfigurationRepository(Cache.IOcelotCache cache, ServiceProviderConfiguration serviceProviderConfig) + { + var consulHost = string.IsNullOrEmpty(serviceProviderConfig?.ServiceProviderHost) ? "localhost" : serviceProviderConfig?.ServiceProviderHost; + var consulPort = serviceProviderConfig?.ServiceProviderPort ?? 8500; + var configuration = new ConsulRegistryConfiguration(consulHost, consulPort, _ocelotConfiguration); + _cache = cache; + _consul = new ConsulClient(c => + { + c.Address = new Uri($"http://{configuration.HostName}:{configuration.Port}"); + }); + } + + public async Task> Get() + { + var config = _cache.Get(_ocelotConfiguration, _ocelotConfiguration); + + if (config != null) + { + return new OkResponse(config); + } + + var queryResult = await _consul.KV.Get(_ocelotConfiguration); + + if (queryResult.Response == null) + { + return new OkResponse(null); + } + + var bytes = queryResult.Response.Value; + + var json = Encoding.UTF8.GetString(bytes); + + var consulConfig = JsonConvert.DeserializeObject(json); + + return new OkResponse(consulConfig); + } + + public async Task Set(FileConfiguration ocelotConfiguration) + { + var json = JsonConvert.SerializeObject(ocelotConfiguration); + + var bytes = Encoding.UTF8.GetBytes(json); + + var kvPair = new KVPair(_ocelotConfiguration) + { + Value = bytes + }; + + var result = await _consul.KV.Put(kvPair); + + if (result.Response) + { + _cache.AddAndDelete(_ocelotConfiguration, ocelotConfiguration, TimeSpan.FromSeconds(3), _ocelotConfiguration); + + return new OkResponse(); + } + + return new ErrorResponse(new UnableToSetConfigInConsulError($"Unable to set FileConfiguration in consul, response status code from consul was {result.StatusCode}")); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Repository/FileConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/FileConfigurationRepository.cs index 54bb36d70..29a0c36e3 100644 --- a/src/Ocelot/Configuration/Repository/FileConfigurationRepository.cs +++ b/src/Ocelot/Configuration/Repository/FileConfigurationRepository.cs @@ -1,52 +1,52 @@ -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Hosting; -using Newtonsoft.Json; -using Ocelot.Configuration.File; -using Ocelot.Responses; - -namespace Ocelot.Configuration.Repository -{ - public class FileConfigurationRepository : IFileConfigurationRepository - { - private readonly string _configFilePath; - - private static readonly object _lock = new object(); - - public FileConfigurationRepository(IHostingEnvironment hostingEnvironment) - { - _configFilePath = $"{AppContext.BaseDirectory}/configuration{(string.IsNullOrEmpty(hostingEnvironment.EnvironmentName) ? string.Empty : ".")}{hostingEnvironment.EnvironmentName}.json"; - } - - public async Task> Get() - { - string jsonConfiguration; - - lock(_lock) - { - jsonConfiguration = System.IO.File.ReadAllText(_configFilePath); - } - - var fileConfiguration = JsonConvert.DeserializeObject(jsonConfiguration); - - return new OkResponse(fileConfiguration); - } - - public async Task Set(FileConfiguration fileConfiguration) - { - string jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); - - lock(_lock) - { - if (System.IO.File.Exists(_configFilePath)) - { - System.IO.File.Delete(_configFilePath); - } - - System.IO.File.WriteAllText(_configFilePath, jsonConfiguration); - } - - return new OkResponse(); - } - } +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Newtonsoft.Json; +using Ocelot.Configuration.File; +using Ocelot.Responses; + +namespace Ocelot.Configuration.Repository +{ + public class FileConfigurationRepository : IFileConfigurationRepository + { + private readonly string _configFilePath; + + private static readonly object _lock = new object(); + + public FileConfigurationRepository(IHostingEnvironment hostingEnvironment) + { + _configFilePath = $"{AppContext.BaseDirectory}/configuration{(string.IsNullOrEmpty(hostingEnvironment.EnvironmentName) ? string.Empty : ".")}{hostingEnvironment.EnvironmentName}.json"; + } + + public async Task> Get() + { + string jsonConfiguration; + + lock(_lock) + { + jsonConfiguration = System.IO.File.ReadAllText(_configFilePath); + } + + var fileConfiguration = JsonConvert.DeserializeObject(jsonConfiguration); + + return new OkResponse(fileConfiguration); + } + + public async Task Set(FileConfiguration fileConfiguration) + { + string jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + + lock(_lock) + { + if (System.IO.File.Exists(_configFilePath)) + { + System.IO.File.Delete(_configFilePath); + } + + System.IO.File.WriteAllText(_configFilePath, jsonConfiguration); + } + + return new OkResponse(); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Repository/IFileConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/IFileConfigurationRepository.cs index 60d428fb9..2fe8140a2 100644 --- a/src/Ocelot/Configuration/Repository/IFileConfigurationRepository.cs +++ b/src/Ocelot/Configuration/Repository/IFileConfigurationRepository.cs @@ -1,12 +1,12 @@ -using System.Threading.Tasks; -using Ocelot.Configuration.File; -using Ocelot.Responses; - -namespace Ocelot.Configuration.Repository -{ - public interface IFileConfigurationRepository - { - Task> Get(); - Task Set(FileConfiguration fileConfiguration); - } +using System.Threading.Tasks; +using Ocelot.Configuration.File; +using Ocelot.Responses; + +namespace Ocelot.Configuration.Repository +{ + public interface IFileConfigurationRepository + { + Task> Get(); + Task Set(FileConfiguration fileConfiguration); + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Repository/IOcelotConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/IOcelotConfigurationRepository.cs index ff720464a..16b386a1f 100644 --- a/src/Ocelot/Configuration/Repository/IOcelotConfigurationRepository.cs +++ b/src/Ocelot/Configuration/Repository/IOcelotConfigurationRepository.cs @@ -1,12 +1,12 @@ -using System.Threading.Tasks; -using Ocelot.Configuration.File; -using Ocelot.Responses; - -namespace Ocelot.Configuration.Repository -{ - public interface IOcelotConfigurationRepository - { - Task> Get(); - Task AddOrReplace(IOcelotConfiguration ocelotConfiguration); - } +using System.Threading.Tasks; +using Ocelot.Configuration.File; +using Ocelot.Responses; + +namespace Ocelot.Configuration.Repository +{ + public interface IOcelotConfigurationRepository + { + Task> Get(); + Task AddOrReplace(IOcelotConfiguration ocelotConfiguration); + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Repository/InMemoryOcelotConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/InMemoryOcelotConfigurationRepository.cs index bb4c8e765..99f6a51b7 100644 --- a/src/Ocelot/Configuration/Repository/InMemoryOcelotConfigurationRepository.cs +++ b/src/Ocelot/Configuration/Repository/InMemoryOcelotConfigurationRepository.cs @@ -1,30 +1,30 @@ -using System.Threading.Tasks; -using Ocelot.Responses; - -namespace Ocelot.Configuration.Repository -{ - /// - /// Register as singleton - /// - public class InMemoryOcelotConfigurationRepository : IOcelotConfigurationRepository - { - private static readonly object LockObject = new object(); - - private IOcelotConfiguration _ocelotConfiguration; - - public async Task> Get() - { - return new OkResponse(_ocelotConfiguration); - } - - public async Task AddOrReplace(IOcelotConfiguration ocelotConfiguration) - { - lock (LockObject) - { - _ocelotConfiguration = ocelotConfiguration; - } - - return new OkResponse(); - } - } +using System.Threading.Tasks; +using Ocelot.Responses; + +namespace Ocelot.Configuration.Repository +{ + /// + /// Register as singleton + /// + public class InMemoryOcelotConfigurationRepository : IOcelotConfigurationRepository + { + private static readonly object LockObject = new object(); + + private IOcelotConfiguration _ocelotConfiguration; + + public async Task> Get() + { + return new OkResponse(_ocelotConfiguration); + } + + public async Task AddOrReplace(IOcelotConfiguration ocelotConfiguration) + { + lock (LockObject) + { + _ocelotConfiguration = ocelotConfiguration; + } + + return new OkResponse(); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Repository/UnableToSetConfigInConsulError.cs b/src/Ocelot/Configuration/Repository/UnableToSetConfigInConsulError.cs index 60c68fe2c..52b5c3728 100644 --- a/src/Ocelot/Configuration/Repository/UnableToSetConfigInConsulError.cs +++ b/src/Ocelot/Configuration/Repository/UnableToSetConfigInConsulError.cs @@ -1,12 +1,12 @@ -using Ocelot.Errors; - -namespace Ocelot.Configuration.Repository -{ - public class UnableToSetConfigInConsulError : Error - { - public UnableToSetConfigInConsulError(string message) - : base(message, OcelotErrorCode.UnableToSetConfigInConsulError) - { - } - } +using Ocelot.Errors; + +namespace Ocelot.Configuration.Repository +{ + public class UnableToSetConfigInConsulError : Error + { + public UnableToSetConfigInConsulError(string message) + : base(message, OcelotErrorCode.UnableToSetConfigInConsulError) + { + } + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/ServiceProviderConfiguraion.cs b/src/Ocelot/Configuration/ServiceProviderConfiguraion.cs index d5ddea6a2..272d22056 100644 --- a/src/Ocelot/Configuration/ServiceProviderConfiguraion.cs +++ b/src/Ocelot/Configuration/ServiceProviderConfiguraion.cs @@ -1,14 +1,14 @@ -namespace Ocelot.Configuration -{ - public class ServiceProviderConfiguration - { - public ServiceProviderConfiguration(string serviceProviderHost, int serviceProviderPort) - { - ServiceProviderHost = serviceProviderHost; - ServiceProviderPort = serviceProviderPort; - } - - public string ServiceProviderHost { get; private set; } - public int ServiceProviderPort { get; private set; } - } +namespace Ocelot.Configuration +{ + public class ServiceProviderConfiguration + { + public ServiceProviderConfiguration(string serviceProviderHost, int serviceProviderPort) + { + ServiceProviderHost = serviceProviderHost; + ServiceProviderPort = serviceProviderPort; + } + + public string ServiceProviderHost { get; private set; } + public int ServiceProviderPort { get; private set; } + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs b/src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs index 453bb92fd..71f562776 100644 --- a/src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs +++ b/src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs @@ -1,42 +1,42 @@ -using System.Threading.Tasks; -using Ocelot.Configuration.Creator; -using Ocelot.Configuration.File; -using Ocelot.Configuration.Repository; -using Ocelot.Responses; - -namespace Ocelot.Configuration.Setter -{ - public class FileConfigurationSetter : IFileConfigurationSetter - { - private readonly IOcelotConfigurationRepository _configRepo; - private readonly IOcelotConfigurationCreator _configCreator; - private readonly IFileConfigurationRepository _repo; - - public FileConfigurationSetter(IOcelotConfigurationRepository configRepo, - IOcelotConfigurationCreator configCreator, IFileConfigurationRepository repo) - { - _configRepo = configRepo; - _configCreator = configCreator; - _repo = repo; - } - - public async Task Set(FileConfiguration fileConfig) - { - var response = await _repo.Set(fileConfig); - - if(response.IsError) - { - return new ErrorResponse(response.Errors); - } - - var config = await _configCreator.Create(fileConfig); - - if(!config.IsError) - { - await _configRepo.AddOrReplace(config.Data); - } - - return new ErrorResponse(config.Errors); - } - } +using System.Threading.Tasks; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Ocelot.Configuration.Repository; +using Ocelot.Responses; + +namespace Ocelot.Configuration.Setter +{ + public class FileConfigurationSetter : IFileConfigurationSetter + { + private readonly IOcelotConfigurationRepository _configRepo; + private readonly IOcelotConfigurationCreator _configCreator; + private readonly IFileConfigurationRepository _repo; + + public FileConfigurationSetter(IOcelotConfigurationRepository configRepo, + IOcelotConfigurationCreator configCreator, IFileConfigurationRepository repo) + { + _configRepo = configRepo; + _configCreator = configCreator; + _repo = repo; + } + + public async Task Set(FileConfiguration fileConfig) + { + var response = await _repo.Set(fileConfig); + + if(response.IsError) + { + return new ErrorResponse(response.Errors); + } + + var config = await _configCreator.Create(fileConfig); + + if(!config.IsError) + { + await _configRepo.AddOrReplace(config.Data); + } + + return new ErrorResponse(config.Errors); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Setter/IFileConfigurationSetter.cs b/src/Ocelot/Configuration/Setter/IFileConfigurationSetter.cs index 8ab31d1b2..21fcfcda0 100644 --- a/src/Ocelot/Configuration/Setter/IFileConfigurationSetter.cs +++ b/src/Ocelot/Configuration/Setter/IFileConfigurationSetter.cs @@ -1,11 +1,11 @@ -using System.Threading.Tasks; -using Ocelot.Configuration.File; -using Ocelot.Responses; - -namespace Ocelot.Configuration.Setter -{ - public interface IFileConfigurationSetter - { - Task Set(FileConfiguration config); - } +using System.Threading.Tasks; +using Ocelot.Configuration.File; +using Ocelot.Responses; + +namespace Ocelot.Configuration.Setter +{ + public interface IFileConfigurationSetter + { + Task Set(FileConfiguration config); + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Validator/ConfigurationValidationResult.cs b/src/Ocelot/Configuration/Validator/ConfigurationValidationResult.cs index 32dec98be..4fe3567d8 100644 --- a/src/Ocelot/Configuration/Validator/ConfigurationValidationResult.cs +++ b/src/Ocelot/Configuration/Validator/ConfigurationValidationResult.cs @@ -1,24 +1,24 @@ -using System.Collections.Generic; -using Ocelot.Errors; - -namespace Ocelot.Configuration.Validator -{ - public class ConfigurationValidationResult - { - public ConfigurationValidationResult(bool isError) - { - IsError = isError; - Errors = new List(); - } - - public ConfigurationValidationResult(bool isError, List errors) - { - IsError = isError; - Errors = errors; - } - - public bool IsError { get; private set; } - - public List Errors { get; private set; } - } -} +using System.Collections.Generic; +using Ocelot.Errors; + +namespace Ocelot.Configuration.Validator +{ + public class ConfigurationValidationResult + { + public ConfigurationValidationResult(bool isError) + { + IsError = isError; + Errors = new List(); + } + + public ConfigurationValidationResult(bool isError, List errors) + { + IsError = isError; + Errors = errors; + } + + public bool IsError { get; private set; } + + public List Errors { get; private set; } + } +} diff --git a/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs b/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs index 08ab574ac..799649f51 100644 --- a/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs +++ b/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs @@ -1,65 +1,65 @@ -using FluentValidation; -using Microsoft.AspNetCore.Authentication; -using Ocelot.Configuration.File; -using Ocelot.Errors; -using Ocelot.Responses; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Ocelot.Configuration.Validator -{ - public class FileConfigurationFluentValidator : AbstractValidator, IConfigurationValidator - { - public FileConfigurationFluentValidator(IAuthenticationSchemeProvider authenticationSchemeProvider) - { - RuleFor(configuration => configuration.ReRoutes) - .SetCollectionValidator(new ReRouteFluentValidator(authenticationSchemeProvider)); - - RuleForEach(configuration => configuration.ReRoutes) - .Must((config, reRoute) => IsNotDuplicateIn(reRoute, config.ReRoutes)) - .WithMessage((config, reRoute) => $"{nameof(reRoute)} {reRoute.UpstreamPathTemplate} has duplicate"); - } - - public async Task> IsValid(FileConfiguration configuration) - { - var validateResult = await ValidateAsync(configuration); - - if (validateResult.IsValid) - { - return new OkResponse(new ConfigurationValidationResult(false)); - } - - var errors = validateResult.Errors.Select(failure => new FileValidationFailedError(failure.ErrorMessage)); - - var result = new ConfigurationValidationResult(true, errors.Cast().ToList()); - - return new OkResponse(result); - } - - private static bool IsNotDuplicateIn(FileReRoute reRoute, List reRoutes) - { - var matchingReRoutes = reRoutes.Where(r => r.UpstreamPathTemplate == reRoute.UpstreamPathTemplate).ToList(); - - if(matchingReRoutes.Count == 1) - { - return true; - } - - var allowAllVerbs = matchingReRoutes.Any(x => x.UpstreamHttpMethod.Count == 0); - - var duplicateAllowAllVerbs = matchingReRoutes.Count(x => x.UpstreamHttpMethod.Count == 0) > 1; - - var specificVerbs = matchingReRoutes.Any(x => x.UpstreamHttpMethod.Count != 0); - - var duplicateSpecificVerbs = matchingReRoutes.SelectMany(x => x.UpstreamHttpMethod).GroupBy(x => x.ToLower()).SelectMany(x => x.Skip(1)).Any(); - - if (duplicateAllowAllVerbs || duplicateSpecificVerbs || (allowAllVerbs && specificVerbs)) - { - return false; - } - - return true; - } - } -} +using FluentValidation; +using Microsoft.AspNetCore.Authentication; +using Ocelot.Configuration.File; +using Ocelot.Errors; +using Ocelot.Responses; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.Configuration.Validator +{ + public class FileConfigurationFluentValidator : AbstractValidator, IConfigurationValidator + { + public FileConfigurationFluentValidator(IAuthenticationSchemeProvider authenticationSchemeProvider) + { + RuleFor(configuration => configuration.ReRoutes) + .SetCollectionValidator(new ReRouteFluentValidator(authenticationSchemeProvider)); + + RuleForEach(configuration => configuration.ReRoutes) + .Must((config, reRoute) => IsNotDuplicateIn(reRoute, config.ReRoutes)) + .WithMessage((config, reRoute) => $"{nameof(reRoute)} {reRoute.UpstreamPathTemplate} has duplicate"); + } + + public async Task> IsValid(FileConfiguration configuration) + { + var validateResult = await ValidateAsync(configuration); + + if (validateResult.IsValid) + { + return new OkResponse(new ConfigurationValidationResult(false)); + } + + var errors = validateResult.Errors.Select(failure => new FileValidationFailedError(failure.ErrorMessage)); + + var result = new ConfigurationValidationResult(true, errors.Cast().ToList()); + + return new OkResponse(result); + } + + private static bool IsNotDuplicateIn(FileReRoute reRoute, List reRoutes) + { + var matchingReRoutes = reRoutes.Where(r => r.UpstreamPathTemplate == reRoute.UpstreamPathTemplate).ToList(); + + if(matchingReRoutes.Count == 1) + { + return true; + } + + var allowAllVerbs = matchingReRoutes.Any(x => x.UpstreamHttpMethod.Count == 0); + + var duplicateAllowAllVerbs = matchingReRoutes.Count(x => x.UpstreamHttpMethod.Count == 0) > 1; + + var specificVerbs = matchingReRoutes.Any(x => x.UpstreamHttpMethod.Count != 0); + + var duplicateSpecificVerbs = matchingReRoutes.SelectMany(x => x.UpstreamHttpMethod).GroupBy(x => x.ToLower()).SelectMany(x => x.Skip(1)).Any(); + + if (duplicateAllowAllVerbs || duplicateSpecificVerbs || (allowAllVerbs && specificVerbs)) + { + return false; + } + + return true; + } + } +} diff --git a/src/Ocelot/Configuration/Validator/FileValidationFailedError.cs b/src/Ocelot/Configuration/Validator/FileValidationFailedError.cs index 02255b5af..b661e0657 100644 --- a/src/Ocelot/Configuration/Validator/FileValidationFailedError.cs +++ b/src/Ocelot/Configuration/Validator/FileValidationFailedError.cs @@ -1,15 +1,15 @@ -using System; -using System.Collections.Generic; -using System.Text; -using Ocelot.Errors; - -namespace Ocelot.Configuration.Validator -{ - public class FileValidationFailedError : Error - { - public FileValidationFailedError(string message) : base(message, OcelotErrorCode.FileValidationFailedError) - { - - } - } -} +using System; +using System.Collections.Generic; +using System.Text; +using Ocelot.Errors; + +namespace Ocelot.Configuration.Validator +{ + public class FileValidationFailedError : Error + { + public FileValidationFailedError(string message) : base(message, OcelotErrorCode.FileValidationFailedError) + { + + } + } +} diff --git a/src/Ocelot/Configuration/Validator/HostAndPortValidator.cs b/src/Ocelot/Configuration/Validator/HostAndPortValidator.cs new file mode 100644 index 000000000..5cc1744e0 --- /dev/null +++ b/src/Ocelot/Configuration/Validator/HostAndPortValidator.cs @@ -0,0 +1,13 @@ +using FluentValidation; +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Validator +{ + public class HostAndPortValidator : AbstractValidator + { + public HostAndPortValidator() + { + RuleFor(r => r.Host).NotEmpty().WithMessage("When not using service discovery Host must be set on DownstreamHostAndPorts if you are not using ReRoute.Host or Ocelot cannot find your service!"); + } + } +} diff --git a/src/Ocelot/Configuration/Validator/IConfigurationValidator.cs b/src/Ocelot/Configuration/Validator/IConfigurationValidator.cs index 5f6ae6bad..6f8868520 100644 --- a/src/Ocelot/Configuration/Validator/IConfigurationValidator.cs +++ b/src/Ocelot/Configuration/Validator/IConfigurationValidator.cs @@ -1,11 +1,11 @@ -using System.Threading.Tasks; -using Ocelot.Configuration.File; -using Ocelot.Responses; - -namespace Ocelot.Configuration.Validator -{ - public interface IConfigurationValidator - { - Task> IsValid(FileConfiguration configuration); - } -} +using System.Threading.Tasks; +using Ocelot.Configuration.File; +using Ocelot.Responses; + +namespace Ocelot.Configuration.Validator +{ + public interface IConfigurationValidator + { + Task> IsValid(FileConfiguration configuration); + } +} diff --git a/src/Ocelot/Configuration/Validator/ReRouteFluentValidator.cs b/src/Ocelot/Configuration/Validator/ReRouteFluentValidator.cs index e8088dab1..f2ebbacee 100644 --- a/src/Ocelot/Configuration/Validator/ReRouteFluentValidator.cs +++ b/src/Ocelot/Configuration/Validator/ReRouteFluentValidator.cs @@ -1,79 +1,84 @@ -using FluentValidation; -using Microsoft.AspNetCore.Authentication; -using Ocelot.Configuration.File; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Ocelot.Configuration.Validator -{ - public class ReRouteFluentValidator : AbstractValidator - { - private readonly IAuthenticationSchemeProvider _authenticationSchemeProvider; - - public ReRouteFluentValidator(IAuthenticationSchemeProvider authenticationSchemeProvider) - { - _authenticationSchemeProvider = authenticationSchemeProvider; - - RuleFor(reRoute => reRoute.DownstreamPathTemplate) - .Must(path => path.StartsWith("/")) - .WithMessage("{PropertyName} {PropertyValue} doesnt start with forward slash"); - - RuleFor(reRoute => reRoute.UpstreamPathTemplate) - .Must(path => !path.Contains("//")) - .WithMessage("{PropertyName} {PropertyValue} contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature."); - - RuleFor(reRoute => reRoute.DownstreamPathTemplate) - .Must(path => !path.Contains("//")) - .WithMessage("{PropertyName} {PropertyValue} contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature."); - - RuleFor(reRoute => reRoute.UpstreamPathTemplate) - .Must(path => path.StartsWith("/")) - .WithMessage("{PropertyName} {PropertyValue} doesnt start with forward slash"); - - RuleFor(reRoute => reRoute.DownstreamPathTemplate) - .Must(path => !path.Contains("https://") && !path.Contains("http://")) - .WithMessage("{PropertyName} {PropertyValue} contains scheme"); - - RuleFor(reRoute => reRoute.UpstreamPathTemplate) - .Must(path => !path.Contains("https://") && !path.Contains("http://")) - .WithMessage("{PropertyName} {PropertyValue} contains scheme"); - - RuleFor(reRoute => reRoute.RateLimitOptions) - .Must(IsValidPeriod) - .WithMessage("RateLimitOptions.Period does not contains (s,m,h,d)"); - - RuleFor(reRoute => reRoute.AuthenticationOptions) - .MustAsync(IsSupportedAuthenticationProviders) - .WithMessage("{PropertyValue} is unsupported authentication provider"); - - When(reRoute => reRoute.UseServiceDiscovery, () => { - RuleFor(r => r.ServiceName).NotEmpty().WithMessage("ServiceName cannot be empty or null when using service discovery or Ocelot cannot look up your service!"); - }); - - When(reRoute => !reRoute.UseServiceDiscovery, () => { - RuleFor(r => r.DownstreamHost).NotEmpty().WithMessage("When not using service discover DownstreamHost must be set or Ocelot cannot find your service!"); - }); - } - - private async Task IsSupportedAuthenticationProviders(FileAuthenticationOptions authenticationOptions, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(authenticationOptions.AuthenticationProviderKey)) - { - return true; - } - var schemes = await _authenticationSchemeProvider.GetAllSchemesAsync(); - - var supportedSchemes = schemes.Select(scheme => scheme.Name).ToList(); - - return supportedSchemes.Contains(authenticationOptions.AuthenticationProviderKey); - } - - private static bool IsValidPeriod(FileRateLimitRule rateLimitOptions) - { - string period = rateLimitOptions.Period; - - return !rateLimitOptions.EnableRateLimiting || period.Contains("s") || period.Contains("m") || period.Contains("h") || period.Contains("d"); - } - } -} +using FluentValidation; +using Microsoft.AspNetCore.Authentication; +using Ocelot.Configuration.File; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Ocelot.Configuration.Validator +{ + public class ReRouteFluentValidator : AbstractValidator + { + private readonly IAuthenticationSchemeProvider _authenticationSchemeProvider; + + public ReRouteFluentValidator(IAuthenticationSchemeProvider authenticationSchemeProvider) + { + _authenticationSchemeProvider = authenticationSchemeProvider; + + RuleFor(reRoute => reRoute.DownstreamPathTemplate) + .Must(path => path.StartsWith("/")) + .WithMessage("{PropertyName} {PropertyValue} doesnt start with forward slash"); + + RuleFor(reRoute => reRoute.UpstreamPathTemplate) + .Must(path => !path.Contains("//")) + .WithMessage("{PropertyName} {PropertyValue} contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature."); + + RuleFor(reRoute => reRoute.DownstreamPathTemplate) + .Must(path => !path.Contains("//")) + .WithMessage("{PropertyName} {PropertyValue} contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature."); + + RuleFor(reRoute => reRoute.UpstreamPathTemplate) + .Must(path => path.StartsWith("/")) + .WithMessage("{PropertyName} {PropertyValue} doesnt start with forward slash"); + + RuleFor(reRoute => reRoute.DownstreamPathTemplate) + .Must(path => !path.Contains("https://") && !path.Contains("http://")) + .WithMessage("{PropertyName} {PropertyValue} contains scheme"); + + RuleFor(reRoute => reRoute.UpstreamPathTemplate) + .Must(path => !path.Contains("https://") && !path.Contains("http://")) + .WithMessage("{PropertyName} {PropertyValue} contains scheme"); + + RuleFor(reRoute => reRoute.RateLimitOptions) + .Must(IsValidPeriod) + .WithMessage("RateLimitOptions.Period does not contains (s,m,h,d)"); + + RuleFor(reRoute => reRoute.AuthenticationOptions) + .MustAsync(IsSupportedAuthenticationProviders) + .WithMessage("{PropertyValue} is unsupported authentication provider"); + + When(reRoute => reRoute.UseServiceDiscovery, () => { + RuleFor(r => r.ServiceName).NotEmpty().WithMessage("ServiceName cannot be empty or null when using service discovery or Ocelot cannot look up your service!"); + }); + + When(reRoute => !reRoute.UseServiceDiscovery, () => { + RuleFor(r => r.DownstreamHostAndPorts).NotEmpty().WithMessage("When not using service discovery DownstreamHostAndPorts must be set and not empty or Ocelot cannot find your service!"); + }); + + When(reRoute => !reRoute.UseServiceDiscovery, () => { + RuleFor(reRoute => reRoute.DownstreamHostAndPorts) + .SetCollectionValidator(new HostAndPortValidator()); + }); + } + + private async Task IsSupportedAuthenticationProviders(FileAuthenticationOptions authenticationOptions, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(authenticationOptions.AuthenticationProviderKey)) + { + return true; + } + var schemes = await _authenticationSchemeProvider.GetAllSchemesAsync(); + + var supportedSchemes = schemes.Select(scheme => scheme.Name).ToList(); + + return supportedSchemes.Contains(authenticationOptions.AuthenticationProviderKey); + } + + private static bool IsValidPeriod(FileRateLimitRule rateLimitOptions) + { + string period = rateLimitOptions.Period; + + return !rateLimitOptions.EnableRateLimiting || period.Contains("s") || period.Contains("m") || period.Contains("h") || period.Contains("d"); + } + } +} diff --git a/src/Ocelot/DependencyInjection/IOcelotBuilder.cs b/src/Ocelot/DependencyInjection/IOcelotBuilder.cs index 2f4a58fbe..d7cb0f0d2 100644 --- a/src/Ocelot/DependencyInjection/IOcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/IOcelotBuilder.cs @@ -1,12 +1,12 @@ -using CacheManager.Core; -using System; - -namespace Ocelot.DependencyInjection -{ - public interface IOcelotBuilder - { - IOcelotBuilder AddStoreOcelotConfigurationInConsul(); - IOcelotBuilder AddCacheManager(Action settings); - IOcelotAdministrationBuilder AddAdministration(string path, string secret); - } -} +using CacheManager.Core; +using System; + +namespace Ocelot.DependencyInjection +{ + public interface IOcelotBuilder + { + IOcelotBuilder AddStoreOcelotConfigurationInConsul(); + IOcelotBuilder AddCacheManager(Action settings); + IOcelotAdministrationBuilder AddAdministration(string path, string secret); + } +} diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index 3b0a008f0..242b357b4 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -1,331 +1,332 @@ -using CacheManager.Core; -using IdentityServer4.Models; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Ocelot.Authorisation; -using Ocelot.Cache; -using Ocelot.Claims; -using Ocelot.Configuration.Authentication; -using Ocelot.Configuration.Creator; -using Ocelot.Configuration.File; -using Ocelot.Configuration.Parser; -using Ocelot.Configuration.Provider; -using Ocelot.Configuration.Repository; -using Ocelot.Configuration.Setter; -using Ocelot.Configuration.Validator; -using Ocelot.DownstreamRouteFinder.Finder; -using Ocelot.DownstreamRouteFinder.UrlMatcher; -using Ocelot.DownstreamUrlCreator; -using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; -using Ocelot.Headers; -using Ocelot.Infrastructure.Claims.Parser; -using Ocelot.Infrastructure.RequestData; -using Ocelot.LoadBalancer.LoadBalancers; -using Ocelot.Logging; -using Ocelot.Middleware; -using Ocelot.QueryStrings; -using Ocelot.RateLimit; -using Ocelot.Request.Builder; -using Ocelot.Request.Mapper; -using Ocelot.Requester; -using Ocelot.Requester.QoS; -using Ocelot.Responder; -using Ocelot.ServiceDiscovery; -using System; -using System.Collections.Generic; -using System.IdentityModel.Tokens.Jwt; -using System.Net.Http; -using System.Reflection; -using System.Security.Cryptography.X509Certificates; -using IdentityServer4.AccessTokenValidation; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using FileConfigurationProvider = Ocelot.Configuration.Provider.FileConfigurationProvider; -using Microsoft.Extensions.DependencyInjection.Extensions; -using System.Linq; -using Ocelot.Raft; -using Rafty.Concensus; -using Rafty.FiniteStateMachine; -using Rafty.Infrastructure; -using Rafty.Log; -using Newtonsoft.Json; - -namespace Ocelot.DependencyInjection -{ - public class OcelotBuilder : IOcelotBuilder - { - private IServiceCollection _services; - private IConfiguration _configurationRoot; - - public OcelotBuilder(IServiceCollection services, IConfiguration configurationRoot) - { - _configurationRoot = configurationRoot; - _services = services; - - //add default cache settings... - Action defaultCachingSettings = x => - { - x.WithDictionaryHandle(); - }; - - AddCacheManager(defaultCachingSettings); - - //add ocelot services... - _services.Configure(configurationRoot); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - // see this for why we register this as singleton http://stackoverflow.com/questions/37371264/invalidoperationexception-unable-to-resolve-service-for-type-microsoft-aspnetc - // could maybe use a scoped data repository - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.AddMemoryCache(); - _services.TryAddSingleton(); - - //add asp.net services.. - var assembly = typeof(FileConfigurationController).GetTypeInfo().Assembly; - - _services.AddMvcCore() - .AddApplicationPart(assembly) - .AddControllersAsServices() - .AddAuthorization() - .AddJsonFormatters(); - - _services.AddLogging(); - _services.AddMiddlewareAnalysis(); - _services.AddWebEncoders(); - _services.AddSingleton(new NullAdministrationPath()); - } - - public IOcelotAdministrationBuilder AddAdministration(string path, string secret) - { - var administrationPath = new AdministrationPath(path); - - //add identity server for admin area - var identityServerConfiguration = IdentityServerConfigurationCreator.GetIdentityServerConfiguration(secret); - - if (identityServerConfiguration != null) - { - AddIdentityServer(identityServerConfiguration, administrationPath); - } - - var descriptor = new ServiceDescriptor(typeof(IAdministrationPath), administrationPath); - _services.Replace(descriptor); - return new OcelotAdministrationBuilder(_services, _configurationRoot); - } - - public IOcelotBuilder AddStoreOcelotConfigurationInConsul() - { - var serviceDiscoveryPort = _configurationRoot.GetValue("GlobalConfiguration:ServiceDiscoveryProvider:Port", 0); - var serviceDiscoveryHost = _configurationRoot.GetValue("GlobalConfiguration:ServiceDiscoveryProvider:Host", string.Empty); - - var config = new ServiceProviderConfigurationBuilder() - .WithServiceDiscoveryProviderPort(serviceDiscoveryPort) - .WithServiceDiscoveryProviderHost(serviceDiscoveryHost) - .Build(); - - _services.AddSingleton(config); - _services.AddSingleton(); - _services.AddSingleton(); - return this; - } - - public IOcelotBuilder AddCacheManager(Action settings) - { - var cacheManagerOutputCache = CacheFactory.Build("OcelotOutputCache", settings); - var ocelotOutputCacheManager = new OcelotCacheManagerCache(cacheManagerOutputCache); - - _services.RemoveAll(typeof(ICacheManager)); - _services.RemoveAll(typeof(IOcelotCache)); - _services.AddSingleton>(cacheManagerOutputCache); - _services.AddSingleton>(ocelotOutputCacheManager); - - var ocelotConfigCacheManagerOutputCache = CacheFactory.Build("OcelotConfigurationCache", settings); - var ocelotConfigCacheManager = new OcelotCacheManagerCache(ocelotConfigCacheManagerOutputCache); - _services.RemoveAll(typeof(ICacheManager)); - _services.RemoveAll(typeof(IOcelotCache)); - _services.AddSingleton>(ocelotConfigCacheManagerOutputCache); - _services.AddSingleton>(ocelotConfigCacheManager); - - var fileConfigCacheManagerOutputCache = CacheFactory.Build("FileConfigurationCache", settings); - var fileConfigCacheManager = new OcelotCacheManagerCache(fileConfigCacheManagerOutputCache); - _services.RemoveAll(typeof(ICacheManager)); - _services.RemoveAll(typeof(IOcelotCache)); - _services.AddSingleton>(fileConfigCacheManagerOutputCache); - _services.AddSingleton>(fileConfigCacheManager); - return this; - } - - private void AddIdentityServer(IIdentityServerConfiguration identityServerConfiguration, IAdministrationPath adminPath) - { - _services.TryAddSingleton(identityServerConfiguration); - _services.TryAddSingleton(); - var identityServerBuilder = _services - .AddIdentityServer(o => { - o.IssuerUri = "Ocelot"; - }) - .AddInMemoryApiResources(Resources(identityServerConfiguration)) - .AddInMemoryClients(Client(identityServerConfiguration)); - - //todo - refactor a method so we know why this is happening - var whb = _services.First(x => x.ServiceType == typeof(IWebHostBuilder)); - var urlFinder = new BaseUrlFinder((IWebHostBuilder)whb.ImplementationInstance); - var baseSchemeUrlAndPort = urlFinder.Find(); - JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); - - _services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme) - .AddIdentityServerAuthentication(o => - { - o.Authority = baseSchemeUrlAndPort + adminPath.Path; - o.ApiName = identityServerConfiguration.ApiName; - o.RequireHttpsMetadata = identityServerConfiguration.RequireHttps; - o.SupportedTokens = SupportedTokens.Both; - o.ApiSecret = identityServerConfiguration.ApiSecret; - }); - - //todo - refactor naming.. - if (string.IsNullOrEmpty(identityServerConfiguration.CredentialsSigningCertificateLocation) || string.IsNullOrEmpty(identityServerConfiguration.CredentialsSigningCertificatePassword)) - { - identityServerBuilder.AddDeveloperSigningCredential(); - } - else - { - //todo - refactor so calls method? - var cert = new X509Certificate2(identityServerConfiguration.CredentialsSigningCertificateLocation, identityServerConfiguration.CredentialsSigningCertificatePassword); - identityServerBuilder.AddSigningCredential(cert); - } - } - - private List Resources(IIdentityServerConfiguration identityServerConfiguration) - { - return new List - { - new ApiResource(identityServerConfiguration.ApiName, identityServerConfiguration.ApiName) - { - ApiSecrets = new List - { - new Secret - { - Value = identityServerConfiguration.ApiSecret.Sha256() - } - } - }, - }; - } - - private List Client(IIdentityServerConfiguration identityServerConfiguration) - { - return new List - { - new Client - { - ClientId = identityServerConfiguration.ApiName, - AllowedGrantTypes = GrantTypes.ClientCredentials, - ClientSecrets = new List {new Secret(identityServerConfiguration.ApiSecret.Sha256())}, - AllowedScopes = { identityServerConfiguration.ApiName } - } - }; - } - } - - public interface IOcelotAdministrationBuilder - { - IOcelotAdministrationBuilder AddRafty(); - } - - public class OcelotAdministrationBuilder : IOcelotAdministrationBuilder - { - private IServiceCollection _services; - private IConfiguration _configurationRoot; - - public OcelotAdministrationBuilder(IServiceCollection services, IConfiguration configurationRoot) - { - _configurationRoot = configurationRoot; - _services = services; - } - - public IOcelotAdministrationBuilder AddRafty() - { - var settings = new InMemorySettings(4000, 5000, 100, 5000); - _services.AddSingleton(); - _services.AddSingleton(); - _services.AddSingleton(settings); - _services.AddSingleton(); - _services.AddSingleton(); - _services.Configure(_configurationRoot); - return this; - } - } - - public interface IAdministrationPath - { - string Path {get;} - } - - public class NullAdministrationPath : IAdministrationPath - { - public NullAdministrationPath() - { - Path = null; - } - - public string Path {get;private set;} - } - - public class AdministrationPath : IAdministrationPath - { - public AdministrationPath(string path) - { - Path = path; - } - - public string Path {get;private set;} - } -} +using CacheManager.Core; +using IdentityServer4.Models; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Ocelot.Authorisation; +using Ocelot.Cache; +using Ocelot.Claims; +using Ocelot.Configuration.Authentication; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Ocelot.Configuration.Parser; +using Ocelot.Configuration.Provider; +using Ocelot.Configuration.Repository; +using Ocelot.Configuration.Setter; +using Ocelot.Configuration.Validator; +using Ocelot.DownstreamRouteFinder.Finder; +using Ocelot.DownstreamRouteFinder.UrlMatcher; +using Ocelot.DownstreamUrlCreator; +using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; +using Ocelot.Headers; +using Ocelot.Infrastructure.Claims.Parser; +using Ocelot.Infrastructure.RequestData; +using Ocelot.LoadBalancer.LoadBalancers; +using Ocelot.Logging; +using Ocelot.Middleware; +using Ocelot.QueryStrings; +using Ocelot.RateLimit; +using Ocelot.Request.Builder; +using Ocelot.Request.Mapper; +using Ocelot.Requester; +using Ocelot.Requester.QoS; +using Ocelot.Responder; +using Ocelot.ServiceDiscovery; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Net.Http; +using System.Reflection; +using System.Security.Cryptography.X509Certificates; +using IdentityServer4.AccessTokenValidation; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using FileConfigurationProvider = Ocelot.Configuration.Provider.FileConfigurationProvider; +using Microsoft.Extensions.DependencyInjection.Extensions; +using System.Linq; +using Ocelot.Raft; +using Rafty.Concensus; +using Rafty.FiniteStateMachine; +using Rafty.Infrastructure; +using Rafty.Log; +using Newtonsoft.Json; + +namespace Ocelot.DependencyInjection +{ + public class OcelotBuilder : IOcelotBuilder + { + private IServiceCollection _services; + private IConfiguration _configurationRoot; + + public OcelotBuilder(IServiceCollection services, IConfiguration configurationRoot) + { + _configurationRoot = configurationRoot; + _services = services; + + //add default cache settings... + Action defaultCachingSettings = x => + { + x.WithDictionaryHandle(); + }; + + AddCacheManager(defaultCachingSettings); + + //add ocelot services... + _services.Configure(configurationRoot); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + // see this for why we register this as singleton http://stackoverflow.com/questions/37371264/invalidoperationexception-unable-to-resolve-service-for-type-microsoft-aspnetc + // could maybe use a scoped data repository + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.AddMemoryCache(); + _services.TryAddSingleton(); + + //add asp.net services.. + var assembly = typeof(FileConfigurationController).GetTypeInfo().Assembly; + + _services.AddMvcCore() + .AddApplicationPart(assembly) + .AddControllersAsServices() + .AddAuthorization() + .AddJsonFormatters(); + + _services.AddLogging(); + _services.AddMiddlewareAnalysis(); + _services.AddWebEncoders(); + _services.AddSingleton(new NullAdministrationPath()); + } + + public IOcelotAdministrationBuilder AddAdministration(string path, string secret) + { + var administrationPath = new AdministrationPath(path); + + //add identity server for admin area + var identityServerConfiguration = IdentityServerConfigurationCreator.GetIdentityServerConfiguration(secret); + + if (identityServerConfiguration != null) + { + AddIdentityServer(identityServerConfiguration, administrationPath); + } + + var descriptor = new ServiceDescriptor(typeof(IAdministrationPath), administrationPath); + _services.Replace(descriptor); + return new OcelotAdministrationBuilder(_services, _configurationRoot); + } + + public IOcelotBuilder AddStoreOcelotConfigurationInConsul() + { + var serviceDiscoveryPort = _configurationRoot.GetValue("GlobalConfiguration:ServiceDiscoveryProvider:Port", 0); + var serviceDiscoveryHost = _configurationRoot.GetValue("GlobalConfiguration:ServiceDiscoveryProvider:Host", string.Empty); + + var config = new ServiceProviderConfigurationBuilder() + .WithServiceDiscoveryProviderPort(serviceDiscoveryPort) + .WithServiceDiscoveryProviderHost(serviceDiscoveryHost) + .Build(); + + _services.AddSingleton(config); + _services.AddSingleton(); + _services.AddSingleton(); + return this; + } + + public IOcelotBuilder AddCacheManager(Action settings) + { + var cacheManagerOutputCache = CacheFactory.Build("OcelotOutputCache", settings); + var ocelotOutputCacheManager = new OcelotCacheManagerCache(cacheManagerOutputCache); + + _services.RemoveAll(typeof(ICacheManager)); + _services.RemoveAll(typeof(IOcelotCache)); + _services.AddSingleton>(cacheManagerOutputCache); + _services.AddSingleton>(ocelotOutputCacheManager); + + var ocelotConfigCacheManagerOutputCache = CacheFactory.Build("OcelotConfigurationCache", settings); + var ocelotConfigCacheManager = new OcelotCacheManagerCache(ocelotConfigCacheManagerOutputCache); + _services.RemoveAll(typeof(ICacheManager)); + _services.RemoveAll(typeof(IOcelotCache)); + _services.AddSingleton>(ocelotConfigCacheManagerOutputCache); + _services.AddSingleton>(ocelotConfigCacheManager); + + var fileConfigCacheManagerOutputCache = CacheFactory.Build("FileConfigurationCache", settings); + var fileConfigCacheManager = new OcelotCacheManagerCache(fileConfigCacheManagerOutputCache); + _services.RemoveAll(typeof(ICacheManager)); + _services.RemoveAll(typeof(IOcelotCache)); + _services.AddSingleton>(fileConfigCacheManagerOutputCache); + _services.AddSingleton>(fileConfigCacheManager); + return this; + } + + private void AddIdentityServer(IIdentityServerConfiguration identityServerConfiguration, IAdministrationPath adminPath) + { + _services.TryAddSingleton(identityServerConfiguration); + _services.TryAddSingleton(); + var identityServerBuilder = _services + .AddIdentityServer(o => { + o.IssuerUri = "Ocelot"; + }) + .AddInMemoryApiResources(Resources(identityServerConfiguration)) + .AddInMemoryClients(Client(identityServerConfiguration)); + + //todo - refactor a method so we know why this is happening + var whb = _services.First(x => x.ServiceType == typeof(IWebHostBuilder)); + var urlFinder = new BaseUrlFinder((IWebHostBuilder)whb.ImplementationInstance); + var baseSchemeUrlAndPort = urlFinder.Find(); + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); + + _services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme) + .AddIdentityServerAuthentication(o => + { + o.Authority = baseSchemeUrlAndPort + adminPath.Path; + o.ApiName = identityServerConfiguration.ApiName; + o.RequireHttpsMetadata = identityServerConfiguration.RequireHttps; + o.SupportedTokens = SupportedTokens.Both; + o.ApiSecret = identityServerConfiguration.ApiSecret; + }); + + //todo - refactor naming.. + if (string.IsNullOrEmpty(identityServerConfiguration.CredentialsSigningCertificateLocation) || string.IsNullOrEmpty(identityServerConfiguration.CredentialsSigningCertificatePassword)) + { + identityServerBuilder.AddDeveloperSigningCredential(); + } + else + { + //todo - refactor so calls method? + var cert = new X509Certificate2(identityServerConfiguration.CredentialsSigningCertificateLocation, identityServerConfiguration.CredentialsSigningCertificatePassword); + identityServerBuilder.AddSigningCredential(cert); + } + } + + private List Resources(IIdentityServerConfiguration identityServerConfiguration) + { + return new List + { + new ApiResource(identityServerConfiguration.ApiName, identityServerConfiguration.ApiName) + { + ApiSecrets = new List + { + new Secret + { + Value = identityServerConfiguration.ApiSecret.Sha256() + } + } + }, + }; + } + + private List Client(IIdentityServerConfiguration identityServerConfiguration) + { + return new List + { + new Client + { + ClientId = identityServerConfiguration.ApiName, + AllowedGrantTypes = GrantTypes.ClientCredentials, + ClientSecrets = new List {new Secret(identityServerConfiguration.ApiSecret.Sha256())}, + AllowedScopes = { identityServerConfiguration.ApiName } + } + }; + } + } + + public interface IOcelotAdministrationBuilder + { + IOcelotAdministrationBuilder AddRafty(); + } + + public class OcelotAdministrationBuilder : IOcelotAdministrationBuilder + { + private IServiceCollection _services; + private IConfiguration _configurationRoot; + + public OcelotAdministrationBuilder(IServiceCollection services, IConfiguration configurationRoot) + { + _configurationRoot = configurationRoot; + _services = services; + } + + public IOcelotAdministrationBuilder AddRafty() + { + var settings = new InMemorySettings(4000, 5000, 100, 5000); + _services.AddSingleton(); + _services.AddSingleton(); + _services.AddSingleton(settings); + _services.AddSingleton(); + _services.AddSingleton(); + _services.Configure(_configurationRoot); + return this; + } + } + + public interface IAdministrationPath + { + string Path {get;} + } + + public class NullAdministrationPath : IAdministrationPath + { + public NullAdministrationPath() + { + Path = null; + } + + public string Path {get;private set;} + } + + public class AdministrationPath : IAdministrationPath + { + public AdministrationPath(string path) + { + Path = path; + } + + public string Path {get;private set;} + } +} diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 1a6bb96c3..51caf3bbc 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -1,22 +1,22 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using System.Linq; - -namespace Ocelot.DependencyInjection -{ - public static class ServiceCollectionExtensions - { - public static IOcelotBuilder AddOcelot(this IServiceCollection services) - { - var service = services.First(x => x.ServiceType == typeof(IConfiguration)); - var configuration = (IConfiguration)service.ImplementationInstance; - return new OcelotBuilder(services, configuration); - } - - public static IOcelotBuilder AddOcelot(this IServiceCollection services, IConfiguration configuration) - { - return new OcelotBuilder(services, configuration); - } - } -} +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using System.Linq; + +namespace Ocelot.DependencyInjection +{ + public static class ServiceCollectionExtensions + { + public static IOcelotBuilder AddOcelot(this IServiceCollection services) + { + var service = services.First(x => x.ServiceType == typeof(IConfiguration)); + var configuration = (IConfiguration)service.ImplementationInstance; + return new OcelotBuilder(services, configuration); + } + + public static IOcelotBuilder AddOcelot(this IServiceCollection services, IConfiguration configuration) + { + return new OcelotBuilder(services, configuration); + } + } +} diff --git a/src/Ocelot/DownstreamRouteFinder/DownstreamRoute.cs b/src/Ocelot/DownstreamRouteFinder/DownstreamRoute.cs index 3a3b3b2dd..f2e219cb9 100644 --- a/src/Ocelot/DownstreamRouteFinder/DownstreamRoute.cs +++ b/src/Ocelot/DownstreamRouteFinder/DownstreamRoute.cs @@ -1,18 +1,18 @@ -using System.Collections.Generic; -using Ocelot.Configuration; -using Ocelot.DownstreamRouteFinder.UrlMatcher; - -namespace Ocelot.DownstreamRouteFinder -{ - public class DownstreamRoute - { - public DownstreamRoute(List templatePlaceholderNameAndValues, ReRoute reRoute) - { - TemplatePlaceholderNameAndValues = templatePlaceholderNameAndValues; - ReRoute = reRoute; - } - public List TemplatePlaceholderNameAndValues { get; private set; } - public ReRoute ReRoute { get; private set; } - public object UpstreamHeadersFindAndReplace {get;private set;} - } +using System.Collections.Generic; +using Ocelot.Configuration; +using Ocelot.DownstreamRouteFinder.UrlMatcher; + +namespace Ocelot.DownstreamRouteFinder +{ + public class DownstreamRoute + { + public DownstreamRoute(List templatePlaceholderNameAndValues, ReRoute reRoute) + { + TemplatePlaceholderNameAndValues = templatePlaceholderNameAndValues; + ReRoute = reRoute; + } + public List TemplatePlaceholderNameAndValues { get; private set; } + public ReRoute ReRoute { get; private set; } + public object UpstreamHeadersFindAndReplace {get;private set;} + } } \ No newline at end of file diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs index 643a44646..12f350b7c 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs @@ -1,56 +1,56 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Ocelot.Configuration; -using Ocelot.Configuration.Provider; -using Ocelot.DownstreamRouteFinder.UrlMatcher; -using Ocelot.Errors; -using Ocelot.Responses; - -namespace Ocelot.DownstreamRouteFinder.Finder -{ - public class DownstreamRouteFinder : IDownstreamRouteFinder - { - private readonly IUrlPathToUrlTemplateMatcher _urlMatcher; - private readonly IPlaceholderNameAndValueFinder __placeholderNameAndValueFinder; - - public DownstreamRouteFinder(IUrlPathToUrlTemplateMatcher urlMatcher, IPlaceholderNameAndValueFinder urlPathPlaceholderNameAndValueFinder) - { - _urlMatcher = urlMatcher; - __placeholderNameAndValueFinder = urlPathPlaceholderNameAndValueFinder; - } - - public Response FindDownstreamRoute(string path, string httpMethod, IOcelotConfiguration configuration) - { - var applicableReRoutes = configuration.ReRoutes.Where(r => r.UpstreamHttpMethod.Count == 0 || r.UpstreamHttpMethod.Select(x => x.Method.ToLower()).Contains(httpMethod.ToLower())).OrderByDescending(x => x.UpstreamTemplatePattern.Priority); - - foreach (var reRoute in applicableReRoutes) - { - if (path == reRoute.UpstreamTemplatePattern.Template) - { - return GetPlaceholderNamesAndValues(path, reRoute); - } - - var urlMatch = _urlMatcher.Match(path, reRoute.UpstreamTemplatePattern.Template); - - if (urlMatch.Data.Match) - { - return GetPlaceholderNamesAndValues(path, reRoute); - } - } - - return new ErrorResponse(new List - { - new UnableToFindDownstreamRouteError() - }); - } - - private OkResponse GetPlaceholderNamesAndValues(string path, ReRoute reRoute) - { - var templatePlaceholderNameAndValues = __placeholderNameAndValueFinder.Find(path, reRoute.UpstreamPathTemplate.Value); - - return new OkResponse(new DownstreamRoute(templatePlaceholderNameAndValues.Data, reRoute)); - } - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Ocelot.Configuration; +using Ocelot.Configuration.Provider; +using Ocelot.DownstreamRouteFinder.UrlMatcher; +using Ocelot.Errors; +using Ocelot.Responses; + +namespace Ocelot.DownstreamRouteFinder.Finder +{ + public class DownstreamRouteFinder : IDownstreamRouteFinder + { + private readonly IUrlPathToUrlTemplateMatcher _urlMatcher; + private readonly IPlaceholderNameAndValueFinder __placeholderNameAndValueFinder; + + public DownstreamRouteFinder(IUrlPathToUrlTemplateMatcher urlMatcher, IPlaceholderNameAndValueFinder urlPathPlaceholderNameAndValueFinder) + { + _urlMatcher = urlMatcher; + __placeholderNameAndValueFinder = urlPathPlaceholderNameAndValueFinder; + } + + public Response FindDownstreamRoute(string path, string httpMethod, IOcelotConfiguration configuration) + { + var applicableReRoutes = configuration.ReRoutes.Where(r => r.UpstreamHttpMethod.Count == 0 || r.UpstreamHttpMethod.Select(x => x.Method.ToLower()).Contains(httpMethod.ToLower())).OrderByDescending(x => x.UpstreamTemplatePattern.Priority); + + foreach (var reRoute in applicableReRoutes) + { + if (path == reRoute.UpstreamTemplatePattern.Template) + { + return GetPlaceholderNamesAndValues(path, reRoute); + } + + var urlMatch = _urlMatcher.Match(path, reRoute.UpstreamTemplatePattern.Template); + + if (urlMatch.Data.Match) + { + return GetPlaceholderNamesAndValues(path, reRoute); + } + } + + return new ErrorResponse(new List + { + new UnableToFindDownstreamRouteError() + }); + } + + private OkResponse GetPlaceholderNamesAndValues(string path, ReRoute reRoute) + { + var templatePlaceholderNameAndValues = __placeholderNameAndValueFinder.Find(path, reRoute.UpstreamPathTemplate.Value); + + return new OkResponse(new DownstreamRoute(templatePlaceholderNameAndValues.Data, reRoute)); + } + } } \ No newline at end of file diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteFinder.cs index eda3acacd..f25402fa0 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteFinder.cs @@ -1,11 +1,11 @@ -using System.Threading.Tasks; -using Ocelot.Configuration; -using Ocelot.Responses; - -namespace Ocelot.DownstreamRouteFinder.Finder -{ - public interface IDownstreamRouteFinder - { - Response FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod, IOcelotConfiguration configuration); - } -} +using System.Threading.Tasks; +using Ocelot.Configuration; +using Ocelot.Responses; + +namespace Ocelot.DownstreamRouteFinder.Finder +{ + public interface IDownstreamRouteFinder + { + Response FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod, IOcelotConfiguration configuration); + } +} diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/UnableToFindDownstreamRouteError.cs b/src/Ocelot/DownstreamRouteFinder/Finder/UnableToFindDownstreamRouteError.cs index d73587b1d..66f7172ae 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/UnableToFindDownstreamRouteError.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/UnableToFindDownstreamRouteError.cs @@ -1,11 +1,11 @@ -using Ocelot.Errors; - -namespace Ocelot.DownstreamRouteFinder.Finder -{ - public class UnableToFindDownstreamRouteError : Error - { - public UnableToFindDownstreamRouteError() : base("UnableToFindDownstreamRouteError", OcelotErrorCode.UnableToFindDownstreamRouteError) - { - } - } -} +using Ocelot.Errors; + +namespace Ocelot.DownstreamRouteFinder.Finder +{ + public class UnableToFindDownstreamRouteError : Error + { + public UnableToFindDownstreamRouteError() : base("UnableToFindDownstreamRouteError", OcelotErrorCode.UnableToFindDownstreamRouteError) + { + } + } +} diff --git a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs index 0e94b2b03..bfdb19957 100644 --- a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs +++ b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs @@ -1,69 +1,69 @@ -using System.Linq; -using System.Security.Cryptography.X509Certificates; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Ocelot.Configuration.Provider; -using Ocelot.DownstreamRouteFinder.Finder; -using Ocelot.Infrastructure.Extensions; -using Ocelot.Infrastructure.RequestData; -using Ocelot.Logging; -using Ocelot.Middleware; - -namespace Ocelot.DownstreamRouteFinder.Middleware -{ - public class DownstreamRouteFinderMiddleware : OcelotMiddleware - { - private readonly RequestDelegate _next; - private readonly IDownstreamRouteFinder _downstreamRouteFinder; - private readonly IOcelotLogger _logger; - private readonly IOcelotConfigurationProvider _configProvider; - - - public DownstreamRouteFinderMiddleware(RequestDelegate next, - IOcelotLoggerFactory loggerFactory, - IDownstreamRouteFinder downstreamRouteFinder, - IRequestScopedDataRepository requestScopedDataRepository, - IOcelotConfigurationProvider configProvider) - :base(requestScopedDataRepository) - { - _configProvider = configProvider; - _next = next; - _downstreamRouteFinder = downstreamRouteFinder; - _logger = loggerFactory.CreateLogger(); - } - - public async Task Invoke(HttpContext context) - { - var upstreamUrlPath = context.Request.Path.ToString(); - - var configuration = await _configProvider.Get(); - - if(configuration.IsError) - { - _logger.LogError($"{MiddlewareName} setting pipeline errors. IOcelotConfigurationProvider returned {configuration.Errors.ToErrorString()}"); - SetPipelineError(configuration.Errors); - return; - } - - SetServiceProviderConfigurationForThisRequest(configuration.Data.ServiceProviderConfiguration); - - _logger.LogDebug("upstream url path is {upstreamUrlPath}", upstreamUrlPath); - - var downstreamRoute = _downstreamRouteFinder.FindDownstreamRoute(upstreamUrlPath, context.Request.Method, configuration.Data); - - if (downstreamRoute.IsError) - { - _logger.LogError($"{MiddlewareName} setting pipeline errors. IDownstreamRouteFinder returned {downstreamRoute.Errors.ToErrorString()}"); - - SetPipelineError(downstreamRoute.Errors); - return; - } - - _logger.LogDebug("downstream template is {downstreamRoute.Data.ReRoute.DownstreamPath}", downstreamRoute.Data.ReRoute.DownstreamPathTemplate); - - SetDownstreamRouteForThisRequest(downstreamRoute.Data); - - await _next.Invoke(context); - } - } +using System.Linq; +using System.Security.Cryptography.X509Certificates; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Ocelot.Configuration.Provider; +using Ocelot.DownstreamRouteFinder.Finder; +using Ocelot.Infrastructure.Extensions; +using Ocelot.Infrastructure.RequestData; +using Ocelot.Logging; +using Ocelot.Middleware; + +namespace Ocelot.DownstreamRouteFinder.Middleware +{ + public class DownstreamRouteFinderMiddleware : OcelotMiddleware + { + private readonly RequestDelegate _next; + private readonly IDownstreamRouteFinder _downstreamRouteFinder; + private readonly IOcelotLogger _logger; + private readonly IOcelotConfigurationProvider _configProvider; + + + public DownstreamRouteFinderMiddleware(RequestDelegate next, + IOcelotLoggerFactory loggerFactory, + IDownstreamRouteFinder downstreamRouteFinder, + IRequestScopedDataRepository requestScopedDataRepository, + IOcelotConfigurationProvider configProvider) + :base(requestScopedDataRepository) + { + _configProvider = configProvider; + _next = next; + _downstreamRouteFinder = downstreamRouteFinder; + _logger = loggerFactory.CreateLogger(); + } + + public async Task Invoke(HttpContext context) + { + var upstreamUrlPath = context.Request.Path.ToString(); + + var configuration = await _configProvider.Get(); + + if(configuration.IsError) + { + _logger.LogError($"{MiddlewareName} setting pipeline errors. IOcelotConfigurationProvider returned {configuration.Errors.ToErrorString()}"); + SetPipelineError(configuration.Errors); + return; + } + + SetServiceProviderConfigurationForThisRequest(configuration.Data.ServiceProviderConfiguration); + + _logger.LogDebug("upstream url path is {upstreamUrlPath}", upstreamUrlPath); + + var downstreamRoute = _downstreamRouteFinder.FindDownstreamRoute(upstreamUrlPath, context.Request.Method, configuration.Data); + + if (downstreamRoute.IsError) + { + _logger.LogError($"{MiddlewareName} setting pipeline errors. IDownstreamRouteFinder returned {downstreamRoute.Errors.ToErrorString()}"); + + SetPipelineError(downstreamRoute.Errors); + return; + } + + _logger.LogDebug("downstream template is {downstreamRoute.Data.ReRoute.DownstreamPath}", downstreamRoute.Data.ReRoute.DownstreamPathTemplate); + + SetDownstreamRouteForThisRequest(downstreamRoute.Data); + + await _next.Invoke(context); + } + } } \ No newline at end of file diff --git a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddlewareExtensions.cs b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddlewareExtensions.cs index 0cd277585..81d2dd2d2 100644 --- a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddlewareExtensions.cs +++ b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddlewareExtensions.cs @@ -1,12 +1,12 @@ -using Microsoft.AspNetCore.Builder; - -namespace Ocelot.DownstreamRouteFinder.Middleware -{ - public static class DownstreamRouteFinderMiddlewareExtensions - { - public static IApplicationBuilder UseDownstreamRouteFinderMiddleware(this IApplicationBuilder builder) - { - return builder.UseMiddleware(); - } - } +using Microsoft.AspNetCore.Builder; + +namespace Ocelot.DownstreamRouteFinder.Middleware +{ + public static class DownstreamRouteFinderMiddlewareExtensions + { + public static IApplicationBuilder UseDownstreamRouteFinderMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } } \ No newline at end of file diff --git a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IUrlPathPlaceholderNameAndValueFinder.cs b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IUrlPathPlaceholderNameAndValueFinder.cs index 678b10810..d2cb41676 100644 --- a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IUrlPathPlaceholderNameAndValueFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IUrlPathPlaceholderNameAndValueFinder.cs @@ -1,10 +1,10 @@ -using System.Collections.Generic; -using Ocelot.Responses; - -namespace Ocelot.DownstreamRouteFinder.UrlMatcher -{ - public interface IPlaceholderNameAndValueFinder - { - Response> Find(string path, string pathTemplate); - } -} +using System.Collections.Generic; +using Ocelot.Responses; + +namespace Ocelot.DownstreamRouteFinder.UrlMatcher +{ + public interface IPlaceholderNameAndValueFinder + { + Response> Find(string path, string pathTemplate); + } +} diff --git a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IUrlPathToUrlTemplateMatcher.cs b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IUrlPathToUrlTemplateMatcher.cs index 6c8956a29..1a9f147af 100644 --- a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IUrlPathToUrlTemplateMatcher.cs +++ b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IUrlPathToUrlTemplateMatcher.cs @@ -1,9 +1,9 @@ -using Ocelot.Responses; - -namespace Ocelot.DownstreamRouteFinder.UrlMatcher -{ - public interface IUrlPathToUrlTemplateMatcher - { - Response Match(string upstreamUrlPath, string upstreamUrlPathTemplate); - } +using Ocelot.Responses; + +namespace Ocelot.DownstreamRouteFinder.UrlMatcher +{ + public interface IUrlPathToUrlTemplateMatcher + { + Response Match(string upstreamUrlPath, string upstreamUrlPathTemplate); + } } \ No newline at end of file diff --git a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcher.cs b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcher.cs index 415ee556a..793d33cf8 100644 --- a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcher.cs +++ b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcher.cs @@ -1,17 +1,17 @@ -using System.Text.RegularExpressions; -using Ocelot.Responses; - -namespace Ocelot.DownstreamRouteFinder.UrlMatcher -{ - public class RegExUrlMatcher : IUrlPathToUrlTemplateMatcher - { - public Response Match(string upstreamUrlPath, string upstreamUrlPathTemplate) - { - var regex = new Regex(upstreamUrlPathTemplate); - - return regex.IsMatch(upstreamUrlPath) - ? new OkResponse(new UrlMatch(true)) - : new OkResponse(new UrlMatch(false)); - } - } -} +using System.Text.RegularExpressions; +using Ocelot.Responses; + +namespace Ocelot.DownstreamRouteFinder.UrlMatcher +{ + public class RegExUrlMatcher : IUrlPathToUrlTemplateMatcher + { + public Response Match(string upstreamUrlPath, string upstreamUrlPathTemplate) + { + var regex = new Regex(upstreamUrlPathTemplate); + + return regex.IsMatch(upstreamUrlPath) + ? new OkResponse(new UrlMatch(true)) + : new OkResponse(new UrlMatch(false)); + } + } +} diff --git a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlMatch.cs b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlMatch.cs index aa2a6f06e..bfbd6fdc1 100644 --- a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlMatch.cs +++ b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlMatch.cs @@ -1,11 +1,11 @@ -namespace Ocelot.DownstreamRouteFinder.UrlMatcher -{ - public class UrlMatch - { - public UrlMatch(bool match) - { - Match = match; - } - public bool Match {get;private set;} - } +namespace Ocelot.DownstreamRouteFinder.UrlMatcher +{ + public class UrlMatch + { + public UrlMatch(bool match) + { + Match = match; + } + public bool Match {get;private set;} + } } \ No newline at end of file diff --git a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValue.cs b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValue.cs index 825f1baba..e299c7709 100644 --- a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValue.cs +++ b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValue.cs @@ -1,13 +1,13 @@ -namespace Ocelot.DownstreamRouteFinder.UrlMatcher -{ - public class PlaceholderNameAndValue - { - public PlaceholderNameAndValue(string name, string value) - { - Name = name; - Value = value; - } - public string Name {get;private set;} - public string Value {get;private set;} - } +namespace Ocelot.DownstreamRouteFinder.UrlMatcher +{ + public class PlaceholderNameAndValue + { + public PlaceholderNameAndValue(string name, string value) + { + Name = name; + Value = value; + } + public string Name {get;private set;} + public string Value {get;private set;} + } } \ No newline at end of file diff --git a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinder.cs b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinder.cs index 8b1c6acf8..70cf2272b 100644 --- a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinder.cs @@ -1,114 +1,114 @@ -using System.Collections.Generic; -using Ocelot.Responses; - -namespace Ocelot.DownstreamRouteFinder.UrlMatcher -{ - public class UrlPathPlaceholderNameAndValueFinder : IPlaceholderNameAndValueFinder - { - public Response> Find(string path, string pathTemplate) - { - var placeHolderNameAndValues = new List(); - - int counterForPath = 0; - - for (int counterForTemplate = 0; counterForTemplate < pathTemplate.Length; counterForTemplate++) - { - if ((path.Length > counterForPath) && CharactersDontMatch(pathTemplate[counterForTemplate], path[counterForPath]) && ContinueScanningUrl(counterForPath,path.Length)) - { - if (IsPlaceholder(pathTemplate[counterForTemplate])) - { - var placeholderName = GetPlaceholderName(pathTemplate, counterForTemplate); - - var placeholderValue = GetPlaceholderValue(pathTemplate, placeholderName, path, counterForPath); - - placeHolderNameAndValues.Add(new PlaceholderNameAndValue(placeholderName, placeholderValue)); - - counterForTemplate = GetNextCounterPosition(pathTemplate, counterForTemplate, '}'); - - counterForPath = GetNextCounterPosition(path, counterForPath, '/'); - - continue; - } - - return new OkResponse>(placeHolderNameAndValues); - } - else if(IsCatchAll(path, counterForPath, pathTemplate)) - { - var endOfPlaceholder = GetNextCounterPosition(pathTemplate, counterForTemplate, '}'); - - var placeholderName = GetPlaceholderName(pathTemplate, 1); - - if(NothingAfterFirstForwardSlash(path)) - { - placeHolderNameAndValues.Add(new PlaceholderNameAndValue(placeholderName, "")); - } - else - { - var placeholderValue = GetPlaceholderValue(pathTemplate, placeholderName, path, counterForPath + 1); - placeHolderNameAndValues.Add(new PlaceholderNameAndValue(placeholderName, placeholderValue)); - } - - counterForTemplate = endOfPlaceholder; - } - counterForPath++; - } - - return new OkResponse>(placeHolderNameAndValues); - } - - private bool IsCatchAll(string path, int counterForPath, string pathTemplate) - { - return string.IsNullOrEmpty(path) || (path.Length > counterForPath && path[counterForPath] == '/') && pathTemplate.Length > 1 - && pathTemplate.Substring(0, 2) == "/{" - && pathTemplate.IndexOf('}') == pathTemplate.Length - 1; - } - - private bool NothingAfterFirstForwardSlash(string path) - { - return path.Length == 1 || path.Length == 0; - } - - private string GetPlaceholderValue(string urlPathTemplate, string variableName, string urlPath, int counterForUrl) - { - var positionOfNextSlash = urlPath.IndexOf('/', counterForUrl); - - if (positionOfNextSlash == -1 || urlPathTemplate.Trim('/').EndsWith(variableName)) - { - positionOfNextSlash = urlPath.Length; - } - - var variableValue = urlPath.Substring(counterForUrl, positionOfNextSlash - counterForUrl); - - return variableValue; - } - - private string GetPlaceholderName(string urlPathTemplate, int counterForTemplate) - { - var postitionOfPlaceHolderClosingBracket = urlPathTemplate.IndexOf('}', counterForTemplate) + 1; - - var variableName = urlPathTemplate.Substring(counterForTemplate, postitionOfPlaceHolderClosingBracket - counterForTemplate); - - return variableName; - } - private int GetNextCounterPosition(string urlTemplate, int counterForTemplate, char delimiter) - { - var closingPlaceHolderPositionOnTemplate = urlTemplate.IndexOf(delimiter, counterForTemplate); - return closingPlaceHolderPositionOnTemplate + 1; - } - - private bool CharactersDontMatch(char characterOne, char characterTwo) - { - return char.ToLower(characterOne) != char.ToLower(characterTwo); - } - - private bool ContinueScanningUrl(int counterForUrl, int urlLength) - { - return counterForUrl < urlLength; - } - - private bool IsPlaceholder(char character) - { - return character == '{'; - } - } +using System.Collections.Generic; +using Ocelot.Responses; + +namespace Ocelot.DownstreamRouteFinder.UrlMatcher +{ + public class UrlPathPlaceholderNameAndValueFinder : IPlaceholderNameAndValueFinder + { + public Response> Find(string path, string pathTemplate) + { + var placeHolderNameAndValues = new List(); + + int counterForPath = 0; + + for (int counterForTemplate = 0; counterForTemplate < pathTemplate.Length; counterForTemplate++) + { + if ((path.Length > counterForPath) && CharactersDontMatch(pathTemplate[counterForTemplate], path[counterForPath]) && ContinueScanningUrl(counterForPath,path.Length)) + { + if (IsPlaceholder(pathTemplate[counterForTemplate])) + { + var placeholderName = GetPlaceholderName(pathTemplate, counterForTemplate); + + var placeholderValue = GetPlaceholderValue(pathTemplate, placeholderName, path, counterForPath); + + placeHolderNameAndValues.Add(new PlaceholderNameAndValue(placeholderName, placeholderValue)); + + counterForTemplate = GetNextCounterPosition(pathTemplate, counterForTemplate, '}'); + + counterForPath = GetNextCounterPosition(path, counterForPath, '/'); + + continue; + } + + return new OkResponse>(placeHolderNameAndValues); + } + else if(IsCatchAll(path, counterForPath, pathTemplate)) + { + var endOfPlaceholder = GetNextCounterPosition(pathTemplate, counterForTemplate, '}'); + + var placeholderName = GetPlaceholderName(pathTemplate, 1); + + if(NothingAfterFirstForwardSlash(path)) + { + placeHolderNameAndValues.Add(new PlaceholderNameAndValue(placeholderName, "")); + } + else + { + var placeholderValue = GetPlaceholderValue(pathTemplate, placeholderName, path, counterForPath + 1); + placeHolderNameAndValues.Add(new PlaceholderNameAndValue(placeholderName, placeholderValue)); + } + + counterForTemplate = endOfPlaceholder; + } + counterForPath++; + } + + return new OkResponse>(placeHolderNameAndValues); + } + + private bool IsCatchAll(string path, int counterForPath, string pathTemplate) + { + return string.IsNullOrEmpty(path) || (path.Length > counterForPath && path[counterForPath] == '/') && pathTemplate.Length > 1 + && pathTemplate.Substring(0, 2) == "/{" + && pathTemplate.IndexOf('}') == pathTemplate.Length - 1; + } + + private bool NothingAfterFirstForwardSlash(string path) + { + return path.Length == 1 || path.Length == 0; + } + + private string GetPlaceholderValue(string urlPathTemplate, string variableName, string urlPath, int counterForUrl) + { + var positionOfNextSlash = urlPath.IndexOf('/', counterForUrl); + + if (positionOfNextSlash == -1 || urlPathTemplate.Trim('/').EndsWith(variableName)) + { + positionOfNextSlash = urlPath.Length; + } + + var variableValue = urlPath.Substring(counterForUrl, positionOfNextSlash - counterForUrl); + + return variableValue; + } + + private string GetPlaceholderName(string urlPathTemplate, int counterForTemplate) + { + var postitionOfPlaceHolderClosingBracket = urlPathTemplate.IndexOf('}', counterForTemplate) + 1; + + var variableName = urlPathTemplate.Substring(counterForTemplate, postitionOfPlaceHolderClosingBracket - counterForTemplate); + + return variableName; + } + private int GetNextCounterPosition(string urlTemplate, int counterForTemplate, char delimiter) + { + var closingPlaceHolderPositionOnTemplate = urlTemplate.IndexOf(delimiter, counterForTemplate); + return closingPlaceHolderPositionOnTemplate + 1; + } + + private bool CharactersDontMatch(char characterOne, char characterTwo) + { + return char.ToLower(characterOne) != char.ToLower(characterTwo); + } + + private bool ContinueScanningUrl(int counterForUrl, int urlLength) + { + return counterForUrl < urlLength; + } + + private bool IsPlaceholder(char character) + { + return character == '{'; + } + } } \ No newline at end of file diff --git a/src/Ocelot/DownstreamUrlCreator/DownstreamHostNullOrEmptyError.cs b/src/Ocelot/DownstreamUrlCreator/DownstreamHostNullOrEmptyError.cs index 8978f6650..d56532a04 100644 --- a/src/Ocelot/DownstreamUrlCreator/DownstreamHostNullOrEmptyError.cs +++ b/src/Ocelot/DownstreamUrlCreator/DownstreamHostNullOrEmptyError.cs @@ -1,12 +1,12 @@ -using Ocelot.Errors; - -namespace Ocelot.DownstreamUrlCreator -{ - public class DownstreamHostNullOrEmptyError : Error - { - public DownstreamHostNullOrEmptyError() - : base("downstream host was null or empty", OcelotErrorCode.DownstreamHostNullOrEmptyError) - { - } - } +using Ocelot.Errors; + +namespace Ocelot.DownstreamUrlCreator +{ + public class DownstreamHostNullOrEmptyError : Error + { + public DownstreamHostNullOrEmptyError() + : base("downstream host was null or empty", OcelotErrorCode.DownstreamHostNullOrEmptyError) + { + } + } } \ No newline at end of file diff --git a/src/Ocelot/DownstreamUrlCreator/DownstreamPathNullOrEmptyError.cs b/src/Ocelot/DownstreamUrlCreator/DownstreamPathNullOrEmptyError.cs index fbc1a5f53..69528d432 100644 --- a/src/Ocelot/DownstreamUrlCreator/DownstreamPathNullOrEmptyError.cs +++ b/src/Ocelot/DownstreamUrlCreator/DownstreamPathNullOrEmptyError.cs @@ -1,12 +1,12 @@ -using Ocelot.Errors; - -namespace Ocelot.DownstreamUrlCreator -{ - public class DownstreamPathNullOrEmptyError : Error - { - public DownstreamPathNullOrEmptyError() - : base("downstream path was null or empty", OcelotErrorCode.DownstreamPathNullOrEmptyError) - { - } - } +using Ocelot.Errors; + +namespace Ocelot.DownstreamUrlCreator +{ + public class DownstreamPathNullOrEmptyError : Error + { + public DownstreamPathNullOrEmptyError() + : base("downstream path was null or empty", OcelotErrorCode.DownstreamPathNullOrEmptyError) + { + } + } } \ No newline at end of file diff --git a/src/Ocelot/DownstreamUrlCreator/DownstreamSchemeNullOrEmptyError.cs b/src/Ocelot/DownstreamUrlCreator/DownstreamSchemeNullOrEmptyError.cs index e52d3488b..9f83bfee9 100644 --- a/src/Ocelot/DownstreamUrlCreator/DownstreamSchemeNullOrEmptyError.cs +++ b/src/Ocelot/DownstreamUrlCreator/DownstreamSchemeNullOrEmptyError.cs @@ -1,12 +1,12 @@ -using Ocelot.Errors; - -namespace Ocelot.DownstreamUrlCreator -{ - public class DownstreamSchemeNullOrEmptyError : Error - { - public DownstreamSchemeNullOrEmptyError() - : base("downstream scheme was null or empty", OcelotErrorCode.DownstreamSchemeNullOrEmptyError) - { - } - } +using Ocelot.Errors; + +namespace Ocelot.DownstreamUrlCreator +{ + public class DownstreamSchemeNullOrEmptyError : Error + { + public DownstreamSchemeNullOrEmptyError() + : base("downstream scheme was null or empty", OcelotErrorCode.DownstreamSchemeNullOrEmptyError) + { + } + } } \ No newline at end of file diff --git a/src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs b/src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs index 8a07a066c..19fa55a16 100644 --- a/src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs +++ b/src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs @@ -1,10 +1,10 @@ -using Ocelot.Responses; -using Ocelot.Values; - -namespace Ocelot.DownstreamUrlCreator -{ - public interface IUrlBuilder - { - Response Build(string downstreamPath, string downstreamScheme, HostAndPort downstreamHostAndPort); - } -} +using Ocelot.Responses; +using Ocelot.Values; + +namespace Ocelot.DownstreamUrlCreator +{ + public interface IUrlBuilder + { + Response Build(string downstreamPath, string downstreamScheme, ServiceHostAndPort downstreamHostAndPort); + } +} diff --git a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs index bf08ba27e..01a388c55 100644 --- a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs +++ b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs @@ -1,57 +1,57 @@ -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; -using Ocelot.Infrastructure.RequestData; -using Ocelot.Logging; -using Ocelot.Middleware; -using System; - -namespace Ocelot.DownstreamUrlCreator.Middleware -{ - public class DownstreamUrlCreatorMiddleware : OcelotMiddleware - { - private readonly RequestDelegate _next; - private readonly IDownstreamPathPlaceholderReplacer _replacer; - private readonly IOcelotLogger _logger; - private readonly IUrlBuilder _urlBuilder; - - public DownstreamUrlCreatorMiddleware(RequestDelegate next, - IOcelotLoggerFactory loggerFactory, - IDownstreamPathPlaceholderReplacer replacer, - IRequestScopedDataRepository requestScopedDataRepository, - IUrlBuilder urlBuilder) - :base(requestScopedDataRepository) - { - _next = next; - _replacer = replacer; - _urlBuilder = urlBuilder; - _logger = loggerFactory.CreateLogger(); - } - - public async Task Invoke(HttpContext context) - { - var dsPath = _replacer - .Replace(DownstreamRoute.ReRoute.DownstreamPathTemplate, DownstreamRoute.TemplatePlaceholderNameAndValues); - - if (dsPath.IsError) - { - _logger.LogDebug("IDownstreamPathPlaceholderReplacer returned an error, setting pipeline error"); - - SetPipelineError(dsPath.Errors); - return; - } - - var uriBuilder = new UriBuilder(DownstreamRequest.RequestUri) - { - Path = dsPath.Data.Value, - Scheme = DownstreamRoute.ReRoute.DownstreamScheme - }; - - DownstreamRequest.RequestUri = uriBuilder.Uri; - - _logger.LogDebug("downstream url is {downstreamUrl.Data.Value}", DownstreamRequest.RequestUri); - - await _next.Invoke(context); - } - } +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; +using Ocelot.Infrastructure.RequestData; +using Ocelot.Logging; +using Ocelot.Middleware; +using System; + +namespace Ocelot.DownstreamUrlCreator.Middleware +{ + public class DownstreamUrlCreatorMiddleware : OcelotMiddleware + { + private readonly RequestDelegate _next; + private readonly IDownstreamPathPlaceholderReplacer _replacer; + private readonly IOcelotLogger _logger; + private readonly IUrlBuilder _urlBuilder; + + public DownstreamUrlCreatorMiddleware(RequestDelegate next, + IOcelotLoggerFactory loggerFactory, + IDownstreamPathPlaceholderReplacer replacer, + IRequestScopedDataRepository requestScopedDataRepository, + IUrlBuilder urlBuilder) + :base(requestScopedDataRepository) + { + _next = next; + _replacer = replacer; + _urlBuilder = urlBuilder; + _logger = loggerFactory.CreateLogger(); + } + + public async Task Invoke(HttpContext context) + { + var dsPath = _replacer + .Replace(DownstreamRoute.ReRoute.DownstreamPathTemplate, DownstreamRoute.TemplatePlaceholderNameAndValues); + + if (dsPath.IsError) + { + _logger.LogDebug("IDownstreamPathPlaceholderReplacer returned an error, setting pipeline error"); + + SetPipelineError(dsPath.Errors); + return; + } + + var uriBuilder = new UriBuilder(DownstreamRequest.RequestUri) + { + Path = dsPath.Data.Value, + Scheme = DownstreamRoute.ReRoute.DownstreamScheme + }; + + DownstreamRequest.RequestUri = uriBuilder.Uri; + + _logger.LogDebug("downstream url is {downstreamUrl.Data.Value}", DownstreamRequest.RequestUri); + + await _next.Invoke(context); + } + } } \ No newline at end of file diff --git a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddlewareExtensions.cs b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddlewareExtensions.cs index 34bfaa54f..238bc7ef6 100644 --- a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddlewareExtensions.cs +++ b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddlewareExtensions.cs @@ -1,12 +1,12 @@ -using Microsoft.AspNetCore.Builder; - -namespace Ocelot.DownstreamUrlCreator.Middleware -{ - public static class DownstreamUrlCreatorMiddlewareExtensions - { - public static IApplicationBuilder UseDownstreamUrlCreatorMiddleware(this IApplicationBuilder builder) - { - return builder.UseMiddleware(); - } - } +using Microsoft.AspNetCore.Builder; + +namespace Ocelot.DownstreamUrlCreator.Middleware +{ + public static class DownstreamUrlCreatorMiddlewareExtensions + { + public static IApplicationBuilder UseDownstreamUrlCreatorMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } } \ No newline at end of file diff --git a/src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs b/src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs index eed7b8d78..80f2507d9 100644 --- a/src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs +++ b/src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs @@ -1,46 +1,46 @@ -using System; -using System.Collections.Generic; -using Ocelot.Errors; -using Ocelot.Responses; -using Ocelot.Values; - -namespace Ocelot.DownstreamUrlCreator -{ - public class UrlBuilder : IUrlBuilder - { - public Response Build(string downstreamPath, string downstreamScheme, HostAndPort downstreamHostAndPort) - { - if (string.IsNullOrEmpty(downstreamPath)) - { - return new ErrorResponse(new List {new DownstreamPathNullOrEmptyError()}); - } - - if (string.IsNullOrEmpty(downstreamScheme)) - { - return new ErrorResponse(new List { new DownstreamSchemeNullOrEmptyError() }); - } - - if (string.IsNullOrEmpty(downstreamHostAndPort.DownstreamHost)) - { - return new ErrorResponse(new List { new DownstreamHostNullOrEmptyError() }); - } - - - var builder = new UriBuilder - { - Host = downstreamHostAndPort.DownstreamHost, - Path = downstreamPath, - Scheme = downstreamScheme - }; - - if (downstreamHostAndPort.DownstreamPort > 0) - { - builder.Port = downstreamHostAndPort.DownstreamPort; - } - - var url = builder.Uri.ToString(); - - return new OkResponse(new DownstreamUrl(url)); - } - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using Ocelot.Errors; +using Ocelot.Responses; +using Ocelot.Values; + +namespace Ocelot.DownstreamUrlCreator +{ + public class UrlBuilder : IUrlBuilder + { + public Response Build(string downstreamPath, string downstreamScheme, ServiceHostAndPort downstreamHostAndPort) + { + if (string.IsNullOrEmpty(downstreamPath)) + { + return new ErrorResponse(new List {new DownstreamPathNullOrEmptyError()}); + } + + if (string.IsNullOrEmpty(downstreamScheme)) + { + return new ErrorResponse(new List { new DownstreamSchemeNullOrEmptyError() }); + } + + if (string.IsNullOrEmpty(downstreamHostAndPort.DownstreamHost)) + { + return new ErrorResponse(new List { new DownstreamHostNullOrEmptyError() }); + } + + + var builder = new UriBuilder + { + Host = downstreamHostAndPort.DownstreamHost, + Path = downstreamPath, + Scheme = downstreamScheme + }; + + if (downstreamHostAndPort.DownstreamPort > 0) + { + builder.Port = downstreamHostAndPort.DownstreamPort; + } + + var url = builder.Uri.ToString(); + + return new OkResponse(new DownstreamUrl(url)); + } + } +} diff --git a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamUrlTemplateVariableReplacer.cs b/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamUrlTemplateVariableReplacer.cs index 1b7448198..dfd70eedb 100644 --- a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamUrlTemplateVariableReplacer.cs +++ b/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamUrlTemplateVariableReplacer.cs @@ -1,25 +1,25 @@ -using System.Collections.Generic; -using System.Text; -using Ocelot.DownstreamRouteFinder.UrlMatcher; -using Ocelot.Responses; -using Ocelot.Values; - -namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer -{ - public class DownstreamTemplatePathPlaceholderReplacer : IDownstreamPathPlaceholderReplacer - { - public Response Replace(PathTemplate downstreamPathTemplate, List urlPathPlaceholderNameAndValues) - { - var downstreamPath = new StringBuilder(); - - downstreamPath.Append(downstreamPathTemplate.Value); - - foreach (var placeholderVariableAndValue in urlPathPlaceholderNameAndValues) - { - downstreamPath.Replace(placeholderVariableAndValue.Name, placeholderVariableAndValue.Value); - } - - return new OkResponse(new DownstreamPath(downstreamPath.ToString())); - } - } +using System.Collections.Generic; +using System.Text; +using Ocelot.DownstreamRouteFinder.UrlMatcher; +using Ocelot.Responses; +using Ocelot.Values; + +namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer +{ + public class DownstreamTemplatePathPlaceholderReplacer : IDownstreamPathPlaceholderReplacer + { + public Response Replace(PathTemplate downstreamPathTemplate, List urlPathPlaceholderNameAndValues) + { + var downstreamPath = new StringBuilder(); + + downstreamPath.Append(downstreamPathTemplate.Value); + + foreach (var placeholderVariableAndValue in urlPathPlaceholderNameAndValues) + { + downstreamPath.Replace(placeholderVariableAndValue.Name, placeholderVariableAndValue.Value); + } + + return new OkResponse(new DownstreamPath(downstreamPath.ToString())); + } + } } \ No newline at end of file diff --git a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/IDownstreamUrlPathTemplateVariableReplacer.cs b/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/IDownstreamUrlPathTemplateVariableReplacer.cs index 46e998d44..9e12b580c 100644 --- a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/IDownstreamUrlPathTemplateVariableReplacer.cs +++ b/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/IDownstreamUrlPathTemplateVariableReplacer.cs @@ -1,12 +1,12 @@ -using System.Collections.Generic; -using Ocelot.DownstreamRouteFinder.UrlMatcher; -using Ocelot.Responses; -using Ocelot.Values; - -namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer -{ - public interface IDownstreamPathPlaceholderReplacer - { - Response Replace(PathTemplate downstreamPathTemplate, List urlPathPlaceholderNameAndValues); - } +using System.Collections.Generic; +using Ocelot.DownstreamRouteFinder.UrlMatcher; +using Ocelot.Responses; +using Ocelot.Values; + +namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer +{ + public interface IDownstreamPathPlaceholderReplacer + { + Response Replace(PathTemplate downstreamPathTemplate, List urlPathPlaceholderNameAndValues); + } } \ No newline at end of file diff --git a/src/Ocelot/Errors/Error.cs b/src/Ocelot/Errors/Error.cs index 9c2145411..520f37753 100644 --- a/src/Ocelot/Errors/Error.cs +++ b/src/Ocelot/Errors/Error.cs @@ -1,19 +1,19 @@ -namespace Ocelot.Errors -{ - public abstract class Error - { - protected Error(string message, OcelotErrorCode code) - { - Message = message; - Code = code; - } - - public string Message { get; private set; } - public OcelotErrorCode Code { get; private set; } - - public override string ToString() - { - return Message; - } - } +namespace Ocelot.Errors +{ + public abstract class Error + { + protected Error(string message, OcelotErrorCode code) + { + Message = message; + Code = code; + } + + public string Message { get; private set; } + public OcelotErrorCode Code { get; private set; } + + public override string ToString() + { + return Message; + } + } } \ No newline at end of file diff --git a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddlewareExtensions.cs b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddlewareExtensions.cs index e355c7f80..14731eb22 100644 --- a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddlewareExtensions.cs +++ b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddlewareExtensions.cs @@ -1,12 +1,12 @@ -using Microsoft.AspNetCore.Builder; - -namespace Ocelot.Errors.Middleware -{ - public static class ExceptionHandlerMiddlewareExtensions - { - public static IApplicationBuilder UseExceptionHandlerMiddleware(this IApplicationBuilder builder) - { - return builder.UseMiddleware(); - } - } -} +using Microsoft.AspNetCore.Builder; + +namespace Ocelot.Errors.Middleware +{ + public static class ExceptionHandlerMiddlewareExtensions + { + public static IApplicationBuilder UseExceptionHandlerMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } +} diff --git a/src/Ocelot/Errors/OcelotErrorCode.cs b/src/Ocelot/Errors/OcelotErrorCode.cs index 3b65a3fdb..e4fba0559 100644 --- a/src/Ocelot/Errors/OcelotErrorCode.cs +++ b/src/Ocelot/Errors/OcelotErrorCode.cs @@ -1,38 +1,38 @@ -namespace Ocelot.Errors -{ - public enum OcelotErrorCode - { - UnauthenticatedError, - UnknownError, - DownstreampathTemplateAlreadyUsedError, - UnableToFindDownstreamRouteError, - CannotAddDataError, - CannotFindDataError, - UnableToCompleteRequestError, - UnableToCreateAuthenticationHandlerError, - UnsupportedAuthenticationProviderError, - CannotFindClaimError, - ParsingConfigurationHeaderError, - NoInstructionsError, - InstructionNotForClaimsError, - UnauthorizedError, - ClaimValueNotAuthorisedError, - ScopeNotAuthorisedError, - UserDoesNotHaveClaimError, - DownstreamPathTemplateContainsSchemeError, - DownstreamPathNullOrEmptyError, - DownstreamSchemeNullOrEmptyError, - DownstreamHostNullOrEmptyError, - ServicesAreNullError, - ServicesAreEmptyError, - UnableToFindServiceDiscoveryProviderError, - UnableToFindLoadBalancerError, - RequestTimedOutError, - UnableToFindQoSProviderError, - UnableToSetConfigInConsulError, - UnmappableRequestError, - RateLimitOptionsError, - PathTemplateDoesntStartWithForwardSlash, - FileValidationFailedError - } -} +namespace Ocelot.Errors +{ + public enum OcelotErrorCode + { + UnauthenticatedError, + UnknownError, + DownstreampathTemplateAlreadyUsedError, + UnableToFindDownstreamRouteError, + CannotAddDataError, + CannotFindDataError, + UnableToCompleteRequestError, + UnableToCreateAuthenticationHandlerError, + UnsupportedAuthenticationProviderError, + CannotFindClaimError, + ParsingConfigurationHeaderError, + NoInstructionsError, + InstructionNotForClaimsError, + UnauthorizedError, + ClaimValueNotAuthorisedError, + ScopeNotAuthorisedError, + UserDoesNotHaveClaimError, + DownstreamPathTemplateContainsSchemeError, + DownstreamPathNullOrEmptyError, + DownstreamSchemeNullOrEmptyError, + DownstreamHostNullOrEmptyError, + ServicesAreNullError, + ServicesAreEmptyError, + UnableToFindServiceDiscoveryProviderError, + UnableToFindLoadBalancerError, + RequestTimedOutError, + UnableToFindQoSProviderError, + UnableToSetConfigInConsulError, + UnmappableRequestError, + RateLimitOptionsError, + PathTemplateDoesntStartWithForwardSlash, + FileValidationFailedError + } +} diff --git a/src/Ocelot/Headers/AddHeadersToRequest.cs b/src/Ocelot/Headers/AddHeadersToRequest.cs index 3c818bcc0..c8ec8cf36 100644 --- a/src/Ocelot/Headers/AddHeadersToRequest.cs +++ b/src/Ocelot/Headers/AddHeadersToRequest.cs @@ -1,43 +1,43 @@ -using System.Collections.Generic; -using System.Linq; -using Ocelot.Configuration; -using Ocelot.Infrastructure.Claims.Parser; -using Ocelot.Responses; -using System.Net.Http; - -namespace Ocelot.Headers -{ - public class AddHeadersToRequest : IAddHeadersToRequest - { - private readonly IClaimsParser _claimsParser; - - public AddHeadersToRequest(IClaimsParser claimsParser) - { - _claimsParser = claimsParser; - } - - public Response SetHeadersOnDownstreamRequest(List claimsToThings, IEnumerable claims, HttpRequestMessage downstreamRequest) - { - foreach (var config in claimsToThings) - { - var value = _claimsParser.GetValue(claims, config.NewKey, config.Delimiter, config.Index); - - if (value.IsError) - { - return new ErrorResponse(value.Errors); - } - - var exists = downstreamRequest.Headers.FirstOrDefault(x => x.Key == config.ExistingKey); - - if (!string.IsNullOrEmpty(exists.Key)) - { - downstreamRequest.Headers.Remove(exists.Key); - } - - downstreamRequest.Headers.Add(config.ExistingKey, value.Data); - } - - return new OkResponse(); - } - } +using System.Collections.Generic; +using System.Linq; +using Ocelot.Configuration; +using Ocelot.Infrastructure.Claims.Parser; +using Ocelot.Responses; +using System.Net.Http; + +namespace Ocelot.Headers +{ + public class AddHeadersToRequest : IAddHeadersToRequest + { + private readonly IClaimsParser _claimsParser; + + public AddHeadersToRequest(IClaimsParser claimsParser) + { + _claimsParser = claimsParser; + } + + public Response SetHeadersOnDownstreamRequest(List claimsToThings, IEnumerable claims, HttpRequestMessage downstreamRequest) + { + foreach (var config in claimsToThings) + { + var value = _claimsParser.GetValue(claims, config.NewKey, config.Delimiter, config.Index); + + if (value.IsError) + { + return new ErrorResponse(value.Errors); + } + + var exists = downstreamRequest.Headers.FirstOrDefault(x => x.Key == config.ExistingKey); + + if (!string.IsNullOrEmpty(exists.Key)) + { + downstreamRequest.Headers.Remove(exists.Key); + } + + downstreamRequest.Headers.Add(config.ExistingKey, value.Data); + } + + return new OkResponse(); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Headers/HttpContextRequestHeaderReplacer.cs b/src/Ocelot/Headers/HttpContextRequestHeaderReplacer.cs index 83be9fe75..02779ca67 100644 --- a/src/Ocelot/Headers/HttpContextRequestHeaderReplacer.cs +++ b/src/Ocelot/Headers/HttpContextRequestHeaderReplacer.cs @@ -1,25 +1,25 @@ -using System.Collections.Generic; -using Microsoft.AspNetCore.Http; -using Ocelot.Configuration; -using Ocelot.Responses; - -namespace Ocelot.Headers -{ - public class HttpContextRequestHeaderReplacer : IHttpContextRequestHeaderReplacer - { - public Response Replace(HttpContext context, List fAndRs) - { - foreach (var f in fAndRs) - { - if(context.Request.Headers.TryGetValue(f.Key, out var values)) - { - var replaced = values[f.Index].Replace(f.Find, f.Replace); - context.Request.Headers.Remove(f.Key); - context.Request.Headers.Add(f.Key, replaced); - } - } - - return new OkResponse(); - } - } +using System.Collections.Generic; +using Microsoft.AspNetCore.Http; +using Ocelot.Configuration; +using Ocelot.Responses; + +namespace Ocelot.Headers +{ + public class HttpContextRequestHeaderReplacer : IHttpContextRequestHeaderReplacer + { + public Response Replace(HttpContext context, List fAndRs) + { + foreach (var f in fAndRs) + { + if(context.Request.Headers.TryGetValue(f.Key, out var values)) + { + var replaced = values[f.Index].Replace(f.Find, f.Replace); + context.Request.Headers.Remove(f.Key); + context.Request.Headers.Add(f.Key, replaced); + } + } + + return new OkResponse(); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Headers/HttpResponseHeaderReplacer.cs b/src/Ocelot/Headers/HttpResponseHeaderReplacer.cs index ef8a4d20e..347923ed1 100644 --- a/src/Ocelot/Headers/HttpResponseHeaderReplacer.cs +++ b/src/Ocelot/Headers/HttpResponseHeaderReplacer.cs @@ -1,57 +1,57 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using Ocelot.Configuration; -using Ocelot.Infrastructure.Extensions; -using Ocelot.Responses; - -namespace Ocelot.Headers -{ - public class HttpResponseHeaderReplacer : IHttpResponseHeaderReplacer - { - private Dictionary> _placeholders; - - public HttpResponseHeaderReplacer() - { - _placeholders = new Dictionary>(); - _placeholders.Add("{DownstreamBaseUrl}", x => { - var downstreamUrl = $"{x.RequestUri.Scheme}://{x.RequestUri.Host}"; - - if(x.RequestUri.Port != 80 && x.RequestUri.Port != 443) - { - downstreamUrl = $"{downstreamUrl}:{x.RequestUri.Port}"; - } - - return $"{downstreamUrl}/"; - }); - } - public Response Replace(HttpResponseMessage response, List fAndRs, HttpRequestMessage httpRequestMessage) - { - foreach (var f in fAndRs) - { - //if the response headers contain a matching find and replace - if(response.Headers.TryGetValues(f.Key, out var values)) - { - //check to see if it is a placeholder in the find... - if(_placeholders.TryGetValue(f.Find, out var replacePlaceholder)) - { - //if it is we need to get the value of the placeholder - var find = replacePlaceholder(httpRequestMessage); - var replaced = values.ToList()[f.Index].Replace(find, f.Replace.LastCharAsForwardSlash()); - response.Headers.Remove(f.Key); - response.Headers.Add(f.Key, replaced); - } - else - { - var replaced = values.ToList()[f.Index].Replace(f.Find, f.Replace); - response.Headers.Remove(f.Key); - response.Headers.Add(f.Key, replaced); - } - } - } - - return new OkResponse(); - } - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using Ocelot.Configuration; +using Ocelot.Infrastructure.Extensions; +using Ocelot.Responses; + +namespace Ocelot.Headers +{ + public class HttpResponseHeaderReplacer : IHttpResponseHeaderReplacer + { + private Dictionary> _placeholders; + + public HttpResponseHeaderReplacer() + { + _placeholders = new Dictionary>(); + _placeholders.Add("{DownstreamBaseUrl}", x => { + var downstreamUrl = $"{x.RequestUri.Scheme}://{x.RequestUri.Host}"; + + if(x.RequestUri.Port != 80 && x.RequestUri.Port != 443) + { + downstreamUrl = $"{downstreamUrl}:{x.RequestUri.Port}"; + } + + return $"{downstreamUrl}/"; + }); + } + public Response Replace(HttpResponseMessage response, List fAndRs, HttpRequestMessage httpRequestMessage) + { + foreach (var f in fAndRs) + { + //if the response headers contain a matching find and replace + if(response.Headers.TryGetValues(f.Key, out var values)) + { + //check to see if it is a placeholder in the find... + if(_placeholders.TryGetValue(f.Find, out var replacePlaceholder)) + { + //if it is we need to get the value of the placeholder + var find = replacePlaceholder(httpRequestMessage); + var replaced = values.ToList()[f.Index].Replace(find, f.Replace.LastCharAsForwardSlash()); + response.Headers.Remove(f.Key); + response.Headers.Add(f.Key, replaced); + } + else + { + var replaced = values.ToList()[f.Index].Replace(f.Find, f.Replace); + response.Headers.Remove(f.Key); + response.Headers.Add(f.Key, replaced); + } + } + } + + return new OkResponse(); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Headers/IAddHeadersToRequest.cs b/src/Ocelot/Headers/IAddHeadersToRequest.cs index cc4e0383c..387b3f01b 100644 --- a/src/Ocelot/Headers/IAddHeadersToRequest.cs +++ b/src/Ocelot/Headers/IAddHeadersToRequest.cs @@ -1,13 +1,13 @@ -namespace Ocelot.Headers -{ - using System.Collections.Generic; - using System.Net.Http; - - using Ocelot.Configuration; - using Ocelot.Responses; - - public interface IAddHeadersToRequest - { - Response SetHeadersOnDownstreamRequest(List claimsToThings, IEnumerable claims, HttpRequestMessage downstreamRequest); - } -} +namespace Ocelot.Headers +{ + using System.Collections.Generic; + using System.Net.Http; + + using Ocelot.Configuration; + using Ocelot.Responses; + + public interface IAddHeadersToRequest + { + Response SetHeadersOnDownstreamRequest(List claimsToThings, IEnumerable claims, HttpRequestMessage downstreamRequest); + } +} diff --git a/src/Ocelot/Headers/IHttpContextRequestHeaderReplacer.cs b/src/Ocelot/Headers/IHttpContextRequestHeaderReplacer.cs index f0e969ec9..5c985cfe1 100644 --- a/src/Ocelot/Headers/IHttpContextRequestHeaderReplacer.cs +++ b/src/Ocelot/Headers/IHttpContextRequestHeaderReplacer.cs @@ -1,12 +1,12 @@ -using System.Collections.Generic; -using Microsoft.AspNetCore.Http; -using Ocelot.Configuration; -using Ocelot.Responses; - -namespace Ocelot.Headers -{ - public interface IHttpContextRequestHeaderReplacer - { - Response Replace(HttpContext context, List fAndRs); - } +using System.Collections.Generic; +using Microsoft.AspNetCore.Http; +using Ocelot.Configuration; +using Ocelot.Responses; + +namespace Ocelot.Headers +{ + public interface IHttpContextRequestHeaderReplacer + { + Response Replace(HttpContext context, List fAndRs); + } } \ No newline at end of file diff --git a/src/Ocelot/Headers/IHttpResponseHeaderReplacer.cs b/src/Ocelot/Headers/IHttpResponseHeaderReplacer.cs index 3a3753180..8e74d1113 100644 --- a/src/Ocelot/Headers/IHttpResponseHeaderReplacer.cs +++ b/src/Ocelot/Headers/IHttpResponseHeaderReplacer.cs @@ -1,12 +1,12 @@ -using System.Collections.Generic; -using System.Net.Http; -using Ocelot.Configuration; -using Ocelot.Responses; - -namespace Ocelot.Headers -{ - public interface IHttpResponseHeaderReplacer - { - Response Replace(HttpResponseMessage response, List fAndRs, HttpRequestMessage httpRequestMessage); - } +using System.Collections.Generic; +using System.Net.Http; +using Ocelot.Configuration; +using Ocelot.Responses; + +namespace Ocelot.Headers +{ + public interface IHttpResponseHeaderReplacer + { + Response Replace(HttpResponseMessage response, List fAndRs, HttpRequestMessage httpRequestMessage); + } } \ No newline at end of file diff --git a/src/Ocelot/Headers/IRemoveOutputHeaders.cs b/src/Ocelot/Headers/IRemoveOutputHeaders.cs index 909d78f95..339d0c359 100644 --- a/src/Ocelot/Headers/IRemoveOutputHeaders.cs +++ b/src/Ocelot/Headers/IRemoveOutputHeaders.cs @@ -1,10 +1,10 @@ -using System.Net.Http.Headers; -using Ocelot.Responses; - -namespace Ocelot.Headers -{ - public interface IRemoveOutputHeaders - { - Response Remove(HttpResponseHeaders headers); - } -} +using System.Net.Http.Headers; +using Ocelot.Responses; + +namespace Ocelot.Headers +{ + public interface IRemoveOutputHeaders + { + Response Remove(HttpResponseHeaders headers); + } +} diff --git a/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs b/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs index 3cf30f3f1..b86cc4d2c 100644 --- a/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs +++ b/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs @@ -1,42 +1,42 @@ -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Ocelot.Infrastructure.RequestData; -using Ocelot.Logging; -using Ocelot.Middleware; - -namespace Ocelot.Headers.Middleware -{ - public class HttpHeadersTransformationMiddleware : OcelotMiddleware - { - private readonly RequestDelegate _next; - private readonly IOcelotLogger _logger; - private readonly IHttpContextRequestHeaderReplacer _preReplacer; - private readonly IHttpResponseHeaderReplacer _postReplacer; - - public HttpHeadersTransformationMiddleware(RequestDelegate next, - IOcelotLoggerFactory loggerFactory, - IRequestScopedDataRepository requestScopedDataRepository, - IHttpContextRequestHeaderReplacer preReplacer, - IHttpResponseHeaderReplacer postReplacer) - : base(requestScopedDataRepository) - { - _next = next; - _postReplacer = postReplacer; - _preReplacer = preReplacer; - _logger = loggerFactory.CreateLogger(); - } - - public async Task Invoke(HttpContext context) - { - var preFAndRs = this.DownstreamRoute.ReRoute.UpstreamHeadersFindAndReplace; - - _preReplacer.Replace(context, preFAndRs); - - await _next.Invoke(context); - - var postFAndRs = this.DownstreamRoute.ReRoute.DownstreamHeadersFindAndReplace; - - _postReplacer.Replace(HttpResponseMessage, postFAndRs, DownstreamRequest); - } - } +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Ocelot.Infrastructure.RequestData; +using Ocelot.Logging; +using Ocelot.Middleware; + +namespace Ocelot.Headers.Middleware +{ + public class HttpHeadersTransformationMiddleware : OcelotMiddleware + { + private readonly RequestDelegate _next; + private readonly IOcelotLogger _logger; + private readonly IHttpContextRequestHeaderReplacer _preReplacer; + private readonly IHttpResponseHeaderReplacer _postReplacer; + + public HttpHeadersTransformationMiddleware(RequestDelegate next, + IOcelotLoggerFactory loggerFactory, + IRequestScopedDataRepository requestScopedDataRepository, + IHttpContextRequestHeaderReplacer preReplacer, + IHttpResponseHeaderReplacer postReplacer) + : base(requestScopedDataRepository) + { + _next = next; + _postReplacer = postReplacer; + _preReplacer = preReplacer; + _logger = loggerFactory.CreateLogger(); + } + + public async Task Invoke(HttpContext context) + { + var preFAndRs = this.DownstreamRoute.ReRoute.UpstreamHeadersFindAndReplace; + + _preReplacer.Replace(context, preFAndRs); + + await _next.Invoke(context); + + var postFAndRs = this.DownstreamRoute.ReRoute.DownstreamHeadersFindAndReplace; + + _postReplacer.Replace(HttpResponseMessage, postFAndRs, DownstreamRequest); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddlewareExtensions.cs b/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddlewareExtensions.cs index 4dc08b4d9..ce920b1a7 100644 --- a/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddlewareExtensions.cs +++ b/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddlewareExtensions.cs @@ -1,12 +1,12 @@ -using Microsoft.AspNetCore.Builder; - -namespace Ocelot.Headers.Middleware -{ - public static class HttpHeadersTransformationMiddlewareExtensions - { - public static IApplicationBuilder UseHttpHeadersTransformationMiddleware(this IApplicationBuilder builder) - { - return builder.UseMiddleware(); - } - } +using Microsoft.AspNetCore.Builder; + +namespace Ocelot.Headers.Middleware +{ + public static class HttpHeadersTransformationMiddlewareExtensions + { + public static IApplicationBuilder UseHttpHeadersTransformationMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddleware.cs b/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddleware.cs index d53cd76da..35d2fe5ab 100644 --- a/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddleware.cs +++ b/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddleware.cs @@ -1,49 +1,49 @@ -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Ocelot.Infrastructure.RequestData; -using Ocelot.Logging; -using Ocelot.Middleware; - -namespace Ocelot.Headers.Middleware -{ - public class HttpRequestHeadersBuilderMiddleware : OcelotMiddleware - { - private readonly RequestDelegate _next; - private readonly IAddHeadersToRequest _addHeadersToRequest; - private readonly IOcelotLogger _logger; - - public HttpRequestHeadersBuilderMiddleware(RequestDelegate next, - IOcelotLoggerFactory loggerFactory, - IRequestScopedDataRepository requestScopedDataRepository, - IAddHeadersToRequest addHeadersToRequest) - : base(requestScopedDataRepository) - { - _next = next; - _addHeadersToRequest = addHeadersToRequest; - _logger = loggerFactory.CreateLogger(); - } - - public async Task Invoke(HttpContext context) - { - if (DownstreamRoute.ReRoute.ClaimsToHeaders.Any()) - { - _logger.LogDebug($"{ DownstreamRoute.ReRoute.DownstreamPathTemplate.Value} has instructions to convert claims to headers"); - - var response = _addHeadersToRequest.SetHeadersOnDownstreamRequest(DownstreamRoute.ReRoute.ClaimsToHeaders, context.User.Claims, DownstreamRequest); - - if (response.IsError) - { - _logger.LogDebug("Error setting headers on context, setting pipeline error"); - - SetPipelineError(response.Errors); - return; - } - - _logger.LogDebug("headers have been set on context"); - } - - await _next.Invoke(context); - } - } -} +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Ocelot.Infrastructure.RequestData; +using Ocelot.Logging; +using Ocelot.Middleware; + +namespace Ocelot.Headers.Middleware +{ + public class HttpRequestHeadersBuilderMiddleware : OcelotMiddleware + { + private readonly RequestDelegate _next; + private readonly IAddHeadersToRequest _addHeadersToRequest; + private readonly IOcelotLogger _logger; + + public HttpRequestHeadersBuilderMiddleware(RequestDelegate next, + IOcelotLoggerFactory loggerFactory, + IRequestScopedDataRepository requestScopedDataRepository, + IAddHeadersToRequest addHeadersToRequest) + : base(requestScopedDataRepository) + { + _next = next; + _addHeadersToRequest = addHeadersToRequest; + _logger = loggerFactory.CreateLogger(); + } + + public async Task Invoke(HttpContext context) + { + if (DownstreamRoute.ReRoute.ClaimsToHeaders.Any()) + { + _logger.LogDebug($"{ DownstreamRoute.ReRoute.DownstreamPathTemplate.Value} has instructions to convert claims to headers"); + + var response = _addHeadersToRequest.SetHeadersOnDownstreamRequest(DownstreamRoute.ReRoute.ClaimsToHeaders, context.User.Claims, DownstreamRequest); + + if (response.IsError) + { + _logger.LogDebug("Error setting headers on context, setting pipeline error"); + + SetPipelineError(response.Errors); + return; + } + + _logger.LogDebug("headers have been set on context"); + } + + await _next.Invoke(context); + } + } +} diff --git a/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddlewareExtensions.cs b/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddlewareExtensions.cs index ce010b141..2a8c50fae 100644 --- a/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddlewareExtensions.cs +++ b/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddlewareExtensions.cs @@ -1,12 +1,12 @@ -using Microsoft.AspNetCore.Builder; - -namespace Ocelot.Headers.Middleware -{ - public static class HttpRequestHeadersBuilderMiddlewareExtensions - { - public static IApplicationBuilder UseHttpRequestHeadersBuilderMiddleware(this IApplicationBuilder builder) - { - return builder.UseMiddleware(); - } - } +using Microsoft.AspNetCore.Builder; + +namespace Ocelot.Headers.Middleware +{ + public static class HttpRequestHeadersBuilderMiddlewareExtensions + { + public static IApplicationBuilder UseHttpRequestHeadersBuilderMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Headers/RemoveOutputHeaders.cs b/src/Ocelot/Headers/RemoveOutputHeaders.cs index f30200367..19d863ebb 100644 --- a/src/Ocelot/Headers/RemoveOutputHeaders.cs +++ b/src/Ocelot/Headers/RemoveOutputHeaders.cs @@ -1,27 +1,27 @@ -using System.Net.Http.Headers; -using Ocelot.Responses; - -namespace Ocelot.Headers -{ - public class RemoveOutputHeaders : IRemoveOutputHeaders - { - /// - /// Some webservers return headers that cannot be forwarded to the client - /// in a given context such as transfer encoding chunked when ASP.NET is not - /// returning the response in this manner - /// - private readonly string[] _unsupportedRequestHeaders = - { - "Transfer-Encoding" - }; - public Response Remove(HttpResponseHeaders headers) - { - foreach (var unsupported in _unsupportedRequestHeaders) - { - headers.Remove(unsupported); - } - - return new OkResponse(); - } - } +using System.Net.Http.Headers; +using Ocelot.Responses; + +namespace Ocelot.Headers +{ + public class RemoveOutputHeaders : IRemoveOutputHeaders + { + /// + /// Some webservers return headers that cannot be forwarded to the client + /// in a given context such as transfer encoding chunked when ASP.NET is not + /// returning the response in this manner + /// + private readonly string[] _unsupportedRequestHeaders = + { + "Transfer-Encoding" + }; + public Response Remove(HttpResponseHeaders headers) + { + foreach (var unsupported in _unsupportedRequestHeaders) + { + headers.Remove(unsupported); + } + + return new OkResponse(); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Infrastructure/Claims/Parser/CannotFindClaimError.cs b/src/Ocelot/Infrastructure/Claims/Parser/CannotFindClaimError.cs index 64a91cb87..5314d394b 100644 --- a/src/Ocelot/Infrastructure/Claims/Parser/CannotFindClaimError.cs +++ b/src/Ocelot/Infrastructure/Claims/Parser/CannotFindClaimError.cs @@ -1,12 +1,12 @@ -namespace Ocelot.Infrastructure.Claims.Parser -{ - using Errors; - - public class CannotFindClaimError : Error - { - public CannotFindClaimError(string message) - : base(message, OcelotErrorCode.CannotFindClaimError) - { - } - } -} +namespace Ocelot.Infrastructure.Claims.Parser +{ + using Errors; + + public class CannotFindClaimError : Error + { + public CannotFindClaimError(string message) + : base(message, OcelotErrorCode.CannotFindClaimError) + { + } + } +} diff --git a/src/Ocelot/Infrastructure/Claims/Parser/ClaimsParser.cs b/src/Ocelot/Infrastructure/Claims/Parser/ClaimsParser.cs index f5f3e3b41..b3a028b33 100644 --- a/src/Ocelot/Infrastructure/Claims/Parser/ClaimsParser.cs +++ b/src/Ocelot/Infrastructure/Claims/Parser/ClaimsParser.cs @@ -1,66 +1,66 @@ -namespace Ocelot.Infrastructure.Claims.Parser -{ - using Errors; - using Responses; - using System.Collections.Generic; - using System.Linq; - using System.Security.Claims; - - public class ClaimsParser : IClaimsParser - { - public Response GetValue(IEnumerable claims, string key, string delimiter, int index) - { - var claimResponse = GetValue(claims, key); - - if (claimResponse.IsError) - { - return claimResponse; - } - - if (string.IsNullOrEmpty(delimiter)) - { - return claimResponse; - } - - var splits = claimResponse.Data.Split(delimiter.ToCharArray()); - - if (splits.Length < index || index < 0) - { - return new ErrorResponse(new List - { - new CannotFindClaimError($"Cannot find claim for key: {key}, delimiter: {delimiter}, index: {index}") - }); - } - - var value = splits[index]; - - return new OkResponse(value); - } - - - public Response> GetValuesByClaimType(IEnumerable claims, string claimType) - { - List values = new List(); - - values.AddRange(claims.Where(x => x.Type == claimType).Select(x => x.Value).ToList()); - - return new OkResponse>(values); - } - - - private Response GetValue(IEnumerable claims, string key) - { - var claim = claims.FirstOrDefault(c => c.Type == key); - - if (claim != null) - { - return new OkResponse(claim.Value); - } - - return new ErrorResponse(new List - { - new CannotFindClaimError($"Cannot find claim for key: {key}") - }); - } - } -} +namespace Ocelot.Infrastructure.Claims.Parser +{ + using Errors; + using Responses; + using System.Collections.Generic; + using System.Linq; + using System.Security.Claims; + + public class ClaimsParser : IClaimsParser + { + public Response GetValue(IEnumerable claims, string key, string delimiter, int index) + { + var claimResponse = GetValue(claims, key); + + if (claimResponse.IsError) + { + return claimResponse; + } + + if (string.IsNullOrEmpty(delimiter)) + { + return claimResponse; + } + + var splits = claimResponse.Data.Split(delimiter.ToCharArray()); + + if (splits.Length < index || index < 0) + { + return new ErrorResponse(new List + { + new CannotFindClaimError($"Cannot find claim for key: {key}, delimiter: {delimiter}, index: {index}") + }); + } + + var value = splits[index]; + + return new OkResponse(value); + } + + + public Response> GetValuesByClaimType(IEnumerable claims, string claimType) + { + List values = new List(); + + values.AddRange(claims.Where(x => x.Type == claimType).Select(x => x.Value).ToList()); + + return new OkResponse>(values); + } + + + private Response GetValue(IEnumerable claims, string key) + { + var claim = claims.FirstOrDefault(c => c.Type == key); + + if (claim != null) + { + return new OkResponse(claim.Value); + } + + return new ErrorResponse(new List + { + new CannotFindClaimError($"Cannot find claim for key: {key}") + }); + } + } +} diff --git a/src/Ocelot/Infrastructure/Claims/Parser/IClaimsParser.cs b/src/Ocelot/Infrastructure/Claims/Parser/IClaimsParser.cs index 1f6e65d38..1575ba20d 100644 --- a/src/Ocelot/Infrastructure/Claims/Parser/IClaimsParser.cs +++ b/src/Ocelot/Infrastructure/Claims/Parser/IClaimsParser.cs @@ -1,12 +1,12 @@ -namespace Ocelot.Infrastructure.Claims.Parser -{ - using System.Collections.Generic; - using System.Security.Claims; - using Responses; - - public interface IClaimsParser - { - Response GetValue(IEnumerable claims, string key, string delimiter, int index); - Response> GetValuesByClaimType(IEnumerable claims, string claimType); - } +namespace Ocelot.Infrastructure.Claims.Parser +{ + using System.Collections.Generic; + using System.Security.Claims; + using Responses; + + public interface IClaimsParser + { + Response GetValue(IEnumerable claims, string key, string delimiter, int index); + Response> GetValuesByClaimType(IEnumerable claims, string claimType); + } } \ No newline at end of file diff --git a/src/Ocelot/Infrastructure/Extensions/StringExtensions.cs b/src/Ocelot/Infrastructure/Extensions/StringExtensions.cs index 2f7d2768c..743c8c9e9 100644 --- a/src/Ocelot/Infrastructure/Extensions/StringExtensions.cs +++ b/src/Ocelot/Infrastructure/Extensions/StringExtensions.cs @@ -1,33 +1,33 @@ -using System; - -namespace Ocelot.Infrastructure.Extensions -{ - public static class StringExtensions - { - public static string TrimStart(this string source, string trim, StringComparison stringComparison = StringComparison.Ordinal) - { - if (source == null) - { - return null; - } - - string s = source; - while (s.StartsWith(trim, stringComparison)) - { - s = s.Substring(trim.Length); - } - - return s; - } - - public static string LastCharAsForwardSlash(this string source) - { - if(source.EndsWith('/')) - { - return source; - } - - return $"{source}/"; - } - } +using System; + +namespace Ocelot.Infrastructure.Extensions +{ + public static class StringExtensions + { + public static string TrimStart(this string source, string trim, StringComparison stringComparison = StringComparison.Ordinal) + { + if (source == null) + { + return null; + } + + string s = source; + while (s.StartsWith(trim, stringComparison)) + { + s = s.Substring(trim.Length); + } + + return s; + } + + public static string LastCharAsForwardSlash(this string source) + { + if(source.EndsWith('/')) + { + return source; + } + + return $"{source}/"; + } + } } \ No newline at end of file diff --git a/src/Ocelot/Infrastructure/RequestData/CannotAddDataError.cs b/src/Ocelot/Infrastructure/RequestData/CannotAddDataError.cs index 806a47e7d..b96db870f 100644 --- a/src/Ocelot/Infrastructure/RequestData/CannotAddDataError.cs +++ b/src/Ocelot/Infrastructure/RequestData/CannotAddDataError.cs @@ -1,11 +1,11 @@ -using Ocelot.Errors; - -namespace Ocelot.Infrastructure.RequestData -{ - public class CannotAddDataError : Error - { - public CannotAddDataError(string message) : base(message, OcelotErrorCode.CannotAddDataError) - { - } - } -} +using Ocelot.Errors; + +namespace Ocelot.Infrastructure.RequestData +{ + public class CannotAddDataError : Error + { + public CannotAddDataError(string message) : base(message, OcelotErrorCode.CannotAddDataError) + { + } + } +} diff --git a/src/Ocelot/Infrastructure/RequestData/CannotFindDataError.cs b/src/Ocelot/Infrastructure/RequestData/CannotFindDataError.cs index 75a851299..d80230ead 100644 --- a/src/Ocelot/Infrastructure/RequestData/CannotFindDataError.cs +++ b/src/Ocelot/Infrastructure/RequestData/CannotFindDataError.cs @@ -1,11 +1,11 @@ -using Ocelot.Errors; - -namespace Ocelot.Infrastructure.RequestData -{ - public class CannotFindDataError : Error - { - public CannotFindDataError(string message) : base(message, OcelotErrorCode.CannotFindDataError) - { - } - } -} +using Ocelot.Errors; + +namespace Ocelot.Infrastructure.RequestData +{ + public class CannotFindDataError : Error + { + public CannotFindDataError(string message) : base(message, OcelotErrorCode.CannotFindDataError) + { + } + } +} diff --git a/src/Ocelot/Infrastructure/RequestData/HttpDataRepository.cs b/src/Ocelot/Infrastructure/RequestData/HttpDataRepository.cs index 94f7e9c80..0f75411de 100644 --- a/src/Ocelot/Infrastructure/RequestData/HttpDataRepository.cs +++ b/src/Ocelot/Infrastructure/RequestData/HttpDataRepository.cs @@ -1,74 +1,74 @@ -using System; -using System.Collections.Generic; -using Microsoft.AspNetCore.Http; -using Ocelot.Errors; -using Ocelot.Responses; - -namespace Ocelot.Infrastructure.RequestData -{ - public class HttpDataRepository : IRequestScopedDataRepository - { - private readonly IHttpContextAccessor _httpContextAccessor; - - public HttpDataRepository(IHttpContextAccessor httpContextAccessor) - { - _httpContextAccessor = httpContextAccessor; - } - - public Response Add(string key, T value) - { - try - { - _httpContextAccessor.HttpContext.Items.Add(key, value); - return new OkResponse(); - } - catch (Exception exception) - { - return new ErrorResponse(new List - { - new CannotAddDataError(string.Format($"Unable to add data for key: {key}, exception: {exception.Message}")) - }); - } - } - - public Response Update(string key, T value) - { - try - { - _httpContextAccessor.HttpContext.Items[key] = value; - return new OkResponse(); - } - catch (Exception exception) - { - return new ErrorResponse(new List - { - new CannotAddDataError(string.Format($"Unable to update data for key: {key}, exception: {exception.Message}")) - }); - } - } - - public Response Get(string key) - { - object obj; - - if(_httpContextAccessor.HttpContext == null || _httpContextAccessor.HttpContext.Items == null) - { - return new ErrorResponse(new List - { - new CannotFindDataError($"Unable to find data for key: {key} because HttpContext or HttpContext.Items is null") - }); - } - - if(_httpContextAccessor.HttpContext.Items.TryGetValue(key, out obj)) - { - var data = (T) obj; - return new OkResponse(data); - } - - return new ErrorResponse(new List - { - new CannotFindDataError($"Unable to find data for key: {key}") - }); - } - } -} +using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Http; +using Ocelot.Errors; +using Ocelot.Responses; + +namespace Ocelot.Infrastructure.RequestData +{ + public class HttpDataRepository : IRequestScopedDataRepository + { + private readonly IHttpContextAccessor _httpContextAccessor; + + public HttpDataRepository(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } + + public Response Add(string key, T value) + { + try + { + _httpContextAccessor.HttpContext.Items.Add(key, value); + return new OkResponse(); + } + catch (Exception exception) + { + return new ErrorResponse(new List + { + new CannotAddDataError(string.Format($"Unable to add data for key: {key}, exception: {exception.Message}")) + }); + } + } + + public Response Update(string key, T value) + { + try + { + _httpContextAccessor.HttpContext.Items[key] = value; + return new OkResponse(); + } + catch (Exception exception) + { + return new ErrorResponse(new List + { + new CannotAddDataError(string.Format($"Unable to update data for key: {key}, exception: {exception.Message}")) + }); + } + } + + public Response Get(string key) + { + object obj; + + if(_httpContextAccessor.HttpContext == null || _httpContextAccessor.HttpContext.Items == null) + { + return new ErrorResponse(new List + { + new CannotFindDataError($"Unable to find data for key: {key} because HttpContext or HttpContext.Items is null") + }); + } + + if(_httpContextAccessor.HttpContext.Items.TryGetValue(key, out obj)) + { + var data = (T) obj; + return new OkResponse(data); + } + + return new ErrorResponse(new List + { + new CannotFindDataError($"Unable to find data for key: {key}") + }); + } + } +} diff --git a/src/Ocelot/Infrastructure/RequestData/IRequestScopedDataRepository.cs b/src/Ocelot/Infrastructure/RequestData/IRequestScopedDataRepository.cs index a1421e111..1431b4468 100644 --- a/src/Ocelot/Infrastructure/RequestData/IRequestScopedDataRepository.cs +++ b/src/Ocelot/Infrastructure/RequestData/IRequestScopedDataRepository.cs @@ -1,11 +1,11 @@ -using Ocelot.Responses; - -namespace Ocelot.Infrastructure.RequestData -{ - public interface IRequestScopedDataRepository - { - Response Add(string key, T value); - Response Update(string key, T value); - Response Get(string key); - } +using Ocelot.Responses; + +namespace Ocelot.Infrastructure.RequestData +{ + public interface IRequestScopedDataRepository + { + Response Add(string key, T value); + Response Update(string key, T value); + Response Get(string key); + } } \ No newline at end of file diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs index 77dc328d5..90ea703db 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs @@ -1,12 +1,12 @@ -using System.Threading.Tasks; -using Ocelot.Responses; -using Ocelot.Values; - -namespace Ocelot.LoadBalancer.LoadBalancers -{ - public interface ILoadBalancer - { - Task> Lease(); - void Release(HostAndPort hostAndPort); - } -} \ No newline at end of file +using System.Threading.Tasks; +using Ocelot.Responses; +using Ocelot.Values; + +namespace Ocelot.LoadBalancer.LoadBalancers +{ + public interface ILoadBalancer + { + Task> Lease(); + void Release(ServiceHostAndPort hostAndPort); + } +} diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerFactory.cs b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerFactory.cs index cc7bc6a62..8a9803fbd 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerFactory.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerFactory.cs @@ -1,10 +1,10 @@ -using System.Threading.Tasks; -using Ocelot.Configuration; - -namespace Ocelot.LoadBalancer.LoadBalancers -{ - public interface ILoadBalancerFactory - { - Task Get(ReRoute reRoute, ServiceProviderConfiguration config); - } +using System.Threading.Tasks; +using Ocelot.Configuration; + +namespace Ocelot.LoadBalancer.LoadBalancers +{ + public interface ILoadBalancerFactory + { + Task Get(ReRoute reRoute, ServiceProviderConfiguration config); + } } \ No newline at end of file diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerHouse.cs b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerHouse.cs index 558861cf4..a4711fbe8 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerHouse.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerHouse.cs @@ -1,11 +1,11 @@ -using System.Threading.Tasks; -using Ocelot.Configuration; -using Ocelot.Responses; - -namespace Ocelot.LoadBalancer.LoadBalancers -{ - public interface ILoadBalancerHouse - { - Task> Get(ReRoute reRoute, ServiceProviderConfiguration config); - } +using System.Threading.Tasks; +using Ocelot.Configuration; +using Ocelot.Responses; + +namespace Ocelot.LoadBalancer.LoadBalancers +{ + public interface ILoadBalancerHouse + { + Task> Get(ReRoute reRoute, ServiceProviderConfiguration config); + } } \ No newline at end of file diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/Lease.cs b/src/Ocelot/LoadBalancer/LoadBalancers/Lease.cs index bd2e1b885..583b30023 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/Lease.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/Lease.cs @@ -1,15 +1,15 @@ -using Ocelot.Values; - -namespace Ocelot.LoadBalancer.LoadBalancers -{ - public class Lease - { - public Lease(HostAndPort hostAndPort, int connections) - { - HostAndPort = hostAndPort; - Connections = connections; - } - public HostAndPort HostAndPort { get; private set; } - public int Connections { get; private set; } - } -} \ No newline at end of file +using Ocelot.Values; + +namespace Ocelot.LoadBalancer.LoadBalancers +{ + public class Lease + { + public Lease(ServiceHostAndPort hostAndPort, int connections) + { + HostAndPort = hostAndPort; + Connections = connections; + } + public ServiceHostAndPort HostAndPort { get; private set; } + public int Connections { get; private set; } + } +} diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnection.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnection.cs index 0afec16b8..1c99c3c5c 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnection.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnection.cs @@ -1,145 +1,145 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Ocelot.Errors; -using Ocelot.Responses; -using Ocelot.Values; - -namespace Ocelot.LoadBalancer.LoadBalancers -{ - public class LeastConnection : ILoadBalancer - { - private readonly Func>> _services; - private readonly List _leases; - private readonly string _serviceName; - private static readonly object _syncLock = new object(); - - public LeastConnection(Func>> services, string serviceName) - { - _services = services; - _serviceName = serviceName; - _leases = new List(); - } - - public async Task> Lease() - { - var services = await _services.Invoke(); - - if (services == null) - { - return new ErrorResponse(new List() { new ServicesAreNullError($"services were null for {_serviceName}") }); - } - - if (!services.Any()) - { - return new ErrorResponse(new List() { new ServicesAreEmptyError($"services were empty for {_serviceName}") }); - } - - lock(_syncLock) - { - //todo - maybe this should be moved somewhere else...? Maybe on a repeater on seperate thread? loop every second and update or something? - UpdateServices(services); - - var leaseWithLeastConnections = GetLeaseWithLeastConnections(); - - _leases.Remove(leaseWithLeastConnections); - - leaseWithLeastConnections = AddConnection(leaseWithLeastConnections); - - _leases.Add(leaseWithLeastConnections); - - return new OkResponse(new HostAndPort(leaseWithLeastConnections.HostAndPort.DownstreamHost, leaseWithLeastConnections.HostAndPort.DownstreamPort)); - } - } - - public void Release(HostAndPort hostAndPort) - { - lock(_syncLock) - { - var matchingLease = _leases.FirstOrDefault(l => l.HostAndPort.DownstreamHost == hostAndPort.DownstreamHost - && l.HostAndPort.DownstreamPort == hostAndPort.DownstreamPort); - - if (matchingLease != null) - { - var replacementLease = new Lease(hostAndPort, matchingLease.Connections - 1); - - _leases.Remove(matchingLease); - - _leases.Add(replacementLease); - } - } - } - - private Lease AddConnection(Lease lease) - { - return new Lease(lease.HostAndPort, lease.Connections + 1); - } - - private Lease GetLeaseWithLeastConnections() - { - //now get the service with the least connections? - Lease leaseWithLeastConnections = null; - - for (var i = 0; i < _leases.Count; i++) - { - if (i == 0) - { - leaseWithLeastConnections = _leases[i]; - } - else - { - if (_leases[i].Connections < leaseWithLeastConnections.Connections) - { - leaseWithLeastConnections = _leases[i]; - } - } - } - - return leaseWithLeastConnections; - } - - private Response UpdateServices(List services) - { - if (_leases.Count > 0) - { - var leasesToRemove = new List(); - - foreach (var lease in _leases) - { - var match = services.FirstOrDefault(s => s.HostAndPort.DownstreamHost == lease.HostAndPort.DownstreamHost - && s.HostAndPort.DownstreamPort == lease.HostAndPort.DownstreamPort); - - if (match == null) - { - leasesToRemove.Add(lease); - } - } - - foreach (var lease in leasesToRemove) - { - _leases.Remove(lease); - } - - foreach (var service in services) - { - var exists = _leases.FirstOrDefault(l => l.HostAndPort.DownstreamHost == service.HostAndPort.DownstreamHost && l.HostAndPort.DownstreamPort == service.HostAndPort.DownstreamPort); - - if (exists == null) - { - _leases.Add(new Lease(service.HostAndPort, 0)); - } - } - } - else - { - foreach (var service in services) - { - _leases.Add(new Lease(service.HostAndPort, 0)); - } - } - - return new OkResponse(); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Ocelot.Errors; +using Ocelot.Responses; +using Ocelot.Values; + +namespace Ocelot.LoadBalancer.LoadBalancers +{ + public class LeastConnection : ILoadBalancer + { + private readonly Func>> _services; + private readonly List _leases; + private readonly string _serviceName; + private static readonly object _syncLock = new object(); + + public LeastConnection(Func>> services, string serviceName) + { + _services = services; + _serviceName = serviceName; + _leases = new List(); + } + + public async Task> Lease() + { + var services = await _services.Invoke(); + + if (services == null) + { + return new ErrorResponse(new List() { new ServicesAreNullError($"services were null for {_serviceName}") }); + } + + if (!services.Any()) + { + return new ErrorResponse(new List() { new ServicesAreEmptyError($"services were empty for {_serviceName}") }); + } + + lock(_syncLock) + { + //todo - maybe this should be moved somewhere else...? Maybe on a repeater on seperate thread? loop every second and update or something? + UpdateServices(services); + + var leaseWithLeastConnections = GetLeaseWithLeastConnections(); + + _leases.Remove(leaseWithLeastConnections); + + leaseWithLeastConnections = AddConnection(leaseWithLeastConnections); + + _leases.Add(leaseWithLeastConnections); + + return new OkResponse(new ServiceHostAndPort(leaseWithLeastConnections.HostAndPort.DownstreamHost, leaseWithLeastConnections.HostAndPort.DownstreamPort)); + } + } + + public void Release(ServiceHostAndPort hostAndPort) + { + lock(_syncLock) + { + var matchingLease = _leases.FirstOrDefault(l => l.HostAndPort.DownstreamHost == hostAndPort.DownstreamHost + && l.HostAndPort.DownstreamPort == hostAndPort.DownstreamPort); + + if (matchingLease != null) + { + var replacementLease = new Lease(hostAndPort, matchingLease.Connections - 1); + + _leases.Remove(matchingLease); + + _leases.Add(replacementLease); + } + } + } + + private Lease AddConnection(Lease lease) + { + return new Lease(lease.HostAndPort, lease.Connections + 1); + } + + private Lease GetLeaseWithLeastConnections() + { + //now get the service with the least connections? + Lease leaseWithLeastConnections = null; + + for (var i = 0; i < _leases.Count; i++) + { + if (i == 0) + { + leaseWithLeastConnections = _leases[i]; + } + else + { + if (_leases[i].Connections < leaseWithLeastConnections.Connections) + { + leaseWithLeastConnections = _leases[i]; + } + } + } + + return leaseWithLeastConnections; + } + + private Response UpdateServices(List services) + { + if (_leases.Count > 0) + { + var leasesToRemove = new List(); + + foreach (var lease in _leases) + { + var match = services.FirstOrDefault(s => s.HostAndPort.DownstreamHost == lease.HostAndPort.DownstreamHost + && s.HostAndPort.DownstreamPort == lease.HostAndPort.DownstreamPort); + + if (match == null) + { + leasesToRemove.Add(lease); + } + } + + foreach (var lease in leasesToRemove) + { + _leases.Remove(lease); + } + + foreach (var service in services) + { + var exists = _leases.FirstOrDefault(l => l.HostAndPort.DownstreamHost == service.HostAndPort.DownstreamHost && l.HostAndPort.DownstreamPort == service.HostAndPort.DownstreamPort); + + if (exists == null) + { + _leases.Add(new Lease(service.HostAndPort, 0)); + } + } + } + else + { + foreach (var service in services) + { + _leases.Add(new Lease(service.HostAndPort, 0)); + } + } + + return new OkResponse(); + } + } +} diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs index b3d686729..3a583b8cc 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs @@ -1,30 +1,30 @@ -using System.Threading.Tasks; -using Ocelot.Configuration; -using Ocelot.ServiceDiscovery; - -namespace Ocelot.LoadBalancer.LoadBalancers -{ - public class LoadBalancerFactory : ILoadBalancerFactory - { - private readonly IServiceDiscoveryProviderFactory _serviceProviderFactory; - public LoadBalancerFactory(IServiceDiscoveryProviderFactory serviceProviderFactory) - { - _serviceProviderFactory = serviceProviderFactory; - } - - public async Task Get(ReRoute reRoute, ServiceProviderConfiguration config) - { - var serviceProvider = _serviceProviderFactory.Get(config, reRoute); - - switch (reRoute.LoadBalancer) - { - case "RoundRobin": - return new RoundRobin(async () => await serviceProvider.Get()); - case "LeastConnection": - return new LeastConnection(async () => await serviceProvider.Get(), reRoute.ServiceName); - default: - return new NoLoadBalancer(await serviceProvider.Get()); - } - } - } -} +using System.Threading.Tasks; +using Ocelot.Configuration; +using Ocelot.ServiceDiscovery; + +namespace Ocelot.LoadBalancer.LoadBalancers +{ + public class LoadBalancerFactory : ILoadBalancerFactory + { + private readonly IServiceDiscoveryProviderFactory _serviceProviderFactory; + public LoadBalancerFactory(IServiceDiscoveryProviderFactory serviceProviderFactory) + { + _serviceProviderFactory = serviceProviderFactory; + } + + public async Task Get(ReRoute reRoute, ServiceProviderConfiguration config) + { + var serviceProvider = _serviceProviderFactory.Get(config, reRoute); + + switch (reRoute.LoadBalancer) + { + case "RoundRobin": + return new RoundRobin(async () => await serviceProvider.Get()); + case "LeastConnection": + return new LeastConnection(async () => await serviceProvider.Get(), reRoute.ServiceName); + default: + return new NoLoadBalancer(await serviceProvider.Get()); + } + } + } +} diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs index 9e0ef2967..7fc1ae055 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs @@ -1,56 +1,56 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Threading.Tasks; -using Ocelot.Configuration; -using Ocelot.Responses; - -namespace Ocelot.LoadBalancer.LoadBalancers -{ - public class LoadBalancerHouse : ILoadBalancerHouse - { - private readonly ILoadBalancerFactory _factory; - private readonly ConcurrentDictionary _loadBalancers; - - public LoadBalancerHouse(ILoadBalancerFactory factory) - { - _factory = factory; - _loadBalancers = new ConcurrentDictionary(); - } - - public async Task> Get(ReRoute reRoute, ServiceProviderConfiguration config) - { - try - { - if(_loadBalancers.TryGetValue(reRoute.ReRouteKey, out var loadBalancer)) - { - loadBalancer = _loadBalancers[reRoute.ReRouteKey]; - - if(reRoute.LoadBalancer != loadBalancer.GetType().Name) - { - loadBalancer = await _factory.Get(reRoute, config); - AddLoadBalancer(reRoute.ReRouteKey, loadBalancer); - } - - return new OkResponse(loadBalancer); - } - - loadBalancer = await _factory.Get(reRoute, config); - AddLoadBalancer(reRoute.ReRouteKey, loadBalancer); - return new OkResponse(loadBalancer); - } - catch(Exception ex) - { - return new ErrorResponse(new List() - { - new UnableToFindLoadBalancerError($"unabe to find load balancer for {reRoute.ReRouteKey} exception is {ex}") - }); - } - } - - private void AddLoadBalancer(string key, ILoadBalancer loadBalancer) - { - _loadBalancers.AddOrUpdate(key, loadBalancer, (x, y) => loadBalancer); - } - } -} +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading.Tasks; +using Ocelot.Configuration; +using Ocelot.Responses; + +namespace Ocelot.LoadBalancer.LoadBalancers +{ + public class LoadBalancerHouse : ILoadBalancerHouse + { + private readonly ILoadBalancerFactory _factory; + private readonly ConcurrentDictionary _loadBalancers; + + public LoadBalancerHouse(ILoadBalancerFactory factory) + { + _factory = factory; + _loadBalancers = new ConcurrentDictionary(); + } + + public async Task> Get(ReRoute reRoute, ServiceProviderConfiguration config) + { + try + { + if(_loadBalancers.TryGetValue(reRoute.ReRouteKey, out var loadBalancer)) + { + loadBalancer = _loadBalancers[reRoute.ReRouteKey]; + + if(reRoute.LoadBalancer != loadBalancer.GetType().Name) + { + loadBalancer = await _factory.Get(reRoute, config); + AddLoadBalancer(reRoute.ReRouteKey, loadBalancer); + } + + return new OkResponse(loadBalancer); + } + + loadBalancer = await _factory.Get(reRoute, config); + AddLoadBalancer(reRoute.ReRouteKey, loadBalancer); + return new OkResponse(loadBalancer); + } + catch(Exception ex) + { + return new ErrorResponse(new List() + { + new UnableToFindLoadBalancerError($"unabe to find load balancer for {reRoute.ReRouteKey} exception is {ex}") + }); + } + } + + private void AddLoadBalancer(string key, ILoadBalancer loadBalancer) + { + _loadBalancers.AddOrUpdate(key, loadBalancer, (x, y) => loadBalancer); + } + } +} diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs b/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs index bf66950b8..172909d7f 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs @@ -1,28 +1,28 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Ocelot.Responses; -using Ocelot.Values; - -namespace Ocelot.LoadBalancer.LoadBalancers -{ - public class NoLoadBalancer : ILoadBalancer - { - private readonly List _services; - - public NoLoadBalancer(List services) - { - _services = services; - } - - public async Task> Lease() - { - var service = await Task.FromResult(_services.FirstOrDefault()); - return new OkResponse(service.HostAndPort); - } - - public void Release(HostAndPort hostAndPort) - { - } - } -} +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Ocelot.Responses; +using Ocelot.Values; + +namespace Ocelot.LoadBalancer.LoadBalancers +{ + public class NoLoadBalancer : ILoadBalancer + { + private readonly List _services; + + public NoLoadBalancer(List services) + { + _services = services; + } + + public async Task> Lease() + { + var service = await Task.FromResult(_services.FirstOrDefault()); + return new OkResponse(service.HostAndPort); + } + + public void Release(ServiceHostAndPort hostAndPort) + { + } + } +} diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobin.cs b/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobin.cs index 817a1b8f8..efc00e5ea 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobin.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobin.cs @@ -1,38 +1,38 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Ocelot.Responses; -using Ocelot.Values; -using System; - -namespace Ocelot.LoadBalancer.LoadBalancers -{ - public class RoundRobin : ILoadBalancer - { - private readonly Func>> _services; - - private int _last; - - public RoundRobin(Func>> services) - { - _services = services; - } - - - public async Task> Lease() - { - var services = await _services.Invoke(); - if (_last >= services.Count) - { - _last = 0; - } - - var next = await Task.FromResult(services[_last]); - _last++; - return new OkResponse(next.HostAndPort); - } - - public void Release(HostAndPort hostAndPort) - { - } - } -} +using System.Collections.Generic; +using System.Threading.Tasks; +using Ocelot.Responses; +using Ocelot.Values; +using System; + +namespace Ocelot.LoadBalancer.LoadBalancers +{ + public class RoundRobin : ILoadBalancer + { + private readonly Func>> _services; + + private int _last; + + public RoundRobin(Func>> services) + { + _services = services; + } + + + public async Task> Lease() + { + var services = await _services.Invoke(); + if (_last >= services.Count) + { + _last = 0; + } + + var next = await Task.FromResult(services[_last]); + _last++; + return new OkResponse(next.HostAndPort); + } + + public void Release(ServiceHostAndPort hostAndPort) + { + } + } +} diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/ServicesAreEmptyError.cs b/src/Ocelot/LoadBalancer/LoadBalancers/ServicesAreEmptyError.cs index 2fc4d953a..b1a1e02cf 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/ServicesAreEmptyError.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/ServicesAreEmptyError.cs @@ -1,12 +1,12 @@ -using Ocelot.Errors; - -namespace Ocelot.LoadBalancer.LoadBalancers -{ - public class ServicesAreEmptyError : Error - { - public ServicesAreEmptyError(string message) - : base(message, OcelotErrorCode.ServicesAreEmptyError) - { - } - } +using Ocelot.Errors; + +namespace Ocelot.LoadBalancer.LoadBalancers +{ + public class ServicesAreEmptyError : Error + { + public ServicesAreEmptyError(string message) + : base(message, OcelotErrorCode.ServicesAreEmptyError) + { + } + } } \ No newline at end of file diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/ServicesAreNullError.cs b/src/Ocelot/LoadBalancer/LoadBalancers/ServicesAreNullError.cs index 8e1bb7009..0116bee9c 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/ServicesAreNullError.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/ServicesAreNullError.cs @@ -1,12 +1,12 @@ -using Ocelot.Errors; - -namespace Ocelot.LoadBalancer.LoadBalancers -{ - public class ServicesAreNullError : Error - { - public ServicesAreNullError(string message) - : base(message, OcelotErrorCode.ServicesAreNullError) - { - } - } +using Ocelot.Errors; + +namespace Ocelot.LoadBalancer.LoadBalancers +{ + public class ServicesAreNullError : Error + { + public ServicesAreNullError(string message) + : base(message, OcelotErrorCode.ServicesAreNullError) + { + } + } } \ No newline at end of file diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/UnableToFindLoadBalancerError.cs b/src/Ocelot/LoadBalancer/LoadBalancers/UnableToFindLoadBalancerError.cs index 3dd3ede4a..2daf74ffc 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/UnableToFindLoadBalancerError.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/UnableToFindLoadBalancerError.cs @@ -1,12 +1,12 @@ -using Ocelot.Errors; - -namespace Ocelot.LoadBalancer.LoadBalancers -{ - public class UnableToFindLoadBalancerError : Errors.Error - { - public UnableToFindLoadBalancerError(string message) - : base(message, OcelotErrorCode.UnableToFindLoadBalancerError) - { - } - } +using Ocelot.Errors; + +namespace Ocelot.LoadBalancer.LoadBalancers +{ + public class UnableToFindLoadBalancerError : Errors.Error + { + public UnableToFindLoadBalancerError(string message) + : base(message, OcelotErrorCode.UnableToFindLoadBalancerError) + { + } + } } \ No newline at end of file diff --git a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs index dc3b1a8f9..29347c88f 100644 --- a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs +++ b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs @@ -1,71 +1,71 @@ -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Ocelot.Configuration.Provider; -using Ocelot.Infrastructure.RequestData; -using Ocelot.LoadBalancer.LoadBalancers; -using Ocelot.Logging; -using Ocelot.Middleware; -using Ocelot.QueryStrings.Middleware; - -namespace Ocelot.LoadBalancer.Middleware -{ - public class LoadBalancingMiddleware : OcelotMiddleware - { - private readonly RequestDelegate _next; - private readonly IOcelotLogger _logger; - private readonly ILoadBalancerHouse _loadBalancerHouse; - - public LoadBalancingMiddleware(RequestDelegate next, - IOcelotLoggerFactory loggerFactory, - IRequestScopedDataRepository requestScopedDataRepository, - ILoadBalancerHouse loadBalancerHouse) - : base(requestScopedDataRepository) - { - _next = next; - _logger = loggerFactory.CreateLogger(); - _loadBalancerHouse = loadBalancerHouse; - } - - public async Task Invoke(HttpContext context) - { - var loadBalancer = await _loadBalancerHouse.Get(DownstreamRoute.ReRoute, ServiceProviderConfiguration); - if(loadBalancer.IsError) - { - _logger.LogDebug("there was an error retriving the loadbalancer, setting pipeline error"); - SetPipelineError(loadBalancer.Errors); - return; - } - - var hostAndPort = await loadBalancer.Data.Lease(); - if(hostAndPort.IsError) - { - _logger.LogDebug("there was an error leasing the loadbalancer, setting pipeline error"); - SetPipelineError(hostAndPort.Errors); - return; - } - - var uriBuilder = new UriBuilder(DownstreamRequest.RequestUri); - uriBuilder.Host = hostAndPort.Data.DownstreamHost; - if (hostAndPort.Data.DownstreamPort > 0) - { - uriBuilder.Port = hostAndPort.Data.DownstreamPort; - } - DownstreamRequest.RequestUri = uriBuilder.Uri; - - try - { - await _next.Invoke(context); - } - catch (Exception) - { - _logger.LogDebug("Exception calling next middleware, exception will be thrown to global handler"); - throw; - } - finally - { - loadBalancer.Data.Release(hostAndPort.Data); - } - } - } -} +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Ocelot.Configuration.Provider; +using Ocelot.Infrastructure.RequestData; +using Ocelot.LoadBalancer.LoadBalancers; +using Ocelot.Logging; +using Ocelot.Middleware; +using Ocelot.QueryStrings.Middleware; + +namespace Ocelot.LoadBalancer.Middleware +{ + public class LoadBalancingMiddleware : OcelotMiddleware + { + private readonly RequestDelegate _next; + private readonly IOcelotLogger _logger; + private readonly ILoadBalancerHouse _loadBalancerHouse; + + public LoadBalancingMiddleware(RequestDelegate next, + IOcelotLoggerFactory loggerFactory, + IRequestScopedDataRepository requestScopedDataRepository, + ILoadBalancerHouse loadBalancerHouse) + : base(requestScopedDataRepository) + { + _next = next; + _logger = loggerFactory.CreateLogger(); + _loadBalancerHouse = loadBalancerHouse; + } + + public async Task Invoke(HttpContext context) + { + var loadBalancer = await _loadBalancerHouse.Get(DownstreamRoute.ReRoute, ServiceProviderConfiguration); + if(loadBalancer.IsError) + { + _logger.LogDebug("there was an error retriving the loadbalancer, setting pipeline error"); + SetPipelineError(loadBalancer.Errors); + return; + } + + var hostAndPort = await loadBalancer.Data.Lease(); + if(hostAndPort.IsError) + { + _logger.LogDebug("there was an error leasing the loadbalancer, setting pipeline error"); + SetPipelineError(hostAndPort.Errors); + return; + } + + var uriBuilder = new UriBuilder(DownstreamRequest.RequestUri); + uriBuilder.Host = hostAndPort.Data.DownstreamHost; + if (hostAndPort.Data.DownstreamPort > 0) + { + uriBuilder.Port = hostAndPort.Data.DownstreamPort; + } + DownstreamRequest.RequestUri = uriBuilder.Uri; + + try + { + await _next.Invoke(context); + } + catch (Exception) + { + _logger.LogDebug("Exception calling next middleware, exception will be thrown to global handler"); + throw; + } + finally + { + loadBalancer.Data.Release(hostAndPort.Data); + } + } + } +} diff --git a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddlewareExtensions.cs b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddlewareExtensions.cs index 0d0224b8d..db026396b 100644 --- a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddlewareExtensions.cs +++ b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddlewareExtensions.cs @@ -1,12 +1,12 @@ -using Microsoft.AspNetCore.Builder; - -namespace Ocelot.LoadBalancer.Middleware -{ - public static class LoadBalancingMiddlewareExtensions - { - public static IApplicationBuilder UseLoadBalancingMiddleware(this IApplicationBuilder builder) - { - return builder.UseMiddleware(); - } - } +using Microsoft.AspNetCore.Builder; + +namespace Ocelot.LoadBalancer.Middleware +{ + public static class LoadBalancingMiddlewareExtensions + { + public static IApplicationBuilder UseLoadBalancingMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Logging/IOcelotLoggerFactory.cs b/src/Ocelot/Logging/IOcelotLoggerFactory.cs index dc96c6495..29b4c9d03 100644 --- a/src/Ocelot/Logging/IOcelotLoggerFactory.cs +++ b/src/Ocelot/Logging/IOcelotLoggerFactory.cs @@ -1,26 +1,26 @@ -using System; - -namespace Ocelot.Logging -{ - public interface IOcelotLoggerFactory - { - IOcelotLogger CreateLogger(); - } - /// - /// Thin wrapper around the DotNet core logging framework, used to allow the scopedDataRepository to be injected giving access to the Ocelot RequestId - /// - public interface IOcelotLogger - { - void LogTrace(string message, params object[] args); - void LogDebug(string message, params object[] args); - void LogInformation(string message, params object[] args); - void LogError(string message, Exception exception); - void LogError(string message, params object[] args); - void LogCritical(string message, Exception exception); - - /// - /// The name of the type the logger has been built for. - /// - string Name { get; } - } -} +using System; + +namespace Ocelot.Logging +{ + public interface IOcelotLoggerFactory + { + IOcelotLogger CreateLogger(); + } + /// + /// Thin wrapper around the DotNet core logging framework, used to allow the scopedDataRepository to be injected giving access to the Ocelot RequestId + /// + public interface IOcelotLogger + { + void LogTrace(string message, params object[] args); + void LogDebug(string message, params object[] args); + void LogInformation(string message, params object[] args); + void LogError(string message, Exception exception); + void LogError(string message, params object[] args); + void LogCritical(string message, Exception exception); + + /// + /// The name of the type the logger has been built for. + /// + string Name { get; } + } +} diff --git a/src/Ocelot/Middleware/BaseUrlFinder.cs b/src/Ocelot/Middleware/BaseUrlFinder.cs index da1fda4e6..63672501c 100644 --- a/src/Ocelot/Middleware/BaseUrlFinder.cs +++ b/src/Ocelot/Middleware/BaseUrlFinder.cs @@ -1,21 +1,21 @@ -using Microsoft.AspNetCore.Hosting; - -namespace Ocelot.Middleware -{ - public class BaseUrlFinder : IBaseUrlFinder - { - private readonly IWebHostBuilder _webHostBuilder; - - public BaseUrlFinder(IWebHostBuilder webHostBuilder) - { - _webHostBuilder = webHostBuilder; - } - - public string Find() - { - var baseSchemeUrlAndPort = _webHostBuilder.GetSetting(WebHostDefaults.ServerUrlsKey); - - return string.IsNullOrEmpty(baseSchemeUrlAndPort) ? "http://localhost:5000" : baseSchemeUrlAndPort; - } - } -} +using Microsoft.AspNetCore.Hosting; + +namespace Ocelot.Middleware +{ + public class BaseUrlFinder : IBaseUrlFinder + { + private readonly IWebHostBuilder _webHostBuilder; + + public BaseUrlFinder(IWebHostBuilder webHostBuilder) + { + _webHostBuilder = webHostBuilder; + } + + public string Find() + { + var baseSchemeUrlAndPort = _webHostBuilder.GetSetting(WebHostDefaults.ServerUrlsKey); + + return string.IsNullOrEmpty(baseSchemeUrlAndPort) ? "http://localhost:5000" : baseSchemeUrlAndPort; + } + } +} diff --git a/src/Ocelot/Middleware/IBaseUrlFinder.cs b/src/Ocelot/Middleware/IBaseUrlFinder.cs index df54c0fe5..23d20104c 100644 --- a/src/Ocelot/Middleware/IBaseUrlFinder.cs +++ b/src/Ocelot/Middleware/IBaseUrlFinder.cs @@ -1,7 +1,7 @@ -namespace Ocelot.Middleware -{ - public interface IBaseUrlFinder - { - string Find(); - } +namespace Ocelot.Middleware +{ + public interface IBaseUrlFinder + { + string Find(); + } } \ No newline at end of file diff --git a/src/Ocelot/Middleware/OcelotMiddleware.cs b/src/Ocelot/Middleware/OcelotMiddleware.cs index 50d5baa5d..8b1eae2e6 100644 --- a/src/Ocelot/Middleware/OcelotMiddleware.cs +++ b/src/Ocelot/Middleware/OcelotMiddleware.cs @@ -1,67 +1,67 @@ -using System.Collections.Generic; -using System.Net.Http; -using Ocelot.Configuration; -using Ocelot.DownstreamRouteFinder; -using Ocelot.Errors; -using Ocelot.Infrastructure.RequestData; - -namespace Ocelot.Middleware -{ - public abstract class OcelotMiddleware - { - private readonly IRequestScopedDataRepository _requestScopedDataRepository; - - protected OcelotMiddleware(IRequestScopedDataRepository requestScopedDataRepository) - { - _requestScopedDataRepository = requestScopedDataRepository; - MiddlewareName = this.GetType().Name; - } - - public string MiddlewareName { get; } - - public bool PipelineError => _requestScopedDataRepository.Get("OcelotMiddlewareError").Data; - - public List PipelineErrors => _requestScopedDataRepository.Get>("OcelotMiddlewareErrors").Data; - - public DownstreamRoute DownstreamRoute => _requestScopedDataRepository.Get("DownstreamRoute").Data; - - public Request.Request Request => _requestScopedDataRepository.Get("Request").Data; - - public HttpRequestMessage DownstreamRequest => _requestScopedDataRepository.Get("DownstreamRequest").Data; - - public HttpResponseMessage HttpResponseMessage => _requestScopedDataRepository.Get("HttpResponseMessage").Data; - - public ServiceProviderConfiguration ServiceProviderConfiguration => _requestScopedDataRepository.Get("ServiceProviderConfiguration").Data; - - public void SetDownstreamRouteForThisRequest(DownstreamRoute downstreamRoute) - { - _requestScopedDataRepository.Add("DownstreamRoute", downstreamRoute); - } - - public void SetServiceProviderConfigurationForThisRequest(ServiceProviderConfiguration serviceProviderConfiguration) - { - _requestScopedDataRepository.Add("ServiceProviderConfiguration", serviceProviderConfiguration); - } - - public void SetUpstreamRequestForThisRequest(Request.Request request) - { - _requestScopedDataRepository.Add("Request", request); - } - - public void SetDownstreamRequest(HttpRequestMessage request) - { - _requestScopedDataRepository.Add("DownstreamRequest", request); - } - - public void SetHttpResponseMessageThisRequest(HttpResponseMessage responseMessage) - { - _requestScopedDataRepository.Add("HttpResponseMessage", responseMessage); - } - - public void SetPipelineError(List errors) - { - _requestScopedDataRepository.Add("OcelotMiddlewareError", true); - _requestScopedDataRepository.Add("OcelotMiddlewareErrors", errors); - } - } -} +using System.Collections.Generic; +using System.Net.Http; +using Ocelot.Configuration; +using Ocelot.DownstreamRouteFinder; +using Ocelot.Errors; +using Ocelot.Infrastructure.RequestData; + +namespace Ocelot.Middleware +{ + public abstract class OcelotMiddleware + { + private readonly IRequestScopedDataRepository _requestScopedDataRepository; + + protected OcelotMiddleware(IRequestScopedDataRepository requestScopedDataRepository) + { + _requestScopedDataRepository = requestScopedDataRepository; + MiddlewareName = this.GetType().Name; + } + + public string MiddlewareName { get; } + + public bool PipelineError => _requestScopedDataRepository.Get("OcelotMiddlewareError").Data; + + public List PipelineErrors => _requestScopedDataRepository.Get>("OcelotMiddlewareErrors").Data; + + public DownstreamRoute DownstreamRoute => _requestScopedDataRepository.Get("DownstreamRoute").Data; + + public Request.Request Request => _requestScopedDataRepository.Get("Request").Data; + + public HttpRequestMessage DownstreamRequest => _requestScopedDataRepository.Get("DownstreamRequest").Data; + + public HttpResponseMessage HttpResponseMessage => _requestScopedDataRepository.Get("HttpResponseMessage").Data; + + public ServiceProviderConfiguration ServiceProviderConfiguration => _requestScopedDataRepository.Get("ServiceProviderConfiguration").Data; + + public void SetDownstreamRouteForThisRequest(DownstreamRoute downstreamRoute) + { + _requestScopedDataRepository.Add("DownstreamRoute", downstreamRoute); + } + + public void SetServiceProviderConfigurationForThisRequest(ServiceProviderConfiguration serviceProviderConfiguration) + { + _requestScopedDataRepository.Add("ServiceProviderConfiguration", serviceProviderConfiguration); + } + + public void SetUpstreamRequestForThisRequest(Request.Request request) + { + _requestScopedDataRepository.Add("Request", request); + } + + public void SetDownstreamRequest(HttpRequestMessage request) + { + _requestScopedDataRepository.Add("DownstreamRequest", request); + } + + public void SetHttpResponseMessageThisRequest(HttpResponseMessage responseMessage) + { + _requestScopedDataRepository.Add("HttpResponseMessage", responseMessage); + } + + public void SetPipelineError(List errors) + { + _requestScopedDataRepository.Add("OcelotMiddlewareError", true); + _requestScopedDataRepository.Add("OcelotMiddlewareErrors", errors); + } + } +} diff --git a/src/Ocelot/Middleware/OcelotMiddlewareConfiguration.cs b/src/Ocelot/Middleware/OcelotMiddlewareConfiguration.cs index 555f8df92..e3d22928f 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareConfiguration.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareConfiguration.cs @@ -1,44 +1,44 @@ -namespace Ocelot.Middleware -{ - using System; - using System.Threading.Tasks; - using Microsoft.AspNetCore.Http; - - public class OcelotMiddlewareConfiguration - { - /// - /// This is called after the global error handling middleware so any code before calling next.invoke - /// is the next thing called in the Ocelot pipeline. Anything after next.invoke is the last thing called - /// in the Ocelot pipeline before we go to the global error handler. - /// - public Func, Task> PreErrorResponderMiddleware { get; set; } - - /// - /// This is to allow the user to run any extra authentication before the Ocelot authentication - /// kicks in - /// - public Func, Task> PreAuthenticationMiddleware { get; set; } - - /// - /// This allows the user to completely override the ocelot authentication middleware - /// - public Func, Task> AuthenticationMiddleware { get; set; } - - /// - /// This is to allow the user to run any extra authorisation before the Ocelot authentication - /// kicks in - /// - public Func, Task> PreAuthorisationMiddleware { get; set; } - - /// - /// This allows the user to completely override the ocelot authorisation middleware - /// - public Func, Task> AuthorisationMiddleware { get; set; } - - /// - /// This allows the user to implement there own query string manipulation logic - /// - public Func, Task> PreQueryStringBuilderMiddleware { get; set; } - - } +namespace Ocelot.Middleware +{ + using System; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Http; + + public class OcelotMiddlewareConfiguration + { + /// + /// This is called after the global error handling middleware so any code before calling next.invoke + /// is the next thing called in the Ocelot pipeline. Anything after next.invoke is the last thing called + /// in the Ocelot pipeline before we go to the global error handler. + /// + public Func, Task> PreErrorResponderMiddleware { get; set; } + + /// + /// This is to allow the user to run any extra authentication before the Ocelot authentication + /// kicks in + /// + public Func, Task> PreAuthenticationMiddleware { get; set; } + + /// + /// This allows the user to completely override the ocelot authentication middleware + /// + public Func, Task> AuthenticationMiddleware { get; set; } + + /// + /// This is to allow the user to run any extra authorisation before the Ocelot authentication + /// kicks in + /// + public Func, Task> PreAuthorisationMiddleware { get; set; } + + /// + /// This allows the user to completely override the ocelot authorisation middleware + /// + public Func, Task> AuthorisationMiddleware { get; set; } + + /// + /// This allows the user to implement there own query string manipulation logic + /// + public Func, Task> PreQueryStringBuilderMiddleware { get; set; } + + } } \ No newline at end of file diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 02c2394fb..5ac19cf2f 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -1,334 +1,334 @@ -using System.Collections.Generic; -using System.Diagnostics; -using System.Reflection; -using IdentityServer4.AccessTokenValidation; -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; -using Ocelot.Authentication.Middleware; -using Ocelot.Cache.Middleware; -using Ocelot.Claims.Middleware; -using Ocelot.DownstreamRouteFinder.Middleware; -using Ocelot.DownstreamUrlCreator.Middleware; -using Ocelot.Errors.Middleware; -using Ocelot.Headers.Middleware; -using Ocelot.Logging; -using Ocelot.QueryStrings.Middleware; -using Ocelot.Request.Middleware; -using Ocelot.Requester.Middleware; -using Ocelot.RequestId.Middleware; -using Ocelot.Responder.Middleware; -using Ocelot.RateLimit.Middleware; - -namespace Ocelot.Middleware -{ - using System; - using System.IO; - using System.Linq; - using System.Threading.Tasks; - using Authorisation.Middleware; - using Microsoft.AspNetCore.Hosting; - using Microsoft.AspNetCore.Http; - using Microsoft.Extensions.Logging; - using Microsoft.Extensions.Options; - using Newtonsoft.Json; - using Ocelot.Configuration; - using Ocelot.Configuration.Creator; - using Ocelot.Configuration.File; - using Ocelot.Configuration.Provider; - using Ocelot.Configuration.Repository; - using Ocelot.Configuration.Setter; - using Ocelot.LoadBalancer.Middleware; - using Ocelot.Raft; - using Ocelot.Responses; - using Rafty.Concensus; - using Rafty.Infrastructure; - - public static class OcelotMiddlewareExtensions - { - /// - /// Registers the Ocelot default middlewares - /// - /// - /// - public static async Task UseOcelot(this IApplicationBuilder builder) - { - await builder.UseOcelot(new OcelotMiddlewareConfiguration()); - - return builder; - } - - /// - /// Registers Ocelot with a combination of default middlewares and optional middlewares in the configuration - /// - /// - /// - /// - public static async Task UseOcelot(this IApplicationBuilder builder, OcelotMiddlewareConfiguration middlewareConfiguration) - { - var configuration = await CreateConfiguration(builder); - - await CreateAdministrationArea(builder, configuration); - - if(UsingRafty(builder)) - { - SetUpRafty(builder); - } - - ConfigureDiagnosticListener(builder); - - // This is registered to catch any global exceptions that are not handled - // It also sets the Request Id if anything is set globally - builder.UseExceptionHandlerMiddleware(); - - // Allow the user to respond with absolutely anything they want. - builder.UseIfNotNull(middlewareConfiguration.PreErrorResponderMiddleware); - - // This is registered first so it can catch any errors and issue an appropriate response - builder.UseResponderMiddleware(); - - // Then we get the downstream route information - builder.UseDownstreamRouteFinderMiddleware(); - - // Now we have the ds route we can transform headers and stuff? - builder.UseHttpHeadersTransformationMiddleware(); - - // Initialises downstream request - builder.UseDownstreamRequestInitialiser(); - - // We check whether the request is ratelimit, and if there is no continue processing - builder.UseRateLimiting(); - - // This adds or updates the request id (initally we try and set this based on global config in the error handling middleware) - // If anything was set at global level and we have a different setting at re route level the global stuff will be overwritten - // This means you can get a scenario where you have a different request id from the first piece of middleware to the request id middleware. - builder.UseRequestIdMiddleware(); - - // Allow pre authentication logic. The idea being people might want to run something custom before what is built in. - builder.UseIfNotNull(middlewareConfiguration.PreAuthenticationMiddleware); - - // Now we know where the client is going to go we can authenticate them. - // We allow the ocelot middleware to be overriden by whatever the - // user wants - if (middlewareConfiguration.AuthenticationMiddleware == null) - { - builder.UseAuthenticationMiddleware(); - } - else - { - builder.Use(middlewareConfiguration.AuthenticationMiddleware); - } - - // The next thing we do is look at any claims transforms in case this is important for authorisation - builder.UseClaimsBuilderMiddleware(); - - // Allow pre authorisation logic. The idea being people might want to run something custom before what is built in. - builder.UseIfNotNull(middlewareConfiguration.PreAuthorisationMiddleware); - - // Now we have authenticated and done any claims transformation we - // can authorise the request - // We allow the ocelot middleware to be overriden by whatever the - // user wants - if (middlewareConfiguration.AuthorisationMiddleware == null) - { - builder.UseAuthorisationMiddleware(); - } - else - { - builder.Use(middlewareConfiguration.AuthorisationMiddleware); - } - - // Now we can run any header transformation logic - builder.UseHttpRequestHeadersBuilderMiddleware(); - - // Allow the user to implement their own query string manipulation logic - builder.UseIfNotNull(middlewareConfiguration.PreQueryStringBuilderMiddleware); - - // Now we can run any query string transformation logic - builder.UseQueryStringBuilderMiddleware(); - - // Get the load balancer for this request - builder.UseLoadBalancingMiddleware(); - - // This takes the downstream route we retrieved earlier and replaces any placeholders with the variables that should be used - builder.UseDownstreamUrlCreatorMiddleware(); - - // Not sure if this is the best place for this but we use the downstream url - // as the basis for our cache key. - builder.UseOutputCacheMiddleware(); - - // Everything should now be ready to build or HttpRequest - builder.UseHttpRequestBuilderMiddleware(); - - //We fire off the request and set the response on the scoped data repo - builder.UseHttpRequesterMiddleware(); - - return builder; - } - - private static bool UsingRafty(IApplicationBuilder builder) - { - var possible = builder.ApplicationServices.GetService(typeof(INode)) as INode; - if(possible != null) - { - return true; - } - - return false; - } - - private static void SetUpRafty(IApplicationBuilder builder) - { - var applicationLifetime = (IApplicationLifetime)builder.ApplicationServices.GetService(typeof(IApplicationLifetime)); - applicationLifetime.ApplicationStopping.Register(() => OnShutdown(builder)); - var node = (INode)builder.ApplicationServices.GetService(typeof(INode)); - var nodeId = (NodeId)builder.ApplicationServices.GetService(typeof(NodeId)); - node.Start(nodeId.Id); - } - - private static async Task CreateConfiguration(IApplicationBuilder builder) - { - var deps = GetDependencies(builder); - - var ocelotConfiguration = await deps.provider.Get(); - - if (ConfigurationNotSetUp(ocelotConfiguration)) - { - var response = await SetConfig(builder, deps.fileConfiguration, deps.setter, deps.provider, deps.repo); - - if (UnableToSetConfig(response)) - { - ThrowToStopOcelotStarting(response); - } - } - - return await GetOcelotConfigAndReturn(deps.provider); - } - - private static async Task SetConfig(IApplicationBuilder builder, IOptions fileConfiguration, IFileConfigurationSetter setter, IOcelotConfigurationProvider provider, IFileConfigurationRepository repo) - { - if (UsingConsul(repo)) - { - return await SetUpConfigFromConsul(builder, repo, setter, fileConfiguration); - } - - return await setter.Set(fileConfiguration.Value); - } - - private static bool UnableToSetConfig(Response response) - { - return response == null || response.IsError; - } - - private static bool ConfigurationNotSetUp(Ocelot.Responses.Response ocelotConfiguration) - { - return ocelotConfiguration == null || ocelotConfiguration.Data == null || ocelotConfiguration.IsError; - } - - private static (IOptions fileConfiguration, IFileConfigurationSetter setter, IOcelotConfigurationProvider provider, IFileConfigurationRepository repo) GetDependencies(IApplicationBuilder builder) - { - var fileConfiguration = (IOptions)builder.ApplicationServices.GetService(typeof(IOptions)); - - var setter = (IFileConfigurationSetter)builder.ApplicationServices.GetService(typeof(IFileConfigurationSetter)); - - var provider = (IOcelotConfigurationProvider)builder.ApplicationServices.GetService(typeof(IOcelotConfigurationProvider)); - - var repo = (IFileConfigurationRepository)builder.ApplicationServices.GetService(typeof(IFileConfigurationRepository)); - - return (fileConfiguration, setter, provider, repo); - } - - private static async Task GetOcelotConfigAndReturn(IOcelotConfigurationProvider provider) - { - var ocelotConfiguration = await provider.Get(); - - if(ocelotConfiguration == null || ocelotConfiguration.Data == null || ocelotConfiguration.IsError) - { - ThrowToStopOcelotStarting(ocelotConfiguration); - } - - return ocelotConfiguration.Data; - } - - private static void ThrowToStopOcelotStarting(Response config) - { - throw new Exception($"Unable to start Ocelot, errors are: {string.Join(",", config.Errors.Select(x => x.ToString()))}"); - } - - private static bool UsingConsul(IFileConfigurationRepository fileConfigRepo) - { - return fileConfigRepo.GetType() == typeof(ConsulFileConfigurationRepository); - } - - private static async Task SetUpConfigFromConsul(IApplicationBuilder builder, IFileConfigurationRepository consulFileConfigRepo, IFileConfigurationSetter setter, IOptions fileConfig) - { - Response config = null; - - var ocelotConfigurationRepository = - (IOcelotConfigurationRepository) builder.ApplicationServices.GetService( - typeof(IOcelotConfigurationRepository)); - var ocelotConfigurationCreator = - (IOcelotConfigurationCreator) builder.ApplicationServices.GetService( - typeof(IOcelotConfigurationCreator)); - - var fileConfigFromConsul = await consulFileConfigRepo.Get(); - if (fileConfigFromConsul.Data == null) - { - config = await setter.Set(fileConfig.Value); - } - else - { - var ocelotConfig = await ocelotConfigurationCreator.Create(fileConfigFromConsul.Data); - if(ocelotConfig.IsError) - { - return new ErrorResponse(ocelotConfig.Errors); - } - config = await ocelotConfigurationRepository.AddOrReplace(ocelotConfig.Data); - //todo - this starts the poller if it has been registered...please this is so bad. - var hack = builder.ApplicationServices.GetService(typeof(ConsulFileConfigurationPoller)); - } - - return new OkResponse(); - } - - private static async Task CreateAdministrationArea(IApplicationBuilder builder, IOcelotConfiguration configuration) - { - var identityServerConfiguration = (IIdentityServerConfiguration)builder.ApplicationServices.GetService(typeof(IIdentityServerConfiguration)); - - if(!string.IsNullOrEmpty(configuration.AdministrationPath) && identityServerConfiguration != null) - { - builder.Map(configuration.AdministrationPath, app => - { - app.UseIdentityServer(); - app.UseAuthentication(); - app.UseMvc(); - }); - } - } - - private static void UseIfNotNull(this IApplicationBuilder builder, Func, Task> middleware) - { - if (middleware != null) - { - builder.Use(middleware); - } - } - - /// - /// Configure a DiagnosticListener to listen for diagnostic events when the middleware starts and ends - /// - /// - private static void ConfigureDiagnosticListener(IApplicationBuilder builder) - { - var env = (IHostingEnvironment)builder.ApplicationServices.GetService(typeof(IHostingEnvironment)); - var listener = (OcelotDiagnosticListener)builder.ApplicationServices.GetService(typeof(OcelotDiagnosticListener)); - var diagnosticListener = (DiagnosticListener)builder.ApplicationServices.GetService(typeof(DiagnosticListener)); - diagnosticListener.SubscribeWithAdapter(listener); - } - - private static void OnShutdown(IApplicationBuilder app) - { - var node = (INode)app.ApplicationServices.GetService(typeof(INode)); - node.Stop(); - } - } -} +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using IdentityServer4.AccessTokenValidation; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Ocelot.Authentication.Middleware; +using Ocelot.Cache.Middleware; +using Ocelot.Claims.Middleware; +using Ocelot.DownstreamRouteFinder.Middleware; +using Ocelot.DownstreamUrlCreator.Middleware; +using Ocelot.Errors.Middleware; +using Ocelot.Headers.Middleware; +using Ocelot.Logging; +using Ocelot.QueryStrings.Middleware; +using Ocelot.Request.Middleware; +using Ocelot.Requester.Middleware; +using Ocelot.RequestId.Middleware; +using Ocelot.Responder.Middleware; +using Ocelot.RateLimit.Middleware; + +namespace Ocelot.Middleware +{ + using System; + using System.IO; + using System.Linq; + using System.Threading.Tasks; + using Authorisation.Middleware; + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.Logging; + using Microsoft.Extensions.Options; + using Newtonsoft.Json; + using Ocelot.Configuration; + using Ocelot.Configuration.Creator; + using Ocelot.Configuration.File; + using Ocelot.Configuration.Provider; + using Ocelot.Configuration.Repository; + using Ocelot.Configuration.Setter; + using Ocelot.LoadBalancer.Middleware; + using Ocelot.Raft; + using Ocelot.Responses; + using Rafty.Concensus; + using Rafty.Infrastructure; + + public static class OcelotMiddlewareExtensions + { + /// + /// Registers the Ocelot default middlewares + /// + /// + /// + public static async Task UseOcelot(this IApplicationBuilder builder) + { + await builder.UseOcelot(new OcelotMiddlewareConfiguration()); + + return builder; + } + + /// + /// Registers Ocelot with a combination of default middlewares and optional middlewares in the configuration + /// + /// + /// + /// + public static async Task UseOcelot(this IApplicationBuilder builder, OcelotMiddlewareConfiguration middlewareConfiguration) + { + var configuration = await CreateConfiguration(builder); + + await CreateAdministrationArea(builder, configuration); + + if(UsingRafty(builder)) + { + SetUpRafty(builder); + } + + ConfigureDiagnosticListener(builder); + + // This is registered to catch any global exceptions that are not handled + // It also sets the Request Id if anything is set globally + builder.UseExceptionHandlerMiddleware(); + + // Allow the user to respond with absolutely anything they want. + builder.UseIfNotNull(middlewareConfiguration.PreErrorResponderMiddleware); + + // This is registered first so it can catch any errors and issue an appropriate response + builder.UseResponderMiddleware(); + + // Then we get the downstream route information + builder.UseDownstreamRouteFinderMiddleware(); + + // Now we have the ds route we can transform headers and stuff? + builder.UseHttpHeadersTransformationMiddleware(); + + // Initialises downstream request + builder.UseDownstreamRequestInitialiser(); + + // We check whether the request is ratelimit, and if there is no continue processing + builder.UseRateLimiting(); + + // This adds or updates the request id (initally we try and set this based on global config in the error handling middleware) + // If anything was set at global level and we have a different setting at re route level the global stuff will be overwritten + // This means you can get a scenario where you have a different request id from the first piece of middleware to the request id middleware. + builder.UseRequestIdMiddleware(); + + // Allow pre authentication logic. The idea being people might want to run something custom before what is built in. + builder.UseIfNotNull(middlewareConfiguration.PreAuthenticationMiddleware); + + // Now we know where the client is going to go we can authenticate them. + // We allow the ocelot middleware to be overriden by whatever the + // user wants + if (middlewareConfiguration.AuthenticationMiddleware == null) + { + builder.UseAuthenticationMiddleware(); + } + else + { + builder.Use(middlewareConfiguration.AuthenticationMiddleware); + } + + // The next thing we do is look at any claims transforms in case this is important for authorisation + builder.UseClaimsBuilderMiddleware(); + + // Allow pre authorisation logic. The idea being people might want to run something custom before what is built in. + builder.UseIfNotNull(middlewareConfiguration.PreAuthorisationMiddleware); + + // Now we have authenticated and done any claims transformation we + // can authorise the request + // We allow the ocelot middleware to be overriden by whatever the + // user wants + if (middlewareConfiguration.AuthorisationMiddleware == null) + { + builder.UseAuthorisationMiddleware(); + } + else + { + builder.Use(middlewareConfiguration.AuthorisationMiddleware); + } + + // Now we can run any header transformation logic + builder.UseHttpRequestHeadersBuilderMiddleware(); + + // Allow the user to implement their own query string manipulation logic + builder.UseIfNotNull(middlewareConfiguration.PreQueryStringBuilderMiddleware); + + // Now we can run any query string transformation logic + builder.UseQueryStringBuilderMiddleware(); + + // Get the load balancer for this request + builder.UseLoadBalancingMiddleware(); + + // This takes the downstream route we retrieved earlier and replaces any placeholders with the variables that should be used + builder.UseDownstreamUrlCreatorMiddleware(); + + // Not sure if this is the best place for this but we use the downstream url + // as the basis for our cache key. + builder.UseOutputCacheMiddleware(); + + // Everything should now be ready to build or HttpRequest + builder.UseHttpRequestBuilderMiddleware(); + + //We fire off the request and set the response on the scoped data repo + builder.UseHttpRequesterMiddleware(); + + return builder; + } + + private static bool UsingRafty(IApplicationBuilder builder) + { + var possible = builder.ApplicationServices.GetService(typeof(INode)) as INode; + if(possible != null) + { + return true; + } + + return false; + } + + private static void SetUpRafty(IApplicationBuilder builder) + { + var applicationLifetime = (IApplicationLifetime)builder.ApplicationServices.GetService(typeof(IApplicationLifetime)); + applicationLifetime.ApplicationStopping.Register(() => OnShutdown(builder)); + var node = (INode)builder.ApplicationServices.GetService(typeof(INode)); + var nodeId = (NodeId)builder.ApplicationServices.GetService(typeof(NodeId)); + node.Start(nodeId.Id); + } + + private static async Task CreateConfiguration(IApplicationBuilder builder) + { + var deps = GetDependencies(builder); + + var ocelotConfiguration = await deps.provider.Get(); + + if (ConfigurationNotSetUp(ocelotConfiguration)) + { + var response = await SetConfig(builder, deps.fileConfiguration, deps.setter, deps.provider, deps.repo); + + if (UnableToSetConfig(response)) + { + ThrowToStopOcelotStarting(response); + } + } + + return await GetOcelotConfigAndReturn(deps.provider); + } + + private static async Task SetConfig(IApplicationBuilder builder, IOptions fileConfiguration, IFileConfigurationSetter setter, IOcelotConfigurationProvider provider, IFileConfigurationRepository repo) + { + if (UsingConsul(repo)) + { + return await SetUpConfigFromConsul(builder, repo, setter, fileConfiguration); + } + + return await setter.Set(fileConfiguration.Value); + } + + private static bool UnableToSetConfig(Response response) + { + return response == null || response.IsError; + } + + private static bool ConfigurationNotSetUp(Ocelot.Responses.Response ocelotConfiguration) + { + return ocelotConfiguration == null || ocelotConfiguration.Data == null || ocelotConfiguration.IsError; + } + + private static (IOptions fileConfiguration, IFileConfigurationSetter setter, IOcelotConfigurationProvider provider, IFileConfigurationRepository repo) GetDependencies(IApplicationBuilder builder) + { + var fileConfiguration = (IOptions)builder.ApplicationServices.GetService(typeof(IOptions)); + + var setter = (IFileConfigurationSetter)builder.ApplicationServices.GetService(typeof(IFileConfigurationSetter)); + + var provider = (IOcelotConfigurationProvider)builder.ApplicationServices.GetService(typeof(IOcelotConfigurationProvider)); + + var repo = (IFileConfigurationRepository)builder.ApplicationServices.GetService(typeof(IFileConfigurationRepository)); + + return (fileConfiguration, setter, provider, repo); + } + + private static async Task GetOcelotConfigAndReturn(IOcelotConfigurationProvider provider) + { + var ocelotConfiguration = await provider.Get(); + + if(ocelotConfiguration == null || ocelotConfiguration.Data == null || ocelotConfiguration.IsError) + { + ThrowToStopOcelotStarting(ocelotConfiguration); + } + + return ocelotConfiguration.Data; + } + + private static void ThrowToStopOcelotStarting(Response config) + { + throw new Exception($"Unable to start Ocelot, errors are: {string.Join(",", config.Errors.Select(x => x.ToString()))}"); + } + + private static bool UsingConsul(IFileConfigurationRepository fileConfigRepo) + { + return fileConfigRepo.GetType() == typeof(ConsulFileConfigurationRepository); + } + + private static async Task SetUpConfigFromConsul(IApplicationBuilder builder, IFileConfigurationRepository consulFileConfigRepo, IFileConfigurationSetter setter, IOptions fileConfig) + { + Response config = null; + + var ocelotConfigurationRepository = + (IOcelotConfigurationRepository) builder.ApplicationServices.GetService( + typeof(IOcelotConfigurationRepository)); + var ocelotConfigurationCreator = + (IOcelotConfigurationCreator) builder.ApplicationServices.GetService( + typeof(IOcelotConfigurationCreator)); + + var fileConfigFromConsul = await consulFileConfigRepo.Get(); + if (fileConfigFromConsul.Data == null) + { + config = await setter.Set(fileConfig.Value); + } + else + { + var ocelotConfig = await ocelotConfigurationCreator.Create(fileConfigFromConsul.Data); + if(ocelotConfig.IsError) + { + return new ErrorResponse(ocelotConfig.Errors); + } + config = await ocelotConfigurationRepository.AddOrReplace(ocelotConfig.Data); + //todo - this starts the poller if it has been registered...please this is so bad. + var hack = builder.ApplicationServices.GetService(typeof(ConsulFileConfigurationPoller)); + } + + return new OkResponse(); + } + + private static async Task CreateAdministrationArea(IApplicationBuilder builder, IOcelotConfiguration configuration) + { + var identityServerConfiguration = (IIdentityServerConfiguration)builder.ApplicationServices.GetService(typeof(IIdentityServerConfiguration)); + + if(!string.IsNullOrEmpty(configuration.AdministrationPath) && identityServerConfiguration != null) + { + builder.Map(configuration.AdministrationPath, app => + { + app.UseIdentityServer(); + app.UseAuthentication(); + app.UseMvc(); + }); + } + } + + private static void UseIfNotNull(this IApplicationBuilder builder, Func, Task> middleware) + { + if (middleware != null) + { + builder.Use(middleware); + } + } + + /// + /// Configure a DiagnosticListener to listen for diagnostic events when the middleware starts and ends + /// + /// + private static void ConfigureDiagnosticListener(IApplicationBuilder builder) + { + var env = (IHostingEnvironment)builder.ApplicationServices.GetService(typeof(IHostingEnvironment)); + var listener = (OcelotDiagnosticListener)builder.ApplicationServices.GetService(typeof(OcelotDiagnosticListener)); + var diagnosticListener = (DiagnosticListener)builder.ApplicationServices.GetService(typeof(DiagnosticListener)); + diagnosticListener.SubscribeWithAdapter(listener); + } + + private static void OnShutdown(IApplicationBuilder app) + { + var node = (INode)app.ApplicationServices.GetService(typeof(INode)); + node.Stop(); + } + } +} diff --git a/src/Ocelot/Middleware/UnauthenticatedError.cs b/src/Ocelot/Middleware/UnauthenticatedError.cs index c8b040393..d99d75039 100644 --- a/src/Ocelot/Middleware/UnauthenticatedError.cs +++ b/src/Ocelot/Middleware/UnauthenticatedError.cs @@ -1,11 +1,11 @@ -using Ocelot.Errors; - -namespace Ocelot.Middleware -{ - public class UnauthenticatedError : Error - { - public UnauthenticatedError(string message) : base(message, OcelotErrorCode.UnauthenticatedError) - { - } - } -} +using Ocelot.Errors; + +namespace Ocelot.Middleware +{ + public class UnauthenticatedError : Error + { + public UnauthenticatedError(string message) : base(message, OcelotErrorCode.UnauthenticatedError) + { + } + } +} diff --git a/src/Ocelot/Ocelot.csproj b/src/Ocelot/Ocelot.csproj index adabe8852..d2a00af1e 100644 --- a/src/Ocelot/Ocelot.csproj +++ b/src/Ocelot/Ocelot.csproj @@ -1,46 +1,46 @@ - - - netcoreapp2.0 - 2.0.0 - 2.0.0 - This project is aimed at people using .NET running a micro services / service orientated architecture that need a unified point of entry into their system. In particular I want easy integration with IdentityServer reference and bearer tokens. We have been unable to find this in my current workplace without having to write our own Javascript middlewares to handle the IdentityServer reference tokens. We would rather use the IdentityServer code that already exists to do this. Ocelot is a bunch of middlewares in a specific order. Ocelot manipulates the HttpRequest object into a state specified by its configuration until it reaches a request builder middleware where it creates a HttpRequestMessage object which is used to make a request to a downstream service. The middleware that makes the request is the last thing in the Ocelot pipeline. It does not call the next middleware. The response from the downstream service is stored in a per request scoped repository and retrived as the requests goes back up the Ocelot pipeline. There is a piece of middleware that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client. That is basically it with a bunch of other features. - Ocelot - 0.0.0-dev - Ocelot - Ocelot - API Gateway;.NET core - https://github.com/TomPallister/Ocelot - https://github.com/TomPallister/Ocelot - win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64 - false - false - True - false - Tom Pallister - - - full - True - - - - - - - - - - - - - - - - - - - - - - + + + netcoreapp2.0 + 2.0.0 + 2.0.0 + This project is aimed at people using .NET running a micro services / service orientated architecture that need a unified point of entry into their system. In particular I want easy integration with IdentityServer reference and bearer tokens. We have been unable to find this in my current workplace without having to write our own Javascript middlewares to handle the IdentityServer reference tokens. We would rather use the IdentityServer code that already exists to do this. Ocelot is a bunch of middlewares in a specific order. Ocelot manipulates the HttpRequest object into a state specified by its configuration until it reaches a request builder middleware where it creates a HttpRequestMessage object which is used to make a request to a downstream service. The middleware that makes the request is the last thing in the Ocelot pipeline. It does not call the next middleware. The response from the downstream service is stored in a per request scoped repository and retrived as the requests goes back up the Ocelot pipeline. There is a piece of middleware that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client. That is basically it with a bunch of other features. + Ocelot + 0.0.0-dev + Ocelot + Ocelot + API Gateway;.NET core + https://github.com/TomPallister/Ocelot + https://github.com/TomPallister/Ocelot + win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64 + false + false + True + false + Tom Pallister + + + full + True + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Ocelot/Properties/AssemblyInfo.cs b/src/Ocelot/Properties/AssemblyInfo.cs index dd8b7610c..cf28a5101 100644 --- a/src/Ocelot/Properties/AssemblyInfo.cs +++ b/src/Ocelot/Properties/AssemblyInfo.cs @@ -1,18 +1,18 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Ocelot")] -[assembly: AssemblyTrademark("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("d6df4206-0dba-41d8-884d-c3e08290fdbb")] +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Ocelot")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("d6df4206-0dba-41d8-884d-c3e08290fdbb")] diff --git a/src/Ocelot/QueryStrings/AddQueriesToRequest.cs b/src/Ocelot/QueryStrings/AddQueriesToRequest.cs index 839da9bc5..25e9772a3 100644 --- a/src/Ocelot/QueryStrings/AddQueriesToRequest.cs +++ b/src/Ocelot/QueryStrings/AddQueriesToRequest.cs @@ -1,67 +1,67 @@ -using System.Collections.Generic; -using System.Linq; -using Microsoft.AspNetCore.Http; -using Ocelot.Configuration; -using Ocelot.Infrastructure.Claims.Parser; -using Ocelot.Responses; -using System.Security.Claims; -using System.Net.Http; -using System; - -namespace Ocelot.QueryStrings -{ - public class AddQueriesToRequest : IAddQueriesToRequest - { - private readonly IClaimsParser _claimsParser; - - public AddQueriesToRequest(IClaimsParser claimsParser) - { - _claimsParser = claimsParser; - } - - public Response SetQueriesOnDownstreamRequest(List claimsToThings, IEnumerable claims, HttpRequestMessage downstreamRequest) - { - var queryDictionary = ConvertQueryStringToDictionary(downstreamRequest.RequestUri.Query); - - foreach (var config in claimsToThings) - { - var value = _claimsParser.GetValue(claims, config.NewKey, config.Delimiter, config.Index); - - if (value.IsError) - { - return new ErrorResponse(value.Errors); - } - - var exists = queryDictionary.FirstOrDefault(x => x.Key == config.ExistingKey); - - if (!string.IsNullOrEmpty(exists.Key)) - { - queryDictionary[exists.Key] = value.Data; - } - else - { - queryDictionary.Add(config.ExistingKey, value.Data); - } - } - - var uriBuilder = new UriBuilder(downstreamRequest.RequestUri); - uriBuilder.Query = ConvertDictionaryToQueryString(queryDictionary); - - downstreamRequest.RequestUri = uriBuilder.Uri; - - return new OkResponse(); - } - - private Dictionary ConvertQueryStringToDictionary(string queryString) - { - return Microsoft.AspNetCore.WebUtilities.QueryHelpers - .ParseQuery(queryString) - .ToDictionary(q => q.Key, q => q.Value.FirstOrDefault() ?? string.Empty); - } - - private string ConvertDictionaryToQueryString(Dictionary queryDictionary) - { - return Microsoft.AspNetCore.WebUtilities.QueryHelpers.AddQueryString("", queryDictionary); - } - } +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Http; +using Ocelot.Configuration; +using Ocelot.Infrastructure.Claims.Parser; +using Ocelot.Responses; +using System.Security.Claims; +using System.Net.Http; +using System; + +namespace Ocelot.QueryStrings +{ + public class AddQueriesToRequest : IAddQueriesToRequest + { + private readonly IClaimsParser _claimsParser; + + public AddQueriesToRequest(IClaimsParser claimsParser) + { + _claimsParser = claimsParser; + } + + public Response SetQueriesOnDownstreamRequest(List claimsToThings, IEnumerable claims, HttpRequestMessage downstreamRequest) + { + var queryDictionary = ConvertQueryStringToDictionary(downstreamRequest.RequestUri.Query); + + foreach (var config in claimsToThings) + { + var value = _claimsParser.GetValue(claims, config.NewKey, config.Delimiter, config.Index); + + if (value.IsError) + { + return new ErrorResponse(value.Errors); + } + + var exists = queryDictionary.FirstOrDefault(x => x.Key == config.ExistingKey); + + if (!string.IsNullOrEmpty(exists.Key)) + { + queryDictionary[exists.Key] = value.Data; + } + else + { + queryDictionary.Add(config.ExistingKey, value.Data); + } + } + + var uriBuilder = new UriBuilder(downstreamRequest.RequestUri); + uriBuilder.Query = ConvertDictionaryToQueryString(queryDictionary); + + downstreamRequest.RequestUri = uriBuilder.Uri; + + return new OkResponse(); + } + + private Dictionary ConvertQueryStringToDictionary(string queryString) + { + return Microsoft.AspNetCore.WebUtilities.QueryHelpers + .ParseQuery(queryString) + .ToDictionary(q => q.Key, q => q.Value.FirstOrDefault() ?? string.Empty); + } + + private string ConvertDictionaryToQueryString(Dictionary queryDictionary) + { + return Microsoft.AspNetCore.WebUtilities.QueryHelpers.AddQueryString("", queryDictionary); + } + } } \ No newline at end of file diff --git a/src/Ocelot/QueryStrings/IAddQueriesToRequest.cs b/src/Ocelot/QueryStrings/IAddQueriesToRequest.cs index 762b8a00a..34a6c2f52 100644 --- a/src/Ocelot/QueryStrings/IAddQueriesToRequest.cs +++ b/src/Ocelot/QueryStrings/IAddQueriesToRequest.cs @@ -1,14 +1,14 @@ -using System.Collections.Generic; -using Microsoft.AspNetCore.Http; -using Ocelot.Configuration; -using Ocelot.Responses; -using System.Net.Http; -using System.Security.Claims; - -namespace Ocelot.QueryStrings -{ - public interface IAddQueriesToRequest - { - Response SetQueriesOnDownstreamRequest(List claimsToThings, IEnumerable claims, HttpRequestMessage downstreamRequest); - } -} +using System.Collections.Generic; +using Microsoft.AspNetCore.Http; +using Ocelot.Configuration; +using Ocelot.Responses; +using System.Net.Http; +using System.Security.Claims; + +namespace Ocelot.QueryStrings +{ + public interface IAddQueriesToRequest + { + Response SetQueriesOnDownstreamRequest(List claimsToThings, IEnumerable claims, HttpRequestMessage downstreamRequest); + } +} diff --git a/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddleware.cs b/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddleware.cs index b359eadb3..bdd797300 100644 --- a/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddleware.cs +++ b/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddleware.cs @@ -1,47 +1,47 @@ -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Ocelot.Infrastructure.RequestData; -using Ocelot.Logging; -using Ocelot.Middleware; - -namespace Ocelot.QueryStrings.Middleware -{ - public class QueryStringBuilderMiddleware : OcelotMiddleware - { - private readonly RequestDelegate _next; - private readonly IAddQueriesToRequest _addQueriesToRequest; - private readonly IOcelotLogger _logger; - - public QueryStringBuilderMiddleware(RequestDelegate next, - IOcelotLoggerFactory loggerFactory, - IRequestScopedDataRepository requestScopedDataRepository, - IAddQueriesToRequest addQueriesToRequest) - : base(requestScopedDataRepository) - { - _next = next; - _addQueriesToRequest = addQueriesToRequest; - _logger = loggerFactory.CreateLogger(); - } - - public async Task Invoke(HttpContext context) - { - if (DownstreamRoute.ReRoute.ClaimsToQueries.Any()) - { - _logger.LogDebug($"{DownstreamRoute.ReRoute.DownstreamPathTemplate.Value} has instructions to convert claims to queries"); - - var response = _addQueriesToRequest.SetQueriesOnDownstreamRequest(DownstreamRoute.ReRoute.ClaimsToQueries, context.User.Claims, DownstreamRequest); - - if (response.IsError) - { - _logger.LogDebug("there was an error setting queries on context, setting pipeline error"); - - SetPipelineError(response.Errors); - return; - } - } - - await _next.Invoke(context); - } - } -} +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Ocelot.Infrastructure.RequestData; +using Ocelot.Logging; +using Ocelot.Middleware; + +namespace Ocelot.QueryStrings.Middleware +{ + public class QueryStringBuilderMiddleware : OcelotMiddleware + { + private readonly RequestDelegate _next; + private readonly IAddQueriesToRequest _addQueriesToRequest; + private readonly IOcelotLogger _logger; + + public QueryStringBuilderMiddleware(RequestDelegate next, + IOcelotLoggerFactory loggerFactory, + IRequestScopedDataRepository requestScopedDataRepository, + IAddQueriesToRequest addQueriesToRequest) + : base(requestScopedDataRepository) + { + _next = next; + _addQueriesToRequest = addQueriesToRequest; + _logger = loggerFactory.CreateLogger(); + } + + public async Task Invoke(HttpContext context) + { + if (DownstreamRoute.ReRoute.ClaimsToQueries.Any()) + { + _logger.LogDebug($"{DownstreamRoute.ReRoute.DownstreamPathTemplate.Value} has instructions to convert claims to queries"); + + var response = _addQueriesToRequest.SetQueriesOnDownstreamRequest(DownstreamRoute.ReRoute.ClaimsToQueries, context.User.Claims, DownstreamRequest); + + if (response.IsError) + { + _logger.LogDebug("there was an error setting queries on context, setting pipeline error"); + + SetPipelineError(response.Errors); + return; + } + } + + await _next.Invoke(context); + } + } +} diff --git a/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddlewareExtensions.cs b/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddlewareExtensions.cs index 8ff39be22..647afb07c 100644 --- a/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddlewareExtensions.cs +++ b/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddlewareExtensions.cs @@ -1,12 +1,12 @@ -using Microsoft.AspNetCore.Builder; - -namespace Ocelot.QueryStrings.Middleware -{ - public static class QueryStringBuilderMiddlewareExtensions - { - public static IApplicationBuilder UseQueryStringBuilderMiddleware(this IApplicationBuilder builder) - { - return builder.UseMiddleware(); - } - } +using Microsoft.AspNetCore.Builder; + +namespace Ocelot.QueryStrings.Middleware +{ + public static class QueryStringBuilderMiddlewareExtensions + { + public static IApplicationBuilder UseQueryStringBuilderMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Raft/ExcludeFromCoverage.cs b/src/Ocelot/Raft/ExcludeFromCoverage.cs index 9ea5544a9..70d421679 100644 --- a/src/Ocelot/Raft/ExcludeFromCoverage.cs +++ b/src/Ocelot/Raft/ExcludeFromCoverage.cs @@ -1,7 +1,7 @@ -using System; - -namespace Ocelot.Raft -{ - [AttributeUsage(AttributeTargets.Class|AttributeTargets.Method|AttributeTargets.Property)] - public class ExcludeFromCoverageAttribute : Attribute{} +using System; + +namespace Ocelot.Raft +{ + [AttributeUsage(AttributeTargets.Class|AttributeTargets.Method|AttributeTargets.Property)] + public class ExcludeFromCoverageAttribute : Attribute{} } \ No newline at end of file diff --git a/src/Ocelot/Raft/FakeCommand.cs b/src/Ocelot/Raft/FakeCommand.cs index b8699c5eb..795ca7a50 100644 --- a/src/Ocelot/Raft/FakeCommand.cs +++ b/src/Ocelot/Raft/FakeCommand.cs @@ -1,15 +1,15 @@ -using Rafty.FiniteStateMachine; - -namespace Ocelot.Raft -{ - [ExcludeFromCoverage] - public class FakeCommand : ICommand - { - public FakeCommand(string value) - { - this.Value = value; - - } - public string Value { get; private set; } - } -} +using Rafty.FiniteStateMachine; + +namespace Ocelot.Raft +{ + [ExcludeFromCoverage] + public class FakeCommand : ICommand + { + public FakeCommand(string value) + { + this.Value = value; + + } + public string Value { get; private set; } + } +} diff --git a/src/Ocelot/Raft/FileFsm.cs b/src/Ocelot/Raft/FileFsm.cs index dbae10dad..13c41c9d7 100644 --- a/src/Ocelot/Raft/FileFsm.cs +++ b/src/Ocelot/Raft/FileFsm.cs @@ -1,33 +1,33 @@ -using System; -using System.IO; -using Newtonsoft.Json; -using Rafty.FiniteStateMachine; -using Rafty.Infrastructure; -using Rafty.Log; - -namespace Ocelot.Raft -{ - [ExcludeFromCoverage] - public class FileFsm : IFiniteStateMachine - { - private string _id; - - public FileFsm(NodeId nodeId) - { - _id = nodeId.Id.Replace("/","").Replace(":",""); - } - - public void Handle(LogEntry log) - { - try - { - var json = JsonConvert.SerializeObject(log.CommandData); - File.AppendAllText(_id, json); - } - catch(Exception exception) - { - Console.WriteLine(exception); - } - } - } -} +using System; +using System.IO; +using Newtonsoft.Json; +using Rafty.FiniteStateMachine; +using Rafty.Infrastructure; +using Rafty.Log; + +namespace Ocelot.Raft +{ + [ExcludeFromCoverage] + public class FileFsm : IFiniteStateMachine + { + private string _id; + + public FileFsm(NodeId nodeId) + { + _id = nodeId.Id.Replace("/","").Replace(":",""); + } + + public void Handle(LogEntry log) + { + try + { + var json = JsonConvert.SerializeObject(log.CommandData); + File.AppendAllText(_id, json); + } + catch(Exception exception) + { + Console.WriteLine(exception); + } + } + } +} diff --git a/src/Ocelot/Raft/FilePeer.cs b/src/Ocelot/Raft/FilePeer.cs index f983d3cc8..dc18840d7 100644 --- a/src/Ocelot/Raft/FilePeer.cs +++ b/src/Ocelot/Raft/FilePeer.cs @@ -1,8 +1,8 @@ -namespace Ocelot.Raft -{ - [ExcludeFromCoverage] - public class FilePeer - { - public string HostAndPort { get; set; } - } -} +namespace Ocelot.Raft +{ + [ExcludeFromCoverage] + public class FilePeer + { + public string HostAndPort { get; set; } + } +} diff --git a/src/Ocelot/Raft/FilePeers.cs b/src/Ocelot/Raft/FilePeers.cs index 0aab1df46..7ad32cbea 100644 --- a/src/Ocelot/Raft/FilePeers.cs +++ b/src/Ocelot/Raft/FilePeers.cs @@ -1,15 +1,15 @@ -using System.Collections.Generic; - -namespace Ocelot.Raft -{ - [ExcludeFromCoverage] - public class FilePeers - { - public FilePeers() - { - Peers = new List(); - } - - public List Peers {get; set;} - } -} +using System.Collections.Generic; + +namespace Ocelot.Raft +{ + [ExcludeFromCoverage] + public class FilePeers + { + public FilePeers() + { + Peers = new List(); + } + + public List Peers {get; set;} + } +} diff --git a/src/Ocelot/Raft/FilePeersProvider.cs b/src/Ocelot/Raft/FilePeersProvider.cs index 413fdb425..2718ae189 100644 --- a/src/Ocelot/Raft/FilePeersProvider.cs +++ b/src/Ocelot/Raft/FilePeersProvider.cs @@ -1,44 +1,44 @@ -using System; -using System.Collections.Generic; -using System.Net.Http; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Options; -using Ocelot.Configuration; -using Ocelot.Configuration.Provider; -using Rafty.Concensus; -using Rafty.Infrastructure; - -namespace Ocelot.Raft -{ - [ExcludeFromCoverage] - public class FilePeersProvider : IPeersProvider - { - private readonly IOptions _options; - private List _peers; - private IWebHostBuilder _builder; - private IOcelotConfigurationProvider _provider; - private IIdentityServerConfiguration _identityServerConfig; - - public FilePeersProvider(IOptions options, IWebHostBuilder builder, IOcelotConfigurationProvider provider, IIdentityServerConfiguration identityServerConfig) - { - _identityServerConfig = identityServerConfig; - _provider = provider; - _builder = builder; - _options = options; - _peers = new List(); - //todo - sort out async nonsense.. - var config = _provider.Get().GetAwaiter().GetResult(); - foreach (var item in _options.Value.Peers) - { - var httpClient = new HttpClient(); - //todo what if this errors? - var httpPeer = new HttpPeer(item.HostAndPort, httpClient, _builder, config.Data, _identityServerConfig); - _peers.Add(httpPeer); - } - } - public List Get() - { - return _peers; - } - } -} +using System; +using System.Collections.Generic; +using System.Net.Http; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Options; +using Ocelot.Configuration; +using Ocelot.Configuration.Provider; +using Rafty.Concensus; +using Rafty.Infrastructure; + +namespace Ocelot.Raft +{ + [ExcludeFromCoverage] + public class FilePeersProvider : IPeersProvider + { + private readonly IOptions _options; + private List _peers; + private IWebHostBuilder _builder; + private IOcelotConfigurationProvider _provider; + private IIdentityServerConfiguration _identityServerConfig; + + public FilePeersProvider(IOptions options, IWebHostBuilder builder, IOcelotConfigurationProvider provider, IIdentityServerConfiguration identityServerConfig) + { + _identityServerConfig = identityServerConfig; + _provider = provider; + _builder = builder; + _options = options; + _peers = new List(); + //todo - sort out async nonsense.. + var config = _provider.Get().GetAwaiter().GetResult(); + foreach (var item in _options.Value.Peers) + { + var httpClient = new HttpClient(); + //todo what if this errors? + var httpPeer = new HttpPeer(item.HostAndPort, httpClient, _builder, config.Data, _identityServerConfig); + _peers.Add(httpPeer); + } + } + public List Get() + { + return _peers; + } + } +} diff --git a/src/Ocelot/Raft/HttpPeer.cs b/src/Ocelot/Raft/HttpPeer.cs index 8ba8fe70a..67cc8e151 100644 --- a/src/Ocelot/Raft/HttpPeer.cs +++ b/src/Ocelot/Raft/HttpPeer.cs @@ -1,128 +1,128 @@ -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Hosting; -using Newtonsoft.Json; -using Ocelot.Authentication; -using Ocelot.Configuration; -using Ocelot.Configuration.Provider; -using Rafty.Concensus; -using Rafty.FiniteStateMachine; - -namespace Ocelot.Raft -{ - [ExcludeFromCoverage] - public class HttpPeer : IPeer - { - private string _hostAndPort; - private HttpClient _httpClient; - private JsonSerializerSettings _jsonSerializerSettings; - private string _baseSchemeUrlAndPort; - private BearerToken _token; - private IOcelotConfiguration _config; - private IIdentityServerConfiguration _identityServerConfiguration; - - public HttpPeer(string hostAndPort, HttpClient httpClient, IWebHostBuilder builder, IOcelotConfiguration config, IIdentityServerConfiguration identityServerConfiguration) - { - _identityServerConfiguration = identityServerConfiguration; - _config = config; - Id = hostAndPort; - _hostAndPort = hostAndPort; - _httpClient = httpClient; - _jsonSerializerSettings = new JsonSerializerSettings() { - TypeNameHandling = TypeNameHandling.All - }; - _baseSchemeUrlAndPort = builder.GetSetting(WebHostDefaults.ServerUrlsKey); - } - - public string Id {get; private set;} - - public RequestVoteResponse Request(RequestVote requestVote) - { - if(_token == null) - { - SetToken(); - } - - var json = JsonConvert.SerializeObject(requestVote, _jsonSerializerSettings); - var content = new StringContent(json); - content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); - var response = _httpClient.PostAsync($"{_hostAndPort}/administration/raft/requestvote", content).GetAwaiter().GetResult(); - if(response.IsSuccessStatusCode) - { - return JsonConvert.DeserializeObject(response.Content.ReadAsStringAsync().GetAwaiter().GetResult(), _jsonSerializerSettings); - } - else - { - return new RequestVoteResponse(false, requestVote.Term); - } - } - - public AppendEntriesResponse Request(AppendEntries appendEntries) - { - try - { - if(_token == null) - { - SetToken(); - } - var json = JsonConvert.SerializeObject(appendEntries, _jsonSerializerSettings); - var content = new StringContent(json); - content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); - var response = _httpClient.PostAsync($"{_hostAndPort}/administration/raft/appendEntries", content).GetAwaiter().GetResult(); - if(response.IsSuccessStatusCode) - { - return JsonConvert.DeserializeObject(response.Content.ReadAsStringAsync().GetAwaiter().GetResult(),_jsonSerializerSettings); - } - else - { - return new AppendEntriesResponse(appendEntries.Term, false); - } - } - catch(Exception ex) - { - Console.WriteLine(ex); - return new AppendEntriesResponse(appendEntries.Term, false); - } - } - - public Response Request(T command) where T : ICommand - { - if(_token == null) - { - SetToken(); - } - var json = JsonConvert.SerializeObject(command, _jsonSerializerSettings); - var content = new StringContent(json); - content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); - var response = _httpClient.PostAsync($"{_hostAndPort}/administration/raft/command", content).GetAwaiter().GetResult(); - if(response.IsSuccessStatusCode) - { - return JsonConvert.DeserializeObject>(response.Content.ReadAsStringAsync().GetAwaiter().GetResult(), _jsonSerializerSettings); - } - else - { - return new ErrorResponse(response.Content.ReadAsStringAsync().GetAwaiter().GetResult(), command); - } - } - - private void SetToken() - { - var tokenUrl = $"{_baseSchemeUrlAndPort}{_config.AdministrationPath}/connect/token"; - var formData = new List> - { - new KeyValuePair("client_id", _identityServerConfiguration.ApiName), - new KeyValuePair("client_secret", _identityServerConfiguration.ApiSecret), - new KeyValuePair("scope", _identityServerConfiguration.ApiName), - new KeyValuePair("grant_type", "client_credentials") - }; - var content = new FormUrlEncodedContent(formData); - var response = _httpClient.PostAsync(tokenUrl, content).GetAwaiter().GetResult(); - var responseContent = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); - response.EnsureSuccessStatusCode(); - _token = JsonConvert.DeserializeObject(responseContent); - _httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(_token.TokenType, _token.AccessToken); - } - } -} +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Newtonsoft.Json; +using Ocelot.Authentication; +using Ocelot.Configuration; +using Ocelot.Configuration.Provider; +using Rafty.Concensus; +using Rafty.FiniteStateMachine; + +namespace Ocelot.Raft +{ + [ExcludeFromCoverage] + public class HttpPeer : IPeer + { + private string _hostAndPort; + private HttpClient _httpClient; + private JsonSerializerSettings _jsonSerializerSettings; + private string _baseSchemeUrlAndPort; + private BearerToken _token; + private IOcelotConfiguration _config; + private IIdentityServerConfiguration _identityServerConfiguration; + + public HttpPeer(string hostAndPort, HttpClient httpClient, IWebHostBuilder builder, IOcelotConfiguration config, IIdentityServerConfiguration identityServerConfiguration) + { + _identityServerConfiguration = identityServerConfiguration; + _config = config; + Id = hostAndPort; + _hostAndPort = hostAndPort; + _httpClient = httpClient; + _jsonSerializerSettings = new JsonSerializerSettings() { + TypeNameHandling = TypeNameHandling.All + }; + _baseSchemeUrlAndPort = builder.GetSetting(WebHostDefaults.ServerUrlsKey); + } + + public string Id {get; private set;} + + public RequestVoteResponse Request(RequestVote requestVote) + { + if(_token == null) + { + SetToken(); + } + + var json = JsonConvert.SerializeObject(requestVote, _jsonSerializerSettings); + var content = new StringContent(json); + content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); + var response = _httpClient.PostAsync($"{_hostAndPort}/administration/raft/requestvote", content).GetAwaiter().GetResult(); + if(response.IsSuccessStatusCode) + { + return JsonConvert.DeserializeObject(response.Content.ReadAsStringAsync().GetAwaiter().GetResult(), _jsonSerializerSettings); + } + else + { + return new RequestVoteResponse(false, requestVote.Term); + } + } + + public AppendEntriesResponse Request(AppendEntries appendEntries) + { + try + { + if(_token == null) + { + SetToken(); + } + var json = JsonConvert.SerializeObject(appendEntries, _jsonSerializerSettings); + var content = new StringContent(json); + content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); + var response = _httpClient.PostAsync($"{_hostAndPort}/administration/raft/appendEntries", content).GetAwaiter().GetResult(); + if(response.IsSuccessStatusCode) + { + return JsonConvert.DeserializeObject(response.Content.ReadAsStringAsync().GetAwaiter().GetResult(),_jsonSerializerSettings); + } + else + { + return new AppendEntriesResponse(appendEntries.Term, false); + } + } + catch(Exception ex) + { + Console.WriteLine(ex); + return new AppendEntriesResponse(appendEntries.Term, false); + } + } + + public Response Request(T command) where T : ICommand + { + if(_token == null) + { + SetToken(); + } + var json = JsonConvert.SerializeObject(command, _jsonSerializerSettings); + var content = new StringContent(json); + content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); + var response = _httpClient.PostAsync($"{_hostAndPort}/administration/raft/command", content).GetAwaiter().GetResult(); + if(response.IsSuccessStatusCode) + { + return JsonConvert.DeserializeObject>(response.Content.ReadAsStringAsync().GetAwaiter().GetResult(), _jsonSerializerSettings); + } + else + { + return new ErrorResponse(response.Content.ReadAsStringAsync().GetAwaiter().GetResult(), command); + } + } + + private void SetToken() + { + var tokenUrl = $"{_baseSchemeUrlAndPort}{_config.AdministrationPath}/connect/token"; + var formData = new List> + { + new KeyValuePair("client_id", _identityServerConfiguration.ApiName), + new KeyValuePair("client_secret", _identityServerConfiguration.ApiSecret), + new KeyValuePair("scope", _identityServerConfiguration.ApiName), + new KeyValuePair("grant_type", "client_credentials") + }; + var content = new FormUrlEncodedContent(formData); + var response = _httpClient.PostAsync(tokenUrl, content).GetAwaiter().GetResult(); + var responseContent = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); + response.EnsureSuccessStatusCode(); + _token = JsonConvert.DeserializeObject(responseContent); + _httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(_token.TokenType, _token.AccessToken); + } + } +} diff --git a/src/Ocelot/Raft/OcelotFiniteStateMachine.cs b/src/Ocelot/Raft/OcelotFiniteStateMachine.cs index 96a9ceb19..f63695f2c 100644 --- a/src/Ocelot/Raft/OcelotFiniteStateMachine.cs +++ b/src/Ocelot/Raft/OcelotFiniteStateMachine.cs @@ -1,25 +1,25 @@ -using Ocelot.Configuration.Setter; -using Rafty.FiniteStateMachine; -using Rafty.Log; - -namespace Ocelot.Raft -{ - [ExcludeFromCoverage] - public class OcelotFiniteStateMachine : IFiniteStateMachine - { - private IFileConfigurationSetter _setter; - - public OcelotFiniteStateMachine(IFileConfigurationSetter setter) - { - _setter = setter; - } - - public void Handle(LogEntry log) - { - //todo - handle an error - //hack it to just cast as at the moment we know this is the only command :P - var hack = (UpdateFileConfiguration)log.CommandData; - _setter.Set(hack.Configuration).GetAwaiter().GetResult();; - } - } +using Ocelot.Configuration.Setter; +using Rafty.FiniteStateMachine; +using Rafty.Log; + +namespace Ocelot.Raft +{ + [ExcludeFromCoverage] + public class OcelotFiniteStateMachine : IFiniteStateMachine + { + private IFileConfigurationSetter _setter; + + public OcelotFiniteStateMachine(IFileConfigurationSetter setter) + { + _setter = setter; + } + + public void Handle(LogEntry log) + { + //todo - handle an error + //hack it to just cast as at the moment we know this is the only command :P + var hack = (UpdateFileConfiguration)log.CommandData; + _setter.Set(hack.Configuration).GetAwaiter().GetResult();; + } + } } \ No newline at end of file diff --git a/src/Ocelot/Raft/RaftController.cs b/src/Ocelot/Raft/RaftController.cs index 08ee0c34d..7177044b1 100644 --- a/src/Ocelot/Raft/RaftController.cs +++ b/src/Ocelot/Raft/RaftController.cs @@ -1,84 +1,84 @@ -using System; -using System.IO; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using Ocelot.Logging; -using Ocelot.Raft; -using Rafty.Concensus; -using Rafty.FiniteStateMachine; - -namespace Ocelot.Raft -{ - [ExcludeFromCoverage] - [Authorize] - [Route("raft")] - public class RaftController : Controller - { - private readonly INode _node; - private IOcelotLogger _logger; - private string _baseSchemeUrlAndPort; - private JsonSerializerSettings _jsonSerialiserSettings; - - public RaftController(INode node, IOcelotLoggerFactory loggerFactory, IWebHostBuilder builder) - { - _jsonSerialiserSettings = new JsonSerializerSettings { - TypeNameHandling = TypeNameHandling.All - }; - _baseSchemeUrlAndPort = builder.GetSetting(WebHostDefaults.ServerUrlsKey); - _logger = loggerFactory.CreateLogger(); - _node = node; - } - - [Route("appendentries")] - public async Task AppendEntries() - { - using(var reader = new StreamReader(HttpContext.Request.Body)) - { - var json = await reader.ReadToEndAsync(); - var appendEntries = JsonConvert.DeserializeObject(json, _jsonSerialiserSettings); - _logger.LogDebug($"{_baseSchemeUrlAndPort}/appendentries called, my state is {_node.State.GetType().FullName}"); - var appendEntriesResponse = _node.Handle(appendEntries); - return new OkObjectResult(appendEntriesResponse); - } - } - - [Route("requestvote")] - public async Task RequestVote() - { - using(var reader = new StreamReader(HttpContext.Request.Body)) - { - var json = await reader.ReadToEndAsync(); - var requestVote = JsonConvert.DeserializeObject(json, _jsonSerialiserSettings); - _logger.LogDebug($"{_baseSchemeUrlAndPort}/requestvote called, my state is {_node.State.GetType().FullName}"); - var requestVoteResponse = _node.Handle(requestVote); - return new OkObjectResult(requestVoteResponse); - } - } - - [Route("command")] - public async Task Command() - { - try - { - using(var reader = new StreamReader(HttpContext.Request.Body)) - { - var json = await reader.ReadToEndAsync(); - var command = JsonConvert.DeserializeObject(json, _jsonSerialiserSettings); - _logger.LogDebug($"{_baseSchemeUrlAndPort}/command called, my state is {_node.State.GetType().FullName}"); - var commandResponse = _node.Accept(command); - json = JsonConvert.SerializeObject(commandResponse, _jsonSerialiserSettings); - return StatusCode(200, json); - } - } - catch(Exception e) - { - _logger.LogError($"THERE WAS A PROBLEM ON NODE {_node.State.CurrentState.Id}", e); - throw e; - } - } - } +using System; +using System.IO; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Ocelot.Logging; +using Ocelot.Raft; +using Rafty.Concensus; +using Rafty.FiniteStateMachine; + +namespace Ocelot.Raft +{ + [ExcludeFromCoverage] + [Authorize] + [Route("raft")] + public class RaftController : Controller + { + private readonly INode _node; + private IOcelotLogger _logger; + private string _baseSchemeUrlAndPort; + private JsonSerializerSettings _jsonSerialiserSettings; + + public RaftController(INode node, IOcelotLoggerFactory loggerFactory, IWebHostBuilder builder) + { + _jsonSerialiserSettings = new JsonSerializerSettings { + TypeNameHandling = TypeNameHandling.All + }; + _baseSchemeUrlAndPort = builder.GetSetting(WebHostDefaults.ServerUrlsKey); + _logger = loggerFactory.CreateLogger(); + _node = node; + } + + [Route("appendentries")] + public async Task AppendEntries() + { + using(var reader = new StreamReader(HttpContext.Request.Body)) + { + var json = await reader.ReadToEndAsync(); + var appendEntries = JsonConvert.DeserializeObject(json, _jsonSerialiserSettings); + _logger.LogDebug($"{_baseSchemeUrlAndPort}/appendentries called, my state is {_node.State.GetType().FullName}"); + var appendEntriesResponse = _node.Handle(appendEntries); + return new OkObjectResult(appendEntriesResponse); + } + } + + [Route("requestvote")] + public async Task RequestVote() + { + using(var reader = new StreamReader(HttpContext.Request.Body)) + { + var json = await reader.ReadToEndAsync(); + var requestVote = JsonConvert.DeserializeObject(json, _jsonSerialiserSettings); + _logger.LogDebug($"{_baseSchemeUrlAndPort}/requestvote called, my state is {_node.State.GetType().FullName}"); + var requestVoteResponse = _node.Handle(requestVote); + return new OkObjectResult(requestVoteResponse); + } + } + + [Route("command")] + public async Task Command() + { + try + { + using(var reader = new StreamReader(HttpContext.Request.Body)) + { + var json = await reader.ReadToEndAsync(); + var command = JsonConvert.DeserializeObject(json, _jsonSerialiserSettings); + _logger.LogDebug($"{_baseSchemeUrlAndPort}/command called, my state is {_node.State.GetType().FullName}"); + var commandResponse = _node.Accept(command); + json = JsonConvert.SerializeObject(commandResponse, _jsonSerialiserSettings); + return StatusCode(200, json); + } + } + catch(Exception e) + { + _logger.LogError($"THERE WAS A PROBLEM ON NODE {_node.State.CurrentState.Id}", e); + throw e; + } + } + } } \ No newline at end of file diff --git a/src/Ocelot/Raft/SqlLiteLog.cs b/src/Ocelot/Raft/SqlLiteLog.cs index aaa1e7266..098d9c066 100644 --- a/src/Ocelot/Raft/SqlLiteLog.cs +++ b/src/Ocelot/Raft/SqlLiteLog.cs @@ -1,279 +1,279 @@ -using System.IO; -using Rafty.Log; -using Microsoft.Data.Sqlite; -using Newtonsoft.Json; -using System; -using Rafty.Infrastructure; -using System.Collections.Generic; - -namespace Ocelot.Raft -{ - [ExcludeFromCoverage] - public class SqlLiteLog : ILog - { - private string _path; - private readonly object _lock = new object(); - - public SqlLiteLog(NodeId nodeId) - { - _path = $"{nodeId.Id.Replace("/","").Replace(":","")}.db"; - if(!File.Exists(_path)) - { - lock(_lock) - { - FileStream fs = File.Create(_path); - fs.Dispose(); - } - using(var connection = new SqliteConnection($"Data Source={_path};")) - { - connection.Open(); - var sql = @"create table logs ( - id integer primary key, - data text not null - )"; - using(var command = new SqliteCommand(sql, connection)) - { - var result = command.ExecuteNonQuery(); - } - } - } - } - - public int LastLogIndex - { - get - { - lock(_lock) - { - var result = 1; - using(var connection = new SqliteConnection($"Data Source={_path};")) - { - connection.Open(); - var sql = @"select id from logs order by id desc limit 1"; - using(var command = new SqliteCommand(sql, connection)) - { - var index = Convert.ToInt32(command.ExecuteScalar()); - if(index > result) - { - result = index; - } - } - } - return result; - } - } - } - - public long LastLogTerm - { - get - { - lock(_lock) - { - long result = 0; - using(var connection = new SqliteConnection($"Data Source={_path};")) - { - connection.Open(); - var sql = @"select data from logs order by id desc limit 1"; - using(var command = new SqliteCommand(sql, connection)) - { - var data = Convert.ToString(command.ExecuteScalar()); - var jsonSerializerSettings = new JsonSerializerSettings() { - TypeNameHandling = TypeNameHandling.All - }; - var log = JsonConvert.DeserializeObject(data, jsonSerializerSettings); - if(log != null && log.Term > result) - { - result = log.Term; - } - } - } - return result; - } - } - } - - public int Count - { - get - { - lock(_lock) - { - var result = 0; - using(var connection = new SqliteConnection($"Data Source={_path};")) - { - connection.Open(); - var sql = @"select count(id) from logs"; - using(var command = new SqliteCommand(sql, connection)) - { - var index = Convert.ToInt32(command.ExecuteScalar()); - if(index > result) - { - result = index; - } - } - } - return result; - } - } - } - - public int Apply(LogEntry log) - { - lock(_lock) - { - using(var connection = new SqliteConnection($"Data Source={_path};")) - { - connection.Open(); - var jsonSerializerSettings = new JsonSerializerSettings() { - TypeNameHandling = TypeNameHandling.All - }; - var data = JsonConvert.SerializeObject(log, jsonSerializerSettings); - //todo - sql injection dont copy this.. - var sql = $"insert into logs (data) values ('{data}')"; - using(var command = new SqliteCommand(sql, connection)) - { - var result = command.ExecuteNonQuery(); - } - - sql = "select last_insert_rowid()"; - using(var command = new SqliteCommand(sql, connection)) - { - var result = command.ExecuteScalar(); - return Convert.ToInt32(result); - } - } - } - } - - public void DeleteConflictsFromThisLog(int index, LogEntry logEntry) - { - lock(_lock) - { - using(var connection = new SqliteConnection($"Data Source={_path};")) - { - connection.Open(); - //todo - sql injection dont copy this.. - var sql = $"select data from logs where id = {index};"; - using(var command = new SqliteCommand(sql, connection)) - { - var data = Convert.ToString(command.ExecuteScalar()); - var jsonSerializerSettings = new JsonSerializerSettings() { - TypeNameHandling = TypeNameHandling.All - }; - var log = JsonConvert.DeserializeObject(data, jsonSerializerSettings); - if(logEntry != null && log != null && logEntry.Term != log.Term) - { - //todo - sql injection dont copy this.. - var deleteSql = $"delete from logs where id >= {index};"; - using(var deleteCommand = new SqliteCommand(deleteSql, connection)) - { - var result = deleteCommand.ExecuteNonQuery(); - } - } - } - } - } - } - - public LogEntry Get(int index) - { - lock(_lock) - { - using(var connection = new SqliteConnection($"Data Source={_path};")) - { - connection.Open(); - //todo - sql injection dont copy this.. - var sql = $"select data from logs where id = {index}"; - using(var command = new SqliteCommand(sql, connection)) - { - var data = Convert.ToString(command.ExecuteScalar()); - var jsonSerializerSettings = new JsonSerializerSettings() { - TypeNameHandling = TypeNameHandling.All - }; - var log = JsonConvert.DeserializeObject(data, jsonSerializerSettings); - return log; - } - } - } - } - - public System.Collections.Generic.List<(int index, LogEntry logEntry)> GetFrom(int index) - { - lock(_lock) - { - var logsToReturn = new List<(int, LogEntry)>(); - - using(var connection = new SqliteConnection($"Data Source={_path};")) - { - connection.Open(); - //todo - sql injection dont copy this.. - var sql = $"select id, data from logs where id >= {index}"; - using(var command = new SqliteCommand(sql, connection)) - { - using(var reader = command.ExecuteReader()) - { - while(reader.Read()) - { - var id = Convert.ToInt32(reader[0]); - var data = (string)reader[1]; - var jsonSerializerSettings = new JsonSerializerSettings() { - TypeNameHandling = TypeNameHandling.All - }; - var log = JsonConvert.DeserializeObject(data, jsonSerializerSettings); - logsToReturn.Add((id, log)); - - } - } - } - } - - return logsToReturn; - } - - } - - public long GetTermAtIndex(int index) - { - lock(_lock) - { - long result = 0; - using(var connection = new SqliteConnection($"Data Source={_path};")) - { - connection.Open(); - //todo - sql injection dont copy this.. - var sql = $"select data from logs where id = {index}"; - using(var command = new SqliteCommand(sql, connection)) - { - var data = Convert.ToString(command.ExecuteScalar()); - var jsonSerializerSettings = new JsonSerializerSettings() { - TypeNameHandling = TypeNameHandling.All - }; - var log = JsonConvert.DeserializeObject(data, jsonSerializerSettings); - if(log != null && log.Term > result) - { - result = log.Term; - } - } - } - return result; - } - } - public void Remove(int indexOfCommand) - { - lock(_lock) - { - using(var connection = new SqliteConnection($"Data Source={_path};")) - { - connection.Open(); - //todo - sql injection dont copy this.. - var deleteSql = $"delete from logs where id >= {indexOfCommand};"; - using(var deleteCommand = new SqliteCommand(deleteSql, connection)) - { - var result = deleteCommand.ExecuteNonQuery(); - } - } - } - } - } +using System.IO; +using Rafty.Log; +using Microsoft.Data.Sqlite; +using Newtonsoft.Json; +using System; +using Rafty.Infrastructure; +using System.Collections.Generic; + +namespace Ocelot.Raft +{ + [ExcludeFromCoverage] + public class SqlLiteLog : ILog + { + private string _path; + private readonly object _lock = new object(); + + public SqlLiteLog(NodeId nodeId) + { + _path = $"{nodeId.Id.Replace("/","").Replace(":","")}.db"; + if(!File.Exists(_path)) + { + lock(_lock) + { + FileStream fs = File.Create(_path); + fs.Dispose(); + } + using(var connection = new SqliteConnection($"Data Source={_path};")) + { + connection.Open(); + var sql = @"create table logs ( + id integer primary key, + data text not null + )"; + using(var command = new SqliteCommand(sql, connection)) + { + var result = command.ExecuteNonQuery(); + } + } + } + } + + public int LastLogIndex + { + get + { + lock(_lock) + { + var result = 1; + using(var connection = new SqliteConnection($"Data Source={_path};")) + { + connection.Open(); + var sql = @"select id from logs order by id desc limit 1"; + using(var command = new SqliteCommand(sql, connection)) + { + var index = Convert.ToInt32(command.ExecuteScalar()); + if(index > result) + { + result = index; + } + } + } + return result; + } + } + } + + public long LastLogTerm + { + get + { + lock(_lock) + { + long result = 0; + using(var connection = new SqliteConnection($"Data Source={_path};")) + { + connection.Open(); + var sql = @"select data from logs order by id desc limit 1"; + using(var command = new SqliteCommand(sql, connection)) + { + var data = Convert.ToString(command.ExecuteScalar()); + var jsonSerializerSettings = new JsonSerializerSettings() { + TypeNameHandling = TypeNameHandling.All + }; + var log = JsonConvert.DeserializeObject(data, jsonSerializerSettings); + if(log != null && log.Term > result) + { + result = log.Term; + } + } + } + return result; + } + } + } + + public int Count + { + get + { + lock(_lock) + { + var result = 0; + using(var connection = new SqliteConnection($"Data Source={_path};")) + { + connection.Open(); + var sql = @"select count(id) from logs"; + using(var command = new SqliteCommand(sql, connection)) + { + var index = Convert.ToInt32(command.ExecuteScalar()); + if(index > result) + { + result = index; + } + } + } + return result; + } + } + } + + public int Apply(LogEntry log) + { + lock(_lock) + { + using(var connection = new SqliteConnection($"Data Source={_path};")) + { + connection.Open(); + var jsonSerializerSettings = new JsonSerializerSettings() { + TypeNameHandling = TypeNameHandling.All + }; + var data = JsonConvert.SerializeObject(log, jsonSerializerSettings); + //todo - sql injection dont copy this.. + var sql = $"insert into logs (data) values ('{data}')"; + using(var command = new SqliteCommand(sql, connection)) + { + var result = command.ExecuteNonQuery(); + } + + sql = "select last_insert_rowid()"; + using(var command = new SqliteCommand(sql, connection)) + { + var result = command.ExecuteScalar(); + return Convert.ToInt32(result); + } + } + } + } + + public void DeleteConflictsFromThisLog(int index, LogEntry logEntry) + { + lock(_lock) + { + using(var connection = new SqliteConnection($"Data Source={_path};")) + { + connection.Open(); + //todo - sql injection dont copy this.. + var sql = $"select data from logs where id = {index};"; + using(var command = new SqliteCommand(sql, connection)) + { + var data = Convert.ToString(command.ExecuteScalar()); + var jsonSerializerSettings = new JsonSerializerSettings() { + TypeNameHandling = TypeNameHandling.All + }; + var log = JsonConvert.DeserializeObject(data, jsonSerializerSettings); + if(logEntry != null && log != null && logEntry.Term != log.Term) + { + //todo - sql injection dont copy this.. + var deleteSql = $"delete from logs where id >= {index};"; + using(var deleteCommand = new SqliteCommand(deleteSql, connection)) + { + var result = deleteCommand.ExecuteNonQuery(); + } + } + } + } + } + } + + public LogEntry Get(int index) + { + lock(_lock) + { + using(var connection = new SqliteConnection($"Data Source={_path};")) + { + connection.Open(); + //todo - sql injection dont copy this.. + var sql = $"select data from logs where id = {index}"; + using(var command = new SqliteCommand(sql, connection)) + { + var data = Convert.ToString(command.ExecuteScalar()); + var jsonSerializerSettings = new JsonSerializerSettings() { + TypeNameHandling = TypeNameHandling.All + }; + var log = JsonConvert.DeserializeObject(data, jsonSerializerSettings); + return log; + } + } + } + } + + public System.Collections.Generic.List<(int index, LogEntry logEntry)> GetFrom(int index) + { + lock(_lock) + { + var logsToReturn = new List<(int, LogEntry)>(); + + using(var connection = new SqliteConnection($"Data Source={_path};")) + { + connection.Open(); + //todo - sql injection dont copy this.. + var sql = $"select id, data from logs where id >= {index}"; + using(var command = new SqliteCommand(sql, connection)) + { + using(var reader = command.ExecuteReader()) + { + while(reader.Read()) + { + var id = Convert.ToInt32(reader[0]); + var data = (string)reader[1]; + var jsonSerializerSettings = new JsonSerializerSettings() { + TypeNameHandling = TypeNameHandling.All + }; + var log = JsonConvert.DeserializeObject(data, jsonSerializerSettings); + logsToReturn.Add((id, log)); + + } + } + } + } + + return logsToReturn; + } + + } + + public long GetTermAtIndex(int index) + { + lock(_lock) + { + long result = 0; + using(var connection = new SqliteConnection($"Data Source={_path};")) + { + connection.Open(); + //todo - sql injection dont copy this.. + var sql = $"select data from logs where id = {index}"; + using(var command = new SqliteCommand(sql, connection)) + { + var data = Convert.ToString(command.ExecuteScalar()); + var jsonSerializerSettings = new JsonSerializerSettings() { + TypeNameHandling = TypeNameHandling.All + }; + var log = JsonConvert.DeserializeObject(data, jsonSerializerSettings); + if(log != null && log.Term > result) + { + result = log.Term; + } + } + } + return result; + } + } + public void Remove(int indexOfCommand) + { + lock(_lock) + { + using(var connection = new SqliteConnection($"Data Source={_path};")) + { + connection.Open(); + //todo - sql injection dont copy this.. + var deleteSql = $"delete from logs where id >= {indexOfCommand};"; + using(var deleteCommand = new SqliteCommand(deleteSql, connection)) + { + var result = deleteCommand.ExecuteNonQuery(); + } + } + } + } + } } \ No newline at end of file diff --git a/src/Ocelot/Raft/UpdateFileConfiguration.cs b/src/Ocelot/Raft/UpdateFileConfiguration.cs index 39ed73f9e..dfe6a4333 100644 --- a/src/Ocelot/Raft/UpdateFileConfiguration.cs +++ b/src/Ocelot/Raft/UpdateFileConfiguration.cs @@ -1,15 +1,15 @@ -using Ocelot.Configuration.File; -using Rafty.FiniteStateMachine; - -namespace Ocelot.Raft -{ - public class UpdateFileConfiguration : ICommand - { - public UpdateFileConfiguration(FileConfiguration configuration) - { - Configuration = configuration; - } - - public FileConfiguration Configuration {get;private set;} - } +using Ocelot.Configuration.File; +using Rafty.FiniteStateMachine; + +namespace Ocelot.Raft +{ + public class UpdateFileConfiguration : ICommand + { + public UpdateFileConfiguration(FileConfiguration configuration) + { + Configuration = configuration; + } + + public FileConfiguration Configuration {get;private set;} + } } \ No newline at end of file diff --git a/src/Ocelot/RateLimit/ClientRateLimitProcessor.cs b/src/Ocelot/RateLimit/ClientRateLimitProcessor.cs index d6221d7b0..0415489ce 100644 --- a/src/Ocelot/RateLimit/ClientRateLimitProcessor.cs +++ b/src/Ocelot/RateLimit/ClientRateLimitProcessor.cs @@ -1,45 +1,45 @@ -using Microsoft.AspNetCore.Http; -using Ocelot.Configuration; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Ocelot.RateLimit -{ - - public class ClientRateLimitProcessor - { - private readonly IRateLimitCounterHandler _counterHandler; - private readonly RateLimitCore _core; - - public ClientRateLimitProcessor(IRateLimitCounterHandler counterHandler) - { - _counterHandler = counterHandler; - _core = new RateLimitCore(_counterHandler); - } - - public RateLimitCounter ProcessRequest(ClientRequestIdentity requestIdentity, RateLimitOptions option) - { - return _core.ProcessRequest(requestIdentity, option); - } - - - public int RetryAfterFrom(DateTime timestamp, RateLimitRule rule) - { - return _core.RetryAfterFrom(timestamp, rule); - } - - public RateLimitHeaders GetRateLimitHeaders(HttpContext context, ClientRequestIdentity requestIdentity, RateLimitOptions option) - { - return _core.GetRateLimitHeaders(context, requestIdentity, option); - } - - public TimeSpan ConvertToTimeSpan(string timeSpan) - { - return _core.ConvertToTimeSpan(timeSpan); - } - - } - -} +using Microsoft.AspNetCore.Http; +using Ocelot.Configuration; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.RateLimit +{ + + public class ClientRateLimitProcessor + { + private readonly IRateLimitCounterHandler _counterHandler; + private readonly RateLimitCore _core; + + public ClientRateLimitProcessor(IRateLimitCounterHandler counterHandler) + { + _counterHandler = counterHandler; + _core = new RateLimitCore(_counterHandler); + } + + public RateLimitCounter ProcessRequest(ClientRequestIdentity requestIdentity, RateLimitOptions option) + { + return _core.ProcessRequest(requestIdentity, option); + } + + + public int RetryAfterFrom(DateTime timestamp, RateLimitRule rule) + { + return _core.RetryAfterFrom(timestamp, rule); + } + + public RateLimitHeaders GetRateLimitHeaders(HttpContext context, ClientRequestIdentity requestIdentity, RateLimitOptions option) + { + return _core.GetRateLimitHeaders(context, requestIdentity, option); + } + + public TimeSpan ConvertToTimeSpan(string timeSpan) + { + return _core.ConvertToTimeSpan(timeSpan); + } + + } + +} diff --git a/src/Ocelot/RateLimit/ClientRequestIdentity.cs b/src/Ocelot/RateLimit/ClientRequestIdentity.cs index a27bc9946..f112c71e9 100644 --- a/src/Ocelot/RateLimit/ClientRequestIdentity.cs +++ b/src/Ocelot/RateLimit/ClientRequestIdentity.cs @@ -1,18 +1,18 @@ -namespace Ocelot.RateLimit -{ - public class ClientRequestIdentity - { - public ClientRequestIdentity(string clientId, string path, string httpverb) - { - ClientId = clientId; - Path = path; - HttpVerb = httpverb; - } - - public string ClientId { get; private set; } - - public string Path { get; private set; } - - public string HttpVerb { get; private set; } - } +namespace Ocelot.RateLimit +{ + public class ClientRequestIdentity + { + public ClientRequestIdentity(string clientId, string path, string httpverb) + { + ClientId = clientId; + Path = path; + HttpVerb = httpverb; + } + + public string ClientId { get; private set; } + + public string Path { get; private set; } + + public string HttpVerb { get; private set; } + } } \ No newline at end of file diff --git a/src/Ocelot/RateLimit/DistributedCacheRateLimitCounterHanlder.cs b/src/Ocelot/RateLimit/DistributedCacheRateLimitCounterHanlder.cs index 1db8f334e..ddcda1848 100644 --- a/src/Ocelot/RateLimit/DistributedCacheRateLimitCounterHanlder.cs +++ b/src/Ocelot/RateLimit/DistributedCacheRateLimitCounterHanlder.cs @@ -1,45 +1,45 @@ -using Microsoft.Extensions.Caching.Distributed; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Ocelot.RateLimit -{ - public class DistributedCacheRateLimitCounterHanlder : IRateLimitCounterHandler - { - private readonly IDistributedCache _memoryCache; - - public DistributedCacheRateLimitCounterHanlder(IDistributedCache memoryCache) - { - _memoryCache = memoryCache; - } - - public void Set(string id, RateLimitCounter counter, TimeSpan expirationTime) - { - _memoryCache.SetString(id, JsonConvert.SerializeObject(counter), new DistributedCacheEntryOptions().SetAbsoluteExpiration(expirationTime)); - } - - public bool Exists(string id) - { - var stored = _memoryCache.GetString(id); - return !string.IsNullOrEmpty(stored); - } - - public RateLimitCounter? Get(string id) - { - var stored = _memoryCache.GetString(id); - if (!string.IsNullOrEmpty(stored)) - { - return JsonConvert.DeserializeObject(stored); - } - return null; - } - - public void Remove(string id) - { - _memoryCache.Remove(id); - } - } -} +using Microsoft.Extensions.Caching.Distributed; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.RateLimit +{ + public class DistributedCacheRateLimitCounterHanlder : IRateLimitCounterHandler + { + private readonly IDistributedCache _memoryCache; + + public DistributedCacheRateLimitCounterHanlder(IDistributedCache memoryCache) + { + _memoryCache = memoryCache; + } + + public void Set(string id, RateLimitCounter counter, TimeSpan expirationTime) + { + _memoryCache.SetString(id, JsonConvert.SerializeObject(counter), new DistributedCacheEntryOptions().SetAbsoluteExpiration(expirationTime)); + } + + public bool Exists(string id) + { + var stored = _memoryCache.GetString(id); + return !string.IsNullOrEmpty(stored); + } + + public RateLimitCounter? Get(string id) + { + var stored = _memoryCache.GetString(id); + if (!string.IsNullOrEmpty(stored)) + { + return JsonConvert.DeserializeObject(stored); + } + return null; + } + + public void Remove(string id) + { + _memoryCache.Remove(id); + } + } +} diff --git a/src/Ocelot/RateLimit/IRateLimitCounterHandler.cs b/src/Ocelot/RateLimit/IRateLimitCounterHandler.cs index cb745a44a..5ab7a5daf 100644 --- a/src/Ocelot/RateLimit/IRateLimitCounterHandler.cs +++ b/src/Ocelot/RateLimit/IRateLimitCounterHandler.cs @@ -1,15 +1,15 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Ocelot.RateLimit -{ - public interface IRateLimitCounterHandler - { - bool Exists(string id); - RateLimitCounter? Get(string id); - void Remove(string id); - void Set(string id, RateLimitCounter counter, TimeSpan expirationTime); - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.RateLimit +{ + public interface IRateLimitCounterHandler + { + bool Exists(string id); + RateLimitCounter? Get(string id); + void Remove(string id); + void Set(string id, RateLimitCounter counter, TimeSpan expirationTime); + } +} diff --git a/src/Ocelot/RateLimit/MemoryCacheRateLimitCounterHandler.cs b/src/Ocelot/RateLimit/MemoryCacheRateLimitCounterHandler.cs index 9756f2aec..fdeeef660 100644 --- a/src/Ocelot/RateLimit/MemoryCacheRateLimitCounterHandler.cs +++ b/src/Ocelot/RateLimit/MemoryCacheRateLimitCounterHandler.cs @@ -1,45 +1,45 @@ -using Microsoft.Extensions.Caching.Memory; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Ocelot.RateLimit -{ - public class MemoryCacheRateLimitCounterHandler : IRateLimitCounterHandler - { - private readonly IMemoryCache _memoryCache; - - public MemoryCacheRateLimitCounterHandler(IMemoryCache memoryCache) - { - _memoryCache = memoryCache; - } - - public void Set(string id, RateLimitCounter counter, TimeSpan expirationTime) - { - _memoryCache.Set(id, counter, new MemoryCacheEntryOptions().SetAbsoluteExpiration(expirationTime)); - } - - public bool Exists(string id) - { - RateLimitCounter counter; - return _memoryCache.TryGetValue(id, out counter); - } - - public RateLimitCounter? Get(string id) - { - RateLimitCounter counter; - if (_memoryCache.TryGetValue(id, out counter)) - { - return counter; - } - - return null; - } - - public void Remove(string id) - { - _memoryCache.Remove(id); - } - } -} +using Microsoft.Extensions.Caching.Memory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.RateLimit +{ + public class MemoryCacheRateLimitCounterHandler : IRateLimitCounterHandler + { + private readonly IMemoryCache _memoryCache; + + public MemoryCacheRateLimitCounterHandler(IMemoryCache memoryCache) + { + _memoryCache = memoryCache; + } + + public void Set(string id, RateLimitCounter counter, TimeSpan expirationTime) + { + _memoryCache.Set(id, counter, new MemoryCacheEntryOptions().SetAbsoluteExpiration(expirationTime)); + } + + public bool Exists(string id) + { + RateLimitCounter counter; + return _memoryCache.TryGetValue(id, out counter); + } + + public RateLimitCounter? Get(string id) + { + RateLimitCounter counter; + if (_memoryCache.TryGetValue(id, out counter)) + { + return counter; + } + + return null; + } + + public void Remove(string id) + { + _memoryCache.Remove(id); + } + } +} diff --git a/src/Ocelot/RateLimit/Middleware/RateLimitMiddlewareExtensions.cs b/src/Ocelot/RateLimit/Middleware/RateLimitMiddlewareExtensions.cs index b27d6d9df..a1533e002 100644 --- a/src/Ocelot/RateLimit/Middleware/RateLimitMiddlewareExtensions.cs +++ b/src/Ocelot/RateLimit/Middleware/RateLimitMiddlewareExtensions.cs @@ -1,16 +1,16 @@ -using Microsoft.AspNetCore.Builder; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Ocelot.RateLimit.Middleware -{ - public static class RateLimitMiddlewareExtensions - { - public static IApplicationBuilder UseRateLimiting(this IApplicationBuilder builder) - { - return builder.UseMiddleware(); - } - } -} +using Microsoft.AspNetCore.Builder; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.RateLimit.Middleware +{ + public static class RateLimitMiddlewareExtensions + { + public static IApplicationBuilder UseRateLimiting(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } +} diff --git a/src/Ocelot/RateLimit/RateLimitCore.cs b/src/Ocelot/RateLimit/RateLimitCore.cs index 685bf47d7..c3455682e 100644 --- a/src/Ocelot/RateLimit/RateLimitCore.cs +++ b/src/Ocelot/RateLimit/RateLimitCore.cs @@ -1,147 +1,147 @@ -using Microsoft.AspNetCore.Http; -using Ocelot.Configuration; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Security.Cryptography; -using System.Text; -using System.Threading.Tasks; - -namespace Ocelot.RateLimit -{ - public class RateLimitCore - { - private readonly IRateLimitCounterHandler _counterHandler; - private static readonly object _processLocker = new object(); - - public RateLimitCore(IRateLimitCounterHandler counterStore) - { - _counterHandler = counterStore; - } - - public RateLimitCounter ProcessRequest(ClientRequestIdentity requestIdentity, RateLimitOptions option) - { - RateLimitCounter counter = new RateLimitCounter(DateTime.UtcNow, 1); - var rule = option.RateLimitRule; - - var counterId = ComputeCounterKey(requestIdentity, option); - - // serial reads and writes - lock (_processLocker) - { - var entry = _counterHandler.Get(counterId); - if (entry.HasValue) - { - // entry has not expired - if (entry.Value.Timestamp + ConvertToTimeSpan(rule.Period) >= DateTime.UtcNow) - { - // increment request count - var totalRequests = entry.Value.TotalRequests + 1; - // deep copy - counter = new RateLimitCounter(entry.Value.Timestamp, totalRequests); - } - } - } - - if (counter.TotalRequests > rule.Limit) - { - var retryAfter = RetryAfterFrom(counter.Timestamp, rule); - if (retryAfter > 0) - { - var expirationTime = TimeSpan.FromSeconds(rule.PeriodTimespan); - _counterHandler.Set(counterId, counter, expirationTime); - } - else - { - _counterHandler.Remove(counterId); - } - } - else - { - var expirationTime = ConvertToTimeSpan(rule.Period); - _counterHandler.Set(counterId, counter, expirationTime); - } - return counter; - } - - public void SaveRateLimitCounter(ClientRequestIdentity requestIdentity, RateLimitOptions option, RateLimitCounter counter, TimeSpan expirationTime) - { - var counterId = ComputeCounterKey(requestIdentity, option); - var rule = option.RateLimitRule; - // stores: id (string) - timestamp (datetime) - total_requests (long) - _counterHandler.Set(counterId, counter, expirationTime); - } - - public RateLimitHeaders GetRateLimitHeaders(HttpContext context, ClientRequestIdentity requestIdentity, RateLimitOptions option) - { - var rule = option.RateLimitRule; - RateLimitHeaders headers = null; - var counterId = ComputeCounterKey(requestIdentity, option); - var entry = _counterHandler.Get(counterId); - if (entry.HasValue) - { - headers = new RateLimitHeaders(context, rule.Period, - (rule.Limit - entry.Value.TotalRequests).ToString(), - (entry.Value.Timestamp + ConvertToTimeSpan(rule.Period)).ToUniversalTime().ToString("o", DateTimeFormatInfo.InvariantInfo) - ); - } - else - { - headers = new RateLimitHeaders(context, - rule.Period, - rule.Limit.ToString(), - (DateTime.UtcNow + ConvertToTimeSpan(rule.Period)).ToUniversalTime().ToString("o", DateTimeFormatInfo.InvariantInfo)); - - } - - return headers; - } - - public string ComputeCounterKey(ClientRequestIdentity requestIdentity, RateLimitOptions option) - { - var key = $"{option.RateLimitCounterPrefix}_{requestIdentity.ClientId}_{option.RateLimitRule.Period}_{requestIdentity.HttpVerb}_{requestIdentity.Path}"; - - var idBytes = Encoding.UTF8.GetBytes(key); - - byte[] hashBytes; - - using (var algorithm = SHA1.Create()) - { - hashBytes = algorithm.ComputeHash(idBytes); - } - - return BitConverter.ToString(hashBytes).Replace("-", string.Empty); - } - - public int RetryAfterFrom(DateTime timestamp, RateLimitRule rule) - { - var secondsPast = Convert.ToInt32((DateTime.UtcNow - timestamp).TotalSeconds); - var retryAfter = Convert.ToInt32(TimeSpan.FromSeconds(rule.PeriodTimespan).TotalSeconds); - retryAfter = retryAfter > 1 ? retryAfter - secondsPast : 1; - return retryAfter; - } - - public TimeSpan ConvertToTimeSpan(string timeSpan) - { - var l = timeSpan.Length - 1; - var value = timeSpan.Substring(0, l); - var type = timeSpan.Substring(l, 1); - - switch (type) - { - case "d": - return TimeSpan.FromDays(double.Parse(value)); - case "h": - return TimeSpan.FromHours(double.Parse(value)); - case "m": - return TimeSpan.FromMinutes(double.Parse(value)); - case "s": - return TimeSpan.FromSeconds(double.Parse(value)); - default: - throw new FormatException($"{timeSpan} can't be converted to TimeSpan, unknown type {type}"); - } - } - - } -} +using Microsoft.AspNetCore.Http; +using Ocelot.Configuration; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace Ocelot.RateLimit +{ + public class RateLimitCore + { + private readonly IRateLimitCounterHandler _counterHandler; + private static readonly object _processLocker = new object(); + + public RateLimitCore(IRateLimitCounterHandler counterStore) + { + _counterHandler = counterStore; + } + + public RateLimitCounter ProcessRequest(ClientRequestIdentity requestIdentity, RateLimitOptions option) + { + RateLimitCounter counter = new RateLimitCounter(DateTime.UtcNow, 1); + var rule = option.RateLimitRule; + + var counterId = ComputeCounterKey(requestIdentity, option); + + // serial reads and writes + lock (_processLocker) + { + var entry = _counterHandler.Get(counterId); + if (entry.HasValue) + { + // entry has not expired + if (entry.Value.Timestamp + ConvertToTimeSpan(rule.Period) >= DateTime.UtcNow) + { + // increment request count + var totalRequests = entry.Value.TotalRequests + 1; + // deep copy + counter = new RateLimitCounter(entry.Value.Timestamp, totalRequests); + } + } + } + + if (counter.TotalRequests > rule.Limit) + { + var retryAfter = RetryAfterFrom(counter.Timestamp, rule); + if (retryAfter > 0) + { + var expirationTime = TimeSpan.FromSeconds(rule.PeriodTimespan); + _counterHandler.Set(counterId, counter, expirationTime); + } + else + { + _counterHandler.Remove(counterId); + } + } + else + { + var expirationTime = ConvertToTimeSpan(rule.Period); + _counterHandler.Set(counterId, counter, expirationTime); + } + return counter; + } + + public void SaveRateLimitCounter(ClientRequestIdentity requestIdentity, RateLimitOptions option, RateLimitCounter counter, TimeSpan expirationTime) + { + var counterId = ComputeCounterKey(requestIdentity, option); + var rule = option.RateLimitRule; + // stores: id (string) - timestamp (datetime) - total_requests (long) + _counterHandler.Set(counterId, counter, expirationTime); + } + + public RateLimitHeaders GetRateLimitHeaders(HttpContext context, ClientRequestIdentity requestIdentity, RateLimitOptions option) + { + var rule = option.RateLimitRule; + RateLimitHeaders headers = null; + var counterId = ComputeCounterKey(requestIdentity, option); + var entry = _counterHandler.Get(counterId); + if (entry.HasValue) + { + headers = new RateLimitHeaders(context, rule.Period, + (rule.Limit - entry.Value.TotalRequests).ToString(), + (entry.Value.Timestamp + ConvertToTimeSpan(rule.Period)).ToUniversalTime().ToString("o", DateTimeFormatInfo.InvariantInfo) + ); + } + else + { + headers = new RateLimitHeaders(context, + rule.Period, + rule.Limit.ToString(), + (DateTime.UtcNow + ConvertToTimeSpan(rule.Period)).ToUniversalTime().ToString("o", DateTimeFormatInfo.InvariantInfo)); + + } + + return headers; + } + + public string ComputeCounterKey(ClientRequestIdentity requestIdentity, RateLimitOptions option) + { + var key = $"{option.RateLimitCounterPrefix}_{requestIdentity.ClientId}_{option.RateLimitRule.Period}_{requestIdentity.HttpVerb}_{requestIdentity.Path}"; + + var idBytes = Encoding.UTF8.GetBytes(key); + + byte[] hashBytes; + + using (var algorithm = SHA1.Create()) + { + hashBytes = algorithm.ComputeHash(idBytes); + } + + return BitConverter.ToString(hashBytes).Replace("-", string.Empty); + } + + public int RetryAfterFrom(DateTime timestamp, RateLimitRule rule) + { + var secondsPast = Convert.ToInt32((DateTime.UtcNow - timestamp).TotalSeconds); + var retryAfter = Convert.ToInt32(TimeSpan.FromSeconds(rule.PeriodTimespan).TotalSeconds); + retryAfter = retryAfter > 1 ? retryAfter - secondsPast : 1; + return retryAfter; + } + + public TimeSpan ConvertToTimeSpan(string timeSpan) + { + var l = timeSpan.Length - 1; + var value = timeSpan.Substring(0, l); + var type = timeSpan.Substring(l, 1); + + switch (type) + { + case "d": + return TimeSpan.FromDays(double.Parse(value)); + case "h": + return TimeSpan.FromHours(double.Parse(value)); + case "m": + return TimeSpan.FromMinutes(double.Parse(value)); + case "s": + return TimeSpan.FromSeconds(double.Parse(value)); + default: + throw new FormatException($"{timeSpan} can't be converted to TimeSpan, unknown type {type}"); + } + } + + } +} diff --git a/src/Ocelot/RateLimit/RateLimitCounter.cs b/src/Ocelot/RateLimit/RateLimitCounter.cs index 42dd03b7b..b5e5174b5 100644 --- a/src/Ocelot/RateLimit/RateLimitCounter.cs +++ b/src/Ocelot/RateLimit/RateLimitCounter.cs @@ -1,23 +1,23 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Ocelot.RateLimit -{ - /// - /// Stores the initial access time and the numbers of calls made from that point - /// - public struct RateLimitCounter - { - public RateLimitCounter(DateTime timestamp, long totalRequest) - { - Timestamp = timestamp; - TotalRequests = totalRequest; - } - - public DateTime Timestamp { get; private set; } - - public long TotalRequests { get; private set; } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.RateLimit +{ + /// + /// Stores the initial access time and the numbers of calls made from that point + /// + public struct RateLimitCounter + { + public RateLimitCounter(DateTime timestamp, long totalRequest) + { + Timestamp = timestamp; + TotalRequests = totalRequest; + } + + public DateTime Timestamp { get; private set; } + + public long TotalRequests { get; private set; } + } +} diff --git a/src/Ocelot/RateLimit/RateLimitHeaders.cs b/src/Ocelot/RateLimit/RateLimitHeaders.cs index 909f656da..5849fa75a 100644 --- a/src/Ocelot/RateLimit/RateLimitHeaders.cs +++ b/src/Ocelot/RateLimit/RateLimitHeaders.cs @@ -1,27 +1,27 @@ -using Microsoft.AspNetCore.Http; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Ocelot.RateLimit -{ - public class RateLimitHeaders - { - public RateLimitHeaders(HttpContext context, string limit, string remaining, string reset) - { - Context = context; - Limit = limit; - Remaining = remaining; - Reset = reset; - } - - public HttpContext Context { get; private set; } - - public string Limit { get; private set; } - - public string Remaining { get; private set; } - - public string Reset { get; private set; } - } -} +using Microsoft.AspNetCore.Http; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.RateLimit +{ + public class RateLimitHeaders + { + public RateLimitHeaders(HttpContext context, string limit, string remaining, string reset) + { + Context = context; + Limit = limit; + Remaining = remaining; + Reset = reset; + } + + public HttpContext Context { get; private set; } + + public string Limit { get; private set; } + + public string Remaining { get; private set; } + + public string Reset { get; private set; } + } +} diff --git a/src/Ocelot/Request/Builder/HttpRequestCreator.cs b/src/Ocelot/Request/Builder/HttpRequestCreator.cs index a2dbb9396..9a7d0b76d 100644 --- a/src/Ocelot/Request/Builder/HttpRequestCreator.cs +++ b/src/Ocelot/Request/Builder/HttpRequestCreator.cs @@ -1,20 +1,20 @@ -using System.Threading.Tasks; -using Ocelot.Responses; -using Ocelot.Requester.QoS; -using System.Net.Http; - -namespace Ocelot.Request.Builder -{ - public sealed class HttpRequestCreator : IRequestCreator - { - public async Task> Build( - HttpRequestMessage httpRequestMessage, - bool isQos, - IQoSProvider qosProvider, - bool useCookieContainer, - bool allowAutoRedirect) - { - return new OkResponse(new Request(httpRequestMessage, isQos, qosProvider, allowAutoRedirect, useCookieContainer)); - } - } +using System.Threading.Tasks; +using Ocelot.Responses; +using Ocelot.Requester.QoS; +using System.Net.Http; + +namespace Ocelot.Request.Builder +{ + public sealed class HttpRequestCreator : IRequestCreator + { + public async Task> Build( + HttpRequestMessage httpRequestMessage, + bool isQos, + IQoSProvider qosProvider, + bool useCookieContainer, + bool allowAutoRedirect) + { + return new OkResponse(new Request(httpRequestMessage, isQos, qosProvider, allowAutoRedirect, useCookieContainer)); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Request/Builder/IRequestCreator.cs b/src/Ocelot/Request/Builder/IRequestCreator.cs index d290db4fe..ab678582b 100644 --- a/src/Ocelot/Request/Builder/IRequestCreator.cs +++ b/src/Ocelot/Request/Builder/IRequestCreator.cs @@ -1,18 +1,18 @@ -namespace Ocelot.Request.Builder -{ - using System.Net.Http; - using System.Threading.Tasks; - - using Ocelot.Requester.QoS; - using Ocelot.Responses; - - public interface IRequestCreator - { - Task> Build( - HttpRequestMessage httpRequestMessage, - bool isQos, - IQoSProvider qosProvider, - bool useCookieContainer, - bool allowAutoRedirect); - } -} +namespace Ocelot.Request.Builder +{ + using System.Net.Http; + using System.Threading.Tasks; + + using Ocelot.Requester.QoS; + using Ocelot.Responses; + + public interface IRequestCreator + { + Task> Build( + HttpRequestMessage httpRequestMessage, + bool isQos, + IQoSProvider qosProvider, + bool useCookieContainer, + bool allowAutoRedirect); + } +} diff --git a/src/Ocelot/Request/Mapper/IRequestMapper.cs b/src/Ocelot/Request/Mapper/IRequestMapper.cs index 941a24f7d..343f1ab2d 100644 --- a/src/Ocelot/Request/Mapper/IRequestMapper.cs +++ b/src/Ocelot/Request/Mapper/IRequestMapper.cs @@ -1,12 +1,12 @@ -namespace Ocelot.Request.Mapper -{ - using System.Net.Http; - using System.Threading.Tasks; - using Microsoft.AspNetCore.Http; - using Ocelot.Responses; - - public interface IRequestMapper - { - Task> Map(HttpRequest request); - } -} +namespace Ocelot.Request.Mapper +{ + using System.Net.Http; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Http; + using Ocelot.Responses; + + public interface IRequestMapper + { + Task> Map(HttpRequest request); + } +} diff --git a/src/Ocelot/Request/Mapper/RequestMapper.cs b/src/Ocelot/Request/Mapper/RequestMapper.cs index 57925920d..a6f22d6de 100644 --- a/src/Ocelot/Request/Mapper/RequestMapper.cs +++ b/src/Ocelot/Request/Mapper/RequestMapper.cs @@ -1,95 +1,95 @@ -namespace Ocelot.Request.Mapper -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Net.Http; - using System.Net.Http.Headers; - using System.Threading.Tasks; - - using Microsoft.AspNetCore.Http; - using Microsoft.AspNetCore.Http.Extensions; - using Microsoft.Extensions.Primitives; - using Ocelot.Responses; - - public class RequestMapper : IRequestMapper - { - private readonly string[] _unsupportedHeaders = { "host" }; - - public async Task> Map(HttpRequest request) - { - try - { - var requestMessage = new HttpRequestMessage() - { - Content = await MapContent(request), - Method = MapMethod(request), - RequestUri = MapUri(request) - }; - - MapHeaders(request, requestMessage); - - return new OkResponse(requestMessage); - } - catch (Exception ex) - { - return new ErrorResponse(new UnmappableRequestError(ex)); - } - } - - private async Task MapContent(HttpRequest request) - { - if (request.Body == null) - { - return null; - } - - var content = new ByteArrayContent(await ToByteArray(request.Body)); - - content.Headers.TryAddWithoutValidation("Content-Type", new[] {request.ContentType}); - - return content; - } - - private HttpMethod MapMethod(HttpRequest request) - { - return new HttpMethod(request.Method); - } - - private Uri MapUri(HttpRequest request) - { - return new Uri(request.GetEncodedUrl()); - } - - private void MapHeaders(HttpRequest request, HttpRequestMessage requestMessage) - { - foreach (var header in request.Headers) - { - //todo get rid of if.. - if (IsSupportedHeader(header)) - { - requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()); - } - } - } - - private async Task ToByteArray(Stream stream) - { - using (stream) - { - using (var memStream = new MemoryStream()) - { - await stream.CopyToAsync(memStream); - return memStream.ToArray(); - } - } - } - - private bool IsSupportedHeader(KeyValuePair header) - { - return !_unsupportedHeaders.Contains(header.Key.ToLower()); - } - } -} - +namespace Ocelot.Request.Mapper +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net.Http; + using System.Net.Http.Headers; + using System.Threading.Tasks; + + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Http.Extensions; + using Microsoft.Extensions.Primitives; + using Ocelot.Responses; + + public class RequestMapper : IRequestMapper + { + private readonly string[] _unsupportedHeaders = { "host" }; + + public async Task> Map(HttpRequest request) + { + try + { + var requestMessage = new HttpRequestMessage() + { + Content = await MapContent(request), + Method = MapMethod(request), + RequestUri = MapUri(request) + }; + + MapHeaders(request, requestMessage); + + return new OkResponse(requestMessage); + } + catch (Exception ex) + { + return new ErrorResponse(new UnmappableRequestError(ex)); + } + } + + private async Task MapContent(HttpRequest request) + { + if (request.Body == null) + { + return null; + } + + var content = new ByteArrayContent(await ToByteArray(request.Body)); + + content.Headers.TryAddWithoutValidation("Content-Type", new[] {request.ContentType}); + + return content; + } + + private HttpMethod MapMethod(HttpRequest request) + { + return new HttpMethod(request.Method); + } + + private Uri MapUri(HttpRequest request) + { + return new Uri(request.GetEncodedUrl()); + } + + private void MapHeaders(HttpRequest request, HttpRequestMessage requestMessage) + { + foreach (var header in request.Headers) + { + //todo get rid of if.. + if (IsSupportedHeader(header)) + { + requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()); + } + } + } + + private async Task ToByteArray(Stream stream) + { + using (stream) + { + using (var memStream = new MemoryStream()) + { + await stream.CopyToAsync(memStream); + return memStream.ToArray(); + } + } + } + + private bool IsSupportedHeader(KeyValuePair header) + { + return !_unsupportedHeaders.Contains(header.Key.ToLower()); + } + } +} + diff --git a/src/Ocelot/Request/Mapper/UnmappableRequestError.cs b/src/Ocelot/Request/Mapper/UnmappableRequestError.cs index 4a860f5bd..79379b6be 100644 --- a/src/Ocelot/Request/Mapper/UnmappableRequestError.cs +++ b/src/Ocelot/Request/Mapper/UnmappableRequestError.cs @@ -1,12 +1,12 @@ -namespace Ocelot.Request.Mapper -{ - using Ocelot.Errors; - using System; - - public class UnmappableRequestError : Error - { - public UnmappableRequestError(Exception ex) : base($"Error when parsing incoming request, exception: {ex.Message}", OcelotErrorCode.UnmappableRequestError) - { - } - } -} +namespace Ocelot.Request.Mapper +{ + using Ocelot.Errors; + using System; + + public class UnmappableRequestError : Error + { + public UnmappableRequestError(Exception ex) : base($"Error when parsing incoming request, exception: {ex.Message}", OcelotErrorCode.UnmappableRequestError) + { + } + } +} diff --git a/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs b/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs index 802b419c6..3ade73d13 100644 --- a/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs +++ b/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs @@ -1,41 +1,41 @@ -namespace Ocelot.Request.Middleware -{ - using System.Threading.Tasks; - using Microsoft.AspNetCore.Http; - - using Ocelot.Infrastructure.RequestData; - using Ocelot.Logging; - using Ocelot.Middleware; - - public class DownstreamRequestInitialiserMiddleware : OcelotMiddleware - { - private readonly RequestDelegate _next; - private readonly IOcelotLogger _logger; - private readonly Mapper.IRequestMapper _requestMapper; - - public DownstreamRequestInitialiserMiddleware(RequestDelegate next, - IOcelotLoggerFactory loggerFactory, - IRequestScopedDataRepository requestScopedDataRepository, - Mapper.IRequestMapper requestMapper) - :base(requestScopedDataRepository) - { - _next = next; - _logger = loggerFactory.CreateLogger(); - _requestMapper = requestMapper; - } - - public async Task Invoke(HttpContext context) - { - var downstreamRequest = await _requestMapper.Map(context.Request); - if (downstreamRequest.IsError) - { - SetPipelineError(downstreamRequest.Errors); - return; - } - - SetDownstreamRequest(downstreamRequest.Data); - - await _next.Invoke(context); - } - } +namespace Ocelot.Request.Middleware +{ + using System.Threading.Tasks; + using Microsoft.AspNetCore.Http; + + using Ocelot.Infrastructure.RequestData; + using Ocelot.Logging; + using Ocelot.Middleware; + + public class DownstreamRequestInitialiserMiddleware : OcelotMiddleware + { + private readonly RequestDelegate _next; + private readonly IOcelotLogger _logger; + private readonly Mapper.IRequestMapper _requestMapper; + + public DownstreamRequestInitialiserMiddleware(RequestDelegate next, + IOcelotLoggerFactory loggerFactory, + IRequestScopedDataRepository requestScopedDataRepository, + Mapper.IRequestMapper requestMapper) + :base(requestScopedDataRepository) + { + _next = next; + _logger = loggerFactory.CreateLogger(); + _requestMapper = requestMapper; + } + + public async Task Invoke(HttpContext context) + { + var downstreamRequest = await _requestMapper.Map(context.Request); + if (downstreamRequest.IsError) + { + SetPipelineError(downstreamRequest.Errors); + return; + } + + SetDownstreamRequest(downstreamRequest.Data); + + await _next.Invoke(context); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs b/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs index 44d9163ae..dd1ad4296 100644 --- a/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs +++ b/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs @@ -1,66 +1,66 @@ -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Ocelot.Infrastructure.RequestData; -using Ocelot.Logging; -using Ocelot.Middleware; -using Ocelot.Request.Builder; -using Ocelot.Requester.QoS; - -namespace Ocelot.Request.Middleware -{ - public class HttpRequestBuilderMiddleware : OcelotMiddleware - { - private readonly RequestDelegate _next; - private readonly IRequestCreator _requestCreator; - private readonly IOcelotLogger _logger; - private readonly IQosProviderHouse _qosProviderHouse; - - public HttpRequestBuilderMiddleware(RequestDelegate next, - IOcelotLoggerFactory loggerFactory, - IRequestScopedDataRepository requestScopedDataRepository, - IRequestCreator requestCreator, - IQosProviderHouse qosProviderHouse) - :base(requestScopedDataRepository) - { - _next = next; - _requestCreator = requestCreator; - _qosProviderHouse = qosProviderHouse; - _logger = loggerFactory.CreateLogger(); - } - - public async Task Invoke(HttpContext context) - { - var qosProvider = _qosProviderHouse.Get(DownstreamRoute.ReRoute); - - if (qosProvider.IsError) - { - _logger.LogDebug("IQosProviderHouse returned an error, setting pipeline error"); - - SetPipelineError(qosProvider.Errors); - - return; - } - - var buildResult = await _requestCreator.Build( - DownstreamRequest, - DownstreamRoute.ReRoute.IsQos, - qosProvider.Data, - DownstreamRoute.ReRoute.HttpHandlerOptions.UseCookieContainer, - DownstreamRoute.ReRoute.HttpHandlerOptions.AllowAutoRedirect); - - if (buildResult.IsError) - { - _logger.LogDebug("IRequestCreator returned an error, setting pipeline error"); - - SetPipelineError(buildResult.Errors); - - return; - } - _logger.LogDebug("setting upstream request"); - - SetUpstreamRequestForThisRequest(buildResult.Data); - - await _next.Invoke(context); - } - } +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Ocelot.Infrastructure.RequestData; +using Ocelot.Logging; +using Ocelot.Middleware; +using Ocelot.Request.Builder; +using Ocelot.Requester.QoS; + +namespace Ocelot.Request.Middleware +{ + public class HttpRequestBuilderMiddleware : OcelotMiddleware + { + private readonly RequestDelegate _next; + private readonly IRequestCreator _requestCreator; + private readonly IOcelotLogger _logger; + private readonly IQosProviderHouse _qosProviderHouse; + + public HttpRequestBuilderMiddleware(RequestDelegate next, + IOcelotLoggerFactory loggerFactory, + IRequestScopedDataRepository requestScopedDataRepository, + IRequestCreator requestCreator, + IQosProviderHouse qosProviderHouse) + :base(requestScopedDataRepository) + { + _next = next; + _requestCreator = requestCreator; + _qosProviderHouse = qosProviderHouse; + _logger = loggerFactory.CreateLogger(); + } + + public async Task Invoke(HttpContext context) + { + var qosProvider = _qosProviderHouse.Get(DownstreamRoute.ReRoute); + + if (qosProvider.IsError) + { + _logger.LogDebug("IQosProviderHouse returned an error, setting pipeline error"); + + SetPipelineError(qosProvider.Errors); + + return; + } + + var buildResult = await _requestCreator.Build( + DownstreamRequest, + DownstreamRoute.ReRoute.IsQos, + qosProvider.Data, + DownstreamRoute.ReRoute.HttpHandlerOptions.UseCookieContainer, + DownstreamRoute.ReRoute.HttpHandlerOptions.AllowAutoRedirect); + + if (buildResult.IsError) + { + _logger.LogDebug("IRequestCreator returned an error, setting pipeline error"); + + SetPipelineError(buildResult.Errors); + + return; + } + _logger.LogDebug("setting upstream request"); + + SetUpstreamRequestForThisRequest(buildResult.Data); + + await _next.Invoke(context); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddlewareExtensions.cs b/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddlewareExtensions.cs index 20bb91647..80a41998b 100644 --- a/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddlewareExtensions.cs +++ b/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddlewareExtensions.cs @@ -1,17 +1,17 @@ -using Microsoft.AspNetCore.Builder; - -namespace Ocelot.Request.Middleware -{ - public static class HttpRequestBuilderMiddlewareExtensions - { - public static IApplicationBuilder UseHttpRequestBuilderMiddleware(this IApplicationBuilder builder) - { - return builder.UseMiddleware(); - } - - public static IApplicationBuilder UseDownstreamRequestInitialiser(this IApplicationBuilder builder) - { - return builder.UseMiddleware(); - } - } +using Microsoft.AspNetCore.Builder; + +namespace Ocelot.Request.Middleware +{ + public static class HttpRequestBuilderMiddlewareExtensions + { + public static IApplicationBuilder UseHttpRequestBuilderMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + + public static IApplicationBuilder UseDownstreamRequestInitialiser(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Request/Request.cs b/src/Ocelot/Request/Request.cs index 9f0b66e21..79c170ea5 100644 --- a/src/Ocelot/Request/Request.cs +++ b/src/Ocelot/Request/Request.cs @@ -1,28 +1,28 @@ -using System.Net.Http; -using Ocelot.Requester.QoS; - -namespace Ocelot.Request -{ - public class Request - { - public Request( - HttpRequestMessage httpRequestMessage, - bool isQos, - IQoSProvider qosProvider, - bool allowAutoRedirect, - bool useCookieContainer) - { - HttpRequestMessage = httpRequestMessage; - IsQos = isQos; - QosProvider = qosProvider; - AllowAutoRedirect = allowAutoRedirect; - UseCookieContainer = useCookieContainer; - } - - public HttpRequestMessage HttpRequestMessage { get; private set; } - public bool IsQos { get; private set; } - public IQoSProvider QosProvider { get; private set; } - public bool AllowAutoRedirect { get; private set; } - public bool UseCookieContainer { get; private set; } - } -} +using System.Net.Http; +using Ocelot.Requester.QoS; + +namespace Ocelot.Request +{ + public class Request + { + public Request( + HttpRequestMessage httpRequestMessage, + bool isQos, + IQoSProvider qosProvider, + bool allowAutoRedirect, + bool useCookieContainer) + { + HttpRequestMessage = httpRequestMessage; + IsQos = isQos; + QosProvider = qosProvider; + AllowAutoRedirect = allowAutoRedirect; + UseCookieContainer = useCookieContainer; + } + + public HttpRequestMessage HttpRequestMessage { get; private set; } + public bool IsQos { get; private set; } + public IQoSProvider QosProvider { get; private set; } + public bool AllowAutoRedirect { get; private set; } + public bool UseCookieContainer { get; private set; } + } +} diff --git a/src/Ocelot/RequestId/DefaultRequestIdKey.cs b/src/Ocelot/RequestId/DefaultRequestIdKey.cs index 94c0d82df..39f4b6ab2 100644 --- a/src/Ocelot/RequestId/DefaultRequestIdKey.cs +++ b/src/Ocelot/RequestId/DefaultRequestIdKey.cs @@ -1,9 +1,9 @@ -namespace Ocelot.RequestId -{ - public static class DefaultRequestIdKey - { - // This is set incase anyone isnt doing this specifically with there requests. - // It will not be forwarded on to downstream services unless specfied in the config. - public const string Value = "RequestId"; - } -} +namespace Ocelot.RequestId +{ + public static class DefaultRequestIdKey + { + // This is set incase anyone isnt doing this specifically with there requests. + // It will not be forwarded on to downstream services unless specfied in the config. + public const string Value = "RequestId"; + } +} diff --git a/src/Ocelot/RequestId/Middleware/ReRouteRequestIdMiddleware.cs b/src/Ocelot/RequestId/Middleware/ReRouteRequestIdMiddleware.cs index 10ec5f101..d4dfbe11b 100644 --- a/src/Ocelot/RequestId/Middleware/ReRouteRequestIdMiddleware.cs +++ b/src/Ocelot/RequestId/Middleware/ReRouteRequestIdMiddleware.cs @@ -1,89 +1,89 @@ -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Primitives; -using Ocelot.Infrastructure.RequestData; -using Ocelot.Logging; -using Ocelot.Middleware; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Collections.Generic; - -namespace Ocelot.RequestId.Middleware -{ - public class ReRouteRequestIdMiddleware : OcelotMiddleware - { - private readonly RequestDelegate _next; - private readonly IOcelotLogger _logger; - private readonly IRequestScopedDataRepository _requestScopedDataRepository; - - public ReRouteRequestIdMiddleware(RequestDelegate next, - IOcelotLoggerFactory loggerFactory, - IRequestScopedDataRepository requestScopedDataRepository) - : base(requestScopedDataRepository) - { - _next = next; - _logger = loggerFactory.CreateLogger(); - _requestScopedDataRepository = requestScopedDataRepository; - } - - public async Task Invoke(HttpContext context) - { - SetOcelotRequestId(context); - await _next.Invoke(context); - } - - private void SetOcelotRequestId(HttpContext context) - { - // if get request ID is set on upstream request then retrieve it - var key = DownstreamRoute.ReRoute.RequestIdKey ?? DefaultRequestIdKey.Value; - - StringValues upstreamRequestIds; - if (context.Request.Headers.TryGetValue(key, out upstreamRequestIds)) - { - //set the traceidentifier - context.TraceIdentifier = upstreamRequestIds.First(); - - //check if we have previous id - var previousRequestId = _requestScopedDataRepository.Get("RequestId"); - if(!previousRequestId.IsError && !string.IsNullOrEmpty(previousRequestId.Data)) - { - //we have a previous request id lets store it and update request id - _requestScopedDataRepository.Add("PreviousRequestId", previousRequestId.Data); - _requestScopedDataRepository.Update("RequestId", context.TraceIdentifier); - } - else - { - //else just add request id - _requestScopedDataRepository.Add("RequestId", context.TraceIdentifier); - } - } - - // set request ID on downstream request, if required - var requestId = new RequestId(DownstreamRoute?.ReRoute?.RequestIdKey, context.TraceIdentifier); - - if (ShouldAddRequestId(requestId, DownstreamRequest.Headers)) - { - AddRequestIdHeader(requestId, DownstreamRequest); - } - } - - private bool ShouldAddRequestId(RequestId requestId, HttpRequestHeaders headers) - { - return !string.IsNullOrEmpty(requestId?.RequestIdKey) - && !string.IsNullOrEmpty(requestId.RequestIdValue) - && !RequestIdInHeaders(requestId, headers); - } - - private bool RequestIdInHeaders(RequestId requestId, HttpRequestHeaders headers) - { - IEnumerable value; - return headers.TryGetValues(requestId.RequestIdKey, out value); - } - - private void AddRequestIdHeader(RequestId requestId, HttpRequestMessage httpRequestMessage) - { - httpRequestMessage.Headers.Add(requestId.RequestIdKey, requestId.RequestIdValue); - } - } +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; +using Ocelot.Infrastructure.RequestData; +using Ocelot.Logging; +using Ocelot.Middleware; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Collections.Generic; + +namespace Ocelot.RequestId.Middleware +{ + public class ReRouteRequestIdMiddleware : OcelotMiddleware + { + private readonly RequestDelegate _next; + private readonly IOcelotLogger _logger; + private readonly IRequestScopedDataRepository _requestScopedDataRepository; + + public ReRouteRequestIdMiddleware(RequestDelegate next, + IOcelotLoggerFactory loggerFactory, + IRequestScopedDataRepository requestScopedDataRepository) + : base(requestScopedDataRepository) + { + _next = next; + _logger = loggerFactory.CreateLogger(); + _requestScopedDataRepository = requestScopedDataRepository; + } + + public async Task Invoke(HttpContext context) + { + SetOcelotRequestId(context); + await _next.Invoke(context); + } + + private void SetOcelotRequestId(HttpContext context) + { + // if get request ID is set on upstream request then retrieve it + var key = DownstreamRoute.ReRoute.RequestIdKey ?? DefaultRequestIdKey.Value; + + StringValues upstreamRequestIds; + if (context.Request.Headers.TryGetValue(key, out upstreamRequestIds)) + { + //set the traceidentifier + context.TraceIdentifier = upstreamRequestIds.First(); + + //check if we have previous id + var previousRequestId = _requestScopedDataRepository.Get("RequestId"); + if(!previousRequestId.IsError && !string.IsNullOrEmpty(previousRequestId.Data)) + { + //we have a previous request id lets store it and update request id + _requestScopedDataRepository.Add("PreviousRequestId", previousRequestId.Data); + _requestScopedDataRepository.Update("RequestId", context.TraceIdentifier); + } + else + { + //else just add request id + _requestScopedDataRepository.Add("RequestId", context.TraceIdentifier); + } + } + + // set request ID on downstream request, if required + var requestId = new RequestId(DownstreamRoute?.ReRoute?.RequestIdKey, context.TraceIdentifier); + + if (ShouldAddRequestId(requestId, DownstreamRequest.Headers)) + { + AddRequestIdHeader(requestId, DownstreamRequest); + } + } + + private bool ShouldAddRequestId(RequestId requestId, HttpRequestHeaders headers) + { + return !string.IsNullOrEmpty(requestId?.RequestIdKey) + && !string.IsNullOrEmpty(requestId.RequestIdValue) + && !RequestIdInHeaders(requestId, headers); + } + + private bool RequestIdInHeaders(RequestId requestId, HttpRequestHeaders headers) + { + IEnumerable value; + return headers.TryGetValues(requestId.RequestIdKey, out value); + } + + private void AddRequestIdHeader(RequestId requestId, HttpRequestMessage httpRequestMessage) + { + httpRequestMessage.Headers.Add(requestId.RequestIdKey, requestId.RequestIdValue); + } + } } \ No newline at end of file diff --git a/src/Ocelot/RequestId/Middleware/RequestIdMiddlewareExtensions.cs b/src/Ocelot/RequestId/Middleware/RequestIdMiddlewareExtensions.cs index 67233a861..236c984a0 100644 --- a/src/Ocelot/RequestId/Middleware/RequestIdMiddlewareExtensions.cs +++ b/src/Ocelot/RequestId/Middleware/RequestIdMiddlewareExtensions.cs @@ -1,12 +1,12 @@ -using Microsoft.AspNetCore.Builder; - -namespace Ocelot.RequestId.Middleware -{ - public static class RequestIdMiddlewareExtensions - { - public static IApplicationBuilder UseRequestIdMiddleware(this IApplicationBuilder builder) - { - return builder.UseMiddleware(); - } - } +using Microsoft.AspNetCore.Builder; + +namespace Ocelot.RequestId.Middleware +{ + public static class RequestIdMiddlewareExtensions + { + public static IApplicationBuilder UseRequestIdMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } } \ No newline at end of file diff --git a/src/Ocelot/RequestId/RequestId.cs b/src/Ocelot/RequestId/RequestId.cs index 998d33aa9..85f9cbbc4 100644 --- a/src/Ocelot/RequestId/RequestId.cs +++ b/src/Ocelot/RequestId/RequestId.cs @@ -1,14 +1,14 @@ -namespace Ocelot.RequestId -{ - public class RequestId - { - public RequestId(string requestIdKey, string requestIdValue) - { - RequestIdKey = requestIdKey; - RequestIdValue = requestIdValue; - } - - public string RequestIdKey { get; private set; } - public string RequestIdValue { get; private set; } - } -} +namespace Ocelot.RequestId +{ + public class RequestId + { + public RequestId(string requestIdKey, string requestIdValue) + { + RequestIdKey = requestIdKey; + RequestIdValue = requestIdValue; + } + + public string RequestIdKey { get; private set; } + public string RequestIdValue { get; private set; } + } +} diff --git a/src/Ocelot/Requester/HttpClientBuilder.cs b/src/Ocelot/Requester/HttpClientBuilder.cs index d32f42558..257c42220 100644 --- a/src/Ocelot/Requester/HttpClientBuilder.cs +++ b/src/Ocelot/Requester/HttpClientBuilder.cs @@ -1,66 +1,66 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using Ocelot.Logging; -using Ocelot.Requester.QoS; - -namespace Ocelot.Requester -{ - internal class HttpClientBuilder : IHttpClientBuilder - { - private readonly Dictionary> _handlers = new Dictionary>(); - - public IHttpClientBuilder WithQos(IQoSProvider qosProvider, IOcelotLogger logger) - { - _handlers.Add(5000, () => new PollyCircuitBreakingDelegatingHandler(qosProvider, logger)); - - return this; - } - - public IHttpClient Create(bool useCookies, bool allowAutoRedirect) - { - var httpclientHandler = new HttpClientHandler { AllowAutoRedirect = allowAutoRedirect, UseCookies = useCookies}; - - var client = new HttpClient(CreateHttpMessageHandler(httpclientHandler)); - - return new HttpClientWrapper(client); - } - - private HttpMessageHandler CreateHttpMessageHandler(HttpMessageHandler httpMessageHandler) - { - _handlers - .OrderByDescending(handler => handler.Key) - .Select(handler => handler.Value) - .Reverse() - .ToList() - .ForEach(handler => - { - var delegatingHandler = handler(); - delegatingHandler.InnerHandler = httpMessageHandler; - httpMessageHandler = delegatingHandler; - }); - return httpMessageHandler; - } - } - - /// - /// This class was made to make unit testing easier when HttpClient is used. - /// - internal class HttpClientWrapper : IHttpClient - { - public HttpClient Client { get; } - - public HttpClientWrapper(HttpClient client) - { - Client = client; - } - - public Task SendAsync(HttpRequestMessage request) - { - return Client.SendAsync(request); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Ocelot.Logging; +using Ocelot.Requester.QoS; + +namespace Ocelot.Requester +{ + internal class HttpClientBuilder : IHttpClientBuilder + { + private readonly Dictionary> _handlers = new Dictionary>(); + + public IHttpClientBuilder WithQos(IQoSProvider qosProvider, IOcelotLogger logger) + { + _handlers.Add(5000, () => new PollyCircuitBreakingDelegatingHandler(qosProvider, logger)); + + return this; + } + + public IHttpClient Create(bool useCookies, bool allowAutoRedirect) + { + var httpclientHandler = new HttpClientHandler { AllowAutoRedirect = allowAutoRedirect, UseCookies = useCookies}; + + var client = new HttpClient(CreateHttpMessageHandler(httpclientHandler)); + + return new HttpClientWrapper(client); + } + + private HttpMessageHandler CreateHttpMessageHandler(HttpMessageHandler httpMessageHandler) + { + _handlers + .OrderByDescending(handler => handler.Key) + .Select(handler => handler.Value) + .Reverse() + .ToList() + .ForEach(handler => + { + var delegatingHandler = handler(); + delegatingHandler.InnerHandler = httpMessageHandler; + httpMessageHandler = delegatingHandler; + }); + return httpMessageHandler; + } + } + + /// + /// This class was made to make unit testing easier when HttpClient is used. + /// + internal class HttpClientWrapper : IHttpClient + { + public HttpClient Client { get; } + + public HttpClientWrapper(HttpClient client) + { + Client = client; + } + + public Task SendAsync(HttpRequestMessage request) + { + return Client.SendAsync(request); + } + } +} diff --git a/src/Ocelot/Requester/HttpClientHttpRequester.cs b/src/Ocelot/Requester/HttpClientHttpRequester.cs index a6a289151..29bb5c3e4 100644 --- a/src/Ocelot/Requester/HttpClientHttpRequester.cs +++ b/src/Ocelot/Requester/HttpClientHttpRequester.cs @@ -1,8 +1,8 @@ -using System; +using System; using System.Collections.Concurrent; using System.Net.Http; using System.Threading.Tasks; -using Ocelot.Configuration; +using Ocelot.Configuration; using Ocelot.Logging; using Ocelot.Responses; using Polly.CircuitBreaker; @@ -80,4 +80,4 @@ private string GetCacheKey(Request.Request request, IHttpClientBuilder builder) return baseUrl; } } -} \ No newline at end of file +} diff --git a/src/Ocelot/Requester/IHttpClient.cs b/src/Ocelot/Requester/IHttpClient.cs index 8a169a367..1912ab0bc 100644 --- a/src/Ocelot/Requester/IHttpClient.cs +++ b/src/Ocelot/Requester/IHttpClient.cs @@ -1,13 +1,13 @@ -using System; -using System.Net.Http; -using System.Threading.Tasks; - -namespace Ocelot.Requester -{ - public interface IHttpClient - { - HttpClient Client { get; } - - Task SendAsync(HttpRequestMessage request); - } -} +using System; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Ocelot.Requester +{ + public interface IHttpClient + { + HttpClient Client { get; } + + Task SendAsync(HttpRequestMessage request); + } +} diff --git a/src/Ocelot/Requester/IHttpClientCache.cs b/src/Ocelot/Requester/IHttpClientCache.cs index f4b8f76e8..ce80dde36 100644 --- a/src/Ocelot/Requester/IHttpClientCache.cs +++ b/src/Ocelot/Requester/IHttpClientCache.cs @@ -1,16 +1,16 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; - -namespace Ocelot.Requester -{ - public interface IHttpClientCache - { - bool Exists(string id); - IHttpClient Get(string id); - void Remove(string id); - void Set(string id, IHttpClient handler, TimeSpan expirationTime); - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Ocelot.Requester +{ + public interface IHttpClientCache + { + bool Exists(string id); + IHttpClient Get(string id); + void Remove(string id); + void Set(string id, IHttpClient handler, TimeSpan expirationTime); + } +} diff --git a/src/Ocelot/Requester/IHttpRequester.cs b/src/Ocelot/Requester/IHttpRequester.cs index e39729575..b21168187 100644 --- a/src/Ocelot/Requester/IHttpRequester.cs +++ b/src/Ocelot/Requester/IHttpRequester.cs @@ -1,13 +1,13 @@ -using System.Net.Http; -using System.Threading.Tasks; -using Ocelot.Responses; - -namespace Ocelot.Requester -{ - public interface IHttpRequester - { - Task> GetResponse(Request.Request request); - - - } -} +using System.Net.Http; +using System.Threading.Tasks; +using Ocelot.Responses; + +namespace Ocelot.Requester +{ + public interface IHttpRequester + { + Task> GetResponse(Request.Request request); + + + } +} diff --git a/src/Ocelot/Requester/MemoryHttpClientCache.cs b/src/Ocelot/Requester/MemoryHttpClientCache.cs index 2000b7b48..3ce1716bb 100644 --- a/src/Ocelot/Requester/MemoryHttpClientCache.cs +++ b/src/Ocelot/Requester/MemoryHttpClientCache.cs @@ -1,53 +1,53 @@ -using Microsoft.Extensions.Caching.Memory; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; - -namespace Ocelot.Requester -{ - public class MemoryHttpClientCache : IHttpClientCache - { - private readonly ConcurrentDictionary> _httpClientsCache = new ConcurrentDictionary>(); - - public void Set(string id, IHttpClient client, TimeSpan expirationTime) - { - ConcurrentQueue connectionQueue; - if (_httpClientsCache.TryGetValue(id, out connectionQueue)) - { - connectionQueue.Enqueue(client); - } - else - { - connectionQueue = new ConcurrentQueue(); - connectionQueue.Enqueue(client); - _httpClientsCache.TryAdd(id, connectionQueue); - } - } - - public bool Exists(string id) - { - ConcurrentQueue connectionQueue; - return _httpClientsCache.TryGetValue(id, out connectionQueue); - } - - public IHttpClient Get(string id) - { - IHttpClient client= null; - ConcurrentQueue connectionQueue; - if (_httpClientsCache.TryGetValue(id, out connectionQueue)) - { - connectionQueue.TryDequeue(out client); - } - return client; - } - - public void Remove(string id) - { - ConcurrentQueue connectionQueue; - _httpClientsCache.TryRemove(id, out connectionQueue); - } - } -} +using Microsoft.Extensions.Caching.Memory; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Ocelot.Requester +{ + public class MemoryHttpClientCache : IHttpClientCache + { + private readonly ConcurrentDictionary> _httpClientsCache = new ConcurrentDictionary>(); + + public void Set(string id, IHttpClient client, TimeSpan expirationTime) + { + ConcurrentQueue connectionQueue; + if (_httpClientsCache.TryGetValue(id, out connectionQueue)) + { + connectionQueue.Enqueue(client); + } + else + { + connectionQueue = new ConcurrentQueue(); + connectionQueue.Enqueue(client); + _httpClientsCache.TryAdd(id, connectionQueue); + } + } + + public bool Exists(string id) + { + ConcurrentQueue connectionQueue; + return _httpClientsCache.TryGetValue(id, out connectionQueue); + } + + public IHttpClient Get(string id) + { + IHttpClient client= null; + ConcurrentQueue connectionQueue; + if (_httpClientsCache.TryGetValue(id, out connectionQueue)) + { + connectionQueue.TryDequeue(out client); + } + return client; + } + + public void Remove(string id) + { + ConcurrentQueue connectionQueue; + _httpClientsCache.TryRemove(id, out connectionQueue); + } + } +} diff --git a/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs b/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs index 6cb1af888..412983de0 100644 --- a/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs +++ b/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs @@ -1,44 +1,44 @@ -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; -using Ocelot.Infrastructure.RequestData; -using Ocelot.Logging; -using Ocelot.Middleware; - -namespace Ocelot.Requester.Middleware -{ - public class HttpRequesterMiddleware : OcelotMiddleware - { - private readonly RequestDelegate _next; - private readonly IHttpRequester _requester; - private readonly IOcelotLogger _logger; - - public HttpRequesterMiddleware(RequestDelegate next, - IOcelotLoggerFactory loggerFactory, - IHttpRequester requester, - IRequestScopedDataRepository requestScopedDataRepository) - :base(requestScopedDataRepository) - { - _next = next; - _requester = requester; - _logger = loggerFactory.CreateLogger(); - } - - public async Task Invoke(HttpContext context) - { - var response = await _requester.GetResponse(Request); - - if (response.IsError) - { - _logger.LogDebug("IHttpRequester returned an error, setting pipeline error"); - - SetPipelineError(response.Errors); - return; - } - - _logger.LogDebug("setting http response message"); - - SetHttpResponseMessageThisRequest(response.Data); - } - } +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Ocelot.Infrastructure.RequestData; +using Ocelot.Logging; +using Ocelot.Middleware; + +namespace Ocelot.Requester.Middleware +{ + public class HttpRequesterMiddleware : OcelotMiddleware + { + private readonly RequestDelegate _next; + private readonly IHttpRequester _requester; + private readonly IOcelotLogger _logger; + + public HttpRequesterMiddleware(RequestDelegate next, + IOcelotLoggerFactory loggerFactory, + IHttpRequester requester, + IRequestScopedDataRepository requestScopedDataRepository) + :base(requestScopedDataRepository) + { + _next = next; + _requester = requester; + _logger = loggerFactory.CreateLogger(); + } + + public async Task Invoke(HttpContext context) + { + var response = await _requester.GetResponse(Request); + + if (response.IsError) + { + _logger.LogDebug("IHttpRequester returned an error, setting pipeline error"); + + SetPipelineError(response.Errors); + return; + } + + _logger.LogDebug("setting http response message"); + + SetHttpResponseMessageThisRequest(response.Data); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Requester/Middleware/HttpRequesterMiddlewareExtensions.cs b/src/Ocelot/Requester/Middleware/HttpRequesterMiddlewareExtensions.cs index 6a3d5bb81..b32d32479 100644 --- a/src/Ocelot/Requester/Middleware/HttpRequesterMiddlewareExtensions.cs +++ b/src/Ocelot/Requester/Middleware/HttpRequesterMiddlewareExtensions.cs @@ -1,12 +1,12 @@ -using Microsoft.AspNetCore.Builder; - -namespace Ocelot.Requester.Middleware -{ - public static class HttpRequesterMiddlewareExtensions - { - public static IApplicationBuilder UseHttpRequesterMiddleware(this IApplicationBuilder builder) - { - return builder.UseMiddleware(); - } - } +using Microsoft.AspNetCore.Builder; + +namespace Ocelot.Requester.Middleware +{ + public static class HttpRequesterMiddlewareExtensions + { + public static IApplicationBuilder UseHttpRequesterMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Requester/PollyCircuitBreakingDelegatingHandler.cs b/src/Ocelot/Requester/PollyCircuitBreakingDelegatingHandler.cs index a09ed2335..ce8eec1a0 100644 --- a/src/Ocelot/Requester/PollyCircuitBreakingDelegatingHandler.cs +++ b/src/Ocelot/Requester/PollyCircuitBreakingDelegatingHandler.cs @@ -1,45 +1,45 @@ -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Ocelot.Logging; -using Ocelot.Requester.QoS; -using Polly; -using Polly.CircuitBreaker; -using Polly.Timeout; - -namespace Ocelot.Requester -{ - public class PollyCircuitBreakingDelegatingHandler : DelegatingHandler - { - private readonly IQoSProvider _qoSProvider; - private readonly IOcelotLogger _logger; - - public PollyCircuitBreakingDelegatingHandler( - IQoSProvider qoSProvider, - IOcelotLogger logger) - { - _qoSProvider = qoSProvider; - _logger = logger; - } - - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - try - { - return await Policy - .WrapAsync(_qoSProvider.CircuitBreaker.CircuitBreakerPolicy, _qoSProvider.CircuitBreaker.TimeoutPolicy) - .ExecuteAsync(() => base.SendAsync(request,cancellationToken)); - } - catch (BrokenCircuitException ex) - { - _logger.LogError($"Reached to allowed number of exceptions. Circuit is open",ex); - throw; - } - catch (HttpRequestException ex) - { - _logger.LogError($"Error in CircuitBreakingDelegatingHandler.SendAync", ex); - throw; - } - } - } -} +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Ocelot.Logging; +using Ocelot.Requester.QoS; +using Polly; +using Polly.CircuitBreaker; +using Polly.Timeout; + +namespace Ocelot.Requester +{ + public class PollyCircuitBreakingDelegatingHandler : DelegatingHandler + { + private readonly IQoSProvider _qoSProvider; + private readonly IOcelotLogger _logger; + + public PollyCircuitBreakingDelegatingHandler( + IQoSProvider qoSProvider, + IOcelotLogger logger) + { + _qoSProvider = qoSProvider; + _logger = logger; + } + + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + try + { + return await Policy + .WrapAsync(_qoSProvider.CircuitBreaker.CircuitBreakerPolicy, _qoSProvider.CircuitBreaker.TimeoutPolicy) + .ExecuteAsync(() => base.SendAsync(request,cancellationToken)); + } + catch (BrokenCircuitException ex) + { + _logger.LogError($"Reached to allowed number of exceptions. Circuit is open",ex); + throw; + } + catch (HttpRequestException ex) + { + _logger.LogError($"Error in CircuitBreakingDelegatingHandler.SendAync", ex); + throw; + } + } + } +} diff --git a/src/Ocelot/Requester/QoS/CircuitBreaker.cs b/src/Ocelot/Requester/QoS/CircuitBreaker.cs index 4b1a0d793..91b8e4302 100644 --- a/src/Ocelot/Requester/QoS/CircuitBreaker.cs +++ b/src/Ocelot/Requester/QoS/CircuitBreaker.cs @@ -1,17 +1,17 @@ -using Polly.CircuitBreaker; -using Polly.Timeout; - -namespace Ocelot.Requester.QoS -{ - public class CircuitBreaker - { - public CircuitBreaker(CircuitBreakerPolicy circuitBreakerPolicy, TimeoutPolicy timeoutPolicy) - { - CircuitBreakerPolicy = circuitBreakerPolicy; - TimeoutPolicy = timeoutPolicy; - } - - public CircuitBreakerPolicy CircuitBreakerPolicy { get; private set; } - public TimeoutPolicy TimeoutPolicy { get; private set; } - } +using Polly.CircuitBreaker; +using Polly.Timeout; + +namespace Ocelot.Requester.QoS +{ + public class CircuitBreaker + { + public CircuitBreaker(CircuitBreakerPolicy circuitBreakerPolicy, TimeoutPolicy timeoutPolicy) + { + CircuitBreakerPolicy = circuitBreakerPolicy; + TimeoutPolicy = timeoutPolicy; + } + + public CircuitBreakerPolicy CircuitBreakerPolicy { get; private set; } + public TimeoutPolicy TimeoutPolicy { get; private set; } + } } \ No newline at end of file diff --git a/src/Ocelot/Requester/QoS/IQoSProvider.cs b/src/Ocelot/Requester/QoS/IQoSProvider.cs index 75fc48e30..599a5b7a4 100644 --- a/src/Ocelot/Requester/QoS/IQoSProvider.cs +++ b/src/Ocelot/Requester/QoS/IQoSProvider.cs @@ -1,7 +1,7 @@ -namespace Ocelot.Requester.QoS -{ - public interface IQoSProvider - { - CircuitBreaker CircuitBreaker { get; } - } +namespace Ocelot.Requester.QoS +{ + public interface IQoSProvider + { + CircuitBreaker CircuitBreaker { get; } + } } \ No newline at end of file diff --git a/src/Ocelot/Requester/QoS/IQoSProviderFactory.cs b/src/Ocelot/Requester/QoS/IQoSProviderFactory.cs index ae23c3970..043ec19d6 100644 --- a/src/Ocelot/Requester/QoS/IQoSProviderFactory.cs +++ b/src/Ocelot/Requester/QoS/IQoSProviderFactory.cs @@ -1,10 +1,10 @@ -using Ocelot.Configuration; -using Ocelot.LoadBalancer.LoadBalancers; - -namespace Ocelot.Requester.QoS -{ - public interface IQoSProviderFactory - { - IQoSProvider Get(ReRoute reRoute); - } -} +using Ocelot.Configuration; +using Ocelot.LoadBalancer.LoadBalancers; + +namespace Ocelot.Requester.QoS +{ + public interface IQoSProviderFactory + { + IQoSProvider Get(ReRoute reRoute); + } +} diff --git a/src/Ocelot/Requester/QoS/IQosProviderHouse.cs b/src/Ocelot/Requester/QoS/IQosProviderHouse.cs index 1ec0cf730..add24a6bf 100644 --- a/src/Ocelot/Requester/QoS/IQosProviderHouse.cs +++ b/src/Ocelot/Requester/QoS/IQosProviderHouse.cs @@ -1,10 +1,10 @@ -using Ocelot.Configuration; -using Ocelot.Responses; - -namespace Ocelot.Requester.QoS -{ - public interface IQosProviderHouse - { - Response Get(ReRoute reRoute); - } +using Ocelot.Configuration; +using Ocelot.Responses; + +namespace Ocelot.Requester.QoS +{ + public interface IQosProviderHouse + { + Response Get(ReRoute reRoute); + } } \ No newline at end of file diff --git a/src/Ocelot/Requester/QoS/NoQoSProvider.cs b/src/Ocelot/Requester/QoS/NoQoSProvider.cs index 51d879de7..45ed22858 100644 --- a/src/Ocelot/Requester/QoS/NoQoSProvider.cs +++ b/src/Ocelot/Requester/QoS/NoQoSProvider.cs @@ -1,7 +1,7 @@ -namespace Ocelot.Requester.QoS -{ - public class NoQoSProvider : IQoSProvider - { - public CircuitBreaker CircuitBreaker { get; } - } +namespace Ocelot.Requester.QoS +{ + public class NoQoSProvider : IQoSProvider + { + public CircuitBreaker CircuitBreaker { get; } + } } \ No newline at end of file diff --git a/src/Ocelot/Requester/QoS/PollyQoSProvider.cs b/src/Ocelot/Requester/QoS/PollyQoSProvider.cs index 69f7e6081..7a23986c4 100644 --- a/src/Ocelot/Requester/QoS/PollyQoSProvider.cs +++ b/src/Ocelot/Requester/QoS/PollyQoSProvider.cs @@ -1,51 +1,51 @@ -using System; -using System.Net.Http; -using Ocelot.Configuration; -using Ocelot.Logging; -using Polly; -using Polly.CircuitBreaker; -using Polly.Timeout; - -namespace Ocelot.Requester.QoS -{ - public class PollyQoSProvider : IQoSProvider - { - private readonly CircuitBreakerPolicy _circuitBreakerPolicy; - private readonly TimeoutPolicy _timeoutPolicy; - private readonly IOcelotLogger _logger; - private readonly CircuitBreaker _circuitBreaker; - - public PollyQoSProvider(ReRoute reRoute, IOcelotLoggerFactory loggerFactory) - { - _logger = loggerFactory.CreateLogger(); - - _timeoutPolicy = Policy.TimeoutAsync(TimeSpan.FromMilliseconds(reRoute.QosOptionsOptions.TimeoutValue), reRoute.QosOptionsOptions.TimeoutStrategy); - - _circuitBreakerPolicy = Policy - .Handle() - .Or() - .Or() - .CircuitBreakerAsync( - exceptionsAllowedBeforeBreaking: reRoute.QosOptionsOptions.ExceptionsAllowedBeforeBreaking, - durationOfBreak: TimeSpan.FromMilliseconds(reRoute.QosOptionsOptions.DurationOfBreak), - onBreak: (ex, breakDelay) => - { - _logger.LogError( - ".Breaker logging: Breaking the circuit for " + breakDelay.TotalMilliseconds + "ms!", ex); - }, - onReset: () => - { - _logger.LogDebug(".Breaker logging: Call ok! Closed the circuit again."); - }, - onHalfOpen: () => - { - _logger.LogDebug(".Breaker logging: Half-open; next call is a trial."); - } - ); - - _circuitBreaker = new CircuitBreaker(_circuitBreakerPolicy, _timeoutPolicy); - } - - public CircuitBreaker CircuitBreaker => _circuitBreaker; - } +using System; +using System.Net.Http; +using Ocelot.Configuration; +using Ocelot.Logging; +using Polly; +using Polly.CircuitBreaker; +using Polly.Timeout; + +namespace Ocelot.Requester.QoS +{ + public class PollyQoSProvider : IQoSProvider + { + private readonly CircuitBreakerPolicy _circuitBreakerPolicy; + private readonly TimeoutPolicy _timeoutPolicy; + private readonly IOcelotLogger _logger; + private readonly CircuitBreaker _circuitBreaker; + + public PollyQoSProvider(ReRoute reRoute, IOcelotLoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + + _timeoutPolicy = Policy.TimeoutAsync(TimeSpan.FromMilliseconds(reRoute.QosOptionsOptions.TimeoutValue), reRoute.QosOptionsOptions.TimeoutStrategy); + + _circuitBreakerPolicy = Policy + .Handle() + .Or() + .Or() + .CircuitBreakerAsync( + exceptionsAllowedBeforeBreaking: reRoute.QosOptionsOptions.ExceptionsAllowedBeforeBreaking, + durationOfBreak: TimeSpan.FromMilliseconds(reRoute.QosOptionsOptions.DurationOfBreak), + onBreak: (ex, breakDelay) => + { + _logger.LogError( + ".Breaker logging: Breaking the circuit for " + breakDelay.TotalMilliseconds + "ms!", ex); + }, + onReset: () => + { + _logger.LogDebug(".Breaker logging: Call ok! Closed the circuit again."); + }, + onHalfOpen: () => + { + _logger.LogDebug(".Breaker logging: Half-open; next call is a trial."); + } + ); + + _circuitBreaker = new CircuitBreaker(_circuitBreakerPolicy, _timeoutPolicy); + } + + public CircuitBreaker CircuitBreaker => _circuitBreaker; + } } \ No newline at end of file diff --git a/src/Ocelot/Requester/QoS/QoSProviderFactory.cs b/src/Ocelot/Requester/QoS/QoSProviderFactory.cs index 59ce123eb..0150cf8f7 100644 --- a/src/Ocelot/Requester/QoS/QoSProviderFactory.cs +++ b/src/Ocelot/Requester/QoS/QoSProviderFactory.cs @@ -1,25 +1,25 @@ -using Ocelot.Configuration; -using Ocelot.Logging; - -namespace Ocelot.Requester.QoS -{ - public class QoSProviderFactory : IQoSProviderFactory - { - private readonly IOcelotLoggerFactory _loggerFactory; - - public QoSProviderFactory(IOcelotLoggerFactory loggerFactory) - { - _loggerFactory = loggerFactory; - } - - public IQoSProvider Get(ReRoute reRoute) - { - if (reRoute.IsQos) - { - return new PollyQoSProvider(reRoute, _loggerFactory); - } - - return new NoQoSProvider(); - } - } +using Ocelot.Configuration; +using Ocelot.Logging; + +namespace Ocelot.Requester.QoS +{ + public class QoSProviderFactory : IQoSProviderFactory + { + private readonly IOcelotLoggerFactory _loggerFactory; + + public QoSProviderFactory(IOcelotLoggerFactory loggerFactory) + { + _loggerFactory = loggerFactory; + } + + public IQoSProvider Get(ReRoute reRoute) + { + if (reRoute.IsQos) + { + return new PollyQoSProvider(reRoute, _loggerFactory); + } + + return new NoQoSProvider(); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Requester/QoS/QosProviderHouse.cs b/src/Ocelot/Requester/QoS/QosProviderHouse.cs index 780f81817..6d09f2743 100644 --- a/src/Ocelot/Requester/QoS/QosProviderHouse.cs +++ b/src/Ocelot/Requester/QoS/QosProviderHouse.cs @@ -1,53 +1,53 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using Ocelot.Configuration; -using Ocelot.Responses; - -namespace Ocelot.Requester.QoS -{ - public class QosProviderHouse : IQosProviderHouse - { - private readonly ConcurrentDictionary _qoSProviders; - private readonly IQoSProviderFactory _qoSProviderFactory; - - public QosProviderHouse(IQoSProviderFactory qoSProviderFactory) - { - _qoSProviderFactory = qoSProviderFactory; - _qoSProviders = new ConcurrentDictionary(); - } - - public Response Get(ReRoute reRoute) - { - try - { - if (_qoSProviders.TryGetValue(reRoute.ReRouteKey, out var qosProvider)) - { - if (reRoute.IsQos && qosProvider.CircuitBreaker == null) - { - qosProvider = _qoSProviderFactory.Get(reRoute); - Add(reRoute.ReRouteKey, qosProvider); - } - - return new OkResponse(_qoSProviders[reRoute.ReRouteKey]); - } - - qosProvider = _qoSProviderFactory.Get(reRoute); - Add(reRoute.ReRouteKey, qosProvider); - return new OkResponse(qosProvider); - } - catch (Exception ex) - { - return new ErrorResponse(new List() - { - new UnableToFindQoSProviderError($"unabe to find qos provider for {reRoute.ReRouteKey}, exception was {ex}") - }); - } - } - - private void Add(string key, IQoSProvider qosProvider) - { - _qoSProviders.AddOrUpdate(key, qosProvider, (x, y) => qosProvider); - } - } +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using Ocelot.Configuration; +using Ocelot.Responses; + +namespace Ocelot.Requester.QoS +{ + public class QosProviderHouse : IQosProviderHouse + { + private readonly ConcurrentDictionary _qoSProviders; + private readonly IQoSProviderFactory _qoSProviderFactory; + + public QosProviderHouse(IQoSProviderFactory qoSProviderFactory) + { + _qoSProviderFactory = qoSProviderFactory; + _qoSProviders = new ConcurrentDictionary(); + } + + public Response Get(ReRoute reRoute) + { + try + { + if (_qoSProviders.TryGetValue(reRoute.ReRouteKey, out var qosProvider)) + { + if (reRoute.IsQos && qosProvider.CircuitBreaker == null) + { + qosProvider = _qoSProviderFactory.Get(reRoute); + Add(reRoute.ReRouteKey, qosProvider); + } + + return new OkResponse(_qoSProviders[reRoute.ReRouteKey]); + } + + qosProvider = _qoSProviderFactory.Get(reRoute); + Add(reRoute.ReRouteKey, qosProvider); + return new OkResponse(qosProvider); + } + catch (Exception ex) + { + return new ErrorResponse(new List() + { + new UnableToFindQoSProviderError($"unabe to find qos provider for {reRoute.ReRouteKey}, exception was {ex}") + }); + } + } + + private void Add(string key, IQoSProvider qosProvider) + { + _qoSProviders.AddOrUpdate(key, qosProvider, (x, y) => qosProvider); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Requester/QoS/UnableToFindQoSProviderError.cs b/src/Ocelot/Requester/QoS/UnableToFindQoSProviderError.cs index 46ec65f3a..d54db5548 100644 --- a/src/Ocelot/Requester/QoS/UnableToFindQoSProviderError.cs +++ b/src/Ocelot/Requester/QoS/UnableToFindQoSProviderError.cs @@ -1,16 +1,16 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Ocelot.Errors; - -namespace Ocelot.Requester.QoS -{ - public class UnableToFindQoSProviderError : Error - { - public UnableToFindQoSProviderError(string message) - : base(message, OcelotErrorCode.UnableToFindQoSProviderError) - { - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Ocelot.Errors; + +namespace Ocelot.Requester.QoS +{ + public class UnableToFindQoSProviderError : Error + { + public UnableToFindQoSProviderError(string message) + : base(message, OcelotErrorCode.UnableToFindQoSProviderError) + { + } + } +} diff --git a/src/Ocelot/Requester/RequestTimedOutError.cs b/src/Ocelot/Requester/RequestTimedOutError.cs index 38afce1a3..86eab0c6b 100644 --- a/src/Ocelot/Requester/RequestTimedOutError.cs +++ b/src/Ocelot/Requester/RequestTimedOutError.cs @@ -1,13 +1,13 @@ -using System; -using Ocelot.Errors; - -namespace Ocelot.Requester -{ - public class RequestTimedOutError : Error - { - public RequestTimedOutError(Exception exception) - : base($"Timeout making http request, exception: {exception.Message}", OcelotErrorCode.RequestTimedOutError) - { - } - } -} +using System; +using Ocelot.Errors; + +namespace Ocelot.Requester +{ + public class RequestTimedOutError : Error + { + public RequestTimedOutError(Exception exception) + : base($"Timeout making http request, exception: {exception.Message}", OcelotErrorCode.RequestTimedOutError) + { + } + } +} diff --git a/src/Ocelot/Requester/UnableToCompleteRequestError.cs b/src/Ocelot/Requester/UnableToCompleteRequestError.cs index d085cd1c7..033ca53f8 100644 --- a/src/Ocelot/Requester/UnableToCompleteRequestError.cs +++ b/src/Ocelot/Requester/UnableToCompleteRequestError.cs @@ -1,13 +1,13 @@ -using System; -using Ocelot.Errors; - -namespace Ocelot.Requester -{ - public class UnableToCompleteRequestError : Error - { - public UnableToCompleteRequestError(Exception exception) - : base($"Error making http request, exception: {exception.Message}", OcelotErrorCode.UnableToCompleteRequestError) - { - } - } -} +using System; +using Ocelot.Errors; + +namespace Ocelot.Requester +{ + public class UnableToCompleteRequestError : Error + { + public UnableToCompleteRequestError(Exception exception) + : base($"Error making http request, exception: {exception.Message}", OcelotErrorCode.UnableToCompleteRequestError) + { + } + } +} diff --git a/src/Ocelot/Responder/HttpContextResponder.cs b/src/Ocelot/Responder/HttpContextResponder.cs index 571113d60..76e8956a8 100644 --- a/src/Ocelot/Responder/HttpContextResponder.cs +++ b/src/Ocelot/Responder/HttpContextResponder.cs @@ -1,82 +1,82 @@ -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Primitives; -using Ocelot.Headers; -using Ocelot.Responses; - -namespace Ocelot.Responder -{ - using System.Collections.Generic; - - /// - /// Cannot unit test things in this class due to methods not being implemented - /// on .net concretes used for testing - /// - public class HttpContextResponder : IHttpResponder - { - private readonly IRemoveOutputHeaders _removeOutputHeaders; - - public HttpContextResponder(IRemoveOutputHeaders removeOutputHeaders) - { - _removeOutputHeaders = removeOutputHeaders; - } - - public async Task SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response) - { - _removeOutputHeaders.Remove(response.Headers); - - foreach (var httpResponseHeader in response.Headers) - { - AddHeaderIfDoesntExist(context, httpResponseHeader); - } - - foreach (var httpResponseHeader in response.Content.Headers) - { - AddHeaderIfDoesntExist(context, httpResponseHeader); - } - - var content = await response.Content.ReadAsByteArrayAsync(); - - AddHeaderIfDoesntExist(context, new KeyValuePair>("Content-Length", new []{ content.Length.ToString() }) ); - - context.Response.OnStarting(state => - { - var httpContext = (HttpContext)state; - - httpContext.Response.StatusCode = (int)response.StatusCode; - - return Task.CompletedTask; - - }, context); - - using (Stream stream = new MemoryStream(content)) - { - if (response.StatusCode != HttpStatusCode.NotModified) - { - await stream.CopyToAsync(context.Response.Body); - } - } - } - - public void SetErrorResponseOnContext(HttpContext context, int statusCode) - { - context.Response.OnStarting(x => - { - context.Response.StatusCode = statusCode; - return Task.CompletedTask; - }, context); - } - - private static void AddHeaderIfDoesntExist(HttpContext context, KeyValuePair> httpResponseHeader) - { - if (!context.Response.Headers.ContainsKey(httpResponseHeader.Key)) - { - context.Response.Headers.Add(httpResponseHeader.Key, new StringValues(httpResponseHeader.Value.ToArray())); - } - } - } +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; +using Ocelot.Headers; +using Ocelot.Responses; + +namespace Ocelot.Responder +{ + using System.Collections.Generic; + + /// + /// Cannot unit test things in this class due to methods not being implemented + /// on .net concretes used for testing + /// + public class HttpContextResponder : IHttpResponder + { + private readonly IRemoveOutputHeaders _removeOutputHeaders; + + public HttpContextResponder(IRemoveOutputHeaders removeOutputHeaders) + { + _removeOutputHeaders = removeOutputHeaders; + } + + public async Task SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response) + { + _removeOutputHeaders.Remove(response.Headers); + + foreach (var httpResponseHeader in response.Headers) + { + AddHeaderIfDoesntExist(context, httpResponseHeader); + } + + foreach (var httpResponseHeader in response.Content.Headers) + { + AddHeaderIfDoesntExist(context, httpResponseHeader); + } + + var content = await response.Content.ReadAsByteArrayAsync(); + + AddHeaderIfDoesntExist(context, new KeyValuePair>("Content-Length", new []{ content.Length.ToString() }) ); + + context.Response.OnStarting(state => + { + var httpContext = (HttpContext)state; + + httpContext.Response.StatusCode = (int)response.StatusCode; + + return Task.CompletedTask; + + }, context); + + using (Stream stream = new MemoryStream(content)) + { + if (response.StatusCode != HttpStatusCode.NotModified) + { + await stream.CopyToAsync(context.Response.Body); + } + } + } + + public void SetErrorResponseOnContext(HttpContext context, int statusCode) + { + context.Response.OnStarting(x => + { + context.Response.StatusCode = statusCode; + return Task.CompletedTask; + }, context); + } + + private static void AddHeaderIfDoesntExist(HttpContext context, KeyValuePair> httpResponseHeader) + { + if (!context.Response.Headers.ContainsKey(httpResponseHeader.Key)) + { + context.Response.Headers.Add(httpResponseHeader.Key, new StringValues(httpResponseHeader.Value.ToArray())); + } + } + } } \ No newline at end of file diff --git a/src/Ocelot/Responder/IHttpResponder.cs b/src/Ocelot/Responder/IHttpResponder.cs index f885c6731..c5e5a0ca4 100644 --- a/src/Ocelot/Responder/IHttpResponder.cs +++ b/src/Ocelot/Responder/IHttpResponder.cs @@ -1,12 +1,12 @@ -using System.Net.Http; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; - -namespace Ocelot.Responder -{ - public interface IHttpResponder - { - Task SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response); - void SetErrorResponseOnContext(HttpContext context, int statusCode); - } -} +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; + +namespace Ocelot.Responder +{ + public interface IHttpResponder + { + Task SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response); + void SetErrorResponseOnContext(HttpContext context, int statusCode); + } +} diff --git a/src/Ocelot/Responder/Middleware/ResponderMiddlewareExtensions.cs b/src/Ocelot/Responder/Middleware/ResponderMiddlewareExtensions.cs index 5c119e693..c998b534e 100644 --- a/src/Ocelot/Responder/Middleware/ResponderMiddlewareExtensions.cs +++ b/src/Ocelot/Responder/Middleware/ResponderMiddlewareExtensions.cs @@ -1,12 +1,12 @@ -using Microsoft.AspNetCore.Builder; - -namespace Ocelot.Responder.Middleware -{ - public static class ResponderMiddlewareExtensions - { - public static IApplicationBuilder UseResponderMiddleware(this IApplicationBuilder builder) - { - return builder.UseMiddleware(); - } - } +using Microsoft.AspNetCore.Builder; + +namespace Ocelot.Responder.Middleware +{ + public static class ResponderMiddlewareExtensions + { + public static IApplicationBuilder UseResponderMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } } \ No newline at end of file diff --git a/src/Ocelot/Responses/ErrorResponse.cs b/src/Ocelot/Responses/ErrorResponse.cs index bf20dbd1f..28cd3f02e 100644 --- a/src/Ocelot/Responses/ErrorResponse.cs +++ b/src/Ocelot/Responses/ErrorResponse.cs @@ -1,15 +1,15 @@ -using System.Collections.Generic; -using Ocelot.Errors; - -namespace Ocelot.Responses -{ - public class ErrorResponse : Response - { - public ErrorResponse(Error error) : base(new List{error}) - { - } - public ErrorResponse(List errors) : base(errors) - { - } - } +using System.Collections.Generic; +using Ocelot.Errors; + +namespace Ocelot.Responses +{ + public class ErrorResponse : Response + { + public ErrorResponse(Error error) : base(new List{error}) + { + } + public ErrorResponse(List errors) : base(errors) + { + } + } } \ No newline at end of file diff --git a/src/Ocelot/Responses/ErrorResponseGeneric.cs b/src/Ocelot/Responses/ErrorResponseGeneric.cs index 044d6f619..837c2e5d2 100644 --- a/src/Ocelot/Responses/ErrorResponseGeneric.cs +++ b/src/Ocelot/Responses/ErrorResponseGeneric.cs @@ -1,18 +1,18 @@ -using System.Collections.Generic; -using Ocelot.Errors; - -namespace Ocelot.Responses -{ - public class ErrorResponse : Response - { - public ErrorResponse(Error error) - : base(new List {error}) - { - - } - public ErrorResponse(List errors) - : base(errors) - { - } - } +using System.Collections.Generic; +using Ocelot.Errors; + +namespace Ocelot.Responses +{ + public class ErrorResponse : Response + { + public ErrorResponse(Error error) + : base(new List {error}) + { + + } + public ErrorResponse(List errors) + : base(errors) + { + } + } } \ No newline at end of file diff --git a/src/Ocelot/Responses/OkResponse.cs b/src/Ocelot/Responses/OkResponse.cs index 541d30615..8ad234000 100644 --- a/src/Ocelot/Responses/OkResponse.cs +++ b/src/Ocelot/Responses/OkResponse.cs @@ -1,9 +1,9 @@ -namespace Ocelot.Responses -{ - public class OkResponse : Response - { - public OkResponse() - { - } - } +namespace Ocelot.Responses +{ + public class OkResponse : Response + { + public OkResponse() + { + } + } } \ No newline at end of file diff --git a/src/Ocelot/Responses/OkResponseGeneric.cs b/src/Ocelot/Responses/OkResponseGeneric.cs index c979dc919..7445d2d98 100644 --- a/src/Ocelot/Responses/OkResponseGeneric.cs +++ b/src/Ocelot/Responses/OkResponseGeneric.cs @@ -1,9 +1,9 @@ -namespace Ocelot.Responses -{ - public class OkResponse : Response - { - public OkResponse(T data) : base(data) - { - } - } +namespace Ocelot.Responses +{ + public class OkResponse : Response + { + public OkResponse(T data) : base(data) + { + } + } } \ No newline at end of file diff --git a/src/Ocelot/Responses/Response.cs b/src/Ocelot/Responses/Response.cs index 32ff9902f..21ec91b54 100644 --- a/src/Ocelot/Responses/Response.cs +++ b/src/Ocelot/Responses/Response.cs @@ -1,28 +1,28 @@ -using System.Collections.Generic; -using Ocelot.Errors; - -namespace Ocelot.Responses -{ - public abstract class Response - { - protected Response() - { - Errors = new List(); - } - - protected Response(List errors) - { - Errors = errors ?? new List(); - } - - public List Errors { get; private set; } - - public bool IsError - { - get - { - return Errors.Count > 0; - } - } - } +using System.Collections.Generic; +using Ocelot.Errors; + +namespace Ocelot.Responses +{ + public abstract class Response + { + protected Response() + { + Errors = new List(); + } + + protected Response(List errors) + { + Errors = errors ?? new List(); + } + + public List Errors { get; private set; } + + public bool IsError + { + get + { + return Errors.Count > 0; + } + } + } } \ No newline at end of file diff --git a/src/Ocelot/Responses/ResponseGeneric.cs b/src/Ocelot/Responses/ResponseGeneric.cs index 1902b3e11..479dd08dc 100644 --- a/src/Ocelot/Responses/ResponseGeneric.cs +++ b/src/Ocelot/Responses/ResponseGeneric.cs @@ -1,20 +1,20 @@ -using System.Collections.Generic; -using Ocelot.Errors; - -namespace Ocelot.Responses -{ - public abstract class Response : Response - { - protected Response(T data) - { - Data = data; - } - - protected Response(List errors) : base(errors) - { - } - - public T Data { get; private set; } - - } +using System.Collections.Generic; +using Ocelot.Errors; + +namespace Ocelot.Responses +{ + public abstract class Response : Response + { + protected Response(T data) + { + Data = data; + } + + protected Response(List errors) : base(errors) + { + } + + public T Data { get; private set; } + + } } \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs b/src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs index 98e7b0f69..28a296c50 100644 --- a/src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs +++ b/src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs @@ -1,21 +1,21 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Ocelot.Values; - -namespace Ocelot.ServiceDiscovery -{ - public class ConfigurationServiceProvider : IServiceDiscoveryProvider - { - private readonly List _services; - - public ConfigurationServiceProvider(List services) - { - _services = services; - } - - public async Task> Get() - { - return await Task.FromResult(_services); - } - } +using System.Collections.Generic; +using System.Threading.Tasks; +using Ocelot.Values; + +namespace Ocelot.ServiceDiscovery +{ + public class ConfigurationServiceProvider : IServiceDiscoveryProvider + { + private readonly List _services; + + public ConfigurationServiceProvider(List services) + { + _services = services; + } + + public async Task> Get() + { + return await Task.FromResult(_services); + } + } } \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/ConsulRegistryConfiguration.cs b/src/Ocelot/ServiceDiscovery/ConsulRegistryConfiguration.cs index e06c22bd7..ba389c05a 100644 --- a/src/Ocelot/ServiceDiscovery/ConsulRegistryConfiguration.cs +++ b/src/Ocelot/ServiceDiscovery/ConsulRegistryConfiguration.cs @@ -1,16 +1,16 @@ -namespace Ocelot.ServiceDiscovery -{ - public class ConsulRegistryConfiguration - { - public ConsulRegistryConfiguration(string hostName, int port, string keyOfServiceInConsul) - { - HostName = hostName; - Port = port; - KeyOfServiceInConsul = keyOfServiceInConsul; - } - - public string KeyOfServiceInConsul { get; private set; } - public string HostName { get; private set; } - public int Port { get; private set; } - } +namespace Ocelot.ServiceDiscovery +{ + public class ConsulRegistryConfiguration + { + public ConsulRegistryConfiguration(string hostName, int port, string keyOfServiceInConsul) + { + HostName = hostName; + Port = port; + KeyOfServiceInConsul = keyOfServiceInConsul; + } + + public string KeyOfServiceInConsul { get; private set; } + public string HostName { get; private set; } + public int Port { get; private set; } + } } \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/ConsulServiceDiscoveryProvider.cs b/src/Ocelot/ServiceDiscovery/ConsulServiceDiscoveryProvider.cs index 5dd906f92..c21650f15 100644 --- a/src/Ocelot/ServiceDiscovery/ConsulServiceDiscoveryProvider.cs +++ b/src/Ocelot/ServiceDiscovery/ConsulServiceDiscoveryProvider.cs @@ -1,55 +1,55 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Consul; -using Ocelot.Infrastructure.Extensions; -using Ocelot.Values; - -namespace Ocelot.ServiceDiscovery -{ - public class ConsulServiceDiscoveryProvider : IServiceDiscoveryProvider - { - private readonly ConsulRegistryConfiguration _consulConfig; - private readonly ConsulClient _consul; - private const string VersionPrefix = "version-"; - - public ConsulServiceDiscoveryProvider(ConsulRegistryConfiguration consulRegistryConfiguration) - { - var consulHost = string.IsNullOrEmpty(consulRegistryConfiguration?.HostName) ? "localhost" : consulRegistryConfiguration.HostName; - var consulPort = consulRegistryConfiguration?.Port ?? 8500; - _consulConfig = new ConsulRegistryConfiguration(consulHost, consulPort, consulRegistryConfiguration?.KeyOfServiceInConsul); - - _consul = new ConsulClient(config => - { - config.Address = new Uri($"http://{_consulConfig.HostName}:{_consulConfig.Port}"); - }); - } - - public async Task> Get() - { - var queryResult = await _consul.Health.Service(_consulConfig.KeyOfServiceInConsul, string.Empty, true); - - var services = queryResult.Response.Select(BuildService); - - return services.ToList(); - } - - private Service BuildService(ServiceEntry serviceEntry) - { - return new Service( - serviceEntry.Service.Service, - new HostAndPort(serviceEntry.Service.Address, serviceEntry.Service.Port), - serviceEntry.Service.ID, - GetVersionFromStrings(serviceEntry.Service.Tags), - serviceEntry.Service.Tags ?? Enumerable.Empty()); - } - - private string GetVersionFromStrings(IEnumerable strings) - { - return strings - ?.FirstOrDefault(x => x.StartsWith(VersionPrefix, StringComparison.Ordinal)) - .TrimStart(VersionPrefix); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Consul; +using Ocelot.Infrastructure.Extensions; +using Ocelot.Values; + +namespace Ocelot.ServiceDiscovery +{ + public class ConsulServiceDiscoveryProvider : IServiceDiscoveryProvider + { + private readonly ConsulRegistryConfiguration _consulConfig; + private readonly ConsulClient _consul; + private const string VersionPrefix = "version-"; + + public ConsulServiceDiscoveryProvider(ConsulRegistryConfiguration consulRegistryConfiguration) + { + var consulHost = string.IsNullOrEmpty(consulRegistryConfiguration?.HostName) ? "localhost" : consulRegistryConfiguration.HostName; + var consulPort = consulRegistryConfiguration?.Port ?? 8500; + _consulConfig = new ConsulRegistryConfiguration(consulHost, consulPort, consulRegistryConfiguration?.KeyOfServiceInConsul); + + _consul = new ConsulClient(config => + { + config.Address = new Uri($"http://{_consulConfig.HostName}:{_consulConfig.Port}"); + }); + } + + public async Task> Get() + { + var queryResult = await _consul.Health.Service(_consulConfig.KeyOfServiceInConsul, string.Empty, true); + + var services = queryResult.Response.Select(BuildService); + + return services.ToList(); + } + + private Service BuildService(ServiceEntry serviceEntry) + { + return new Service( + serviceEntry.Service.Service, + new ServiceHostAndPort(serviceEntry.Service.Address, serviceEntry.Service.Port), + serviceEntry.Service.ID, + GetVersionFromStrings(serviceEntry.Service.Tags), + serviceEntry.Service.Tags ?? Enumerable.Empty()); + } + + private string GetVersionFromStrings(IEnumerable strings) + { + return strings + ?.FirstOrDefault(x => x.StartsWith(VersionPrefix, StringComparison.Ordinal)) + .TrimStart(VersionPrefix); + } + } +} diff --git a/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProvider.cs b/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProvider.cs index 2c643d4b1..3d9887f35 100644 --- a/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProvider.cs +++ b/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProvider.cs @@ -1,11 +1,11 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Ocelot.Values; - -namespace Ocelot.ServiceDiscovery -{ - public interface IServiceDiscoveryProvider - { - Task> Get(); - } +using System.Collections.Generic; +using System.Threading.Tasks; +using Ocelot.Values; + +namespace Ocelot.ServiceDiscovery +{ + public interface IServiceDiscoveryProvider + { + Task> Get(); + } } \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs b/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs index e92d50b04..c4ea0ac6f 100644 --- a/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs +++ b/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs @@ -1,9 +1,9 @@ -using Ocelot.Configuration; - -namespace Ocelot.ServiceDiscovery -{ - public interface IServiceDiscoveryProviderFactory - { - IServiceDiscoveryProvider Get(ServiceProviderConfiguration serviceConfig, ReRoute reRoute); - } +using Ocelot.Configuration; + +namespace Ocelot.ServiceDiscovery +{ + public interface IServiceDiscoveryProviderFactory + { + IServiceDiscoveryProvider Get(ServiceProviderConfiguration serviceConfig, ReRoute reRoute); + } } \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs index cd7242925..eb73dae00 100644 --- a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs +++ b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs @@ -1,34 +1,34 @@ -using System.Collections.Generic; -using Ocelot.Configuration; -using Ocelot.Values; - -namespace Ocelot.ServiceDiscovery -{ - public class ServiceDiscoveryProviderFactory : IServiceDiscoveryProviderFactory - { - public IServiceDiscoveryProvider Get(ServiceProviderConfiguration serviceConfig, ReRoute reRoute) - { - if (reRoute.UseServiceDiscovery) - { - return GetServiceDiscoveryProvider(reRoute.ServiceName, serviceConfig.ServiceProviderHost, serviceConfig.ServiceProviderPort); - } - - var services = new List() - { - new Service(reRoute.ServiceName, - new HostAndPort(reRoute.DownstreamHost, reRoute.DownstreamPort), - string.Empty, - string.Empty, - new string[0]) - }; - - return new ConfigurationServiceProvider(services); - } - - private IServiceDiscoveryProvider GetServiceDiscoveryProvider(string keyOfServiceInConsul, string providerHostName, int providerPort) - { - var consulRegistryConfiguration = new ConsulRegistryConfiguration(providerHostName, providerPort, keyOfServiceInConsul); - return new ConsulServiceDiscoveryProvider(consulRegistryConfiguration); - } - } -} \ No newline at end of file +using System.Collections.Generic; +using Ocelot.Configuration; +using Ocelot.Values; + +namespace Ocelot.ServiceDiscovery +{ + public class ServiceDiscoveryProviderFactory : IServiceDiscoveryProviderFactory + { + public IServiceDiscoveryProvider Get(ServiceProviderConfiguration serviceConfig, ReRoute reRoute) + { + if (reRoute.UseServiceDiscovery) + { + return GetServiceDiscoveryProvider(reRoute.ServiceName, serviceConfig.ServiceProviderHost, serviceConfig.ServiceProviderPort); + } + + var services = new List(); + + foreach (var downstreamAddress in reRoute.DownstreamAddresses) + { + var service = new Service(reRoute.ServiceName, new ServiceHostAndPort(downstreamAddress.Host, downstreamAddress.Port), string.Empty, string.Empty, new string[0]); + + services.Add(service); + } + + return new ConfigurationServiceProvider(services); + } + + private IServiceDiscoveryProvider GetServiceDiscoveryProvider(string keyOfServiceInConsul, string providerHostName, int providerPort) + { + var consulRegistryConfiguration = new ConsulRegistryConfiguration(providerHostName, providerPort, keyOfServiceInConsul); + return new ConsulServiceDiscoveryProvider(consulRegistryConfiguration); + } + } +} diff --git a/src/Ocelot/ServiceDiscovery/UnableToFindServiceDiscoveryProviderError.cs b/src/Ocelot/ServiceDiscovery/UnableToFindServiceDiscoveryProviderError.cs index 163e63ef9..639e46597 100644 --- a/src/Ocelot/ServiceDiscovery/UnableToFindServiceDiscoveryProviderError.cs +++ b/src/Ocelot/ServiceDiscovery/UnableToFindServiceDiscoveryProviderError.cs @@ -1,12 +1,12 @@ -using Ocelot.Errors; - -namespace Ocelot.ServiceDiscovery -{ - public class UnableToFindServiceDiscoveryProviderError : Error - { - public UnableToFindServiceDiscoveryProviderError(string message) - : base(message, OcelotErrorCode.UnableToFindServiceDiscoveryProviderError) - { - } - } +using Ocelot.Errors; + +namespace Ocelot.ServiceDiscovery +{ + public class UnableToFindServiceDiscoveryProviderError : Error + { + public UnableToFindServiceDiscoveryProviderError(string message) + : base(message, OcelotErrorCode.UnableToFindServiceDiscoveryProviderError) + { + } + } } \ No newline at end of file diff --git a/src/Ocelot/Values/DownstreamPath.cs b/src/Ocelot/Values/DownstreamPath.cs index 90f2e83eb..b4dd346b7 100644 --- a/src/Ocelot/Values/DownstreamPath.cs +++ b/src/Ocelot/Values/DownstreamPath.cs @@ -1,12 +1,12 @@ -namespace Ocelot.Values -{ - public class DownstreamPath - { - public DownstreamPath(string value) - { - Value = value; - } - - public string Value { get; private set; } - } -} +namespace Ocelot.Values +{ + public class DownstreamPath + { + public DownstreamPath(string value) + { + Value = value; + } + + public string Value { get; private set; } + } +} diff --git a/src/Ocelot/Values/DownstreamUrl.cs b/src/Ocelot/Values/DownstreamUrl.cs index f809c84b9..48644595f 100644 --- a/src/Ocelot/Values/DownstreamUrl.cs +++ b/src/Ocelot/Values/DownstreamUrl.cs @@ -1,12 +1,12 @@ -namespace Ocelot.Values -{ - public class DownstreamUrl - { - public DownstreamUrl(string value) - { - Value = value; - } - - public string Value { get; private set; } - } +namespace Ocelot.Values +{ + public class DownstreamUrl + { + public DownstreamUrl(string value) + { + Value = value; + } + + public string Value { get; private set; } + } } \ No newline at end of file diff --git a/src/Ocelot/Values/PathTemplate.cs b/src/Ocelot/Values/PathTemplate.cs index b0b534180..6d7221a69 100644 --- a/src/Ocelot/Values/PathTemplate.cs +++ b/src/Ocelot/Values/PathTemplate.cs @@ -1,12 +1,12 @@ -namespace Ocelot.Values -{ - public class PathTemplate - { - public PathTemplate(string value) - { - Value = value; - } - - public string Value { get; private set; } - } -} +namespace Ocelot.Values +{ + public class PathTemplate + { + public PathTemplate(string value) + { + Value = value; + } + + public string Value { get; private set; } + } +} diff --git a/src/Ocelot/Values/Service.cs b/src/Ocelot/Values/Service.cs index 0ba12b79b..1acb26e63 100644 --- a/src/Ocelot/Values/Service.cs +++ b/src/Ocelot/Values/Service.cs @@ -1,29 +1,29 @@ -using System.Collections.Generic; - -namespace Ocelot.Values -{ - public class Service - { - public Service(string name, - HostAndPort hostAndPort, - string id, - string version, - IEnumerable tags) - { - Name = name; - HostAndPort = hostAndPort; - Id = id; - Version = version; - Tags = tags; - } - public string Id { get; private set; } - - public string Name { get; private set; } - - public string Version { get; private set; } - - public IEnumerable Tags { get; private set; } - - public HostAndPort HostAndPort { get; private set; } - } -} \ No newline at end of file +using System.Collections.Generic; + +namespace Ocelot.Values +{ + public class Service + { + public Service(string name, + ServiceHostAndPort hostAndPort, + string id, + string version, + IEnumerable tags) + { + Name = name; + HostAndPort = hostAndPort; + Id = id; + Version = version; + Tags = tags; + } + public string Id { get; private set; } + + public string Name { get; private set; } + + public string Version { get; private set; } + + public IEnumerable Tags { get; private set; } + + public ServiceHostAndPort HostAndPort { get; private set; } + } +} diff --git a/src/Ocelot/Values/HostAndPort.cs b/src/Ocelot/Values/ServiceHostAndPort.cs similarity index 68% rename from src/Ocelot/Values/HostAndPort.cs rename to src/Ocelot/Values/ServiceHostAndPort.cs index c12195279..135944b10 100644 --- a/src/Ocelot/Values/HostAndPort.cs +++ b/src/Ocelot/Values/ServiceHostAndPort.cs @@ -1,14 +1,14 @@ -namespace Ocelot.Values -{ - public class HostAndPort - { - public HostAndPort(string downstreamHost, int downstreamPort) - { - DownstreamHost = downstreamHost?.Trim('/'); - DownstreamPort = downstreamPort; - } - - public string DownstreamHost { get; private set; } - public int DownstreamPort { get; private set; } - } -} \ No newline at end of file +namespace Ocelot.Values +{ + public class ServiceHostAndPort + { + public ServiceHostAndPort(string downstreamHost, int downstreamPort) + { + DownstreamHost = downstreamHost?.Trim('/'); + DownstreamPort = downstreamPort; + } + + public string DownstreamHost { get; private set; } + public int DownstreamPort { get; private set; } + } +} diff --git a/src/Ocelot/Values/UpstreamPathTemplate.cs b/src/Ocelot/Values/UpstreamPathTemplate.cs index 00b70b2f0..0cd44bc51 100644 --- a/src/Ocelot/Values/UpstreamPathTemplate.cs +++ b/src/Ocelot/Values/UpstreamPathTemplate.cs @@ -1,14 +1,14 @@ -namespace Ocelot.Values -{ - public class UpstreamPathTemplate - { - public UpstreamPathTemplate(string template, int priority) - { - Template = template; - Priority = priority; - } - - public string Template {get;} - public int Priority {get;} - } +namespace Ocelot.Values +{ + public class UpstreamPathTemplate + { + public UpstreamPathTemplate(string template, int priority) + { + Template = template; + Priority = priority; + } + + public string Template {get;} + public int Priority {get;} + } } \ No newline at end of file diff --git a/test/Ocelot.AcceptanceTests/.gitignore b/test/Ocelot.AcceptanceTests/.gitignore index 0ca27f04e..d65bb0df9 100644 --- a/test/Ocelot.AcceptanceTests/.gitignore +++ b/test/Ocelot.AcceptanceTests/.gitignore @@ -1,234 +1,234 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. - -# User-specific files -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -build/ -bld/ -[Bb]in/ -[Oo]bj/ - -# Visual Studio 2015 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUNIT -*.VisualState.xml -TestResult.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# DNX -project.lock.json -artifacts/ - -*_i.c -*_p.c -*_i.h -*.ilk -*.meta -*.obj -*.pch -*.pdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# JustCode is a .NET coding add-in -.JustCode - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# TODO: Comment the next line if you want to checkin your web deploy settings -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# NuGet Packages -*.nupkg -# The packages folder can be ignored because of Package Restore -**/packages/* -# except build/, which is used as an MSBuild target. -!**/packages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/packages/repositories.config - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Microsoft Azure ApplicationInsights config file -ApplicationInsights.config - -# Windows Store app package directory -AppPackages/ -BundleArtifacts/ - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.pfx -*.publishsettings -node_modules/ -orleans.codegen.cs - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm - -# SQL Server files -*.mdf -*.ldf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe - -# FAKE - F# Make -.fake/ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Microsoft Azure ApplicationInsights config file +ApplicationInsights.config + +# Windows Store app package directory +AppPackages/ +BundleArtifacts/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe + +# FAKE - F# Make +.fake/ diff --git a/test/Ocelot.AcceptanceTests/AcceptanceTestsStartup.cs b/test/Ocelot.AcceptanceTests/AcceptanceTestsStartup.cs index ade7a3fdf..c3cbf1d05 100644 --- a/test/Ocelot.AcceptanceTests/AcceptanceTestsStartup.cs +++ b/test/Ocelot.AcceptanceTests/AcceptanceTestsStartup.cs @@ -1,39 +1,39 @@ -using System; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Ocelot.DependencyInjection; -using Ocelot.Middleware; -using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; - -namespace Ocelot.AcceptanceTests -{ - public class AcceptanceTestsStartup - { - public AcceptanceTestsStartup(IHostingEnvironment env) - { - var builder = new ConfigurationBuilder() - .SetBasePath(env.ContentRootPath) - .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) - .AddJsonFile("configuration.json") - .AddEnvironmentVariables(); - - Configuration = builder.Build(); - } - - public IConfiguration Configuration { get; } - - public virtual void ConfigureServices(IServiceCollection services) - { - services.AddOcelot(Configuration); - } - - public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) - { - app.UseOcelot().Wait(); - } - } -} +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Ocelot.DependencyInjection; +using Ocelot.Middleware; +using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; + +namespace Ocelot.AcceptanceTests +{ + public class AcceptanceTestsStartup + { + public AcceptanceTestsStartup(IHostingEnvironment env) + { + var builder = new ConfigurationBuilder() + .SetBasePath(env.ContentRootPath) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) + .AddJsonFile("configuration.json") + .AddEnvironmentVariables(); + + Configuration = builder.Build(); + } + + public IConfiguration Configuration { get; } + + public virtual void ConfigureServices(IServiceCollection services) + { + services.AddOcelot(Configuration); + } + + public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) + { + app.UseOcelot().Wait(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/AuthenticationTests.cs b/test/Ocelot.AcceptanceTests/AuthenticationTests.cs index eb51e8896..bc76ccffa 100644 --- a/test/Ocelot.AcceptanceTests/AuthenticationTests.cs +++ b/test/Ocelot.AcceptanceTests/AuthenticationTests.cs @@ -1,351 +1,381 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Security.Claims; -using IdentityServer4.AccessTokenValidation; -using IdentityServer4.Models; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; -using Ocelot.Configuration.File; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.AcceptanceTests -{ - using IdentityServer4.Test; - - public class AuthenticationTests : IDisposable - { - private IWebHost _servicebuilder; - private readonly Steps _steps; - private IWebHost _identityServerBuilder; - private string _identityServerRootUrl = "http://localhost:51888"; - private string _downstreamServicePath = "/"; - private string _downstreamServiceHost = "localhost"; - private int _downstreamServicePort = 51876; - private string _downstreamServiceScheme = "http"; - private string _downstreamServiceUrl = "http://localhost:51876"; - private readonly Action _options; - - public AuthenticationTests() - { - _steps = new Steps(); - _options = o => - { - o.Authority = _identityServerRootUrl; - o.ApiName = "api"; - o.RequireHttpsMetadata = false; - o.SupportedTokens = SupportedTokens.Both; - o.ApiSecret = "secret"; - }; - } - - [Fact] - public void should_return_401_using_identity_server_access_token() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = _downstreamServicePath, - DownstreamPort = _downstreamServicePort, - DownstreamHost = _downstreamServiceHost, - DownstreamScheme = _downstreamServiceScheme, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Post" }, - AuthenticationOptions = new FileAuthenticationOptions - { - AuthenticationProviderKey = "Test" - } - } - } - }; - - this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Jwt)) - .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) - .And(x => _steps.GivenThePostHasContent("postContent")) - .When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized)) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_using_identity_server() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = _downstreamServicePath, - DownstreamPort = _downstreamServicePort, - DownstreamHost = _downstreamServiceHost, - DownstreamScheme = _downstreamServiceScheme, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - AuthenticationOptions = new FileAuthenticationOptions - { - AuthenticationProviderKey = "Test" - } - } - } - }; - - this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Jwt)) - .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 200, "Hello from Laura")) - .And(x => _steps.GivenIHaveAToken(_identityServerRootUrl)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) - .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void should_return_response_401_using_identity_server_with_token_requested_for_other_api() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = _downstreamServicePath, - DownstreamPort = _downstreamServicePort, - DownstreamHost = _downstreamServiceHost, - DownstreamScheme = _downstreamServiceScheme, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - AuthenticationOptions = new FileAuthenticationOptions - { - AuthenticationProviderKey = "Test" - } - } - } - }; - - this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Jwt)) - .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 200, "Hello from Laura")) - .And(x => _steps.GivenIHaveATokenForApi2(_identityServerRootUrl)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) - .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized)) - .BDDfy(); - } - - [Fact] - public void should_return_201_using_identity_server_access_token() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = _downstreamServicePath, - DownstreamPort = _downstreamServicePort, - DownstreamHost = _downstreamServiceHost, - DownstreamScheme = _downstreamServiceScheme, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Post" }, - AuthenticationOptions = new FileAuthenticationOptions - { - AuthenticationProviderKey = "Test" - } - } - } - }; - - this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Jwt)) - .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty)) - .And(x => _steps.GivenIHaveAToken(_identityServerRootUrl)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) - .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) - .And(x => _steps.GivenThePostHasContent("postContent")) - .When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Created)) - .BDDfy(); - } - - [Fact] - public void should_return_201_using_identity_server_reference_token() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = _downstreamServicePath, - DownstreamPort = _downstreamServicePort, - DownstreamHost = _downstreamServiceHost, - DownstreamScheme = _downstreamServiceScheme, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Post" }, - AuthenticationOptions = new FileAuthenticationOptions - { - AuthenticationProviderKey = "Test" - } - } - } - }; - - this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Reference)) - .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty)) - .And(x => _steps.GivenIHaveAToken(_identityServerRootUrl)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) - .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) - .And(x => _steps.GivenThePostHasContent("postContent")) - .When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Created)) - .BDDfy(); - } - - private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody) - { - _servicebuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.Run(async context => - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - }); - }) - .Build(); - - _servicebuilder.Start(); - } - - private void GivenThereIsAnIdentityServerOn(string url, string apiName, string api2Name, AccessTokenType tokenType) - { - _identityServerBuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .ConfigureServices(services => - { - services.AddLogging(); - services.AddIdentityServer() - .AddDeveloperSigningCredential() - .AddInMemoryApiResources(new List - { - new ApiResource - { - Name = apiName, - Description = "My API", - Enabled = true, - DisplayName = "test", - Scopes = new List() - { - new Scope("api"), - new Scope("api.readOnly"), - new Scope("openid"), - new Scope("offline_access") - }, - ApiSecrets = new List() - { - new Secret - { - Value = "secret".Sha256() - } - }, - UserClaims = new List() - { - "CustomerId", "LocationId" - } - }, - new ApiResource - { - Name = api2Name, - Description = "My second API", - Enabled = true, - DisplayName = "second test", - Scopes = new List() - { - new Scope("api2"), - new Scope("api2.readOnly"), - new Scope("openid"), - new Scope("offline_access") - }, - ApiSecrets = new List() - { - new Secret - { - Value = "secret".Sha256() - } - }, - UserClaims = new List() - { - "CustomerId", "LocationId" - } - }, - }) - .AddInMemoryClients(new List - { - new Client - { - ClientId = "client", - AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, - ClientSecrets = new List {new Secret("secret".Sha256())}, - AllowedScopes = new List { apiName, api2Name, "api.readOnly", "openid", "offline_access" }, - AccessTokenType = tokenType, - Enabled = true, - RequireClientSecret = false - } - }) - .AddTestUsers(new List - { - new TestUser - { - Username = "test", - Password = "test", - SubjectId = "registered|1231231", - Claims = new List - { - new Claim("CustomerId", "123"), - new Claim("LocationId", "321") - } - } - }); - }) - .Configure(app => - { - app.UseIdentityServer(); - }) - .Build(); - - _identityServerBuilder.Start(); - - _steps.VerifyIdentiryServerStarted(url); - } - - public void Dispose() - { - _servicebuilder?.Dispose(); - _steps.Dispose(); - _identityServerBuilder?.Dispose(); - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Security.Claims; +using IdentityServer4.AccessTokenValidation; +using IdentityServer4.Models; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Ocelot.Configuration.File; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.AcceptanceTests +{ + using IdentityServer4.Test; + + public class AuthenticationTests : IDisposable + { + private IWebHost _servicebuilder; + private readonly Steps _steps; + private IWebHost _identityServerBuilder; + private string _identityServerRootUrl = "http://localhost:51888"; + private string _downstreamServicePath = "/"; + private string _downstreamServiceHost = "localhost"; + private int _downstreamServicePort = 51876; + private string _downstreamServiceScheme = "http"; + private string _downstreamServiceUrl = "http://localhost:51876"; + private readonly Action _options; + + public AuthenticationTests() + { + _steps = new Steps(); + _options = o => + { + o.Authority = _identityServerRootUrl; + o.ApiName = "api"; + o.RequireHttpsMetadata = false; + o.SupportedTokens = SupportedTokens.Both; + o.ApiSecret = "secret"; + }; + } + + [Fact] + public void should_return_401_using_identity_server_access_token() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = _downstreamServicePath, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host =_downstreamServiceHost, + Port = _downstreamServicePort, + } + }, + DownstreamScheme = _downstreamServiceScheme, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Post" }, + AuthenticationOptions = new FileAuthenticationOptions + { + AuthenticationProviderKey = "Test" + } + } + } + }; + + this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Jwt)) + .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) + .And(x => _steps.GivenThePostHasContent("postContent")) + .When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized)) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_using_identity_server() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = _downstreamServicePath, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host =_downstreamServiceHost, + Port = _downstreamServicePort, + } + }, + DownstreamScheme = _downstreamServiceScheme, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + AuthenticationOptions = new FileAuthenticationOptions + { + AuthenticationProviderKey = "Test" + } + } + } + }; + + this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Jwt)) + .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 200, "Hello from Laura")) + .And(x => _steps.GivenIHaveAToken(_identityServerRootUrl)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) + .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [Fact] + public void should_return_response_401_using_identity_server_with_token_requested_for_other_api() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = _downstreamServicePath, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host =_downstreamServiceHost, + Port = _downstreamServicePort, + } + }, + DownstreamScheme = _downstreamServiceScheme, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + AuthenticationOptions = new FileAuthenticationOptions + { + AuthenticationProviderKey = "Test" + } + } + } + }; + + this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Jwt)) + .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 200, "Hello from Laura")) + .And(x => _steps.GivenIHaveATokenForApi2(_identityServerRootUrl)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) + .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized)) + .BDDfy(); + } + + [Fact] + public void should_return_201_using_identity_server_access_token() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = _downstreamServicePath, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host =_downstreamServiceHost, + Port = _downstreamServicePort, + } + }, + DownstreamScheme = _downstreamServiceScheme, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Post" }, + AuthenticationOptions = new FileAuthenticationOptions + { + AuthenticationProviderKey = "Test" + } + } + } + }; + + this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Jwt)) + .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty)) + .And(x => _steps.GivenIHaveAToken(_identityServerRootUrl)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) + .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) + .And(x => _steps.GivenThePostHasContent("postContent")) + .When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Created)) + .BDDfy(); + } + + [Fact] + public void should_return_201_using_identity_server_reference_token() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = _downstreamServicePath, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host =_downstreamServiceHost, + Port = _downstreamServicePort, + } + }, + DownstreamScheme = _downstreamServiceScheme, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Post" }, + AuthenticationOptions = new FileAuthenticationOptions + { + AuthenticationProviderKey = "Test" + } + } + } + }; + + this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Reference)) + .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty)) + .And(x => _steps.GivenIHaveAToken(_identityServerRootUrl)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) + .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) + .And(x => _steps.GivenThePostHasContent("postContent")) + .When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Created)) + .BDDfy(); + } + + private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody) + { + _servicebuilder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(async context => + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + }); + }) + .Build(); + + _servicebuilder.Start(); + } + + private void GivenThereIsAnIdentityServerOn(string url, string apiName, string api2Name, AccessTokenType tokenType) + { + _identityServerBuilder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .ConfigureServices(services => + { + services.AddLogging(); + services.AddIdentityServer() + .AddDeveloperSigningCredential() + .AddInMemoryApiResources(new List + { + new ApiResource + { + Name = apiName, + Description = "My API", + Enabled = true, + DisplayName = "test", + Scopes = new List() + { + new Scope("api"), + new Scope("api.readOnly"), + new Scope("openid"), + new Scope("offline_access") + }, + ApiSecrets = new List() + { + new Secret + { + Value = "secret".Sha256() + } + }, + UserClaims = new List() + { + "CustomerId", "LocationId" + } + }, + new ApiResource + { + Name = api2Name, + Description = "My second API", + Enabled = true, + DisplayName = "second test", + Scopes = new List() + { + new Scope("api2"), + new Scope("api2.readOnly"), + new Scope("openid"), + new Scope("offline_access") + }, + ApiSecrets = new List() + { + new Secret + { + Value = "secret".Sha256() + } + }, + UserClaims = new List() + { + "CustomerId", "LocationId" + } + }, + }) + .AddInMemoryClients(new List + { + new Client + { + ClientId = "client", + AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, + ClientSecrets = new List {new Secret("secret".Sha256())}, + AllowedScopes = new List { apiName, api2Name, "api.readOnly", "openid", "offline_access" }, + AccessTokenType = tokenType, + Enabled = true, + RequireClientSecret = false + } + }) + .AddTestUsers(new List + { + new TestUser + { + Username = "test", + Password = "test", + SubjectId = "registered|1231231", + Claims = new List + { + new Claim("CustomerId", "123"), + new Claim("LocationId", "321") + } + } + }); + }) + .Configure(app => + { + app.UseIdentityServer(); + }) + .Build(); + + _identityServerBuilder.Start(); + + _steps.VerifyIdentiryServerStarted(url); + } + + public void Dispose() + { + _servicebuilder?.Dispose(); + _steps.Dispose(); + _identityServerBuilder?.Dispose(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/AuthorisationTests.cs b/test/Ocelot.AcceptanceTests/AuthorisationTests.cs index a89015736..b30f1db09 100644 --- a/test/Ocelot.AcceptanceTests/AuthorisationTests.cs +++ b/test/Ocelot.AcceptanceTests/AuthorisationTests.cs @@ -1,324 +1,348 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Security.Claims; -using IdentityServer4.AccessTokenValidation; -using IdentityServer4.Models; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; -using Ocelot.Configuration.File; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.AcceptanceTests -{ - using IdentityServer4; - using IdentityServer4.Test; - - public class AuthorisationTests : IDisposable - { - private IWebHost _servicebuilder; - private IWebHost _identityServerBuilder; - private readonly Steps _steps; - private Action _options; - private string _identityServerRootUrl = "http://localhost:51888"; - - public AuthorisationTests() - { - _steps = new Steps(); - _options = o => - { - o.Authority = _identityServerRootUrl; - o.ApiName = "api"; - o.RequireHttpsMetadata = false; - o.SupportedTokens = SupportedTokens.Both; - o.ApiSecret = "secret"; - }; - } - - [Fact] - public void should_return_response_200_authorising_route() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamPort = 51876, - DownstreamScheme = "http", - DownstreamHost = "localhost", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - AuthenticationOptions = new FileAuthenticationOptions - { - AuthenticationProviderKey = "Test" - }, - AddHeadersToRequest = - { - {"CustomerId", "Claims[CustomerId] > value"}, - {"LocationId", "Claims[LocationId] > value"}, - {"UserType", "Claims[sub] > value[0] > |"}, - {"UserId", "Claims[sub] > value[1] > |"} - }, - AddClaimsToRequest = - { - {"CustomerId", "Claims[CustomerId] > value"}, - {"UserType", "Claims[sub] > value[0] > |"}, - {"UserId", "Claims[sub] > value[1] > |"} - }, - RouteClaimsRequirement = - { - {"UserType", "registered"} - } - } - } - }; - - this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:51888", "api", AccessTokenType.Jwt)) - .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", 200, "Hello from Laura")) - .And(x => _steps.GivenIHaveAToken("http://localhost:51888")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) - .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void should_return_response_403_authorising_route() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamPort = 51876, - DownstreamScheme = "http", - DownstreamHost = "localhost", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - AuthenticationOptions = new FileAuthenticationOptions - { - AuthenticationProviderKey = "Test" - }, - AddHeadersToRequest = - { - {"CustomerId", "Claims[CustomerId] > value"}, - {"LocationId", "Claims[LocationId] > value"}, - {"UserType", "Claims[sub] > value[0] > |"}, - {"UserId", "Claims[sub] > value[1] > |"} - }, - AddClaimsToRequest = - { - {"CustomerId", "Claims[CustomerId] > value"}, - {"UserId", "Claims[sub] > value[1] > |"} - }, - RouteClaimsRequirement = - { - {"UserType", "registered"} - } - } - } - }; - - this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:51888", "api", AccessTokenType.Jwt)) - .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", 200, "Hello from Laura")) - .And(x => _steps.GivenIHaveAToken("http://localhost:51888")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) - .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Forbidden)) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_using_identity_server_with_allowed_scope() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamPort = 51876, - DownstreamHost = "localhost", - DownstreamScheme = "http", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - AuthenticationOptions = new FileAuthenticationOptions - { - AuthenticationProviderKey = "Test", - AllowedScopes = new List{ "api", "api.readOnly", "openid", "offline_access" }, - }, - } - } - }; - - this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:51888", "api", AccessTokenType.Jwt)) - .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", 200, "Hello from Laura")) - .And(x => _steps.GivenIHaveATokenForApiReadOnlyScope("http://localhost:51888")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) - .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .BDDfy(); - } - - [Fact] - public void should_return_response_403_using_identity_server_with_scope_not_allowed() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamPort = 51876, - DownstreamHost = "localhost", - DownstreamScheme = "http", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - AuthenticationOptions = new FileAuthenticationOptions - { - AuthenticationProviderKey = "Test", - AllowedScopes = new List{ "api", "openid", "offline_access" }, - }, - } - } - }; - - this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:51888", "api", AccessTokenType.Jwt)) - .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", 200, "Hello from Laura")) - .And(x => _steps.GivenIHaveATokenForApiReadOnlyScope("http://localhost:51888")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) - .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Forbidden)) - .BDDfy(); - } - - private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody) - { - _servicebuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.Run(async context => - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - }); - }) - .Build(); - - _servicebuilder.Start(); - } - - private void GivenThereIsAnIdentityServerOn(string url, string apiName, AccessTokenType tokenType) - { - _identityServerBuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .ConfigureServices(services => - { - services.AddLogging(); - services.AddIdentityServer() - .AddDeveloperSigningCredential() - .AddInMemoryApiResources(new List - { - new ApiResource - { - Name = apiName, - Description = "My API", - Enabled = true, - DisplayName = "test", - Scopes = new List() - { - new Scope("api"), - new Scope("api.readOnly"), - new Scope("openid"), - new Scope("offline_access") - }, - ApiSecrets = new List() - { - new Secret - { - Value = "secret".Sha256() - } - }, - UserClaims = new List() - { - "CustomerId", "LocationId", "UserType", "UserId" - } - }, - - }) - .AddInMemoryClients(new List - { - new Client - { - ClientId = "client", - AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, - ClientSecrets = new List {new Secret("secret".Sha256())}, - AllowedScopes = new List { apiName, "api.readOnly", "openid", "offline_access" }, - AccessTokenType = tokenType, - Enabled = true, - RequireClientSecret = false - } - }) - .AddTestUsers(new List - { - new TestUser - { - Username = "test", - Password = "test", - SubjectId = "registered|1231231", - Claims = new List - { - new Claim("CustomerId", "123"), - new Claim("LocationId", "321") - } - } - }); - }) - .Configure(app => - { - app.UseIdentityServer(); - }) - .Build(); - - _identityServerBuilder.Start(); - - _steps.VerifyIdentiryServerStarted(url); - - } - - public void Dispose() - { - _servicebuilder?.Dispose(); - _steps.Dispose(); - _identityServerBuilder?.Dispose(); - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Security.Claims; +using IdentityServer4.AccessTokenValidation; +using IdentityServer4.Models; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Ocelot.Configuration.File; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.AcceptanceTests +{ + using IdentityServer4; + using IdentityServer4.Test; + + public class AuthorisationTests : IDisposable + { + private IWebHost _servicebuilder; + private IWebHost _identityServerBuilder; + private readonly Steps _steps; + private Action _options; + private string _identityServerRootUrl = "http://localhost:51888"; + + public AuthorisationTests() + { + _steps = new Steps(); + _options = o => + { + o.Authority = _identityServerRootUrl; + o.ApiName = "api"; + o.RequireHttpsMetadata = false; + o.SupportedTokens = SupportedTokens.Both; + o.ApiSecret = "secret"; + }; + } + + [Fact] + public void should_return_response_200_authorising_route() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51876, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + AuthenticationOptions = new FileAuthenticationOptions + { + AuthenticationProviderKey = "Test" + }, + AddHeadersToRequest = + { + {"CustomerId", "Claims[CustomerId] > value"}, + {"LocationId", "Claims[LocationId] > value"}, + {"UserType", "Claims[sub] > value[0] > |"}, + {"UserId", "Claims[sub] > value[1] > |"} + }, + AddClaimsToRequest = + { + {"CustomerId", "Claims[CustomerId] > value"}, + {"UserType", "Claims[sub] > value[0] > |"}, + {"UserId", "Claims[sub] > value[1] > |"} + }, + RouteClaimsRequirement = + { + {"UserType", "registered"} + } + } + } + }; + + this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:51888", "api", AccessTokenType.Jwt)) + .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", 200, "Hello from Laura")) + .And(x => _steps.GivenIHaveAToken("http://localhost:51888")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) + .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [Fact] + public void should_return_response_403_authorising_route() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51876, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + AuthenticationOptions = new FileAuthenticationOptions + { + AuthenticationProviderKey = "Test" + }, + AddHeadersToRequest = + { + {"CustomerId", "Claims[CustomerId] > value"}, + {"LocationId", "Claims[LocationId] > value"}, + {"UserType", "Claims[sub] > value[0] > |"}, + {"UserId", "Claims[sub] > value[1] > |"} + }, + AddClaimsToRequest = + { + {"CustomerId", "Claims[CustomerId] > value"}, + {"UserId", "Claims[sub] > value[1] > |"} + }, + RouteClaimsRequirement = + { + {"UserType", "registered"} + } + } + } + }; + + this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:51888", "api", AccessTokenType.Jwt)) + .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", 200, "Hello from Laura")) + .And(x => _steps.GivenIHaveAToken("http://localhost:51888")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) + .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Forbidden)) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_using_identity_server_with_allowed_scope() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51876, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + AuthenticationOptions = new FileAuthenticationOptions + { + AuthenticationProviderKey = "Test", + AllowedScopes = new List{ "api", "api.readOnly", "openid", "offline_access" }, + }, + } + } + }; + + this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:51888", "api", AccessTokenType.Jwt)) + .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", 200, "Hello from Laura")) + .And(x => _steps.GivenIHaveATokenForApiReadOnlyScope("http://localhost:51888")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) + .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .BDDfy(); + } + + [Fact] + public void should_return_response_403_using_identity_server_with_scope_not_allowed() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51876, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + AuthenticationOptions = new FileAuthenticationOptions + { + AuthenticationProviderKey = "Test", + AllowedScopes = new List{ "api", "openid", "offline_access" }, + }, + } + } + }; + + this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:51888", "api", AccessTokenType.Jwt)) + .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", 200, "Hello from Laura")) + .And(x => _steps.GivenIHaveATokenForApiReadOnlyScope("http://localhost:51888")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) + .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Forbidden)) + .BDDfy(); + } + + private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody) + { + _servicebuilder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(async context => + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + }); + }) + .Build(); + + _servicebuilder.Start(); + } + + private void GivenThereIsAnIdentityServerOn(string url, string apiName, AccessTokenType tokenType) + { + _identityServerBuilder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .ConfigureServices(services => + { + services.AddLogging(); + services.AddIdentityServer() + .AddDeveloperSigningCredential() + .AddInMemoryApiResources(new List + { + new ApiResource + { + Name = apiName, + Description = "My API", + Enabled = true, + DisplayName = "test", + Scopes = new List() + { + new Scope("api"), + new Scope("api.readOnly"), + new Scope("openid"), + new Scope("offline_access") + }, + ApiSecrets = new List() + { + new Secret + { + Value = "secret".Sha256() + } + }, + UserClaims = new List() + { + "CustomerId", "LocationId", "UserType", "UserId" + } + }, + + }) + .AddInMemoryClients(new List + { + new Client + { + ClientId = "client", + AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, + ClientSecrets = new List {new Secret("secret".Sha256())}, + AllowedScopes = new List { apiName, "api.readOnly", "openid", "offline_access" }, + AccessTokenType = tokenType, + Enabled = true, + RequireClientSecret = false + } + }) + .AddTestUsers(new List + { + new TestUser + { + Username = "test", + Password = "test", + SubjectId = "registered|1231231", + Claims = new List + { + new Claim("CustomerId", "123"), + new Claim("LocationId", "321") + } + } + }); + }) + .Configure(app => + { + app.UseIdentityServer(); + }) + .Build(); + + _identityServerBuilder.Start(); + + _steps.VerifyIdentiryServerStarted(url); + + } + + public void Dispose() + { + _servicebuilder?.Dispose(); + _steps.Dispose(); + _identityServerBuilder?.Dispose(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/BearerToken.cs b/test/Ocelot.AcceptanceTests/BearerToken.cs index 26fd8d3fc..019b4d99f 100644 --- a/test/Ocelot.AcceptanceTests/BearerToken.cs +++ b/test/Ocelot.AcceptanceTests/BearerToken.cs @@ -1,16 +1,16 @@ -using Newtonsoft.Json; - -namespace Ocelot.AcceptanceTests -{ - class BearerToken - { - [JsonProperty("access_token")] - public string AccessToken { get; set; } - - [JsonProperty("expires_in")] - public int ExpiresIn { get; set; } - - [JsonProperty("token_type")] - public string TokenType { get; set; } - } +using Newtonsoft.Json; + +namespace Ocelot.AcceptanceTests +{ + class BearerToken + { + [JsonProperty("access_token")] + public string AccessToken { get; set; } + + [JsonProperty("expires_in")] + public int ExpiresIn { get; set; } + + [JsonProperty("token_type")] + public string TokenType { get; set; } + } } \ No newline at end of file diff --git a/test/Ocelot.AcceptanceTests/Caching/InMemoryJsonHandle.cs b/test/Ocelot.AcceptanceTests/Caching/InMemoryJsonHandle.cs index 4ab61427c..3f160a7ec 100644 --- a/test/Ocelot.AcceptanceTests/Caching/InMemoryJsonHandle.cs +++ b/test/Ocelot.AcceptanceTests/Caching/InMemoryJsonHandle.cs @@ -1,137 +1,137 @@ -using CacheManager.Core; -using CacheManager.Core.Internal; -using CacheManager.Core.Logging; -using System; -using System.Collections.Concurrent; -using System.Linq; -using static CacheManager.Core.Utility.Guard; - -namespace Ocelot.AcceptanceTests.Caching -{ - public class InMemoryJsonHandle : BaseCacheHandle - { - private readonly ICacheSerializer _serializer; - private readonly ConcurrentDictionary> _cache; - - public InMemoryJsonHandle( - ICacheManagerConfiguration managerConfiguration, - CacheHandleConfiguration configuration, - ICacheSerializer serializer, - ILoggerFactory loggerFactory) : base(managerConfiguration, configuration) - { - _cache = new ConcurrentDictionary>(); - _serializer = serializer; - Logger = loggerFactory.CreateLogger(this); - } - - public override int Count => _cache.Count; - - protected override ILogger Logger { get; } - - public override void Clear() => _cache.Clear(); - - public override void ClearRegion(string region) - { - NotNullOrWhiteSpace(region, nameof(region)); - - var key = string.Concat(region, ":"); - foreach (var item in _cache.Where(p => p.Key.StartsWith(key, StringComparison.OrdinalIgnoreCase))) - { - _cache.TryRemove(item.Key, out Tuple val); - } - } - - public override bool Exists(string key) - { - NotNullOrWhiteSpace(key, nameof(key)); - - return _cache.ContainsKey(key); - } - - public override bool Exists(string key, string region) - { - NotNullOrWhiteSpace(region, nameof(region)); - var fullKey = GetKey(key, region); - return _cache.ContainsKey(fullKey); - } - - protected override bool AddInternalPrepared(CacheItem item) - { - NotNull(item, nameof(item)); - - var key = GetKey(item.Key, item.Region); - - var serializedItem = _serializer.SerializeCacheItem(item); - - return _cache.TryAdd(key, new Tuple(item.Value.GetType(), serializedItem)); - } - - protected override CacheItem GetCacheItemInternal(string key) => GetCacheItemInternal(key, null); - - protected override CacheItem GetCacheItemInternal(string key, string region) - { - var fullKey = GetKey(key, region); - - CacheItem deserializedResult = null; - - if (_cache.TryGetValue(fullKey, out Tuple result)) - { - deserializedResult = _serializer.DeserializeCacheItem(result.Item2, result.Item1); - - if (deserializedResult.ExpirationMode != ExpirationMode.None && IsExpired(deserializedResult, DateTime.UtcNow)) - { - _cache.TryRemove(fullKey, out Tuple removeResult); - TriggerCacheSpecificRemove(key, region, CacheItemRemovedReason.Expired, deserializedResult.Value); - return null; - } - } - - return deserializedResult; - } - - protected override void PutInternalPrepared(CacheItem item) - { - NotNull(item, nameof(item)); - - var serializedItem = _serializer.SerializeCacheItem(item); - - _cache[GetKey(item.Key, item.Region)] = new Tuple(item.Value.GetType(), serializedItem); - } - - protected override bool RemoveInternal(string key) => RemoveInternal(key, null); - - protected override bool RemoveInternal(string key, string region) - { - var fullKey = GetKey(key, region); - return _cache.TryRemove(fullKey, out Tuple val); - } - - private static string GetKey(string key, string region) - { - NotNullOrWhiteSpace(key, nameof(key)); - - if (string.IsNullOrWhiteSpace(region)) - { - return key; - } - - return string.Concat(region, ":", key); - } - - private static bool IsExpired(CacheItem item, DateTime now) - { - if (item.ExpirationMode == ExpirationMode.Absolute - && item.CreatedUtc.Add(item.ExpirationTimeout) < now) - { - return true; - } - else if (item.ExpirationMode == ExpirationMode.Sliding - && item.LastAccessedUtc.Add(item.ExpirationTimeout) < now) - { - return true; - } - - return false; - } - } -} +using CacheManager.Core; +using CacheManager.Core.Internal; +using CacheManager.Core.Logging; +using System; +using System.Collections.Concurrent; +using System.Linq; +using static CacheManager.Core.Utility.Guard; + +namespace Ocelot.AcceptanceTests.Caching +{ + public class InMemoryJsonHandle : BaseCacheHandle + { + private readonly ICacheSerializer _serializer; + private readonly ConcurrentDictionary> _cache; + + public InMemoryJsonHandle( + ICacheManagerConfiguration managerConfiguration, + CacheHandleConfiguration configuration, + ICacheSerializer serializer, + ILoggerFactory loggerFactory) : base(managerConfiguration, configuration) + { + _cache = new ConcurrentDictionary>(); + _serializer = serializer; + Logger = loggerFactory.CreateLogger(this); + } + + public override int Count => _cache.Count; + + protected override ILogger Logger { get; } + + public override void Clear() => _cache.Clear(); + + public override void ClearRegion(string region) + { + NotNullOrWhiteSpace(region, nameof(region)); + + var key = string.Concat(region, ":"); + foreach (var item in _cache.Where(p => p.Key.StartsWith(key, StringComparison.OrdinalIgnoreCase))) + { + _cache.TryRemove(item.Key, out Tuple val); + } + } + + public override bool Exists(string key) + { + NotNullOrWhiteSpace(key, nameof(key)); + + return _cache.ContainsKey(key); + } + + public override bool Exists(string key, string region) + { + NotNullOrWhiteSpace(region, nameof(region)); + var fullKey = GetKey(key, region); + return _cache.ContainsKey(fullKey); + } + + protected override bool AddInternalPrepared(CacheItem item) + { + NotNull(item, nameof(item)); + + var key = GetKey(item.Key, item.Region); + + var serializedItem = _serializer.SerializeCacheItem(item); + + return _cache.TryAdd(key, new Tuple(item.Value.GetType(), serializedItem)); + } + + protected override CacheItem GetCacheItemInternal(string key) => GetCacheItemInternal(key, null); + + protected override CacheItem GetCacheItemInternal(string key, string region) + { + var fullKey = GetKey(key, region); + + CacheItem deserializedResult = null; + + if (_cache.TryGetValue(fullKey, out Tuple result)) + { + deserializedResult = _serializer.DeserializeCacheItem(result.Item2, result.Item1); + + if (deserializedResult.ExpirationMode != ExpirationMode.None && IsExpired(deserializedResult, DateTime.UtcNow)) + { + _cache.TryRemove(fullKey, out Tuple removeResult); + TriggerCacheSpecificRemove(key, region, CacheItemRemovedReason.Expired, deserializedResult.Value); + return null; + } + } + + return deserializedResult; + } + + protected override void PutInternalPrepared(CacheItem item) + { + NotNull(item, nameof(item)); + + var serializedItem = _serializer.SerializeCacheItem(item); + + _cache[GetKey(item.Key, item.Region)] = new Tuple(item.Value.GetType(), serializedItem); + } + + protected override bool RemoveInternal(string key) => RemoveInternal(key, null); + + protected override bool RemoveInternal(string key, string region) + { + var fullKey = GetKey(key, region); + return _cache.TryRemove(fullKey, out Tuple val); + } + + private static string GetKey(string key, string region) + { + NotNullOrWhiteSpace(key, nameof(key)); + + if (string.IsNullOrWhiteSpace(region)) + { + return key; + } + + return string.Concat(region, ":", key); + } + + private static bool IsExpired(CacheItem item, DateTime now) + { + if (item.ExpirationMode == ExpirationMode.Absolute + && item.CreatedUtc.Add(item.ExpirationTimeout) < now) + { + return true; + } + else if (item.ExpirationMode == ExpirationMode.Sliding + && item.LastAccessedUtc.Add(item.ExpirationTimeout) < now) + { + return true; + } + + return false; + } + } +} diff --git a/test/Ocelot.AcceptanceTests/CachingTests.cs b/test/Ocelot.AcceptanceTests/CachingTests.cs index 1c45135a8..244a872f0 100644 --- a/test/Ocelot.AcceptanceTests/CachingTests.cs +++ b/test/Ocelot.AcceptanceTests/CachingTests.cs @@ -1,172 +1,190 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Threading; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Ocelot.Configuration.File; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.AcceptanceTests -{ - public class CachingTests : IDisposable - { - private IWebHost _builder; - private readonly Steps _steps; - - public CachingTests() - { - _steps = new Steps(); - } - - [Fact] - public void should_return_cached_response() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamPort = 51879, - DownstreamScheme = "http", - DownstreamHost = "localhost", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - FileCacheOptions = new FileCacheOptions - { - TtlSeconds = 100 - } - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .Given(x => x.GivenTheServiceNowReturns("http://localhost:51879", 200, "Hello from Tom")) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void should_return_cached_response_when_using_jsonserialized_cache() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamPort = 51879, - DownstreamScheme = "http", - DownstreamHost = "localhost", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - FileCacheOptions = new FileCacheOptions - { - TtlSeconds = 100 - } - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunningUsingJsonSerializedCache()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .Given(x => x.GivenTheServiceNowReturns("http://localhost:51879", 200, "Hello from Tom")) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void should_not_return_cached_response_as_ttl_expires() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamPort = 51879, - DownstreamScheme = "http", - DownstreamHost = "localhost", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - FileCacheOptions = new FileCacheOptions - { - TtlSeconds = 1 - } - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .Given(x => x.GivenTheServiceNowReturns("http://localhost:51879", 200, "Hello from Tom")) - .And(x => x.GivenTheCacheExpires()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Tom")) - .BDDfy(); - } - - private void GivenTheCacheExpires() - { - Thread.Sleep(1000); - } - - private void GivenTheServiceNowReturns(string url, int statusCode, string responseBody) - { - _builder.Dispose(); - GivenThereIsAServiceRunningOn(url, statusCode, responseBody); - } - - private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody) - { - _builder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.Run(async context => - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - }); - }) - .Build(); - - _builder.Start(); - } - - public void Dispose() - { - _builder?.Dispose(); - _steps.Dispose(); - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Threading; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Ocelot.Configuration.File; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.AcceptanceTests +{ + public class CachingTests : IDisposable + { + private IWebHost _builder; + private readonly Steps _steps; + + public CachingTests() + { + _steps = new Steps(); + } + + [Fact] + public void should_return_cached_response() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + FileCacheOptions = new FileCacheOptions + { + TtlSeconds = 100 + } + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .Given(x => x.GivenTheServiceNowReturns("http://localhost:51879", 200, "Hello from Tom")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [Fact] + public void should_return_cached_response_when_using_jsonserialized_cache() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + FileCacheOptions = new FileCacheOptions + { + TtlSeconds = 100 + } + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunningUsingJsonSerializedCache()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .Given(x => x.GivenTheServiceNowReturns("http://localhost:51879", 200, "Hello from Tom")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [Fact] + public void should_not_return_cached_response_as_ttl_expires() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + FileCacheOptions = new FileCacheOptions + { + TtlSeconds = 1 + } + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .Given(x => x.GivenTheServiceNowReturns("http://localhost:51879", 200, "Hello from Tom")) + .And(x => x.GivenTheCacheExpires()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Tom")) + .BDDfy(); + } + + private void GivenTheCacheExpires() + { + Thread.Sleep(1000); + } + + private void GivenTheServiceNowReturns(string url, int statusCode, string responseBody) + { + _builder.Dispose(); + GivenThereIsAServiceRunningOn(url, statusCode, responseBody); + } + + private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody) + { + _builder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(async context => + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + }); + }) + .Build(); + + _builder.Start(); + } + + public void Dispose() + { + _builder?.Dispose(); + _steps.Dispose(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/CannotStartOcelotTests.cs b/test/Ocelot.AcceptanceTests/CannotStartOcelotTests.cs index 25a9565cc..a4dfcac67 100644 --- a/test/Ocelot.AcceptanceTests/CannotStartOcelotTests.cs +++ b/test/Ocelot.AcceptanceTests/CannotStartOcelotTests.cs @@ -1,58 +1,58 @@ - -using System; -using System.Collections.Generic; -using Microsoft.AspNetCore.Hosting; -using Ocelot.Configuration.File; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.AcceptanceTests -{ - public class CannotStartOcelotTests : IDisposable - { - private IWebHost _builder; - private readonly Steps _steps; - private string _downstreamPath; - - public CannotStartOcelotTests() - { - _steps = new Steps(); - } - - [Fact] - public void should_throw_exception_if_cannot_start() - { - var invalidConfig = new FileConfiguration() - { - ReRoutes = new List - { - new FileReRoute - { - UpstreamPathTemplate = "api", - DownstreamPathTemplate = "test" - } - } - }; - - Exception exception = null; - _steps.GivenThereIsAConfiguration(invalidConfig); - try - { - _steps.GivenOcelotIsRunning(); - } - catch(Exception ex) - { - exception = ex; - } - - exception.ShouldNotBeNull(); - } - - public void Dispose() - { - _builder?.Dispose(); - _steps.Dispose(); - } - } + +using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Hosting; +using Ocelot.Configuration.File; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.AcceptanceTests +{ + public class CannotStartOcelotTests : IDisposable + { + private IWebHost _builder; + private readonly Steps _steps; + private string _downstreamPath; + + public CannotStartOcelotTests() + { + _steps = new Steps(); + } + + [Fact] + public void should_throw_exception_if_cannot_start() + { + var invalidConfig = new FileConfiguration() + { + ReRoutes = new List + { + new FileReRoute + { + UpstreamPathTemplate = "api", + DownstreamPathTemplate = "test" + } + } + }; + + Exception exception = null; + _steps.GivenThereIsAConfiguration(invalidConfig); + try + { + _steps.GivenOcelotIsRunning(); + } + catch(Exception ex) + { + exception = ex; + } + + exception.ShouldNotBeNull(); + } + + public void Dispose() + { + _builder?.Dispose(); + _steps.Dispose(); + } + } } \ No newline at end of file diff --git a/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs b/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs index 8c7ef1cd5..247338eb4 100644 --- a/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs @@ -1,219 +1,255 @@ -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 TestStack.BDDfy; -using Xunit; - -namespace Ocelot.AcceptanceTests -{ - public class CaseSensitiveRoutingTests : IDisposable - { - private IWebHost _builder; - private readonly Steps _steps; - - public CaseSensitiveRoutingTests() - { - _steps = new Steps(); - } - - [Fact] - public void should_return_response_200_when_global_ignore_case_sensitivity_set() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/{productId}", - DownstreamPort = 51879, - DownstreamScheme = "http", - DownstreamHost = "localhost", - UpstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = new List { "Get" }, - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products/1", 200, "Some Product")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/PRODUCTS/1")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_when_reroute_ignore_case_sensitivity_set() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/{productId}", - DownstreamPort = 51879, - DownstreamScheme = "http", - DownstreamHost = "localhost", - UpstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = new List { "Get" }, - ReRouteIsCaseSensitive = false, - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products/1", 200, "Some Product")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/PRODUCTS/1")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .BDDfy(); - } - - [Fact] - public void should_return_response_404_when_reroute_respect_case_sensitivity_set() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/{productId}", - DownstreamPort = 51879, - DownstreamScheme = "http", - DownstreamHost = "localhost", - UpstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = new List { "Get" }, - ReRouteIsCaseSensitive = true, - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products/1", 200, "Some Product")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/PRODUCTS/1")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_when_reroute_respect_case_sensitivity_set() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/{productId}", - DownstreamPort = 51879, - DownstreamScheme = "http", - DownstreamHost = "localhost", - UpstreamPathTemplate = "/PRODUCTS/{productId}", - UpstreamHttpMethod = new List { "Get" }, - ReRouteIsCaseSensitive = true, - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products/1", 200, "Some Product")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/PRODUCTS/1")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .BDDfy(); - } - - [Fact] - public void should_return_response_404_when_global_respect_case_sensitivity_set() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/{productId}", - DownstreamPort = 51879, - DownstreamScheme = "http", - DownstreamHost = "localhost", - UpstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = new List { "Get" }, - ReRouteIsCaseSensitive = true, - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products/1", 200, "Some Product")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/PRODUCTS/1")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_when_global_respect_case_sensitivity_set() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/{productId}", - DownstreamPort = 51879, - DownstreamScheme = "http", - DownstreamHost = "localhost", - UpstreamPathTemplate = "/PRODUCTS/{productId}", - UpstreamHttpMethod = new List { "Get" }, - ReRouteIsCaseSensitive = true, - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products/1", 200, "Some Product")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/PRODUCTS/1")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .BDDfy(); - } - - private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody) - { - _builder = new WebHostBuilder() - .UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(baseUrl) - .Configure(app => - { - app.UsePathBase(basePath); - app.Run(async context => - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - }); - }) - .Build(); - - _builder.Start(); - } - - public void Dispose() - { - _builder?.Dispose(); - _steps.Dispose(); - } - } -} +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 TestStack.BDDfy; +using Xunit; + +namespace Ocelot.AcceptanceTests +{ + public class CaseSensitiveRoutingTests : IDisposable + { + private IWebHost _builder; + private readonly Steps _steps; + + public CaseSensitiveRoutingTests() + { + _steps = new Steps(); + } + + [Fact] + public void should_return_response_200_when_global_ignore_case_sensitivity_set() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/{productId}", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/products/{productId}", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products/1", 200, "Some Product")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/PRODUCTS/1")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_when_reroute_ignore_case_sensitivity_set() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/{productId}", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/products/{productId}", + UpstreamHttpMethod = new List { "Get" }, + ReRouteIsCaseSensitive = false, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products/1", 200, "Some Product")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/PRODUCTS/1")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .BDDfy(); + } + + [Fact] + public void should_return_response_404_when_reroute_respect_case_sensitivity_set() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/{productId}", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/products/{productId}", + UpstreamHttpMethod = new List { "Get" }, + ReRouteIsCaseSensitive = true, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products/1", 200, "Some Product")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/PRODUCTS/1")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_when_reroute_respect_case_sensitivity_set() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/{productId}", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/PRODUCTS/{productId}", + UpstreamHttpMethod = new List { "Get" }, + ReRouteIsCaseSensitive = true, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products/1", 200, "Some Product")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/PRODUCTS/1")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .BDDfy(); + } + + [Fact] + public void should_return_response_404_when_global_respect_case_sensitivity_set() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/{productId}", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/products/{productId}", + UpstreamHttpMethod = new List { "Get" }, + ReRouteIsCaseSensitive = true, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products/1", 200, "Some Product")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/PRODUCTS/1")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_when_global_respect_case_sensitivity_set() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/{productId}", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/PRODUCTS/{productId}", + UpstreamHttpMethod = new List { "Get" }, + ReRouteIsCaseSensitive = true, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products/1", 200, "Some Product")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/PRODUCTS/1")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .BDDfy(); + } + + private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody) + { + _builder = new WebHostBuilder() + .UseUrls(baseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(baseUrl) + .Configure(app => + { + app.UsePathBase(basePath); + app.Run(async context => + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + }); + }) + .Build(); + + _builder.Start(); + } + + public void Dispose() + { + _builder?.Dispose(); + _steps.Dispose(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs b/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs index abfb002a1..541823346 100644 --- a/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs +++ b/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs @@ -1,205 +1,211 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Security.Claims; -using IdentityServer4.AccessTokenValidation; -using IdentityServer4.Models; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; -using Ocelot.Configuration.File; -using TestStack.BDDfy; -using Xunit; - -[assembly: CollectionBehavior(DisableTestParallelization = true)] -namespace Ocelot.AcceptanceTests -{ - using IdentityServer4; - using IdentityServer4.Test; - - public class ClaimsToHeadersForwardingTests : IDisposable - { - private IWebHost _servicebuilder; - private IWebHost _identityServerBuilder; - private readonly Steps _steps; - private Action _options; - private string _identityServerRootUrl = "http://localhost:52888"; - - public ClaimsToHeadersForwardingTests() - { - _steps = new Steps(); - _options = o => - { - o.Authority = _identityServerRootUrl; - o.ApiName = "api"; - o.RequireHttpsMetadata = false; - o.SupportedTokens = SupportedTokens.Both; - o.ApiSecret = "secret"; - }; - } - - [Fact] - public void should_return_response_200_and_foward_claim_as_header() - { - var user = new TestUser() - { - Username = "test", - Password = "test", - SubjectId = "registered|1231231", - Claims = new List - { - new Claim("CustomerId", "123"), - new Claim("LocationId", "1") - } - }; - - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamPort = 52876, - DownstreamScheme = "http", - DownstreamHost = "localhost", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - AuthenticationOptions = new FileAuthenticationOptions - { - AuthenticationProviderKey = "Test", - AllowedScopes = new List - { - "openid", "offline_access", "api" - }, - }, - AddHeadersToRequest = - { - {"CustomerId", "Claims[CustomerId] > value"}, - {"LocationId", "Claims[LocationId] > value"}, - {"UserType", "Claims[sub] > value[0] > |"}, - {"UserId", "Claims[sub] > value[1] > |"} - } - } - } - }; - - this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:52888", "api", AccessTokenType.Jwt, user)) - .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:52876", 200)) - .And(x => _steps.GivenIHaveAToken("http://localhost:52888")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) - .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("CustomerId: 123 LocationId: 1 UserType: registered UserId: 1231231")) - .BDDfy(); - } - - private void GivenThereIsAServiceRunningOn(string url, int statusCode) - { - _servicebuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.Run(async context => - { - var customerId = context.Request.Headers.First(x => x.Key == "CustomerId").Value.First(); - var locationId = context.Request.Headers.First(x => x.Key == "LocationId").Value.First(); - var userType = context.Request.Headers.First(x => x.Key == "UserType").Value.First(); - var userId = context.Request.Headers.First(x => x.Key == "UserId").Value.First(); - - var responseBody = $"CustomerId: {customerId} LocationId: {locationId} UserType: {userType} UserId: {userId}"; - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - }); - }) - .Build(); - - _servicebuilder.Start(); - } - - private void GivenThereIsAnIdentityServerOn(string url, string apiName, AccessTokenType tokenType, TestUser user) - { - _identityServerBuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .ConfigureServices(services => - { - services.AddLogging(); - services.AddIdentityServer() - .AddDeveloperSigningCredential() - .AddInMemoryApiResources(new List - { - new ApiResource - { - Name = apiName, - Description = "My API", - Enabled = true, - DisplayName = "test", - Scopes = new List() - { - new Scope("api"), - new Scope("openid"), - new Scope("offline_access") - }, - ApiSecrets = new List() - { - new Secret - { - Value = "secret".Sha256() - } - }, - UserClaims = new List() - { - "CustomerId", "LocationId", "UserType", "UserId" - } - } - }) - .AddInMemoryClients(new List - { - new Client - { - ClientId = "client", - AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, - ClientSecrets = new List {new Secret("secret".Sha256())}, - AllowedScopes = new List { apiName, "openid", "offline_access" }, - AccessTokenType = tokenType, - Enabled = true, - RequireClientSecret = false - } - }) - .AddTestUsers(new List - { - user - }); - }) - .Configure(app => - { - app.UseIdentityServer(); - }) - .Build(); - - _identityServerBuilder.Start(); - - _steps.VerifyIdentiryServerStarted(url); - } - - public void Dispose() - { - _servicebuilder?.Dispose(); - _steps.Dispose(); - _identityServerBuilder?.Dispose(); - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Security.Claims; +using IdentityServer4.AccessTokenValidation; +using IdentityServer4.Models; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Ocelot.Configuration.File; +using TestStack.BDDfy; +using Xunit; + +[assembly: CollectionBehavior(DisableTestParallelization = true)] +namespace Ocelot.AcceptanceTests +{ + using IdentityServer4; + using IdentityServer4.Test; + + public class ClaimsToHeadersForwardingTests : IDisposable + { + private IWebHost _servicebuilder; + private IWebHost _identityServerBuilder; + private readonly Steps _steps; + private Action _options; + private string _identityServerRootUrl = "http://localhost:52888"; + + public ClaimsToHeadersForwardingTests() + { + _steps = new Steps(); + _options = o => + { + o.Authority = _identityServerRootUrl; + o.ApiName = "api"; + o.RequireHttpsMetadata = false; + o.SupportedTokens = SupportedTokens.Both; + o.ApiSecret = "secret"; + }; + } + + [Fact] + public void should_return_response_200_and_foward_claim_as_header() + { + var user = new TestUser() + { + Username = "test", + Password = "test", + SubjectId = "registered|1231231", + Claims = new List + { + new Claim("CustomerId", "123"), + new Claim("LocationId", "1") + } + }; + + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 52876, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + AuthenticationOptions = new FileAuthenticationOptions + { + AuthenticationProviderKey = "Test", + AllowedScopes = new List + { + "openid", "offline_access", "api" + }, + }, + AddHeadersToRequest = + { + {"CustomerId", "Claims[CustomerId] > value"}, + {"LocationId", "Claims[LocationId] > value"}, + {"UserType", "Claims[sub] > value[0] > |"}, + {"UserId", "Claims[sub] > value[1] > |"} + } + } + } + }; + + this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:52888", "api", AccessTokenType.Jwt, user)) + .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:52876", 200)) + .And(x => _steps.GivenIHaveAToken("http://localhost:52888")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) + .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("CustomerId: 123 LocationId: 1 UserType: registered UserId: 1231231")) + .BDDfy(); + } + + private void GivenThereIsAServiceRunningOn(string url, int statusCode) + { + _servicebuilder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(async context => + { + var customerId = context.Request.Headers.First(x => x.Key == "CustomerId").Value.First(); + var locationId = context.Request.Headers.First(x => x.Key == "LocationId").Value.First(); + var userType = context.Request.Headers.First(x => x.Key == "UserType").Value.First(); + var userId = context.Request.Headers.First(x => x.Key == "UserId").Value.First(); + + var responseBody = $"CustomerId: {customerId} LocationId: {locationId} UserType: {userType} UserId: {userId}"; + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + }); + }) + .Build(); + + _servicebuilder.Start(); + } + + private void GivenThereIsAnIdentityServerOn(string url, string apiName, AccessTokenType tokenType, TestUser user) + { + _identityServerBuilder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .ConfigureServices(services => + { + services.AddLogging(); + services.AddIdentityServer() + .AddDeveloperSigningCredential() + .AddInMemoryApiResources(new List + { + new ApiResource + { + Name = apiName, + Description = "My API", + Enabled = true, + DisplayName = "test", + Scopes = new List() + { + new Scope("api"), + new Scope("openid"), + new Scope("offline_access") + }, + ApiSecrets = new List() + { + new Secret + { + Value = "secret".Sha256() + } + }, + UserClaims = new List() + { + "CustomerId", "LocationId", "UserType", "UserId" + } + } + }) + .AddInMemoryClients(new List + { + new Client + { + ClientId = "client", + AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, + ClientSecrets = new List {new Secret("secret".Sha256())}, + AllowedScopes = new List { apiName, "openid", "offline_access" }, + AccessTokenType = tokenType, + Enabled = true, + RequireClientSecret = false + } + }) + .AddTestUsers(new List + { + user + }); + }) + .Configure(app => + { + app.UseIdentityServer(); + }) + .Build(); + + _identityServerBuilder.Start(); + + _steps.VerifyIdentiryServerStarted(url); + } + + public void Dispose() + { + _servicebuilder?.Dispose(); + _steps.Dispose(); + _identityServerBuilder?.Dispose(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs b/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs index d7d1b39ce..b8c1a59ed 100644 --- a/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs +++ b/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs @@ -1,212 +1,218 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Security.Claims; -using IdentityServer4.AccessTokenValidation; -using IdentityServer4.Models; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Primitives; -using Ocelot.Configuration.File; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.AcceptanceTests -{ - using IdentityServer4; - using IdentityServer4.Test; - - public class ClaimsToQueryStringForwardingTests : IDisposable - { - private IWebHost _servicebuilder; - private IWebHost _identityServerBuilder; - private readonly Steps _steps; - private Action _options; - private string _identityServerRootUrl = "http://localhost:57888"; - - public ClaimsToQueryStringForwardingTests() - { - _steps = new Steps(); - _options = o => - { - o.Authority = _identityServerRootUrl; - o.ApiName = "api"; - o.RequireHttpsMetadata = false; - o.SupportedTokens = SupportedTokens.Both; - o.ApiSecret = "secret"; - }; - } - - [Fact] - public void should_return_response_200_and_foward_claim_as_query_string() - { - var user = new TestUser() - { - Username = "test", - Password = "test", - SubjectId = "registered|1231231", - Claims = new List - { - new Claim("CustomerId", "123"), - new Claim("LocationId", "1") - } - }; - - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamPort = 57876, - DownstreamScheme = "http", - DownstreamHost = "localhost", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - AuthenticationOptions = new FileAuthenticationOptions - { - AuthenticationProviderKey = "Test", - AllowedScopes = new List - { - "openid", "offline_access", "api" - }, - }, - AddQueriesToRequest = - { - {"CustomerId", "Claims[CustomerId] > value"}, - {"LocationId", "Claims[LocationId] > value"}, - {"UserType", "Claims[sub] > value[0] > |"}, - {"UserId", "Claims[sub] > value[1] > |"} - } - } - } - }; - - this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:57888", "api", AccessTokenType.Jwt, user)) - .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:57876", 200)) - .And(x => _steps.GivenIHaveAToken("http://localhost:57888")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) - .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("CustomerId: 123 LocationId: 1 UserType: registered UserId: 1231231")) - .BDDfy(); - } - - private void GivenThereIsAServiceRunningOn(string url, int statusCode) - { - _servicebuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.Run(async context => - { - StringValues customerId; - context.Request.Query.TryGetValue("CustomerId", out customerId); - - StringValues locationId; - context.Request.Query.TryGetValue("LocationId", out locationId); - - StringValues userType; - context.Request.Query.TryGetValue("UserType", out userType); - - StringValues userId; - context.Request.Query.TryGetValue("UserId", out userId); - - var responseBody = $"CustomerId: {customerId} LocationId: {locationId} UserType: {userType} UserId: {userId}"; - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - }); - }) - .Build(); - - _servicebuilder.Start(); - } - - private void GivenThereIsAnIdentityServerOn(string url, string apiName, AccessTokenType tokenType, TestUser user) - { - _identityServerBuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .ConfigureServices(services => - { - services.AddLogging(); - services.AddIdentityServer() - .AddDeveloperSigningCredential() - .AddInMemoryApiResources(new List - { - new ApiResource - { - Name = apiName, - Description = "My API", - Enabled = true, - DisplayName = "test", - Scopes = new List() - { - new Scope("api"), - new Scope("openid"), - new Scope("offline_access") - }, - ApiSecrets = new List() - { - new Secret - { - Value = "secret".Sha256() - } - }, - UserClaims = new List() - { - "CustomerId", "LocationId", "UserType", "UserId" - } - } - }) - .AddInMemoryClients(new List - { - new Client - { - ClientId = "client", - AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, - ClientSecrets = new List {new Secret("secret".Sha256())}, - AllowedScopes = new List { apiName, "openid", "offline_access" }, - AccessTokenType = tokenType, - Enabled = true, - RequireClientSecret = false - } - }) - .AddTestUsers(new List - { - user - }); - }) - .Configure(app => - { - app.UseIdentityServer(); - }) - .Build(); - - _identityServerBuilder.Start(); - - _steps.VerifyIdentiryServerStarted(url); - } - - public void Dispose() - { - _servicebuilder?.Dispose(); - _steps.Dispose(); - _identityServerBuilder?.Dispose(); - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Security.Claims; +using IdentityServer4.AccessTokenValidation; +using IdentityServer4.Models; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Primitives; +using Ocelot.Configuration.File; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.AcceptanceTests +{ + using IdentityServer4; + using IdentityServer4.Test; + + public class ClaimsToQueryStringForwardingTests : IDisposable + { + private IWebHost _servicebuilder; + private IWebHost _identityServerBuilder; + private readonly Steps _steps; + private Action _options; + private string _identityServerRootUrl = "http://localhost:57888"; + + public ClaimsToQueryStringForwardingTests() + { + _steps = new Steps(); + _options = o => + { + o.Authority = _identityServerRootUrl; + o.ApiName = "api"; + o.RequireHttpsMetadata = false; + o.SupportedTokens = SupportedTokens.Both; + o.ApiSecret = "secret"; + }; + } + + [Fact] + public void should_return_response_200_and_foward_claim_as_query_string() + { + var user = new TestUser() + { + Username = "test", + Password = "test", + SubjectId = "registered|1231231", + Claims = new List + { + new Claim("CustomerId", "123"), + new Claim("LocationId", "1") + } + }; + + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 57876, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + AuthenticationOptions = new FileAuthenticationOptions + { + AuthenticationProviderKey = "Test", + AllowedScopes = new List + { + "openid", "offline_access", "api" + }, + }, + AddQueriesToRequest = + { + {"CustomerId", "Claims[CustomerId] > value"}, + {"LocationId", "Claims[LocationId] > value"}, + {"UserType", "Claims[sub] > value[0] > |"}, + {"UserId", "Claims[sub] > value[1] > |"} + } + } + } + }; + + this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:57888", "api", AccessTokenType.Jwt, user)) + .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:57876", 200)) + .And(x => _steps.GivenIHaveAToken("http://localhost:57888")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) + .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("CustomerId: 123 LocationId: 1 UserType: registered UserId: 1231231")) + .BDDfy(); + } + + private void GivenThereIsAServiceRunningOn(string url, int statusCode) + { + _servicebuilder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(async context => + { + StringValues customerId; + context.Request.Query.TryGetValue("CustomerId", out customerId); + + StringValues locationId; + context.Request.Query.TryGetValue("LocationId", out locationId); + + StringValues userType; + context.Request.Query.TryGetValue("UserType", out userType); + + StringValues userId; + context.Request.Query.TryGetValue("UserId", out userId); + + var responseBody = $"CustomerId: {customerId} LocationId: {locationId} UserType: {userType} UserId: {userId}"; + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + }); + }) + .Build(); + + _servicebuilder.Start(); + } + + private void GivenThereIsAnIdentityServerOn(string url, string apiName, AccessTokenType tokenType, TestUser user) + { + _identityServerBuilder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .ConfigureServices(services => + { + services.AddLogging(); + services.AddIdentityServer() + .AddDeveloperSigningCredential() + .AddInMemoryApiResources(new List + { + new ApiResource + { + Name = apiName, + Description = "My API", + Enabled = true, + DisplayName = "test", + Scopes = new List() + { + new Scope("api"), + new Scope("openid"), + new Scope("offline_access") + }, + ApiSecrets = new List() + { + new Secret + { + Value = "secret".Sha256() + } + }, + UserClaims = new List() + { + "CustomerId", "LocationId", "UserType", "UserId" + } + } + }) + .AddInMemoryClients(new List + { + new Client + { + ClientId = "client", + AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, + ClientSecrets = new List {new Secret("secret".Sha256())}, + AllowedScopes = new List { apiName, "openid", "offline_access" }, + AccessTokenType = tokenType, + Enabled = true, + RequireClientSecret = false + } + }) + .AddTestUsers(new List + { + user + }); + }) + .Configure(app => + { + app.UseIdentityServer(); + }) + .Build(); + + _identityServerBuilder.Start(); + + _steps.VerifyIdentiryServerStarted(url); + } + + public void Dispose() + { + _servicebuilder?.Dispose(); + _steps.Dispose(); + _identityServerBuilder?.Dispose(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs b/test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs index c970c0c0a..47a4c03d2 100644 --- a/test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs +++ b/test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs @@ -1,166 +1,178 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Ocelot.Configuration.File; -using Shouldly; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.AcceptanceTests -{ - public class ClientRateLimitTests : IDisposable - { - private IWebHost _builder; - private readonly Steps _steps; - private int _counterOne; - - - public ClientRateLimitTests() - { - _steps = new Steps(); - } - - - public void Dispose() - { - _builder?.Dispose(); - _steps.Dispose(); - } - - [Fact] - public void should_call_withratelimiting() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/ClientRateLimit", - DownstreamPort = 51879, - DownstreamScheme = "http", - DownstreamHost = "localhost", - UpstreamPathTemplate = "/api/ClientRateLimit", - UpstreamHttpMethod = new List { "Get" }, - RequestIdKey = _steps.RequestIdKey, - - RateLimitOptions = new FileRateLimitRule() - { - EnableRateLimiting = true, - ClientWhitelist = new List(), - Limit = 3, - Period = "1s", - PeriodTimespan = 1000 - } - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - RateLimitOptions = new FileRateLimitOptions() - { - ClientIdHeader = "ClientId", - DisableRateLimitHeaders = false, - QuotaExceededMessage = "", - RateLimitCounterPrefix = "", - HttpStatusCode = 428 - - }, - RequestIdKey ="oceclientrequest" - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/ClientRateLimit")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit",1)) - .Then(x => _steps.ThenTheStatusCodeShouldBe(200)) - .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit", 2)) - .Then(x => _steps.ThenTheStatusCodeShouldBe(200)) - .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit",1)) - .Then(x => _steps.ThenTheStatusCodeShouldBe(428)) - .BDDfy(); - } - - - [Fact] - public void should_call_middleware_withWhitelistClient() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/ClientRateLimit", - DownstreamPort = 51879, - DownstreamScheme = "http", - DownstreamHost = "localhost", - UpstreamPathTemplate = "/api/ClientRateLimit", - UpstreamHttpMethod = new List { "Get" }, - RequestIdKey = _steps.RequestIdKey, - - RateLimitOptions = new FileRateLimitRule() - { - EnableRateLimiting = true, - ClientWhitelist = new List() { "ocelotclient1"}, - Limit = 3, - Period = "1s", - PeriodTimespan = 100 - } - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - RateLimitOptions = new FileRateLimitOptions() - { - ClientIdHeader = "ClientId", - DisableRateLimitHeaders = false, - QuotaExceededMessage = "", - RateLimitCounterPrefix = "" - }, - RequestIdKey = "oceclientrequest" - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/ClientRateLimit")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit", 4)) - .Then(x => _steps.ThenTheStatusCodeShouldBe(200)) - .BDDfy(); - } - - - private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath) - { - _builder = new WebHostBuilder() - .UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(baseUrl) - .Configure(app => - { - app.UsePathBase(basePath); - app.Run(context => - { - _counterOne++; - context.Response.StatusCode = 200; - context.Response.WriteAsync(_counterOne.ToString()); - return Task.CompletedTask; - }); - }) - .Build(); - - _builder.Start(); - } - - - } -} \ No newline at end of file +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Ocelot.Configuration.File; +using Shouldly; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.AcceptanceTests +{ + public class ClientRateLimitTests : IDisposable + { + private IWebHost _builder; + private readonly Steps _steps; + private int _counterOne; + + + public ClientRateLimitTests() + { + _steps = new Steps(); + } + + + public void Dispose() + { + _builder?.Dispose(); + _steps.Dispose(); + } + + [Fact] + public void should_call_withratelimiting() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/ClientRateLimit", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/api/ClientRateLimit", + UpstreamHttpMethod = new List { "Get" }, + RequestIdKey = _steps.RequestIdKey, + + RateLimitOptions = new FileRateLimitRule() + { + EnableRateLimiting = true, + ClientWhitelist = new List(), + Limit = 3, + Period = "1s", + PeriodTimespan = 1000 + } + } + }, + GlobalConfiguration = new FileGlobalConfiguration() + { + RateLimitOptions = new FileRateLimitOptions() + { + ClientIdHeader = "ClientId", + DisableRateLimitHeaders = false, + QuotaExceededMessage = "", + RateLimitCounterPrefix = "", + HttpStatusCode = 428 + + }, + RequestIdKey ="oceclientrequest" + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/ClientRateLimit")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit",1)) + .Then(x => _steps.ThenTheStatusCodeShouldBe(200)) + .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit", 2)) + .Then(x => _steps.ThenTheStatusCodeShouldBe(200)) + .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit",1)) + .Then(x => _steps.ThenTheStatusCodeShouldBe(428)) + .BDDfy(); + } + + + [Fact] + public void should_call_middleware_withWhitelistClient() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/ClientRateLimit", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/api/ClientRateLimit", + UpstreamHttpMethod = new List { "Get" }, + RequestIdKey = _steps.RequestIdKey, + + RateLimitOptions = new FileRateLimitRule() + { + EnableRateLimiting = true, + ClientWhitelist = new List() { "ocelotclient1"}, + Limit = 3, + Period = "1s", + PeriodTimespan = 100 + } + } + }, + GlobalConfiguration = new FileGlobalConfiguration() + { + RateLimitOptions = new FileRateLimitOptions() + { + ClientIdHeader = "ClientId", + DisableRateLimitHeaders = false, + QuotaExceededMessage = "", + RateLimitCounterPrefix = "" + }, + RequestIdKey = "oceclientrequest" + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/ClientRateLimit")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit", 4)) + .Then(x => _steps.ThenTheStatusCodeShouldBe(200)) + .BDDfy(); + } + + + private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath) + { + _builder = new WebHostBuilder() + .UseUrls(baseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(baseUrl) + .Configure(app => + { + app.UsePathBase(basePath); + app.Run(context => + { + _counterOne++; + context.Response.StatusCode = 200; + context.Response.WriteAsync(_counterOne.ToString()); + return Task.CompletedTask; + }); + }) + .Build(); + + _builder.Start(); + } + + + } +} diff --git a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs b/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs index 5a65050fa..c95d6323a 100644 --- a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs +++ b/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs @@ -1,353 +1,383 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Net; -using System.Text; -using System.Threading; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Newtonsoft.Json; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.Configuration.File; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.AcceptanceTests -{ - public class ConfigurationInConsulTests : IDisposable - { - private IWebHost _builder; - private readonly Steps _steps; - private IWebHost _fakeConsulBuilder; - private FileConfiguration _config; - - public ConfigurationInConsulTests() - { - _steps = new Steps(); - } - - [Fact] - public void should_return_response_200_with_simple_url() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 51779, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = 9500 - } - } - }; - - var fakeConsulServiceDiscoveryUrl = "http://localhost:9500"; - - this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) - .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_with_simple_url_when_using_jsonserialized_cache() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 51779, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = 9502 - } - } - }; - - var fakeConsulServiceDiscoveryUrl = "http://localhost:9502"; - - this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) - .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfigAndJsonSerializedCache()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void should_load_configuration_out_of_consul() - { - var consulPort = 8500; - var configuration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = consulPort - } - } - }; - - var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; - - var consulConfig = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/status", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 51779, - UpstreamPathTemplate = "/cs/status", - UpstreamHttpMethod = new List {"Get"} - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = consulPort - } - } - }; - - this.Given(x => GivenTheConsulConfigurationIs(consulConfig)) - .And(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) - .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "/status", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/cs/status")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - - [Fact] - public void should_load_configuration_out_of_consul_if_it_is_changed() - { - var consulPort = 8506; - var configuration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = consulPort - } - } - }; - - var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; - - var consulConfig = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/status", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 51779, - UpstreamPathTemplate = "/cs/status", - UpstreamHttpMethod = new List {"Get"} - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = consulPort - } - } - }; - - var secondConsulConfig = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/status", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 51779, - UpstreamPathTemplate = "/cs/status/awesome", - UpstreamHttpMethod = new List {"Get"} - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = consulPort - } - } - }; - - this.Given(x => GivenTheConsulConfigurationIs(consulConfig)) - .And(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) - .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "/status", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig()) - .And(x => _steps.WhenIGetUrlOnTheApiGateway("/cs/status")) - .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .And(x => GivenTheConsulConfigurationIs(secondConsulConfig)) - .And(x => GivenIWaitForTheConfigToReplicateToOcelot()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/cs/status/awesome")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - private void GivenIWaitForTheConfigToReplicateToOcelot() - { - Thread.Sleep(10000); - } - - private void GivenTheConsulConfigurationIs(FileConfiguration config) - { - _config = config; - } - - private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url) - { - _fakeConsulBuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.Run(async context => - { - if (context.Request.Method.ToLower() == "get" && context.Request.Path.Value == "/v1/kv/OcelotConfiguration") - { - var json = JsonConvert.SerializeObject(_config); - - var bytes = Encoding.UTF8.GetBytes(json); - - var base64 = Convert.ToBase64String(bytes); - - var kvp = new FakeConsulGetResponse(base64); - - await context.Response.WriteJsonAsync(new FakeConsulGetResponse[] { kvp }); - } - - else if (context.Request.Method.ToLower() == "put" && context.Request.Path.Value == "/v1/kv/OcelotConfiguration") - { - try - { - var reader = new StreamReader(context.Request.Body); - - var json = reader.ReadToEnd(); - - _config = JsonConvert.DeserializeObject(json); - - var response = JsonConvert.SerializeObject(true); - - await context.Response.WriteAsync(response); - } - catch (Exception e) - { - Console.WriteLine(e); - throw; - } - } - }); - }) - .Build(); - - _fakeConsulBuilder.Start(); - } - - public class FakeConsulGetResponse - { - public FakeConsulGetResponse(string value) - { - Value = value; - } - - public int CreateIndex => 100; - public int ModifyIndex => 200; - public int LockIndex => 200; - public string Key => "OcelotConfiguration"; - public int Flags => 0; - public string Value { get; private set; } - public string Session => "adf4238a-882b-9ddc-4a9d-5b6758e4159e"; - } - - private void GivenThereIsAServiceRunningOn(string url, string basePath, int statusCode, string responseBody) - { - _builder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.UsePathBase(basePath); - - app.Run(async context => - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - }); - }) - .Build(); - - _builder.Start(); - } - - public void Dispose() - { - _builder?.Dispose(); - _steps.Dispose(); - } - } -} +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Net; +using System.Text; +using System.Threading; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Newtonsoft.Json; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.File; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.AcceptanceTests +{ + public class ConfigurationInConsulTests : IDisposable + { + private IWebHost _builder; + private readonly Steps _steps; + private IWebHost _fakeConsulBuilder; + private FileConfiguration _config; + + public ConfigurationInConsulTests() + { + _steps = new Steps(); + } + + [Fact] + public void should_return_response_200_with_simple_url() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51779, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + }, + GlobalConfiguration = new FileGlobalConfiguration() + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() + { + Host = "localhost", + Port = 9500 + } + } + }; + + var fakeConsulServiceDiscoveryUrl = "http://localhost:9500"; + + this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) + .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_with_simple_url_when_using_jsonserialized_cache() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51779, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + }, + GlobalConfiguration = new FileGlobalConfiguration() + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() + { + Host = "localhost", + Port = 9502 + } + } + }; + + var fakeConsulServiceDiscoveryUrl = "http://localhost:9502"; + + this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) + .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfigAndJsonSerializedCache()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [Fact] + public void should_load_configuration_out_of_consul() + { + var consulPort = 8500; + var configuration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration() + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() + { + Host = "localhost", + Port = consulPort + } + } + }; + + var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; + + var consulConfig = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/status", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51779, + } + }, + UpstreamPathTemplate = "/cs/status", + UpstreamHttpMethod = new List {"Get"} + } + }, + GlobalConfiguration = new FileGlobalConfiguration() + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() + { + Host = "localhost", + Port = consulPort + } + } + }; + + this.Given(x => GivenTheConsulConfigurationIs(consulConfig)) + .And(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) + .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "/status", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/cs/status")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + + [Fact] + public void should_load_configuration_out_of_consul_if_it_is_changed() + { + var consulPort = 8506; + var configuration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration() + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() + { + Host = "localhost", + Port = consulPort + } + } + }; + + var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; + + var consulConfig = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/status", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51780, + } + }, + UpstreamPathTemplate = "/cs/status", + UpstreamHttpMethod = new List {"Get"} + } + }, + GlobalConfiguration = new FileGlobalConfiguration() + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() + { + Host = "localhost", + Port = consulPort + } + } + }; + + var secondConsulConfig = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/status", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51780, + } + }, + UpstreamPathTemplate = "/cs/status/awesome", + UpstreamHttpMethod = new List {"Get"} + } + }, + GlobalConfiguration = new FileGlobalConfiguration() + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() + { + Host = "localhost", + Port = consulPort + } + } + }; + + this.Given(x => GivenTheConsulConfigurationIs(consulConfig)) + .And(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) + .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51780", "/status", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig()) + .And(x => _steps.WhenIGetUrlOnTheApiGateway("/cs/status")) + .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .And(x => GivenTheConsulConfigurationIs(secondConsulConfig)) + .And(x => GivenIWaitForTheConfigToReplicateToOcelot()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/cs/status/awesome")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + private void GivenIWaitForTheConfigToReplicateToOcelot() + { + Thread.Sleep(10000); + } + + private void GivenTheConsulConfigurationIs(FileConfiguration config) + { + _config = config; + } + + private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url) + { + _fakeConsulBuilder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(async context => + { + if (context.Request.Method.ToLower() == "get" && context.Request.Path.Value == "/v1/kv/OcelotConfiguration") + { + var json = JsonConvert.SerializeObject(_config); + + var bytes = Encoding.UTF8.GetBytes(json); + + var base64 = Convert.ToBase64String(bytes); + + var kvp = new FakeConsulGetResponse(base64); + + await context.Response.WriteJsonAsync(new FakeConsulGetResponse[] { kvp }); + } + + else if (context.Request.Method.ToLower() == "put" && context.Request.Path.Value == "/v1/kv/OcelotConfiguration") + { + try + { + var reader = new StreamReader(context.Request.Body); + + var json = reader.ReadToEnd(); + + _config = JsonConvert.DeserializeObject(json); + + var response = JsonConvert.SerializeObject(true); + + await context.Response.WriteAsync(response); + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + } + }); + }) + .Build(); + + _fakeConsulBuilder.Start(); + } + + public class FakeConsulGetResponse + { + public FakeConsulGetResponse(string value) + { + Value = value; + } + + public int CreateIndex => 100; + public int ModifyIndex => 200; + public int LockIndex => 200; + public string Key => "OcelotConfiguration"; + public int Flags => 0; + public string Value { get; private set; } + public string Session => "adf4238a-882b-9ddc-4a9d-5b6758e4159e"; + } + + private void GivenThereIsAServiceRunningOn(string url, string basePath, int statusCode, string responseBody) + { + _builder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.UsePathBase(basePath); + + app.Run(async context => + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + }); + }) + .Build(); + + _builder.Start(); + } + + public void Dispose() + { + _builder?.Dispose(); + _steps.Dispose(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/ConsulStartup.cs b/test/Ocelot.AcceptanceTests/ConsulStartup.cs index f63257a53..b7eb569b2 100644 --- a/test/Ocelot.AcceptanceTests/ConsulStartup.cs +++ b/test/Ocelot.AcceptanceTests/ConsulStartup.cs @@ -1,40 +1,40 @@ -using System; -using CacheManager.Core; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Ocelot.DependencyInjection; -using Ocelot.Middleware; -using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; - -namespace Ocelot.AcceptanceTests -{ - public class ConsulStartup - { - public ConsulStartup(IHostingEnvironment env) - { - var builder = new ConfigurationBuilder() - .SetBasePath(env.ContentRootPath) - .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) - .AddJsonFile("configuration.json") - .AddEnvironmentVariables(); - - Config = builder.Build(); - } - - public static IConfiguration Config { get; private set; } - - public void ConfigureServices(IServiceCollection services) - { - services.AddOcelot(Config).AddStoreOcelotConfigurationInConsul(); - } - - public void Configure(IApplicationBuilder app) - { - app.UseOcelot().Wait(); - } - } -} +using System; +using CacheManager.Core; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Ocelot.DependencyInjection; +using Ocelot.Middleware; +using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; + +namespace Ocelot.AcceptanceTests +{ + public class ConsulStartup + { + public ConsulStartup(IHostingEnvironment env) + { + var builder = new ConfigurationBuilder() + .SetBasePath(env.ContentRootPath) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) + .AddJsonFile("configuration.json") + .AddEnvironmentVariables(); + + Config = builder.Build(); + } + + public static IConfiguration Config { get; private set; } + + public void ConfigureServices(IServiceCollection services) + { + services.AddOcelot(Config).AddStoreOcelotConfigurationInConsul(); + } + + public void Configure(IApplicationBuilder app) + { + app.UseOcelot().Wait(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs b/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs index 661c1281b..1e3898817 100644 --- a/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs +++ b/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs @@ -1,284 +1,320 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Ocelot.Configuration.File; -using Ocelot.Middleware; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.AcceptanceTests -{ - public class CustomMiddlewareTests : IDisposable - { - private readonly string _configurationPath; - private IWebHost _builder; - private readonly Steps _steps; - private int _counter; - - public CustomMiddlewareTests() - { - _counter = 0; - _steps = new Steps();; - _configurationPath = "configuration.json"; - } - - [Fact] - public void should_call_pre_query_string_builder_middleware() - { - var configuration = new OcelotMiddlewareConfiguration - { - AuthorisationMiddleware = async (ctx, next) => - { - _counter++; - await next.Invoke(); - } - }; - - var fileConfiguration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamPort = 41879, - DownstreamScheme = "http", - DownstreamHost = "localhost", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200)) - .And(x => _steps.GivenThereIsAConfiguration(fileConfiguration, _configurationPath)) - .And(x => _steps.GivenOcelotIsRunning(configuration)) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => x.ThenTheCounterIs(1)) - .BDDfy(); - } - - [Fact] - public void should_call_authorisation_middleware() - { - var configuration = new OcelotMiddlewareConfiguration - { - AuthorisationMiddleware = async (ctx, next) => - { - _counter++; - await next.Invoke(); - } - }; - - var fileConfiguration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamPort = 41879, - DownstreamScheme = "http", - DownstreamHost = "localhost", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200)) - .And(x => _steps.GivenThereIsAConfiguration(fileConfiguration, _configurationPath)) - .And(x => _steps.GivenOcelotIsRunning(configuration)) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => x.ThenTheCounterIs(1)) - .BDDfy(); - } - - [Fact] - public void should_call_authentication_middleware() - { - var configuration = new OcelotMiddlewareConfiguration - { - AuthenticationMiddleware = async (ctx, next) => - { - _counter++; - await next.Invoke(); - } - }; - - var fileConfiguration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/41879/", - DownstreamPort = 41879, - DownstreamScheme = "http", - DownstreamHost = "localhost", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200)) - .And(x => _steps.GivenThereIsAConfiguration(fileConfiguration, _configurationPath)) - .And(x => _steps.GivenOcelotIsRunning(configuration)) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => x.ThenTheCounterIs(1)) - .BDDfy(); - } - - [Fact] - public void should_call_pre_error_middleware() - { - var configuration = new OcelotMiddlewareConfiguration - { - PreErrorResponderMiddleware = async (ctx, next) => - { - _counter++; - await next.Invoke(); - } - }; - - var fileConfiguration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamPort = 41879, - DownstreamScheme = "http", - DownstreamHost = "localhost", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200)) - .And(x => _steps.GivenThereIsAConfiguration(fileConfiguration, _configurationPath)) - .And(x => _steps.GivenOcelotIsRunning(configuration)) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => x.ThenTheCounterIs(1)) - .BDDfy(); - } - - [Fact] - public void should_call_pre_authorisation_middleware() - { - var configuration = new OcelotMiddlewareConfiguration - { - PreAuthorisationMiddleware = async (ctx, next) => - { - _counter++; - await next.Invoke(); - } - }; - - var fileConfiguration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamPort = 41879, - DownstreamScheme = "http", - DownstreamHost = "localhost", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200)) - .And(x => _steps.GivenThereIsAConfiguration(fileConfiguration, _configurationPath)) - .And(x => _steps.GivenOcelotIsRunning(configuration)) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => x.ThenTheCounterIs(1)) - .BDDfy(); - } - - [Fact] - public void should_call_pre_http_authentication_middleware() - { - var configuration = new OcelotMiddlewareConfiguration - { - PreAuthenticationMiddleware = async (ctx, next) => - { - _counter++; - await next.Invoke(); - } - }; - - var fileConfiguration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamPort = 41879, - DownstreamScheme = "http", - DownstreamHost = "localhost", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200)) - .And(x => _steps.GivenThereIsAConfiguration(fileConfiguration, _configurationPath)) - .And(x => _steps.GivenOcelotIsRunning(configuration)) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => x.ThenTheCounterIs(1)) - .BDDfy(); - } - - private void ThenTheCounterIs(int expected) - { - _counter.ShouldBe(expected); - } - - private void GivenThereIsAServiceRunningOn(string url, int statusCode) - { - _builder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.Run(context => - { - context.Response.StatusCode = statusCode; - return Task.CompletedTask; - }); - }) - .Build(); - - _builder.Start(); - } - - public void Dispose() - { - _builder?.Dispose(); - _steps.Dispose(); - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Ocelot.Configuration.File; +using Ocelot.Middleware; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.AcceptanceTests +{ + public class CustomMiddlewareTests : IDisposable + { + private readonly string _configurationPath; + private IWebHost _builder; + private readonly Steps _steps; + private int _counter; + + public CustomMiddlewareTests() + { + _counter = 0; + _steps = new Steps();; + _configurationPath = "configuration.json"; + } + + [Fact] + public void should_call_pre_query_string_builder_middleware() + { + var configuration = new OcelotMiddlewareConfiguration + { + AuthorisationMiddleware = async (ctx, next) => + { + _counter++; + await next.Invoke(); + } + }; + + var fileConfiguration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 41879, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200)) + .And(x => _steps.GivenThereIsAConfiguration(fileConfiguration, _configurationPath)) + .And(x => _steps.GivenOcelotIsRunning(configuration)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => x.ThenTheCounterIs(1)) + .BDDfy(); + } + + [Fact] + public void should_call_authorisation_middleware() + { + var configuration = new OcelotMiddlewareConfiguration + { + AuthorisationMiddleware = async (ctx, next) => + { + _counter++; + await next.Invoke(); + } + }; + + var fileConfiguration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 41879, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200)) + .And(x => _steps.GivenThereIsAConfiguration(fileConfiguration, _configurationPath)) + .And(x => _steps.GivenOcelotIsRunning(configuration)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => x.ThenTheCounterIs(1)) + .BDDfy(); + } + + [Fact] + public void should_call_authentication_middleware() + { + var configuration = new OcelotMiddlewareConfiguration + { + AuthenticationMiddleware = async (ctx, next) => + { + _counter++; + await next.Invoke(); + } + }; + + var fileConfiguration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/41879/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 41879, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200)) + .And(x => _steps.GivenThereIsAConfiguration(fileConfiguration, _configurationPath)) + .And(x => _steps.GivenOcelotIsRunning(configuration)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => x.ThenTheCounterIs(1)) + .BDDfy(); + } + + [Fact] + public void should_call_pre_error_middleware() + { + var configuration = new OcelotMiddlewareConfiguration + { + PreErrorResponderMiddleware = async (ctx, next) => + { + _counter++; + await next.Invoke(); + } + }; + + var fileConfiguration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 41879, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200)) + .And(x => _steps.GivenThereIsAConfiguration(fileConfiguration, _configurationPath)) + .And(x => _steps.GivenOcelotIsRunning(configuration)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => x.ThenTheCounterIs(1)) + .BDDfy(); + } + + [Fact] + public void should_call_pre_authorisation_middleware() + { + var configuration = new OcelotMiddlewareConfiguration + { + PreAuthorisationMiddleware = async (ctx, next) => + { + _counter++; + await next.Invoke(); + } + }; + + var fileConfiguration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 41879, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200)) + .And(x => _steps.GivenThereIsAConfiguration(fileConfiguration, _configurationPath)) + .And(x => _steps.GivenOcelotIsRunning(configuration)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => x.ThenTheCounterIs(1)) + .BDDfy(); + } + + [Fact] + public void should_call_pre_http_authentication_middleware() + { + var configuration = new OcelotMiddlewareConfiguration + { + PreAuthenticationMiddleware = async (ctx, next) => + { + _counter++; + await next.Invoke(); + } + }; + + var fileConfiguration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 41879, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200)) + .And(x => _steps.GivenThereIsAConfiguration(fileConfiguration, _configurationPath)) + .And(x => _steps.GivenOcelotIsRunning(configuration)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => x.ThenTheCounterIs(1)) + .BDDfy(); + } + + private void ThenTheCounterIs(int expected) + { + _counter.ShouldBe(expected); + } + + private void GivenThereIsAServiceRunningOn(string url, int statusCode) + { + _builder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(context => + { + context.Response.StatusCode = statusCode; + return Task.CompletedTask; + }); + }) + .Build(); + + _builder.Start(); + } + + public void Dispose() + { + _builder?.Dispose(); + _steps.Dispose(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/HeaderTests.cs b/test/Ocelot.AcceptanceTests/HeaderTests.cs index 70fdeb908..46d7cd406 100644 --- a/test/Ocelot.AcceptanceTests/HeaderTests.cs +++ b/test/Ocelot.AcceptanceTests/HeaderTests.cs @@ -1,226 +1,250 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Threading.Tasks; -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 HeaderTests : IDisposable - { - private IWebHost _builder; - private readonly Steps _steps; - private string _downstreamPath; - - public HeaderTests() - { - _steps = new Steps(); - } - - [Fact] - public void should_transform_upstream_header() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 51879, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTransform = new Dictionary - { - {"Laz", "D, GP"} - } - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 200, "Laz")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIAddAHeader("Laz", "D")) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("GP")) - .BDDfy(); - } - - [Fact] - public void should_transform_downstream_header() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 51879, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - DownstreamHeaderTransform = new Dictionary - { - {"Location", "http://www.bbc.co.uk/, http://ocelot.com/"} - } - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 200, "Location", "http://www.bbc.co.uk/")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseHeaderIs("Location", "http://ocelot.com/")) - .BDDfy(); - } - - [Fact] - public void should_fix_issue_190() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 6773, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - DownstreamHeaderTransform = new Dictionary - { - {"Location", "http://localhost:6773, {BaseUrl}"} - }, - HttpHandlerOptions = new FileHttpHandlerOptions - { - AllowAutoRedirect = false - } - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:6773", "/", 302, "Location", "http://localhost:6773/pay/Receive")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Redirect)) - .And(x => _steps.ThenTheResponseHeaderIs("Location", "http://localhost:5000/pay/Receive")) - .BDDfy(); - } - - [Fact] - public void should_fix_issue_205() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 6773, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - DownstreamHeaderTransform = new Dictionary - { - {"Location", "{DownstreamBaseUrl}, {BaseUrl}"} - }, - HttpHandlerOptions = new FileHttpHandlerOptions - { - AllowAutoRedirect = false - } - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:6773", "/", 302, "Location", "http://localhost:6773/pay/Receive")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Redirect)) - .And(x => _steps.ThenTheResponseHeaderIs("Location", "http://localhost:5000/pay/Receive")) - .BDDfy(); - } - - - private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string headerKey) - { - _builder = new WebHostBuilder() - .UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .Configure(app => - { - app.UsePathBase(basePath); - app.Run(async context => - { - if(context.Request.Headers.TryGetValue(headerKey, out var values)) - { - var result = values.First(); - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(result); - } - }); - }) - .Build(); - - _builder.Start(); - } - - private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string headerKey, string headerValue) - { - _builder = new WebHostBuilder() - .UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .Configure(app => - { - app.UsePathBase(basePath); - app.Run(async context => - { - context.Response.OnStarting(() => { - context.Response.Headers.Add(headerKey, headerValue); - context.Response.StatusCode = statusCode; - return Task.CompletedTask; - }); - }); - }) - .Build(); - - _builder.Start(); - } - - internal void ThenTheDownstreamUrlPathShouldBe(string expectedDownstreamPath) - { - _downstreamPath.ShouldBe(expectedDownstreamPath); - } - - public void Dispose() - { - _builder?.Dispose(); - _steps.Dispose(); - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +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 HeaderTests : IDisposable + { + private IWebHost _builder; + private readonly Steps _steps; + private string _downstreamPath; + + public HeaderTests() + { + _steps = new Steps(); + } + + [Fact] + public void should_transform_upstream_header() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaderTransform = new Dictionary + { + {"Laz", "D, GP"} + } + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 200, "Laz")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIAddAHeader("Laz", "D")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("GP")) + .BDDfy(); + } + + [Fact] + public void should_transform_downstream_header() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + DownstreamHeaderTransform = new Dictionary + { + {"Location", "http://www.bbc.co.uk/, http://ocelot.com/"} + } + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 200, "Location", "http://www.bbc.co.uk/")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseHeaderIs("Location", "http://ocelot.com/")) + .BDDfy(); + } + + [Fact] + public void should_fix_issue_190() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 6773, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + DownstreamHeaderTransform = new Dictionary + { + {"Location", "http://localhost:6773, {BaseUrl}"} + }, + HttpHandlerOptions = new FileHttpHandlerOptions + { + AllowAutoRedirect = false + } + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:6773", "/", 302, "Location", "http://localhost:6773/pay/Receive")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Redirect)) + .And(x => _steps.ThenTheResponseHeaderIs("Location", "http://localhost:5000/pay/Receive")) + .BDDfy(); + } + + [Fact] + public void should_fix_issue_205() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 6773, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + DownstreamHeaderTransform = new Dictionary + { + {"Location", "{DownstreamBaseUrl}, {BaseUrl}"} + }, + HttpHandlerOptions = new FileHttpHandlerOptions + { + AllowAutoRedirect = false + } + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:6773", "/", 302, "Location", "http://localhost:6773/pay/Receive")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Redirect)) + .And(x => _steps.ThenTheResponseHeaderIs("Location", "http://localhost:5000/pay/Receive")) + .BDDfy(); + } + + + private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string headerKey) + { + _builder = new WebHostBuilder() + .UseUrls(baseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .Configure(app => + { + app.UsePathBase(basePath); + app.Run(async context => + { + if(context.Request.Headers.TryGetValue(headerKey, out var values)) + { + var result = values.First(); + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(result); + } + }); + }) + .Build(); + + _builder.Start(); + } + + private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string headerKey, string headerValue) + { + _builder = new WebHostBuilder() + .UseUrls(baseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .Configure(app => + { + app.UsePathBase(basePath); + app.Run(async context => + { + context.Response.OnStarting(() => { + context.Response.Headers.Add(headerKey, headerValue); + context.Response.StatusCode = statusCode; + return Task.CompletedTask; + }); + }); + }) + .Build(); + + _builder.Start(); + } + + internal void ThenTheDownstreamUrlPathShouldBe(string expectedDownstreamPath) + { + _downstreamPath.ShouldBe(expectedDownstreamPath); + } + + public void Dispose() + { + _builder?.Dispose(); + _steps.Dispose(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/LoadBalancerTests.cs b/test/Ocelot.AcceptanceTests/LoadBalancerTests.cs new file mode 100644 index 000000000..a5c5e07cb --- /dev/null +++ b/test/Ocelot.AcceptanceTests/LoadBalancerTests.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using Consul; +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 LoadBalancerTests : IDisposable + { + private IWebHost _builderOne; + private IWebHost _builderTwo; + private readonly Steps _steps; + private int _counterOne; + private int _counterTwo; + private static readonly object _syncLock = new object(); + + public LoadBalancerTests() + { + _steps = new Steps(); + } + + [Fact] + public void should_use_service_discovery_and_load_balance_request() + { + var downstreamServiceOneUrl = "http://localhost:50881"; + var downstreamServiceTwoUrl = "http://localhost:50882"; + + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + LoadBalancer = "LeastConnection", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 50881 + }, + new FileHostAndPort + { + Host = "localhost", + Port = 50882 + } + } + } + }, + GlobalConfiguration = new FileGlobalConfiguration() + { + } + }; + + this.Given(x => x.GivenProductServiceOneIsRunning(downstreamServiceOneUrl, 200)) + .And(x => x.GivenProductServiceTwoIsRunning(downstreamServiceTwoUrl, 200)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimes("/", 50)) + .Then(x => x.ThenTheTwoServicesShouldHaveBeenCalledTimes(50)) + .And(x => x.ThenBothServicesCalledRealisticAmountOfTimes(24, 26)) + .BDDfy(); + } + + private void ThenOnlyOneServiceHasBeenCalled() + { + _counterOne.ShouldBe(10); + _counterTwo.ShouldBe(0); + } + + private void GivenIResetCounters() + { + _counterOne = 0; + _counterTwo = 0; + } + + private void ThenBothServicesCalledRealisticAmountOfTimes(int bottom, int top) + { + _counterOne.ShouldBeInRange(bottom, top); + _counterOne.ShouldBeInRange(bottom, top); + } + + private void ThenTheTwoServicesShouldHaveBeenCalledTimes(int expected) + { + var total = _counterOne + _counterTwo; + total.ShouldBe(expected); + } + + private void GivenProductServiceOneIsRunning(string url, int statusCode) + { + _builderOne = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(async context => + { + try + { + var response = string.Empty; + lock (_syncLock) + { + _counterOne++; + response = _counterOne.ToString(); + } + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(response); + } + catch (System.Exception exception) + { + await context.Response.WriteAsync(exception.StackTrace); + } + }); + }) + .Build(); + + _builderOne.Start(); + } + + private void GivenProductServiceTwoIsRunning(string url, int statusCode) + { + _builderTwo = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(async context => + { + try + { + var response = string.Empty; + lock (_syncLock) + { + _counterTwo++; + response = _counterTwo.ToString(); + } + + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(response); + } + catch (System.Exception exception) + { + await context.Response.WriteAsync(exception.StackTrace); + } + + }); + }) + .Build(); + + _builderTwo.Start(); + } + + public void Dispose() + { + _builderOne?.Dispose(); + _builderTwo?.Dispose(); + _steps.Dispose(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj index 6f4e1f9a1..ab7ae7ce2 100644 --- a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj +++ b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj @@ -1,53 +1,53 @@ - - - - 0.0.0-dev - netcoreapp2.0 - 2.0.0 - Ocelot.AcceptanceTests - Exe - Ocelot.AcceptanceTests - true - osx.10.11-x64;osx.10.12-x64;win7-x64;win10-x64 - false - false - false - - - - - PreserveNewest - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + 0.0.0-dev + netcoreapp2.0 + 2.0.0 + Ocelot.AcceptanceTests + Exe + Ocelot.AcceptanceTests + true + osx.10.11-x64;osx.10.12-x64;win7-x64;win10-x64 + false + false + false + + + + + PreserveNewest + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/Ocelot.AcceptanceTests/Properties/AssemblyInfo.cs b/test/Ocelot.AcceptanceTests/Properties/AssemblyInfo.cs index 2f3ceca82..7ad0f5bb4 100644 --- a/test/Ocelot.AcceptanceTests/Properties/AssemblyInfo.cs +++ b/test/Ocelot.AcceptanceTests/Properties/AssemblyInfo.cs @@ -1,19 +1,19 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Ocelot.AcceptanceTests")] -[assembly: AssemblyTrademark("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("f8c224fe-36be-45f5-9b0e-666d8f4a9b52")] +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Ocelot.AcceptanceTests")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("f8c224fe-36be-45f5-9b0e-666d8f4a9b52")] diff --git a/test/Ocelot.AcceptanceTests/QoSTests.cs b/test/Ocelot.AcceptanceTests/QoSTests.cs index e8a8e6a17..bf86455d6 100644 --- a/test/Ocelot.AcceptanceTests/QoSTests.cs +++ b/test/Ocelot.AcceptanceTests/QoSTests.cs @@ -1,207 +1,225 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Ocelot.Configuration.File; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.AcceptanceTests -{ - public class QoSTests : IDisposable - { - private IWebHost _brokenService; - private readonly Steps _steps; - private int _requestCount; - private IWebHost _workingService; - - public QoSTests() - { - _steps = new Steps(); - } - - [Fact] - public void should_open_circuit_breaker_then_close() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 51879, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - QoSOptions = new FileQoSOptions - { - ExceptionsAllowedBeforeBreaking = 1, - TimeoutValue = 500, - DurationOfBreak = 1000 - }, - - } - } - }; - - this.Given(x => x.GivenThereIsAPossiblyBrokenServiceRunningOn("http://localhost:51879", "Hello from Laura")) - .Given(x => _steps.GivenThereIsAConfiguration(configuration)) - .Given(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .Given(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) - .Given(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) - .Given(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) - .Given(x => x.GivenIWaitMilliseconds(3000)) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void open_circuit_should_not_effect_different_reRoute() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 51879, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - QoSOptions = new FileQoSOptions - { - ExceptionsAllowedBeforeBreaking = 1, - TimeoutValue = 500, - DurationOfBreak = 1000 - } - }, - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 51880, - UpstreamPathTemplate = "/working", - UpstreamHttpMethod = new List { "Get" }, - } - } - }; - - this.Given(x => x.GivenThereIsAPossiblyBrokenServiceRunningOn("http://localhost:51879", "Hello from Laura")) - .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51880/", 200, "Hello from Tom")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .And(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) - .And(x => _steps.WhenIGetUrlOnTheApiGateway("/working")) - .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Tom")) - .And(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) - .And(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) - .And(x => x.GivenIWaitMilliseconds(3000)) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - private void GivenIWaitMilliseconds(int ms) - { - Thread.Sleep(ms); - } - - private void GivenThereIsAPossiblyBrokenServiceRunningOn(string url, string responseBody) - { - _brokenService = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.Run(async context => - { - //circuit starts closed - if (_requestCount == 0) - { - _requestCount++; - context.Response.StatusCode = 200; - await context.Response.WriteAsync(responseBody); - return; - } - - //request one times out and polly throws exception, circuit opens - if (_requestCount == 1) - { - _requestCount++; - await Task.Delay(1000); - context.Response.StatusCode = 200; - return; - } - - //after break closes we return 200 OK - if (_requestCount == 2) - { - context.Response.StatusCode = 200; - await context.Response.WriteAsync(responseBody); - return; - } - }); - }) - .Build(); - - _brokenService.Start(); - } - - private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody) - { - _workingService = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.Run(async context => - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - }); - }) - .Build(); - - _workingService.Start(); - } - - public void Dispose() - { - _workingService?.Dispose(); - _brokenService?.Dispose(); - _steps.Dispose(); - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Ocelot.Configuration.File; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.AcceptanceTests +{ + public class QoSTests : IDisposable + { + private IWebHost _brokenService; + private readonly Steps _steps; + private int _requestCount; + private IWebHost _workingService; + + public QoSTests() + { + _steps = new Steps(); + } + + [Fact] + public void should_open_circuit_breaker_then_close() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + QoSOptions = new FileQoSOptions + { + ExceptionsAllowedBeforeBreaking = 1, + TimeoutValue = 500, + DurationOfBreak = 1000 + }, + + } + } + }; + + this.Given(x => x.GivenThereIsAPossiblyBrokenServiceRunningOn("http://localhost:51879", "Hello from Laura")) + .Given(x => _steps.GivenThereIsAConfiguration(configuration)) + .Given(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .Given(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) + .Given(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) + .Given(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) + .Given(x => x.GivenIWaitMilliseconds(3000)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [Fact] + public void open_circuit_should_not_effect_different_reRoute() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + QoSOptions = new FileQoSOptions + { + ExceptionsAllowedBeforeBreaking = 1, + TimeoutValue = 500, + DurationOfBreak = 1000 + } + }, + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51880, + } + }, + UpstreamPathTemplate = "/working", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAPossiblyBrokenServiceRunningOn("http://localhost:51879", "Hello from Laura")) + .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51880/", 200, "Hello from Tom")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .And(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) + .And(x => _steps.WhenIGetUrlOnTheApiGateway("/working")) + .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Tom")) + .And(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) + .And(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) + .And(x => x.GivenIWaitMilliseconds(3000)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + private void GivenIWaitMilliseconds(int ms) + { + Thread.Sleep(ms); + } + + private void GivenThereIsAPossiblyBrokenServiceRunningOn(string url, string responseBody) + { + _brokenService = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(async context => + { + //circuit starts closed + if (_requestCount == 0) + { + _requestCount++; + context.Response.StatusCode = 200; + await context.Response.WriteAsync(responseBody); + return; + } + + //request one times out and polly throws exception, circuit opens + if (_requestCount == 1) + { + _requestCount++; + await Task.Delay(1000); + context.Response.StatusCode = 200; + return; + } + + //after break closes we return 200 OK + if (_requestCount == 2) + { + context.Response.StatusCode = 200; + await context.Response.WriteAsync(responseBody); + return; + } + }); + }) + .Build(); + + _brokenService.Start(); + } + + private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody) + { + _workingService = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(async context => + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + }); + }) + .Build(); + + _workingService.Start(); + } + + public void Dispose() + { + _workingService?.Dispose(); + _brokenService?.Dispose(); + _steps.Dispose(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/RequestIdTests.cs b/test/Ocelot.AcceptanceTests/RequestIdTests.cs index 1c87bc9c5..444133d90 100644 --- a/test/Ocelot.AcceptanceTests/RequestIdTests.cs +++ b/test/Ocelot.AcceptanceTests/RequestIdTests.cs @@ -1,146 +1,164 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Primitives; -using Ocelot.Configuration.File; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.AcceptanceTests -{ - public class RequestIdTests : IDisposable - { - private IWebHost _builder; - private readonly Steps _steps; - - public RequestIdTests() - { - _steps = new Steps(); - } - - [Fact] - public void should_use_default_request_id_and_forward() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamPort = 51879, - DownstreamScheme = "http", - DownstreamHost = "localhost", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - RequestIdKey = _steps.RequestIdKey, - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheRequestIdIsReturned()) - .BDDfy(); - } - - [Fact] - public void should_use_request_id_and_forward() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamPort = 51879, - DownstreamScheme = "http", - DownstreamHost = "localhost", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - } - } - }; - - var requestId = Guid.NewGuid().ToString(); - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/", requestId)) - .Then(x => _steps.ThenTheRequestIdIsReturned(requestId)) - .BDDfy(); - } - - [Fact] - public void should_use_global_request_id_and_forward() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamPort = 51879, - DownstreamScheme = "http", - DownstreamHost = "localhost", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - } - }, - GlobalConfiguration = new FileGlobalConfiguration - { - RequestIdKey = _steps.RequestIdKey - } - }; - - var requestId = Guid.NewGuid().ToString(); - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/", requestId)) - .Then(x => _steps.ThenTheRequestIdIsReturned(requestId)) - .BDDfy(); - } - - private void GivenThereIsAServiceRunningOn(string url) - { - _builder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.Run(context => - { - StringValues requestId; - context.Request.Headers.TryGetValue(_steps.RequestIdKey, out requestId); - context.Response.Headers.Add(_steps.RequestIdKey, requestId.First()); - return Task.CompletedTask; - }); - }) - .Build(); - - _builder.Start(); - } - - public void Dispose() - { - _builder?.Dispose(); - _steps.Dispose(); - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; +using Ocelot.Configuration.File; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.AcceptanceTests +{ + public class RequestIdTests : IDisposable + { + private IWebHost _builder; + private readonly Steps _steps; + + public RequestIdTests() + { + _steps = new Steps(); + } + + [Fact] + public void should_use_default_request_id_and_forward() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + RequestIdKey = _steps.RequestIdKey, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheRequestIdIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_use_request_id_and_forward() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + var requestId = Guid.NewGuid().ToString(); + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/", requestId)) + .Then(x => _steps.ThenTheRequestIdIsReturned(requestId)) + .BDDfy(); + } + + [Fact] + public void should_use_global_request_id_and_forward() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + }, + GlobalConfiguration = new FileGlobalConfiguration + { + RequestIdKey = _steps.RequestIdKey + } + }; + + var requestId = Guid.NewGuid().ToString(); + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/", requestId)) + .Then(x => _steps.ThenTheRequestIdIsReturned(requestId)) + .BDDfy(); + } + + private void GivenThereIsAServiceRunningOn(string url) + { + _builder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(context => + { + StringValues requestId; + context.Request.Headers.TryGetValue(_steps.RequestIdKey, out requestId); + context.Response.Headers.Add(_steps.RequestIdKey, requestId.First()); + return Task.CompletedTask; + }); + }) + .Build(); + + _builder.Start(); + } + + public void Dispose() + { + _builder?.Dispose(); + _steps.Dispose(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs b/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs index 86815e98e..72569b972 100644 --- a/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs +++ b/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs @@ -1,76 +1,82 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Ocelot.Configuration.File; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.AcceptanceTests -{ - public class ReturnsErrorTests : IDisposable - { - private IWebHost _servicebuilder; - private readonly Steps _steps; - - public ReturnsErrorTests() - { - _steps = new Steps(); - } - - [Fact] - public void should_return_internal_server_error_if_downstream_service_returns_internal_server_error() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - DownstreamPort = 53876, - DownstreamScheme = "http", - DownstreamHost = "localhost" - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:53876")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.InternalServerError)) - .BDDfy(); - } - - private void GivenThereIsAServiceRunningOn(string url) - { - _servicebuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.Run(context => - { - throw new Exception("BLAMMMM"); - }); - }) - .Build(); - - _servicebuilder.Start(); - } - - public void Dispose() - { - _servicebuilder?.Dispose(); - _steps.Dispose(); - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Ocelot.Configuration.File; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.AcceptanceTests +{ + public class ReturnsErrorTests : IDisposable + { + private IWebHost _servicebuilder; + private readonly Steps _steps; + + public ReturnsErrorTests() + { + _steps = new Steps(); + } + + [Fact] + public void should_return_internal_server_error_if_downstream_service_returns_internal_server_error() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 53876, + } + }, + DownstreamScheme = "http", + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:53876")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.InternalServerError)) + .BDDfy(); + } + + private void GivenThereIsAServiceRunningOn(string url) + { + _servicebuilder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(context => + { + throw new Exception("BLAMMMM"); + }); + }) + .Build(); + + _servicebuilder.Start(); + } + + public void Dispose() + { + _servicebuilder?.Dispose(); + _steps.Dispose(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/RoutingTests.cs b/test/Ocelot.AcceptanceTests/RoutingTests.cs index 21c21c067..5fc72b272 100644 --- a/test/Ocelot.AcceptanceTests/RoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingTests.cs @@ -1,758 +1,920 @@ -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 RoutingTests : IDisposable - { - private IWebHost _builder; - private readonly Steps _steps; - private string _downstreamPath; - - public RoutingTests() - { - _steps = new Steps(); - } - - [Fact] - public void should_return_response_404_when_no_configuration_at_all() - { - this.Given(x => _steps.GivenThereIsAConfiguration(new FileConfiguration())) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_with_forward_slash_and_placeholder_only() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/{url}", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 51879, - UpstreamPathTemplate = "/{url}", - UpstreamHttpMethod = new List { "Get" }, - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/", "/", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_favouring_forward_slash_with_path_route() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/{url}", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 51880, - UpstreamPathTemplate = "/{url}", - UpstreamHttpMethod = new List { "Get" }, - }, - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 51879, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51880/", "/test", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/test")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_favouring_forward_slash() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/{url}", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 51880, - UpstreamPathTemplate = "/{url}", - UpstreamHttpMethod = new List { "Get" }, - }, - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 51879, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/", "/", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_favouring_forward_slash_route_because_it_is_first() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 51880, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - }, - new FileReRoute - { - DownstreamPathTemplate = "/{url}", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 51879, - UpstreamPathTemplate = "/{url}", - UpstreamHttpMethod = new List { "Get" }, - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51880/", "/", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_with_nothing_and_placeholder_only() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/{url}", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 51879, - UpstreamPathTemplate = "/{url}", - UpstreamHttpMethod = new List { "Get" }, - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_with_simple_url() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 51879, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void bug() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/v1/vacancy", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 51879, - UpstreamPathTemplate = "/vacancy/", - UpstreamHttpMethod = new List { "Options", "Put", "Get", "Post", "Delete" }, - ServiceName = "botCore", - LoadBalancer = "LeastConnection" - }, - new FileReRoute - { - DownstreamPathTemplate = "/api/v1/vacancy/{vacancyId}", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 51879, - UpstreamPathTemplate = "/vacancy/{vacancyId}", - UpstreamHttpMethod = new List { "Options", "Put", "Get", "Post", "Delete" }, - ServiceName = "botCore", - LoadBalancer = "LeastConnection" - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/v1/vacancy/1", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/vacancy/1")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_when_path_missing_forward_slash_as_first_char() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 51879, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_when_host_has_trailing_slash() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products", - DownstreamScheme = "http", - DownstreamHost = "localhost/", - DownstreamPort = 51879, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void should_return_ok_when_upstream_url_ends_with_forward_slash_but_template_does_not() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/products", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 51879, - UpstreamPathTemplate = "/products/", - UpstreamHttpMethod = new List { "Get" }, - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/products", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void should_return_not_found_when_upstream_url_ends_with_forward_slash_but_template_does_not() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/products", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 51879, - UpstreamPathTemplate = "/products", - UpstreamHttpMethod = new List { "Get" }, - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/products", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) - .BDDfy(); - } - - [Fact] - public void should_return_not_found() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/products", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 51879, - UpstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = new List { "Get" }, - QoSOptions = new FileQoSOptions() - { - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak = 5, - TimeoutValue = 5000 - } - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/products", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_with_complex_url() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/{productId}", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 51879, - UpstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = new List { "Get" }, - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products/1", 200, "Some Product")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products/1")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Some Product")) - .BDDfy(); - } - - - [Fact] - public void should_return_response_200_with_complex_url_that_starts_with_placeholder() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/{variantId}/products/{productId}", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 51879, - UpstreamPathTemplate = "/{variantId}/products/{productId}", - UpstreamHttpMethod = new List { "Get" }, - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/23/products/1", 200, "Some Product")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("23/products/1")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Some Product")) - .BDDfy(); - } - - - [Fact] - public void should_not_add_trailing_slash_to_downstream_url() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/{productId}", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 51879, - UpstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = new List { "Get" }, - } - } - }; - - this.Given(x => GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products/1", 200, "Some Product")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products/1")) - .Then(x => ThenTheDownstreamUrlPathShouldBe("/api/products/1")) - .BDDfy(); - } - - [Fact] - public void should_return_response_201_with_simple_url() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamHost = "localhost", - DownstreamPort = 51879, - DownstreamScheme = "http", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Post" }, - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 201, string.Empty)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenThePostHasContent("postContent")) - .When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Created)) - .BDDfy(); - } - - [Fact] - public void should_return_response_201_with_complex_query_string() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/newThing", - UpstreamPathTemplate = "/newThing", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 51879, - UpstreamHttpMethod = new List { "Get" }, - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/newThing", 200, "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=-")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_with_placeholder_for_final_url_path() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/{urlPath}", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 51879, - UpstreamPathTemplate = "/myApp1Name/api/{urlPath}", - UpstreamHttpMethod = new List { "Get" }, - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products/1", 200, "Some Product")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/myApp1Name/api/products/1")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Some Product")) - .BDDfy(); - } - - [Fact] - public void should_return_response_201_with_simple_url_and_multiple_upstream_http_method() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamHost = "localhost", - DownstreamPort = 51879, - DownstreamScheme = "http", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get", "Post" }, - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "", 201, string.Empty)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenThePostHasContent("postContent")) - .When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Created)) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_with_simple_url_and_any_upstream_http_method() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamHost = "localhost", - DownstreamPort = 51879, - DownstreamScheme = "http", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List(), - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void should_return_404_when_calling_upstream_route_with_no_matching_downstream_re_route_github_issue_134() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/v1/vacancy", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 51879, - UpstreamPathTemplate = "/vacancy/", - UpstreamHttpMethod = new List { "Options", "Put", "Get", "Post", "Delete" }, - ServiceName = "botCore", - LoadBalancer = "LeastConnection" - }, - new FileReRoute - { - DownstreamPathTemplate = "/api/v1/vacancy/{vacancyId}", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 51879, - UpstreamPathTemplate = "/vacancy/{vacancyId}", - UpstreamHttpMethod = new List { "Options", "Put", "Get", "Post", "Delete" }, - ServiceName = "botCore", - LoadBalancer = "LeastConnection" - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/v1/vacancy/1", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("api/vacancy/1")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) - .BDDfy(); - } - - - [Fact] - public void should_fix_145() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/{url}", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 51899, - UpstreamPathTemplate = "/platform/{url}", - UpstreamHttpMethod = new List { "Get" }, - QoSOptions = new FileQoSOptions { - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak = 10, - TimeoutValue = 5000 - } - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51899", "/api/swagger/lib/backbone-min.js", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/platform/swagger/lib/backbone-min.js")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .And(x => ThenTheDownstreamUrlPathShouldBe("/api/swagger/lib/backbone-min.js")) - .BDDfy(); - } - - private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody) - { - _builder = new WebHostBuilder() - .UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .Configure(app => - { - app.UsePathBase(basePath); - app.Run(async context => - { - _downstreamPath = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; - - if(_downstreamPath != basePath) - { - context.Response.StatusCode = statusCode; - 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(); - } - } -} +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 RoutingTests : IDisposable + { + private IWebHost _builder; + private readonly Steps _steps; + private string _downstreamPath; + + public RoutingTests() + { + _steps = new Steps(); + } + + [Fact] + public void should_return_response_404_when_no_configuration_at_all() + { + this.Given(x => _steps.GivenThereIsAConfiguration(new FileConfiguration())) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_with_forward_slash_and_placeholder_only() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/{url}", + DownstreamScheme = "http", + UpstreamPathTemplate = "/{url}", + UpstreamHttpMethod = new List { "Get" }, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + } + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/", "/", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_favouring_forward_slash_with_path_route() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/{url}", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51880, + } + }, + UpstreamPathTemplate = "/{url}", + UpstreamHttpMethod = new List { "Get" }, + }, + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51880/", "/test", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/test")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_favouring_forward_slash() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/{url}", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51880, + } + }, + UpstreamPathTemplate = "/{url}", + UpstreamHttpMethod = new List { "Get" }, + }, + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/", "/", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_favouring_forward_slash_route_because_it_is_first() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51880, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + }, + new FileReRoute + { + DownstreamPathTemplate = "/{url}", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + UpstreamPathTemplate = "/{url}", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51880/", "/", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_with_nothing_and_placeholder_only() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/{url}", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + UpstreamPathTemplate = "/{url}", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_with_simple_url() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [Fact] + public void bug() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/v1/vacancy", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + UpstreamPathTemplate = "/vacancy/", + UpstreamHttpMethod = new List { "Options", "Put", "Get", "Post", "Delete" }, + ServiceName = "botCore", + LoadBalancer = "LeastConnection" + }, + new FileReRoute + { + DownstreamPathTemplate = "/api/v1/vacancy/{vacancyId}", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + UpstreamPathTemplate = "/vacancy/{vacancyId}", + UpstreamHttpMethod = new List { "Options", "Put", "Get", "Post", "Delete" }, + ServiceName = "botCore", + LoadBalancer = "LeastConnection" + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/v1/vacancy/1", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/vacancy/1")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_when_path_missing_forward_slash_as_first_char() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_when_host_has_trailing_slash() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [Fact] + public void should_return_ok_when_upstream_url_ends_with_forward_slash_but_template_does_not() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/products", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + UpstreamPathTemplate = "/products/", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/products", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [Fact] + public void should_return_not_found_when_upstream_url_ends_with_forward_slash_but_template_does_not() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/products", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + UpstreamPathTemplate = "/products", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/products", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) + .BDDfy(); + } + + [Fact] + public void should_return_not_found() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/products", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + UpstreamPathTemplate = "/products/{productId}", + UpstreamHttpMethod = new List { "Get" }, + QoSOptions = new FileQoSOptions() + { + ExceptionsAllowedBeforeBreaking = 3, + DurationOfBreak = 5, + TimeoutValue = 5000 + } + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/products", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_with_complex_url() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/{productId}", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + UpstreamPathTemplate = "/products/{productId}", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products/1", 200, "Some Product")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products/1")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Some Product")) + .BDDfy(); + } + + + [Fact] + public void should_return_response_200_with_complex_url_that_starts_with_placeholder() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/{variantId}/products/{productId}", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + UpstreamPathTemplate = "/{variantId}/products/{productId}", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/23/products/1", 200, "Some Product")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("23/products/1")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Some Product")) + .BDDfy(); + } + + + [Fact] + public void should_not_add_trailing_slash_to_downstream_url() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/{productId}", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + UpstreamPathTemplate = "/products/{productId}", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products/1", 200, "Some Product")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products/1")) + .Then(x => ThenTheDownstreamUrlPathShouldBe("/api/products/1")) + .BDDfy(); + } + + [Fact] + public void should_return_response_201_with_simple_url() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Post" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 201, string.Empty)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenThePostHasContent("postContent")) + .When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Created)) + .BDDfy(); + } + + [Fact] + public void should_return_response_201_with_complex_query_string() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/newThing", + UpstreamPathTemplate = "/newThing", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/newThing", 200, "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=-")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_with_placeholder_for_final_url_path() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/{urlPath}", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + UpstreamPathTemplate = "/myApp1Name/api/{urlPath}", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products/1", 200, "Some Product")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/myApp1Name/api/products/1")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Some Product")) + .BDDfy(); + } + + [Fact] + public void should_return_response_201_with_simple_url_and_multiple_upstream_http_method() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get", "Post" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "", 201, string.Empty)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenThePostHasContent("postContent")) + .When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Created)) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_with_simple_url_and_any_upstream_http_method() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List(), + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [Fact] + public void should_return_404_when_calling_upstream_route_with_no_matching_downstream_re_route_github_issue_134() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/v1/vacancy", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + UpstreamPathTemplate = "/vacancy/", + UpstreamHttpMethod = new List { "Options", "Put", "Get", "Post", "Delete" }, + ServiceName = "botCore", + LoadBalancer = "LeastConnection" + }, + new FileReRoute + { + DownstreamPathTemplate = "/api/v1/vacancy/{vacancyId}", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + UpstreamPathTemplate = "/vacancy/{vacancyId}", + UpstreamHttpMethod = new List { "Options", "Put", "Get", "Post", "Delete" }, + ServiceName = "botCore", + LoadBalancer = "LeastConnection" + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/v1/vacancy/1", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("api/vacancy/1")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) + .BDDfy(); + } + + + [Fact] + public void should_fix_145() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/{url}", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51899, + } + }, + UpstreamPathTemplate = "/platform/{url}", + UpstreamHttpMethod = new List { "Get" }, + QoSOptions = new FileQoSOptions { + ExceptionsAllowedBeforeBreaking = 3, + DurationOfBreak = 10, + TimeoutValue = 5000 + } + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51899", "/api/swagger/lib/backbone-min.js", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/platform/swagger/lib/backbone-min.js")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .And(x => ThenTheDownstreamUrlPathShouldBe("/api/swagger/lib/backbone-min.js")) + .BDDfy(); + } + + private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody) + { + _builder = new WebHostBuilder() + .UseUrls(baseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .Configure(app => + { + app.UsePathBase(basePath); + app.Run(async context => + { + _downstreamPath = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; + + if(_downstreamPath != basePath) + { + context.Response.StatusCode = statusCode; + 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(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs index d6e9d862e..572c7126d 100644 --- a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs +++ b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs @@ -23,6 +23,8 @@ public class ServiceDiscoveryTests : IDisposable private int _counterOne; private int _counterTwo; private static readonly object _syncLock = new object(); + private IWebHost _builder; + private string _downstreamPath; public ServiceDiscoveryTests() { @@ -88,7 +90,7 @@ public void should_use_service_discovery_and_load_balance_request() this.Given(x => x.GivenProductServiceOneIsRunning(downstreamServiceOneUrl, 200)) .And(x => x.GivenProductServiceTwoIsRunning(downstreamServiceTwoUrl, 200)) - .And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) + .And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName)) .And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne, serviceEntryTwo)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) @@ -98,6 +100,62 @@ public void should_use_service_discovery_and_load_balance_request() .BDDfy(); } + //test from issue 213 + [Fact] + public void should_handle_request_to_consul_for_downstream_service_and_make_request() + { + var consulPort = 8505; + var serviceName = "web"; + var downstreamServiceOneUrl = "http://localhost:8080"; + var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; + var serviceEntryOne = new ServiceEntry() + { + Service = new AgentService() + { + Service = serviceName, + Address = "localhost", + Port = 8080, + ID = "web_90_0_2_224_8080", + Tags = new string[1]{"version-v1"} + }, + }; + + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/home", + DownstreamScheme = "http", + UpstreamPathTemplate = "/home", + UpstreamHttpMethod = new List { "Get", "Options" }, + ServiceName = serviceName, + LoadBalancer = "LeastConnection", + UseServiceDiscovery = true, + } + }, + GlobalConfiguration = new FileGlobalConfiguration() + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() + { + Host = "localhost", + Port = consulPort + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn(downstreamServiceOneUrl, "/api/home", 200, "Hello from Laura")) + .And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName)) + .And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/home")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + [Fact] public void should_send_request_to_service_after_it_becomes_available() { @@ -156,7 +214,7 @@ public void should_send_request_to_service_after_it_becomes_available() this.Given(x => x.GivenProductServiceOneIsRunning(downstreamServiceOneUrl, 200)) .And(x => x.GivenProductServiceTwoIsRunning(downstreamServiceTwoUrl, 200)) - .And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) + .And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName)) .And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne, serviceEntryTwo)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) @@ -217,7 +275,7 @@ private void GivenTheServicesAreRegisteredWithConsul(params ServiceEntry[] servi } } - private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url) + private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url, string serviceName) { _fakeConsulBuilder = new WebHostBuilder() .UseUrls(url) @@ -229,7 +287,7 @@ private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url) { app.Run(async context => { - if(context.Request.Path.Value == "/v1/health/service/product") + if(context.Request.Path.Value == $"/v1/health/service/{serviceName}") { await context.Response.WriteJsonAsync(_serviceEntries); } @@ -310,6 +368,37 @@ private void GivenProductServiceTwoIsRunning(string url, int statusCode) _builderTwo.Start(); } + private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody) + { + _builder = new WebHostBuilder() + .UseUrls(baseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .Configure(app => + { + app.UsePathBase(basePath); + app.Run(async context => + { + _downstreamPath = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; + + if(_downstreamPath != basePath) + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync("downstream path didnt match base path"); + } + else + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + } + }); + }) + .Build(); + + _builder.Start(); + } + public void Dispose() { _builderOne?.Dispose(); @@ -317,4 +406,4 @@ public void Dispose() _steps.Dispose(); } } -} +} \ No newline at end of file diff --git a/test/Ocelot.AcceptanceTests/StartupWithConsulAndCustomCacheHandle.cs b/test/Ocelot.AcceptanceTests/StartupWithConsulAndCustomCacheHandle.cs index 3b2f97d77..e48efcdb5 100644 --- a/test/Ocelot.AcceptanceTests/StartupWithConsulAndCustomCacheHandle.cs +++ b/test/Ocelot.AcceptanceTests/StartupWithConsulAndCustomCacheHandle.cs @@ -1,29 +1,29 @@ -using CacheManager.Core; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Ocelot.DependencyInjection; -using Ocelot.AcceptanceTests.Caching; - -namespace Ocelot.AcceptanceTests -{ - public class StartupWithConsulAndCustomCacheHandle : AcceptanceTestsStartup - { - public StartupWithConsulAndCustomCacheHandle(IHostingEnvironment env) : base(env) { } - - public override void ConfigureServices(IServiceCollection services) - { - services.AddOcelot(Configuration) - .AddCacheManager((x) => - { - x.WithMicrosoftLogging(log => - { - log.AddConsole(LogLevel.Debug); - }) - .WithJsonSerializer() - .WithHandle(typeof(InMemoryJsonHandle<>)); - }) - .AddStoreOcelotConfigurationInConsul(); - } - } -} +using CacheManager.Core; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Ocelot.DependencyInjection; +using Ocelot.AcceptanceTests.Caching; + +namespace Ocelot.AcceptanceTests +{ + public class StartupWithConsulAndCustomCacheHandle : AcceptanceTestsStartup + { + public StartupWithConsulAndCustomCacheHandle(IHostingEnvironment env) : base(env) { } + + public override void ConfigureServices(IServiceCollection services) + { + services.AddOcelot(Configuration) + .AddCacheManager((x) => + { + x.WithMicrosoftLogging(log => + { + log.AddConsole(LogLevel.Debug); + }) + .WithJsonSerializer() + .WithHandle(typeof(InMemoryJsonHandle<>)); + }) + .AddStoreOcelotConfigurationInConsul(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/StartupWithCustomCacheHandle.cs b/test/Ocelot.AcceptanceTests/StartupWithCustomCacheHandle.cs index 817ddc7fc..2570bc93f 100644 --- a/test/Ocelot.AcceptanceTests/StartupWithCustomCacheHandle.cs +++ b/test/Ocelot.AcceptanceTests/StartupWithCustomCacheHandle.cs @@ -1,28 +1,28 @@ -using CacheManager.Core; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Ocelot.DependencyInjection; -using Ocelot.AcceptanceTests.Caching; - -namespace Ocelot.AcceptanceTests -{ - public class StartupWithCustomCacheHandle : AcceptanceTestsStartup - { - public StartupWithCustomCacheHandle(IHostingEnvironment env) : base(env) { } - - public override void ConfigureServices(IServiceCollection services) - { - services.AddOcelot(Configuration) - .AddCacheManager((x) => - { - x.WithMicrosoftLogging(log => - { - log.AddConsole(LogLevel.Debug); - }) - .WithJsonSerializer() - .WithHandle(typeof(InMemoryJsonHandle<>)); - }); - } - } -} +using CacheManager.Core; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Ocelot.DependencyInjection; +using Ocelot.AcceptanceTests.Caching; + +namespace Ocelot.AcceptanceTests +{ + public class StartupWithCustomCacheHandle : AcceptanceTestsStartup + { + public StartupWithCustomCacheHandle(IHostingEnvironment env) : base(env) { } + + public override void ConfigureServices(IServiceCollection services) + { + services.AddOcelot(Configuration) + .AddCacheManager((x) => + { + x.WithMicrosoftLogging(log => + { + log.AddConsole(LogLevel.Debug); + }) + .WithJsonSerializer() + .WithHandle(typeof(InMemoryJsonHandle<>)); + }); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 7f260d5ff..bd41f007f 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -1,423 +1,429 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Threading; -using System.Threading.Tasks; -using CacheManager.Core; -using IdentityServer4.AccessTokenValidation; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using Ocelot.Configuration.File; -using Ocelot.Configuration.Repository; -using Ocelot.DependencyInjection; -using Ocelot.Middleware; -using Ocelot.ServiceDiscovery; -using Shouldly; -using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; -using Ocelot.AcceptanceTests.Caching; - -namespace Ocelot.AcceptanceTests -{ - public class Steps : IDisposable - { - private TestServer _ocelotServer; - private HttpClient _ocelotClient; - private HttpResponseMessage _response; - private HttpContent _postContent; - private BearerToken _token; - public HttpClient OcelotClient => _ocelotClient; - public string RequestIdKey = "OcRequestId"; - private readonly Random _random; - private IWebHostBuilder _webHostBuilder; - - public Steps() - { - _random = new Random(); - } - - public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) - { - var configurationPath = TestConfiguration.ConfigurationPath; - - var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); - - if (File.Exists(configurationPath)) - { - File.Delete(configurationPath); - } - - File.WriteAllText(configurationPath, jsonConfiguration); - } - - public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration, string configurationPath) - { - var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); - - if (File.Exists(configurationPath)) - { - File.Delete(configurationPath); - } - - File.WriteAllText(configurationPath, jsonConfiguration); - } - - /// - /// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step. - /// - public void GivenOcelotIsRunning() - { - _webHostBuilder = new WebHostBuilder(); - - _webHostBuilder.ConfigureServices(s => - { - s.AddSingleton(_webHostBuilder); - }); - - _ocelotServer = new TestServer(_webHostBuilder - .UseStartup()); - - _ocelotClient = _ocelotServer.CreateClient(); - } - - /// - /// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step. - /// - public void GivenOcelotIsRunning(Action options, string authenticationProviderKey) - { - _webHostBuilder = new WebHostBuilder(); - - _webHostBuilder.ConfigureServices(s => - { - s.AddSingleton(_webHostBuilder); - s.AddAuthentication() - .AddIdentityServerAuthentication(authenticationProviderKey, options); - }); - - _ocelotServer = new TestServer(_webHostBuilder - .UseStartup()); - - _ocelotClient = _ocelotServer.CreateClient(); - } - - public void ThenTheResponseHeaderIs(string key, string value) - { - var header = _response.Headers.GetValues(key); - header.First().ShouldBe(value); - } - - public void GivenOcelotIsRunningUsingJsonSerializedCache() - { - _webHostBuilder = new WebHostBuilder(); - - _webHostBuilder.ConfigureServices(s => - { - s.AddSingleton(_webHostBuilder); - }); - - _ocelotServer = new TestServer(_webHostBuilder - .UseStartup()); - - _ocelotClient = _ocelotServer.CreateClient(); - } - - public void GivenOcelotIsRunningUsingConsulToStoreConfig() - { - _webHostBuilder = new WebHostBuilder(); - - _webHostBuilder.ConfigureServices(s => - { - s.AddSingleton(_webHostBuilder); - }); - - _ocelotServer = new TestServer(_webHostBuilder - .UseStartup()); - - _ocelotClient = _ocelotServer.CreateClient(); - } - - public void GivenOcelotIsRunningUsingConsulToStoreConfigAndJsonSerializedCache() - { - _webHostBuilder = new WebHostBuilder(); - - _webHostBuilder.ConfigureServices(s => - { - s.AddSingleton(_webHostBuilder); - }); - - _ocelotServer = new TestServer(_webHostBuilder - .UseStartup()); - - _ocelotClient = _ocelotServer.CreateClient(); - } - - internal void ThenTheResponseShouldBe(FileConfiguration expected) - { - var response = JsonConvert.DeserializeObject(_response.Content.ReadAsStringAsync().Result); - - response.GlobalConfiguration.RequestIdKey.ShouldBe(expected.GlobalConfiguration.RequestIdKey); - response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Host); - response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Port); - - for (var i = 0; i < response.ReRoutes.Count; i++) - { - response.ReRoutes[i].DownstreamHost.ShouldBe(expected.ReRoutes[i].DownstreamHost); - response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expected.ReRoutes[i].DownstreamPathTemplate); - response.ReRoutes[i].DownstreamPort.ShouldBe(expected.ReRoutes[i].DownstreamPort); - response.ReRoutes[i].DownstreamScheme.ShouldBe(expected.ReRoutes[i].DownstreamScheme); - response.ReRoutes[i].UpstreamPathTemplate.ShouldBe(expected.ReRoutes[i].UpstreamPathTemplate); - response.ReRoutes[i].UpstreamHttpMethod.ShouldBe(expected.ReRoutes[i].UpstreamHttpMethod); - } - } - - /// - /// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step. - /// - public void GivenOcelotIsRunning(OcelotMiddlewareConfiguration ocelotMiddlewareConfig) - { - var builder = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile("configuration.json") - .AddEnvironmentVariables(); - - var configuration = builder.Build(); - _webHostBuilder = new WebHostBuilder(); - _webHostBuilder.ConfigureServices(s => - { - s.AddSingleton(_webHostBuilder); - }); - - _ocelotServer = new TestServer(_webHostBuilder - .UseConfiguration(configuration) - .ConfigureServices(s => - { - Action settings = (x) => - { - x.WithMicrosoftLogging(log => - { - log.AddConsole(LogLevel.Debug); - }) - .WithDictionaryHandle(); - }; - - s.AddOcelot(configuration); - }) - .ConfigureLogging(l => - { - l.AddConsole(); - l.AddDebug(); - }) - .Configure(a => - { - a.UseOcelot(ocelotMiddlewareConfig).Wait(); - })); - - _ocelotClient = _ocelotServer.CreateClient(); - } - - public void GivenIHaveAddedATokenToMyRequest() - { - _ocelotClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); - } - - public void GivenIHaveAToken(string url) - { - var tokenUrl = $"{url}/connect/token"; - var formData = new List> - { - new KeyValuePair("client_id", "client"), - new KeyValuePair("client_secret", "secret"), - new KeyValuePair("scope", "api"), - new KeyValuePair("username", "test"), - new KeyValuePair("password", "test"), - new KeyValuePair("grant_type", "password") - }; - var content = new FormUrlEncodedContent(formData); - - using (var httpClient = new HttpClient()) - { - var response = httpClient.PostAsync(tokenUrl, content).Result; - var responseContent = response.Content.ReadAsStringAsync().Result; - response.EnsureSuccessStatusCode(); - _token = JsonConvert.DeserializeObject(responseContent); - } - } - - public void GivenIHaveATokenForApiReadOnlyScope(string url) - { - var tokenUrl = $"{url}/connect/token"; - var formData = new List> - { - new KeyValuePair("client_id", "client"), - new KeyValuePair("client_secret", "secret"), - new KeyValuePair("scope", "api.readOnly"), - new KeyValuePair("username", "test"), - new KeyValuePair("password", "test"), - new KeyValuePair("grant_type", "password") - }; - var content = new FormUrlEncodedContent(formData); - - using (var httpClient = new HttpClient()) - { - var response = httpClient.PostAsync(tokenUrl, content).Result; - var responseContent = response.Content.ReadAsStringAsync().Result; - response.EnsureSuccessStatusCode(); - _token = JsonConvert.DeserializeObject(responseContent); - } - } - - public void GivenIHaveATokenForApi2(string url) - { - var tokenUrl = $"{url}/connect/token"; - var formData = new List> - { - new KeyValuePair("client_id", "client"), - new KeyValuePair("client_secret", "secret"), - new KeyValuePair("scope", "api2"), - new KeyValuePair("username", "test"), - new KeyValuePair("password", "test"), - new KeyValuePair("grant_type", "password") - }; - var content = new FormUrlEncodedContent(formData); - - using (var httpClient = new HttpClient()) - { - var response = httpClient.PostAsync(tokenUrl, content).Result; - var responseContent = response.Content.ReadAsStringAsync().Result; - response.EnsureSuccessStatusCode(); - _token = JsonConvert.DeserializeObject(responseContent); - } - } - - public void GivenIHaveAnOcelotToken(string adminPath) - { - var tokenUrl = $"{adminPath}/connect/token"; - var formData = new List> - { - new KeyValuePair("client_id", "admin"), - new KeyValuePair("client_secret", "secret"), - new KeyValuePair("scope", "admin"), - new KeyValuePair("username", "admin"), - new KeyValuePair("password", "admin"), - new KeyValuePair("grant_type", "password") - }; - var content = new FormUrlEncodedContent(formData); - - var response = _ocelotClient.PostAsync(tokenUrl, content).Result; - var responseContent = response.Content.ReadAsStringAsync().Result; - response.EnsureSuccessStatusCode(); - _token = JsonConvert.DeserializeObject(responseContent); - } - - public void VerifyIdentiryServerStarted(string url) - { - using (var httpClient = new HttpClient()) - { - var response = httpClient.GetAsync($"{url}/.well-known/openid-configuration").Result; - response.EnsureSuccessStatusCode(); - } - } - - public void WhenIGetUrlOnTheApiGateway(string url) - { - _response = _ocelotClient.GetAsync(url).Result; - } - - public void GivenIAddAHeader(string key, string value) - { - _ocelotClient.DefaultRequestHeaders.Add(key, value); - } - - public void WhenIGetUrlOnTheApiGatewayMultipleTimes(string url, int times) - { - var tasks = new Task[times]; - - for (int i = 0; i < times; i++) - { - var urlCopy = url; - tasks[i] = GetForServiceDiscoveryTest(urlCopy); - Thread.Sleep(_random.Next(40, 60)); - } - - Task.WaitAll(tasks); - } - - private async Task GetForServiceDiscoveryTest(string url) - { - var response = await _ocelotClient.GetAsync(url); - var content = await response.Content.ReadAsStringAsync(); - int count = int.Parse(content); - count.ShouldBeGreaterThan(0); - } - - public void WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit(string url, int times) - { - for (int i = 0; i < times; i++) - { - var clientId = "ocelotclient1"; - var request = new HttpRequestMessage(new HttpMethod("GET"), url); - request.Headers.Add("ClientId", clientId); - _response = _ocelotClient.SendAsync(request).Result; - } - } - - public void WhenIGetUrlOnTheApiGateway(string url, string requestId) - { - _ocelotClient.DefaultRequestHeaders.TryAddWithoutValidation(RequestIdKey, requestId); - - _response = _ocelotClient.GetAsync(url).Result; - } - - public void WhenIPostUrlOnTheApiGateway(string url) - { - _response = _ocelotClient.PostAsync(url, _postContent).Result; - } - - public void GivenThePostHasContent(string postcontent) - { - _postContent = new StringContent(postcontent); - } - - public void ThenTheResponseBodyShouldBe(string expectedBody) - { - _response.Content.ReadAsStringAsync().Result.ShouldBe(expectedBody); - } - - public void ThenTheStatusCodeShouldBe(HttpStatusCode expectedHttpStatusCode) - { - _response.StatusCode.ShouldBe(expectedHttpStatusCode); - } - - - public void ThenTheStatusCodeShouldBe(int expectedHttpStatusCode) - { - var responseStatusCode = (int)_response.StatusCode; - responseStatusCode.ShouldBe(expectedHttpStatusCode); - } - - public void Dispose() - { - _ocelotClient?.Dispose(); - _ocelotServer?.Dispose(); - } - - public void ThenTheRequestIdIsReturned() - { - _response.Headers.GetValues(RequestIdKey).First().ShouldNotBeNullOrEmpty(); - } - - public void ThenTheRequestIdIsReturned(string expected) - { - _response.Headers.GetValues(RequestIdKey).First().ShouldBe(expected); - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; +using CacheManager.Core; +using IdentityServer4.AccessTokenValidation; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Ocelot.Configuration.File; +using Ocelot.Configuration.Repository; +using Ocelot.DependencyInjection; +using Ocelot.Middleware; +using Ocelot.ServiceDiscovery; +using Shouldly; +using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; +using Ocelot.AcceptanceTests.Caching; + +namespace Ocelot.AcceptanceTests +{ + public class Steps : IDisposable + { + private TestServer _ocelotServer; + private HttpClient _ocelotClient; + private HttpResponseMessage _response; + private HttpContent _postContent; + private BearerToken _token; + public HttpClient OcelotClient => _ocelotClient; + public string RequestIdKey = "OcRequestId"; + private readonly Random _random; + private IWebHostBuilder _webHostBuilder; + + public Steps() + { + _random = new Random(); + } + + public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) + { + var configurationPath = TestConfiguration.ConfigurationPath; + + var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + + if (File.Exists(configurationPath)) + { + File.Delete(configurationPath); + } + + File.WriteAllText(configurationPath, jsonConfiguration); + } + + public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration, string configurationPath) + { + var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + + if (File.Exists(configurationPath)) + { + File.Delete(configurationPath); + } + + File.WriteAllText(configurationPath, jsonConfiguration); + } + + /// + /// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step. + /// + public void GivenOcelotIsRunning() + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder.ConfigureServices(s => + { + s.AddSingleton(_webHostBuilder); + }); + + _ocelotServer = new TestServer(_webHostBuilder + .UseStartup()); + + _ocelotClient = _ocelotServer.CreateClient(); + } + + /// + /// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step. + /// + public void GivenOcelotIsRunning(Action options, string authenticationProviderKey) + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder.ConfigureServices(s => + { + s.AddSingleton(_webHostBuilder); + s.AddAuthentication() + .AddIdentityServerAuthentication(authenticationProviderKey, options); + }); + + _ocelotServer = new TestServer(_webHostBuilder + .UseStartup()); + + _ocelotClient = _ocelotServer.CreateClient(); + } + + public void ThenTheResponseHeaderIs(string key, string value) + { + var header = _response.Headers.GetValues(key); + header.First().ShouldBe(value); + } + + public void GivenOcelotIsRunningUsingJsonSerializedCache() + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder.ConfigureServices(s => + { + s.AddSingleton(_webHostBuilder); + }); + + _ocelotServer = new TestServer(_webHostBuilder + .UseStartup()); + + _ocelotClient = _ocelotServer.CreateClient(); + } + + public void GivenOcelotIsRunningUsingConsulToStoreConfig() + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder.ConfigureServices(s => + { + s.AddSingleton(_webHostBuilder); + }); + + _ocelotServer = new TestServer(_webHostBuilder + .UseStartup()); + + _ocelotClient = _ocelotServer.CreateClient(); + } + + public void GivenOcelotIsRunningUsingConsulToStoreConfigAndJsonSerializedCache() + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder.ConfigureServices(s => + { + s.AddSingleton(_webHostBuilder); + }); + + _ocelotServer = new TestServer(_webHostBuilder + .UseStartup()); + + _ocelotClient = _ocelotServer.CreateClient(); + } + + internal void ThenTheResponseShouldBe(FileConfiguration expecteds) + { + var response = JsonConvert.DeserializeObject(_response.Content.ReadAsStringAsync().Result); + + response.GlobalConfiguration.RequestIdKey.ShouldBe(expecteds.GlobalConfiguration.RequestIdKey); + response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Host); + response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Port); + + for (var i = 0; i < response.ReRoutes.Count; i++) + { + for (var j = 0; j < response.ReRoutes[i].DownstreamHostAndPorts.Count; j++) + { + var result = response.ReRoutes[i].DownstreamHostAndPorts[j]; + var expected = expecteds.ReRoutes[i].DownstreamHostAndPorts[j]; + result.Host.ShouldBe(expected.Host); + result.Port.ShouldBe(expected.Port); + } + + response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expecteds.ReRoutes[i].DownstreamPathTemplate); + response.ReRoutes[i].DownstreamScheme.ShouldBe(expecteds.ReRoutes[i].DownstreamScheme); + response.ReRoutes[i].UpstreamPathTemplate.ShouldBe(expecteds.ReRoutes[i].UpstreamPathTemplate); + response.ReRoutes[i].UpstreamHttpMethod.ShouldBe(expecteds.ReRoutes[i].UpstreamHttpMethod); + } + } + + /// + /// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step. + /// + public void GivenOcelotIsRunning(OcelotMiddlewareConfiguration ocelotMiddlewareConfig) + { + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile("configuration.json") + .AddEnvironmentVariables(); + + var configuration = builder.Build(); + _webHostBuilder = new WebHostBuilder(); + _webHostBuilder.ConfigureServices(s => + { + s.AddSingleton(_webHostBuilder); + }); + + _ocelotServer = new TestServer(_webHostBuilder + .UseConfiguration(configuration) + .ConfigureServices(s => + { + Action settings = (x) => + { + x.WithMicrosoftLogging(log => + { + log.AddConsole(LogLevel.Debug); + }) + .WithDictionaryHandle(); + }; + + s.AddOcelot(configuration); + }) + .ConfigureLogging(l => + { + l.AddConsole(); + l.AddDebug(); + }) + .Configure(a => + { + a.UseOcelot(ocelotMiddlewareConfig).Wait(); + })); + + _ocelotClient = _ocelotServer.CreateClient(); + } + + public void GivenIHaveAddedATokenToMyRequest() + { + _ocelotClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); + } + + public void GivenIHaveAToken(string url) + { + var tokenUrl = $"{url}/connect/token"; + var formData = new List> + { + new KeyValuePair("client_id", "client"), + new KeyValuePair("client_secret", "secret"), + new KeyValuePair("scope", "api"), + new KeyValuePair("username", "test"), + new KeyValuePair("password", "test"), + new KeyValuePair("grant_type", "password") + }; + var content = new FormUrlEncodedContent(formData); + + using (var httpClient = new HttpClient()) + { + var response = httpClient.PostAsync(tokenUrl, content).Result; + var responseContent = response.Content.ReadAsStringAsync().Result; + response.EnsureSuccessStatusCode(); + _token = JsonConvert.DeserializeObject(responseContent); + } + } + + public void GivenIHaveATokenForApiReadOnlyScope(string url) + { + var tokenUrl = $"{url}/connect/token"; + var formData = new List> + { + new KeyValuePair("client_id", "client"), + new KeyValuePair("client_secret", "secret"), + new KeyValuePair("scope", "api.readOnly"), + new KeyValuePair("username", "test"), + new KeyValuePair("password", "test"), + new KeyValuePair("grant_type", "password") + }; + var content = new FormUrlEncodedContent(formData); + + using (var httpClient = new HttpClient()) + { + var response = httpClient.PostAsync(tokenUrl, content).Result; + var responseContent = response.Content.ReadAsStringAsync().Result; + response.EnsureSuccessStatusCode(); + _token = JsonConvert.DeserializeObject(responseContent); + } + } + + public void GivenIHaveATokenForApi2(string url) + { + var tokenUrl = $"{url}/connect/token"; + var formData = new List> + { + new KeyValuePair("client_id", "client"), + new KeyValuePair("client_secret", "secret"), + new KeyValuePair("scope", "api2"), + new KeyValuePair("username", "test"), + new KeyValuePair("password", "test"), + new KeyValuePair("grant_type", "password") + }; + var content = new FormUrlEncodedContent(formData); + + using (var httpClient = new HttpClient()) + { + var response = httpClient.PostAsync(tokenUrl, content).Result; + var responseContent = response.Content.ReadAsStringAsync().Result; + response.EnsureSuccessStatusCode(); + _token = JsonConvert.DeserializeObject(responseContent); + } + } + + public void GivenIHaveAnOcelotToken(string adminPath) + { + var tokenUrl = $"{adminPath}/connect/token"; + var formData = new List> + { + new KeyValuePair("client_id", "admin"), + new KeyValuePair("client_secret", "secret"), + new KeyValuePair("scope", "admin"), + new KeyValuePair("username", "admin"), + new KeyValuePair("password", "admin"), + new KeyValuePair("grant_type", "password") + }; + var content = new FormUrlEncodedContent(formData); + + var response = _ocelotClient.PostAsync(tokenUrl, content).Result; + var responseContent = response.Content.ReadAsStringAsync().Result; + response.EnsureSuccessStatusCode(); + _token = JsonConvert.DeserializeObject(responseContent); + } + + public void VerifyIdentiryServerStarted(string url) + { + using (var httpClient = new HttpClient()) + { + var response = httpClient.GetAsync($"{url}/.well-known/openid-configuration").Result; + response.EnsureSuccessStatusCode(); + } + } + + public void WhenIGetUrlOnTheApiGateway(string url) + { + _response = _ocelotClient.GetAsync(url).Result; + } + + public void GivenIAddAHeader(string key, string value) + { + _ocelotClient.DefaultRequestHeaders.Add(key, value); + } + + public void WhenIGetUrlOnTheApiGatewayMultipleTimes(string url, int times) + { + var tasks = new Task[times]; + + for (int i = 0; i < times; i++) + { + var urlCopy = url; + tasks[i] = GetForServiceDiscoveryTest(urlCopy); + Thread.Sleep(_random.Next(40, 60)); + } + + Task.WaitAll(tasks); + } + + private async Task GetForServiceDiscoveryTest(string url) + { + var response = await _ocelotClient.GetAsync(url); + var content = await response.Content.ReadAsStringAsync(); + int count = int.Parse(content); + count.ShouldBeGreaterThan(0); + } + + public void WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit(string url, int times) + { + for (int i = 0; i < times; i++) + { + var clientId = "ocelotclient1"; + var request = new HttpRequestMessage(new HttpMethod("GET"), url); + request.Headers.Add("ClientId", clientId); + _response = _ocelotClient.SendAsync(request).Result; + } + } + + public void WhenIGetUrlOnTheApiGateway(string url, string requestId) + { + _ocelotClient.DefaultRequestHeaders.TryAddWithoutValidation(RequestIdKey, requestId); + + _response = _ocelotClient.GetAsync(url).Result; + } + + public void WhenIPostUrlOnTheApiGateway(string url) + { + _response = _ocelotClient.PostAsync(url, _postContent).Result; + } + + public void GivenThePostHasContent(string postcontent) + { + _postContent = new StringContent(postcontent); + } + + public void ThenTheResponseBodyShouldBe(string expectedBody) + { + _response.Content.ReadAsStringAsync().Result.ShouldBe(expectedBody); + } + + public void ThenTheStatusCodeShouldBe(HttpStatusCode expectedHttpStatusCode) + { + _response.StatusCode.ShouldBe(expectedHttpStatusCode); + } + + + public void ThenTheStatusCodeShouldBe(int expectedHttpStatusCode) + { + var responseStatusCode = (int)_response.StatusCode; + responseStatusCode.ShouldBe(expectedHttpStatusCode); + } + + public void Dispose() + { + _ocelotClient?.Dispose(); + _ocelotServer?.Dispose(); + } + + public void ThenTheRequestIdIsReturned() + { + _response.Headers.GetValues(RequestIdKey).First().ShouldNotBeNullOrEmpty(); + } + + public void ThenTheRequestIdIsReturned(string expected) + { + _response.Headers.GetValues(RequestIdKey).First().ShouldBe(expected); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/TestConfiguration.cs b/test/Ocelot.AcceptanceTests/TestConfiguration.cs index 3265955d6..5f29d1bde 100644 --- a/test/Ocelot.AcceptanceTests/TestConfiguration.cs +++ b/test/Ocelot.AcceptanceTests/TestConfiguration.cs @@ -1,10 +1,10 @@ -using System; -using System.IO; - -namespace Ocelot.AcceptanceTests -{ - public static class TestConfiguration - { - public static string ConfigurationPath => Path.Combine(AppContext.BaseDirectory, "configuration.json"); - } -} +using System; +using System.IO; + +namespace Ocelot.AcceptanceTests +{ + public static class TestConfiguration + { + public static string ConfigurationPath => Path.Combine(AppContext.BaseDirectory, "configuration.json"); + } +} diff --git a/test/Ocelot.AcceptanceTests/TwoDownstreamServicesTests.cs b/test/Ocelot.AcceptanceTests/TwoDownstreamServicesTests.cs index e1efa9546..203f4e7c4 100644 --- a/test/Ocelot.AcceptanceTests/TwoDownstreamServicesTests.cs +++ b/test/Ocelot.AcceptanceTests/TwoDownstreamServicesTests.cs @@ -1,230 +1,191 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using Consul; -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 TwoDownstreamServicesTests : IDisposable - { - private IWebHost _builderOne; - private IWebHost _builderTwo; - private IWebHost _fakeConsulBuilder; - private readonly Steps _steps; - private readonly List _serviceEntries; - private string _downstreamPathOne; - private string _downstreamPathTwo; - - public TwoDownstreamServicesTests() - { - _steps = new Steps(); - _serviceEntries = new List(); - } - - [Fact] - public void should_fix_issue_194() - { - var consulPort = 8503; - var downstreamServiceOneUrl = "http://localhost:8362"; - var downstreamServiceTwoUrl = "http://localhost:8330"; - var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; - -// http://localhost:8362/api/user/info?id=1 is ok -// http://localhost:3164/api/user/info?id=1 is ok -// http://localhost:8330/api/product/info?id=1 is ok -// http://localhost:3164/api/product/info?id=1 is 404 - -// is my configuration.json -// { -// "ReRoutes": [ -// //{ -// // "DownstreamPathTemplate": "/{product}", -// // "DownstreamScheme": "http", -// // "UpstreamPathTemplate": "/{product}", -// // "UpstreamHttpMethod": [ "Get", "Post" ], -// // "ServiceName": "api-product", -// // "LoadBalancer": "LeastConnection", -// // "UseServiceDiscovery": true -// //}, -// //{ -// // "DownstreamPathTemplate": "/{user}", -// // "DownstreamScheme": "http", -// // "UpstreamPathTemplate": "/{user}", -// // "UpstreamHttpMethod": [ "Get", "Post" ], -// // "ServiceName": "api-user", -// // "LoadBalancer": "LeastConnection", -// // "UseServiceDiscovery": true -// //}, -// { -// "DownstreamPathTemplate": "/api/user/{user}", -// "DownstreamScheme": "http", -// "DownstreamHost": "localhost", -// "DownstreamPort": 8362, -// "UpstreamPathTemplate": "/api/user/{user}", -// "UpstreamHttpMethod": [ "Get" ] -// }, -// { -// "DownstreamPathTemplate": "/api/product/{product}", -// "DownstreamScheme": "http", -// "DownstreamHost": "localhost", -// "DownstreamPort": 8330, -// "UpstreamPathTemplate": "//api/product/{product}", -// "UpstreamHttpMethod": [ "Get" ] -// } -// ], -// "GlobalConfiguration": { -// "ServiceDiscoveryProvider": { -// "Host": "localhost", -// "Port": 8500 -// } -// } -// } - - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/user/{user}", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 8362, - UpstreamPathTemplate = "/api/user/{user}", - UpstreamHttpMethod = new List { "Get" }, - }, - new FileReRoute - { - DownstreamPathTemplate = "/api/product/{product}", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 8330, - UpstreamPathTemplate = "/api/product/{product}", - UpstreamHttpMethod = new List { "Get" }, - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = consulPort - } - } - }; - - this.Given(x => x.GivenProductServiceOneIsRunning(downstreamServiceOneUrl, "/api/user/info", 200, "user")) - .And(x => x.GivenProductServiceTwoIsRunning(downstreamServiceTwoUrl, "/api/product/info", 200, "product")) - .And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/api/user/info?id=1")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("user")) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/api/product/info?id=1")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("product")) - .BDDfy(); - } - - private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url) - { - _fakeConsulBuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.Run(async context => - { - if(context.Request.Path.Value == "/v1/health/service/product") - { - await context.Response.WriteJsonAsync(_serviceEntries); - } - }); - }) - .Build(); - - _fakeConsulBuilder.Start(); - } - - private void GivenProductServiceOneIsRunning(string baseUrl, string basePath, int statusCode, string responseBody) - { - _builderOne = new WebHostBuilder() - .UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .Configure(app => - { - app.UsePathBase(basePath); - app.Run(async context => - { - _downstreamPathOne = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; - - if(_downstreamPathOne != basePath) - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync("downstream path didnt match base path"); - } - else - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - } - }); - }) - .Build(); - - _builderOne.Start(); - } - - private void GivenProductServiceTwoIsRunning(string baseUrl, string basePath, int statusCode, string responseBody) - { - _builderTwo = new WebHostBuilder() - .UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .Configure(app => - { - app.UsePathBase(basePath); - app.Run(async context => - { - _downstreamPathTwo = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; - - if(_downstreamPathTwo != basePath) - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync("downstream path didnt match base path"); - } - else - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - } - }); - }) - .Build(); - - _builderTwo.Start(); - } - - public void Dispose() - { - _builderOne?.Dispose(); - _builderTwo?.Dispose(); - _steps.Dispose(); - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using Consul; +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 TwoDownstreamServicesTests : IDisposable + { + private IWebHost _builderOne; + private IWebHost _builderTwo; + private IWebHost _fakeConsulBuilder; + private readonly Steps _steps; + private readonly List _serviceEntries; + private string _downstreamPathOne; + private string _downstreamPathTwo; + + public TwoDownstreamServicesTests() + { + _steps = new Steps(); + _serviceEntries = new List(); + } + + [Fact] + public void should_fix_issue_194() + { + var consulPort = 8503; + var downstreamServiceOneUrl = "http://localhost:8362"; + var downstreamServiceTwoUrl = "http://localhost:8330"; + var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; + + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/user/{user}", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 8362, + } + }, + UpstreamPathTemplate = "/api/user/{user}", + UpstreamHttpMethod = new List { "Get" }, + }, + new FileReRoute + { + DownstreamPathTemplate = "/api/product/{product}", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 8330, + } + }, + UpstreamPathTemplate = "/api/product/{product}", + UpstreamHttpMethod = new List { "Get" }, + } + }, + GlobalConfiguration = new FileGlobalConfiguration() + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() + { + Host = "localhost", + Port = consulPort + } + } + }; + + this.Given(x => x.GivenProductServiceOneIsRunning(downstreamServiceOneUrl, "/api/user/info", 200, "user")) + .And(x => x.GivenProductServiceTwoIsRunning(downstreamServiceTwoUrl, "/api/product/info", 200, "product")) + .And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/api/user/info?id=1")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("user")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/api/product/info?id=1")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("product")) + .BDDfy(); + } + + private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url) + { + _fakeConsulBuilder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(async context => + { + if(context.Request.Path.Value == "/v1/health/service/product") + { + await context.Response.WriteJsonAsync(_serviceEntries); + } + }); + }) + .Build(); + + _fakeConsulBuilder.Start(); + } + + private void GivenProductServiceOneIsRunning(string baseUrl, string basePath, int statusCode, string responseBody) + { + _builderOne = new WebHostBuilder() + .UseUrls(baseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .Configure(app => + { + app.UsePathBase(basePath); + app.Run(async context => + { + _downstreamPathOne = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; + + if(_downstreamPathOne != basePath) + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync("downstream path didnt match base path"); + } + else + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + } + }); + }) + .Build(); + + _builderOne.Start(); + } + + private void GivenProductServiceTwoIsRunning(string baseUrl, string basePath, int statusCode, string responseBody) + { + _builderTwo = new WebHostBuilder() + .UseUrls(baseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .Configure(app => + { + app.UsePathBase(basePath); + app.Run(async context => + { + _downstreamPathTwo = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; + + if(_downstreamPathTwo != basePath) + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync("downstream path didnt match base path"); + } + else + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + } + }); + }) + .Build(); + + _builderTwo.Start(); + } + + public void Dispose() + { + _builderOne?.Dispose(); + _builderTwo?.Dispose(); + _steps.Dispose(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/appsettings.json b/test/Ocelot.AcceptanceTests/appsettings.json index df0788dea..6d9b7c55b 100644 --- a/test/Ocelot.AcceptanceTests/appsettings.json +++ b/test/Ocelot.AcceptanceTests/appsettings.json @@ -1,10 +1,10 @@ -{ - "Logging": { - "IncludeScopes": true, - "LogLevel": { - "Default": "Error", - "System": "Error", - "Microsoft": "Error" - } - } -} +{ + "Logging": { + "IncludeScopes": true, + "LogLevel": { + "Default": "Error", + "System": "Error", + "Microsoft": "Error" + } + } +} diff --git a/test/Ocelot.AcceptanceTests/configuration.json b/test/Ocelot.AcceptanceTests/configuration.json index ba64677a3..027025b08 100755 --- a/test/Ocelot.AcceptanceTests/configuration.json +++ b/test/Ocelot.AcceptanceTests/configuration.json @@ -1 +1,59 @@ -{"ReRoutes":[{"DownstreamPathTemplate":"41879/","UpstreamPathTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ApiName":null,"RequireHttps":false,"AllowedScopes":[],"ApiSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null,"RateLimitOptions":{"ClientWhitelist":[],"EnableRateLimiting":false,"Period":null,"PeriodTimespan":0.0,"Limit":0}}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"AdministrationPath":null,"RateLimitOptions":{"ClientIdHeader":"ClientId","QuotaExceededMessage":null,"RateLimitCounterPrefix":"ocelot","DisableRateLimitHeaders":false,"HttpStatusCode":429}}} \ No newline at end of file +{ + "ReRoutes": [ + { + "DownstreamPathTemplate": "41879/", + "UpstreamPathTemplate": "/", + "UpstreamHttpMethod": "Get", + "AuthenticationOptions": { + "Provider": null, + "ProviderRootUrl": null, + "ApiName": null, + "RequireHttps": false, + "AllowedScopes": [], + "ApiSecret": null + }, + "AddHeadersToRequest": {}, + "AddClaimsToRequest": {}, + "RouteClaimsRequirement": {}, + "AddQueriesToRequest": {}, + "RequestIdKey": null, + "FileCacheOptions": { + "TtlSeconds": 0 + }, + "ReRouteIsCaseSensitive": false, + "ServiceName": null, + "DownstreamScheme": "http", + "DownstreamHost": "localhost", + "DownstreamPort": 41879, + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 0, + "DurationOfBreak": 0, + "TimeoutValue": 0 + }, + "LoadBalancer": null, + "RateLimitOptions": { + "ClientWhitelist": [], + "EnableRateLimiting": false, + "Period": null, + "PeriodTimespan": 0, + "Limit": 0 + } + } + ], + "GlobalConfiguration": { + "RequestIdKey": null, + "ServiceDiscoveryProvider": { + "Provider": null, + "Host": null, + "Port": 0 + }, + "AdministrationPath": null, + "RateLimitOptions": { + "ClientIdHeader": "ClientId", + "QuotaExceededMessage": null, + "RateLimitCounterPrefix": "ocelot", + "DisableRateLimitHeaders": false, + "HttpStatusCode": 429 + } + } +} \ No newline at end of file diff --git a/test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj b/test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj index 232305d40..ac2b01913 100644 --- a/test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj +++ b/test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj @@ -1,24 +1,24 @@ - - - - 0.0.0-dev - netcoreapp2.0 - 2.0.0 - Ocelot.Benchmarks - Exe - Ocelot.Benchmarks - osx.10.11-x64;osx.10.12-x64;win7-x64;win10-x64 - false - false - false - - - - - - - - - - - + + + + 0.0.0-dev + netcoreapp2.0 + 2.0.0 + Ocelot.Benchmarks + Exe + Ocelot.Benchmarks + osx.10.11-x64;osx.10.12-x64;win7-x64;win10-x64 + false + false + false + + + + + + + + + + + diff --git a/test/Ocelot.Benchmarks/Program.cs b/test/Ocelot.Benchmarks/Program.cs index 66efb0404..231a816bb 100644 --- a/test/Ocelot.Benchmarks/Program.cs +++ b/test/Ocelot.Benchmarks/Program.cs @@ -1,17 +1,17 @@ -using BenchmarkDotNet.Running; - -namespace Ocelot.Benchmarks -{ - public class Program - { - public static void Main(string[] args) - { - var switcher = new BenchmarkSwitcher(new[] { - typeof(UrlPathToUrlPathTemplateMatcherBenchmarks), - - }); - - switcher.Run(args); - } - } -} +using BenchmarkDotNet.Running; + +namespace Ocelot.Benchmarks +{ + public class Program + { + public static void Main(string[] args) + { + var switcher = new BenchmarkSwitcher(new[] { + typeof(UrlPathToUrlPathTemplateMatcherBenchmarks), + + }); + + switcher.Run(args); + } + } +} diff --git a/test/Ocelot.Benchmarks/Properties/AssemblyInfo.cs b/test/Ocelot.Benchmarks/Properties/AssemblyInfo.cs index b10c2d2f6..e39a657b5 100644 --- a/test/Ocelot.Benchmarks/Properties/AssemblyInfo.cs +++ b/test/Ocelot.Benchmarks/Properties/AssemblyInfo.cs @@ -1,19 +1,19 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Ocelot.Benchmarks")] -[assembly: AssemblyTrademark("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("106b49e6-95f6-4a7b-b81c-96bfa74af035")] +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Ocelot.Benchmarks")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("106b49e6-95f6-4a7b-b81c-96bfa74af035")] diff --git a/test/Ocelot.Benchmarks/UrlPathToUrlPathTemplateMatcherBenchmarks.cs b/test/Ocelot.Benchmarks/UrlPathToUrlPathTemplateMatcherBenchmarks.cs index 40dc50369..853fd7e58 100644 --- a/test/Ocelot.Benchmarks/UrlPathToUrlPathTemplateMatcherBenchmarks.cs +++ b/test/Ocelot.Benchmarks/UrlPathToUrlPathTemplateMatcherBenchmarks.cs @@ -1,40 +1,40 @@ -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Columns; -using BenchmarkDotNet.Configs; -using Ocelot.DownstreamRouteFinder.UrlMatcher; - -namespace Ocelot.Benchmarks -{ - [Config(typeof(UrlPathToUrlPathTemplateMatcherBenchmarks))] - public class UrlPathToUrlPathTemplateMatcherBenchmarks : ManualConfig - { - private RegExUrlMatcher _urlPathMatcher; - private string _downstreamUrlPath; - private string _downstreamUrlPathTemplate; - - public UrlPathToUrlPathTemplateMatcherBenchmarks() - { - Add(StatisticColumn.AllStatistics); - } - - [Setup] - public void SetUp() - { - _urlPathMatcher = new RegExUrlMatcher(); - _downstreamUrlPath = "api/product/products/1/variants/?soldout=false"; - _downstreamUrlPathTemplate = "api/product/products/{productId}/variants/"; - } - - [Benchmark] - public void Benchmark1() - { - _urlPathMatcher.Match(_downstreamUrlPath, _downstreamUrlPathTemplate); - } - - [Benchmark] - public void Benchmark2() - { - _urlPathMatcher.Match(_downstreamUrlPath, _downstreamUrlPathTemplate); - } - } +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Configs; +using Ocelot.DownstreamRouteFinder.UrlMatcher; + +namespace Ocelot.Benchmarks +{ + [Config(typeof(UrlPathToUrlPathTemplateMatcherBenchmarks))] + public class UrlPathToUrlPathTemplateMatcherBenchmarks : ManualConfig + { + private RegExUrlMatcher _urlPathMatcher; + private string _downstreamUrlPath; + private string _downstreamUrlPathTemplate; + + public UrlPathToUrlPathTemplateMatcherBenchmarks() + { + Add(StatisticColumn.AllStatistics); + } + + [Setup] + public void SetUp() + { + _urlPathMatcher = new RegExUrlMatcher(); + _downstreamUrlPath = "api/product/products/1/variants/?soldout=false"; + _downstreamUrlPathTemplate = "api/product/products/{productId}/variants/"; + } + + [Benchmark] + public void Benchmark1() + { + _urlPathMatcher.Match(_downstreamUrlPath, _downstreamUrlPathTemplate); + } + + [Benchmark] + public void Benchmark2() + { + _urlPathMatcher.Match(_downstreamUrlPath, _downstreamUrlPathTemplate); + } + } } \ No newline at end of file diff --git a/test/Ocelot.IntegrationTests/AdministrationTests.cs b/test/Ocelot.IntegrationTests/AdministrationTests.cs index b7c2813e5..44f394c0b 100644 --- a/test/Ocelot.IntegrationTests/AdministrationTests.cs +++ b/test/Ocelot.IntegrationTests/AdministrationTests.cs @@ -1,415 +1,469 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; -using Newtonsoft.Json; -using Ocelot.Cache; -using Ocelot.Configuration.File; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -[assembly: CollectionBehavior(DisableTestParallelization = true)] -namespace Ocelot.IntegrationTests -{ - public class AdministrationTests : IDisposable - { - private readonly HttpClient _httpClient; - private readonly HttpClient _httpClientTwo; - private HttpResponseMessage _response; - private IWebHost _builder; - private IWebHostBuilder _webHostBuilder; - private readonly string _ocelotBaseUrl; - private BearerToken _token; - private IWebHostBuilder _webHostBuilderTwo; - private IWebHost _builderTwo; - - public AdministrationTests() - { - _httpClient = new HttpClient(); - _httpClientTwo = new HttpClient(); - _ocelotBaseUrl = "http://localhost:5000"; - _httpClient.BaseAddress = new Uri(_ocelotBaseUrl); - } - - [Fact] - public void should_return_response_401_with_call_re_routes_controller() - { - var configuration = new FileConfiguration(); - - this.Given(x => GivenThereIsAConfiguration(configuration)) - .And(x => GivenOcelotIsRunning()) - .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized)) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_with_call_re_routes_controller() - { - var configuration = new FileConfiguration(); - - this.Given(x => GivenThereIsAConfiguration(configuration)) - .And(x => GivenOcelotIsRunning()) - .And(x => GivenIHaveAnOcelotToken("/administration")) - .And(x => GivenIHaveAddedATokenToMyRequest()) - .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .BDDfy(); - } - - [Fact] - public void should_be_able_to_use_token_from_ocelot_a_on_ocelot_b() - { - var configuration = new FileConfiguration(); - - this.Given(x => GivenThereIsAConfiguration(configuration)) - .And(x => GivenIdentityServerSigningEnvironmentalVariablesAreSet()) - .And(x => GivenOcelotIsRunning()) - .And(x => GivenIHaveAnOcelotToken("/administration")) - .And(x => GivenAnotherOcelotIsRunning("http://localhost:5007")) - .When(x => WhenIGetUrlOnTheSecondOcelot("/administration/configuration")) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .BDDfy(); - } - - [Fact] - public void should_return_file_configuration() - { - var configuration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - RequestIdKey = "RequestId", - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider - { - Host = "127.0.0.1", - } - - }, - ReRoutes = new List() - { - new FileReRoute() - { - DownstreamHost = "localhost", - DownstreamPort = 80, - DownstreamScheme = "https", - DownstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/", - FileCacheOptions = new FileCacheOptions - { - TtlSeconds = 10, - Region = "Geoff" - } - }, - new FileReRoute() - { - DownstreamHost = "localhost", - DownstreamPort = 80, - DownstreamScheme = "https", - DownstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/test", - FileCacheOptions = new FileCacheOptions - { - TtlSeconds = 10, - Region = "Dave" - } - } - } - }; - - this.Given(x => GivenThereIsAConfiguration(configuration)) - .And(x => GivenOcelotIsRunning()) - .And(x => GivenIHaveAnOcelotToken("/administration")) - .And(x => GivenIHaveAddedATokenToMyRequest()) - .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => ThenTheResponseShouldBe(configuration)) - .BDDfy(); - } - - [Fact] - public void should_get_file_configuration_edit_and_post_updated_version() - { - var initialConfiguration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - }, - ReRoutes = new List() - { - new FileReRoute() - { - DownstreamHost = "localhost", - DownstreamPort = 80, - DownstreamScheme = "https", - DownstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/" - }, - new FileReRoute() - { - DownstreamHost = "localhost", - DownstreamPort = 80, - DownstreamScheme = "https", - DownstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/test" - } - } - }; - - var updatedConfiguration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - }, - ReRoutes = new List() - { - new FileReRoute() - { - DownstreamHost = "127.0.0.1", - DownstreamPort = 80, - DownstreamScheme = "http", - DownstreamPathTemplate = "/geoffrey", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/" - }, - new FileReRoute() - { - DownstreamHost = "123.123.123", - DownstreamPort = 443, - DownstreamScheme = "https", - DownstreamPathTemplate = "/blooper/{productId}", - UpstreamHttpMethod = new List { "post" }, - UpstreamPathTemplate = "/test" - } - } - }; - - this.Given(x => GivenThereIsAConfiguration(initialConfiguration)) - .And(x => GivenOcelotIsRunning()) - .And(x => GivenIHaveAnOcelotToken("/administration")) - .And(x => GivenIHaveAddedATokenToMyRequest()) - .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) - .When(x => WhenIPostOnTheApiGateway("/administration/configuration", updatedConfiguration)) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => ThenTheResponseShouldBe(updatedConfiguration)) - .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) - .And(x => ThenTheResponseShouldBe(updatedConfiguration)) - .BDDfy(); - } - - [Fact] - public void should_clear_region() - { - var initialConfiguration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - }, - ReRoutes = new List() - { - new FileReRoute() - { - DownstreamHost = "localhost", - DownstreamPort = 80, - DownstreamScheme = "https", - DownstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/", - FileCacheOptions = new FileCacheOptions - { - TtlSeconds = 10 - } - }, - new FileReRoute() - { - DownstreamHost = "localhost", - DownstreamPort = 80, - DownstreamScheme = "https", - DownstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/test", - FileCacheOptions = new FileCacheOptions - { - TtlSeconds = 10 - } - } - } - }; - - var regionToClear = "gettest"; - - this.Given(x => GivenThereIsAConfiguration(initialConfiguration)) - .And(x => GivenOcelotIsRunning()) - .And(x => GivenIHaveAnOcelotToken("/administration")) - .And(x => GivenIHaveAddedATokenToMyRequest()) - .When(x => WhenIDeleteOnTheApiGateway($"/administration/outputcache/{regionToClear}")) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.NoContent)) - .BDDfy(); - } - - private void GivenAnotherOcelotIsRunning(string baseUrl) - { - _httpClientTwo.BaseAddress = new Uri(baseUrl); - - _webHostBuilderTwo = new WebHostBuilder() - .UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureServices(x => { - x.AddSingleton(_webHostBuilderTwo); - }) - .UseStartup(); - - _builderTwo = _webHostBuilderTwo.Build(); - - _builderTwo.Start(); - } - - private void GivenIdentityServerSigningEnvironmentalVariablesAreSet() - { - Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE", "idsrv3test.pfx"); - Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD", "idsrv3test"); - } - - private void WhenIGetUrlOnTheSecondOcelot(string url) - { - _httpClientTwo.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); - _response = _httpClientTwo.GetAsync(url).Result; - } - - private void WhenIPostOnTheApiGateway(string url, FileConfiguration updatedConfiguration) - { - var json = JsonConvert.SerializeObject(updatedConfiguration); - var content = new StringContent(json); - content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); - _response = _httpClient.PostAsync(url, content).Result; - } - - private void ThenTheResponseShouldBe(List expected) - { - var content = _response.Content.ReadAsStringAsync().Result; - var result = JsonConvert.DeserializeObject(content); - result.Value.ShouldBe(expected); - } - - private void ThenTheResponseShouldBe(FileConfiguration expected) - { - var response = JsonConvert.DeserializeObject(_response.Content.ReadAsStringAsync().Result); - - response.GlobalConfiguration.RequestIdKey.ShouldBe(expected.GlobalConfiguration.RequestIdKey); - response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Host); - response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Port); - - for (var i = 0; i < response.ReRoutes.Count; i++) - { - response.ReRoutes[i].DownstreamHost.ShouldBe(expected.ReRoutes[i].DownstreamHost); - response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expected.ReRoutes[i].DownstreamPathTemplate); - response.ReRoutes[i].DownstreamPort.ShouldBe(expected.ReRoutes[i].DownstreamPort); - response.ReRoutes[i].DownstreamScheme.ShouldBe(expected.ReRoutes[i].DownstreamScheme); - response.ReRoutes[i].UpstreamPathTemplate.ShouldBe(expected.ReRoutes[i].UpstreamPathTemplate); - response.ReRoutes[i].UpstreamHttpMethod.ShouldBe(expected.ReRoutes[i].UpstreamHttpMethod); - } - } - - private void GivenIHaveAddedATokenToMyRequest() - { - _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); - } - - private void GivenIHaveAnOcelotToken(string adminPath) - { - var tokenUrl = $"{adminPath}/connect/token"; - var formData = new List> - { - new KeyValuePair("client_id", "admin"), - new KeyValuePair("client_secret", "secret"), - new KeyValuePair("scope", "admin"), - new KeyValuePair("grant_type", "client_credentials") - }; - var content = new FormUrlEncodedContent(formData); - - var response = _httpClient.PostAsync(tokenUrl, content).Result; - var responseContent = response.Content.ReadAsStringAsync().Result; - response.EnsureSuccessStatusCode(); - _token = JsonConvert.DeserializeObject(responseContent); - var configPath = $"{adminPath}/.well-known/openid-configuration"; - response = _httpClient.GetAsync(configPath).Result; - response.EnsureSuccessStatusCode(); - } - - private void GivenOcelotIsRunning() - { - _webHostBuilder = new WebHostBuilder() - .UseUrls(_ocelotBaseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureServices(x => { - x.AddSingleton(_webHostBuilder); - }) - .UseStartup(); - - _builder = _webHostBuilder.Build(); - - _builder.Start(); - } - - private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) - { - var configurationPath = $"{Directory.GetCurrentDirectory()}/configuration.json"; - - var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); - - if (File.Exists(configurationPath)) - { - File.Delete(configurationPath); - } - - File.WriteAllText(configurationPath, jsonConfiguration); - - var text = File.ReadAllText(configurationPath); - - configurationPath = $"{AppContext.BaseDirectory}/configuration.json"; - - if (File.Exists(configurationPath)) - { - File.Delete(configurationPath); - } - - File.WriteAllText(configurationPath, jsonConfiguration); - - text = File.ReadAllText(configurationPath); - } - - private void WhenIGetUrlOnTheApiGateway(string url) - { - _response = _httpClient.GetAsync(url).Result; - } - - private void WhenIDeleteOnTheApiGateway(string url) - { - _response = _httpClient.DeleteAsync(url).Result; - } - - private void ThenTheStatusCodeShouldBe(HttpStatusCode expectedHttpStatusCode) - { - _response.StatusCode.ShouldBe(expectedHttpStatusCode); - } - - public void Dispose() - { - Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE", ""); - Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD", ""); - _builder?.Dispose(); - _httpClient?.Dispose(); - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json; +using Ocelot.Cache; +using Ocelot.Configuration.File; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +[assembly: CollectionBehavior(DisableTestParallelization = true)] +namespace Ocelot.IntegrationTests +{ + public class AdministrationTests : IDisposable + { + private readonly HttpClient _httpClient; + private readonly HttpClient _httpClientTwo; + private HttpResponseMessage _response; + private IWebHost _builder; + private IWebHostBuilder _webHostBuilder; + private readonly string _ocelotBaseUrl; + private BearerToken _token; + private IWebHostBuilder _webHostBuilderTwo; + private IWebHost _builderTwo; + + public AdministrationTests() + { + _httpClient = new HttpClient(); + _httpClientTwo = new HttpClient(); + _ocelotBaseUrl = "http://localhost:5000"; + _httpClient.BaseAddress = new Uri(_ocelotBaseUrl); + } + + [Fact] + public void should_return_response_401_with_call_re_routes_controller() + { + var configuration = new FileConfiguration(); + + this.Given(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized)) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_with_call_re_routes_controller() + { + var configuration = new FileConfiguration(); + + this.Given(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .And(x => GivenIHaveAnOcelotToken("/administration")) + .And(x => GivenIHaveAddedATokenToMyRequest()) + .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .BDDfy(); + } + + [Fact] + public void should_be_able_to_use_token_from_ocelot_a_on_ocelot_b() + { + var configuration = new FileConfiguration(); + + this.Given(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenIdentityServerSigningEnvironmentalVariablesAreSet()) + .And(x => GivenOcelotIsRunning()) + .And(x => GivenIHaveAnOcelotToken("/administration")) + .And(x => GivenAnotherOcelotIsRunning("http://localhost:5007")) + .When(x => WhenIGetUrlOnTheSecondOcelot("/administration/configuration")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .BDDfy(); + } + + [Fact] + public void should_return_file_configuration() + { + var configuration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + RequestIdKey = "RequestId", + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Host = "127.0.0.1", + } + + }, + ReRoutes = new List() + { + new FileReRoute() + { + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 80, + } + }, + DownstreamScheme = "https", + DownstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "get" }, + UpstreamPathTemplate = "/", + FileCacheOptions = new FileCacheOptions + { + TtlSeconds = 10, + Region = "Geoff" + } + }, + new FileReRoute() + { + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 80, + } + }, + DownstreamScheme = "https", + DownstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "get" }, + UpstreamPathTemplate = "/test", + FileCacheOptions = new FileCacheOptions + { + TtlSeconds = 10, + Region = "Dave" + } + } + } + }; + + this.Given(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .And(x => GivenIHaveAnOcelotToken("/administration")) + .And(x => GivenIHaveAddedATokenToMyRequest()) + .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => ThenTheResponseShouldBe(configuration)) + .BDDfy(); + } + + [Fact] + public void should_get_file_configuration_edit_and_post_updated_version() + { + var initialConfiguration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + }, + ReRoutes = new List() + { + new FileReRoute() + { + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 80, + } + }, + DownstreamScheme = "https", + DownstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "get" }, + UpstreamPathTemplate = "/" + }, + new FileReRoute() + { + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 80, + } + }, + DownstreamScheme = "https", + DownstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "get" }, + UpstreamPathTemplate = "/test" + } + } + }; + + var updatedConfiguration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + }, + ReRoutes = new List() + { + new FileReRoute() + { + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 80, + } + }, + DownstreamScheme = "http", + DownstreamPathTemplate = "/geoffrey", + UpstreamHttpMethod = new List { "get" }, + UpstreamPathTemplate = "/" + }, + new FileReRoute() + { + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "123.123.123", + Port = 443, + } + }, + DownstreamScheme = "https", + DownstreamPathTemplate = "/blooper/{productId}", + UpstreamHttpMethod = new List { "post" }, + UpstreamPathTemplate = "/test" + } + } + }; + + this.Given(x => GivenThereIsAConfiguration(initialConfiguration)) + .And(x => GivenOcelotIsRunning()) + .And(x => GivenIHaveAnOcelotToken("/administration")) + .And(x => GivenIHaveAddedATokenToMyRequest()) + .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) + .When(x => WhenIPostOnTheApiGateway("/administration/configuration", updatedConfiguration)) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => ThenTheResponseShouldBe(updatedConfiguration)) + .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) + .And(x => ThenTheResponseShouldBe(updatedConfiguration)) + .BDDfy(); + } + + [Fact] + public void should_clear_region() + { + var initialConfiguration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + }, + ReRoutes = new List() + { + new FileReRoute() + { + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 80, + } + }, + DownstreamScheme = "https", + DownstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "get" }, + UpstreamPathTemplate = "/", + FileCacheOptions = new FileCacheOptions + { + TtlSeconds = 10 + } + }, + new FileReRoute() + { + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 80, + } + }, + DownstreamScheme = "https", + DownstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "get" }, + UpstreamPathTemplate = "/test", + FileCacheOptions = new FileCacheOptions + { + TtlSeconds = 10 + } + } + } + }; + + var regionToClear = "gettest"; + + this.Given(x => GivenThereIsAConfiguration(initialConfiguration)) + .And(x => GivenOcelotIsRunning()) + .And(x => GivenIHaveAnOcelotToken("/administration")) + .And(x => GivenIHaveAddedATokenToMyRequest()) + .When(x => WhenIDeleteOnTheApiGateway($"/administration/outputcache/{regionToClear}")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.NoContent)) + .BDDfy(); + } + + private void GivenAnotherOcelotIsRunning(string baseUrl) + { + _httpClientTwo.BaseAddress = new Uri(baseUrl); + + _webHostBuilderTwo = new WebHostBuilder() + .UseUrls(baseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .ConfigureServices(x => { + x.AddSingleton(_webHostBuilderTwo); + }) + .UseStartup(); + + _builderTwo = _webHostBuilderTwo.Build(); + + _builderTwo.Start(); + } + + private void GivenIdentityServerSigningEnvironmentalVariablesAreSet() + { + Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE", "idsrv3test.pfx"); + Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD", "idsrv3test"); + } + + private void WhenIGetUrlOnTheSecondOcelot(string url) + { + _httpClientTwo.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); + _response = _httpClientTwo.GetAsync(url).Result; + } + + private void WhenIPostOnTheApiGateway(string url, FileConfiguration updatedConfiguration) + { + var json = JsonConvert.SerializeObject(updatedConfiguration); + var content = new StringContent(json); + content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + _response = _httpClient.PostAsync(url, content).Result; + } + + private void ThenTheResponseShouldBe(List expected) + { + var content = _response.Content.ReadAsStringAsync().Result; + var result = JsonConvert.DeserializeObject(content); + result.Value.ShouldBe(expected); + } + + private void ThenTheResponseShouldBe(FileConfiguration expecteds) + { + var response = JsonConvert.DeserializeObject(_response.Content.ReadAsStringAsync().Result); + + response.GlobalConfiguration.RequestIdKey.ShouldBe(expecteds.GlobalConfiguration.RequestIdKey); + response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Host); + response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Port); + + for (var i = 0; i < response.ReRoutes.Count; i++) + { + for (var j = 0; j < response.ReRoutes[i].DownstreamHostAndPorts.Count; j++) + { + var result = response.ReRoutes[i].DownstreamHostAndPorts[j]; + var expected = expecteds.ReRoutes[i].DownstreamHostAndPorts[j]; + result.Host.ShouldBe(expected.Host); + result.Port.ShouldBe(expected.Port); + } + + response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expecteds.ReRoutes[i].DownstreamPathTemplate); + response.ReRoutes[i].DownstreamScheme.ShouldBe(expecteds.ReRoutes[i].DownstreamScheme); + response.ReRoutes[i].UpstreamPathTemplate.ShouldBe(expecteds.ReRoutes[i].UpstreamPathTemplate); + response.ReRoutes[i].UpstreamHttpMethod.ShouldBe(expecteds.ReRoutes[i].UpstreamHttpMethod); + } + } + + private void GivenIHaveAddedATokenToMyRequest() + { + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); + } + + private void GivenIHaveAnOcelotToken(string adminPath) + { + var tokenUrl = $"{adminPath}/connect/token"; + var formData = new List> + { + new KeyValuePair("client_id", "admin"), + new KeyValuePair("client_secret", "secret"), + new KeyValuePair("scope", "admin"), + new KeyValuePair("grant_type", "client_credentials") + }; + var content = new FormUrlEncodedContent(formData); + + var response = _httpClient.PostAsync(tokenUrl, content).Result; + var responseContent = response.Content.ReadAsStringAsync().Result; + response.EnsureSuccessStatusCode(); + _token = JsonConvert.DeserializeObject(responseContent); + var configPath = $"{adminPath}/.well-known/openid-configuration"; + response = _httpClient.GetAsync(configPath).Result; + response.EnsureSuccessStatusCode(); + } + + private void GivenOcelotIsRunning() + { + _webHostBuilder = new WebHostBuilder() + .UseUrls(_ocelotBaseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .ConfigureServices(x => { + x.AddSingleton(_webHostBuilder); + }) + .UseStartup(); + + _builder = _webHostBuilder.Build(); + + _builder.Start(); + } + + private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) + { + var configurationPath = $"{Directory.GetCurrentDirectory()}/configuration.json"; + + var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + + if (File.Exists(configurationPath)) + { + File.Delete(configurationPath); + } + + File.WriteAllText(configurationPath, jsonConfiguration); + + var text = File.ReadAllText(configurationPath); + + configurationPath = $"{AppContext.BaseDirectory}/configuration.json"; + + if (File.Exists(configurationPath)) + { + File.Delete(configurationPath); + } + + File.WriteAllText(configurationPath, jsonConfiguration); + + text = File.ReadAllText(configurationPath); + } + + private void WhenIGetUrlOnTheApiGateway(string url) + { + _response = _httpClient.GetAsync(url).Result; + } + + private void WhenIDeleteOnTheApiGateway(string url) + { + _response = _httpClient.DeleteAsync(url).Result; + } + + private void ThenTheStatusCodeShouldBe(HttpStatusCode expectedHttpStatusCode) + { + _response.StatusCode.ShouldBe(expectedHttpStatusCode); + } + + public void Dispose() + { + Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE", ""); + Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD", ""); + _builder?.Dispose(); + _httpClient?.Dispose(); + } + } +} diff --git a/test/Ocelot.IntegrationTests/BearerToken.cs b/test/Ocelot.IntegrationTests/BearerToken.cs index efd35c334..02180844e 100644 --- a/test/Ocelot.IntegrationTests/BearerToken.cs +++ b/test/Ocelot.IntegrationTests/BearerToken.cs @@ -1,16 +1,16 @@ -using Newtonsoft.Json; - -namespace Ocelot.IntegrationTests -{ - class BearerToken - { - [JsonProperty("access_token")] - public string AccessToken { get; set; } - - [JsonProperty("expires_in")] - public int ExpiresIn { get; set; } - - [JsonProperty("token_type")] - public string TokenType { get; set; } - } -} +using Newtonsoft.Json; + +namespace Ocelot.IntegrationTests +{ + class BearerToken + { + [JsonProperty("access_token")] + public string AccessToken { get; set; } + + [JsonProperty("expires_in")] + public int ExpiresIn { get; set; } + + [JsonProperty("token_type")] + public string TokenType { get; set; } + } +} diff --git a/test/Ocelot.IntegrationTests/IntegrationTestsStartup.cs b/test/Ocelot.IntegrationTests/IntegrationTestsStartup.cs index 91923d6e3..8f1906d03 100644 --- a/test/Ocelot.IntegrationTests/IntegrationTestsStartup.cs +++ b/test/Ocelot.IntegrationTests/IntegrationTestsStartup.cs @@ -1,51 +1,51 @@ -using System; -using CacheManager.Core; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Ocelot.DependencyInjection; -using Ocelot.Middleware; -using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; - -namespace Ocelot.IntegrationTests -{ - public class IntegrationTestsStartup - { - public IntegrationTestsStartup(IHostingEnvironment env) - { - var builder = new ConfigurationBuilder() - .SetBasePath(env.ContentRootPath) - .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) - .AddJsonFile("configuration.json") - .AddEnvironmentVariables(); - - Configuration = builder.Build(); - } - - public IConfiguration Configuration { get; } - - public void ConfigureServices(IServiceCollection services) - { - Action settings = (x) => - { - x.WithMicrosoftLogging(log => - { - log.AddConsole(LogLevel.Debug); - }) - .WithDictionaryHandle(); - }; - - services.AddOcelot(Configuration) - .AddCacheManager(settings) - .AddAdministration("/administration", "secret"); - } - - public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) - { - app.UseOcelot().Wait(); - } - } +using System; +using CacheManager.Core; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Ocelot.DependencyInjection; +using Ocelot.Middleware; +using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; + +namespace Ocelot.IntegrationTests +{ + public class IntegrationTestsStartup + { + public IntegrationTestsStartup(IHostingEnvironment env) + { + var builder = new ConfigurationBuilder() + .SetBasePath(env.ContentRootPath) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) + .AddJsonFile("configuration.json") + .AddEnvironmentVariables(); + + Configuration = builder.Build(); + } + + public IConfiguration Configuration { get; } + + public void ConfigureServices(IServiceCollection services) + { + Action settings = (x) => + { + x.WithMicrosoftLogging(log => + { + log.AddConsole(LogLevel.Debug); + }) + .WithDictionaryHandle(); + }; + + services.AddOcelot(Configuration) + .AddCacheManager(settings) + .AddAdministration("/administration", "secret"); + } + + public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) + { + app.UseOcelot().Wait(); + } + } } \ No newline at end of file diff --git a/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj b/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj index bf5b2fe4e..6e1fdb4b9 100644 --- a/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj +++ b/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj @@ -1,46 +1,46 @@ - - - 0.0.0-dev - netcoreapp2.0 - 2.0.0 - Ocelot.IntegrationTests - Exe - Ocelot.IntegrationTests - true - win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64 - false - false - false - - - - PreserveNewest - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + 0.0.0-dev + netcoreapp2.0 + 2.0.0 + Ocelot.IntegrationTests + Exe + Ocelot.IntegrationTests + true + win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64 + false + false + false + + + + PreserveNewest + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/Ocelot.IntegrationTests/Properties/AssemblyInfo.cs b/test/Ocelot.IntegrationTests/Properties/AssemblyInfo.cs index 5535cd703..6ddcdad32 100644 --- a/test/Ocelot.IntegrationTests/Properties/AssemblyInfo.cs +++ b/test/Ocelot.IntegrationTests/Properties/AssemblyInfo.cs @@ -1,19 +1,19 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Ocelot.IntegrationTests")] -[assembly: AssemblyTrademark("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("d4575572-99ca-4530-8737-c296eda326f8")] +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Ocelot.IntegrationTests")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("d4575572-99ca-4530-8737-c296eda326f8")] diff --git a/test/Ocelot.IntegrationTests/RaftStartup.cs b/test/Ocelot.IntegrationTests/RaftStartup.cs index 2b5d37c65..70a91e2c6 100644 --- a/test/Ocelot.IntegrationTests/RaftStartup.cs +++ b/test/Ocelot.IntegrationTests/RaftStartup.cs @@ -1,52 +1,52 @@ -using System; -using System.IO; -using System.Linq; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using Ocelot.DependencyInjection; -using Ocelot.Middleware; -using Ocelot.Raft; -using Rafty.Concensus; -using Rafty.FiniteStateMachine; -using Rafty.Infrastructure; -using Rafty.Log; -using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; - -namespace Ocelot.IntegrationTests -{ - public class RaftStartup - { - public RaftStartup(IHostingEnvironment env) - { - var builder = new ConfigurationBuilder() - .SetBasePath(env.ContentRootPath) - .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) - .AddJsonFile("peers.json", optional: true, reloadOnChange: true) - .AddJsonFile("configuration.json") - .AddEnvironmentVariables(); - - Configuration = builder.Build(); - } - - public IConfiguration Configuration { get; } - - public virtual void ConfigureServices(IServiceCollection services) - { - services - .AddOcelot(Configuration) - .AddAdministration("/administration", "secret") - .AddRafty(); - } - - public virtual void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) - { - app.UseOcelot().Wait(); - } - } -} +using System; +using System.IO; +using System.Linq; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Ocelot.DependencyInjection; +using Ocelot.Middleware; +using Ocelot.Raft; +using Rafty.Concensus; +using Rafty.FiniteStateMachine; +using Rafty.Infrastructure; +using Rafty.Log; +using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; + +namespace Ocelot.IntegrationTests +{ + public class RaftStartup + { + public RaftStartup(IHostingEnvironment env) + { + var builder = new ConfigurationBuilder() + .SetBasePath(env.ContentRootPath) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) + .AddJsonFile("peers.json", optional: true, reloadOnChange: true) + .AddJsonFile("configuration.json") + .AddEnvironmentVariables(); + + Configuration = builder.Build(); + } + + public IConfiguration Configuration { get; } + + public virtual void ConfigureServices(IServiceCollection services) + { + services + .AddOcelot(Configuration) + .AddAdministration("/administration", "secret") + .AddRafty(); + } + + public virtual void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) + { + app.UseOcelot().Wait(); + } + } +} diff --git a/test/Ocelot.IntegrationTests/RaftTests.cs b/test/Ocelot.IntegrationTests/RaftTests.cs index 528b6d209..2e1752de6 100644 --- a/test/Ocelot.IntegrationTests/RaftTests.cs +++ b/test/Ocelot.IntegrationTests/RaftTests.cs @@ -1,431 +1,372 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; -using Newtonsoft.Json; -using Ocelot.Configuration.File; -using Ocelot.Raft; -using Rafty.Concensus; -using Rafty.FiniteStateMachine; -using Rafty.Infrastructure; -using Shouldly; -using Xunit; -using static Rafty.Infrastructure.Wait; -using Microsoft.Data.Sqlite; - -namespace Ocelot.IntegrationTests -{ - public class RaftTests : IDisposable - { - private List _builders; - private List _webHostBuilders; - private List _threads; - private FilePeers _peers; - private HttpClient _httpClient; - private HttpClient _httpClientForAssertions; - private string _ocelotBaseUrl; - private BearerToken _token; - private HttpResponseMessage _response; - private static object _lock = new object(); - - public RaftTests() - { - _httpClientForAssertions = new HttpClient(); - _httpClient = new HttpClient(); - _ocelotBaseUrl = "http://localhost:5000"; - _httpClient.BaseAddress = new Uri(_ocelotBaseUrl); - _webHostBuilders = new List(); - _builders = new List(); - _threads = new List(); - } - public void Dispose() - { - foreach (var builder in _builders) - { - builder?.Dispose(); - } - - foreach (var peer in _peers.Peers) - { - File.Delete(peer.HostAndPort.Replace("/","").Replace(":","")); - File.Delete($"{peer.HostAndPort.Replace("/","").Replace(":","")}.db"); - } - } - - [Fact] - public void should_persist_command_to_five_servers() - { - var configuration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - } - }; - - var updatedConfiguration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - }, - ReRoutes = new List() - { - new FileReRoute() - { - DownstreamHost = "127.0.0.1", - DownstreamPort = 80, - DownstreamScheme = "http", - DownstreamPathTemplate = "/geoffrey", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/" - }, - new FileReRoute() - { - DownstreamHost = "123.123.123", - DownstreamPort = 443, - DownstreamScheme = "https", - DownstreamPathTemplate = "/blooper/{productId}", - UpstreamHttpMethod = new List { "post" }, - UpstreamPathTemplate = "/test" - } - } - }; - - var command = new UpdateFileConfiguration(updatedConfiguration); - GivenThereIsAConfiguration(configuration); - GivenFiveServersAreRunning(); - GivenALeaderIsElected(); - GivenIHaveAnOcelotToken("/administration"); - WhenISendACommandIntoTheCluster(command); - ThenTheCommandIsReplicatedToAllStateMachines(command); - } - - [Fact] - public void should_persist_command_to_five_servers_when_using_administration_api() - { - var configuration = new FileConfiguration - { - }; - - var updatedConfiguration = new FileConfiguration - { - ReRoutes = new List() - { - new FileReRoute() - { - DownstreamHost = "127.0.0.1", - DownstreamPort = 80, - DownstreamScheme = "http", - DownstreamPathTemplate = "/geoffrey", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/" - }, - new FileReRoute() - { - DownstreamHost = "123.123.123", - DownstreamPort = 443, - DownstreamScheme = "https", - DownstreamPathTemplate = "/blooper/{productId}", - UpstreamHttpMethod = new List { "post" }, - UpstreamPathTemplate = "/test" - } - } - }; - - var command = new UpdateFileConfiguration(updatedConfiguration); - GivenThereIsAConfiguration(configuration); - GivenFiveServersAreRunning(); - GivenALeaderIsElected(); - GivenIHaveAnOcelotToken("/administration"); - GivenIHaveAddedATokenToMyRequest(); - WhenIPostOnTheApiGateway("/administration/configuration", updatedConfiguration); - ThenTheCommandIsReplicatedToAllStateMachines(command); - } - - private void WhenISendACommandIntoTheCluster(UpdateFileConfiguration command) - { - var p = _peers.Peers.First(); - var json = JsonConvert.SerializeObject(command,new JsonSerializerSettings() { - TypeNameHandling = TypeNameHandling.All - }); - var httpContent = new StringContent(json); - httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); - using(var httpClient = new HttpClient()) - { - httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); - var response = httpClient.PostAsync($"{p.HostAndPort}/administration/raft/command", httpContent).GetAwaiter().GetResult(); - response.EnsureSuccessStatusCode(); - var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); - var result = JsonConvert.DeserializeObject>(content); - result.Command.Configuration.ReRoutes.Count.ShouldBe(2); - } - - //dirty sleep to make sure command replicated... - var stopwatch = Stopwatch.StartNew(); - while(stopwatch.ElapsedMilliseconds < 10000) - { - - } - } - - private void ThenTheCommandIsReplicatedToAllStateMachines(UpdateFileConfiguration expected) - { - //dirty sleep to give a chance to replicate... - var stopwatch = Stopwatch.StartNew(); - while(stopwatch.ElapsedMilliseconds < 2000) - { - - } - - bool CommandCalledOnAllStateMachines() - { - try - { - var passed = 0; - foreach (var peer in _peers.Peers) - { - var path = $"{peer.HostAndPort.Replace("/","").Replace(":","")}.db"; - using(var connection = new SqliteConnection($"Data Source={path};")) - { - connection.Open(); - var sql = @"select count(id) from logs"; - using(var command = new SqliteCommand(sql, connection)) - { - var index = Convert.ToInt32(command.ExecuteScalar()); - index.ShouldBe(1); - } - } - _httpClientForAssertions.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); - var result = _httpClientForAssertions.GetAsync($"{peer.HostAndPort}/administration/configuration").Result; - var json = result.Content.ReadAsStringAsync().Result; - var response = JsonConvert.DeserializeObject(json, new JsonSerializerSettings{TypeNameHandling = TypeNameHandling.All}); - response.GlobalConfiguration.RequestIdKey.ShouldBe(expected.Configuration.GlobalConfiguration.RequestIdKey); - response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expected.Configuration.GlobalConfiguration.ServiceDiscoveryProvider.Host); - response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expected.Configuration.GlobalConfiguration.ServiceDiscoveryProvider.Port); - - for (var i = 0; i < response.ReRoutes.Count; i++) - { - response.ReRoutes[i].DownstreamHost.ShouldBe(expected.Configuration.ReRoutes[i].DownstreamHost); - response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expected.Configuration.ReRoutes[i].DownstreamPathTemplate); - response.ReRoutes[i].DownstreamPort.ShouldBe(expected.Configuration.ReRoutes[i].DownstreamPort); - response.ReRoutes[i].DownstreamScheme.ShouldBe(expected.Configuration.ReRoutes[i].DownstreamScheme); - response.ReRoutes[i].UpstreamPathTemplate.ShouldBe(expected.Configuration.ReRoutes[i].UpstreamPathTemplate); - response.ReRoutes[i].UpstreamHttpMethod.ShouldBe(expected.Configuration.ReRoutes[i].UpstreamHttpMethod); - } - passed++; - } - - return passed == 5; - } - catch(Exception e) - { - Console.WriteLine(e); - return false; - } - } - - var commandOnAllStateMachines = WaitFor(20000).Until(() => CommandCalledOnAllStateMachines()); - commandOnAllStateMachines.ShouldBeTrue(); - } - - private void ThenTheResponseShouldBe(FileConfiguration expected) - { - var response = JsonConvert.DeserializeObject(_response.Content.ReadAsStringAsync().Result); - - response.GlobalConfiguration.RequestIdKey.ShouldBe(expected.GlobalConfiguration.RequestIdKey); - response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Host); - response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Port); - - for (var i = 0; i < response.ReRoutes.Count; i++) - { - response.ReRoutes[i].DownstreamHost.ShouldBe(expected.ReRoutes[i].DownstreamHost); - response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expected.ReRoutes[i].DownstreamPathTemplate); - response.ReRoutes[i].DownstreamPort.ShouldBe(expected.ReRoutes[i].DownstreamPort); - response.ReRoutes[i].DownstreamScheme.ShouldBe(expected.ReRoutes[i].DownstreamScheme); - response.ReRoutes[i].UpstreamPathTemplate.ShouldBe(expected.ReRoutes[i].UpstreamPathTemplate); - response.ReRoutes[i].UpstreamHttpMethod.ShouldBe(expected.ReRoutes[i].UpstreamHttpMethod); - } - } - - private void WhenIGetUrlOnTheApiGateway(string url) - { - _response = _httpClient.GetAsync(url).Result; - } - - private void WhenIPostOnTheApiGateway(string url, FileConfiguration updatedConfiguration) - { - var json = JsonConvert.SerializeObject(updatedConfiguration); - var content = new StringContent(json); - content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); - _response = _httpClient.PostAsync(url, content).Result; - } - - private void GivenIHaveAddedATokenToMyRequest() - { - _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); - } - - private void GivenIHaveAnOcelotToken(string adminPath) - { - var tokenUrl = $"{adminPath}/connect/token"; - var formData = new List> - { - new KeyValuePair("client_id", "admin"), - new KeyValuePair("client_secret", "secret"), - new KeyValuePair("scope", "admin"), - new KeyValuePair("grant_type", "client_credentials") - }; - var content = new FormUrlEncodedContent(formData); - - var response = _httpClient.PostAsync(tokenUrl, content).Result; - var responseContent = response.Content.ReadAsStringAsync().Result; - response.EnsureSuccessStatusCode(); - _token = JsonConvert.DeserializeObject(responseContent); - var configPath = $"{adminPath}/.well-known/openid-configuration"; - response = _httpClient.GetAsync(configPath).Result; - response.EnsureSuccessStatusCode(); - } - - private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) - { - var configurationPath = $"{Directory.GetCurrentDirectory()}/configuration.json"; - - var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); - - if (File.Exists(configurationPath)) - { - File.Delete(configurationPath); - } - - File.WriteAllText(configurationPath, jsonConfiguration); - - var text = File.ReadAllText(configurationPath); - - configurationPath = $"{AppContext.BaseDirectory}/configuration.json"; - - if (File.Exists(configurationPath)) - { - File.Delete(configurationPath); - } - - File.WriteAllText(configurationPath, jsonConfiguration); - - text = File.ReadAllText(configurationPath); - } - - private void GivenAServerIsRunning(string url) - { - lock(_lock) - { - IWebHostBuilder webHostBuilder = new WebHostBuilder(); - webHostBuilder.UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureServices(x => - { - x.AddSingleton(webHostBuilder); - x.AddSingleton(new NodeId(url)); - }) - .UseStartup(); - - var builder = webHostBuilder.Build(); - builder.Start(); - - _webHostBuilders.Add(webHostBuilder); - _builders.Add(builder); - } - } - - private void GivenFiveServersAreRunning() - { - var bytes = File.ReadAllText("peers.json"); - _peers = JsonConvert.DeserializeObject(bytes); - - foreach (var peer in _peers.Peers) - { - var thread = new Thread(() => GivenAServerIsRunning(peer.HostAndPort)); - thread.Start(); - _threads.Add(thread); - } - } - - private void GivenALeaderIsElected() - { - //dirty sleep to make sure we have a leader - var stopwatch = Stopwatch.StartNew(); - while(stopwatch.ElapsedMilliseconds < 20000) - { - - } - } - - private void WhenISendACommandIntoTheCluster(FakeCommand command) - { - var p = _peers.Peers.First(); - var json = JsonConvert.SerializeObject(command,new JsonSerializerSettings() { - TypeNameHandling = TypeNameHandling.All - }); - var httpContent = new StringContent(json); - httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); - using(var httpClient = new HttpClient()) - { - httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); - var response = httpClient.PostAsync($"{p.HostAndPort}/administration/raft/command", httpContent).GetAwaiter().GetResult(); - response.EnsureSuccessStatusCode(); - var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); - var result = JsonConvert.DeserializeObject>(content); - result.Command.Value.ShouldBe(command.Value); - } - - //dirty sleep to make sure command replicated... - var stopwatch = Stopwatch.StartNew(); - while(stopwatch.ElapsedMilliseconds < 10000) - { - - } - } - - private void ThenTheCommandIsReplicatedToAllStateMachines(FakeCommand command) - { - //dirty sleep to give a chance to replicate... - var stopwatch = Stopwatch.StartNew(); - while(stopwatch.ElapsedMilliseconds < 2000) - { - - } - - bool CommandCalledOnAllStateMachines() - { - try - { - var passed = 0; - foreach (var peer in _peers.Peers) - { - string fsmData; - fsmData = File.ReadAllText(peer.HostAndPort.Replace("/","").Replace(":","")); - fsmData.ShouldNotBeNullOrEmpty(); - var fakeCommand = JsonConvert.DeserializeObject(fsmData); - fakeCommand.Value.ShouldBe(command.Value); - passed++; - } - - return passed == 5; - } - catch(Exception e) - { - return false; - } - } - - var commandOnAllStateMachines = WaitFor(20000).Until(() => CommandCalledOnAllStateMachines()); - commandOnAllStateMachines.ShouldBeTrue(); - } - } -} +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json; +using Ocelot.Configuration.File; +using Ocelot.Raft; +using Rafty.Concensus; +using Rafty.Infrastructure; +using Shouldly; +using Xunit; +using static Rafty.Infrastructure.Wait; +using Microsoft.Data.Sqlite; + +namespace Ocelot.IntegrationTests +{ + public class RaftTests : IDisposable + { + private readonly List _builders; + private readonly List _webHostBuilders; + private readonly List _threads; + private FilePeers _peers; + private readonly HttpClient _httpClient; + private readonly HttpClient _httpClientForAssertions; + private string _ocelotBaseUrl; + private BearerToken _token; + private HttpResponseMessage _response; + private static readonly object _lock = new object(); + + public RaftTests() + { + _httpClientForAssertions = new HttpClient(); + _httpClient = new HttpClient(); + _ocelotBaseUrl = "http://localhost:5000"; + _httpClient.BaseAddress = new Uri(_ocelotBaseUrl); + _webHostBuilders = new List(); + _builders = new List(); + _threads = new List(); + } + public void Dispose() + { + foreach (var builder in _builders) + { + builder?.Dispose(); + } + + foreach (var peer in _peers.Peers) + { + File.Delete(peer.HostAndPort.Replace("/","").Replace(":","")); + File.Delete($"{peer.HostAndPort.Replace("/","").Replace(":","")}.db"); + } + } + + [Fact] + public void should_persist_command_to_five_servers() + { + var configuration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + } + }; + + var updatedConfiguration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + }, + ReRoutes = new List() + { + new FileReRoute() + { + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "127.0.0.1", + Port = 80, + } + }, + DownstreamScheme = "http", + DownstreamPathTemplate = "/geoffrey", + UpstreamHttpMethod = new List { "get" }, + UpstreamPathTemplate = "/" + }, + new FileReRoute() + { + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "123.123.123", + Port = 443, + } + }, + DownstreamScheme = "https", + DownstreamPathTemplate = "/blooper/{productId}", + UpstreamHttpMethod = new List { "post" }, + UpstreamPathTemplate = "/test" + } + } + }; + + var command = new UpdateFileConfiguration(updatedConfiguration); + GivenThereIsAConfiguration(configuration); + GivenFiveServersAreRunning(); + GivenALeaderIsElected(); + GivenIHaveAnOcelotToken("/administration"); + WhenISendACommandIntoTheCluster(command); + ThenTheCommandIsReplicatedToAllStateMachines(command); + } + + [Fact] + public void should_persist_command_to_five_servers_when_using_administration_api() + { + var configuration = new FileConfiguration + { + }; + + var updatedConfiguration = new FileConfiguration + { + ReRoutes = new List() + { + new FileReRoute() + { + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "127.0.0.1", + Port = 80, + } + }, + DownstreamScheme = "http", + DownstreamPathTemplate = "/geoffrey", + UpstreamHttpMethod = new List { "get" }, + UpstreamPathTemplate = "/" + }, + new FileReRoute() + { + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "123.123.123", + Port = 443, + } + }, + DownstreamScheme = "https", + DownstreamPathTemplate = "/blooper/{productId}", + UpstreamHttpMethod = new List { "post" }, + UpstreamPathTemplate = "/test" + } + } + }; + + var command = new UpdateFileConfiguration(updatedConfiguration); + GivenThereIsAConfiguration(configuration); + GivenFiveServersAreRunning(); + GivenALeaderIsElected(); + GivenIHaveAnOcelotToken("/administration"); + GivenIHaveAddedATokenToMyRequest(); + WhenIPostOnTheApiGateway("/administration/configuration", updatedConfiguration); + ThenTheCommandIsReplicatedToAllStateMachines(command); + } + + private void WhenISendACommandIntoTheCluster(UpdateFileConfiguration command) + { + var p = _peers.Peers.First(); + var json = JsonConvert.SerializeObject(command,new JsonSerializerSettings() { + TypeNameHandling = TypeNameHandling.All + }); + var httpContent = new StringContent(json); + httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); + using(var httpClient = new HttpClient()) + { + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); + var response = httpClient.PostAsync($"{p.HostAndPort}/administration/raft/command", httpContent).GetAwaiter().GetResult(); + response.EnsureSuccessStatusCode(); + var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); + var result = JsonConvert.DeserializeObject>(content); + result.Command.Configuration.ReRoutes.Count.ShouldBe(2); + } + + //dirty sleep to make sure command replicated... + var stopwatch = Stopwatch.StartNew(); + while(stopwatch.ElapsedMilliseconds < 10000) + { + + } + } + + private void ThenTheCommandIsReplicatedToAllStateMachines(UpdateFileConfiguration expecteds) + { + //dirty sleep to give a chance to replicate... + var stopwatch = Stopwatch.StartNew(); + while(stopwatch.ElapsedMilliseconds < 2000) + { + + } + + bool CommandCalledOnAllStateMachines() + { + try + { + var passed = 0; + foreach (var peer in _peers.Peers) + { + var path = $"{peer.HostAndPort.Replace("/","").Replace(":","")}.db"; + using(var connection = new SqliteConnection($"Data Source={path};")) + { + connection.Open(); + var sql = @"select count(id) from logs"; + using(var command = new SqliteCommand(sql, connection)) + { + var index = Convert.ToInt32(command.ExecuteScalar()); + index.ShouldBe(1); + } + } + _httpClientForAssertions.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); + var result = _httpClientForAssertions.GetAsync($"{peer.HostAndPort}/administration/configuration").Result; + var json = result.Content.ReadAsStringAsync().Result; + var response = JsonConvert.DeserializeObject(json, new JsonSerializerSettings{TypeNameHandling = TypeNameHandling.All}); + response.GlobalConfiguration.RequestIdKey.ShouldBe(expecteds.Configuration.GlobalConfiguration.RequestIdKey); + response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expecteds.Configuration.GlobalConfiguration.ServiceDiscoveryProvider.Host); + response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expecteds.Configuration.GlobalConfiguration.ServiceDiscoveryProvider.Port); + + for (var i = 0; i < response.ReRoutes.Count; i++) + { + for (var j = 0; j < response.ReRoutes[i].DownstreamHostAndPorts.Count; j++) + { + var res = response.ReRoutes[i].DownstreamHostAndPorts[j]; + var expected = expecteds.Configuration.ReRoutes[i].DownstreamHostAndPorts[j]; + res.Host.ShouldBe(expected.Host); + res.Port.ShouldBe(expected.Port); + } + + response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expecteds.Configuration.ReRoutes[i].DownstreamPathTemplate); + response.ReRoutes[i].DownstreamScheme.ShouldBe(expecteds.Configuration.ReRoutes[i].DownstreamScheme); + response.ReRoutes[i].UpstreamPathTemplate.ShouldBe(expecteds.Configuration.ReRoutes[i].UpstreamPathTemplate); + response.ReRoutes[i].UpstreamHttpMethod.ShouldBe(expecteds.Configuration.ReRoutes[i].UpstreamHttpMethod); + } + passed++; + } + + return passed == 5; + } + catch(Exception e) + { + Console.WriteLine(e); + return false; + } + } + + var commandOnAllStateMachines = WaitFor(20000).Until(() => CommandCalledOnAllStateMachines()); + commandOnAllStateMachines.ShouldBeTrue(); + } + + private void WhenIPostOnTheApiGateway(string url, FileConfiguration updatedConfiguration) + { + var json = JsonConvert.SerializeObject(updatedConfiguration); + var content = new StringContent(json); + content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + _response = _httpClient.PostAsync(url, content).Result; + } + + private void GivenIHaveAddedATokenToMyRequest() + { + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); + } + + private void GivenIHaveAnOcelotToken(string adminPath) + { + var tokenUrl = $"{adminPath}/connect/token"; + var formData = new List> + { + new KeyValuePair("client_id", "admin"), + new KeyValuePair("client_secret", "secret"), + new KeyValuePair("scope", "admin"), + new KeyValuePair("grant_type", "client_credentials") + }; + var content = new FormUrlEncodedContent(formData); + + var response = _httpClient.PostAsync(tokenUrl, content).Result; + var responseContent = response.Content.ReadAsStringAsync().Result; + response.EnsureSuccessStatusCode(); + _token = JsonConvert.DeserializeObject(responseContent); + var configPath = $"{adminPath}/.well-known/openid-configuration"; + response = _httpClient.GetAsync(configPath).Result; + response.EnsureSuccessStatusCode(); + } + + private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) + { + var configurationPath = $"{Directory.GetCurrentDirectory()}/configuration.json"; + + var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + + if (File.Exists(configurationPath)) + { + File.Delete(configurationPath); + } + + File.WriteAllText(configurationPath, jsonConfiguration); + + var text = File.ReadAllText(configurationPath); + + configurationPath = $"{AppContext.BaseDirectory}/configuration.json"; + + if (File.Exists(configurationPath)) + { + File.Delete(configurationPath); + } + + File.WriteAllText(configurationPath, jsonConfiguration); + + text = File.ReadAllText(configurationPath); + } + + private void GivenAServerIsRunning(string url) + { + lock(_lock) + { + IWebHostBuilder webHostBuilder = new WebHostBuilder(); + webHostBuilder.UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .ConfigureServices(x => + { + x.AddSingleton(webHostBuilder); + x.AddSingleton(new NodeId(url)); + }) + .UseStartup(); + + var builder = webHostBuilder.Build(); + builder.Start(); + + _webHostBuilders.Add(webHostBuilder); + _builders.Add(builder); + } + } + + private void GivenFiveServersAreRunning() + { + var bytes = File.ReadAllText("peers.json"); + _peers = JsonConvert.DeserializeObject(bytes); + + foreach (var peer in _peers.Peers) + { + var thread = new Thread(() => GivenAServerIsRunning(peer.HostAndPort)); + thread.Start(); + _threads.Add(thread); + } + } + + private void GivenALeaderIsElected() + { + //dirty sleep to make sure we have a leader + var stopwatch = Stopwatch.StartNew(); + while(stopwatch.ElapsedMilliseconds < 20000) + { + + } + } + } +} diff --git a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs index 51901026a..cb233625b 100644 --- a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs +++ b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs @@ -1,184 +1,190 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net.Http; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; -using Newtonsoft.Json; -using Ocelot.Configuration.File; -using Shouldly; -using TestStack.BDDfy; -using Xunit; -using Microsoft.AspNetCore.Http; -using System.Threading.Tasks; -using System.Collections.Concurrent; - -namespace Ocelot.IntegrationTests -{ - public class ThreadSafeHeadersTests : IDisposable - { - private readonly HttpClient _httpClient; - private IWebHost _builder; - private IWebHostBuilder _webHostBuilder; - private readonly string _ocelotBaseUrl; - private IWebHost _downstreamBuilder; - private readonly Random _random; - private readonly ConcurrentBag _results; - - public ThreadSafeHeadersTests() - { - _results = new ConcurrentBag(); - _random = new Random(); - _httpClient = new HttpClient(); - _ocelotBaseUrl = "http://localhost:5001"; - _httpClient.BaseAddress = new Uri(_ocelotBaseUrl); - } - - [Fact] - public void should_return_same_response_for_each_different_header_under_load_to_downsteam_service() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 51879, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - } - } - }; - - this.Given(x => GivenThereIsAConfiguration(configuration)) - .And(x => GivenThereIsAServiceRunningOn("http://localhost:51879")) - .And(x => GivenOcelotIsRunning()) - .When(x => WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues("/", 300)) - .Then(x => ThenTheSameHeaderValuesAreReturnedByTheDownstreamService()) - .BDDfy(); - } - - private void GivenThereIsAServiceRunningOn(string url) - { - _downstreamBuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.Run(async context => - { - var header = context.Request.Headers["ThreadSafeHeadersTest"]; - - context.Response.StatusCode = 200; - await context.Response.WriteAsync(header[0]); - }); - }) - .Build(); - - _downstreamBuilder.Start(); - } - - private void GivenOcelotIsRunning() - { - _webHostBuilder = new WebHostBuilder() - .UseUrls(_ocelotBaseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureServices(x => - { - x.AddSingleton(_webHostBuilder); - }) - .UseStartup(); - - _builder = _webHostBuilder.Build(); - - _builder.Start(); - } - - private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) - { - var configurationPath = $"{Directory.GetCurrentDirectory()}/configuration.json"; - - var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); - - if (File.Exists(configurationPath)) - { - File.Delete(configurationPath); - } - - File.WriteAllText(configurationPath, jsonConfiguration); - - var text = File.ReadAllText(configurationPath); - - configurationPath = $"{AppContext.BaseDirectory}/configuration.json"; - - if (File.Exists(configurationPath)) - { - File.Delete(configurationPath); - } - - File.WriteAllText(configurationPath, jsonConfiguration); - - text = File.ReadAllText(configurationPath); - } - - private void WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues(string url, int times) - { - var tasks = new Task[times]; - - for (int i = 0; i < times; i++) - { - var urlCopy = url; - var random = _random.Next(0, 50); - tasks[i] = GetForThreadSafeHeadersTest(urlCopy, random); - } - - Task.WaitAll(tasks); - } - - private async Task GetForThreadSafeHeadersTest(string url, int random) - { - var request = new HttpRequestMessage(HttpMethod.Get, url); - request.Headers.Add("ThreadSafeHeadersTest", new List { random.ToString() }); - var response = await _httpClient.SendAsync(request); - var content = await response.Content.ReadAsStringAsync(); - int result = int.Parse(content); - var tshtr = new ThreadSafeHeadersTestResult(result, random); - _results.Add(tshtr); - } - - private void ThenTheSameHeaderValuesAreReturnedByTheDownstreamService() - { - foreach(var result in _results) - { - result.Result.ShouldBe(result.Random); - } - } - public void Dispose() - { - _builder?.Dispose(); - _httpClient?.Dispose(); - _downstreamBuilder?.Dispose(); - } - - class ThreadSafeHeadersTestResult - { - public ThreadSafeHeadersTestResult(int result, int random) - { - Result = result; - Random = random; - - } - - public int Result { get; private set; } - public int Random { get; private set; } - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Http; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json; +using Ocelot.Configuration.File; +using Shouldly; +using TestStack.BDDfy; +using Xunit; +using Microsoft.AspNetCore.Http; +using System.Threading.Tasks; +using System.Collections.Concurrent; + +namespace Ocelot.IntegrationTests +{ + public class ThreadSafeHeadersTests : IDisposable + { + private readonly HttpClient _httpClient; + private IWebHost _builder; + private IWebHostBuilder _webHostBuilder; + private readonly string _ocelotBaseUrl; + private IWebHost _downstreamBuilder; + private readonly Random _random; + private readonly ConcurrentBag _results; + + public ThreadSafeHeadersTests() + { + _results = new ConcurrentBag(); + _random = new Random(); + _httpClient = new HttpClient(); + _ocelotBaseUrl = "http://localhost:5001"; + _httpClient.BaseAddress = new Uri(_ocelotBaseUrl); + } + + [Fact] + public void should_return_same_response_for_each_different_header_under_load_to_downsteam_service() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenThereIsAServiceRunningOn("http://localhost:51879")) + .And(x => GivenOcelotIsRunning()) + .When(x => WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues("/", 300)) + .Then(x => ThenTheSameHeaderValuesAreReturnedByTheDownstreamService()) + .BDDfy(); + } + + private void GivenThereIsAServiceRunningOn(string url) + { + _downstreamBuilder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(async context => + { + var header = context.Request.Headers["ThreadSafeHeadersTest"]; + + context.Response.StatusCode = 200; + await context.Response.WriteAsync(header[0]); + }); + }) + .Build(); + + _downstreamBuilder.Start(); + } + + private void GivenOcelotIsRunning() + { + _webHostBuilder = new WebHostBuilder() + .UseUrls(_ocelotBaseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .ConfigureServices(x => + { + x.AddSingleton(_webHostBuilder); + }) + .UseStartup(); + + _builder = _webHostBuilder.Build(); + + _builder.Start(); + } + + private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) + { + var configurationPath = $"{Directory.GetCurrentDirectory()}/configuration.json"; + + var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + + if (File.Exists(configurationPath)) + { + File.Delete(configurationPath); + } + + File.WriteAllText(configurationPath, jsonConfiguration); + + var text = File.ReadAllText(configurationPath); + + configurationPath = $"{AppContext.BaseDirectory}/configuration.json"; + + if (File.Exists(configurationPath)) + { + File.Delete(configurationPath); + } + + File.WriteAllText(configurationPath, jsonConfiguration); + + text = File.ReadAllText(configurationPath); + } + + private void WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues(string url, int times) + { + var tasks = new Task[times]; + + for (int i = 0; i < times; i++) + { + var urlCopy = url; + var random = _random.Next(0, 50); + tasks[i] = GetForThreadSafeHeadersTest(urlCopy, random); + } + + Task.WaitAll(tasks); + } + + private async Task GetForThreadSafeHeadersTest(string url, int random) + { + var request = new HttpRequestMessage(HttpMethod.Get, url); + request.Headers.Add("ThreadSafeHeadersTest", new List { random.ToString() }); + var response = await _httpClient.SendAsync(request); + var content = await response.Content.ReadAsStringAsync(); + int result = int.Parse(content); + var tshtr = new ThreadSafeHeadersTestResult(result, random); + _results.Add(tshtr); + } + + private void ThenTheSameHeaderValuesAreReturnedByTheDownstreamService() + { + foreach(var result in _results) + { + result.Result.ShouldBe(result.Random); + } + } + public void Dispose() + { + _builder?.Dispose(); + _httpClient?.Dispose(); + _downstreamBuilder?.Dispose(); + } + + class ThreadSafeHeadersTestResult + { + public ThreadSafeHeadersTestResult(int result, int random) + { + Result = result; + Random = random; + + } + + public int Result { get; private set; } + public int Random { get; private set; } + } + } +} diff --git a/test/Ocelot.IntegrationTests/appsettings.json b/test/Ocelot.IntegrationTests/appsettings.json index df0788dea..6d9b7c55b 100644 --- a/test/Ocelot.IntegrationTests/appsettings.json +++ b/test/Ocelot.IntegrationTests/appsettings.json @@ -1,10 +1,10 @@ -{ - "Logging": { - "IncludeScopes": true, - "LogLevel": { - "Default": "Error", - "System": "Error", - "Microsoft": "Error" - } - } -} +{ + "Logging": { + "IncludeScopes": true, + "LogLevel": { + "Default": "Error", + "System": "Error", + "Microsoft": "Error" + } + } +} diff --git a/test/Ocelot.IntegrationTests/peers.json b/test/Ocelot.IntegrationTests/peers.json index d81d183fd..95b50a6e4 100644 --- a/test/Ocelot.IntegrationTests/peers.json +++ b/test/Ocelot.IntegrationTests/peers.json @@ -1,18 +1,18 @@ -{ - "Peers": [{ - "HostAndPort": "http://localhost:5000" - }, - { - "HostAndPort": "http://localhost:5002" - }, - { - "HostAndPort": "http://localhost:5003" - }, - { - "HostAndPort": "http://localhost:5004" - }, - { - "HostAndPort": "http://localhost:5001" - } - ] +{ + "Peers": [{ + "HostAndPort": "http://localhost:5000" + }, + { + "HostAndPort": "http://localhost:5002" + }, + { + "HostAndPort": "http://localhost:5003" + }, + { + "HostAndPort": "http://localhost:5004" + }, + { + "HostAndPort": "http://localhost:5001" + } + ] } \ No newline at end of file diff --git a/test/Ocelot.ManualTest/ManualTestStartup.cs b/test/Ocelot.ManualTest/ManualTestStartup.cs index a55271050..329984a10 100644 --- a/test/Ocelot.ManualTest/ManualTestStartup.cs +++ b/test/Ocelot.ManualTest/ManualTestStartup.cs @@ -1,40 +1,40 @@ -using System; -using CacheManager.Core; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Ocelot.DependencyInjection; -using Ocelot.Middleware; -using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; - -namespace Ocelot.ManualTest -{ - public class ManualTestStartup - { - public void ConfigureServices(IServiceCollection services) - { - Action settings = (x) => - { - x.WithDictionaryHandle(); - }; - - services.AddAuthentication() - .AddJwtBearer("TestKey", x => - { - x.Authority = "test"; - x.Audience = "test"; - }); - - services.AddOcelot() - .AddCacheManager(settings) - .AddAdministration("/administration", "secret"); - } - - public void Configure(IApplicationBuilder app) - { - app.UseOcelot().Wait(); - } - } -} +using System; +using CacheManager.Core; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Ocelot.DependencyInjection; +using Ocelot.Middleware; +using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; + +namespace Ocelot.ManualTest +{ + public class ManualTestStartup + { + public void ConfigureServices(IServiceCollection services) + { + Action settings = (x) => + { + x.WithDictionaryHandle(); + }; + + services.AddAuthentication() + .AddJwtBearer("TestKey", x => + { + x.Authority = "test"; + x.Audience = "test"; + }); + + services.AddOcelot() + .AddCacheManager(settings) + .AddAdministration("/administration", "secret"); + } + + public void Configure(IApplicationBuilder app) + { + app.UseOcelot().Wait(); + } + } +} diff --git a/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj b/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj index 5d8754108..7ec84c3db 100644 --- a/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj +++ b/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj @@ -1,43 +1,43 @@ - - - - 0.0.0-dev - netcoreapp2.0 - 2.0.0 - true - Ocelot.ManualTest - Exe - Ocelot.ManualTest - osx.10.11-x64;osx.10.12-x64;win7-x64;win10-x64 - - - - - PreserveNewest - - - - - - PreserveNewest - - - - - - - - - - - - - - - - - - - - - + + + + 0.0.0-dev + netcoreapp2.0 + 2.0.0 + true + Ocelot.ManualTest + Exe + Ocelot.ManualTest + osx.10.11-x64;osx.10.12-x64;win7-x64;win10-x64 + + + + + PreserveNewest + + + + + + PreserveNewest + + + + + + + + + + + + + + + + + + + + + diff --git a/test/Ocelot.ManualTest/Ocelot.postman_collection.json b/test/Ocelot.ManualTest/Ocelot.postman_collection.json index d8598539a..92ac9c735 100644 --- a/test/Ocelot.ManualTest/Ocelot.postman_collection.json +++ b/test/Ocelot.ManualTest/Ocelot.postman_collection.json @@ -1,371 +1,371 @@ -{ - "id": "4dbde9fe-89f5-be35-bb9f-d3b438e16375", - "name": "Ocelot", - "description": "", - "order": [ - "a1c95935-ed18-d5dc-bcb8-a3db8ba1934f", - "ea0ed57a-2cb9-8acc-47dd-006b8db2f1b2", - "c4494401-3985-a5bf-71fb-6e4171384ac6", - "09af8dda-a9cb-20d2-5ee3-0a3023773a1a", - "e8825dc3-4137-99a7-0000-ef5786610dc3", - "fddfc4fa-5114-69e3-4744-203ed71a526b", - "c45d30d7-d9c4-fa05-8110-d6e769bb6ff9", - "4684c2fa-f38c-c193-5f55-bf563a1978c6", - "37bfa9f1-fe29-6a68-e558-66d125d2c96f", - "5f308240-79e3-cf74-7a6b-fe462f0d54f1", - "178f16da-c61b-c881-1c33-9d64a56851a4" - ], - "folders": [], - "folders_order": [], - "timestamp": 0, - "owner": "212120", - "public": false, - "requests": [ - { - "folder": null, - "id": "09af8dda-a9cb-20d2-5ee3-0a3023773a1a", - "name": "GET http://localhost:5000/comments?postId=1", - "dataMode": "params", - "data": null, - "rawModeData": null, - "descriptionFormat": "html", - "description": "", - "headers": "", - "method": "GET", - "pathVariables": {}, - "url": "http://localhost:5000/comments?postId=1", - "preRequestScript": null, - "tests": null, - "currentHelper": "normal", - "helperAttributes": "{}", - "queryParams": [], - "headerData": [], - "pathVariableData": [], - "responses": [], - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" - }, - { - "id": "178f16da-c61b-c881-1c33-9d64a56851a4", - "headers": "Authorization: Bearer {{AccessToken}}\n", - "headerData": [ - { - "key": "Authorization", - "value": "Bearer {{AccessToken}}", - "enabled": true, - "description": "" - } - ], - "url": "http://localhost:5000/administration/configuration", - "folder": null, - "queryParams": [], - "preRequestScript": null, - "pathVariables": {}, - "pathVariableData": [], - "method": "GET", - "data": null, - "dataMode": "params", - "tests": null, - "currentHelper": "normal", - "helperAttributes": "{}", - "time": 1508849878025, - "name": "GET http://localhost:5000/admin/configuration", - "description": "", - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375", - "responses": [], - "isFromCollection": true, - "collectionRequestId": "178f16da-c61b-c881-1c33-9d64a56851a4", - "rawModeData": null, - "descriptionFormat": null - }, - { - "id": "37bfa9f1-fe29-6a68-e558-66d125d2c96f", - "headers": "", - "headerData": [], - "url": "http://localhost:5000/administration/connect/token", - "folder": null, - "queryParams": [], - "preRequestScript": null, - "pathVariables": {}, - "pathVariableData": [], - "method": "POST", - "data": [ - { - "key": "client_id", - "value": "admin", - "type": "text", - "enabled": true - }, - { - "key": "client_secret", - "value": "secret", - "type": "text", - "enabled": true - }, - { - "key": "scope", - "value": "admin", - "type": "text", - "enabled": true - }, - { - "key": "username", - "value": "admin", - "type": "text", - "enabled": true - }, - { - "key": "password", - "value": "secret", - "type": "text", - "enabled": true - }, - { - "key": "grant_type", - "value": "password", - "type": "text", - "enabled": true - } - ], - "dataMode": "params", - "tests": "var jsonData = JSON.parse(responseBody);\npostman.setGlobalVariable(\"AccessToken\", jsonData.access_token);\npostman.setGlobalVariable(\"RefreshToken\", jsonData.refresh_token);", - "currentHelper": "normal", - "helperAttributes": "{}", - "time": 1506359585080, - "name": "POST http://localhost:5000/admin/connect/token copy", - "description": "", - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375", - "responses": [], - "isFromCollection": true, - "collectionRequestId": "37bfa9f1-fe29-6a68-e558-66d125d2c96f", - "rawModeData": null, - "descriptionFormat": null - }, - { - "folder": null, - "id": "4684c2fa-f38c-c193-5f55-bf563a1978c6", - "name": "DELETE http://localhost:5000/posts/1", - "dataMode": "params", - "data": null, - "rawModeData": null, - "descriptionFormat": "html", - "description": "", - "headers": "", - "method": "DELETE", - "pathVariables": {}, - "url": "http://localhost:5000/posts/1", - "preRequestScript": null, - "tests": null, - "currentHelper": "normal", - "helperAttributes": "{}", - "queryParams": [], - "headerData": [], - "pathVariableData": [], - "responses": [], - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" - }, - { - "id": "5f308240-79e3-cf74-7a6b-fe462f0d54f1", - "headers": "Authorization: Bearer {{AccessToken}}\n", - "headerData": [ - { - "key": "Authorization", - "value": "Bearer {{AccessToken}}", - "description": "", - "enabled": true - } - ], - "url": "http://localhost:5000/administration/.well-known/openid-configuration", - "folder": null, - "queryParams": [], - "preRequestScript": null, - "pathVariables": {}, - "pathVariableData": [], - "method": "GET", - "data": null, - "dataMode": "params", - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "time": 1508849923518, - "name": "GET http://localhost:5000/admin/.well-known/openid-configuration", - "description": "", - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375", - "responses": [] - }, - { - "folder": null, - "id": "a1c95935-ed18-d5dc-bcb8-a3db8ba1934f", - "name": "GET http://localhost:5000/posts", - "dataMode": "params", - "data": [ - { - "key": "client_id", - "value": "admin", - "type": "text", - "enabled": true - }, - { - "key": "client_secret", - "value": "secret", - "type": "text", - "enabled": true - }, - { - "key": "scope", - "value": "admin", - "type": "text", - "enabled": true - }, - { - "key": "username", - "value": "admin", - "type": "text", - "enabled": true - }, - { - "key": "password", - "value": "admin", - "type": "text", - "enabled": true - }, - { - "key": "grant_type", - "value": "password", - "type": "text", - "enabled": true - } - ], - "rawModeData": null, - "descriptionFormat": "html", - "description": "", - "headers": "", - "method": "POST", - "pathVariables": {}, - "url": "http://localhost:5000/admin/configuration", - "preRequestScript": null, - "tests": null, - "currentHelper": "normal", - "helperAttributes": "{}", - "queryParams": [], - "headerData": [], - "pathVariableData": [], - "responses": [], - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" - }, - { - "folder": null, - "id": "c4494401-3985-a5bf-71fb-6e4171384ac6", - "name": "GET http://localhost:5000/posts/1/comments", - "dataMode": "params", - "data": null, - "rawModeData": null, - "descriptionFormat": "html", - "description": "", - "headers": "", - "method": "GET", - "pathVariables": {}, - "url": "http://localhost:5000/posts/1/comments", - "preRequestScript": null, - "tests": null, - "currentHelper": "normal", - "helperAttributes": "{}", - "queryParams": [], - "headerData": [], - "pathVariableData": [], - "responses": [], - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" - }, - { - "folder": null, - "id": "c45d30d7-d9c4-fa05-8110-d6e769bb6ff9", - "name": "PATCH http://localhost:5000/posts/1", - "dataMode": "raw", - "data": [], - "rawModeData": "{\n \"title\": \"gfdgsgsdgsdfgsdfgdfg\",\n}", - "descriptionFormat": "html", - "description": "", - "headers": "", - "method": "PATCH", - "pathVariables": {}, - "url": "http://localhost:5000/posts/1", - "preRequestScript": null, - "tests": null, - "currentHelper": "normal", - "helperAttributes": "{}", - "queryParams": [], - "headerData": [], - "pathVariableData": [], - "responses": [], - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" - }, - { - "folder": null, - "id": "e8825dc3-4137-99a7-0000-ef5786610dc3", - "name": "POST http://localhost:5000/posts/1", - "dataMode": "raw", - "data": [], - "rawModeData": "{\n \"userId\": 1,\n \"title\": \"test\",\n \"body\": \"test\"\n}", - "descriptionFormat": "html", - "description": "", - "headers": "", - "method": "POST", - "pathVariables": {}, - "url": "http://localhost:5000/posts", - "preRequestScript": null, - "tests": null, - "currentHelper": "normal", - "helperAttributes": "{}", - "queryParams": [], - "headerData": [], - "pathVariableData": [], - "responses": [], - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" - }, - { - "folder": null, - "id": "ea0ed57a-2cb9-8acc-47dd-006b8db2f1b2", - "name": "GET http://localhost:5000/posts/1", - "dataMode": "params", - "data": null, - "rawModeData": null, - "descriptionFormat": "html", - "description": "", - "headers": "", - "method": "GET", - "pathVariables": {}, - "url": "http://localhost:5000/posts/1", - "preRequestScript": null, - "tests": null, - "currentHelper": "normal", - "helperAttributes": "{}", - "queryParams": [], - "headerData": [], - "pathVariableData": [], - "responses": [], - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" - }, - { - "folder": null, - "id": "fddfc4fa-5114-69e3-4744-203ed71a526b", - "name": "PUT http://localhost:5000/posts/1", - "dataMode": "raw", - "data": [], - "rawModeData": "{\n \"userId\": 1,\n \"title\": \"test\",\n \"body\": \"test\"\n}", - "descriptionFormat": "html", - "description": "", - "headers": "", - "method": "PUT", - "pathVariables": {}, - "url": "http://localhost:5000/posts/1", - "preRequestScript": null, - "tests": null, - "currentHelper": "normal", - "helperAttributes": "{}", - "queryParams": [], - "headerData": [], - "pathVariableData": [], - "responses": [], - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" - } - ] +{ + "id": "4dbde9fe-89f5-be35-bb9f-d3b438e16375", + "name": "Ocelot", + "description": "", + "order": [ + "a1c95935-ed18-d5dc-bcb8-a3db8ba1934f", + "ea0ed57a-2cb9-8acc-47dd-006b8db2f1b2", + "c4494401-3985-a5bf-71fb-6e4171384ac6", + "09af8dda-a9cb-20d2-5ee3-0a3023773a1a", + "e8825dc3-4137-99a7-0000-ef5786610dc3", + "fddfc4fa-5114-69e3-4744-203ed71a526b", + "c45d30d7-d9c4-fa05-8110-d6e769bb6ff9", + "4684c2fa-f38c-c193-5f55-bf563a1978c6", + "37bfa9f1-fe29-6a68-e558-66d125d2c96f", + "5f308240-79e3-cf74-7a6b-fe462f0d54f1", + "178f16da-c61b-c881-1c33-9d64a56851a4" + ], + "folders": [], + "folders_order": [], + "timestamp": 0, + "owner": "212120", + "public": false, + "requests": [ + { + "folder": null, + "id": "09af8dda-a9cb-20d2-5ee3-0a3023773a1a", + "name": "GET http://localhost:5000/comments?postId=1", + "dataMode": "params", + "data": null, + "rawModeData": null, + "descriptionFormat": "html", + "description": "", + "headers": "", + "method": "GET", + "pathVariables": {}, + "url": "http://localhost:5000/comments?postId=1", + "preRequestScript": null, + "tests": null, + "currentHelper": "normal", + "helperAttributes": "{}", + "queryParams": [], + "headerData": [], + "pathVariableData": [], + "responses": [], + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" + }, + { + "id": "178f16da-c61b-c881-1c33-9d64a56851a4", + "headers": "Authorization: Bearer {{AccessToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "enabled": true, + "description": "" + } + ], + "url": "http://localhost:5000/administration/configuration", + "folder": null, + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": "{}", + "time": 1508849878025, + "name": "GET http://localhost:5000/admin/configuration", + "description": "", + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375", + "responses": [], + "isFromCollection": true, + "collectionRequestId": "178f16da-c61b-c881-1c33-9d64a56851a4", + "rawModeData": null, + "descriptionFormat": null + }, + { + "id": "37bfa9f1-fe29-6a68-e558-66d125d2c96f", + "headers": "", + "headerData": [], + "url": "http://localhost:5000/administration/connect/token", + "folder": null, + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "POST", + "data": [ + { + "key": "client_id", + "value": "admin", + "type": "text", + "enabled": true + }, + { + "key": "client_secret", + "value": "secret", + "type": "text", + "enabled": true + }, + { + "key": "scope", + "value": "admin", + "type": "text", + "enabled": true + }, + { + "key": "username", + "value": "admin", + "type": "text", + "enabled": true + }, + { + "key": "password", + "value": "secret", + "type": "text", + "enabled": true + }, + { + "key": "grant_type", + "value": "password", + "type": "text", + "enabled": true + } + ], + "dataMode": "params", + "tests": "var jsonData = JSON.parse(responseBody);\npostman.setGlobalVariable(\"AccessToken\", jsonData.access_token);\npostman.setGlobalVariable(\"RefreshToken\", jsonData.refresh_token);", + "currentHelper": "normal", + "helperAttributes": "{}", + "time": 1506359585080, + "name": "POST http://localhost:5000/admin/connect/token copy", + "description": "", + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375", + "responses": [], + "isFromCollection": true, + "collectionRequestId": "37bfa9f1-fe29-6a68-e558-66d125d2c96f", + "rawModeData": null, + "descriptionFormat": null + }, + { + "folder": null, + "id": "4684c2fa-f38c-c193-5f55-bf563a1978c6", + "name": "DELETE http://localhost:5000/posts/1", + "dataMode": "params", + "data": null, + "rawModeData": null, + "descriptionFormat": "html", + "description": "", + "headers": "", + "method": "DELETE", + "pathVariables": {}, + "url": "http://localhost:5000/posts/1", + "preRequestScript": null, + "tests": null, + "currentHelper": "normal", + "helperAttributes": "{}", + "queryParams": [], + "headerData": [], + "pathVariableData": [], + "responses": [], + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" + }, + { + "id": "5f308240-79e3-cf74-7a6b-fe462f0d54f1", + "headers": "Authorization: Bearer {{AccessToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "description": "", + "enabled": true + } + ], + "url": "http://localhost:5000/administration/.well-known/openid-configuration", + "folder": null, + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1508849923518, + "name": "GET http://localhost:5000/admin/.well-known/openid-configuration", + "description": "", + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375", + "responses": [] + }, + { + "folder": null, + "id": "a1c95935-ed18-d5dc-bcb8-a3db8ba1934f", + "name": "GET http://localhost:5000/posts", + "dataMode": "params", + "data": [ + { + "key": "client_id", + "value": "admin", + "type": "text", + "enabled": true + }, + { + "key": "client_secret", + "value": "secret", + "type": "text", + "enabled": true + }, + { + "key": "scope", + "value": "admin", + "type": "text", + "enabled": true + }, + { + "key": "username", + "value": "admin", + "type": "text", + "enabled": true + }, + { + "key": "password", + "value": "admin", + "type": "text", + "enabled": true + }, + { + "key": "grant_type", + "value": "password", + "type": "text", + "enabled": true + } + ], + "rawModeData": null, + "descriptionFormat": "html", + "description": "", + "headers": "", + "method": "POST", + "pathVariables": {}, + "url": "http://localhost:5000/admin/configuration", + "preRequestScript": null, + "tests": null, + "currentHelper": "normal", + "helperAttributes": "{}", + "queryParams": [], + "headerData": [], + "pathVariableData": [], + "responses": [], + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" + }, + { + "folder": null, + "id": "c4494401-3985-a5bf-71fb-6e4171384ac6", + "name": "GET http://localhost:5000/posts/1/comments", + "dataMode": "params", + "data": null, + "rawModeData": null, + "descriptionFormat": "html", + "description": "", + "headers": "", + "method": "GET", + "pathVariables": {}, + "url": "http://localhost:5000/posts/1/comments", + "preRequestScript": null, + "tests": null, + "currentHelper": "normal", + "helperAttributes": "{}", + "queryParams": [], + "headerData": [], + "pathVariableData": [], + "responses": [], + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" + }, + { + "folder": null, + "id": "c45d30d7-d9c4-fa05-8110-d6e769bb6ff9", + "name": "PATCH http://localhost:5000/posts/1", + "dataMode": "raw", + "data": [], + "rawModeData": "{\n \"title\": \"gfdgsgsdgsdfgsdfgdfg\",\n}", + "descriptionFormat": "html", + "description": "", + "headers": "", + "method": "PATCH", + "pathVariables": {}, + "url": "http://localhost:5000/posts/1", + "preRequestScript": null, + "tests": null, + "currentHelper": "normal", + "helperAttributes": "{}", + "queryParams": [], + "headerData": [], + "pathVariableData": [], + "responses": [], + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" + }, + { + "folder": null, + "id": "e8825dc3-4137-99a7-0000-ef5786610dc3", + "name": "POST http://localhost:5000/posts/1", + "dataMode": "raw", + "data": [], + "rawModeData": "{\n \"userId\": 1,\n \"title\": \"test\",\n \"body\": \"test\"\n}", + "descriptionFormat": "html", + "description": "", + "headers": "", + "method": "POST", + "pathVariables": {}, + "url": "http://localhost:5000/posts", + "preRequestScript": null, + "tests": null, + "currentHelper": "normal", + "helperAttributes": "{}", + "queryParams": [], + "headerData": [], + "pathVariableData": [], + "responses": [], + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" + }, + { + "folder": null, + "id": "ea0ed57a-2cb9-8acc-47dd-006b8db2f1b2", + "name": "GET http://localhost:5000/posts/1", + "dataMode": "params", + "data": null, + "rawModeData": null, + "descriptionFormat": "html", + "description": "", + "headers": "", + "method": "GET", + "pathVariables": {}, + "url": "http://localhost:5000/posts/1", + "preRequestScript": null, + "tests": null, + "currentHelper": "normal", + "helperAttributes": "{}", + "queryParams": [], + "headerData": [], + "pathVariableData": [], + "responses": [], + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" + }, + { + "folder": null, + "id": "fddfc4fa-5114-69e3-4744-203ed71a526b", + "name": "PUT http://localhost:5000/posts/1", + "dataMode": "raw", + "data": [], + "rawModeData": "{\n \"userId\": 1,\n \"title\": \"test\",\n \"body\": \"test\"\n}", + "descriptionFormat": "html", + "description": "", + "headers": "", + "method": "PUT", + "pathVariables": {}, + "url": "http://localhost:5000/posts/1", + "preRequestScript": null, + "tests": null, + "currentHelper": "normal", + "helperAttributes": "{}", + "queryParams": [], + "headerData": [], + "pathVariableData": [], + "responses": [], + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" + } + ] } \ No newline at end of file diff --git a/test/Ocelot.ManualTest/Program.cs b/test/Ocelot.ManualTest/Program.cs index 5c7f482db..53cf41759 100644 --- a/test/Ocelot.ManualTest/Program.cs +++ b/test/Ocelot.ManualTest/Program.cs @@ -1,39 +1,39 @@ -using System.IO; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Configuration; - -namespace Ocelot.ManualTest -{ - public class Program - { - public static void Main(string[] args) - { - IWebHostBuilder builder = new WebHostBuilder(); - builder.ConfigureServices(s => { - s.AddSingleton(builder); - }); - builder.UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); - config.AddEnvironmentVariables(); - }) - .ConfigureLogging((hostingContext, logging) => - { - logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); - logging.AddConsole(); - }) - .UseIISIntegration() - .UseStartup(); - var host = builder.Build(); - host.Run(); - } - } -} +using System.IO; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Configuration; + +namespace Ocelot.ManualTest +{ + public class Program + { + public static void Main(string[] args) + { + IWebHostBuilder builder = new WebHostBuilder(); + builder.ConfigureServices(s => { + s.AddSingleton(builder); + }); + builder.UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); + config.AddJsonFile("configuration.json"); + config.AddEnvironmentVariables(); + }) + .ConfigureLogging((hostingContext, logging) => + { + logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); + logging.AddConsole(); + }) + .UseIISIntegration() + .UseStartup(); + var host = builder.Build(); + host.Run(); + } + } +} diff --git a/test/Ocelot.ManualTest/Properties/launchSettings.json b/test/Ocelot.ManualTest/Properties/launchSettings.json index bfc47fdf6..7bf7225e3 100644 --- a/test/Ocelot.ManualTest/Properties/launchSettings.json +++ b/test/Ocelot.ManualTest/Properties/launchSettings.json @@ -1,26 +1,26 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:24620/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Ocelot.ManualTest": { - "commandName": "Project", - "launchUrl": "http://localhost:5000", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:24620/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Ocelot.ManualTest": { + "commandName": "Project", + "launchUrl": "http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } } \ No newline at end of file diff --git a/test/Ocelot.ManualTest/appsettings.json b/test/Ocelot.ManualTest/appsettings.json index 930ca4c1a..e3439bab9 100644 --- a/test/Ocelot.ManualTest/appsettings.json +++ b/test/Ocelot.ManualTest/appsettings.json @@ -1,10 +1,10 @@ -{ - "Logging": { - "IncludeScopes": false, - "LogLevel": { - "Default": "Debug", - "System": "Error", - "Microsoft": "Error" - } - } -} +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Debug", + "System": "Error", + "Microsoft": "Error" + } + } +} diff --git a/test/Ocelot.ManualTest/configuration.json b/test/Ocelot.ManualTest/configuration.json index fcaf49f59..c8298b20b 100644 --- a/test/Ocelot.ManualTest/configuration.json +++ b/test/Ocelot.ManualTest/configuration.json @@ -1,311 +1,311 @@ -{ - "ReRoutes": [ - { - "DownstreamPathTemplate": "/", - "DownstreamScheme": "http", - "DownstreamHost": "localhost", - "DownstreamPort": 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" - }, - { - "DownstreamPathTemplate": "/posts", - "DownstreamScheme": "https", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 443, - "UpstreamPathTemplate": "/posts", - "UpstreamHttpMethod": [ "Get" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - } - }, - { - "DownstreamPathTemplate": "/posts/{postId}", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamPathTemplate": "/posts/{postId}", - "UpstreamHttpMethod": [ "Get" ], - "RequestIdKey": "ReRouteRequestId", - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - } - }, - { - "DownstreamPathTemplate": "/posts/{postId}/comments", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamPathTemplate": "/posts/{postId}/comments", - "UpstreamHttpMethod": [ "Get" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - } - }, - { - "DownstreamPathTemplate": "/comments", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamPathTemplate": "/comments", - "UpstreamHttpMethod": [ "Get" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - } - }, - { - "DownstreamPathTemplate": "/posts", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamPathTemplate": "/posts", - "UpstreamHttpMethod": [ "Post" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - } - }, - { - "DownstreamPathTemplate": "/posts/{postId}", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamPathTemplate": "/posts/{postId}", - "UpstreamHttpMethod": [ "Put" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - } - }, - { - "DownstreamPathTemplate": "/posts/{postId}", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamPathTemplate": "/posts/{postId}", - "UpstreamHttpMethod": [ "Patch" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - } - }, - { - "DownstreamPathTemplate": "/posts/{postId}", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamPathTemplate": "/posts/{postId}", - "UpstreamHttpMethod": [ "Delete" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - } - }, - { - "DownstreamPathTemplate": "/api/products", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamPathTemplate": "/products", - "UpstreamHttpMethod": [ "Get" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - }, - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/api/products/{productId}", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamPathTemplate": "/products/{productId}", - "UpstreamHttpMethod": [ "Get" ], - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/api/products", - "DownstreamScheme": "http", - "DownstreamHost": "products20161126090340.azurewebsites.net", - "DownstreamPort": 80, - "UpstreamPathTemplate": "/products", - "UpstreamHttpMethod": [ "Post" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - } - }, - { - "DownstreamPathTemplate": "/api/products/{productId}", - "DownstreamScheme": "http", - "DownstreamHost": "products20161126090340.azurewebsites.net", - "DownstreamPort": 80, - "UpstreamPathTemplate": "/products/{productId}", - "UpstreamHttpMethod": [ "Put" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - }, - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/api/products/{productId}", - "DownstreamScheme": "http", - "DownstreamHost": "products20161126090340.azurewebsites.net", - "DownstreamPort": 80, - "UpstreamPathTemplate": "/products/{productId}", - "UpstreamHttpMethod": [ "Delete" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - }, - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/api/customers", - "DownstreamScheme": "http", - "DownstreamHost": "customers20161126090811.azurewebsites.net", - "DownstreamPort": 80, - "UpstreamPathTemplate": "/customers", - "UpstreamHttpMethod": [ "Get" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - }, - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/api/customers/{customerId}", - "DownstreamScheme": "http", - "DownstreamHost": "customers20161126090811.azurewebsites.net", - "DownstreamPort": 80, - "UpstreamPathTemplate": "/customers/{customerId}", - "UpstreamHttpMethod": [ "Get" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - }, - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/api/customers", - "DownstreamScheme": "http", - "DownstreamHost": "customers20161126090811.azurewebsites.net", - "DownstreamPort": 80, - "UpstreamPathTemplate": "/customers", - "UpstreamHttpMethod": [ "Post" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - }, - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/api/customers/{customerId}", - "DownstreamScheme": "http", - "DownstreamHost": "customers20161126090811.azurewebsites.net", - "DownstreamPort": 80, - "UpstreamPathTemplate": "/customers/{customerId}", - "UpstreamHttpMethod": [ "Put" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - }, - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/api/customers/{customerId}", - "DownstreamScheme": "http", - "DownstreamHost": "customers20161126090811.azurewebsites.net", - "DownstreamPort": 80, - "UpstreamPathTemplate": "/customers/{customerId}", - "UpstreamHttpMethod": [ "Delete" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - }, - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/posts", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamPathTemplate": "/posts/", - "UpstreamHttpMethod": [ "Get" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - }, - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/", - "DownstreamScheme": "http", - "DownstreamHost": "www.bbc.co.uk", - "DownstreamPort": 80, - "UpstreamPathTemplate": "/bbc/", - "UpstreamHttpMethod": [ "Get" ] - } - ], - - "GlobalConfiguration": { - "RequestIdKey": "OcRequestId" - } +{ + "ReRoutes": [ + { + "DownstreamPathTemplate": "/", + "DownstreamScheme": "http", + "DownstreamHost": "localhost", + "DownstreamPort": 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" + }, + { + "DownstreamPathTemplate": "/posts", + "DownstreamScheme": "https", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 443, + "UpstreamPathTemplate": "/posts", + "UpstreamHttpMethod": [ "Get" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/posts/{postId}", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamPathTemplate": "/posts/{postId}", + "UpstreamHttpMethod": [ "Get" ], + "RequestIdKey": "ReRouteRequestId", + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/posts/{postId}/comments", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamPathTemplate": "/posts/{postId}/comments", + "UpstreamHttpMethod": [ "Get" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/comments", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamPathTemplate": "/comments", + "UpstreamHttpMethod": [ "Get" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/posts", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamPathTemplate": "/posts", + "UpstreamHttpMethod": [ "Post" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/posts/{postId}", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamPathTemplate": "/posts/{postId}", + "UpstreamHttpMethod": [ "Put" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/posts/{postId}", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamPathTemplate": "/posts/{postId}", + "UpstreamHttpMethod": [ "Patch" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/posts/{postId}", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamPathTemplate": "/posts/{postId}", + "UpstreamHttpMethod": [ "Delete" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/api/products", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamPathTemplate": "/products", + "UpstreamHttpMethod": [ "Get" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + }, + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/api/products/{productId}", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamPathTemplate": "/products/{productId}", + "UpstreamHttpMethod": [ "Get" ], + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/api/products", + "DownstreamScheme": "http", + "DownstreamHost": "products20161126090340.azurewebsites.net", + "DownstreamPort": 80, + "UpstreamPathTemplate": "/products", + "UpstreamHttpMethod": [ "Post" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/api/products/{productId}", + "DownstreamScheme": "http", + "DownstreamHost": "products20161126090340.azurewebsites.net", + "DownstreamPort": 80, + "UpstreamPathTemplate": "/products/{productId}", + "UpstreamHttpMethod": [ "Put" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + }, + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/api/products/{productId}", + "DownstreamScheme": "http", + "DownstreamHost": "products20161126090340.azurewebsites.net", + "DownstreamPort": 80, + "UpstreamPathTemplate": "/products/{productId}", + "UpstreamHttpMethod": [ "Delete" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + }, + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/api/customers", + "DownstreamScheme": "http", + "DownstreamHost": "customers20161126090811.azurewebsites.net", + "DownstreamPort": 80, + "UpstreamPathTemplate": "/customers", + "UpstreamHttpMethod": [ "Get" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + }, + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/api/customers/{customerId}", + "DownstreamScheme": "http", + "DownstreamHost": "customers20161126090811.azurewebsites.net", + "DownstreamPort": 80, + "UpstreamPathTemplate": "/customers/{customerId}", + "UpstreamHttpMethod": [ "Get" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + }, + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/api/customers", + "DownstreamScheme": "http", + "DownstreamHost": "customers20161126090811.azurewebsites.net", + "DownstreamPort": 80, + "UpstreamPathTemplate": "/customers", + "UpstreamHttpMethod": [ "Post" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + }, + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/api/customers/{customerId}", + "DownstreamScheme": "http", + "DownstreamHost": "customers20161126090811.azurewebsites.net", + "DownstreamPort": 80, + "UpstreamPathTemplate": "/customers/{customerId}", + "UpstreamHttpMethod": [ "Put" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + }, + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/api/customers/{customerId}", + "DownstreamScheme": "http", + "DownstreamHost": "customers20161126090811.azurewebsites.net", + "DownstreamPort": 80, + "UpstreamPathTemplate": "/customers/{customerId}", + "UpstreamHttpMethod": [ "Delete" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + }, + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/posts", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamPathTemplate": "/posts/", + "UpstreamHttpMethod": [ "Get" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + }, + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/", + "DownstreamScheme": "http", + "DownstreamHost": "www.bbc.co.uk", + "DownstreamPort": 80, + "UpstreamPathTemplate": "/bbc/", + "UpstreamHttpMethod": [ "Get" ] + } + ], + + "GlobalConfiguration": { + "RequestIdKey": "OcRequestId" + } } \ No newline at end of file diff --git a/test/Ocelot.ManualTest/web.config b/test/Ocelot.ManualTest/web.config index dc0514fca..20741057b 100644 --- a/test/Ocelot.ManualTest/web.config +++ b/test/Ocelot.ManualTest/web.config @@ -1,14 +1,14 @@ - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs b/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs index dcb54388d..20cd82ef2 100644 --- a/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs @@ -1,70 +1,70 @@ -namespace Ocelot.UnitTests.Authentication -{ - using System.Collections.Generic; - using Microsoft.AspNetCore.Builder; - using Microsoft.AspNetCore.Http; - using Microsoft.Extensions.DependencyInjection; - using Moq; - using Ocelot.Authentication.Middleware; - using Ocelot.Configuration.Builder; - using Ocelot.DownstreamRouteFinder; - using Ocelot.DownstreamRouteFinder.UrlMatcher; - using Ocelot.Logging; - using Ocelot.Responses; - using Shouldly; - using TestStack.BDDfy; - using Xunit; - - public class AuthenticationMiddlewareTests : ServerHostedMiddlewareTest - { - private OkResponse _downstreamRoute; - - public AuthenticationMiddlewareTests() - { - GivenTheTestServerIsConfigured(); - } - - [Fact] - public void should_call_next_middleware_if_route_is_not_authenticated() - { - this.Given(x => x.GivenTheDownStreamRouteIs( - new DownstreamRoute( - new List(), - new ReRouteBuilder().WithUpstreamHttpMethod(new List { "Get" }).Build()))) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheUserIsAuthenticated()) - .BDDfy(); - } - - protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) - { - services.AddSingleton(); - services.AddLogging(); - services.AddSingleton(ScopedRepository.Object); - } - - protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) - { - app.UseAuthenticationMiddleware(); - - app.Run(async x => - { - await x.Response.WriteAsync("The user is authenticated"); - }); - } - - private void ThenTheUserIsAuthenticated() - { - var content = ResponseMessage.Content.ReadAsStringAsync().Result; - content.ShouldBe("The user is authenticated"); - } - - private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) - { - _downstreamRoute = new OkResponse(downstreamRoute); - ScopedRepository - .Setup(x => x.Get(It.IsAny())) - .Returns(_downstreamRoute); - } - } -} +namespace Ocelot.UnitTests.Authentication +{ + using System.Collections.Generic; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using Ocelot.Authentication.Middleware; + using Ocelot.Configuration.Builder; + using Ocelot.DownstreamRouteFinder; + using Ocelot.DownstreamRouteFinder.UrlMatcher; + using Ocelot.Logging; + using Ocelot.Responses; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + + public class AuthenticationMiddlewareTests : ServerHostedMiddlewareTest + { + private OkResponse _downstreamRoute; + + public AuthenticationMiddlewareTests() + { + GivenTheTestServerIsConfigured(); + } + + [Fact] + public void should_call_next_middleware_if_route_is_not_authenticated() + { + this.Given(x => x.GivenTheDownStreamRouteIs( + new DownstreamRoute( + new List(), + new ReRouteBuilder().WithUpstreamHttpMethod(new List { "Get" }).Build()))) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheUserIsAuthenticated()) + .BDDfy(); + } + + protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + { + services.AddSingleton(); + services.AddLogging(); + services.AddSingleton(ScopedRepository.Object); + } + + protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) + { + app.UseAuthenticationMiddleware(); + + app.Run(async x => + { + await x.Response.WriteAsync("The user is authenticated"); + }); + } + + private void ThenTheUserIsAuthenticated() + { + var content = ResponseMessage.Content.ReadAsStringAsync().Result; + content.ShouldBe("The user is authenticated"); + } + + private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) + { + _downstreamRoute = new OkResponse(downstreamRoute); + ScopedRepository + .Setup(x => x.Get(It.IsAny())) + .Returns(_downstreamRoute); + } + } +} diff --git a/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs b/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs index 9ab81f94c..ea89accb4 100644 --- a/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs @@ -1,82 +1,82 @@ -namespace Ocelot.UnitTests.Authorization -{ - using System.Collections.Generic; - using System.Security.Claims; - using Microsoft.AspNetCore.Builder; - using Microsoft.Extensions.DependencyInjection; - using Moq; - using Ocelot.Authorisation; - using Ocelot.Authorisation.Middleware; - using Ocelot.Configuration.Builder; - using Ocelot.DownstreamRouteFinder; - using Ocelot.DownstreamRouteFinder.UrlMatcher; - using Ocelot.Logging; - using Ocelot.Responses; - using TestStack.BDDfy; - using Xunit; - - public class AuthorisationMiddlewareTests : ServerHostedMiddlewareTest - { - private readonly Mock _authService; - private readonly Mock _authScopesService; - private OkResponse _downstreamRoute; - - public AuthorisationMiddlewareTests() - { - _authService = new Mock(); - _authScopesService = new Mock(); - - GivenTheTestServerIsConfigured(); - } - - [Fact] - public void should_call_authorisation_service() - { - this.Given(x => x.GivenTheDownStreamRouteIs(new DownstreamRoute(new List(), - new ReRouteBuilder() - .WithIsAuthorised(true) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()))) - .And(x => x.GivenTheAuthServiceReturns(new OkResponse(true))) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheAuthServiceIsCalledCorrectly()) - .BDDfy(); - } - - protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) - { - services.AddSingleton(); - services.AddLogging(); - services.AddSingleton(_authService.Object); - services.AddSingleton(_authScopesService.Object); - services.AddSingleton(ScopedRepository.Object); - } - - protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) - { - app.UseAuthorisationMiddleware(); - } - - private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) - { - _downstreamRoute = new OkResponse(downstreamRoute); - ScopedRepository - .Setup(x => x.Get(It.IsAny())) - .Returns(_downstreamRoute); - } - - private void GivenTheAuthServiceReturns(Response expected) - { - _authService - .Setup(x => x.Authorise(It.IsAny(), It.IsAny>())) - .Returns(expected); - } - - private void ThenTheAuthServiceIsCalledCorrectly() - { - _authService - .Verify(x => x.Authorise(It.IsAny(), - It.IsAny>()), Times.Once); - } - } -} +namespace Ocelot.UnitTests.Authorization +{ + using System.Collections.Generic; + using System.Security.Claims; + using Microsoft.AspNetCore.Builder; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using Ocelot.Authorisation; + using Ocelot.Authorisation.Middleware; + using Ocelot.Configuration.Builder; + using Ocelot.DownstreamRouteFinder; + using Ocelot.DownstreamRouteFinder.UrlMatcher; + using Ocelot.Logging; + using Ocelot.Responses; + using TestStack.BDDfy; + using Xunit; + + public class AuthorisationMiddlewareTests : ServerHostedMiddlewareTest + { + private readonly Mock _authService; + private readonly Mock _authScopesService; + private OkResponse _downstreamRoute; + + public AuthorisationMiddlewareTests() + { + _authService = new Mock(); + _authScopesService = new Mock(); + + GivenTheTestServerIsConfigured(); + } + + [Fact] + public void should_call_authorisation_service() + { + this.Given(x => x.GivenTheDownStreamRouteIs(new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithIsAuthorised(true) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()))) + .And(x => x.GivenTheAuthServiceReturns(new OkResponse(true))) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheAuthServiceIsCalledCorrectly()) + .BDDfy(); + } + + protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + { + services.AddSingleton(); + services.AddLogging(); + services.AddSingleton(_authService.Object); + services.AddSingleton(_authScopesService.Object); + services.AddSingleton(ScopedRepository.Object); + } + + protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) + { + app.UseAuthorisationMiddleware(); + } + + private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) + { + _downstreamRoute = new OkResponse(downstreamRoute); + ScopedRepository + .Setup(x => x.Get(It.IsAny())) + .Returns(_downstreamRoute); + } + + private void GivenTheAuthServiceReturns(Response expected) + { + _authService + .Setup(x => x.Authorise(It.IsAny(), It.IsAny>())) + .Returns(expected); + } + + private void ThenTheAuthServiceIsCalledCorrectly() + { + _authService + .Verify(x => x.Authorise(It.IsAny(), + It.IsAny>()), Times.Once); + } + } +} diff --git a/test/Ocelot.UnitTests/Authorization/ClaimsAuthoriserTests.cs b/test/Ocelot.UnitTests/Authorization/ClaimsAuthoriserTests.cs index 7b3779a6b..b435f7e7b 100644 --- a/test/Ocelot.UnitTests/Authorization/ClaimsAuthoriserTests.cs +++ b/test/Ocelot.UnitTests/Authorization/ClaimsAuthoriserTests.cs @@ -1,79 +1,79 @@ -using System.Collections.Generic; -using System.Security.Claims; -using Ocelot.Authorisation; -using Ocelot.Responses; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Authorization -{ - using Ocelot.Infrastructure.Claims.Parser; - - public class ClaimsAuthoriserTests - { - private readonly ClaimsAuthoriser _claimsAuthoriser; - private ClaimsPrincipal _claimsPrincipal; - private Dictionary _requirement; - private Response _result; - - public ClaimsAuthoriserTests() - { - _claimsAuthoriser = new ClaimsAuthoriser(new ClaimsParser()); - } - - [Fact] - public void should_authorise_user() - { - this.Given(x => x.GivenAClaimsPrincipal(new ClaimsPrincipal(new ClaimsIdentity(new List - { - new Claim("UserType", "registered") - })))) - .And(x => x.GivenARouteClaimsRequirement(new Dictionary - { - {"UserType", "registered"} - })) - .When(x => x.WhenICallTheAuthoriser()) - .Then(x => x.ThenTheUserIsAuthorised()) - .BDDfy(); - } - - [Fact] - public void should_not_authorise_user() - { - this.Given(x => x.GivenAClaimsPrincipal(new ClaimsPrincipal(new ClaimsIdentity(new List())))) - .And(x => x.GivenARouteClaimsRequirement(new Dictionary - { - { "UserType", "registered" } - })) - .When(x => x.WhenICallTheAuthoriser()) - .Then(x => x.ThenTheUserIsntAuthorised()) - .BDDfy(); - } - - private void GivenAClaimsPrincipal(ClaimsPrincipal claimsPrincipal) - { - _claimsPrincipal = claimsPrincipal; - } - - private void GivenARouteClaimsRequirement(Dictionary requirement) - { - _requirement = requirement; - } - - private void WhenICallTheAuthoriser() - { - _result = _claimsAuthoriser.Authorise(_claimsPrincipal, _requirement); - } - - private void ThenTheUserIsAuthorised() - { - _result.Data.ShouldBe(true); - } - - private void ThenTheUserIsntAuthorised() - { - _result.Data.ShouldBe(false); - } - } -} +using System.Collections.Generic; +using System.Security.Claims; +using Ocelot.Authorisation; +using Ocelot.Responses; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Authorization +{ + using Ocelot.Infrastructure.Claims.Parser; + + public class ClaimsAuthoriserTests + { + private readonly ClaimsAuthoriser _claimsAuthoriser; + private ClaimsPrincipal _claimsPrincipal; + private Dictionary _requirement; + private Response _result; + + public ClaimsAuthoriserTests() + { + _claimsAuthoriser = new ClaimsAuthoriser(new ClaimsParser()); + } + + [Fact] + public void should_authorise_user() + { + this.Given(x => x.GivenAClaimsPrincipal(new ClaimsPrincipal(new ClaimsIdentity(new List + { + new Claim("UserType", "registered") + })))) + .And(x => x.GivenARouteClaimsRequirement(new Dictionary + { + {"UserType", "registered"} + })) + .When(x => x.WhenICallTheAuthoriser()) + .Then(x => x.ThenTheUserIsAuthorised()) + .BDDfy(); + } + + [Fact] + public void should_not_authorise_user() + { + this.Given(x => x.GivenAClaimsPrincipal(new ClaimsPrincipal(new ClaimsIdentity(new List())))) + .And(x => x.GivenARouteClaimsRequirement(new Dictionary + { + { "UserType", "registered" } + })) + .When(x => x.WhenICallTheAuthoriser()) + .Then(x => x.ThenTheUserIsntAuthorised()) + .BDDfy(); + } + + private void GivenAClaimsPrincipal(ClaimsPrincipal claimsPrincipal) + { + _claimsPrincipal = claimsPrincipal; + } + + private void GivenARouteClaimsRequirement(Dictionary requirement) + { + _requirement = requirement; + } + + private void WhenICallTheAuthoriser() + { + _result = _claimsAuthoriser.Authorise(_claimsPrincipal, _requirement); + } + + private void ThenTheUserIsAuthorised() + { + _result.Data.ShouldBe(true); + } + + private void ThenTheUserIsntAuthorised() + { + _result.Data.ShouldBe(false); + } + } +} diff --git a/test/Ocelot.UnitTests/Cache/CacheManagerCacheTests.cs b/test/Ocelot.UnitTests/Cache/CacheManagerCacheTests.cs index 54107298d..4b891d734 100644 --- a/test/Ocelot.UnitTests/Cache/CacheManagerCacheTests.cs +++ b/test/Ocelot.UnitTests/Cache/CacheManagerCacheTests.cs @@ -1,103 +1,103 @@ -using System; -using CacheManager.Core; -using Moq; -using Ocelot.Cache; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Cache -{ - public class CacheManagerCacheTests - { - private OcelotCacheManagerCache _ocelotOcelotCacheManager; - private Mock> _mockCacheManager; - private string _key; - private string _value; - private string _resultGet; - private TimeSpan _ttlSeconds; - private string _region; - - public CacheManagerCacheTests() - { - _mockCacheManager = new Mock>(); - _ocelotOcelotCacheManager = new OcelotCacheManagerCache(_mockCacheManager.Object); - } - - [Fact] - public void should_get_from_cache() - { - this.Given(x => x.GivenTheFollowingIsCached("someKey", "someRegion", "someValue")) - .When(x => x.WhenIGetFromTheCache()) - .Then(x => x.ThenTheResultIs("someValue")) - .BDDfy(); - } - - [Fact] - public void should_add_to_cache() - { - this.When(x => x.WhenIAddToTheCache("someKey", "someValue", TimeSpan.FromSeconds(1))) - .Then(x => x.ThenTheCacheIsCalledCorrectly()) - .BDDfy(); - } - - [Fact] - public void should_delete_key_from_cache() - { - this.Given(_ => GivenTheFollowingRegion("fookey")) - .When(_ => WhenIDeleteTheRegion("fookey")) - .Then(_ => ThenTheRegionIsDeleted("fookey")) - .BDDfy(); - } - - private void WhenIDeleteTheRegion(string region) - { - _ocelotOcelotCacheManager.ClearRegion(region); - } - - private void ThenTheRegionIsDeleted(string region) - { - _mockCacheManager - .Verify(x => x.ClearRegion(region), Times.Once); - } - - private void GivenTheFollowingRegion(string key) - { - _ocelotOcelotCacheManager.Add(key, "doesnt matter", TimeSpan.FromSeconds(10), "region"); - } - - private void WhenIAddToTheCache(string key, string value, TimeSpan ttlSeconds) - { - _key = key; - _value = value; - _ttlSeconds = ttlSeconds; - _ocelotOcelotCacheManager.Add(_key, _value, _ttlSeconds, "region"); - } - - private void ThenTheCacheIsCalledCorrectly() - { - _mockCacheManager - .Verify(x => x.Add(It.IsAny>()), Times.Once); - } - - private void ThenTheResultIs(string expected) - { - _resultGet.ShouldBe(expected); - } - - private void WhenIGetFromTheCache() - { - _resultGet = _ocelotOcelotCacheManager.Get(_key, _region); - } - - private void GivenTheFollowingIsCached(string key, string region, string value) - { - _key = key; - _value = value; - _region = region; - _mockCacheManager - .Setup(x => x.Get(It.IsAny(), It.IsAny())) - .Returns(value); - } - } -} +using System; +using CacheManager.Core; +using Moq; +using Ocelot.Cache; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Cache +{ + public class CacheManagerCacheTests + { + private OcelotCacheManagerCache _ocelotOcelotCacheManager; + private Mock> _mockCacheManager; + private string _key; + private string _value; + private string _resultGet; + private TimeSpan _ttlSeconds; + private string _region; + + public CacheManagerCacheTests() + { + _mockCacheManager = new Mock>(); + _ocelotOcelotCacheManager = new OcelotCacheManagerCache(_mockCacheManager.Object); + } + + [Fact] + public void should_get_from_cache() + { + this.Given(x => x.GivenTheFollowingIsCached("someKey", "someRegion", "someValue")) + .When(x => x.WhenIGetFromTheCache()) + .Then(x => x.ThenTheResultIs("someValue")) + .BDDfy(); + } + + [Fact] + public void should_add_to_cache() + { + this.When(x => x.WhenIAddToTheCache("someKey", "someValue", TimeSpan.FromSeconds(1))) + .Then(x => x.ThenTheCacheIsCalledCorrectly()) + .BDDfy(); + } + + [Fact] + public void should_delete_key_from_cache() + { + this.Given(_ => GivenTheFollowingRegion("fookey")) + .When(_ => WhenIDeleteTheRegion("fookey")) + .Then(_ => ThenTheRegionIsDeleted("fookey")) + .BDDfy(); + } + + private void WhenIDeleteTheRegion(string region) + { + _ocelotOcelotCacheManager.ClearRegion(region); + } + + private void ThenTheRegionIsDeleted(string region) + { + _mockCacheManager + .Verify(x => x.ClearRegion(region), Times.Once); + } + + private void GivenTheFollowingRegion(string key) + { + _ocelotOcelotCacheManager.Add(key, "doesnt matter", TimeSpan.FromSeconds(10), "region"); + } + + private void WhenIAddToTheCache(string key, string value, TimeSpan ttlSeconds) + { + _key = key; + _value = value; + _ttlSeconds = ttlSeconds; + _ocelotOcelotCacheManager.Add(_key, _value, _ttlSeconds, "region"); + } + + private void ThenTheCacheIsCalledCorrectly() + { + _mockCacheManager + .Verify(x => x.Add(It.IsAny>()), Times.Once); + } + + private void ThenTheResultIs(string expected) + { + _resultGet.ShouldBe(expected); + } + + private void WhenIGetFromTheCache() + { + _resultGet = _ocelotOcelotCacheManager.Get(_key, _region); + } + + private void GivenTheFollowingIsCached(string key, string region, string value) + { + _key = key; + _value = value; + _region = region; + _mockCacheManager + .Setup(x => x.Get(It.IsAny(), It.IsAny())) + .Returns(value); + } + } +} diff --git a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs index 1499e19df..71839a5a1 100644 --- a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs @@ -1,130 +1,130 @@ -namespace Ocelot.UnitTests.Cache -{ - using System; - using System.Collections.Generic; - using System.Net.Http; - using Microsoft.AspNetCore.Builder; - using Microsoft.Extensions.DependencyInjection; - using Moq; - using Ocelot.Cache; - using Ocelot.Cache.Middleware; - using Ocelot.Configuration; - using Ocelot.Configuration.Builder; - using Ocelot.DownstreamRouteFinder; - using Ocelot.DownstreamRouteFinder.UrlMatcher; - using Ocelot.Logging; - using Ocelot.Responses; - using TestStack.BDDfy; - using Xunit; - - public class OutputCacheMiddlewareTests : ServerHostedMiddlewareTest - { - private readonly Mock> _cacheManager; - private CachedResponse _response; - - public OutputCacheMiddlewareTests() - { - _cacheManager = new Mock>(); - - ScopedRepository - .Setup(sr => sr.Get("DownstreamRequest")) - .Returns(new OkResponse(new HttpRequestMessage(HttpMethod.Get, "https://some.url/blah?abcd=123"))); - - GivenTheTestServerIsConfigured(); - } - - [Fact] - public void should_returned_cached_item_when_it_is_in_cache() - { - var cachedResponse = new CachedResponse(); - this.Given(x => x.GivenThereIsACachedResponse(cachedResponse)) - .And(x => x.GivenTheDownstreamRouteIs()) - .And(x => x.GivenThereIsADownstreamUrl()) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheCacheGetIsCalledCorrectly()) - .BDDfy(); - } - - [Fact] - public void should_continue_with_pipeline_and_cache_response() - { - this.Given(x => x.GivenResponseIsNotCached()) - .And(x => x.GivenTheDownstreamRouteIs()) - .And(x => x.GivenThereAreNoErrors()) - .And(x => x.GivenThereIsADownstreamUrl()) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheCacheAddIsCalledCorrectly()) - .BDDfy(); - } - - protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) - { - services.AddSingleton(); - services.AddLogging(); - services.AddSingleton(_cacheManager.Object); - services.AddSingleton(ScopedRepository.Object); - services.AddSingleton(); - } - - protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) - { - app.UseOutputCacheMiddleware(); - } - - private void GivenThereIsACachedResponse(CachedResponse response) - { - _response = response; - _cacheManager - .Setup(x => x.Get(It.IsAny(), It.IsAny())) - .Returns(_response); - } - - private void GivenResponseIsNotCached() - { - ScopedRepository - .Setup(x => x.Get("HttpResponseMessage")) - .Returns(new OkResponse(new HttpResponseMessage())); - } - - private void GivenTheDownstreamRouteIs() - { - var reRoute = new ReRouteBuilder() - .WithIsCached(true) - .WithCacheOptions(new CacheOptions(100, "kanken")) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build(); - - var downstreamRoute = new DownstreamRoute(new List(), reRoute); - - ScopedRepository - .Setup(x => x.Get(It.IsAny())) - .Returns(new OkResponse(downstreamRoute)); - } - - private void GivenThereAreNoErrors() - { - ScopedRepository - .Setup(x => x.Get("OcelotMiddlewareError")) - .Returns(new OkResponse(false)); - } - - private void GivenThereIsADownstreamUrl() - { - ScopedRepository - .Setup(x => x.Get("DownstreamUrl")) - .Returns(new OkResponse("anything")); - } - - private void ThenTheCacheGetIsCalledCorrectly() - { - _cacheManager - .Verify(x => x.Get(It.IsAny(), It.IsAny()), Times.Once); - } - - private void ThenTheCacheAddIsCalledCorrectly() - { - _cacheManager - .Verify(x => x.Add(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); - } - } -} +namespace Ocelot.UnitTests.Cache +{ + using System; + using System.Collections.Generic; + using System.Net.Http; + using Microsoft.AspNetCore.Builder; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using Ocelot.Cache; + using Ocelot.Cache.Middleware; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; + using Ocelot.DownstreamRouteFinder; + using Ocelot.DownstreamRouteFinder.UrlMatcher; + using Ocelot.Logging; + using Ocelot.Responses; + using TestStack.BDDfy; + using Xunit; + + public class OutputCacheMiddlewareTests : ServerHostedMiddlewareTest + { + private readonly Mock> _cacheManager; + private CachedResponse _response; + + public OutputCacheMiddlewareTests() + { + _cacheManager = new Mock>(); + + ScopedRepository + .Setup(sr => sr.Get("DownstreamRequest")) + .Returns(new OkResponse(new HttpRequestMessage(HttpMethod.Get, "https://some.url/blah?abcd=123"))); + + GivenTheTestServerIsConfigured(); + } + + [Fact] + public void should_returned_cached_item_when_it_is_in_cache() + { + var cachedResponse = new CachedResponse(); + this.Given(x => x.GivenThereIsACachedResponse(cachedResponse)) + .And(x => x.GivenTheDownstreamRouteIs()) + .And(x => x.GivenThereIsADownstreamUrl()) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheCacheGetIsCalledCorrectly()) + .BDDfy(); + } + + [Fact] + public void should_continue_with_pipeline_and_cache_response() + { + this.Given(x => x.GivenResponseIsNotCached()) + .And(x => x.GivenTheDownstreamRouteIs()) + .And(x => x.GivenThereAreNoErrors()) + .And(x => x.GivenThereIsADownstreamUrl()) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheCacheAddIsCalledCorrectly()) + .BDDfy(); + } + + protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + { + services.AddSingleton(); + services.AddLogging(); + services.AddSingleton(_cacheManager.Object); + services.AddSingleton(ScopedRepository.Object); + services.AddSingleton(); + } + + protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) + { + app.UseOutputCacheMiddleware(); + } + + private void GivenThereIsACachedResponse(CachedResponse response) + { + _response = response; + _cacheManager + .Setup(x => x.Get(It.IsAny(), It.IsAny())) + .Returns(_response); + } + + private void GivenResponseIsNotCached() + { + ScopedRepository + .Setup(x => x.Get("HttpResponseMessage")) + .Returns(new OkResponse(new HttpResponseMessage())); + } + + private void GivenTheDownstreamRouteIs() + { + var reRoute = new ReRouteBuilder() + .WithIsCached(true) + .WithCacheOptions(new CacheOptions(100, "kanken")) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build(); + + var downstreamRoute = new DownstreamRoute(new List(), reRoute); + + ScopedRepository + .Setup(x => x.Get(It.IsAny())) + .Returns(new OkResponse(downstreamRoute)); + } + + private void GivenThereAreNoErrors() + { + ScopedRepository + .Setup(x => x.Get("OcelotMiddlewareError")) + .Returns(new OkResponse(false)); + } + + private void GivenThereIsADownstreamUrl() + { + ScopedRepository + .Setup(x => x.Get("DownstreamUrl")) + .Returns(new OkResponse("anything")); + } + + private void ThenTheCacheGetIsCalledCorrectly() + { + _cacheManager + .Verify(x => x.Get(It.IsAny(), It.IsAny()), Times.Once); + } + + private void ThenTheCacheAddIsCalledCorrectly() + { + _cacheManager + .Verify(x => x.Add(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + } + } +} diff --git a/test/Ocelot.UnitTests/Cache/RegionCreatorTests.cs b/test/Ocelot.UnitTests/Cache/RegionCreatorTests.cs index 6a4754259..8c3e3b63a 100644 --- a/test/Ocelot.UnitTests/Cache/RegionCreatorTests.cs +++ b/test/Ocelot.UnitTests/Cache/RegionCreatorTests.cs @@ -1,65 +1,65 @@ -using System.Collections.Generic; -using Ocelot.Cache; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.Configuration.File; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Cache -{ - public class RegionCreatorTests - { - private string _result; - private FileReRoute _reRoute; - - [Fact] - public void should_create_region() - { - var reRoute = new FileReRoute - { - UpstreamHttpMethod = new List { "Get" }, - UpstreamPathTemplate = "/testdummy" - }; - - this.Given(_ => GivenTheReRoute(reRoute)) - .When(_ => WhenICreateTheRegion()) - .Then(_ => ThenTheRegionIs("Gettestdummy")) - .BDDfy(); - } - - [Fact] - public void should_use_region() - { - var reRoute = new FileReRoute - { - FileCacheOptions = new FileCacheOptions - { - Region = "region" - } - }; - - this.Given(_ => GivenTheReRoute(reRoute)) - .When(_ => WhenICreateTheRegion()) - .Then(_ => ThenTheRegionIs("region")) - .BDDfy(); - } - - private void GivenTheReRoute(FileReRoute reRoute) - { - _reRoute = reRoute; - } - - private void WhenICreateTheRegion() - { - RegionCreator regionCreator = new RegionCreator(); - _result = regionCreator.Create(_reRoute); - } - - private void ThenTheRegionIs(string expected) - { - _result.ShouldBe(expected); - } - } +using System.Collections.Generic; +using Ocelot.Cache; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.File; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Cache +{ + public class RegionCreatorTests + { + private string _result; + private FileReRoute _reRoute; + + [Fact] + public void should_create_region() + { + var reRoute = new FileReRoute + { + UpstreamHttpMethod = new List { "Get" }, + UpstreamPathTemplate = "/testdummy" + }; + + this.Given(_ => GivenTheReRoute(reRoute)) + .When(_ => WhenICreateTheRegion()) + .Then(_ => ThenTheRegionIs("Gettestdummy")) + .BDDfy(); + } + + [Fact] + public void should_use_region() + { + var reRoute = new FileReRoute + { + FileCacheOptions = new FileCacheOptions + { + Region = "region" + } + }; + + this.Given(_ => GivenTheReRoute(reRoute)) + .When(_ => WhenICreateTheRegion()) + .Then(_ => ThenTheRegionIs("region")) + .BDDfy(); + } + + private void GivenTheReRoute(FileReRoute reRoute) + { + _reRoute = reRoute; + } + + private void WhenICreateTheRegion() + { + RegionCreator regionCreator = new RegionCreator(); + _result = regionCreator.Create(_reRoute); + } + + private void ThenTheRegionIs(string expected) + { + _result.ShouldBe(expected); + } + } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Claims/AddClaimsToRequestTests.cs b/test/Ocelot.UnitTests/Claims/AddClaimsToRequestTests.cs index e88fc773b..ac60ee8ba 100644 --- a/test/Ocelot.UnitTests/Claims/AddClaimsToRequestTests.cs +++ b/test/Ocelot.UnitTests/Claims/AddClaimsToRequestTests.cs @@ -1,144 +1,144 @@ -using System.Collections.Generic; -using System.Security.Claims; -using Microsoft.AspNetCore.Http; -using Moq; -using Ocelot.Claims; -using Ocelot.Configuration; -using Ocelot.Errors; -using Ocelot.Infrastructure.Claims.Parser; -using Ocelot.Responses; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Claims -{ - public class AddClaimsToRequestTests - { - private readonly AddClaimsToRequest _addClaimsToRequest; - private readonly Mock _parser; - private List _claimsToThings; - private HttpContext _context; - private Response _result; - private Response _claimValue; - - public AddClaimsToRequestTests() - { - _parser = new Mock(); - _addClaimsToRequest = new AddClaimsToRequest(_parser.Object); - } - - [Fact] - public void should_add_claims_to_context() - { - var context = new DefaultHttpContext - { - User = new ClaimsPrincipal(new ClaimsIdentity(new List - { - new Claim("test", "data") - })) - }; - - this.Given( - x => x.GivenClaimsToThings(new List - { - new ClaimToThing("claim-key", "", "", 0) - })) - .Given(x => x.GivenHttpContext(context)) - .And(x => x.GivenTheClaimParserReturns(new OkResponse("value"))) - .When(x => x.WhenIAddClaimsToTheRequest()) - .Then(x => x.ThenTheResultIsSuccess()) - .BDDfy(); - } - - [Fact] - public void if_claims_exists_should_replace_it() - { - var context = new DefaultHttpContext - { - User = new ClaimsPrincipal(new ClaimsIdentity(new List - { - new Claim("existing-key", "data"), - new Claim("new-key", "data") - })), - }; - - this.Given( - x => x.GivenClaimsToThings(new List - { - new ClaimToThing("existing-key", "new-key", "", 0) - })) - .Given(x => x.GivenHttpContext(context)) - .And(x => x.GivenTheClaimParserReturns(new OkResponse("value"))) - .When(x => x.WhenIAddClaimsToTheRequest()) - .Then(x => x.ThenTheResultIsSuccess()) - .BDDfy(); - } - - [Fact] - public void should_return_error() - { - this.Given( - x => x.GivenClaimsToThings(new List - { - new ClaimToThing("", "", "", 0) - })) - .Given(x => x.GivenHttpContext(new DefaultHttpContext())) - .And(x => x.GivenTheClaimParserReturns(new ErrorResponse(new List - { - new AnyError() - }))) - .When(x => x.WhenIAddClaimsToTheRequest()) - .Then(x => x.ThenTheResultIsError()) - .BDDfy(); - } - - - private void GivenClaimsToThings(List configuration) - { - _claimsToThings = configuration; - } - - private void GivenHttpContext(HttpContext context) - { - _context = context; - } - - private void GivenTheClaimParserReturns(Response claimValue) - { - _claimValue = claimValue; - _parser - .Setup( - x => - x.GetValue(It.IsAny>(), - It.IsAny(), - It.IsAny(), - It.IsAny())) - .Returns(_claimValue); - } - - private void WhenIAddClaimsToTheRequest() - { - _result = _addClaimsToRequest.SetClaimsOnContext(_claimsToThings, _context); - } - - private void ThenTheResultIsSuccess() - { - _result.IsError.ShouldBe(false); - } - - private void ThenTheResultIsError() - { - - _result.IsError.ShouldBe(true); - } - - class AnyError : Error - { - public AnyError() - : base("blahh", OcelotErrorCode.UnknownError) - { - } - } - } -} +using System.Collections.Generic; +using System.Security.Claims; +using Microsoft.AspNetCore.Http; +using Moq; +using Ocelot.Claims; +using Ocelot.Configuration; +using Ocelot.Errors; +using Ocelot.Infrastructure.Claims.Parser; +using Ocelot.Responses; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Claims +{ + public class AddClaimsToRequestTests + { + private readonly AddClaimsToRequest _addClaimsToRequest; + private readonly Mock _parser; + private List _claimsToThings; + private HttpContext _context; + private Response _result; + private Response _claimValue; + + public AddClaimsToRequestTests() + { + _parser = new Mock(); + _addClaimsToRequest = new AddClaimsToRequest(_parser.Object); + } + + [Fact] + public void should_add_claims_to_context() + { + var context = new DefaultHttpContext + { + User = new ClaimsPrincipal(new ClaimsIdentity(new List + { + new Claim("test", "data") + })) + }; + + this.Given( + x => x.GivenClaimsToThings(new List + { + new ClaimToThing("claim-key", "", "", 0) + })) + .Given(x => x.GivenHttpContext(context)) + .And(x => x.GivenTheClaimParserReturns(new OkResponse("value"))) + .When(x => x.WhenIAddClaimsToTheRequest()) + .Then(x => x.ThenTheResultIsSuccess()) + .BDDfy(); + } + + [Fact] + public void if_claims_exists_should_replace_it() + { + var context = new DefaultHttpContext + { + User = new ClaimsPrincipal(new ClaimsIdentity(new List + { + new Claim("existing-key", "data"), + new Claim("new-key", "data") + })), + }; + + this.Given( + x => x.GivenClaimsToThings(new List + { + new ClaimToThing("existing-key", "new-key", "", 0) + })) + .Given(x => x.GivenHttpContext(context)) + .And(x => x.GivenTheClaimParserReturns(new OkResponse("value"))) + .When(x => x.WhenIAddClaimsToTheRequest()) + .Then(x => x.ThenTheResultIsSuccess()) + .BDDfy(); + } + + [Fact] + public void should_return_error() + { + this.Given( + x => x.GivenClaimsToThings(new List + { + new ClaimToThing("", "", "", 0) + })) + .Given(x => x.GivenHttpContext(new DefaultHttpContext())) + .And(x => x.GivenTheClaimParserReturns(new ErrorResponse(new List + { + new AnyError() + }))) + .When(x => x.WhenIAddClaimsToTheRequest()) + .Then(x => x.ThenTheResultIsError()) + .BDDfy(); + } + + + private void GivenClaimsToThings(List configuration) + { + _claimsToThings = configuration; + } + + private void GivenHttpContext(HttpContext context) + { + _context = context; + } + + private void GivenTheClaimParserReturns(Response claimValue) + { + _claimValue = claimValue; + _parser + .Setup( + x => + x.GetValue(It.IsAny>(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns(_claimValue); + } + + private void WhenIAddClaimsToTheRequest() + { + _result = _addClaimsToRequest.SetClaimsOnContext(_claimsToThings, _context); + } + + private void ThenTheResultIsSuccess() + { + _result.IsError.ShouldBe(false); + } + + private void ThenTheResultIsError() + { + + _result.IsError.ShouldBe(true); + } + + class AnyError : Error + { + public AnyError() + : base("blahh", OcelotErrorCode.UnknownError) + { + } + } + } +} diff --git a/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs index 2470a6fb6..44ab1dc08 100644 --- a/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs @@ -1,87 +1,87 @@ -namespace Ocelot.UnitTests.Claims -{ - using System.Collections.Generic; - using Microsoft.AspNetCore.Builder; - using Microsoft.AspNetCore.Http; - using Microsoft.Extensions.DependencyInjection; - using Moq; - using Ocelot.Claims; - using Ocelot.Claims.Middleware; - using Ocelot.Configuration; - using Ocelot.Configuration.Builder; - using Ocelot.DownstreamRouteFinder; - using Ocelot.DownstreamRouteFinder.UrlMatcher; - using Ocelot.Logging; - using Ocelot.Responses; - using TestStack.BDDfy; - using Xunit; - - public class ClaimsBuilderMiddlewareTests : ServerHostedMiddlewareTest - { - private readonly Mock _addHeaders; - private Response _downstreamRoute; - - public ClaimsBuilderMiddlewareTests() - { - _addHeaders = new Mock(); - - GivenTheTestServerIsConfigured(); - } - - [Fact] - public void should_call_claims_to_request_correctly() - { - var downstreamRoute = new DownstreamRoute(new List(), - new ReRouteBuilder() - .WithDownstreamPathTemplate("any old string") - .WithClaimsToClaims(new List - { - new ClaimToThing("sub", "UserType", "|", 0) - }) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()); - - this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .And(x => x.GivenTheAddClaimsToRequestReturns()) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheClaimsToRequestIsCalledCorrectly()) - .BDDfy(); - } - - protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) - { - services.AddSingleton(); - services.AddLogging(); - services.AddSingleton(_addHeaders.Object); - services.AddSingleton(ScopedRepository.Object); - } - - protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) - { - app.UseClaimsBuilderMiddleware(); - } - - private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) - { - _downstreamRoute = new OkResponse(downstreamRoute); - ScopedRepository - .Setup(x => x.Get(It.IsAny())) - .Returns(_downstreamRoute); - } - - private void GivenTheAddClaimsToRequestReturns() - { - _addHeaders - .Setup(x => x.SetClaimsOnContext(It.IsAny>(), - It.IsAny())) - .Returns(new OkResponse()); - } - - private void ThenTheClaimsToRequestIsCalledCorrectly() - { - _addHeaders - .Verify(x => x.SetClaimsOnContext(It.IsAny>(), - It.IsAny()), Times.Once); - } - } -} +namespace Ocelot.UnitTests.Claims +{ + using System.Collections.Generic; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using Ocelot.Claims; + using Ocelot.Claims.Middleware; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; + using Ocelot.DownstreamRouteFinder; + using Ocelot.DownstreamRouteFinder.UrlMatcher; + using Ocelot.Logging; + using Ocelot.Responses; + using TestStack.BDDfy; + using Xunit; + + public class ClaimsBuilderMiddlewareTests : ServerHostedMiddlewareTest + { + private readonly Mock _addHeaders; + private Response _downstreamRoute; + + public ClaimsBuilderMiddlewareTests() + { + _addHeaders = new Mock(); + + GivenTheTestServerIsConfigured(); + } + + [Fact] + public void should_call_claims_to_request_correctly() + { + var downstreamRoute = new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithClaimsToClaims(new List + { + new ClaimToThing("sub", "UserType", "|", 0) + }) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()); + + this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => x.GivenTheAddClaimsToRequestReturns()) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheClaimsToRequestIsCalledCorrectly()) + .BDDfy(); + } + + protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + { + services.AddSingleton(); + services.AddLogging(); + services.AddSingleton(_addHeaders.Object); + services.AddSingleton(ScopedRepository.Object); + } + + protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) + { + app.UseClaimsBuilderMiddleware(); + } + + private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) + { + _downstreamRoute = new OkResponse(downstreamRoute); + ScopedRepository + .Setup(x => x.Get(It.IsAny())) + .Returns(_downstreamRoute); + } + + private void GivenTheAddClaimsToRequestReturns() + { + _addHeaders + .Setup(x => x.SetClaimsOnContext(It.IsAny>(), + It.IsAny())) + .Returns(new OkResponse()); + } + + private void ThenTheClaimsToRequestIsCalledCorrectly() + { + _addHeaders + .Verify(x => x.SetClaimsOnContext(It.IsAny>(), + It.IsAny()), Times.Once); + } + } +} diff --git a/test/Ocelot.UnitTests/Configuration/AuthenticationOptionsCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/AuthenticationOptionsCreatorTests.cs index d6c62df08..5ad181377 100644 --- a/test/Ocelot.UnitTests/Configuration/AuthenticationOptionsCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/AuthenticationOptionsCreatorTests.cs @@ -1,62 +1,62 @@ -using System.Collections.Generic; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.Configuration.Creator; -using Ocelot.Configuration.File; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Configuration -{ - public class AuthenticationOptionsCreatorTests - { - private readonly AuthenticationOptionsCreator _authOptionsCreator; - private FileReRoute _fileReRoute; - private AuthenticationOptions _result; - - public AuthenticationOptionsCreatorTests() - { - _authOptionsCreator = new AuthenticationOptionsCreator(); - } - - [Fact] - public void should_return_auth_options() - { - var fileReRoute = new FileReRoute() - { - AuthenticationOptions = new FileAuthenticationOptions - { - AuthenticationProviderKey = "Test", - AllowedScopes = new List { "cheese" }, - } - }; - - var expected = new AuthenticationOptionsBuilder() - .WithAllowedScopes(fileReRoute.AuthenticationOptions?.AllowedScopes) - .WithAuthenticationProviderKey("Test") - .Build(); - - this.Given(x => x.GivenTheFollowing(fileReRoute)) - .When(x => x.WhenICreateTheAuthenticationOptions()) - .Then(x => x.ThenTheFollowingConfigIsReturned(expected)) - .BDDfy(); - } - - private void GivenTheFollowing(FileReRoute fileReRoute) - { - _fileReRoute = fileReRoute; - } - - private void WhenICreateTheAuthenticationOptions() - { - _result = _authOptionsCreator.Create(_fileReRoute); - } - - private void ThenTheFollowingConfigIsReturned(AuthenticationOptions expected) - { - _result.AllowedScopes.ShouldBe(expected.AllowedScopes); - _result.AuthenticationProviderKey.ShouldBe(expected.AuthenticationProviderKey); - } - } +using System.Collections.Generic; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class AuthenticationOptionsCreatorTests + { + private readonly AuthenticationOptionsCreator _authOptionsCreator; + private FileReRoute _fileReRoute; + private AuthenticationOptions _result; + + public AuthenticationOptionsCreatorTests() + { + _authOptionsCreator = new AuthenticationOptionsCreator(); + } + + [Fact] + public void should_return_auth_options() + { + var fileReRoute = new FileReRoute() + { + AuthenticationOptions = new FileAuthenticationOptions + { + AuthenticationProviderKey = "Test", + AllowedScopes = new List { "cheese" }, + } + }; + + var expected = new AuthenticationOptionsBuilder() + .WithAllowedScopes(fileReRoute.AuthenticationOptions?.AllowedScopes) + .WithAuthenticationProviderKey("Test") + .Build(); + + this.Given(x => x.GivenTheFollowing(fileReRoute)) + .When(x => x.WhenICreateTheAuthenticationOptions()) + .Then(x => x.ThenTheFollowingConfigIsReturned(expected)) + .BDDfy(); + } + + private void GivenTheFollowing(FileReRoute fileReRoute) + { + _fileReRoute = fileReRoute; + } + + private void WhenICreateTheAuthenticationOptions() + { + _result = _authOptionsCreator.Create(_fileReRoute); + } + + private void ThenTheFollowingConfigIsReturned(AuthenticationOptions expected) + { + _result.AllowedScopes.ShouldBe(expected.AllowedScopes); + _result.AuthenticationProviderKey.ShouldBe(expected.AuthenticationProviderKey); + } + } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Configuration/ClaimToThingConfigurationParserTests.cs b/test/Ocelot.UnitTests/Configuration/ClaimToThingConfigurationParserTests.cs index 1f3ba9bc3..b8d3769af 100644 --- a/test/Ocelot.UnitTests/Configuration/ClaimToThingConfigurationParserTests.cs +++ b/test/Ocelot.UnitTests/Configuration/ClaimToThingConfigurationParserTests.cs @@ -1,117 +1,117 @@ -using System.Collections.Generic; -using System.Linq; -using Ocelot.Configuration; -using Ocelot.Configuration.Parser; -using Ocelot.Errors; -using Ocelot.Responses; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Configuration -{ - public class ClaimToThingConfigurationParserTests - { - private Dictionary _dictionary; - private readonly IClaimToThingConfigurationParser _claimToThingConfigurationParser; - private Response _result; - - public ClaimToThingConfigurationParserTests() - { - _claimToThingConfigurationParser = new ClaimToThingConfigurationParser(); - } - - [Fact] - public void returns_no_instructions_error() - { - this.Given(x => x.GivenTheDictionaryIs(new Dictionary() - { - {"CustomerId", ""}, - })) - .When(x => x.WhenICallTheExtractor()) - .Then( - x => - x.ThenAnErrorIsReturned(new ErrorResponse( - new List - { - new NoInstructionsError(">") - }))) - .BDDfy(); - } - - [Fact] - public void returns_no_instructions_not_for_claims_error() - { - this.Given(x => x.GivenTheDictionaryIs(new Dictionary() - { - {"CustomerId", "Cheese[CustomerId] > value"}, - })) - .When(x => x.WhenICallTheExtractor()) - .Then( - x => - x.ThenAnErrorIsReturned(new ErrorResponse( - new List - { - new InstructionNotForClaimsError() - }))) - .BDDfy(); - } - - [Fact] - public void can_parse_entry_to_work_out_properties_with_key() - { - this.Given(x => x.GivenTheDictionaryIs(new Dictionary() - { - {"CustomerId", "Claims[CustomerId] > value"}, - })) - .When(x => x.WhenICallTheExtractor()) - .Then( - x => - x.ThenTheClaimParserPropertiesAreReturned( - new OkResponse( - new ClaimToThing("CustomerId", "CustomerId", "", 0)))) - .BDDfy(); - } - - [Fact] - public void can_parse_entry_to_work_out_properties_with_key_delimiter_and_index() - { - this.Given(x => x.GivenTheDictionaryIs(new Dictionary() - { - {"UserId", "Claims[Subject] > value[0] > |"}, - })) - .When(x => x.WhenICallTheExtractor()) - .Then( - x => - x.ThenTheClaimParserPropertiesAreReturned( - new OkResponse( - new ClaimToThing("UserId", "Subject", "|", 0)))) - .BDDfy(); - } - - private void ThenAnErrorIsReturned(Response expected) - { - _result.IsError.ShouldBe(expected.IsError); - _result.Errors[0].ShouldBeOfType(expected.Errors[0].GetType()); - } - - private void ThenTheClaimParserPropertiesAreReturned(Response expected) - { - _result.Data.NewKey.ShouldBe(expected.Data.NewKey); - _result.Data.Delimiter.ShouldBe(expected.Data.Delimiter); - _result.Data.Index.ShouldBe(expected.Data.Index); - _result.IsError.ShouldBe(expected.IsError); - } - - private void WhenICallTheExtractor() - { - var first = _dictionary.First(); - _result = _claimToThingConfigurationParser.Extract(first.Key, first.Value); - } - - private void GivenTheDictionaryIs(Dictionary dictionary) - { - _dictionary = dictionary; - } - } -} +using System.Collections.Generic; +using System.Linq; +using Ocelot.Configuration; +using Ocelot.Configuration.Parser; +using Ocelot.Errors; +using Ocelot.Responses; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class ClaimToThingConfigurationParserTests + { + private Dictionary _dictionary; + private readonly IClaimToThingConfigurationParser _claimToThingConfigurationParser; + private Response _result; + + public ClaimToThingConfigurationParserTests() + { + _claimToThingConfigurationParser = new ClaimToThingConfigurationParser(); + } + + [Fact] + public void returns_no_instructions_error() + { + this.Given(x => x.GivenTheDictionaryIs(new Dictionary() + { + {"CustomerId", ""}, + })) + .When(x => x.WhenICallTheExtractor()) + .Then( + x => + x.ThenAnErrorIsReturned(new ErrorResponse( + new List + { + new NoInstructionsError(">") + }))) + .BDDfy(); + } + + [Fact] + public void returns_no_instructions_not_for_claims_error() + { + this.Given(x => x.GivenTheDictionaryIs(new Dictionary() + { + {"CustomerId", "Cheese[CustomerId] > value"}, + })) + .When(x => x.WhenICallTheExtractor()) + .Then( + x => + x.ThenAnErrorIsReturned(new ErrorResponse( + new List + { + new InstructionNotForClaimsError() + }))) + .BDDfy(); + } + + [Fact] + public void can_parse_entry_to_work_out_properties_with_key() + { + this.Given(x => x.GivenTheDictionaryIs(new Dictionary() + { + {"CustomerId", "Claims[CustomerId] > value"}, + })) + .When(x => x.WhenICallTheExtractor()) + .Then( + x => + x.ThenTheClaimParserPropertiesAreReturned( + new OkResponse( + new ClaimToThing("CustomerId", "CustomerId", "", 0)))) + .BDDfy(); + } + + [Fact] + public void can_parse_entry_to_work_out_properties_with_key_delimiter_and_index() + { + this.Given(x => x.GivenTheDictionaryIs(new Dictionary() + { + {"UserId", "Claims[Subject] > value[0] > |"}, + })) + .When(x => x.WhenICallTheExtractor()) + .Then( + x => + x.ThenTheClaimParserPropertiesAreReturned( + new OkResponse( + new ClaimToThing("UserId", "Subject", "|", 0)))) + .BDDfy(); + } + + private void ThenAnErrorIsReturned(Response expected) + { + _result.IsError.ShouldBe(expected.IsError); + _result.Errors[0].ShouldBeOfType(expected.Errors[0].GetType()); + } + + private void ThenTheClaimParserPropertiesAreReturned(Response expected) + { + _result.Data.NewKey.ShouldBe(expected.Data.NewKey); + _result.Data.Delimiter.ShouldBe(expected.Data.Delimiter); + _result.Data.Index.ShouldBe(expected.Data.Index); + _result.IsError.ShouldBe(expected.IsError); + } + + private void WhenICallTheExtractor() + { + var first = _dictionary.First(); + _result = _claimToThingConfigurationParser.Extract(first.Key, first.Value); + } + + private void GivenTheDictionaryIs(Dictionary dictionary) + { + _dictionary = dictionary; + } + } +} diff --git a/test/Ocelot.UnitTests/Configuration/ClaimsToThingCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/ClaimsToThingCreatorTests.cs index 467835d07..656fe5dc7 100644 --- a/test/Ocelot.UnitTests/Configuration/ClaimsToThingCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/ClaimsToThingCreatorTests.cs @@ -1,110 +1,110 @@ -using System.Collections.Generic; -using System.Linq; -using Moq; -using Ocelot.Configuration; -using Ocelot.Configuration.Creator; -using Ocelot.Configuration.Parser; -using Ocelot.Errors; -using Ocelot.Logging; -using Ocelot.Responses; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Configuration -{ - public class ClaimsToThingCreatorTests - { - private readonly Mock _configParser; - private Dictionary _claimsToThings; - private ClaimsToThingCreator _claimsToThingsCreator; - private Mock _loggerFactory; - private List _result; - private Mock _logger; - - public ClaimsToThingCreatorTests() - { - _loggerFactory = new Mock(); - _logger = new Mock(); - _loggerFactory - .Setup(x => x.CreateLogger()) - .Returns(_logger.Object); - _configParser = new Mock(); - _claimsToThingsCreator = new ClaimsToThingCreator(_configParser.Object, _loggerFactory.Object); - } - - [Fact] - public void should_return_claims_to_things() - { - var userInput = new Dictionary() - { - {"CustomerId", "Claims[CustomerId] > value"} - }; - - var claimsToThing = new OkResponse(new ClaimToThing("CustomerId", "CustomerId", "", 0)); - - this.Given(x => x.GivenTheFollowingDictionary(userInput)) - .And(x => x.GivenTheConfigHeaderExtractorReturns(claimsToThing)) - .When(x => x.WhenIGetTheThings()) - .Then(x => x.ThenTheConfigParserIsCalledCorrectly()) - .And(x => x.ThenClaimsToThingsAreReturned()) - .BDDfy(); - } - - [Fact] - public void should_log_error_if_cannot_parse_claim_to_thing() - { - var userInput = new Dictionary() - { - {"CustomerId", "Claims[CustomerId] > value"} - }; - - var claimsToThing = new ErrorResponse(It.IsAny()); - - this.Given(x => x.GivenTheFollowingDictionary(userInput)) - .And(x => x.GivenTheConfigHeaderExtractorReturns(claimsToThing)) - .When(x => x.WhenIGetTheThings()) - .Then(x => x.ThenTheConfigParserIsCalledCorrectly()) - .And(x => x.ThenNoClaimsToThingsAreReturned()) - .BDDfy(); - } - - private void ThenTheLoggerIsCalledCorrectly() - { - _logger - .Verify(x => x.LogDebug(It.IsAny(), It.IsAny()), Times.Once); - } - - private void ThenClaimsToThingsAreReturned() - { - _result.Count.ShouldBeGreaterThan(0); - } - private void GivenTheFollowingDictionary(Dictionary claimsToThings) - { - _claimsToThings = claimsToThings; - } - - private void GivenTheConfigHeaderExtractorReturns(Response expected) - { - _configParser - .Setup(x => x.Extract(It.IsAny(), It.IsAny())) - .Returns(expected); - } - - private void ThenNoClaimsToThingsAreReturned() - { - _result.Count.ShouldBe(0); - } - - private void WhenIGetTheThings() - { - _result = _claimsToThingsCreator.Create(_claimsToThings); - } - - private void ThenTheConfigParserIsCalledCorrectly() - { - _configParser - .Verify(x => x.Extract(_claimsToThings.First().Key, _claimsToThings.First().Value), Times.Once); - } - } +using System.Collections.Generic; +using System.Linq; +using Moq; +using Ocelot.Configuration; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.Parser; +using Ocelot.Errors; +using Ocelot.Logging; +using Ocelot.Responses; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class ClaimsToThingCreatorTests + { + private readonly Mock _configParser; + private Dictionary _claimsToThings; + private ClaimsToThingCreator _claimsToThingsCreator; + private Mock _loggerFactory; + private List _result; + private Mock _logger; + + public ClaimsToThingCreatorTests() + { + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory + .Setup(x => x.CreateLogger()) + .Returns(_logger.Object); + _configParser = new Mock(); + _claimsToThingsCreator = new ClaimsToThingCreator(_configParser.Object, _loggerFactory.Object); + } + + [Fact] + public void should_return_claims_to_things() + { + var userInput = new Dictionary() + { + {"CustomerId", "Claims[CustomerId] > value"} + }; + + var claimsToThing = new OkResponse(new ClaimToThing("CustomerId", "CustomerId", "", 0)); + + this.Given(x => x.GivenTheFollowingDictionary(userInput)) + .And(x => x.GivenTheConfigHeaderExtractorReturns(claimsToThing)) + .When(x => x.WhenIGetTheThings()) + .Then(x => x.ThenTheConfigParserIsCalledCorrectly()) + .And(x => x.ThenClaimsToThingsAreReturned()) + .BDDfy(); + } + + [Fact] + public void should_log_error_if_cannot_parse_claim_to_thing() + { + var userInput = new Dictionary() + { + {"CustomerId", "Claims[CustomerId] > value"} + }; + + var claimsToThing = new ErrorResponse(It.IsAny()); + + this.Given(x => x.GivenTheFollowingDictionary(userInput)) + .And(x => x.GivenTheConfigHeaderExtractorReturns(claimsToThing)) + .When(x => x.WhenIGetTheThings()) + .Then(x => x.ThenTheConfigParserIsCalledCorrectly()) + .And(x => x.ThenNoClaimsToThingsAreReturned()) + .BDDfy(); + } + + private void ThenTheLoggerIsCalledCorrectly() + { + _logger + .Verify(x => x.LogDebug(It.IsAny(), It.IsAny()), Times.Once); + } + + private void ThenClaimsToThingsAreReturned() + { + _result.Count.ShouldBeGreaterThan(0); + } + private void GivenTheFollowingDictionary(Dictionary claimsToThings) + { + _claimsToThings = claimsToThings; + } + + private void GivenTheConfigHeaderExtractorReturns(Response expected) + { + _configParser + .Setup(x => x.Extract(It.IsAny(), It.IsAny())) + .Returns(expected); + } + + private void ThenNoClaimsToThingsAreReturned() + { + _result.Count.ShouldBe(0); + } + + private void WhenIGetTheThings() + { + _result = _claimsToThingsCreator.Create(_claimsToThings); + } + + private void ThenTheConfigParserIsCalledCorrectly() + { + _configParser + .Verify(x => x.Extract(_claimsToThings.First().Key, _claimsToThings.First().Value), Times.Once); + } + } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Configuration/ConfigurationFluentValidationTests.cs b/test/Ocelot.UnitTests/Configuration/ConfigurationFluentValidationTests.cs index ea476ca8a..5664192d6 100644 --- a/test/Ocelot.UnitTests/Configuration/ConfigurationFluentValidationTests.cs +++ b/test/Ocelot.UnitTests/Configuration/ConfigurationFluentValidationTests.cs @@ -1,497 +1,659 @@ -using System.Collections.Generic; -using System.Security.Claims; -using System.Text.Encodings.Web; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authentication; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Moq; -using Ocelot.Configuration.File; -using Ocelot.Configuration.Validator; -using Ocelot.Responses; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Configuration -{ - public class ConfigurationFluentValidationTests - { - private readonly IConfigurationValidator _configurationValidator; - private FileConfiguration _fileConfiguration; - private Response _result; - private Mock _provider; - - public ConfigurationFluentValidationTests() - { - _provider = new Mock(); - _configurationValidator = new FileConfigurationFluentValidator(_provider.Object); - } - - [Fact] - public void configuration_is_invalid_if_scheme_in_downstream_or_upstream_template() - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "http://www.bbc.co.uk/api/products/{productId}", - UpstreamPathTemplate = "http://asdf.com" - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsNotValid()) - .Then(x => x.ThenTheErrorIs()) - .And(x => x.ThenTheErrorMessageAtPositionIs(0, "Downstream Path Template http://www.bbc.co.uk/api/products/{productId} doesnt start with forward slash")) - .And(x => x.ThenTheErrorMessageAtPositionIs(1, "Upstream Path Template http://asdf.com contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature.")) - .And(x => x.ThenTheErrorMessageAtPositionIs(2, "Downstream Path Template http://www.bbc.co.uk/api/products/{productId} contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature.")) - .And(x => x.ThenTheErrorMessageAtPositionIs(3, "Upstream Path Template http://asdf.com doesnt start with forward slash")) - .And(x => x.ThenTheErrorMessageAtPositionIs(4, "Downstream Path Template http://www.bbc.co.uk/api/products/{productId} contains scheme")) - .And(x => x.ThenTheErrorMessageAtPositionIs(5, "Upstream Path Template http://asdf.com contains scheme")) - .BDDfy(); - } - - [Fact] - public void configuration_is_valid_with_one_reroute() - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "/asdf/", - DownstreamHost = "bbc.co.uk" - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsValid()) - .BDDfy(); - } - - [Fact] - public void configuration_is_invalid_without_slash_prefix_downstream_path_template() - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "api/products/", - UpstreamPathTemplate = "/asdf/" - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsNotValid()) - .And(x => x.ThenTheErrorMessageAtPositionIs(0, "Downstream Path Template api/products/ doesnt start with forward slash")) - .BDDfy(); - } - - [Fact] - public void configuration_is_invalid_without_slash_prefix_upstream_path_template() - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "api/prod/", - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsNotValid()) - .And(x => x.ThenTheErrorMessageAtPositionIs(0, "Upstream Path Template api/prod/ doesnt start with forward slash")) - .BDDfy(); - } - - [Fact] - public void configuration_is_invalid_if_upstream_url_contains_forward_slash_then_another_forward_slash() - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "//api/prod/", - DownstreamHost = "bbc.co.uk", - DownstreamPort = 80 - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsNotValid()) - .And(x => x.ThenTheErrorMessageAtPositionIs(0, "Upstream Path Template //api/prod/ contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature.")) - .BDDfy(); - } - - [Fact] - public void configuration_is_invalid_if_downstream_url_contains_forward_slash_then_another_forward_slash() - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "//api/products/", - UpstreamPathTemplate = "/api/prod/", - DownstreamHost = "bbc.co.uk", - DownstreamPort = 80 - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsNotValid()) - .And(x => x.ThenTheErrorMessageAtPositionIs(0, "Downstream Path Template //api/products/ contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature.")) - .BDDfy(); - } - - [Fact] - public void configuration_is_valid_with_valid_authentication_provider() - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "/asdf/", - DownstreamHost = "bbc.co.uk", - AuthenticationOptions = new FileAuthenticationOptions() - { - AuthenticationProviderKey = "Test" - } - } - } - })) - .And(x => x.GivenTheAuthSchemeExists("Test")) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsValid()) - .BDDfy(); - } - - [Fact] - public void configuration_is_invalid_with_invalid_authentication_provider() - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "/asdf/", - AuthenticationOptions = new FileAuthenticationOptions() - { - AuthenticationProviderKey = "Test" - } } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsNotValid()) - .And(x => x.ThenTheErrorMessageAtPositionIs(0, "AuthenticationProviderKey:Test,AllowedScopes:[] is unsupported authentication provider")) - .BDDfy(); - } - - [Fact] - public void configuration_is_not_valid_with_duplicate_reroutes_all_verbs() - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "/asdf/", - DownstreamHost = "bb.co.uk" - }, - new FileReRoute - { - DownstreamPathTemplate = "/www/test/", - UpstreamPathTemplate = "/asdf/", - DownstreamHost = "bb.co.uk" - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsNotValid()) - .And(x => x.ThenTheErrorMessageAtPositionIs(0, "reRoute /asdf/ has duplicate")) - .BDDfy(); - } - - [Fact] - public void configuration_is_not_valid_with_duplicate_reroutes_specific_verbs() - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "/asdf/", - DownstreamHost = "bbc.co.uk", - UpstreamHttpMethod = new List {"Get"} - }, - new FileReRoute - { - DownstreamPathTemplate = "/www/test/", - UpstreamPathTemplate = "/asdf/", - DownstreamHost = "bbc.co.uk", - UpstreamHttpMethod = new List {"Get"} - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsNotValid()) - .And(x => x.ThenTheErrorMessageAtPositionIs(0, "reRoute /asdf/ has duplicate")) - .BDDfy(); - } - - [Fact] - public void configuration_is_valid_with_duplicate_reroutes_different_verbs() - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "/asdf/", - UpstreamHttpMethod = new List {"Get"}, - DownstreamHost = "bbc.co.uk", - }, - new FileReRoute - { - DownstreamPathTemplate = "/www/test/", - UpstreamPathTemplate = "/asdf/", - UpstreamHttpMethod = new List {"Post"}, - DownstreamHost = "bbc.co.uk", - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsValid()) - .BDDfy(); - } - - [Fact] - public void configuration_is_invalid_with_invalid_rate_limit_configuration() - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "/asdf/", - UpstreamHttpMethod = new List {"Get"}, - DownstreamHost = "bbc.co.uk", - RateLimitOptions = new FileRateLimitRule - { - Period = "1x", - EnableRateLimiting = true - } - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsNotValid()) - .And(x => x.ThenTheErrorMessageAtPositionIs(0, "RateLimitOptions.Period does not contains (s,m,h,d)")) - .BDDfy(); - } - - [Fact] - public void configuration_is_valid_with_valid_rate_limit_configuration() - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "/asdf/", - UpstreamHttpMethod = new List {"Get"}, - DownstreamHost = "bbc.co.uk", - RateLimitOptions = new FileRateLimitRule - { - Period = "1d", - EnableRateLimiting = true - } - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsValid()) - .BDDfy(); - } - - [Theory] - [InlineData(null)] - [InlineData("")] - public void configuration_is_invalid_with_using_service_discovery_and_no_service_name(string serviceName) - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "/asdf/", - UpstreamHttpMethod = new List {"Get"}, - UseServiceDiscovery = true, - ServiceName = serviceName - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsNotValid()) - .And(x => x.ThenTheErrorMessageAtPositionIs(0, "ServiceName cannot be empty or null when using service discovery or Ocelot cannot look up your service!")) - .BDDfy(); - } - - [Fact] - public void configuration_is_valid_with_using_service_discovery_and_service_name() - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "/asdf/", - UpstreamHttpMethod = new List {"Get"}, - UseServiceDiscovery = true, - ServiceName = "Test" - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsValid()) - .BDDfy(); - } - - - [Theory] - [InlineData(null)] - [InlineData("")] - public void configuration_is_invalid_when_not_using_service_discovery_and_host(string downstreamHost) - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "/asdf/", - UpstreamHttpMethod = new List {"Get"}, - UseServiceDiscovery = false, - DownstreamHost = downstreamHost - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsNotValid()) - .And(x => x.ThenTheErrorMessageAtPositionIs(0, "When not using service discover DownstreamHost must be set or Ocelot cannot find your service!")) - .BDDfy(); - } - - [Fact] - public void configuration_is_valid_when_not_using_service_discovery_and_host_is_set() - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "/asdf/", - UpstreamHttpMethod = new List {"Get"}, - UseServiceDiscovery = false, - DownstreamHost = "bbc.co.uk" - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsValid()) - .BDDfy(); - } - - - private void GivenAConfiguration(FileConfiguration fileConfiguration) - { - _fileConfiguration = fileConfiguration; - } - - private void WhenIValidateTheConfiguration() - { - _result = _configurationValidator.IsValid(_fileConfiguration).Result; - } - - private void ThenTheResultIsValid() - { - _result.Data.IsError.ShouldBeFalse(); - } - - private void ThenTheResultIsNotValid() - { - _result.Data.IsError.ShouldBeTrue(); - } - - private void ThenTheErrorIs() - { - _result.Data.Errors[0].ShouldBeOfType(); - } - - private void ThenTheErrorMessageAtPositionIs(int index, string expected) - { - _result.Data.Errors[index].Message.ShouldBe(expected); - } - - private void GivenTheAuthSchemeExists(string name) - { - _provider.Setup(x => x.GetAllSchemesAsync()).ReturnsAsync(new List - { - new AuthenticationScheme(name, name, typeof(TestHandler)) - }); - } - - private class TestOptions : AuthenticationSchemeOptions - { - } - - private class TestHandler : AuthenticationHandler - { - public TestHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) - { - } - - protected override Task HandleAuthenticateAsync() - { - var principal = new ClaimsPrincipal(); - return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(principal, new AuthenticationProperties(), Scheme.Name))); - } - } - } -} +using System.Collections.Generic; +using System.Security.Claims; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Moq; +using Ocelot.Configuration.File; +using Ocelot.Configuration.Validator; +using Ocelot.Responses; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class ConfigurationFluentValidationTests + { + private readonly IConfigurationValidator _configurationValidator; + private FileConfiguration _fileConfiguration; + private Response _result; + private Mock _provider; + + public ConfigurationFluentValidationTests() + { + _provider = new Mock(); + _configurationValidator = new FileConfigurationFluentValidator(_provider.Object); + } + + [Fact] + public void configuration_is_invalid_if_scheme_in_downstream_or_upstream_template() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "http://www.bbc.co.uk/api/products/{productId}", + UpstreamPathTemplate = "http://asdf.com" + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .Then(x => x.ThenTheErrorIs()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "Downstream Path Template http://www.bbc.co.uk/api/products/{productId} doesnt start with forward slash")) + .And(x => x.ThenTheErrorMessageAtPositionIs(1, "Upstream Path Template http://asdf.com contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature.")) + .And(x => x.ThenTheErrorMessageAtPositionIs(2, "Downstream Path Template http://www.bbc.co.uk/api/products/{productId} contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature.")) + .And(x => x.ThenTheErrorMessageAtPositionIs(3, "Upstream Path Template http://asdf.com doesnt start with forward slash")) + .And(x => x.ThenTheErrorMessageAtPositionIs(4, "Downstream Path Template http://www.bbc.co.uk/api/products/{productId} contains scheme")) + .And(x => x.ThenTheErrorMessageAtPositionIs(5, "Upstream Path Template http://asdf.com contains scheme")) + .BDDfy(); + } + + [Fact] + public void configuration_is_valid_with_one_reroute() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "/asdf/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "bbc.co.uk" + } + }, + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsValid()) + .BDDfy(); + } + + [Fact] + public void configuration_is_invalid_without_slash_prefix_downstream_path_template() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "api/products/", + UpstreamPathTemplate = "/asdf/" + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "Downstream Path Template api/products/ doesnt start with forward slash")) + .BDDfy(); + } + + [Fact] + public void configuration_is_invalid_without_slash_prefix_upstream_path_template() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "api/prod/", + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "Upstream Path Template api/prod/ doesnt start with forward slash")) + .BDDfy(); + } + + [Fact] + public void configuration_is_invalid_if_upstream_url_contains_forward_slash_then_another_forward_slash() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "//api/prod/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "bbc.co.uk", + Port = 80 + } + }, + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "Upstream Path Template //api/prod/ contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature.")) + .BDDfy(); + } + + [Fact] + public void configuration_is_invalid_if_downstream_url_contains_forward_slash_then_another_forward_slash() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "//api/products/", + UpstreamPathTemplate = "/api/prod/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "bbc.co.uk", + Port = 80 + } + }, + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "Downstream Path Template //api/products/ contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature.")) + .BDDfy(); + } + + [Fact] + public void configuration_is_valid_with_valid_authentication_provider() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "/asdf/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "bbc.co.uk", + } + }, + AuthenticationOptions = new FileAuthenticationOptions() + { + AuthenticationProviderKey = "Test" + } + } + } + })) + .And(x => x.GivenTheAuthSchemeExists("Test")) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsValid()) + .BDDfy(); + } + + [Fact] + public void configuration_is_invalid_with_invalid_authentication_provider() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "/asdf/", + AuthenticationOptions = new FileAuthenticationOptions() + { + AuthenticationProviderKey = "Test" + } } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "AuthenticationProviderKey:Test,AllowedScopes:[] is unsupported authentication provider")) + .BDDfy(); + } + + [Fact] + public void configuration_is_not_valid_with_duplicate_reroutes_all_verbs() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "/asdf/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "bb.co.uk" + } + }, + }, + new FileReRoute + { + DownstreamPathTemplate = "/www/test/", + UpstreamPathTemplate = "/asdf/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "bb.co.uk" + } + }, + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "reRoute /asdf/ has duplicate")) + .BDDfy(); + } + + [Fact] + public void configuration_is_not_valid_with_duplicate_reroutes_specific_verbs() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "/asdf/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "bbc.co.uk", + } + }, + UpstreamHttpMethod = new List {"Get"} + }, + new FileReRoute + { + DownstreamPathTemplate = "/www/test/", + UpstreamPathTemplate = "/asdf/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "bbc.co.uk", + } + }, + UpstreamHttpMethod = new List {"Get"} + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "reRoute /asdf/ has duplicate")) + .BDDfy(); + } + + [Fact] + public void configuration_is_valid_with_duplicate_reroutes_different_verbs() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "/asdf/", + UpstreamHttpMethod = new List {"Get"}, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "bbc.co.uk", + } + }, + }, + new FileReRoute + { + DownstreamPathTemplate = "/www/test/", + UpstreamPathTemplate = "/asdf/", + UpstreamHttpMethod = new List {"Post"}, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "bbc.co.uk", + } + }, + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsValid()) + .BDDfy(); + } + + [Fact] + public void configuration_is_invalid_with_invalid_rate_limit_configuration() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "/asdf/", + UpstreamHttpMethod = new List {"Get"}, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "bbc.co.uk", + } + }, + RateLimitOptions = new FileRateLimitRule + { + Period = "1x", + EnableRateLimiting = true + } + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "RateLimitOptions.Period does not contains (s,m,h,d)")) + .BDDfy(); + } + + [Fact] + public void configuration_is_valid_with_valid_rate_limit_configuration() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "/asdf/", + UpstreamHttpMethod = new List {"Get"}, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "bbc.co.uk", + } + }, + RateLimitOptions = new FileRateLimitRule + { + Period = "1d", + EnableRateLimiting = true + } + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsValid()) + .BDDfy(); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void configuration_is_invalid_with_using_service_discovery_and_no_service_name(string serviceName) + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "/asdf/", + UpstreamHttpMethod = new List {"Get"}, + UseServiceDiscovery = true, + ServiceName = serviceName + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "ServiceName cannot be empty or null when using service discovery or Ocelot cannot look up your service!")) + .BDDfy(); + } + + [Fact] + public void configuration_is_valid_with_using_service_discovery_and_service_name() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "/asdf/", + UpstreamHttpMethod = new List {"Get"}, + UseServiceDiscovery = true, + ServiceName = "Test" + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsValid()) + .BDDfy(); + } + + + [Theory] + [InlineData(null)] + [InlineData("")] + public void configuration_is_invalid_when_not_using_service_discovery_and_host(string downstreamHost) + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "/asdf/", + UpstreamHttpMethod = new List {"Get"}, + UseServiceDiscovery = false, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = downstreamHost, + } + }, + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "When not using service discovery Host must be set on DownstreamHostAndPorts if you are not using ReRoute.Host or Ocelot cannot find your service!")) + .BDDfy(); + } + + [Fact] + public void configuration_is_valid_when_not_using_service_discovery_and_host_is_set() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "/asdf/", + UpstreamHttpMethod = new List {"Get"}, + UseServiceDiscovery = false, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "bbc.co.uk" + } + }, + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsValid()) + .BDDfy(); + } + + [Fact] + public void configuration_is_valid_when_no_downstream_but_has_host_and_port() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "/asdf/", + UpstreamHttpMethod = new List {"Get"}, + UseServiceDiscovery = false, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "test" + } + } + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsValid()) + .BDDfy(); + } + + [Fact] + public void configuration_is_not_valid_when_no_host_and_port() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "/asdf/", + UpstreamHttpMethod = new List {"Get"}, + UseServiceDiscovery = false, + DownstreamHostAndPorts = new List + { + } + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "When not using service discovery DownstreamHostAndPorts must be set and not empty or Ocelot cannot find your service!")) + .BDDfy(); + } + [Fact] + public void configuration_is_not_valid_when_host_and_port_is_empty() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "/asdf/", + UpstreamHttpMethod = new List {"Get"}, + UseServiceDiscovery = false, + DownstreamHostAndPorts = new List + { + new FileHostAndPort() + } + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "When not using service discovery Host must be set on DownstreamHostAndPorts if you are not using ReRoute.Host or Ocelot cannot find your service!")) + .BDDfy(); + } + + + private void GivenAConfiguration(FileConfiguration fileConfiguration) + { + _fileConfiguration = fileConfiguration; + } + + private void WhenIValidateTheConfiguration() + { + _result = _configurationValidator.IsValid(_fileConfiguration).Result; + } + + private void ThenTheResultIsValid() + { + _result.Data.IsError.ShouldBeFalse(); + } + + private void ThenTheResultIsNotValid() + { + _result.Data.IsError.ShouldBeTrue(); + } + + private void ThenTheErrorIs() + { + _result.Data.Errors[0].ShouldBeOfType(); + } + + private void ThenTheErrorMessageAtPositionIs(int index, string expected) + { + _result.Data.Errors[index].Message.ShouldBe(expected); + } + + private void GivenTheAuthSchemeExists(string name) + { + _provider.Setup(x => x.GetAllSchemesAsync()).ReturnsAsync(new List + { + new AuthenticationScheme(name, name, typeof(TestHandler)) + }); + } + + private class TestOptions : AuthenticationSchemeOptions + { + } + + private class TestHandler : AuthenticationHandler + { + public TestHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) + { + } + + protected override Task HandleAuthenticateAsync() + { + var principal = new ClaimsPrincipal(); + return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(principal, new AuthenticationProperties(), Scheme.Name))); + } + } + } +} diff --git a/test/Ocelot.UnitTests/Configuration/ConsulFileConfigurationPollerTests.cs b/test/Ocelot.UnitTests/Configuration/ConsulFileConfigurationPollerTests.cs index 5569448db..64104871a 100644 --- a/test/Ocelot.UnitTests/Configuration/ConsulFileConfigurationPollerTests.cs +++ b/test/Ocelot.UnitTests/Configuration/ConsulFileConfigurationPollerTests.cs @@ -1,89 +1,95 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using Moq; -using Ocelot.Configuration.File; -using Ocelot.Configuration.Repository; -using Ocelot.Configuration.Setter; -using Ocelot.Logging; -using Ocelot.Responses; -using TestStack.BDDfy; -using Xunit; -using Shouldly; -using static Ocelot.UnitTests.Wait; - - -namespace Ocelot.UnitTests.Configuration -{ - public class ConsulFileConfigurationPollerTests : IDisposable - { - private ConsulFileConfigurationPoller _poller; - private Mock _factory; - private Mock _repo; - private Mock _setter; - private FileConfiguration _fileConfig; - - public ConsulFileConfigurationPollerTests() - { - var logger = new Mock(); - _factory = new Mock(); - _factory.Setup(x => x.CreateLogger()).Returns(logger.Object); - _repo = new Mock(); - _setter = new Mock(); - _fileConfig = new FileConfiguration(); - _repo.Setup(x => x.Get()).ReturnsAsync(new OkResponse(_fileConfig)); - _poller = new ConsulFileConfigurationPoller(_factory.Object, _repo.Object, _setter.Object); - } - public void Dispose() - { - _poller.Dispose(); - } - - [Fact] - public void should_start() - { - this.Given(x => ThenTheSetterIsCalled(_fileConfig)) - .BDDfy(); - } - - [Fact] - public void should_call_setter_when_gets_new_config() - { - - var newConfig = new FileConfiguration { - ReRoutes = new List - { - new FileReRoute - { - DownstreamHost = "test" - } - } - }; - - this.Given(x => WhenTheConfigIsChangedInConsul(newConfig)) - .Then(x => ThenTheSetterIsCalled(newConfig)) - .BDDfy(); - } - - private void WhenTheConfigIsChangedInConsul(FileConfiguration newConfig) - { - _repo.Setup(x => x.Get()).ReturnsAsync(new OkResponse(newConfig)); - } - - private void ThenTheSetterIsCalled(FileConfiguration fileConfig) - { - var result = WaitFor(2000).Until(() => { - try - { - _setter.Verify(x => x.Set(fileConfig), Times.Once); - return true; - } - catch(Exception ex) - { - return false; - } - }); - result.ShouldBeTrue(); - } - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Moq; +using Ocelot.Configuration.File; +using Ocelot.Configuration.Repository; +using Ocelot.Configuration.Setter; +using Ocelot.Logging; +using Ocelot.Responses; +using TestStack.BDDfy; +using Xunit; +using Shouldly; +using static Ocelot.UnitTests.Wait; + + +namespace Ocelot.UnitTests.Configuration +{ + public class ConsulFileConfigurationPollerTests : IDisposable + { + private ConsulFileConfigurationPoller _poller; + private Mock _factory; + private Mock _repo; + private Mock _setter; + private FileConfiguration _fileConfig; + + public ConsulFileConfigurationPollerTests() + { + var logger = new Mock(); + _factory = new Mock(); + _factory.Setup(x => x.CreateLogger()).Returns(logger.Object); + _repo = new Mock(); + _setter = new Mock(); + _fileConfig = new FileConfiguration(); + _repo.Setup(x => x.Get()).ReturnsAsync(new OkResponse(_fileConfig)); + _poller = new ConsulFileConfigurationPoller(_factory.Object, _repo.Object, _setter.Object); + } + public void Dispose() + { + _poller.Dispose(); + } + + [Fact] + public void should_start() + { + this.Given(x => ThenTheSetterIsCalled(_fileConfig)) + .BDDfy(); + } + + [Fact] + public void should_call_setter_when_gets_new_config() + { + + var newConfig = new FileConfiguration { + ReRoutes = new List + { + new FileReRoute + { + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "test" + } + }, + } + } + }; + + this.Given(x => WhenTheConfigIsChangedInConsul(newConfig)) + .Then(x => ThenTheSetterIsCalled(newConfig)) + .BDDfy(); + } + + private void WhenTheConfigIsChangedInConsul(FileConfiguration newConfig) + { + _repo.Setup(x => x.Get()).ReturnsAsync(new OkResponse(newConfig)); + } + + private void ThenTheSetterIsCalled(FileConfiguration fileConfig) + { + var result = WaitFor(2000).Until(() => { + try + { + _setter.Verify(x => x.Set(fileConfig), Times.Once); + return true; + } + catch(Exception ex) + { + return false; + } + }); + result.ShouldBeTrue(); + } + } +} diff --git a/test/Ocelot.UnitTests/Configuration/DownstreamAddressesCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/DownstreamAddressesCreatorTests.cs new file mode 100644 index 000000000..060398f9e --- /dev/null +++ b/test/Ocelot.UnitTests/Configuration/DownstreamAddressesCreatorTests.cs @@ -0,0 +1,122 @@ +using System.Collections.Generic; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class DownstreamAddressesCreatorTests + { + public DownstreamAddressesCreator _creator; + private FileReRoute _reRoute; + private List _result; + + public DownstreamAddressesCreatorTests() + { + _creator = new DownstreamAddressesCreator(); + } + + [Fact] + public void should_do_nothing() + { + var reRoute = new FileReRoute + { + }; + + var expected = new List + { + }; + + this.Given(x => GivenTheFollowingReRoute(reRoute)) + .When(x => WhenICreate()) + .Then(x => TheThenFollowingIsReturned(expected)) + .BDDfy(); + } + + [Fact] + public void should_create_downstream_addresses_from_old_downstream_path_and_port() + { + var reRoute = new FileReRoute + { + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "test", + Port = 80 + } + }, + }; + + var expected = new List + { + new DownstreamHostAndPort("test", 80), + }; + + this.Given(x => GivenTheFollowingReRoute(reRoute)) + .When(x => WhenICreate()) + .Then(x => TheThenFollowingIsReturned(expected)) + .BDDfy(); + } + + [Fact] + public void should_create_downstream_addresses_from_downstream_host_and_ports() + { + var reRoute = new FileReRoute + { + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "test", + Port = 80 + }, + new FileHostAndPort + { + Host = "west", + Port = 443 + } + } + }; + + var expected = new List + { + new DownstreamHostAndPort("test", 80), + new DownstreamHostAndPort("west", 443) + }; + + this.Given(x => GivenTheFollowingReRoute(reRoute)) + .When(x => WhenICreate()) + .Then(x => TheThenFollowingIsReturned(expected)) + .BDDfy(); + } + + private void GivenTheFollowingReRoute(FileReRoute reRoute) + { + _reRoute = reRoute; + } + + private void WhenICreate() + { + _result = _creator.Create(_reRoute); + } + + private void TheThenFollowingIsReturned(List expecteds) + { + _result.Count.ShouldBe(expecteds.Count); + + for (int i = 0; i < _result.Count; i++) + { + var result = _result[i]; + var expected = expecteds[i]; + + result.Host.ShouldBe(expected.Host); + result.Port.ShouldBe(expected.Port); + } + } + } +} diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index 807bb30e1..22a73e2a6 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -15,6 +15,7 @@ namespace Ocelot.UnitTests.Configuration { + using System; using Ocelot.DependencyInjection; using Ocelot.Errors; using Ocelot.UnitTests.TestData; @@ -40,6 +41,7 @@ public class FileConfigurationCreatorTests private Mock _httpHandlerOptionsCreator; private Mock _adminPath; private readonly Mock _headerFindAndReplaceCreator; + private readonly Mock _downstreamAddressesCreator; public FileConfigurationCreatorTests() { @@ -58,6 +60,7 @@ public FileConfigurationCreatorTests() _httpHandlerOptionsCreator = new Mock(); _adminPath = new Mock(); _headerFindAndReplaceCreator = new Mock(); + _downstreamAddressesCreator = new Mock(); _ocelotConfigurationCreator = new FileOcelotConfigurationCreator( _fileConfig.Object, @@ -74,7 +77,8 @@ public FileConfigurationCreatorTests() _regionCreator.Object, _httpHandlerOptionsCreator.Object, _adminPath.Object, - _headerFindAndReplaceCreator.Object); + _headerFindAndReplaceCreator.Object, + _downstreamAddressesCreator.Object); } [Fact] @@ -94,6 +98,7 @@ public void should_call_service_provider_config_creator() } })) .And(x => x.GivenTheFollowingIsReturned(serviceProviderConfig)) + .And(x => GivenTheDownstreamAddresses()) .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) .And(x => x.GivenTheConfigIsValid()) .When(x => x.WhenICreateTheConfig()) @@ -113,7 +118,13 @@ public void should_call_region_creator() { new FileReRoute { - DownstreamHost = "127.0.0.1", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "127.0.0.1", + } + }, UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = new List { "Get" }, @@ -125,6 +136,7 @@ public void should_call_region_creator() }, })) .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) + .And(x => GivenTheDownstreamAddresses()) .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) .And(x => x.GivenTheConfigIsValid()) .And(x => x.GivenTheFollowingRegionIsReturned("region")) @@ -146,7 +158,13 @@ public void should_call_rate_limit_options_creator() { new FileReRoute { - DownstreamHost = "127.0.0.1", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "127.0.0.1", + } + }, UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = new List { "Get" }, @@ -154,6 +172,7 @@ public void should_call_rate_limit_options_creator() }, })) .And(x => x.GivenTheConfigIsValid()) + .And(x => GivenTheDownstreamAddresses()) .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) .When(x => x.WhenICreateTheConfig()) @@ -180,7 +199,13 @@ public void should_call_qos_options_creator() { new FileReRoute { - DownstreamHost = "127.0.0.1", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "127.0.0.1", + } + }, UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = new List { "Get" }, @@ -194,6 +219,7 @@ public void should_call_qos_options_creator() }, })) .And(x => x.GivenTheConfigIsValid()) + .And(x => GivenTheDownstreamAddresses()) .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) .And(x => x.GivenTheFollowingOptionsAreReturned(serviceOptions)) .And(x => x.GivenTheQosOptionsCreatorReturns(expected)) @@ -214,7 +240,13 @@ public void should_use_downstream_host() { new FileReRoute { - DownstreamHost = "127.0.0.1", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "127.0.0.1", + } + }, UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = new List { "Get" }, @@ -222,13 +254,14 @@ public void should_use_downstream_host() }, })) .And(x => x.GivenTheConfigIsValid()) + .And(x => GivenTheDownstreamAddresses()) .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) .When(x => x.WhenICreateTheConfig()) .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() - .WithDownstreamHost("127.0.0.1") + .WithDownstreamAddresses(new List(){new DownstreamHostAndPort("127.0.0.1", 80) }) .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod(new List { "Get" }) @@ -257,6 +290,7 @@ public void should_use_downstream_scheme() }, })) .And(x => x.GivenTheConfigIsValid()) + .And(x => GivenTheDownstreamAddresses()) .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) .When(x => x.WhenICreateTheConfig()) @@ -300,6 +334,7 @@ public void should_use_service_discovery_for_downstream_service_host() } })) .And(x => x.GivenTheConfigIsValid()) + .And(x => GivenTheDownstreamAddresses()) .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) .When(x => x.WhenICreateTheConfig()) @@ -336,6 +371,7 @@ public void should_not_use_service_discovery_for_downstream_host_url_when_no_ser } })) .And(x => x.GivenTheConfigIsValid()) + .And(x => GivenTheDownstreamAddresses()) .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) .When(x => x.WhenICreateTheConfig()) @@ -371,6 +407,7 @@ public void should_call_template_pattern_creator_correctly() } })) .And(x => x.GivenTheConfigIsValid()) + .And(x => GivenTheDownstreamAddresses()) .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) .And(x => x.GivenTheUpstreamTemplatePatternCreatorReturns("(?i)/api/products/.*/$")) @@ -411,6 +448,7 @@ public void should_call_request_id_creator() } })) .And(x => x.GivenTheConfigIsValid()) + .And(x => GivenTheDownstreamAddresses()) .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) .And(x => x.GivenTheRequestIdCreatorReturns("blahhhh")) @@ -441,7 +479,13 @@ public void should_call_httpHandler_creator() { new FileReRoute { - DownstreamHost = "127.0.0.1", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "127.0.0.1", + } + }, UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = new List { "Get" } @@ -449,6 +493,7 @@ public void should_call_httpHandler_creator() }, })) .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) + .And(x => GivenTheDownstreamAddresses()) .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) .And(x => x.GivenTheConfigIsValid()) .And(x => x.GivenTheFollowingHttpHandlerOptionsAreReturned(httpHandlerOptions)) @@ -484,6 +529,7 @@ public void should_create_with_headers_to_extract(FileConfiguration fileConfig) }; this.Given(x => x.GivenTheConfigIs(fileConfig)) + .And(x => GivenTheDownstreamAddresses()) .And(x => x.GivenTheConfigIsValid()) .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) .And(x => x.GivenTheAuthOptionsCreatorReturns(authenticationOptions)) @@ -519,6 +565,7 @@ public void should_create_with_authentication_properties(FileConfiguration fileC }; this.Given(x => x.GivenTheConfigIs(fileConfig)) + .And(x => GivenTheDownstreamAddresses()) .And(x => x.GivenTheConfigIsValid()) .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) @@ -536,6 +583,7 @@ public void should_return_validation_errors() var errors = new List {new FileValidationFailedError("some message")}; this.Given(x => x.GivenTheConfigIs(new FileConfiguration())) + .And(x => GivenTheDownstreamAddresses()) .And(x => x.GivenTheConfigIsInvalid(errors)) .When(x => x.WhenICreateTheConfig()) .Then(x => x.ThenTheErrorsAreReturned(errors)) @@ -719,5 +767,10 @@ private void ThenTheHttpHandlerOptionsCreatorIsCalledCorrectly() { _httpHandlerOptionsCreator.Verify(x => x.Create(_fileConfiguration.ReRoutes[0]), Times.Once()); } + + private void GivenTheDownstreamAddresses() + { + _downstreamAddressesCreator.Setup(x => x.Create(It.IsAny())).Returns(new List()); + } } } diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs index 39b3125f4..be3df6cc1 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs @@ -1,62 +1,62 @@ -using System; -using System.Collections.Generic; -using Moq; -using Ocelot.Configuration; -using Ocelot.Configuration.File; -using Ocelot.Responses; -using Shouldly; -using TestStack.BDDfy; -using Xunit; -using Newtonsoft.Json; -using System.IO; -using Ocelot.Configuration.Provider; -using Ocelot.Configuration.Repository; - -namespace Ocelot.UnitTests.Configuration -{ - public class FileConfigurationProviderTests - { - private readonly IFileConfigurationProvider _provider; - private Mock _repo; - private FileConfiguration _result; - private FileConfiguration _fileConfiguration; - - public FileConfigurationProviderTests() - { - _repo = new Mock(); - _provider = new FileConfigurationProvider(_repo.Object); - } - - [Fact] - public void should_return_file_configuration() - { - var config = new FileConfiguration(); - - this.Given(x => x.GivenTheConfigurationIs(config)) - .When(x => x.WhenIGetTheReRoutes()) - .Then(x => x.ThenTheRepoIsCalledCorrectly()) - .BDDfy(); - } - - - - private void GivenTheConfigurationIs(FileConfiguration fileConfiguration) - { - _fileConfiguration = fileConfiguration; - _repo - .Setup(x => x.Get()) - .ReturnsAsync(new OkResponse(fileConfiguration)); - } - - private void WhenIGetTheReRoutes() - { - _result = _provider.Get().Result.Data; - } - - private void ThenTheRepoIsCalledCorrectly() - { - _repo - .Verify(x => x.Get(), Times.Once); - } - } +using System; +using System.Collections.Generic; +using Moq; +using Ocelot.Configuration; +using Ocelot.Configuration.File; +using Ocelot.Responses; +using Shouldly; +using TestStack.BDDfy; +using Xunit; +using Newtonsoft.Json; +using System.IO; +using Ocelot.Configuration.Provider; +using Ocelot.Configuration.Repository; + +namespace Ocelot.UnitTests.Configuration +{ + public class FileConfigurationProviderTests + { + private readonly IFileConfigurationProvider _provider; + private Mock _repo; + private FileConfiguration _result; + private FileConfiguration _fileConfiguration; + + public FileConfigurationProviderTests() + { + _repo = new Mock(); + _provider = new FileConfigurationProvider(_repo.Object); + } + + [Fact] + public void should_return_file_configuration() + { + var config = new FileConfiguration(); + + this.Given(x => x.GivenTheConfigurationIs(config)) + .When(x => x.WhenIGetTheReRoutes()) + .Then(x => x.ThenTheRepoIsCalledCorrectly()) + .BDDfy(); + } + + + + private void GivenTheConfigurationIs(FileConfiguration fileConfiguration) + { + _fileConfiguration = fileConfiguration; + _repo + .Setup(x => x.Get()) + .ReturnsAsync(new OkResponse(fileConfiguration)); + } + + private void WhenIGetTheReRoutes() + { + _result = _provider.Get().Result.Data; + } + + private void ThenTheRepoIsCalledCorrectly() + { + _repo + .Verify(x => x.Get(), Times.Once); + } + } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationRepositoryTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationRepositoryTests.cs index bd9558791..e74d00676 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationRepositoryTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationRepositoryTests.cs @@ -1,199 +1,225 @@ -using System; -using System.Collections.Generic; -using Moq; -using Ocelot.Configuration.File; -using Shouldly; -using TestStack.BDDfy; -using Xunit; -using Newtonsoft.Json; -using System.IO; -using Microsoft.AspNetCore.Hosting; -using Ocelot.Configuration.Repository; - -namespace Ocelot.UnitTests.Configuration -{ - public class FileConfigurationRepositoryTests - { - private readonly Mock _hostingEnvironment = new Mock(); - private IFileConfigurationRepository _repo; - private FileConfiguration _result; - private FileConfiguration _fileConfiguration; - private string _environmentName = "DEV"; - - public FileConfigurationRepositoryTests() - { - _hostingEnvironment.Setup(he => he.EnvironmentName).Returns(_environmentName); - _repo = new FileConfigurationRepository(_hostingEnvironment.Object); - } - - [Fact] - public void should_return_file_configuration() - { - var config = FakeFileConfigurationForGet(); - - this.Given(x => x.GivenTheConfigurationIs(config)) - .When(x => x.WhenIGetTheReRoutes()) - .Then(x => x.ThenTheFollowingIsReturned(config)) - .BDDfy(); - } - - [Fact] - public void should_return_file_configuration_if_environment_name_is_unavailable() - { - var config = FakeFileConfigurationForGet(); - - this.Given(x => x.GivenTheEnvironmentNameIsUnavailable()) - .And(x => x.GivenTheConfigurationIs(config)) - .When(x => x.WhenIGetTheReRoutes()) - .Then(x => x.ThenTheFollowingIsReturned(config)) - .BDDfy(); - } - - [Fact] - public void should_set_file_configuration() - { - var config = FakeFileConfigurationForSet(); - - this.Given(x => GivenIHaveAConfiguration(config)) - .When(x => WhenISetTheConfiguration()) - .Then(x => ThenTheConfigurationIsStoredAs(config)) - .BDDfy(); - } - - [Fact] - public void should_set_file_configuration_if_environment_name_is_unavailable() - { - var config = FakeFileConfigurationForSet(); - this.Given(x => GivenIHaveAConfiguration(config)) - .And(x => GivenTheEnvironmentNameIsUnavailable()) - .When(x => WhenISetTheConfiguration()) - .Then(x => ThenTheConfigurationIsStoredAs(config)) - .BDDfy(); - } - - private void GivenTheEnvironmentNameIsUnavailable() - { - _environmentName = null; - _hostingEnvironment.Setup(he => he.EnvironmentName).Returns(_environmentName); - _repo = new FileConfigurationRepository(_hostingEnvironment.Object); - } - - private void GivenIHaveAConfiguration(FileConfiguration fileConfiguration) - { - _fileConfiguration = fileConfiguration; - } - - private void WhenISetTheConfiguration() - { - _repo.Set(_fileConfiguration); - _result = _repo.Get().Result.Data; - } - - private void ThenTheConfigurationIsStoredAs(FileConfiguration expected) - { - _result.GlobalConfiguration.RequestIdKey.ShouldBe(expected.GlobalConfiguration.RequestIdKey); - _result.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Host); - _result.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Port); - - for(var i = 0; i < _result.ReRoutes.Count; i++) - { - _result.ReRoutes[i].DownstreamHost.ShouldBe(expected.ReRoutes[i].DownstreamHost); - _result.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expected.ReRoutes[i].DownstreamPathTemplate); - _result.ReRoutes[i].DownstreamPort.ShouldBe(expected.ReRoutes[i].DownstreamPort); - _result.ReRoutes[i].DownstreamScheme.ShouldBe(expected.ReRoutes[i].DownstreamScheme); - } - } - - private void GivenTheConfigurationIs(FileConfiguration fileConfiguration) - { - var configurationPath = $"{AppContext.BaseDirectory}/configuration{(string.IsNullOrEmpty(_environmentName) ? string.Empty : ".")}{_environmentName}.json"; - - var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); - - if (File.Exists(configurationPath)) - { - File.Delete(configurationPath); - } - - File.WriteAllText(configurationPath, jsonConfiguration); - } - - private void WhenIGetTheReRoutes() - { - _result = _repo.Get().Result.Data; - } - - private void ThenTheFollowingIsReturned(FileConfiguration expected) - { - _result.GlobalConfiguration.RequestIdKey.ShouldBe(expected.GlobalConfiguration.RequestIdKey); - _result.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Host); - _result.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Port); - - for(var i = 0; i < _result.ReRoutes.Count; i++) - { - _result.ReRoutes[i].DownstreamHost.ShouldBe(expected.ReRoutes[i].DownstreamHost); - _result.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expected.ReRoutes[i].DownstreamPathTemplate); - _result.ReRoutes[i].DownstreamPort.ShouldBe(expected.ReRoutes[i].DownstreamPort); - _result.ReRoutes[i].DownstreamScheme.ShouldBe(expected.ReRoutes[i].DownstreamScheme); - } - } - - private FileConfiguration FakeFileConfigurationForSet() - { - var reRoutes = new List - { - new FileReRoute - { - DownstreamHost = "123.12.12.12", - DownstreamPort = 80, - DownstreamScheme = "https", - DownstreamPathTemplate = "/asdfs/test/{test}" - } - }; - - var globalConfiguration = new FileGlobalConfiguration - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider - { - Port = 198, - Host = "blah" - } - }; - - return new FileConfiguration - { - GlobalConfiguration = globalConfiguration, - ReRoutes = reRoutes - }; - } - - private FileConfiguration FakeFileConfigurationForGet() - { - var reRoutes = new List - { - new FileReRoute - { - DownstreamHost = "localhost", - DownstreamPort = 80, - DownstreamScheme = "https", - DownstreamPathTemplate = "/test/test/{test}" - } - }; - - var globalConfiguration = new FileGlobalConfiguration - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider - { - Port = 198, - Host = "blah" - } - }; - - return new FileConfiguration - { - GlobalConfiguration = globalConfiguration, - ReRoutes = reRoutes - }; - } - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using Moq; +using Ocelot.Configuration.File; +using Shouldly; +using TestStack.BDDfy; +using Xunit; +using Newtonsoft.Json; +using System.IO; +using Microsoft.AspNetCore.Hosting; +using Ocelot.Configuration.Repository; + +namespace Ocelot.UnitTests.Configuration +{ + public class FileConfigurationRepositoryTests + { + private readonly Mock _hostingEnvironment = new Mock(); + private IFileConfigurationRepository _repo; + private FileConfiguration _result; + private FileConfiguration _fileConfiguration; + private string _environmentName = "DEV"; + + public FileConfigurationRepositoryTests() + { + _hostingEnvironment.Setup(he => he.EnvironmentName).Returns(_environmentName); + _repo = new FileConfigurationRepository(_hostingEnvironment.Object); + } + + [Fact] + public void should_return_file_configuration() + { + var config = FakeFileConfigurationForGet(); + + this.Given(x => x.GivenTheConfigurationIs(config)) + .When(x => x.WhenIGetTheReRoutes()) + .Then(x => x.ThenTheFollowingIsReturned(config)) + .BDDfy(); + } + + [Fact] + public void should_return_file_configuration_if_environment_name_is_unavailable() + { + var config = FakeFileConfigurationForGet(); + + this.Given(x => x.GivenTheEnvironmentNameIsUnavailable()) + .And(x => x.GivenTheConfigurationIs(config)) + .When(x => x.WhenIGetTheReRoutes()) + .Then(x => x.ThenTheFollowingIsReturned(config)) + .BDDfy(); + } + + [Fact] + public void should_set_file_configuration() + { + var config = FakeFileConfigurationForSet(); + + this.Given(x => GivenIHaveAConfiguration(config)) + .When(x => WhenISetTheConfiguration()) + .Then(x => ThenTheConfigurationIsStoredAs(config)) + .BDDfy(); + } + + [Fact] + public void should_set_file_configuration_if_environment_name_is_unavailable() + { + var config = FakeFileConfigurationForSet(); + this.Given(x => GivenIHaveAConfiguration(config)) + .And(x => GivenTheEnvironmentNameIsUnavailable()) + .When(x => WhenISetTheConfiguration()) + .Then(x => ThenTheConfigurationIsStoredAs(config)) + .BDDfy(); + } + + private void GivenTheEnvironmentNameIsUnavailable() + { + _environmentName = null; + _hostingEnvironment.Setup(he => he.EnvironmentName).Returns(_environmentName); + _repo = new FileConfigurationRepository(_hostingEnvironment.Object); + } + + private void GivenIHaveAConfiguration(FileConfiguration fileConfiguration) + { + _fileConfiguration = fileConfiguration; + } + + private void WhenISetTheConfiguration() + { + _repo.Set(_fileConfiguration); + _result = _repo.Get().Result.Data; + } + + private void ThenTheConfigurationIsStoredAs(FileConfiguration expecteds) + { + _result.GlobalConfiguration.RequestIdKey.ShouldBe(expecteds.GlobalConfiguration.RequestIdKey); + _result.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Host); + _result.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Port); + + for(var i = 0; i < _result.ReRoutes.Count; i++) + { + for (int j = 0; j < _result.ReRoutes[i].DownstreamHostAndPorts.Count; j++) + { + var result = _result.ReRoutes[i].DownstreamHostAndPorts[j]; + var expected = expecteds.ReRoutes[i].DownstreamHostAndPorts[j]; + + result.Host.ShouldBe(expected.Host); + result.Port.ShouldBe(expected.Port); + } + + _result.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expecteds.ReRoutes[i].DownstreamPathTemplate); + _result.ReRoutes[i].DownstreamScheme.ShouldBe(expecteds.ReRoutes[i].DownstreamScheme); + } + } + + private void GivenTheConfigurationIs(FileConfiguration fileConfiguration) + { + var configurationPath = $"{AppContext.BaseDirectory}/configuration{(string.IsNullOrEmpty(_environmentName) ? string.Empty : ".")}{_environmentName}.json"; + + var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + + if (File.Exists(configurationPath)) + { + File.Delete(configurationPath); + } + + File.WriteAllText(configurationPath, jsonConfiguration); + } + + private void WhenIGetTheReRoutes() + { + _result = _repo.Get().Result.Data; + } + + private void ThenTheFollowingIsReturned(FileConfiguration expecteds) + { + _result.GlobalConfiguration.RequestIdKey.ShouldBe(expecteds.GlobalConfiguration.RequestIdKey); + _result.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Host); + _result.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Port); + + for(var i = 0; i < _result.ReRoutes.Count; i++) + { + for (int j = 0; j < _result.ReRoutes[i].DownstreamHostAndPorts.Count; j++) + { + var result = _result.ReRoutes[i].DownstreamHostAndPorts[j]; + var expected = expecteds.ReRoutes[i].DownstreamHostAndPorts[j]; + + result.Host.ShouldBe(expected.Host); + result.Port.ShouldBe(expected.Port); + } + + _result.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expecteds.ReRoutes[i].DownstreamPathTemplate); + _result.ReRoutes[i].DownstreamScheme.ShouldBe(expecteds.ReRoutes[i].DownstreamScheme); + } + } + + private FileConfiguration FakeFileConfigurationForSet() + { + var reRoutes = new List + { + new FileReRoute + { + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "123.12.12.12", + Port = 80, + } + }, + DownstreamScheme = "https", + DownstreamPathTemplate = "/asdfs/test/{test}" + } + }; + + var globalConfiguration = new FileGlobalConfiguration + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Port = 198, + Host = "blah" + } + }; + + return new FileConfiguration + { + GlobalConfiguration = globalConfiguration, + ReRoutes = reRoutes + }; + } + + private FileConfiguration FakeFileConfigurationForGet() + { + var reRoutes = new List + { + new FileReRoute + { + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 80, + } + }, + DownstreamScheme = "https", + DownstreamPathTemplate = "/test/test/{test}" + } + }; + + var globalConfiguration = new FileGlobalConfiguration + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Port = 198, + Host = "blah" + } + }; + + return new FileConfiguration + { + GlobalConfiguration = globalConfiguration, + ReRoutes = reRoutes + }; + } + } +} diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationSetterTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationSetterTests.cs index 66c6582a0..603656610 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationSetterTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationSetterTests.cs @@ -1,113 +1,113 @@ -using System; -using System.Collections.Generic; -using Moq; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.Configuration.Creator; -using Ocelot.Configuration.File; -using Ocelot.Configuration.Repository; -using Ocelot.Configuration.Setter; -using Ocelot.Errors; -using Ocelot.Responses; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Configuration -{ - public class FileConfigurationSetterTests - { - private FileConfiguration _fileConfiguration; - private FileConfigurationSetter _configSetter; - private Mock _configRepo; - private Mock _configCreator; - private Response _configuration; - private object _result; - private Mock _repo; - - public FileConfigurationSetterTests() - { - _repo = new Mock(); - _configRepo = new Mock(); - _configCreator = new Mock(); - _configSetter = new FileConfigurationSetter(_configRepo.Object, _configCreator.Object, _repo.Object); - } - - [Fact] - public void should_set_configuration() - { - var fileConfig = new FileConfiguration(); - var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); - var config = new OcelotConfiguration(new List(), string.Empty, serviceProviderConfig, "asdf"); - - this.Given(x => GivenTheFollowingConfiguration(fileConfig)) - .And(x => GivenTheRepoReturns(new OkResponse())) - .And(x => GivenTheCreatorReturns(new OkResponse(config))) - .When(x => WhenISetTheConfiguration()) - .Then(x => ThenTheConfigurationRepositoryIsCalledCorrectly()) - .BDDfy(); - } - - - [Fact] - public void should_return_error_if_unable_to_set_file_configuration() - { - var fileConfig = new FileConfiguration(); - - this.Given(x => GivenTheFollowingConfiguration(fileConfig)) - .And(x => GivenTheRepoReturns(new ErrorResponse(It.IsAny()))) - .When(x => WhenISetTheConfiguration()) - .And(x => ThenAnErrorResponseIsReturned()) - .BDDfy(); - } - - [Fact] - public void should_return_error_if_unable_to_set_ocelot_configuration() - { - var fileConfig = new FileConfiguration(); - - this.Given(x => GivenTheFollowingConfiguration(fileConfig)) - .And(x => GivenTheRepoReturns(new OkResponse())) - .And(x => GivenTheCreatorReturns(new ErrorResponse(It.IsAny()))) - .When(x => WhenISetTheConfiguration()) - .And(x => ThenAnErrorResponseIsReturned()) - .BDDfy(); - } - - private void GivenTheRepoReturns(Response response) - { - _repo - .Setup(x => x.Set(It.IsAny())) - .ReturnsAsync(response); - } - - private void ThenAnErrorResponseIsReturned() - { - _result.ShouldBeOfType(); - } - - private void GivenTheCreatorReturns(Response configuration) - { - _configuration = configuration; - _configCreator - .Setup(x => x.Create(_fileConfiguration)) - .ReturnsAsync(_configuration); - } - - private void GivenTheFollowingConfiguration(FileConfiguration fileConfiguration) - { - _fileConfiguration = fileConfiguration; - } - - private void WhenISetTheConfiguration() - { - _result = _configSetter.Set(_fileConfiguration).Result; - } - - private void ThenTheConfigurationRepositoryIsCalledCorrectly() - { - _configRepo - .Verify(x => x.AddOrReplace(_configuration.Data), Times.Once); - } - } +using System; +using System.Collections.Generic; +using Moq; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Ocelot.Configuration.Repository; +using Ocelot.Configuration.Setter; +using Ocelot.Errors; +using Ocelot.Responses; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class FileConfigurationSetterTests + { + private FileConfiguration _fileConfiguration; + private FileConfigurationSetter _configSetter; + private Mock _configRepo; + private Mock _configCreator; + private Response _configuration; + private object _result; + private Mock _repo; + + public FileConfigurationSetterTests() + { + _repo = new Mock(); + _configRepo = new Mock(); + _configCreator = new Mock(); + _configSetter = new FileConfigurationSetter(_configRepo.Object, _configCreator.Object, _repo.Object); + } + + [Fact] + public void should_set_configuration() + { + var fileConfig = new FileConfiguration(); + var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); + var config = new OcelotConfiguration(new List(), string.Empty, serviceProviderConfig, "asdf"); + + this.Given(x => GivenTheFollowingConfiguration(fileConfig)) + .And(x => GivenTheRepoReturns(new OkResponse())) + .And(x => GivenTheCreatorReturns(new OkResponse(config))) + .When(x => WhenISetTheConfiguration()) + .Then(x => ThenTheConfigurationRepositoryIsCalledCorrectly()) + .BDDfy(); + } + + + [Fact] + public void should_return_error_if_unable_to_set_file_configuration() + { + var fileConfig = new FileConfiguration(); + + this.Given(x => GivenTheFollowingConfiguration(fileConfig)) + .And(x => GivenTheRepoReturns(new ErrorResponse(It.IsAny()))) + .When(x => WhenISetTheConfiguration()) + .And(x => ThenAnErrorResponseIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_return_error_if_unable_to_set_ocelot_configuration() + { + var fileConfig = new FileConfiguration(); + + this.Given(x => GivenTheFollowingConfiguration(fileConfig)) + .And(x => GivenTheRepoReturns(new OkResponse())) + .And(x => GivenTheCreatorReturns(new ErrorResponse(It.IsAny()))) + .When(x => WhenISetTheConfiguration()) + .And(x => ThenAnErrorResponseIsReturned()) + .BDDfy(); + } + + private void GivenTheRepoReturns(Response response) + { + _repo + .Setup(x => x.Set(It.IsAny())) + .ReturnsAsync(response); + } + + private void ThenAnErrorResponseIsReturned() + { + _result.ShouldBeOfType(); + } + + private void GivenTheCreatorReturns(Response configuration) + { + _configuration = configuration; + _configCreator + .Setup(x => x.Create(_fileConfiguration)) + .ReturnsAsync(_configuration); + } + + private void GivenTheFollowingConfiguration(FileConfiguration fileConfiguration) + { + _fileConfiguration = fileConfiguration; + } + + private void WhenISetTheConfiguration() + { + _result = _configSetter.Set(_fileConfiguration).Result; + } + + private void ThenTheConfigurationRepositoryIsCalledCorrectly() + { + _configRepo + .Verify(x => x.AddOrReplace(_configuration.Data), Times.Once); + } + } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Configuration/HashCreationTests.cs b/test/Ocelot.UnitTests/Configuration/HashCreationTests.cs index d99efd589..5f6c9064d 100644 --- a/test/Ocelot.UnitTests/Configuration/HashCreationTests.cs +++ b/test/Ocelot.UnitTests/Configuration/HashCreationTests.cs @@ -1,33 +1,33 @@ -using System; -using System.Security.Cryptography; -using Microsoft.AspNetCore.Cryptography.KeyDerivation; -using Shouldly; -using Xunit; - -namespace Ocelot.UnitTests.Configuration -{ - public class HashCreationTests - { - [Fact] - public void should_create_hash_and_salt() - { - var password = "secret"; - - var salt = new byte[128 / 8]; - - using (var rng = RandomNumberGenerator.Create()) - { - rng.GetBytes(salt); - } - - var storedSalt = Convert.ToBase64String(salt); - - var storedHash = Convert.ToBase64String(KeyDerivation.Pbkdf2( - password: password, - salt: salt, - prf: KeyDerivationPrf.HMACSHA256, - iterationCount: 10000, - numBytesRequested: 256 / 8)); - } - } +using System; +using System.Security.Cryptography; +using Microsoft.AspNetCore.Cryptography.KeyDerivation; +using Shouldly; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class HashCreationTests + { + [Fact] + public void should_create_hash_and_salt() + { + var password = "secret"; + + var salt = new byte[128 / 8]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(salt); + } + + var storedSalt = Convert.ToBase64String(salt); + + var storedHash = Convert.ToBase64String(KeyDerivation.Pbkdf2( + password: password, + salt: salt, + prf: KeyDerivationPrf.HMACSHA256, + iterationCount: 10000, + numBytesRequested: 256 / 8)); + } + } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Configuration/HashMatcherTests.cs b/test/Ocelot.UnitTests/Configuration/HashMatcherTests.cs index 34e9477e6..55c5edc9c 100644 --- a/test/Ocelot.UnitTests/Configuration/HashMatcherTests.cs +++ b/test/Ocelot.UnitTests/Configuration/HashMatcherTests.cs @@ -1,76 +1,76 @@ -using Ocelot.Configuration.Authentication; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Configuration -{ - public class HashMatcherTests - { - private string _password; - private string _hash; - private string _salt; - private bool _result; - private HashMatcher _hashMatcher; - - public HashMatcherTests() - { - _hashMatcher = new HashMatcher(); - } - - [Fact] - public void should_match_hash() - { - var hash = "kE/mxd1hO9h9Sl2VhGhwJUd9xZEv4NP6qXoN39nIqM4="; - var salt = "zzWITpnDximUNKYLiUam/w=="; - var password = "secret"; - - this.Given(x => GivenThePassword(password)) - .And(x => GivenTheHash(hash)) - .And(x => GivenTheSalt(salt)) - .When(x => WhenIMatch()) - .Then(x => ThenTheResultIs(true)) - .BDDfy(); - } - - [Fact] - public void should_not_match_hash() - { - var hash = "kE/mxd1hO9h9Sl2VhGhwJUd9xZEv4NP6qXoN39nIqM4="; - var salt = "zzWITpnDximUNKYLiUam/w=="; - var password = "secret1"; - - this.Given(x => GivenThePassword(password)) - .And(x => GivenTheHash(hash)) - .And(x => GivenTheSalt(salt)) - .When(x => WhenIMatch()) - .Then(x => ThenTheResultIs(false)) - .BDDfy(); - } - - private void GivenThePassword(string password) - { - _password = password; - } - - private void GivenTheHash(string hash) - { - _hash = hash; - } - - private void GivenTheSalt(string salt) - { - _salt = salt; - } - - private void WhenIMatch() - { - _result = _hashMatcher.Match(_password, _salt, _hash); - } - - private void ThenTheResultIs(bool expected) - { - _result.ShouldBe(expected); - } - } +using Ocelot.Configuration.Authentication; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class HashMatcherTests + { + private string _password; + private string _hash; + private string _salt; + private bool _result; + private HashMatcher _hashMatcher; + + public HashMatcherTests() + { + _hashMatcher = new HashMatcher(); + } + + [Fact] + public void should_match_hash() + { + var hash = "kE/mxd1hO9h9Sl2VhGhwJUd9xZEv4NP6qXoN39nIqM4="; + var salt = "zzWITpnDximUNKYLiUam/w=="; + var password = "secret"; + + this.Given(x => GivenThePassword(password)) + .And(x => GivenTheHash(hash)) + .And(x => GivenTheSalt(salt)) + .When(x => WhenIMatch()) + .Then(x => ThenTheResultIs(true)) + .BDDfy(); + } + + [Fact] + public void should_not_match_hash() + { + var hash = "kE/mxd1hO9h9Sl2VhGhwJUd9xZEv4NP6qXoN39nIqM4="; + var salt = "zzWITpnDximUNKYLiUam/w=="; + var password = "secret1"; + + this.Given(x => GivenThePassword(password)) + .And(x => GivenTheHash(hash)) + .And(x => GivenTheSalt(salt)) + .When(x => WhenIMatch()) + .Then(x => ThenTheResultIs(false)) + .BDDfy(); + } + + private void GivenThePassword(string password) + { + _password = password; + } + + private void GivenTheHash(string hash) + { + _hash = hash; + } + + private void GivenTheSalt(string salt) + { + _salt = salt; + } + + private void WhenIMatch() + { + _result = _hashMatcher.Match(_password, _salt, _hash); + } + + private void ThenTheResultIs(bool expected) + { + _result.ShouldBe(expected); + } + } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs index ceac035eb..76c3c47f4 100644 --- a/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs @@ -1,158 +1,158 @@ -using System; -using System.Collections.Generic; -using Moq; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.Configuration.Creator; -using Ocelot.Configuration.File; -using Ocelot.Middleware; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Configuration -{ - public class HeaderFindAndReplaceCreatorTests - { - private HeaderFindAndReplaceCreator _creator; - private FileReRoute _reRoute; - private HeaderTransformations _result; - private Mock _finder; - - public HeaderFindAndReplaceCreatorTests() - { - _finder = new Mock(); - _creator = new HeaderFindAndReplaceCreator(_finder.Object); - } - - [Fact] - public void should_create() - { - var reRoute = new FileReRoute - { - UpstreamHeaderTransform = new Dictionary - { - {"Test", "Test, Chicken"}, - - {"Moop", "o, a"} - }, - DownstreamHeaderTransform = new Dictionary - { - {"Pop", "West, East"}, - - {"Bop", "e, r"} - } - }; - - var upstream = new List - { - new HeaderFindAndReplace("Test", "Test", "Chicken", 0), - new HeaderFindAndReplace("Moop", "o", "a", 0) - }; - - var downstream = new List - { - new HeaderFindAndReplace("Pop", "West", "East", 0), - new HeaderFindAndReplace("Bop", "e", "r", 0) - }; - - this.Given(x => GivenTheReRoute(reRoute)) - .When(x => WhenICreate()) - .Then(x => ThenTheFollowingUpstreamIsReturned(upstream)) - .Then(x => ThenTheFollowingDownstreamIsReturned(downstream)) - .BDDfy(); - } - - [Fact] - public void should_use_base_url_placeholder() - { - var reRoute = new FileReRoute - { - DownstreamHeaderTransform = new Dictionary - { - {"Location", "http://www.bbc.co.uk/, {BaseUrl}"}, - } - }; - - var downstream = new List - { - new HeaderFindAndReplace("Location", "http://www.bbc.co.uk/", "http://ocelot.com/", 0), - }; - - this.Given(x => GivenTheReRoute(reRoute)) - .And(x => GivenTheBaseUrlIs("http://ocelot.com/")) - .When(x => WhenICreate()) - .Then(x => ThenTheFollowingDownstreamIsReturned(downstream)) - .BDDfy(); - } - - - [Fact] - public void should_use_base_url_partial_placeholder() - { - var reRoute = new FileReRoute - { - DownstreamHeaderTransform = new Dictionary - { - {"Location", "http://www.bbc.co.uk/pay, {BaseUrl}pay"}, - } - }; - - var downstream = new List - { - new HeaderFindAndReplace("Location", "http://www.bbc.co.uk/pay", "http://ocelot.com/pay", 0), - }; - - this.Given(x => GivenTheReRoute(reRoute)) - .And(x => GivenTheBaseUrlIs("http://ocelot.com/")) - .When(x => WhenICreate()) - .Then(x => ThenTheFollowingDownstreamIsReturned(downstream)) - .BDDfy(); - } - - private void GivenTheBaseUrlIs(string baseUrl) - { - _finder.Setup(x => x.Find()).Returns(baseUrl); - } - - private void ThenTheFollowingDownstreamIsReturned(List downstream) - { - _result.Downstream.Count.ShouldBe(downstream.Count); - - for (int i = 0; i < _result.Downstream.Count; i++) - { - var result = _result.Downstream[i]; - var expected = downstream[i]; - result.Find.ShouldBe(expected.Find); - result.Index.ShouldBe(expected.Index); - result.Key.ShouldBe(expected.Key); - result.Replace.ShouldBe(expected.Replace); - } - } - - private void GivenTheReRoute(FileReRoute reRoute) - { - _reRoute = reRoute; - } - - private void WhenICreate() - { - _result = _creator.Create(_reRoute); - } - - private void ThenTheFollowingUpstreamIsReturned(List expecteds) - { - _result.Upstream.Count.ShouldBe(expecteds.Count); - - for (int i = 0; i < _result.Upstream.Count; i++) - { - var result = _result.Upstream[i]; - var expected = expecteds[i]; - result.Find.ShouldBe(expected.Find); - result.Index.ShouldBe(expected.Index); - result.Key.ShouldBe(expected.Key); - result.Replace.ShouldBe(expected.Replace); - } - } - } +using System; +using System.Collections.Generic; +using Moq; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Ocelot.Middleware; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class HeaderFindAndReplaceCreatorTests + { + private HeaderFindAndReplaceCreator _creator; + private FileReRoute _reRoute; + private HeaderTransformations _result; + private Mock _finder; + + public HeaderFindAndReplaceCreatorTests() + { + _finder = new Mock(); + _creator = new HeaderFindAndReplaceCreator(_finder.Object); + } + + [Fact] + public void should_create() + { + var reRoute = new FileReRoute + { + UpstreamHeaderTransform = new Dictionary + { + {"Test", "Test, Chicken"}, + + {"Moop", "o, a"} + }, + DownstreamHeaderTransform = new Dictionary + { + {"Pop", "West, East"}, + + {"Bop", "e, r"} + } + }; + + var upstream = new List + { + new HeaderFindAndReplace("Test", "Test", "Chicken", 0), + new HeaderFindAndReplace("Moop", "o", "a", 0) + }; + + var downstream = new List + { + new HeaderFindAndReplace("Pop", "West", "East", 0), + new HeaderFindAndReplace("Bop", "e", "r", 0) + }; + + this.Given(x => GivenTheReRoute(reRoute)) + .When(x => WhenICreate()) + .Then(x => ThenTheFollowingUpstreamIsReturned(upstream)) + .Then(x => ThenTheFollowingDownstreamIsReturned(downstream)) + .BDDfy(); + } + + [Fact] + public void should_use_base_url_placeholder() + { + var reRoute = new FileReRoute + { + DownstreamHeaderTransform = new Dictionary + { + {"Location", "http://www.bbc.co.uk/, {BaseUrl}"}, + } + }; + + var downstream = new List + { + new HeaderFindAndReplace("Location", "http://www.bbc.co.uk/", "http://ocelot.com/", 0), + }; + + this.Given(x => GivenTheReRoute(reRoute)) + .And(x => GivenTheBaseUrlIs("http://ocelot.com/")) + .When(x => WhenICreate()) + .Then(x => ThenTheFollowingDownstreamIsReturned(downstream)) + .BDDfy(); + } + + + [Fact] + public void should_use_base_url_partial_placeholder() + { + var reRoute = new FileReRoute + { + DownstreamHeaderTransform = new Dictionary + { + {"Location", "http://www.bbc.co.uk/pay, {BaseUrl}pay"}, + } + }; + + var downstream = new List + { + new HeaderFindAndReplace("Location", "http://www.bbc.co.uk/pay", "http://ocelot.com/pay", 0), + }; + + this.Given(x => GivenTheReRoute(reRoute)) + .And(x => GivenTheBaseUrlIs("http://ocelot.com/")) + .When(x => WhenICreate()) + .Then(x => ThenTheFollowingDownstreamIsReturned(downstream)) + .BDDfy(); + } + + private void GivenTheBaseUrlIs(string baseUrl) + { + _finder.Setup(x => x.Find()).Returns(baseUrl); + } + + private void ThenTheFollowingDownstreamIsReturned(List downstream) + { + _result.Downstream.Count.ShouldBe(downstream.Count); + + for (int i = 0; i < _result.Downstream.Count; i++) + { + var result = _result.Downstream[i]; + var expected = downstream[i]; + result.Find.ShouldBe(expected.Find); + result.Index.ShouldBe(expected.Index); + result.Key.ShouldBe(expected.Key); + result.Replace.ShouldBe(expected.Replace); + } + } + + private void GivenTheReRoute(FileReRoute reRoute) + { + _reRoute = reRoute; + } + + private void WhenICreate() + { + _result = _creator.Create(_reRoute); + } + + private void ThenTheFollowingUpstreamIsReturned(List expecteds) + { + _result.Upstream.Count.ShouldBe(expecteds.Count); + + for (int i = 0; i < _result.Upstream.Count; i++) + { + var result = _result.Upstream[i]; + var expected = expecteds[i]; + result.Find.ShouldBe(expected.Find); + result.Index.ShouldBe(expected.Index); + result.Key.ShouldBe(expected.Key); + result.Replace.ShouldBe(expected.Replace); + } + } + } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Configuration/IdentityServerConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/IdentityServerConfigurationCreatorTests.cs index 93a867434..5f8d3210f 100644 --- a/test/Ocelot.UnitTests/Configuration/IdentityServerConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/IdentityServerConfigurationCreatorTests.cs @@ -1,16 +1,16 @@ -using Ocelot.Configuration.Creator; -using Shouldly; -using Xunit; - -namespace Ocelot.UnitTests.Configuration -{ - public class IdentityServerConfigurationCreatorTests - { - [Fact] - public void happy_path_only_exists_for_test_coverage_even_uncle_bob_probably_wouldnt_test_this() - { - var result = IdentityServerConfigurationCreator.GetIdentityServerConfiguration("secret"); - result.ApiName.ShouldBe("admin"); - } - } +using Ocelot.Configuration.Creator; +using Shouldly; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class IdentityServerConfigurationCreatorTests + { + [Fact] + public void happy_path_only_exists_for_test_coverage_even_uncle_bob_probably_wouldnt_test_this() + { + var result = IdentityServerConfigurationCreator.GetIdentityServerConfiguration("secret"); + result.ApiName.ShouldBe("admin"); + } + } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs b/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs index 9a9fc69e8..ab5a9eea0 100644 --- a/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs +++ b/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs @@ -1,101 +1,101 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.Configuration.Repository; -using Ocelot.Responses; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Configuration -{ - public class InMemoryConfigurationRepositoryTests - { - private readonly InMemoryOcelotConfigurationRepository _repo; - private IOcelotConfiguration _config; - private Response _result; - private Response _getResult; - - public InMemoryConfigurationRepositoryTests() - { - _repo = new InMemoryOcelotConfigurationRepository(); - } - - [Fact] - public void can_add_config() - { - this.Given(x => x.GivenTheConfigurationIs(new FakeConfig("initial", "adminath"))) - .When(x => x.WhenIAddOrReplaceTheConfig()) - .Then(x => x.ThenNoErrorsAreReturned()) - .BDDfy(); - } - - [Fact] - public void can_get_config() - { - this.Given(x => x.GivenThereIsASavedConfiguration()) - .When(x => x.WhenIGetTheConfiguration()) - .Then(x => x.ThenTheConfigurationIsReturned()) - .BDDfy(); - } - - private void ThenTheConfigurationIsReturned() - { - _getResult.Data.ReRoutes[0].DownstreamPathTemplate.Value.ShouldBe("initial"); - } - - private void WhenIGetTheConfiguration() - { - _getResult = _repo.Get().Result; - } - - private void GivenThereIsASavedConfiguration() - { - GivenTheConfigurationIs(new FakeConfig("initial", "adminath")); - WhenIAddOrReplaceTheConfig(); - } - - private void GivenTheConfigurationIs(IOcelotConfiguration config) - { - _config = config; - } - - private void WhenIAddOrReplaceTheConfig() - { - _result = _repo.AddOrReplace(_config).Result; - } - - private void ThenNoErrorsAreReturned() - { - _result.IsError.ShouldBeFalse(); - } - - class FakeConfig : IOcelotConfiguration - { - private readonly string _downstreamTemplatePath; - - public FakeConfig(string downstreamTemplatePath, string administrationPath) - { - _downstreamTemplatePath = downstreamTemplatePath; - AdministrationPath = administrationPath; - } - - public List ReRoutes => new List - { - new ReRouteBuilder() - .WithDownstreamPathTemplate(_downstreamTemplatePath) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build() - }; - - public string AdministrationPath {get;} - - public ServiceProviderConfiguration ServiceProviderConfiguration => throw new NotImplementedException(); - - public string RequestId {get;} - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.Repository; +using Ocelot.Responses; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class InMemoryConfigurationRepositoryTests + { + private readonly InMemoryOcelotConfigurationRepository _repo; + private IOcelotConfiguration _config; + private Response _result; + private Response _getResult; + + public InMemoryConfigurationRepositoryTests() + { + _repo = new InMemoryOcelotConfigurationRepository(); + } + + [Fact] + public void can_add_config() + { + this.Given(x => x.GivenTheConfigurationIs(new FakeConfig("initial", "adminath"))) + .When(x => x.WhenIAddOrReplaceTheConfig()) + .Then(x => x.ThenNoErrorsAreReturned()) + .BDDfy(); + } + + [Fact] + public void can_get_config() + { + this.Given(x => x.GivenThereIsASavedConfiguration()) + .When(x => x.WhenIGetTheConfiguration()) + .Then(x => x.ThenTheConfigurationIsReturned()) + .BDDfy(); + } + + private void ThenTheConfigurationIsReturned() + { + _getResult.Data.ReRoutes[0].DownstreamPathTemplate.Value.ShouldBe("initial"); + } + + private void WhenIGetTheConfiguration() + { + _getResult = _repo.Get().Result; + } + + private void GivenThereIsASavedConfiguration() + { + GivenTheConfigurationIs(new FakeConfig("initial", "adminath")); + WhenIAddOrReplaceTheConfig(); + } + + private void GivenTheConfigurationIs(IOcelotConfiguration config) + { + _config = config; + } + + private void WhenIAddOrReplaceTheConfig() + { + _result = _repo.AddOrReplace(_config).Result; + } + + private void ThenNoErrorsAreReturned() + { + _result.IsError.ShouldBeFalse(); + } + + class FakeConfig : IOcelotConfiguration + { + private readonly string _downstreamTemplatePath; + + public FakeConfig(string downstreamTemplatePath, string administrationPath) + { + _downstreamTemplatePath = downstreamTemplatePath; + AdministrationPath = administrationPath; + } + + public List ReRoutes => new List + { + new ReRouteBuilder() + .WithDownstreamPathTemplate(_downstreamTemplatePath) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build() + }; + + public string AdministrationPath {get;} + + public ServiceProviderConfiguration ServiceProviderConfiguration => throw new NotImplementedException(); + + public string RequestId {get;} + } + } +} diff --git a/test/Ocelot.UnitTests/Configuration/OcelotConfigurationProviderTests.cs b/test/Ocelot.UnitTests/Configuration/OcelotConfigurationProviderTests.cs index 9a8579efe..982fe0912 100644 --- a/test/Ocelot.UnitTests/Configuration/OcelotConfigurationProviderTests.cs +++ b/test/Ocelot.UnitTests/Configuration/OcelotConfigurationProviderTests.cs @@ -1,80 +1,80 @@ -using System.Collections.Generic; -using Moq; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.Configuration.Creator; -using Ocelot.Configuration.Provider; -using Ocelot.Configuration.Repository; -using Ocelot.Errors; -using Ocelot.Responses; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Configuration -{ - public class OcelotConfigurationProviderTests - { - private readonly IOcelotConfigurationProvider _ocelotConfigurationProvider; - private readonly Mock _configurationRepository; - private Response _result; - - public OcelotConfigurationProviderTests() - { - _configurationRepository = new Mock(); - _ocelotConfigurationProvider = new OcelotConfigurationProvider(_configurationRepository.Object); - } - - [Fact] - public void should_get_config() - { - var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); - - this.Given(x => x.GivenTheRepoReturns(new OkResponse(new OcelotConfiguration(new List(), string.Empty, serviceProviderConfig, "")))) - .When(x => x.WhenIGetTheConfig()) - .Then(x => x.TheFollowingIsReturned(new OkResponse(new OcelotConfiguration(new List(), string.Empty, serviceProviderConfig, "")))) - .BDDfy(); - } - - [Fact] - public void should_return_error() - { - this.Given(x => x.GivenTheRepoReturns(new ErrorResponse(new List - { - new AnyError() - }))) - .When(x => x.WhenIGetTheConfig()) - .Then(x => x.TheFollowingIsReturned( - new ErrorResponse(new List - { - new AnyError() - }))) - .BDDfy(); - } - - private void GivenTheRepoReturns(Response config) - { - _configurationRepository - .Setup(x => x.Get()) - .ReturnsAsync(config); - } - - private void WhenIGetTheConfig() - { - _result = _ocelotConfigurationProvider.Get().Result; - } - - private void TheFollowingIsReturned(Response expected) - { - _result.IsError.ShouldBe(expected.IsError); - } - - class AnyError : Error - { - public AnyError() - : base("blamo", OcelotErrorCode.UnknownError) - { - } - } - } -} +using System.Collections.Generic; +using Moq; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.Provider; +using Ocelot.Configuration.Repository; +using Ocelot.Errors; +using Ocelot.Responses; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class OcelotConfigurationProviderTests + { + private readonly IOcelotConfigurationProvider _ocelotConfigurationProvider; + private readonly Mock _configurationRepository; + private Response _result; + + public OcelotConfigurationProviderTests() + { + _configurationRepository = new Mock(); + _ocelotConfigurationProvider = new OcelotConfigurationProvider(_configurationRepository.Object); + } + + [Fact] + public void should_get_config() + { + var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); + + this.Given(x => x.GivenTheRepoReturns(new OkResponse(new OcelotConfiguration(new List(), string.Empty, serviceProviderConfig, "")))) + .When(x => x.WhenIGetTheConfig()) + .Then(x => x.TheFollowingIsReturned(new OkResponse(new OcelotConfiguration(new List(), string.Empty, serviceProviderConfig, "")))) + .BDDfy(); + } + + [Fact] + public void should_return_error() + { + this.Given(x => x.GivenTheRepoReturns(new ErrorResponse(new List + { + new AnyError() + }))) + .When(x => x.WhenIGetTheConfig()) + .Then(x => x.TheFollowingIsReturned( + new ErrorResponse(new List + { + new AnyError() + }))) + .BDDfy(); + } + + private void GivenTheRepoReturns(Response config) + { + _configurationRepository + .Setup(x => x.Get()) + .ReturnsAsync(config); + } + + private void WhenIGetTheConfig() + { + _result = _ocelotConfigurationProvider.Get().Result; + } + + private void TheFollowingIsReturned(Response expected) + { + _result.IsError.ShouldBe(expected.IsError); + } + + class AnyError : Error + { + public AnyError() + : base("blamo", OcelotErrorCode.UnknownError) + { + } + } + } +} diff --git a/test/Ocelot.UnitTests/Configuration/QoSOptionsCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/QoSOptionsCreatorTests.cs index be3ac27a9..0ce64a383 100644 --- a/test/Ocelot.UnitTests/Configuration/QoSOptionsCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/QoSOptionsCreatorTests.cs @@ -1,63 +1,63 @@ -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.Configuration.Creator; -using Ocelot.Configuration.File; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Configuration -{ - public class QoSOptionsCreatorTests - { - private QoSOptionsCreator _creator; - private FileReRoute _fileReRoute; - private QoSOptions _result; - - public QoSOptionsCreatorTests() - { - _creator = new QoSOptionsCreator(); - } - - [Fact] - public void should_create_qos_options() - { - var reRoute = new FileReRoute - { - QoSOptions = new FileQoSOptions - { - ExceptionsAllowedBeforeBreaking = 1, - DurationOfBreak = 1, - TimeoutValue = 1 - } - }; - var expected = new QoSOptionsBuilder() - .WithDurationOfBreak(1) - .WithExceptionsAllowedBeforeBreaking(1) - .WithTimeoutValue(1) - .Build(); - - this.Given(x => x.GivenTheFollowingReRoute(reRoute)) - .When(x => x.WhenICreate()) - .Then(x => x.ThenTheFollowingIsReturned(expected)) - .BDDfy(); - } - - private void GivenTheFollowingReRoute(FileReRoute fileReRoute) - { - _fileReRoute = fileReRoute; - } - - private void WhenICreate() - { - _result = _creator.Create(_fileReRoute); - } - - private void ThenTheFollowingIsReturned(QoSOptions expected) - { - _result.DurationOfBreak.ShouldBe(expected.DurationOfBreak); - _result.ExceptionsAllowedBeforeBreaking.ShouldBe(expected.ExceptionsAllowedBeforeBreaking); - _result.TimeoutValue.ShouldBe(expected.TimeoutValue); - } - } +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class QoSOptionsCreatorTests + { + private QoSOptionsCreator _creator; + private FileReRoute _fileReRoute; + private QoSOptions _result; + + public QoSOptionsCreatorTests() + { + _creator = new QoSOptionsCreator(); + } + + [Fact] + public void should_create_qos_options() + { + var reRoute = new FileReRoute + { + QoSOptions = new FileQoSOptions + { + ExceptionsAllowedBeforeBreaking = 1, + DurationOfBreak = 1, + TimeoutValue = 1 + } + }; + var expected = new QoSOptionsBuilder() + .WithDurationOfBreak(1) + .WithExceptionsAllowedBeforeBreaking(1) + .WithTimeoutValue(1) + .Build(); + + this.Given(x => x.GivenTheFollowingReRoute(reRoute)) + .When(x => x.WhenICreate()) + .Then(x => x.ThenTheFollowingIsReturned(expected)) + .BDDfy(); + } + + private void GivenTheFollowingReRoute(FileReRoute fileReRoute) + { + _fileReRoute = fileReRoute; + } + + private void WhenICreate() + { + _result = _creator.Create(_fileReRoute); + } + + private void ThenTheFollowingIsReturned(QoSOptions expected) + { + _result.DurationOfBreak.ShouldBe(expected.DurationOfBreak); + _result.ExceptionsAllowedBeforeBreaking.ShouldBe(expected.ExceptionsAllowedBeforeBreaking); + _result.TimeoutValue.ShouldBe(expected.TimeoutValue); + } + } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Configuration/RateLimitOptionsCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/RateLimitOptionsCreatorTests.cs index 7d2234e2d..69093cca2 100644 --- a/test/Ocelot.UnitTests/Configuration/RateLimitOptionsCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/RateLimitOptionsCreatorTests.cs @@ -1,108 +1,108 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.Configuration.Creator; -using Ocelot.Configuration.File; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Configuration -{ - public class RateLimitOptionsCreatorTests - { - private FileReRoute _fileReRoute; - private FileGlobalConfiguration _fileGlobalConfig; - private bool _enabled; - private RateLimitOptionsCreator _creator; - private RateLimitOptions _result; - - public RateLimitOptionsCreatorTests() - { - _creator = new RateLimitOptionsCreator(); - } - - [Fact] - public void should_create_rate_limit_options() - { - var fileReRoute = new FileReRoute - { - RateLimitOptions = new FileRateLimitRule - { - ClientWhitelist = new List(), - Period = "Period", - Limit = 1, - PeriodTimespan = 1, - EnableRateLimiting = true - } - }; - var fileGlobalConfig = new FileGlobalConfiguration - { - RateLimitOptions = new FileRateLimitOptions - { - ClientIdHeader = "ClientIdHeader", - DisableRateLimitHeaders = true, - QuotaExceededMessage = "QuotaExceededMessage", - RateLimitCounterPrefix = "RateLimitCounterPrefix", - HttpStatusCode = 200 - } - }; - var expected = new RateLimitOptionsBuilder() - .WithClientIdHeader("ClientIdHeader") - .WithClientWhiteList(fileReRoute.RateLimitOptions.ClientWhitelist) - .WithDisableRateLimitHeaders(true) - .WithEnableRateLimiting(true) - .WithHttpStatusCode(200) - .WithQuotaExceededMessage("QuotaExceededMessage") - .WithRateLimitCounterPrefix("RateLimitCounterPrefix") - .WithRateLimitRule(new RateLimitRule(fileReRoute.RateLimitOptions.Period, - fileReRoute.RateLimitOptions.PeriodTimespan, - fileReRoute.RateLimitOptions.Limit)) - .Build(); - - this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) - .And(x => x.GivenTheFollowingFileGlobalConfig(fileGlobalConfig)) - .And(x => x.GivenRateLimitingIsEnabled()) - .When(x => x.WhenICreate()) - .Then(x => x.ThenTheFollowingIsReturned(expected)) - .BDDfy(); - } - - private void GivenTheFollowingFileReRoute(FileReRoute fileReRoute) - { - _fileReRoute = fileReRoute; - } - - private void GivenTheFollowingFileGlobalConfig(FileGlobalConfiguration fileGlobalConfig) - { - _fileGlobalConfig = fileGlobalConfig; - } - - private void GivenRateLimitingIsEnabled() - { - _enabled = true; - } - - private void WhenICreate() - { - _result = _creator.Create(_fileReRoute, _fileGlobalConfig, _enabled); - } - - private void ThenTheFollowingIsReturned(RateLimitOptions expected) - { - _result.ClientIdHeader.ShouldBe(expected.ClientIdHeader); - _result.ClientWhitelist.ShouldBe(expected.ClientWhitelist); - _result.DisableRateLimitHeaders.ShouldBe(expected.DisableRateLimitHeaders); - _result.EnableRateLimiting.ShouldBe(expected.EnableRateLimiting); - _result.HttpStatusCode.ShouldBe(expected.HttpStatusCode); - _result.QuotaExceededMessage.ShouldBe(expected.QuotaExceededMessage); - _result.RateLimitCounterPrefix.ShouldBe(expected.RateLimitCounterPrefix); - _result.RateLimitRule.Limit.ShouldBe(expected.RateLimitRule.Limit); - _result.RateLimitRule.Period.ShouldBe(expected.RateLimitRule.Period); - TimeSpan.FromSeconds(_result.RateLimitRule.PeriodTimespan).Ticks.ShouldBe(TimeSpan.FromSeconds(expected.RateLimitRule.PeriodTimespan).Ticks); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class RateLimitOptionsCreatorTests + { + private FileReRoute _fileReRoute; + private FileGlobalConfiguration _fileGlobalConfig; + private bool _enabled; + private RateLimitOptionsCreator _creator; + private RateLimitOptions _result; + + public RateLimitOptionsCreatorTests() + { + _creator = new RateLimitOptionsCreator(); + } + + [Fact] + public void should_create_rate_limit_options() + { + var fileReRoute = new FileReRoute + { + RateLimitOptions = new FileRateLimitRule + { + ClientWhitelist = new List(), + Period = "Period", + Limit = 1, + PeriodTimespan = 1, + EnableRateLimiting = true + } + }; + var fileGlobalConfig = new FileGlobalConfiguration + { + RateLimitOptions = new FileRateLimitOptions + { + ClientIdHeader = "ClientIdHeader", + DisableRateLimitHeaders = true, + QuotaExceededMessage = "QuotaExceededMessage", + RateLimitCounterPrefix = "RateLimitCounterPrefix", + HttpStatusCode = 200 + } + }; + var expected = new RateLimitOptionsBuilder() + .WithClientIdHeader("ClientIdHeader") + .WithClientWhiteList(fileReRoute.RateLimitOptions.ClientWhitelist) + .WithDisableRateLimitHeaders(true) + .WithEnableRateLimiting(true) + .WithHttpStatusCode(200) + .WithQuotaExceededMessage("QuotaExceededMessage") + .WithRateLimitCounterPrefix("RateLimitCounterPrefix") + .WithRateLimitRule(new RateLimitRule(fileReRoute.RateLimitOptions.Period, + fileReRoute.RateLimitOptions.PeriodTimespan, + fileReRoute.RateLimitOptions.Limit)) + .Build(); + + this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) + .And(x => x.GivenTheFollowingFileGlobalConfig(fileGlobalConfig)) + .And(x => x.GivenRateLimitingIsEnabled()) + .When(x => x.WhenICreate()) + .Then(x => x.ThenTheFollowingIsReturned(expected)) + .BDDfy(); + } + + private void GivenTheFollowingFileReRoute(FileReRoute fileReRoute) + { + _fileReRoute = fileReRoute; + } + + private void GivenTheFollowingFileGlobalConfig(FileGlobalConfiguration fileGlobalConfig) + { + _fileGlobalConfig = fileGlobalConfig; + } + + private void GivenRateLimitingIsEnabled() + { + _enabled = true; + } + + private void WhenICreate() + { + _result = _creator.Create(_fileReRoute, _fileGlobalConfig, _enabled); + } + + private void ThenTheFollowingIsReturned(RateLimitOptions expected) + { + _result.ClientIdHeader.ShouldBe(expected.ClientIdHeader); + _result.ClientWhitelist.ShouldBe(expected.ClientWhitelist); + _result.DisableRateLimitHeaders.ShouldBe(expected.DisableRateLimitHeaders); + _result.EnableRateLimiting.ShouldBe(expected.EnableRateLimiting); + _result.HttpStatusCode.ShouldBe(expected.HttpStatusCode); + _result.QuotaExceededMessage.ShouldBe(expected.QuotaExceededMessage); + _result.RateLimitCounterPrefix.ShouldBe(expected.RateLimitCounterPrefix); + _result.RateLimitRule.Limit.ShouldBe(expected.RateLimitRule.Limit); + _result.RateLimitRule.Period.ShouldBe(expected.RateLimitRule.Period); + TimeSpan.FromSeconds(_result.RateLimitRule.PeriodTimespan).Ticks.ShouldBe(TimeSpan.FromSeconds(expected.RateLimitRule.PeriodTimespan).Ticks); + } + } +} diff --git a/test/Ocelot.UnitTests/Configuration/ReRouteOptionsCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/ReRouteOptionsCreatorTests.cs index 3c1b7561e..6961c968a 100644 --- a/test/Ocelot.UnitTests/Configuration/ReRouteOptionsCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/ReRouteOptionsCreatorTests.cs @@ -1,84 +1,84 @@ -using System.Collections.Generic; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.Configuration.Creator; -using Ocelot.Configuration.File; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Configuration -{ - public class ReRouteOptionsCreatorTests - { - private ReRouteOptionsCreator _creator; - private FileReRoute _reRoute; - private ReRouteOptions _result; - - public ReRouteOptionsCreatorTests() - { - _creator = new ReRouteOptionsCreator(); - } - - [Fact] - public void should_create_re_route_options() - { - var reRoute = new FileReRoute - { - RateLimitOptions = new FileRateLimitRule - { - EnableRateLimiting = true - }, - QoSOptions = new FileQoSOptions - { - ExceptionsAllowedBeforeBreaking = 1, - TimeoutValue = 1 - }, - AuthenticationOptions = new FileAuthenticationOptions() - { - AuthenticationProviderKey = "Test" - }, - RouteClaimsRequirement = new Dictionary() - { - {"",""} - }, - FileCacheOptions = new FileCacheOptions - { - TtlSeconds = 1 - } - }; - - var expected = new ReRouteOptionsBuilder() - .WithIsAuthenticated(true) - .WithIsAuthorised(true) - .WithIsCached(true) - .WithIsQos(true) - .WithRateLimiting(true) - .Build(); - - this.Given(x => x.GivenTheFollowing(reRoute)) - .When(x => x.WhenICreate()) - .Then(x => x.ThenTheFollowingIsReturned(expected)) - .BDDfy(); - } - - private void GivenTheFollowing(FileReRoute reRoute) - { - _reRoute = reRoute; - } - - private void WhenICreate() - { - _result = _creator.Create(_reRoute); - } - - private void ThenTheFollowingIsReturned(ReRouteOptions expected) - { - _result.IsAuthenticated.ShouldBe(expected.IsAuthenticated); - _result.IsAuthorised.ShouldBe(expected.IsAuthorised); - _result.IsQos.ShouldBe(expected.IsQos); - _result.IsCached.ShouldBe(expected.IsCached); - _result.EnableRateLimiting.ShouldBe(expected.EnableRateLimiting); - } - } +using System.Collections.Generic; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class ReRouteOptionsCreatorTests + { + private ReRouteOptionsCreator _creator; + private FileReRoute _reRoute; + private ReRouteOptions _result; + + public ReRouteOptionsCreatorTests() + { + _creator = new ReRouteOptionsCreator(); + } + + [Fact] + public void should_create_re_route_options() + { + var reRoute = new FileReRoute + { + RateLimitOptions = new FileRateLimitRule + { + EnableRateLimiting = true + }, + QoSOptions = new FileQoSOptions + { + ExceptionsAllowedBeforeBreaking = 1, + TimeoutValue = 1 + }, + AuthenticationOptions = new FileAuthenticationOptions() + { + AuthenticationProviderKey = "Test" + }, + RouteClaimsRequirement = new Dictionary() + { + {"",""} + }, + FileCacheOptions = new FileCacheOptions + { + TtlSeconds = 1 + } + }; + + var expected = new ReRouteOptionsBuilder() + .WithIsAuthenticated(true) + .WithIsAuthorised(true) + .WithIsCached(true) + .WithIsQos(true) + .WithRateLimiting(true) + .Build(); + + this.Given(x => x.GivenTheFollowing(reRoute)) + .When(x => x.WhenICreate()) + .Then(x => x.ThenTheFollowingIsReturned(expected)) + .BDDfy(); + } + + private void GivenTheFollowing(FileReRoute reRoute) + { + _reRoute = reRoute; + } + + private void WhenICreate() + { + _result = _creator.Create(_reRoute); + } + + private void ThenTheFollowingIsReturned(ReRouteOptions expected) + { + _result.IsAuthenticated.ShouldBe(expected.IsAuthenticated); + _result.IsAuthorised.ShouldBe(expected.IsAuthorised); + _result.IsQos.ShouldBe(expected.IsQos); + _result.IsCached.ShouldBe(expected.IsCached); + _result.EnableRateLimiting.ShouldBe(expected.EnableRateLimiting); + } + } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Configuration/RequestIdKeyCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/RequestIdKeyCreatorTests.cs index 3c686d688..dabdd55f3 100644 --- a/test/Ocelot.UnitTests/Configuration/RequestIdKeyCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/RequestIdKeyCreatorTests.cs @@ -1,92 +1,92 @@ -using Ocelot.Configuration.Creator; -using Ocelot.Configuration.File; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Configuration -{ - public class RequestIdKeyCreatorTests - { - private FileReRoute _fileReRoute; - private FileGlobalConfiguration _fileGlobalConfig; - private string _result; - private RequestIdKeyCreator _creator; - - public RequestIdKeyCreatorTests() - { - _creator = new RequestIdKeyCreator(); - } - - [Fact] - public void should_use_global_configuration() - { - var reRoute = new FileReRoute(); - var globalConfig = new FileGlobalConfiguration - { - RequestIdKey = "cheese" - }; - - this.Given(x => x.GivenTheFollowingReRoute(reRoute)) - .And(x => x.GivenTheFollowingGlobalConfig(globalConfig)) - .When(x => x.WhenICreate()) - .Then(x => x.ThenTheFollowingIsReturned("cheese")) - .BDDfy(); - } - - [Fact] - public void should_use_re_route_specific() - { - var reRoute = new FileReRoute - { - RequestIdKey = "cheese" - }; - var globalConfig = new FileGlobalConfiguration(); - - this.Given(x => x.GivenTheFollowingReRoute(reRoute)) - .And(x => x.GivenTheFollowingGlobalConfig(globalConfig)) - .When(x => x.WhenICreate()) - .Then(x => x.ThenTheFollowingIsReturned("cheese")) - .BDDfy(); - } - - [Fact] - public void should_use_re_route_over_global_specific() - { - var reRoute = new FileReRoute - { - RequestIdKey = "cheese" - }; - var globalConfig = new FileGlobalConfiguration - { - RequestIdKey = "test" - }; - - this.Given(x => x.GivenTheFollowingReRoute(reRoute)) - .And(x => x.GivenTheFollowingGlobalConfig(globalConfig)) - .When(x => x.WhenICreate()) - .Then(x => x.ThenTheFollowingIsReturned("cheese")) - .BDDfy(); - } - - private void GivenTheFollowingReRoute(FileReRoute fileReRoute) - { - _fileReRoute = fileReRoute; - } - - private void GivenTheFollowingGlobalConfig(FileGlobalConfiguration globalConfig) - { - _fileGlobalConfig = globalConfig; - } - - private void WhenICreate() - { - _result = _creator.Create(_fileReRoute, _fileGlobalConfig); - } - - private void ThenTheFollowingIsReturned(string expected) - { - _result.ShouldBe(expected); - } - } +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class RequestIdKeyCreatorTests + { + private FileReRoute _fileReRoute; + private FileGlobalConfiguration _fileGlobalConfig; + private string _result; + private RequestIdKeyCreator _creator; + + public RequestIdKeyCreatorTests() + { + _creator = new RequestIdKeyCreator(); + } + + [Fact] + public void should_use_global_configuration() + { + var reRoute = new FileReRoute(); + var globalConfig = new FileGlobalConfiguration + { + RequestIdKey = "cheese" + }; + + this.Given(x => x.GivenTheFollowingReRoute(reRoute)) + .And(x => x.GivenTheFollowingGlobalConfig(globalConfig)) + .When(x => x.WhenICreate()) + .Then(x => x.ThenTheFollowingIsReturned("cheese")) + .BDDfy(); + } + + [Fact] + public void should_use_re_route_specific() + { + var reRoute = new FileReRoute + { + RequestIdKey = "cheese" + }; + var globalConfig = new FileGlobalConfiguration(); + + this.Given(x => x.GivenTheFollowingReRoute(reRoute)) + .And(x => x.GivenTheFollowingGlobalConfig(globalConfig)) + .When(x => x.WhenICreate()) + .Then(x => x.ThenTheFollowingIsReturned("cheese")) + .BDDfy(); + } + + [Fact] + public void should_use_re_route_over_global_specific() + { + var reRoute = new FileReRoute + { + RequestIdKey = "cheese" + }; + var globalConfig = new FileGlobalConfiguration + { + RequestIdKey = "test" + }; + + this.Given(x => x.GivenTheFollowingReRoute(reRoute)) + .And(x => x.GivenTheFollowingGlobalConfig(globalConfig)) + .When(x => x.WhenICreate()) + .Then(x => x.ThenTheFollowingIsReturned("cheese")) + .BDDfy(); + } + + private void GivenTheFollowingReRoute(FileReRoute fileReRoute) + { + _fileReRoute = fileReRoute; + } + + private void GivenTheFollowingGlobalConfig(FileGlobalConfiguration globalConfig) + { + _fileGlobalConfig = globalConfig; + } + + private void WhenICreate() + { + _result = _creator.Create(_fileReRoute, _fileGlobalConfig); + } + + private void ThenTheFollowingIsReturned(string expected) + { + _result.ShouldBe(expected); + } + } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Configuration/ServiceProviderCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/ServiceProviderCreatorTests.cs index 2d9fe3a29..c0a356964 100644 --- a/test/Ocelot.UnitTests/Configuration/ServiceProviderCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/ServiceProviderCreatorTests.cs @@ -1,70 +1,70 @@ -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.Configuration.Creator; -using Ocelot.Configuration.File; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Configuration -{ - public class ServiceProviderCreatorTests - { - private ServiceProviderConfigurationCreator _creator; - private FileReRoute _reRoute; - private FileGlobalConfiguration _globalConfig; - private ServiceProviderConfiguration _result; - - public ServiceProviderCreatorTests() - { - _creator = new ServiceProviderConfigurationCreator(); - } - - [Fact] - public void should_create_service_provider_config() - { - var reRoute = new FileReRoute(); - - var globalConfig = new FileGlobalConfiguration - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider - { - Host = "127.0.0.1", - Port = 1234 - } - }; - - var expected = new ServiceProviderConfigurationBuilder() - .WithServiceDiscoveryProviderHost("127.0.0.1") - .WithServiceDiscoveryProviderPort(1234) - .Build(); - - this.Given(x => x.GivenTheFollowingReRoute(reRoute)) - .And(x => x.GivenTheFollowingGlobalConfig(globalConfig)) - .When(x => x.WhenICreate()) - .Then(x => x.ThenTheConfigIs(expected)) - .BDDfy(); - } - - private void GivenTheFollowingReRoute(FileReRoute fileReRoute) - { - _reRoute = fileReRoute; - } - - private void GivenTheFollowingGlobalConfig(FileGlobalConfiguration fileGlobalConfig) - { - _globalConfig = fileGlobalConfig; - } - - private void WhenICreate() - { - _result = _creator.Create(_globalConfig); - } - - private void ThenTheConfigIs(ServiceProviderConfiguration expected) - { - _result.ServiceProviderHost.ShouldBe(expected.ServiceProviderHost); - _result.ServiceProviderPort.ShouldBe(expected.ServiceProviderPort); - } - } +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class ServiceProviderCreatorTests + { + private ServiceProviderConfigurationCreator _creator; + private FileReRoute _reRoute; + private FileGlobalConfiguration _globalConfig; + private ServiceProviderConfiguration _result; + + public ServiceProviderCreatorTests() + { + _creator = new ServiceProviderConfigurationCreator(); + } + + [Fact] + public void should_create_service_provider_config() + { + var reRoute = new FileReRoute(); + + var globalConfig = new FileGlobalConfiguration + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Host = "127.0.0.1", + Port = 1234 + } + }; + + var expected = new ServiceProviderConfigurationBuilder() + .WithServiceDiscoveryProviderHost("127.0.0.1") + .WithServiceDiscoveryProviderPort(1234) + .Build(); + + this.Given(x => x.GivenTheFollowingReRoute(reRoute)) + .And(x => x.GivenTheFollowingGlobalConfig(globalConfig)) + .When(x => x.WhenICreate()) + .Then(x => x.ThenTheConfigIs(expected)) + .BDDfy(); + } + + private void GivenTheFollowingReRoute(FileReRoute fileReRoute) + { + _reRoute = fileReRoute; + } + + private void GivenTheFollowingGlobalConfig(FileGlobalConfiguration fileGlobalConfig) + { + _globalConfig = fileGlobalConfig; + } + + private void WhenICreate() + { + _result = _creator.Create(_globalConfig); + } + + private void ThenTheConfigIs(ServiceProviderConfiguration expected) + { + _result.ServiceProviderHost.ShouldBe(expected.ServiceProviderHost); + _result.ServiceProviderPort.ShouldBe(expected.ServiceProviderPort); + } + } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Configuration/UpstreamTemplatePatternCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/UpstreamTemplatePatternCreatorTests.cs index 87e2f2d6a..ab7fba4a2 100644 --- a/test/Ocelot.UnitTests/Configuration/UpstreamTemplatePatternCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/UpstreamTemplatePatternCreatorTests.cs @@ -1,184 +1,184 @@ -using System; -using Ocelot.Configuration.Creator; -using Ocelot.Configuration.File; -using Ocelot.Values; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Configuration -{ - public class UpstreamTemplatePatternCreatorTests - { - private FileReRoute _fileReRoute; - private UpstreamTemplatePatternCreator _creator; - private UpstreamPathTemplate _result; - - public UpstreamTemplatePatternCreatorTests() - { - _creator = new UpstreamTemplatePatternCreator(); - } - - [Fact] - public void should_set_upstream_template_pattern_to_ignore_case_sensitivity() - { - var fileReRoute = new FileReRoute - { - UpstreamPathTemplate = "/PRODUCTS/{productId}", - ReRouteIsCaseSensitive = false - }; - - this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) - .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("^(?i)/PRODUCTS/[0-9a-zA-Z].*$")) - .And(x => ThenThePriorityIs(1)) - .BDDfy(); - } - - - [Fact] - public void should_match_forward_slash_or_no_forward_slash_if_template_end_with_forward_slash() - { - var fileReRoute = new FileReRoute - { - UpstreamPathTemplate = "/PRODUCTS/", - ReRouteIsCaseSensitive = false - }; - - this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) - .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("^(?i)/PRODUCTS(/|)$")) - .And(x => ThenThePriorityIs(1)) - .BDDfy(); - } - - [Fact] - public void should_set_upstream_template_pattern_to_respect_case_sensitivity() - { - var fileReRoute = new FileReRoute - { - UpstreamPathTemplate = "/PRODUCTS/{productId}", - ReRouteIsCaseSensitive = true - }; - this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) - .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("^/PRODUCTS/[0-9a-zA-Z].*$")) - .And(x => ThenThePriorityIs(1)) - .BDDfy(); - } - - [Fact] - public void should_create_template_pattern_that_matches_anything_to_end_of_string() - { - var fileReRoute = new FileReRoute - { - UpstreamPathTemplate = "/api/products/{productId}", - ReRouteIsCaseSensitive = true - }; - - this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) - .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("^/api/products/[0-9a-zA-Z].*$")) - .And(x => ThenThePriorityIs(1)) - .BDDfy(); - } - - [Fact] - public void should_create_template_pattern_that_matches_more_than_one_placeholder() - { - var fileReRoute = new FileReRoute - { - UpstreamPathTemplate = "/api/products/{productId}/variants/{variantId}", - ReRouteIsCaseSensitive = true - }; - - this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) - .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("^/api/products/[0-9a-zA-Z].*/variants/[0-9a-zA-Z].*$")) - .And(x => ThenThePriorityIs(1)) - .BDDfy(); - } - - [Fact] - public void should_create_template_pattern_that_matches_more_than_one_placeholder_with_trailing_slash() - { - var fileReRoute = new FileReRoute - { - UpstreamPathTemplate = "/api/products/{productId}/variants/{variantId}/", - ReRouteIsCaseSensitive = true - }; - - this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) - .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("^/api/products/[0-9a-zA-Z].*/variants/[0-9a-zA-Z].*(/|)$")) - .And(x => ThenThePriorityIs(1)) - .BDDfy(); - } - - [Fact] - public void should_create_template_pattern_that_matches_to_end_of_string() - { - var fileReRoute = new FileReRoute - { - UpstreamPathTemplate = "/" - }; - - this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) - .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("^/$")) - .And(x => ThenThePriorityIs(1)) - .BDDfy(); - } - - [Fact] - public void should_create_template_pattern_that_matches_to_end_of_string_when_slash_and_placeholder() - { - var fileReRoute = new FileReRoute - { - UpstreamPathTemplate = "/{url}" - }; - - this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) - .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("^/.*")) - .And(x => ThenThePriorityIs(0)) - .BDDfy(); - } - - [Fact] - public void should_create_template_pattern_that_starts_with_placeholder_then_has_another_later() - { - var fileReRoute = new FileReRoute - { - UpstreamPathTemplate = "/{productId}/products/variants/{variantId}/", - ReRouteIsCaseSensitive = true - }; - - this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) - .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("^/[0-9a-zA-Z].*/products/variants/[0-9a-zA-Z].*(/|)$")) - .And(x => ThenThePriorityIs(1)) - .BDDfy(); - } - - private void GivenTheFollowingFileReRoute(FileReRoute fileReRoute) - { - _fileReRoute = fileReRoute; - } - - private void WhenICreateTheTemplatePattern() - { - _result = _creator.Create(_fileReRoute); - } - - private void ThenTheFollowingIsReturned(string expected) - { - _result.Template.ShouldBe(expected); - } - - private void ThenThePriorityIs(int v) - { - _result.Priority.ShouldBe(v); - } - } +using System; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Ocelot.Values; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class UpstreamTemplatePatternCreatorTests + { + private FileReRoute _fileReRoute; + private UpstreamTemplatePatternCreator _creator; + private UpstreamPathTemplate _result; + + public UpstreamTemplatePatternCreatorTests() + { + _creator = new UpstreamTemplatePatternCreator(); + } + + [Fact] + public void should_set_upstream_template_pattern_to_ignore_case_sensitivity() + { + var fileReRoute = new FileReRoute + { + UpstreamPathTemplate = "/PRODUCTS/{productId}", + ReRouteIsCaseSensitive = false + }; + + this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("^(?i)/PRODUCTS/[0-9a-zA-Z].*$")) + .And(x => ThenThePriorityIs(1)) + .BDDfy(); + } + + + [Fact] + public void should_match_forward_slash_or_no_forward_slash_if_template_end_with_forward_slash() + { + var fileReRoute = new FileReRoute + { + UpstreamPathTemplate = "/PRODUCTS/", + ReRouteIsCaseSensitive = false + }; + + this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("^(?i)/PRODUCTS(/|)$")) + .And(x => ThenThePriorityIs(1)) + .BDDfy(); + } + + [Fact] + public void should_set_upstream_template_pattern_to_respect_case_sensitivity() + { + var fileReRoute = new FileReRoute + { + UpstreamPathTemplate = "/PRODUCTS/{productId}", + ReRouteIsCaseSensitive = true + }; + this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("^/PRODUCTS/[0-9a-zA-Z].*$")) + .And(x => ThenThePriorityIs(1)) + .BDDfy(); + } + + [Fact] + public void should_create_template_pattern_that_matches_anything_to_end_of_string() + { + var fileReRoute = new FileReRoute + { + UpstreamPathTemplate = "/api/products/{productId}", + ReRouteIsCaseSensitive = true + }; + + this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("^/api/products/[0-9a-zA-Z].*$")) + .And(x => ThenThePriorityIs(1)) + .BDDfy(); + } + + [Fact] + public void should_create_template_pattern_that_matches_more_than_one_placeholder() + { + var fileReRoute = new FileReRoute + { + UpstreamPathTemplate = "/api/products/{productId}/variants/{variantId}", + ReRouteIsCaseSensitive = true + }; + + this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("^/api/products/[0-9a-zA-Z].*/variants/[0-9a-zA-Z].*$")) + .And(x => ThenThePriorityIs(1)) + .BDDfy(); + } + + [Fact] + public void should_create_template_pattern_that_matches_more_than_one_placeholder_with_trailing_slash() + { + var fileReRoute = new FileReRoute + { + UpstreamPathTemplate = "/api/products/{productId}/variants/{variantId}/", + ReRouteIsCaseSensitive = true + }; + + this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("^/api/products/[0-9a-zA-Z].*/variants/[0-9a-zA-Z].*(/|)$")) + .And(x => ThenThePriorityIs(1)) + .BDDfy(); + } + + [Fact] + public void should_create_template_pattern_that_matches_to_end_of_string() + { + var fileReRoute = new FileReRoute + { + UpstreamPathTemplate = "/" + }; + + this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("^/$")) + .And(x => ThenThePriorityIs(1)) + .BDDfy(); + } + + [Fact] + public void should_create_template_pattern_that_matches_to_end_of_string_when_slash_and_placeholder() + { + var fileReRoute = new FileReRoute + { + UpstreamPathTemplate = "/{url}" + }; + + this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("^/.*")) + .And(x => ThenThePriorityIs(0)) + .BDDfy(); + } + + [Fact] + public void should_create_template_pattern_that_starts_with_placeholder_then_has_another_later() + { + var fileReRoute = new FileReRoute + { + UpstreamPathTemplate = "/{productId}/products/variants/{variantId}/", + ReRouteIsCaseSensitive = true + }; + + this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("^/[0-9a-zA-Z].*/products/variants/[0-9a-zA-Z].*(/|)$")) + .And(x => ThenThePriorityIs(1)) + .BDDfy(); + } + + private void GivenTheFollowingFileReRoute(FileReRoute fileReRoute) + { + _fileReRoute = fileReRoute; + } + + private void WhenICreateTheTemplatePattern() + { + _result = _creator.Create(_fileReRoute); + } + + private void ThenTheFollowingIsReturned(string expected) + { + _result.Template.ShouldBe(expected); + } + + private void ThenThePriorityIs(int v) + { + _result.Priority.ShouldBe(v); + } + } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs b/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs index f23a8b15b..0d95f0ea6 100644 --- a/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs +++ b/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs @@ -1,195 +1,195 @@ -using System; -using Microsoft.AspNetCore.Mvc; -using Moq; -using Ocelot.Configuration.File; -using Ocelot.Configuration.Setter; -using Ocelot.Errors; -using Ocelot.Responses; -using TestStack.BDDfy; -using Xunit; -using Shouldly; -using Ocelot.Configuration.Provider; -using Microsoft.Extensions.DependencyInjection; -using Ocelot.Raft; -using Rafty.Concensus; -using Newtonsoft.Json; -using Rafty.FiniteStateMachine; -using Ocelot.Configuration; - -namespace Ocelot.UnitTests.Controllers -{ - public class FileConfigurationControllerTests - { - private FileConfigurationController _controller; - private Mock _configGetter; - private Mock _configSetter; - private IActionResult _result; - private FileConfiguration _fileConfiguration; - private Mock _provider; - private Mock _node; - - public FileConfigurationControllerTests() - { - _provider = new Mock(); - _configGetter = new Mock(); - _configSetter = new Mock(); - _controller = new FileConfigurationController(_configGetter.Object, _configSetter.Object, _provider.Object); - } - - [Fact] - public void should_get_file_configuration() - { - var expected = new Responses.OkResponse(new FileConfiguration()); - - this.Given(x => x.GivenTheGetConfigurationReturns(expected)) - .When(x => x.WhenIGetTheFileConfiguration()) - .Then(x => x.TheTheGetFileConfigurationIsCalledCorrectly()) - .BDDfy(); - } - - [Fact] - public void should_return_error_when_cannot_get_config() - { - var expected = new Responses.ErrorResponse(It.IsAny()); - - this.Given(x => x.GivenTheGetConfigurationReturns(expected)) - .When(x => x.WhenIGetTheFileConfiguration()) - .Then(x => x.TheTheGetFileConfigurationIsCalledCorrectly()) - .And(x => x.ThenTheResponseIs()) - .BDDfy(); - } - - [Fact] - public void should_post_file_configuration() - { - var expected = new FileConfiguration(); - - this.Given(x => GivenTheFileConfiguration(expected)) - .And(x => GivenTheConfigSetterReturns(new OkResponse())) - .When(x => WhenIPostTheFileConfiguration()) - .Then(x => x.ThenTheConfigrationSetterIsCalledCorrectly()) - .BDDfy(); - } - - [Fact] - public void should_post_file_configuration_using_raft_node() - { - var expected = new FileConfiguration(); - - this.Given(x => GivenTheFileConfiguration(expected)) - .And(x => GivenARaftNodeIsRegistered()) - .And(x => GivenTheNodeReturnsOK()) - .And(x => GivenTheConfigSetterReturns(new OkResponse())) - .When(x => WhenIPostTheFileConfiguration()) - .Then(x => x.ThenTheNodeIsCalledCorrectly()) - .BDDfy(); - } - - [Fact] - public void should_return_error_when_cannot_set_config_using_raft_node() - { - var expected = new FileConfiguration(); - - this.Given(x => GivenTheFileConfiguration(expected)) - .And(x => GivenARaftNodeIsRegistered()) - .And(x => GivenTheNodeReturnsError()) - .When(x => WhenIPostTheFileConfiguration()) - .Then(x => ThenTheResponseIs()) - .BDDfy(); - } - - [Fact] - public void should_return_error_when_cannot_set_config() - { - var expected = new FileConfiguration(); - - this.Given(x => GivenTheFileConfiguration(expected)) - .And(x => GivenTheConfigSetterReturns(new ErrorResponse(new FakeError()))) - .When(x => WhenIPostTheFileConfiguration()) - .Then(x => x.ThenTheConfigrationSetterIsCalledCorrectly()) - .And(x => ThenTheResponseIs()) - .BDDfy(); - } - - - private void ThenTheNodeIsCalledCorrectly() - { - _node.Verify(x => x.Accept(It.IsAny()), Times.Once); - } - - private void GivenARaftNodeIsRegistered() - { - _node = new Mock(); - _provider - .Setup(x => x.GetService(typeof(INode))) - .Returns(_node.Object); - } - - private void GivenTheNodeReturnsOK() - { - _node - .Setup(x => x.Accept(It.IsAny())) - .Returns(new Rafty.Concensus.OkResponse(new UpdateFileConfiguration(new FileConfiguration()))); - } - - private void GivenTheNodeReturnsError() - { - _node - .Setup(x => x.Accept(It.IsAny())) - .Returns(new Rafty.Concensus.ErrorResponse("error", new UpdateFileConfiguration(new FileConfiguration()))); - } - - private void GivenTheConfigSetterReturns(Response response) - { - _configSetter - .Setup(x => x.Set(It.IsAny())) - .ReturnsAsync(response); - } - - private void ThenTheConfigrationSetterIsCalledCorrectly() - { - _configSetter - .Verify(x => x.Set(_fileConfiguration), Times.Once); - } - - private void WhenIPostTheFileConfiguration() - { - _result = _controller.Post(_fileConfiguration).Result; - } - - private void GivenTheFileConfiguration(FileConfiguration fileConfiguration) - { - _fileConfiguration = fileConfiguration; - } - - private void ThenTheResponseIs() - { - _result.ShouldBeOfType(); - } - - private void GivenTheGetConfigurationReturns(Ocelot.Responses.Response fileConfiguration) - { - _configGetter - .Setup(x => x.Get()) - .ReturnsAsync(fileConfiguration); - } - - private void WhenIGetTheFileConfiguration() - { - _result = _controller.Get().Result; - } - - private void TheTheGetFileConfigurationIsCalledCorrectly() - { - _configGetter - .Verify(x => x.Get(), Times.Once); - } - - class FakeError : Error - { - public FakeError() : base(string.Empty, OcelotErrorCode.CannotAddDataError) - { - } - } - } -} +using System; +using Microsoft.AspNetCore.Mvc; +using Moq; +using Ocelot.Configuration.File; +using Ocelot.Configuration.Setter; +using Ocelot.Errors; +using Ocelot.Responses; +using TestStack.BDDfy; +using Xunit; +using Shouldly; +using Ocelot.Configuration.Provider; +using Microsoft.Extensions.DependencyInjection; +using Ocelot.Raft; +using Rafty.Concensus; +using Newtonsoft.Json; +using Rafty.FiniteStateMachine; +using Ocelot.Configuration; + +namespace Ocelot.UnitTests.Controllers +{ + public class FileConfigurationControllerTests + { + private FileConfigurationController _controller; + private Mock _configGetter; + private Mock _configSetter; + private IActionResult _result; + private FileConfiguration _fileConfiguration; + private Mock _provider; + private Mock _node; + + public FileConfigurationControllerTests() + { + _provider = new Mock(); + _configGetter = new Mock(); + _configSetter = new Mock(); + _controller = new FileConfigurationController(_configGetter.Object, _configSetter.Object, _provider.Object); + } + + [Fact] + public void should_get_file_configuration() + { + var expected = new Responses.OkResponse(new FileConfiguration()); + + this.Given(x => x.GivenTheGetConfigurationReturns(expected)) + .When(x => x.WhenIGetTheFileConfiguration()) + .Then(x => x.TheTheGetFileConfigurationIsCalledCorrectly()) + .BDDfy(); + } + + [Fact] + public void should_return_error_when_cannot_get_config() + { + var expected = new Responses.ErrorResponse(It.IsAny()); + + this.Given(x => x.GivenTheGetConfigurationReturns(expected)) + .When(x => x.WhenIGetTheFileConfiguration()) + .Then(x => x.TheTheGetFileConfigurationIsCalledCorrectly()) + .And(x => x.ThenTheResponseIs()) + .BDDfy(); + } + + [Fact] + public void should_post_file_configuration() + { + var expected = new FileConfiguration(); + + this.Given(x => GivenTheFileConfiguration(expected)) + .And(x => GivenTheConfigSetterReturns(new OkResponse())) + .When(x => WhenIPostTheFileConfiguration()) + .Then(x => x.ThenTheConfigrationSetterIsCalledCorrectly()) + .BDDfy(); + } + + [Fact] + public void should_post_file_configuration_using_raft_node() + { + var expected = new FileConfiguration(); + + this.Given(x => GivenTheFileConfiguration(expected)) + .And(x => GivenARaftNodeIsRegistered()) + .And(x => GivenTheNodeReturnsOK()) + .And(x => GivenTheConfigSetterReturns(new OkResponse())) + .When(x => WhenIPostTheFileConfiguration()) + .Then(x => x.ThenTheNodeIsCalledCorrectly()) + .BDDfy(); + } + + [Fact] + public void should_return_error_when_cannot_set_config_using_raft_node() + { + var expected = new FileConfiguration(); + + this.Given(x => GivenTheFileConfiguration(expected)) + .And(x => GivenARaftNodeIsRegistered()) + .And(x => GivenTheNodeReturnsError()) + .When(x => WhenIPostTheFileConfiguration()) + .Then(x => ThenTheResponseIs()) + .BDDfy(); + } + + [Fact] + public void should_return_error_when_cannot_set_config() + { + var expected = new FileConfiguration(); + + this.Given(x => GivenTheFileConfiguration(expected)) + .And(x => GivenTheConfigSetterReturns(new ErrorResponse(new FakeError()))) + .When(x => WhenIPostTheFileConfiguration()) + .Then(x => x.ThenTheConfigrationSetterIsCalledCorrectly()) + .And(x => ThenTheResponseIs()) + .BDDfy(); + } + + + private void ThenTheNodeIsCalledCorrectly() + { + _node.Verify(x => x.Accept(It.IsAny()), Times.Once); + } + + private void GivenARaftNodeIsRegistered() + { + _node = new Mock(); + _provider + .Setup(x => x.GetService(typeof(INode))) + .Returns(_node.Object); + } + + private void GivenTheNodeReturnsOK() + { + _node + .Setup(x => x.Accept(It.IsAny())) + .Returns(new Rafty.Concensus.OkResponse(new UpdateFileConfiguration(new FileConfiguration()))); + } + + private void GivenTheNodeReturnsError() + { + _node + .Setup(x => x.Accept(It.IsAny())) + .Returns(new Rafty.Concensus.ErrorResponse("error", new UpdateFileConfiguration(new FileConfiguration()))); + } + + private void GivenTheConfigSetterReturns(Response response) + { + _configSetter + .Setup(x => x.Set(It.IsAny())) + .ReturnsAsync(response); + } + + private void ThenTheConfigrationSetterIsCalledCorrectly() + { + _configSetter + .Verify(x => x.Set(_fileConfiguration), Times.Once); + } + + private void WhenIPostTheFileConfiguration() + { + _result = _controller.Post(_fileConfiguration).Result; + } + + private void GivenTheFileConfiguration(FileConfiguration fileConfiguration) + { + _fileConfiguration = fileConfiguration; + } + + private void ThenTheResponseIs() + { + _result.ShouldBeOfType(); + } + + private void GivenTheGetConfigurationReturns(Ocelot.Responses.Response fileConfiguration) + { + _configGetter + .Setup(x => x.Get()) + .ReturnsAsync(fileConfiguration); + } + + private void WhenIGetTheFileConfiguration() + { + _result = _controller.Get().Result; + } + + private void TheTheGetFileConfigurationIsCalledCorrectly() + { + _configGetter + .Verify(x => x.Get(), Times.Once); + } + + class FakeError : Error + { + public FakeError() : base(string.Empty, OcelotErrorCode.CannotAddDataError) + { + } + } + } +} diff --git a/test/Ocelot.UnitTests/Controllers/OutputCacheControllerTests.cs b/test/Ocelot.UnitTests/Controllers/OutputCacheControllerTests.cs index 28818e719..7b4da861c 100644 --- a/test/Ocelot.UnitTests/Controllers/OutputCacheControllerTests.cs +++ b/test/Ocelot.UnitTests/Controllers/OutputCacheControllerTests.cs @@ -1,45 +1,45 @@ -using Xunit; -using Shouldly; -using TestStack.BDDfy; -using Ocelot.Cache; -using System; -using Moq; -using System.Net.Http; -using System.Collections.Generic; -using Microsoft.AspNetCore.Mvc; - -namespace Ocelot.UnitTests.Controllers -{ - public class OutputCacheControllerTests - { - private OutputCacheController _controller; - private Mock> _cache; - private IActionResult _result; - - public OutputCacheControllerTests() - { - _cache = new Mock>(); - _controller = new OutputCacheController(_cache.Object); - } - - [Fact] - public void should_delete_key() - { - this.When(_ => WhenIDeleteTheKey("a")) - .Then(_ => ThenTheKeyIsDeleted("a")) - .BDDfy(); - } - - private void ThenTheKeyIsDeleted(string key) - { - _result.ShouldBeOfType(); - _cache - .Verify(x => x.ClearRegion(key), Times.Once); - } - - private void WhenIDeleteTheKey(string key) - { - _result = _controller.Delete(key); - } - } +using Xunit; +using Shouldly; +using TestStack.BDDfy; +using Ocelot.Cache; +using System; +using Moq; +using System.Net.Http; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc; + +namespace Ocelot.UnitTests.Controllers +{ + public class OutputCacheControllerTests + { + private OutputCacheController _controller; + private Mock> _cache; + private IActionResult _result; + + public OutputCacheControllerTests() + { + _cache = new Mock>(); + _controller = new OutputCacheController(_cache.Object); + } + + [Fact] + public void should_delete_key() + { + this.When(_ => WhenIDeleteTheKey("a")) + .Then(_ => ThenTheKeyIsDeleted("a")) + .BDDfy(); + } + + private void ThenTheKeyIsDeleted(string key) + { + _result.ShouldBeOfType(); + _cache + .Verify(x => x.ClearRegion(key), Times.Once); + } + + private void WhenIDeleteTheKey(string key) + { + _result = _controller.Delete(key); + } + } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs index 52cbb737b..ef7835919 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs @@ -1,225 +1,225 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Text; -using CacheManager.Core; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Hosting.Internal; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Ocelot.Cache; -using Ocelot.Configuration; -using Ocelot.Configuration.File; -using Ocelot.Configuration.Setter; -using Ocelot.DependencyInjection; -using Ocelot.Logging; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.DependencyInjection -{ - public class OcelotBuilderTests - { - private IServiceCollection _services; - private IServiceProvider _serviceProvider; - private IConfiguration _configRoot; - private IOcelotBuilder _ocelotBuilder; - private int _maxRetries; - - public OcelotBuilderTests() - { - IWebHostBuilder builder = new WebHostBuilder(); - _configRoot = new ConfigurationRoot(new List()); - _services = new ServiceCollection(); - _services.AddSingleton(builder); - _services.AddSingleton(); - _services.AddSingleton(_configRoot); - _maxRetries = 100; - } - private Exception _ex; - - [Fact] - public void should_set_up_services() - { - this.When(x => WhenISetUpOcelotServices()) - .Then(x => ThenAnExceptionIsntThrown()) - .BDDfy(); - } - - [Fact] - public void should_return_ocelot_builder() - { - this.When(x => WhenISetUpOcelotServices()) - .Then(x => ThenAnOcelotBuilderIsReturned()) - .BDDfy(); - } - - - [Fact] - public void should_set_up_cache_manager() - { - this.Given(x => WhenISetUpOcelotServices()) - .When(x => WhenISetUpCacheManager()) - .Then(x => ThenAnExceptionIsntThrown()) - .And(x => OnlyOneVersionOfEachCacheIsRegistered()) - .BDDfy(); - } - - [Fact] - public void should_set_up_consul() - { - this.Given(x => WhenISetUpOcelotServices()) - .When(x => WhenISetUpConsul()) - .Then(x => ThenAnExceptionIsntThrown()) - .BDDfy(); - } - - [Fact] - public void should_set_up_rafty() - { - this.Given(x => WhenISetUpOcelotServices()) - .When(x => WhenISetUpRafty()) - .Then(x => ThenAnExceptionIsntThrown()) - .Then(x => ThenTheCorrectAdminPathIsRegitered()) - .BDDfy(); - } - - [Fact] - public void should_use_logger_factory() - { - this.Given(x => WhenISetUpOcelotServices()) - .When(x => WhenIValidateScopes()) - .When(x => WhenIAccessLoggerFactory()) - .Then(x => ThenAnExceptionIsntThrown()) - .BDDfy(); - } - - [Fact] - public void should_set_up_without_passing_in_config() - { - this.When(x => WhenISetUpOcelotServicesWithoutConfig()) - .Then(x => ThenAnExceptionIsntThrown()) - .BDDfy(); - } - - private void ThenTheCorrectAdminPathIsRegitered() - { - _serviceProvider = _services.BuildServiceProvider(); - var path = _serviceProvider.GetService(); - path.Path.ShouldBe("/administration"); - } - - private void OnlyOneVersionOfEachCacheIsRegistered() - { - var outputCache = _services.Single(x => x.ServiceType == typeof(IOcelotCache)); - var outputCacheManager = _services.Single(x => x.ServiceType == typeof(ICacheManager)); - var thing = (CacheManager.Core.ICacheManager)outputCacheManager.ImplementationInstance; - thing.Configuration.MaxRetries.ShouldBe(_maxRetries); - - var ocelotConfigCache = _services.Single(x => x.ServiceType == typeof(IOcelotCache)); - var ocelotConfigCacheManager = _services.Single(x => x.ServiceType == typeof(ICacheManager)); - - var fileConfigCache = _services.Single(x => x.ServiceType == typeof(IOcelotCache)); - var fileConfigCacheManager = _services.Single(x => x.ServiceType == typeof(ICacheManager)); - } - - private void WhenISetUpConsul() - { - try - { - _ocelotBuilder.AddStoreOcelotConfigurationInConsul(); - } - catch (Exception e) - { - _ex = e; - } - } - - private void WhenISetUpRafty() - { - try - { - _ocelotBuilder.AddAdministration("/administration", "secret").AddRafty(); - } - catch (Exception e) - { - _ex = e; - } - } - - private void ThenAnOcelotBuilderIsReturned() - { - _ocelotBuilder.ShouldBeOfType(); - } - - private void WhenISetUpOcelotServices() - { - try - { - _ocelotBuilder = _services.AddOcelot(_configRoot); - } - catch (Exception e) - { - _ex = e; - } - } - - private void WhenISetUpOcelotServicesWithoutConfig() - { - try - { - _ocelotBuilder = _services.AddOcelot(); - } - catch (Exception e) - { - _ex = e; - } - } - - private void WhenISetUpCacheManager() - { - try - { - _ocelotBuilder.AddCacheManager(x => { - x.WithMaxRetries(_maxRetries); - x.WithDictionaryHandle(); - }); - } - catch (Exception e) - { - _ex = e; - } - } - - private void WhenIAccessLoggerFactory() - { - try - { - var logger = _serviceProvider.GetService(); - } - catch (Exception e) - { - _ex = e; - } - } - - private void WhenIValidateScopes() - { - try - { - _serviceProvider = _services.BuildServiceProvider(new ServiceProviderOptions { ValidateScopes = true }); - } - catch (Exception e) - { - _ex = e; - } - } - - private void ThenAnExceptionIsntThrown() - { - _ex.ShouldBeNull(); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; +using CacheManager.Core; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Internal; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Ocelot.Cache; +using Ocelot.Configuration; +using Ocelot.Configuration.File; +using Ocelot.Configuration.Setter; +using Ocelot.DependencyInjection; +using Ocelot.Logging; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.DependencyInjection +{ + public class OcelotBuilderTests + { + private IServiceCollection _services; + private IServiceProvider _serviceProvider; + private IConfiguration _configRoot; + private IOcelotBuilder _ocelotBuilder; + private int _maxRetries; + + public OcelotBuilderTests() + { + IWebHostBuilder builder = new WebHostBuilder(); + _configRoot = new ConfigurationRoot(new List()); + _services = new ServiceCollection(); + _services.AddSingleton(builder); + _services.AddSingleton(); + _services.AddSingleton(_configRoot); + _maxRetries = 100; + } + private Exception _ex; + + [Fact] + public void should_set_up_services() + { + this.When(x => WhenISetUpOcelotServices()) + .Then(x => ThenAnExceptionIsntThrown()) + .BDDfy(); + } + + [Fact] + public void should_return_ocelot_builder() + { + this.When(x => WhenISetUpOcelotServices()) + .Then(x => ThenAnOcelotBuilderIsReturned()) + .BDDfy(); + } + + + [Fact] + public void should_set_up_cache_manager() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => WhenISetUpCacheManager()) + .Then(x => ThenAnExceptionIsntThrown()) + .And(x => OnlyOneVersionOfEachCacheIsRegistered()) + .BDDfy(); + } + + [Fact] + public void should_set_up_consul() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => WhenISetUpConsul()) + .Then(x => ThenAnExceptionIsntThrown()) + .BDDfy(); + } + + [Fact] + public void should_set_up_rafty() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => WhenISetUpRafty()) + .Then(x => ThenAnExceptionIsntThrown()) + .Then(x => ThenTheCorrectAdminPathIsRegitered()) + .BDDfy(); + } + + [Fact] + public void should_use_logger_factory() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => WhenIValidateScopes()) + .When(x => WhenIAccessLoggerFactory()) + .Then(x => ThenAnExceptionIsntThrown()) + .BDDfy(); + } + + [Fact] + public void should_set_up_without_passing_in_config() + { + this.When(x => WhenISetUpOcelotServicesWithoutConfig()) + .Then(x => ThenAnExceptionIsntThrown()) + .BDDfy(); + } + + private void ThenTheCorrectAdminPathIsRegitered() + { + _serviceProvider = _services.BuildServiceProvider(); + var path = _serviceProvider.GetService(); + path.Path.ShouldBe("/administration"); + } + + private void OnlyOneVersionOfEachCacheIsRegistered() + { + var outputCache = _services.Single(x => x.ServiceType == typeof(IOcelotCache)); + var outputCacheManager = _services.Single(x => x.ServiceType == typeof(ICacheManager)); + var thing = (CacheManager.Core.ICacheManager)outputCacheManager.ImplementationInstance; + thing.Configuration.MaxRetries.ShouldBe(_maxRetries); + + var ocelotConfigCache = _services.Single(x => x.ServiceType == typeof(IOcelotCache)); + var ocelotConfigCacheManager = _services.Single(x => x.ServiceType == typeof(ICacheManager)); + + var fileConfigCache = _services.Single(x => x.ServiceType == typeof(IOcelotCache)); + var fileConfigCacheManager = _services.Single(x => x.ServiceType == typeof(ICacheManager)); + } + + private void WhenISetUpConsul() + { + try + { + _ocelotBuilder.AddStoreOcelotConfigurationInConsul(); + } + catch (Exception e) + { + _ex = e; + } + } + + private void WhenISetUpRafty() + { + try + { + _ocelotBuilder.AddAdministration("/administration", "secret").AddRafty(); + } + catch (Exception e) + { + _ex = e; + } + } + + private void ThenAnOcelotBuilderIsReturned() + { + _ocelotBuilder.ShouldBeOfType(); + } + + private void WhenISetUpOcelotServices() + { + try + { + _ocelotBuilder = _services.AddOcelot(_configRoot); + } + catch (Exception e) + { + _ex = e; + } + } + + private void WhenISetUpOcelotServicesWithoutConfig() + { + try + { + _ocelotBuilder = _services.AddOcelot(); + } + catch (Exception e) + { + _ex = e; + } + } + + private void WhenISetUpCacheManager() + { + try + { + _ocelotBuilder.AddCacheManager(x => { + x.WithMaxRetries(_maxRetries); + x.WithDictionaryHandle(); + }); + } + catch (Exception e) + { + _ex = e; + } + } + + private void WhenIAccessLoggerFactory() + { + try + { + var logger = _serviceProvider.GetService(); + } + catch (Exception e) + { + _ex = e; + } + } + + private void WhenIValidateScopes() + { + try + { + _serviceProvider = _services.BuildServiceProvider(new ServiceProviderOptions { ValidateScopes = true }); + } + catch (Exception e) + { + _ex = e; + } + } + + private void ThenAnExceptionIsntThrown() + { + _ex.ShouldBeNull(); + } + } +} diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs index 31cafa286..df1aaf06e 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs @@ -1,91 +1,91 @@ -namespace Ocelot.UnitTests.DownstreamRouteFinder -{ - using System.Collections.Generic; - using Microsoft.AspNetCore.Builder; - using Microsoft.Extensions.DependencyInjection; - using Moq; - using Ocelot.Configuration; - using Ocelot.Configuration.Builder; - using Ocelot.Configuration.Provider; - using Ocelot.DownstreamRouteFinder; - using Ocelot.DownstreamRouteFinder.Finder; - using Ocelot.DownstreamRouteFinder.Middleware; - using Ocelot.DownstreamRouteFinder.UrlMatcher; - using Ocelot.Logging; - using Ocelot.Responses; - using TestStack.BDDfy; - using Xunit; - - public class DownstreamRouteFinderMiddlewareTests : ServerHostedMiddlewareTest - { - private readonly Mock _downstreamRouteFinder; - private readonly Mock _provider; - private Response _downstreamRoute; - private IOcelotConfiguration _config; - - public DownstreamRouteFinderMiddlewareTests() - { - _provider = new Mock(); - _downstreamRouteFinder = new Mock(); - - GivenTheTestServerIsConfigured(); - } - - [Fact] - public void should_call_scoped_data_repository_correctly() - { - var config = new OcelotConfiguration(null, null, new ServiceProviderConfigurationBuilder().Build(), ""); - - this.Given(x => x.GivenTheDownStreamRouteFinderReturns( - new DownstreamRoute( - new List(), - new ReRouteBuilder() - .WithDownstreamPathTemplate("any old string") - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()))) - .And(x => GivenTheFollowingConfig(config)) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly()) - .BDDfy(); - } - - private void GivenTheFollowingConfig(IOcelotConfiguration config) - { - _config = config; - _provider - .Setup(x => x.Get()) - .ReturnsAsync(new OkResponse(_config)); - } - - protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) - { - services.AddSingleton(); - services.AddLogging(); - services.AddSingleton(_downstreamRouteFinder.Object); - services.AddSingleton(_provider.Object); - services.AddSingleton(ScopedRepository.Object); - } - - protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) - { - app.UseDownstreamRouteFinderMiddleware(); - } - - private void GivenTheDownStreamRouteFinderReturns(DownstreamRoute downstreamRoute) - { - _downstreamRoute = new OkResponse(downstreamRoute); - _downstreamRouteFinder - .Setup(x => x.FindDownstreamRoute(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(_downstreamRoute); - } - - private void ThenTheScopedDataRepositoryIsCalledCorrectly() - { - ScopedRepository - .Verify(x => x.Add("DownstreamRoute", _downstreamRoute.Data), Times.Once()); - - ScopedRepository - .Verify(x => x.Add("ServiceProviderConfiguration", _config.ServiceProviderConfiguration), Times.Once()); - } - } -} +namespace Ocelot.UnitTests.DownstreamRouteFinder +{ + using System.Collections.Generic; + using Microsoft.AspNetCore.Builder; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; + using Ocelot.Configuration.Provider; + using Ocelot.DownstreamRouteFinder; + using Ocelot.DownstreamRouteFinder.Finder; + using Ocelot.DownstreamRouteFinder.Middleware; + using Ocelot.DownstreamRouteFinder.UrlMatcher; + using Ocelot.Logging; + using Ocelot.Responses; + using TestStack.BDDfy; + using Xunit; + + public class DownstreamRouteFinderMiddlewareTests : ServerHostedMiddlewareTest + { + private readonly Mock _downstreamRouteFinder; + private readonly Mock _provider; + private Response _downstreamRoute; + private IOcelotConfiguration _config; + + public DownstreamRouteFinderMiddlewareTests() + { + _provider = new Mock(); + _downstreamRouteFinder = new Mock(); + + GivenTheTestServerIsConfigured(); + } + + [Fact] + public void should_call_scoped_data_repository_correctly() + { + var config = new OcelotConfiguration(null, null, new ServiceProviderConfigurationBuilder().Build(), ""); + + this.Given(x => x.GivenTheDownStreamRouteFinderReturns( + new DownstreamRoute( + new List(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()))) + .And(x => GivenTheFollowingConfig(config)) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly()) + .BDDfy(); + } + + private void GivenTheFollowingConfig(IOcelotConfiguration config) + { + _config = config; + _provider + .Setup(x => x.Get()) + .ReturnsAsync(new OkResponse(_config)); + } + + protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + { + services.AddSingleton(); + services.AddLogging(); + services.AddSingleton(_downstreamRouteFinder.Object); + services.AddSingleton(_provider.Object); + services.AddSingleton(ScopedRepository.Object); + } + + protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) + { + app.UseDownstreamRouteFinderMiddleware(); + } + + private void GivenTheDownStreamRouteFinderReturns(DownstreamRoute downstreamRoute) + { + _downstreamRoute = new OkResponse(downstreamRoute); + _downstreamRouteFinder + .Setup(x => x.FindDownstreamRoute(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(_downstreamRoute); + } + + private void ThenTheScopedDataRepositoryIsCalledCorrectly() + { + ScopedRepository + .Verify(x => x.Add("DownstreamRoute", _downstreamRoute.Data), Times.Once()); + + ScopedRepository + .Verify(x => x.Add("ServiceProviderConfiguration", _config.ServiceProviderConfiguration), Times.Once()); + } + } +} diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index 81b7be708..5e2006d38 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -1,452 +1,452 @@ -using System.Collections.Generic; -using Moq; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.Configuration.Creator; -using Ocelot.Configuration.Provider; -using Ocelot.DownstreamRouteFinder; -using Ocelot.DownstreamRouteFinder.Finder; -using Ocelot.DownstreamRouteFinder.UrlMatcher; -using Ocelot.Responses; -using Ocelot.Values; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.DownstreamRouteFinder -{ - public class DownstreamRouteFinderTests - { - private readonly IDownstreamRouteFinder _downstreamRouteFinder; - private readonly Mock _mockMatcher; - private readonly Mock _finder; - private string _upstreamUrlPath; - private Response _result; - private List _reRoutesConfig; - private OcelotConfiguration _config; - private Response _match; - private string _upstreamHttpMethod; - - public DownstreamRouteFinderTests() - { - _mockMatcher = new Mock(); - _finder = new Mock(); - _downstreamRouteFinder = new Ocelot.DownstreamRouteFinder.Finder.DownstreamRouteFinder(_mockMatcher.Object, _finder.Object); - } - - - [Fact] - public void should_return_highest_priority_when_first() - { - var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); - - this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) - .And(x => x.GivenTheTemplateVariableAndNameFinderReturns( - new OkResponse>(new List()))) - .And(x => x.GivenTheConfigurationIs(new List - { - new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamPathTemplate("someUpstreamPath") - .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) - .Build(), - new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamPathTemplate("someUpstreamPath") - .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 0)) - .Build() - }, string.Empty, serviceProviderConfig)) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) - .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) - .When(x => x.WhenICallTheFinder()) - .Then(x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), - new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) - .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) - .Build() - ))) - .BDDfy(); - } - - [Fact] - public void should_return_highest_priority_when_lowest() - { - var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); - - this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) - .And(x => x.GivenTheTemplateVariableAndNameFinderReturns( - new OkResponse>(new List()))) - .And(x => x.GivenTheConfigurationIs(new List - { - new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamPathTemplate("someUpstreamPath") - .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 0)) - .Build(), - new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamPathTemplate("someUpstreamPath") - .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) - .Build() - }, string.Empty, serviceProviderConfig)) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) - .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) - .When(x => x.WhenICallTheFinder()) - .Then(x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), - new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) - .WithUpstreamHttpMethod(new List { "Post" }) - .Build() - ))) - .BDDfy(); - } - - [Fact] - public void should_return_route() - { - var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); - - this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/")) - .And(x =>x.GivenTheTemplateVariableAndNameFinderReturns( - new OkResponse>( - new List()))) - .And(x => x.GivenTheConfigurationIs(new List - { - new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamPathTemplate("someUpstreamPath") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) - .Build() - }, string.Empty, serviceProviderConfig - )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) - .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) - .When(x => x.WhenICallTheFinder()) - .Then( - x => x.ThenTheFollowingIsReturned(new DownstreamRoute( - new List(), - new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) - .Build() - ))) - .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) - .BDDfy(); - } - - - [Fact] - public void should_not_append_slash_to_upstream_url_path() - { - var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); - - this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher")) - .And(x =>x.GivenTheTemplateVariableAndNameFinderReturns( - new OkResponse>( - new List()))) - .And(x => x.GivenTheConfigurationIs(new List - { - new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamPathTemplate("someUpstreamPath") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) - .Build() - }, string.Empty, serviceProviderConfig - )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) - .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) - .When(x => x.WhenICallTheFinder()) - .Then( - x => x.ThenTheFollowingIsReturned(new DownstreamRoute( - new List(), - new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) - .Build() - ))) - .And(x => x.ThenTheUrlMatcherIsCalledCorrectly("matchInUrlMatcher")) - .BDDfy(); - } - - [Fact] - public void should_return_route_if_upstream_path_and_upstream_template_are_the_same() - { - var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); - - this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) - .And( - x => - x.GivenTheTemplateVariableAndNameFinderReturns( - new OkResponse>(new List()))) - .And(x => x.GivenTheConfigurationIs(new List - { - new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamPathTemplate("someUpstreamPath") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) - .Build() - }, string.Empty, serviceProviderConfig - )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) - .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) - .When(x => x.WhenICallTheFinder()) - .Then( - x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), - new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) - .Build() - ))) - .And(x => x.ThenTheUrlMatcherIsNotCalled()) - .BDDfy(); - } - - [Fact] - public void should_return_correct_route_for_http_verb() - { - var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); - - this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) - .And( - x => - x.GivenTheTemplateVariableAndNameFinderReturns( - new OkResponse>(new List()))) - .And(x => x.GivenTheConfigurationIs(new List - { - new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamPathTemplate("someUpstreamPath") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) - .Build(), - new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPathForAPost") - .WithUpstreamPathTemplate("someUpstreamPath") - .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) - .Build() - }, string.Empty, serviceProviderConfig - )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) - .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) - .When(x => x.WhenICallTheFinder()) - .Then( - x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), - new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPathForAPost") - .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) - .Build() - ))) - .BDDfy(); - } - - [Fact] - public void should_not_return_route() - { - var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); - - this.Given(x => x.GivenThereIsAnUpstreamUrlPath("dontMatchPath/")) - .And(x => x.GivenTheConfigurationIs(new List - { - new ReRouteBuilder() - .WithDownstreamPathTemplate("somPath") - .WithUpstreamPathTemplate("somePath") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("somePath", 1)) - .Build(), - }, string.Empty, serviceProviderConfig - )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(false)))) - .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) - .When(x => x.WhenICallTheFinder()) - .Then( - x => x.ThenAnErrorResponseIsReturned()) - .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) - .BDDfy(); - } - - [Fact] - public void should_return_correct_route_for_http_verb_setting_multiple_upstream_http_method() - { - var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); - - this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) - .And( - x => - x.GivenTheTemplateVariableAndNameFinderReturns( - new OkResponse>(new List()))) - .And(x => x.GivenTheConfigurationIs(new List - { - new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamPathTemplate("someUpstreamPath") - .WithUpstreamHttpMethod(new List { "Get", "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) - .Build() - }, string.Empty, serviceProviderConfig - )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) - .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) - .When(x => x.WhenICallTheFinder()) - .Then( - x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), - new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) - .Build() - ))) - .BDDfy(); - } - - [Fact] - public void should_return_correct_route_for_http_verb_setting_all_upstream_http_method() - { - var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); - - this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) - .And( - x => - x.GivenTheTemplateVariableAndNameFinderReturns( - new OkResponse>(new List()))) - .And(x => x.GivenTheConfigurationIs(new List - { - new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamPathTemplate("someUpstreamPath") - .WithUpstreamHttpMethod(new List()) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) - .Build() - }, string.Empty, serviceProviderConfig - )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) - .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) - .When(x => x.WhenICallTheFinder()) - .Then( - x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), - new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) - .Build() - ))) - .BDDfy(); - } - - [Fact] - public void should_not_return_route_for_http_verb_not_setting_in_upstream_http_method() - { - var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); - - this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) - .And( - x => - x.GivenTheTemplateVariableAndNameFinderReturns( - new OkResponse>(new List()))) - .And(x => x.GivenTheConfigurationIs(new List - { - new ReRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamPathTemplate("someUpstreamPath") - .WithUpstreamHttpMethod(new List { "Get", "Patch", "Delete" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) - .Build() - }, string.Empty, serviceProviderConfig - )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) - .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) - .When(x => x.WhenICallTheFinder()) - .Then( - x => x.ThenAnErrorResponseIsReturned()) - .And(x => x.ThenTheUrlMatcherIsNotCalled()) - .BDDfy(); - } - - private void GivenTheTemplateVariableAndNameFinderReturns(Response> response) - { - _finder - .Setup(x => x.Find(It.IsAny(), It.IsAny())) - .Returns(response); - } - - private void GivenTheUpstreamHttpMethodIs(string upstreamHttpMethod) - { - _upstreamHttpMethod = upstreamHttpMethod; - } - - private void ThenAnErrorResponseIsReturned() - { - _result.IsError.ShouldBeTrue(); - } - - private void ThenTheUrlMatcherIsCalledCorrectly() - { - _mockMatcher - .Verify(x => x.Match(_upstreamUrlPath, _reRoutesConfig[0].UpstreamPathTemplate.Value), Times.Once); - } - - private void ThenTheUrlMatcherIsCalledCorrectly(string expectedUpstreamUrlPath) - { - _mockMatcher - .Verify(x => x.Match(expectedUpstreamUrlPath, _reRoutesConfig[0].UpstreamPathTemplate.Value), Times.Once); - } - - private void ThenTheUrlMatcherIsNotCalled() - { - _mockMatcher - .Verify(x => x.Match(_upstreamUrlPath, _reRoutesConfig[0].UpstreamPathTemplate.Value), Times.Never); - } - - private void GivenTheUrlMatcherReturns(Response match) - { - _match = match; - _mockMatcher - .Setup(x => x.Match(It.IsAny(), It.IsAny())) - .Returns(_match); - } - - private void GivenTheConfigurationIs(List reRoutesConfig, string adminPath, ServiceProviderConfiguration serviceProviderConfig) - { - _reRoutesConfig = reRoutesConfig; - _config = new OcelotConfiguration(_reRoutesConfig, adminPath, serviceProviderConfig, ""); - } - - private void GivenThereIsAnUpstreamUrlPath(string upstreamUrlPath) - { - _upstreamUrlPath = upstreamUrlPath; - } - - private void WhenICallTheFinder() - { - _result = _downstreamRouteFinder.FindDownstreamRoute(_upstreamUrlPath, _upstreamHttpMethod, _config); - } - - private void ThenTheFollowingIsReturned(DownstreamRoute expected) - { - _result.Data.ReRoute.DownstreamPathTemplate.Value.ShouldBe(expected.ReRoute.DownstreamPathTemplate.Value); - _result.Data.ReRoute.UpstreamTemplatePattern.Priority.ShouldBe(expected.ReRoute.UpstreamTemplatePattern.Priority); - - for (int i = 0; i < _result.Data.TemplatePlaceholderNameAndValues.Count; i++) - { - _result.Data.TemplatePlaceholderNameAndValues[i].Name.ShouldBe(expected.TemplatePlaceholderNameAndValues[i].Name); - _result.Data.TemplatePlaceholderNameAndValues[i].Value.ShouldBe(expected.TemplatePlaceholderNameAndValues[i].Value); - } - - _result.IsError.ShouldBeFalse(); - } - } -} +using System.Collections.Generic; +using Moq; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.Provider; +using Ocelot.DownstreamRouteFinder; +using Ocelot.DownstreamRouteFinder.Finder; +using Ocelot.DownstreamRouteFinder.UrlMatcher; +using Ocelot.Responses; +using Ocelot.Values; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.DownstreamRouteFinder +{ + public class DownstreamRouteFinderTests + { + private readonly IDownstreamRouteFinder _downstreamRouteFinder; + private readonly Mock _mockMatcher; + private readonly Mock _finder; + private string _upstreamUrlPath; + private Response _result; + private List _reRoutesConfig; + private OcelotConfiguration _config; + private Response _match; + private string _upstreamHttpMethod; + + public DownstreamRouteFinderTests() + { + _mockMatcher = new Mock(); + _finder = new Mock(); + _downstreamRouteFinder = new Ocelot.DownstreamRouteFinder.Finder.DownstreamRouteFinder(_mockMatcher.Object, _finder.Object); + } + + + [Fact] + public void should_return_highest_priority_when_first() + { + var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); + + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) + .And(x => x.GivenTheTemplateVariableAndNameFinderReturns( + new OkResponse>(new List()))) + .And(x => x.GivenTheConfigurationIs(new List + { + new ReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) + .Build(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 0)) + .Build() + }, string.Empty, serviceProviderConfig)) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) + .When(x => x.WhenICallTheFinder()) + .Then(x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) + .Build() + ))) + .BDDfy(); + } + + [Fact] + public void should_return_highest_priority_when_lowest() + { + var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); + + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) + .And(x => x.GivenTheTemplateVariableAndNameFinderReturns( + new OkResponse>(new List()))) + .And(x => x.GivenTheConfigurationIs(new List + { + new ReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 0)) + .Build(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) + .Build() + }, string.Empty, serviceProviderConfig)) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) + .When(x => x.WhenICallTheFinder()) + .Then(x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) + .WithUpstreamHttpMethod(new List { "Post" }) + .Build() + ))) + .BDDfy(); + } + + [Fact] + public void should_return_route() + { + var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); + + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/")) + .And(x =>x.GivenTheTemplateVariableAndNameFinderReturns( + new OkResponse>( + new List()))) + .And(x => x.GivenTheConfigurationIs(new List + { + new ReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .Build() + }, string.Empty, serviceProviderConfig + )) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) + .When(x => x.WhenICallTheFinder()) + .Then( + x => x.ThenTheFollowingIsReturned(new DownstreamRoute( + new List(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .Build() + ))) + .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) + .BDDfy(); + } + + + [Fact] + public void should_not_append_slash_to_upstream_url_path() + { + var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); + + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher")) + .And(x =>x.GivenTheTemplateVariableAndNameFinderReturns( + new OkResponse>( + new List()))) + .And(x => x.GivenTheConfigurationIs(new List + { + new ReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .Build() + }, string.Empty, serviceProviderConfig + )) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) + .When(x => x.WhenICallTheFinder()) + .Then( + x => x.ThenTheFollowingIsReturned(new DownstreamRoute( + new List(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .Build() + ))) + .And(x => x.ThenTheUrlMatcherIsCalledCorrectly("matchInUrlMatcher")) + .BDDfy(); + } + + [Fact] + public void should_return_route_if_upstream_path_and_upstream_template_are_the_same() + { + var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); + + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) + .And( + x => + x.GivenTheTemplateVariableAndNameFinderReturns( + new OkResponse>(new List()))) + .And(x => x.GivenTheConfigurationIs(new List + { + new ReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .Build() + }, string.Empty, serviceProviderConfig + )) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) + .When(x => x.WhenICallTheFinder()) + .Then( + x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .Build() + ))) + .And(x => x.ThenTheUrlMatcherIsNotCalled()) + .BDDfy(); + } + + [Fact] + public void should_return_correct_route_for_http_verb() + { + var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); + + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) + .And( + x => + x.GivenTheTemplateVariableAndNameFinderReturns( + new OkResponse>(new List()))) + .And(x => x.GivenTheConfigurationIs(new List + { + new ReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .Build(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPathForAPost") + .WithUpstreamPathTemplate("someUpstreamPath") + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .Build() + }, string.Empty, serviceProviderConfig + )) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) + .When(x => x.WhenICallTheFinder()) + .Then( + x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPathForAPost") + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .Build() + ))) + .BDDfy(); + } + + [Fact] + public void should_not_return_route() + { + var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); + + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("dontMatchPath/")) + .And(x => x.GivenTheConfigurationIs(new List + { + new ReRouteBuilder() + .WithDownstreamPathTemplate("somPath") + .WithUpstreamPathTemplate("somePath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("somePath", 1)) + .Build(), + }, string.Empty, serviceProviderConfig + )) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(false)))) + .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) + .When(x => x.WhenICallTheFinder()) + .Then( + x => x.ThenAnErrorResponseIsReturned()) + .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) + .BDDfy(); + } + + [Fact] + public void should_return_correct_route_for_http_verb_setting_multiple_upstream_http_method() + { + var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); + + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) + .And( + x => + x.GivenTheTemplateVariableAndNameFinderReturns( + new OkResponse>(new List()))) + .And(x => x.GivenTheConfigurationIs(new List + { + new ReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") + .WithUpstreamHttpMethod(new List { "Get", "Post" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .Build() + }, string.Empty, serviceProviderConfig + )) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) + .When(x => x.WhenICallTheFinder()) + .Then( + x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .Build() + ))) + .BDDfy(); + } + + [Fact] + public void should_return_correct_route_for_http_verb_setting_all_upstream_http_method() + { + var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); + + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) + .And( + x => + x.GivenTheTemplateVariableAndNameFinderReturns( + new OkResponse>(new List()))) + .And(x => x.GivenTheConfigurationIs(new List + { + new ReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") + .WithUpstreamHttpMethod(new List()) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .Build() + }, string.Empty, serviceProviderConfig + )) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) + .When(x => x.WhenICallTheFinder()) + .Then( + x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .Build() + ))) + .BDDfy(); + } + + [Fact] + public void should_not_return_route_for_http_verb_not_setting_in_upstream_http_method() + { + var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); + + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) + .And( + x => + x.GivenTheTemplateVariableAndNameFinderReturns( + new OkResponse>(new List()))) + .And(x => x.GivenTheConfigurationIs(new List + { + new ReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") + .WithUpstreamHttpMethod(new List { "Get", "Patch", "Delete" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .Build() + }, string.Empty, serviceProviderConfig + )) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) + .When(x => x.WhenICallTheFinder()) + .Then( + x => x.ThenAnErrorResponseIsReturned()) + .And(x => x.ThenTheUrlMatcherIsNotCalled()) + .BDDfy(); + } + + private void GivenTheTemplateVariableAndNameFinderReturns(Response> response) + { + _finder + .Setup(x => x.Find(It.IsAny(), It.IsAny())) + .Returns(response); + } + + private void GivenTheUpstreamHttpMethodIs(string upstreamHttpMethod) + { + _upstreamHttpMethod = upstreamHttpMethod; + } + + private void ThenAnErrorResponseIsReturned() + { + _result.IsError.ShouldBeTrue(); + } + + private void ThenTheUrlMatcherIsCalledCorrectly() + { + _mockMatcher + .Verify(x => x.Match(_upstreamUrlPath, _reRoutesConfig[0].UpstreamPathTemplate.Value), Times.Once); + } + + private void ThenTheUrlMatcherIsCalledCorrectly(string expectedUpstreamUrlPath) + { + _mockMatcher + .Verify(x => x.Match(expectedUpstreamUrlPath, _reRoutesConfig[0].UpstreamPathTemplate.Value), Times.Once); + } + + private void ThenTheUrlMatcherIsNotCalled() + { + _mockMatcher + .Verify(x => x.Match(_upstreamUrlPath, _reRoutesConfig[0].UpstreamPathTemplate.Value), Times.Never); + } + + private void GivenTheUrlMatcherReturns(Response match) + { + _match = match; + _mockMatcher + .Setup(x => x.Match(It.IsAny(), It.IsAny())) + .Returns(_match); + } + + private void GivenTheConfigurationIs(List reRoutesConfig, string adminPath, ServiceProviderConfiguration serviceProviderConfig) + { + _reRoutesConfig = reRoutesConfig; + _config = new OcelotConfiguration(_reRoutesConfig, adminPath, serviceProviderConfig, ""); + } + + private void GivenThereIsAnUpstreamUrlPath(string upstreamUrlPath) + { + _upstreamUrlPath = upstreamUrlPath; + } + + private void WhenICallTheFinder() + { + _result = _downstreamRouteFinder.FindDownstreamRoute(_upstreamUrlPath, _upstreamHttpMethod, _config); + } + + private void ThenTheFollowingIsReturned(DownstreamRoute expected) + { + _result.Data.ReRoute.DownstreamPathTemplate.Value.ShouldBe(expected.ReRoute.DownstreamPathTemplate.Value); + _result.Data.ReRoute.UpstreamTemplatePattern.Priority.ShouldBe(expected.ReRoute.UpstreamTemplatePattern.Priority); + + for (int i = 0; i < _result.Data.TemplatePlaceholderNameAndValues.Count; i++) + { + _result.Data.TemplatePlaceholderNameAndValues[i].Name.ShouldBe(expected.TemplatePlaceholderNameAndValues[i].Name); + _result.Data.TemplatePlaceholderNameAndValues[i].Value.ShouldBe(expected.TemplatePlaceholderNameAndValues[i].Value); + } + + _result.IsError.ShouldBeFalse(); + } + } +} diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcherTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcherTests.cs index f57ed8420..0f3fea3b9 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcherTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcherTests.cs @@ -1,218 +1,218 @@ -using Ocelot.DownstreamRouteFinder.UrlMatcher; -using Ocelot.Responses; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher -{ - public class RegExUrlMatcherTests - { - private readonly IUrlPathToUrlTemplateMatcher _urlMatcher; - private string _downstreamUrlPath; - private string _downstreamPathTemplate; - private Response _result; - - public RegExUrlMatcherTests() - { - _urlMatcher = new RegExUrlMatcher(); - } - - [Fact] - public void should_not_match_slash_becaue_we_need_to_match_something_after_it() - { - const string RegExForwardSlashAndOnePlaceHolder = "^/[0-9a-zA-Z].*"; - - this.Given(x => x.GivenIHaveAUpstreamPath("/")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern(RegExForwardSlashAndOnePlaceHolder)) - .When(x => x.WhenIMatchThePaths()) - .And(x => x.ThenTheResultIsFalse()) - .BDDfy(); - } - - [Fact] - public void should_not_match_forward_slash_only_regex() - { - this.Given(x => x.GivenIHaveAUpstreamPath("/working/")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^/$")) - .When(x => x.WhenIMatchThePaths()) - .And(x => x.ThenTheResultIsFalse()) - .BDDfy(); - } - - [Fact] - public void should_not_match_issue_134() - { - this.Given(x => x.GivenIHaveAUpstreamPath("/api/vacancy/1/")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^(?i)/vacancy/.*/$")) - .When(x => x.WhenIMatchThePaths()) - .And(x => x.ThenTheResultIsFalse()) - .BDDfy(); - } - - [Fact] - public void should_match_forward_slash_only_regex() - { - this.Given(x => x.GivenIHaveAUpstreamPath("/")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^/$")) - .When(x => x.WhenIMatchThePaths()) - .And(x => x.ThenTheResultIsTrue()) - .BDDfy(); - } - - [Fact] - public void should_find_match_when_template_smaller_than_valid_path() - { - this.Given(x => x.GivenIHaveAUpstreamPath("/api/products/2354325435624623464235")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^/api/products/.*$")) - .When(x => x.WhenIMatchThePaths()) - .And(x => x.ThenTheResultIsTrue()) - .BDDfy(); - } - - [Fact] - public void should_not_find_match() - { - this.Given(x => x.GivenIHaveAUpstreamPath("/api/values")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^/$")) - .When(x => x.WhenIMatchThePaths()) - .And(x => x.ThenTheResultIsFalse()) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url() - { - this.Given(x => x.GivenIHaveAUpstreamPath("")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^$")) - .When(x => x.WhenIMatchThePaths()) - .And(x => x.ThenTheResultIsTrue()) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_no_slash() - { - this.Given(x => x.GivenIHaveAUpstreamPath("api")) - .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api$")) - .When(x => x.WhenIMatchThePaths()) - .Then(x => x.ThenTheResultIsTrue()) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_one_slash() - { - this.Given(x => x.GivenIHaveAUpstreamPath("api/")) - .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/$")) - .When(x => x.WhenIMatchThePaths()) - .Then(x => x.ThenTheResultIsTrue()) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_downstream_template() - { - this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/")) - .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/$")) - .When(x => x.WhenIMatchThePaths()) - .Then(x => x.ThenTheResultIsTrue()) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_downstream_template_with_one_place_holder() - { - this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1")) - .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.*$")) - .When(x => x.WhenIMatchThePaths()) - .Then(x => x.ThenTheResultIsTrue()) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_downstream_template_with_two_place_holders() - { - this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/2")) - .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.*/.*$")) - .When(x => x.WhenIMatchThePaths()) - .Then(x => x.ThenTheResultIsTrue()) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_downstream_template_with_two_place_holders_seperated_by_something() - { - this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.*/categories/.*$")) - .When(x => x.WhenIMatchThePaths()) - .Then(x => x.ThenTheResultIsTrue()) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_downstream_template_with_three_place_holders_seperated_by_something() - { - this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2/variant/123")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.*/categories/.*/variant/.*$")) - .When(x => x.WhenIMatchThePaths()) - .Then(x => x.ThenTheResultIsTrue()) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_downstream_template_with_three_place_holders() - { - this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2/variant/")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.*/categories/.*/variant/$")) - .When(x => x.WhenIMatchThePaths()) - .Then(x => x.ThenTheResultIsTrue()) - .BDDfy(); - } - - [Fact] - public void should_ignore_case_sensitivity() - { - this.Given(x => x.GivenIHaveAUpstreamPath("API/product/products/1/categories/2/variant/")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^(?i)api/product/products/.*/categories/.*/variant/$")) - .When(x => x.WhenIMatchThePaths()) - .Then(x => x.ThenTheResultIsTrue()) - .BDDfy(); - } - - [Fact] - public void should_respect_case_sensitivity() - { - this.Given(x => x.GivenIHaveAUpstreamPath("API/product/products/1/categories/2/variant/")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.*/categories/.*/variant/$")) - .When(x => x.WhenIMatchThePaths()) - .Then(x => x.ThenTheResultIsFalse()) - .BDDfy(); - } - - private void GivenIHaveAUpstreamPath(string downstreamPath) - { - _downstreamUrlPath = downstreamPath; - } - - private void GivenIHaveAnUpstreamUrlTemplatePattern(string downstreamUrlTemplate) - { - _downstreamPathTemplate = downstreamUrlTemplate; - } - - private void WhenIMatchThePaths() - { - _result = _urlMatcher.Match(_downstreamUrlPath, _downstreamPathTemplate); - } - - private void ThenTheResultIsTrue() - { - _result.Data.Match.ShouldBeTrue(); - } - - private void ThenTheResultIsFalse() - { - _result.Data.Match.ShouldBeFalse(); - } - } +using Ocelot.DownstreamRouteFinder.UrlMatcher; +using Ocelot.Responses; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher +{ + public class RegExUrlMatcherTests + { + private readonly IUrlPathToUrlTemplateMatcher _urlMatcher; + private string _downstreamUrlPath; + private string _downstreamPathTemplate; + private Response _result; + + public RegExUrlMatcherTests() + { + _urlMatcher = new RegExUrlMatcher(); + } + + [Fact] + public void should_not_match_slash_becaue_we_need_to_match_something_after_it() + { + const string RegExForwardSlashAndOnePlaceHolder = "^/[0-9a-zA-Z].*"; + + this.Given(x => x.GivenIHaveAUpstreamPath("/")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern(RegExForwardSlashAndOnePlaceHolder)) + .When(x => x.WhenIMatchThePaths()) + .And(x => x.ThenTheResultIsFalse()) + .BDDfy(); + } + + [Fact] + public void should_not_match_forward_slash_only_regex() + { + this.Given(x => x.GivenIHaveAUpstreamPath("/working/")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^/$")) + .When(x => x.WhenIMatchThePaths()) + .And(x => x.ThenTheResultIsFalse()) + .BDDfy(); + } + + [Fact] + public void should_not_match_issue_134() + { + this.Given(x => x.GivenIHaveAUpstreamPath("/api/vacancy/1/")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^(?i)/vacancy/.*/$")) + .When(x => x.WhenIMatchThePaths()) + .And(x => x.ThenTheResultIsFalse()) + .BDDfy(); + } + + [Fact] + public void should_match_forward_slash_only_regex() + { + this.Given(x => x.GivenIHaveAUpstreamPath("/")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^/$")) + .When(x => x.WhenIMatchThePaths()) + .And(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void should_find_match_when_template_smaller_than_valid_path() + { + this.Given(x => x.GivenIHaveAUpstreamPath("/api/products/2354325435624623464235")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^/api/products/.*$")) + .When(x => x.WhenIMatchThePaths()) + .And(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void should_not_find_match() + { + this.Given(x => x.GivenIHaveAUpstreamPath("/api/values")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^/$")) + .When(x => x.WhenIMatchThePaths()) + .And(x => x.ThenTheResultIsFalse()) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url() + { + this.Given(x => x.GivenIHaveAUpstreamPath("")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^$")) + .When(x => x.WhenIMatchThePaths()) + .And(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_no_slash() + { + this.Given(x => x.GivenIHaveAUpstreamPath("api")) + .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api$")) + .When(x => x.WhenIMatchThePaths()) + .Then(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_one_slash() + { + this.Given(x => x.GivenIHaveAUpstreamPath("api/")) + .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/$")) + .When(x => x.WhenIMatchThePaths()) + .Then(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_downstream_template() + { + this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/")) + .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/$")) + .When(x => x.WhenIMatchThePaths()) + .Then(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_downstream_template_with_one_place_holder() + { + this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1")) + .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.*$")) + .When(x => x.WhenIMatchThePaths()) + .Then(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_downstream_template_with_two_place_holders() + { + this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/2")) + .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.*/.*$")) + .When(x => x.WhenIMatchThePaths()) + .Then(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_downstream_template_with_two_place_holders_seperated_by_something() + { + this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.*/categories/.*$")) + .When(x => x.WhenIMatchThePaths()) + .Then(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_downstream_template_with_three_place_holders_seperated_by_something() + { + this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2/variant/123")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.*/categories/.*/variant/.*$")) + .When(x => x.WhenIMatchThePaths()) + .Then(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_downstream_template_with_three_place_holders() + { + this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2/variant/")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.*/categories/.*/variant/$")) + .When(x => x.WhenIMatchThePaths()) + .Then(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void should_ignore_case_sensitivity() + { + this.Given(x => x.GivenIHaveAUpstreamPath("API/product/products/1/categories/2/variant/")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^(?i)api/product/products/.*/categories/.*/variant/$")) + .When(x => x.WhenIMatchThePaths()) + .Then(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void should_respect_case_sensitivity() + { + this.Given(x => x.GivenIHaveAUpstreamPath("API/product/products/1/categories/2/variant/")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.*/categories/.*/variant/$")) + .When(x => x.WhenIMatchThePaths()) + .Then(x => x.ThenTheResultIsFalse()) + .BDDfy(); + } + + private void GivenIHaveAUpstreamPath(string downstreamPath) + { + _downstreamUrlPath = downstreamPath; + } + + private void GivenIHaveAnUpstreamUrlTemplatePattern(string downstreamUrlTemplate) + { + _downstreamPathTemplate = downstreamUrlTemplate; + } + + private void WhenIMatchThePaths() + { + _result = _urlMatcher.Match(_downstreamUrlPath, _downstreamPathTemplate); + } + + private void ThenTheResultIsTrue() + { + _result.Data.Match.ShouldBeTrue(); + } + + private void ThenTheResultIsFalse() + { + _result.Data.Match.ShouldBeFalse(); + } + } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinderTests.cs index 9fa9366b9..afd1b3ae7 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinderTests.cs @@ -1,267 +1,267 @@ -using System.Collections.Generic; -using System.Linq; -using Ocelot.DownstreamRouteFinder.UrlMatcher; -using Ocelot.Responses; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher -{ - public class UrlPathPlaceholderNameAndValueFinderTests - { - private readonly IPlaceholderNameAndValueFinder _finder; - private string _downstreamUrlPath; - private string _downstreamPathTemplate; - private Response> _result; - - public UrlPathPlaceholderNameAndValueFinderTests() - { - _finder = new UrlPathPlaceholderNameAndValueFinder(); - } - - [Fact] - public void can_match_down_stream_url() - { - this.Given(x => x.GivenIHaveAUpstreamPath("")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(new List())) - .BDDfy(); - } - - - [Fact] - public void can_match_down_stream_url_with_nothing_then_placeholder_no_value_is_blank() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{url}", "") - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/{url}")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_nothing_then_placeholder_value_is_test() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{url}", "test") - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("/test")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/{url}")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_forward_slash_then_placeholder_no_value_is_blank() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{url}", "") - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("/")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/{url}")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_forward_slash() - { - var expectedTemplates = new List - { - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("/")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_forward_slash_then_placeholder_then_another_value() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{url}", "1") - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("/1/products")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/{url}/products")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void should_not_find_anything() - { - this.Given(x => x.GivenIHaveAUpstreamPath("/products")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/products/")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(new List())) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_no_slash() - { - this.Given(x => x.GivenIHaveAUpstreamPath("api")) - .Given(x => x.GivenIHaveAnUpstreamUrlTemplate("api")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(new List())) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_one_slash() - { - this.Given(x => x.GivenIHaveAUpstreamPath("api/")) - .Given(x => x.GivenIHaveAnUpstreamUrlTemplate("api/")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(new List())) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_downstream_template() - { - this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/")) - .Given(x => x.GivenIHaveAnUpstreamUrlTemplate("api/product/products/")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(new List())) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_downstream_template_with_one_place_holder() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{productId}", "1") - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1")) - .Given(x => x.GivenIHaveAnUpstreamUrlTemplate("api/product/products/{productId}")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_downstream_template_with_two_place_holders() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{productId}", "1"), - new PlaceholderNameAndValue("{categoryId}", "2") - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/2")) - .Given(x => x.GivenIHaveAnUpstreamUrlTemplate("api/product/products/{productId}/{categoryId}")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_downstream_template_with_two_place_holders_seperated_by_something() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{productId}", "1"), - new PlaceholderNameAndValue("{categoryId}", "2") - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("api/product/products/{productId}/categories/{categoryId}")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_downstream_template_with_three_place_holders_seperated_by_something() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{productId}", "1"), - new PlaceholderNameAndValue("{categoryId}", "2"), - new PlaceholderNameAndValue("{variantId}", "123") - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2/variant/123")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("api/product/products/{productId}/categories/{categoryId}/variant/{variantId}")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_downstream_template_with_three_place_holders() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{productId}", "1"), - new PlaceholderNameAndValue("{categoryId}", "2") - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2/variant/")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("api/product/products/{productId}/categories/{categoryId}/variant/")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_downstream_template_with_place_holder_to_final_url_path() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{finalUrlPath}", "product/products/categories/"), - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/categories/")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("api/{finalUrlPath}/")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - private void ThenTheTemplatesVariablesAre(List expectedResults) - { - foreach (var expectedResult in expectedResults) - { - var result = _result.Data.First(t => t.Name == expectedResult.Name); - result.Value.ShouldBe(expectedResult.Value); - } - } - - private void GivenIHaveAUpstreamPath(string downstreamPath) - { - _downstreamUrlPath = downstreamPath; - } - - private void GivenIHaveAnUpstreamUrlTemplate(string downstreamUrlTemplate) - { - _downstreamPathTemplate = downstreamUrlTemplate; - } - - private void WhenIFindTheUrlVariableNamesAndValues() - { - _result = _finder.Find(_downstreamUrlPath, _downstreamPathTemplate); - } - } +using System.Collections.Generic; +using System.Linq; +using Ocelot.DownstreamRouteFinder.UrlMatcher; +using Ocelot.Responses; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher +{ + public class UrlPathPlaceholderNameAndValueFinderTests + { + private readonly IPlaceholderNameAndValueFinder _finder; + private string _downstreamUrlPath; + private string _downstreamPathTemplate; + private Response> _result; + + public UrlPathPlaceholderNameAndValueFinderTests() + { + _finder = new UrlPathPlaceholderNameAndValueFinder(); + } + + [Fact] + public void can_match_down_stream_url() + { + this.Given(x => x.GivenIHaveAUpstreamPath("")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(new List())) + .BDDfy(); + } + + + [Fact] + public void can_match_down_stream_url_with_nothing_then_placeholder_no_value_is_blank() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{url}", "") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/{url}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_nothing_then_placeholder_value_is_test() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{url}", "test") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("/test")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/{url}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_forward_slash_then_placeholder_no_value_is_blank() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{url}", "") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("/")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/{url}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_forward_slash() + { + var expectedTemplates = new List + { + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("/")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_forward_slash_then_placeholder_then_another_value() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{url}", "1") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("/1/products")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/{url}/products")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void should_not_find_anything() + { + this.Given(x => x.GivenIHaveAUpstreamPath("/products")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/products/")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(new List())) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_no_slash() + { + this.Given(x => x.GivenIHaveAUpstreamPath("api")) + .Given(x => x.GivenIHaveAnUpstreamUrlTemplate("api")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(new List())) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_one_slash() + { + this.Given(x => x.GivenIHaveAUpstreamPath("api/")) + .Given(x => x.GivenIHaveAnUpstreamUrlTemplate("api/")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(new List())) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_downstream_template() + { + this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/")) + .Given(x => x.GivenIHaveAnUpstreamUrlTemplate("api/product/products/")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(new List())) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_downstream_template_with_one_place_holder() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{productId}", "1") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1")) + .Given(x => x.GivenIHaveAnUpstreamUrlTemplate("api/product/products/{productId}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_downstream_template_with_two_place_holders() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{productId}", "1"), + new PlaceholderNameAndValue("{categoryId}", "2") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/2")) + .Given(x => x.GivenIHaveAnUpstreamUrlTemplate("api/product/products/{productId}/{categoryId}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_downstream_template_with_two_place_holders_seperated_by_something() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{productId}", "1"), + new PlaceholderNameAndValue("{categoryId}", "2") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("api/product/products/{productId}/categories/{categoryId}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_downstream_template_with_three_place_holders_seperated_by_something() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{productId}", "1"), + new PlaceholderNameAndValue("{categoryId}", "2"), + new PlaceholderNameAndValue("{variantId}", "123") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2/variant/123")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("api/product/products/{productId}/categories/{categoryId}/variant/{variantId}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_downstream_template_with_three_place_holders() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{productId}", "1"), + new PlaceholderNameAndValue("{categoryId}", "2") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2/variant/")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("api/product/products/{productId}/categories/{categoryId}/variant/")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_downstream_template_with_place_holder_to_final_url_path() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{finalUrlPath}", "product/products/categories/"), + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/categories/")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("api/{finalUrlPath}/")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + private void ThenTheTemplatesVariablesAre(List expectedResults) + { + foreach (var expectedResult in expectedResults) + { + var result = _result.Data.First(t => t.Name == expectedResult.Name); + result.Value.ShouldBe(expectedResult.Value); + } + } + + private void GivenIHaveAUpstreamPath(string downstreamPath) + { + _downstreamUrlPath = downstreamPath; + } + + private void GivenIHaveAnUpstreamUrlTemplate(string downstreamUrlTemplate) + { + _downstreamPathTemplate = downstreamUrlTemplate; + } + + private void WhenIFindTheUrlVariableNamesAndValues() + { + _result = _finder.Find(_downstreamUrlPath, _downstreamPathTemplate); + } + } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs index 04afdf3a7..b7e80e87c 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs @@ -1,103 +1,103 @@ -namespace Ocelot.UnitTests.DownstreamUrlCreator -{ - using System; - using System.Collections.Generic; - using System.Net.Http; - using Microsoft.AspNetCore.Builder; - using Microsoft.Extensions.DependencyInjection; - using Moq; - using Ocelot.Configuration.Builder; - using Ocelot.DownstreamRouteFinder; - using Ocelot.DownstreamRouteFinder.UrlMatcher; - using Ocelot.DownstreamUrlCreator; - using Ocelot.DownstreamUrlCreator.Middleware; - using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; - using Ocelot.Infrastructure.RequestData; - using Ocelot.Logging; - using Ocelot.Responses; - using Ocelot.Values; - using TestStack.BDDfy; - using Xunit; - using Shouldly; - - public class DownstreamUrlCreatorMiddlewareTests : ServerHostedMiddlewareTest - { - private readonly Mock _downstreamUrlTemplateVariableReplacer; - private readonly Mock _urlBuilder; - private Response _downstreamRoute; - private OkResponse _downstreamPath; - private HttpRequestMessage _downstreamRequest; - - public DownstreamUrlCreatorMiddlewareTests() - { - _downstreamUrlTemplateVariableReplacer = new Mock(); - _urlBuilder = new Mock(); - - _downstreamRequest = new HttpRequestMessage(HttpMethod.Get, "https://my.url/abc/?q=123"); - - ScopedRepository - .Setup(sr => sr.Get("DownstreamRequest")) - .Returns(new OkResponse(_downstreamRequest)); - - GivenTheTestServerIsConfigured(); - } - - [Fact] - public void should_replace_scheme_and_path() - { - this.Given(x => x.GivenTheDownStreamRouteIs( - new DownstreamRoute( - new List(), - new ReRouteBuilder() - .WithDownstreamPathTemplate("any old string") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithDownstreamScheme("https") - .Build()))) - .And(x => x.GivenTheDownstreamRequestUriIs("http://my.url/abc?q=123")) - .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(); - } - - protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) - { - services.AddSingleton(); - services.AddLogging(); - services.AddSingleton(_downstreamUrlTemplateVariableReplacer.Object); - services.AddSingleton(ScopedRepository.Object); - services.AddSingleton(_urlBuilder.Object); - } - - protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) - { - app.UseDownstreamUrlCreatorMiddleware(); - } - - private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) - { - _downstreamRoute = new OkResponse(downstreamRoute); - ScopedRepository - .Setup(x => x.Get(It.IsAny())) - .Returns(_downstreamRoute); - } - - private void GivenTheDownstreamRequestUriIs(string uri) - { - _downstreamRequest.RequestUri = new Uri(uri); - } - - private void GivenTheUrlReplacerWillReturn(string path) - { - _downstreamPath = new OkResponse(new DownstreamPath(path)); - _downstreamUrlTemplateVariableReplacer - .Setup(x => x.Replace(It.IsAny(), It.IsAny>())) - .Returns(_downstreamPath); - } - - private void ThenTheDownstreamRequestUriIs(string expectedUri) - { - _downstreamRequest.RequestUri.OriginalString.ShouldBe(expectedUri); - } - } -} +namespace Ocelot.UnitTests.DownstreamUrlCreator +{ + using System; + using System.Collections.Generic; + using System.Net.Http; + using Microsoft.AspNetCore.Builder; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using Ocelot.Configuration.Builder; + using Ocelot.DownstreamRouteFinder; + using Ocelot.DownstreamRouteFinder.UrlMatcher; + using Ocelot.DownstreamUrlCreator; + using Ocelot.DownstreamUrlCreator.Middleware; + using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; + using Ocelot.Infrastructure.RequestData; + using Ocelot.Logging; + using Ocelot.Responses; + using Ocelot.Values; + using TestStack.BDDfy; + using Xunit; + using Shouldly; + + public class DownstreamUrlCreatorMiddlewareTests : ServerHostedMiddlewareTest + { + private readonly Mock _downstreamUrlTemplateVariableReplacer; + private readonly Mock _urlBuilder; + private Response _downstreamRoute; + private OkResponse _downstreamPath; + private HttpRequestMessage _downstreamRequest; + + public DownstreamUrlCreatorMiddlewareTests() + { + _downstreamUrlTemplateVariableReplacer = new Mock(); + _urlBuilder = new Mock(); + + _downstreamRequest = new HttpRequestMessage(HttpMethod.Get, "https://my.url/abc/?q=123"); + + ScopedRepository + .Setup(sr => sr.Get("DownstreamRequest")) + .Returns(new OkResponse(_downstreamRequest)); + + GivenTheTestServerIsConfigured(); + } + + [Fact] + public void should_replace_scheme_and_path() + { + this.Given(x => x.GivenTheDownStreamRouteIs( + new DownstreamRoute( + new List(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithDownstreamScheme("https") + .Build()))) + .And(x => x.GivenTheDownstreamRequestUriIs("http://my.url/abc?q=123")) + .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(); + } + + protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + { + services.AddSingleton(); + services.AddLogging(); + services.AddSingleton(_downstreamUrlTemplateVariableReplacer.Object); + services.AddSingleton(ScopedRepository.Object); + services.AddSingleton(_urlBuilder.Object); + } + + protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) + { + app.UseDownstreamUrlCreatorMiddleware(); + } + + private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) + { + _downstreamRoute = new OkResponse(downstreamRoute); + ScopedRepository + .Setup(x => x.Get(It.IsAny())) + .Returns(_downstreamRoute); + } + + private void GivenTheDownstreamRequestUriIs(string uri) + { + _downstreamRequest.RequestUri = new Uri(uri); + } + + private void GivenTheUrlReplacerWillReturn(string path) + { + _downstreamPath = new OkResponse(new DownstreamPath(path)); + _downstreamUrlTemplateVariableReplacer + .Setup(x => x.Replace(It.IsAny(), It.IsAny>())) + .Returns(_downstreamPath); + } + + private void ThenTheDownstreamRequestUriIs(string expectedUri) + { + _downstreamRequest.RequestUri.OriginalString.ShouldBe(expectedUri); + } + } +} diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlBuilderTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlBuilderTests.cs index 0414bbb36..20dfa1a1d 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlBuilderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlBuilderTests.cs @@ -1,122 +1,122 @@ -using System; -using Ocelot.DownstreamUrlCreator; -using Ocelot.Responses; -using Ocelot.Values; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.DownstreamUrlCreator -{ - public class UrlBuilderTests - { - private readonly IUrlBuilder _urlBuilder; - private string _dsPath; - private string _dsScheme; - private string _dsHost; - private int _dsPort; - - private Response _result; - - public UrlBuilderTests() - { - _urlBuilder = new UrlBuilder(); - } - - [Fact] - public void should_return_error_when_downstream_path_is_null() - { - this.Given(x => x.GivenADownstreamPath(null)) - .When(x => x.WhenIBuildTheUrl()) - .Then(x => x.ThenThereIsAnErrorOfType()) - .BDDfy(); - } - - [Fact] - public void should_return_error_when_downstream_scheme_is_null() - { - this.Given(x => x.GivenADownstreamScheme(null)) - .And(x => x.GivenADownstreamPath("test")) - .When(x => x.WhenIBuildTheUrl()) - .Then(x => x.ThenThereIsAnErrorOfType()) - .BDDfy(); - } - - [Fact] - public void should_return_error_when_downstream_host_is_null() - { - this.Given(x => x.GivenADownstreamScheme(null)) - .And(x => x.GivenADownstreamPath("test")) - .And(x => x.GivenADownstreamScheme("test")) - .When(x => x.WhenIBuildTheUrl()) - .Then(x => x.ThenThereIsAnErrorOfType()) - .BDDfy(); - } - - [Fact] - public void should_not_use_port_if_zero() - { - this.Given(x => x.GivenADownstreamPath("/api/products/1")) - .And(x => x.GivenADownstreamScheme("http")) - .And(x => x.GivenADownstreamHost("127.0.0.1")) - .And(x => x.GivenADownstreamPort(0)) - .When(x => x.WhenIBuildTheUrl()) - .Then(x => x.ThenTheUrlIsReturned("http://127.0.0.1/api/products/1")) - .And(x => x.ThenTheUrlIsWellFormed()) - .BDDfy(); - } - - [Fact] - public void should_build_well_formed_uri() - { - this.Given(x => x.GivenADownstreamPath("/api/products/1")) - .And(x => x.GivenADownstreamScheme("http")) - .And(x => x.GivenADownstreamHost("127.0.0.1")) - .And(x => x.GivenADownstreamPort(5000)) - .When(x => x.WhenIBuildTheUrl()) - .Then(x => x.ThenTheUrlIsReturned("http://127.0.0.1:5000/api/products/1")) - .And(x => x.ThenTheUrlIsWellFormed()) - .BDDfy(); - } - - private void ThenThereIsAnErrorOfType() - { - _result.Errors[0].ShouldBeOfType(); - } - - private void GivenADownstreamPath(string dsPath) - { - _dsPath = dsPath; - } - - private void GivenADownstreamScheme(string dsScheme) - { - _dsScheme = dsScheme; - } - - private void GivenADownstreamHost(string dsHost) - { - _dsHost = dsHost; - } - - private void GivenADownstreamPort(int dsPort) - { - _dsPort = dsPort; - } - - private void WhenIBuildTheUrl() - { - _result = _urlBuilder.Build(_dsPath, _dsScheme, new HostAndPort(_dsHost, _dsPort)); - } - - private void ThenTheUrlIsReturned(string expected) - { - _result.Data.Value.ShouldBe(expected); - } - - private void ThenTheUrlIsWellFormed() - { - Uri.IsWellFormedUriString(_result.Data.Value, UriKind.Absolute).ShouldBeTrue(); - } - } -} +using System; +using Ocelot.DownstreamUrlCreator; +using Ocelot.Responses; +using Ocelot.Values; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.DownstreamUrlCreator +{ + public class UrlBuilderTests + { + private readonly IUrlBuilder _urlBuilder; + private string _dsPath; + private string _dsScheme; + private string _dsHost; + private int _dsPort; + + private Response _result; + + public UrlBuilderTests() + { + _urlBuilder = new UrlBuilder(); + } + + [Fact] + public void should_return_error_when_downstream_path_is_null() + { + this.Given(x => x.GivenADownstreamPath(null)) + .When(x => x.WhenIBuildTheUrl()) + .Then(x => x.ThenThereIsAnErrorOfType()) + .BDDfy(); + } + + [Fact] + public void should_return_error_when_downstream_scheme_is_null() + { + this.Given(x => x.GivenADownstreamScheme(null)) + .And(x => x.GivenADownstreamPath("test")) + .When(x => x.WhenIBuildTheUrl()) + .Then(x => x.ThenThereIsAnErrorOfType()) + .BDDfy(); + } + + [Fact] + public void should_return_error_when_downstream_host_is_null() + { + this.Given(x => x.GivenADownstreamScheme(null)) + .And(x => x.GivenADownstreamPath("test")) + .And(x => x.GivenADownstreamScheme("test")) + .When(x => x.WhenIBuildTheUrl()) + .Then(x => x.ThenThereIsAnErrorOfType()) + .BDDfy(); + } + + [Fact] + public void should_not_use_port_if_zero() + { + this.Given(x => x.GivenADownstreamPath("/api/products/1")) + .And(x => x.GivenADownstreamScheme("http")) + .And(x => x.GivenADownstreamHost("127.0.0.1")) + .And(x => x.GivenADownstreamPort(0)) + .When(x => x.WhenIBuildTheUrl()) + .Then(x => x.ThenTheUrlIsReturned("http://127.0.0.1/api/products/1")) + .And(x => x.ThenTheUrlIsWellFormed()) + .BDDfy(); + } + + [Fact] + public void should_build_well_formed_uri() + { + this.Given(x => x.GivenADownstreamPath("/api/products/1")) + .And(x => x.GivenADownstreamScheme("http")) + .And(x => x.GivenADownstreamHost("127.0.0.1")) + .And(x => x.GivenADownstreamPort(5000)) + .When(x => x.WhenIBuildTheUrl()) + .Then(x => x.ThenTheUrlIsReturned("http://127.0.0.1:5000/api/products/1")) + .And(x => x.ThenTheUrlIsWellFormed()) + .BDDfy(); + } + + private void ThenThereIsAnErrorOfType() + { + _result.Errors[0].ShouldBeOfType(); + } + + private void GivenADownstreamPath(string dsPath) + { + _dsPath = dsPath; + } + + private void GivenADownstreamScheme(string dsScheme) + { + _dsScheme = dsScheme; + } + + private void GivenADownstreamHost(string dsHost) + { + _dsHost = dsHost; + } + + private void GivenADownstreamPort(int dsPort) + { + _dsPort = dsPort; + } + + private void WhenIBuildTheUrl() + { + _result = _urlBuilder.Build(_dsPath, _dsScheme, new ServiceHostAndPort(_dsHost, _dsPort)); + } + + private void ThenTheUrlIsReturned(string expected) + { + _result.Data.Value.ShouldBe(expected); + } + + private void ThenTheUrlIsWellFormed() + { + Uri.IsWellFormedUriString(_result.Data.Value, UriKind.Absolute).ShouldBeTrue(); + } + } +} diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs index 7aa8fc16e..de10cbb5f 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs @@ -1,183 +1,183 @@ -using System.Collections.Generic; -using Ocelot.Configuration.Builder; -using Ocelot.DownstreamRouteFinder; -using Ocelot.DownstreamRouteFinder.UrlMatcher; -using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; -using Ocelot.Responses; -using Ocelot.Values; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer -{ - public class UpstreamUrlPathTemplateVariableReplacerTests - { - private DownstreamRoute _downstreamRoute; - private Response _result; - private readonly IDownstreamPathPlaceholderReplacer _downstreamPathReplacer; - - public UpstreamUrlPathTemplateVariableReplacerTests() - { - _downstreamPathReplacer = new DownstreamTemplatePathPlaceholderReplacer(); - } - - [Fact] - public void can_replace_no_template_variables() - { - this.Given(x => x.GivenThereIsAUrlMatch( - new DownstreamRoute( - new List(), - new ReRouteBuilder() - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()))) - .When(x => x.WhenIReplaceTheTemplateVariables()) - .Then(x => x.ThenTheDownstreamUrlPathIsReturned("")) - .BDDfy(); - } - - [Fact] - public void can_replace_no_template_variables_with_slash() - { - this.Given(x => x.GivenThereIsAUrlMatch( - new DownstreamRoute( - new List(), - new ReRouteBuilder() - .WithDownstreamPathTemplate("/") - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()))) - .When(x => x.WhenIReplaceTheTemplateVariables()) - .Then(x => x.ThenTheDownstreamUrlPathIsReturned("/")) - .BDDfy(); - } - - [Fact] - public void can_replace_url_no_slash() - { - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), - new ReRouteBuilder() - .WithDownstreamPathTemplate("api") - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()))) - .When(x => x.WhenIReplaceTheTemplateVariables()) - .Then(x => x.ThenTheDownstreamUrlPathIsReturned("api")) - .BDDfy(); - } - - [Fact] - public void can_replace_url_one_slash() - { - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), - new ReRouteBuilder() - .WithDownstreamPathTemplate("api/") - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()))) - .When(x => x.WhenIReplaceTheTemplateVariables()) - .Then(x => x.ThenTheDownstreamUrlPathIsReturned("api/")) - .BDDfy(); - } - - [Fact] - public void can_replace_url_multiple_slash() - { - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), - new ReRouteBuilder() - .WithDownstreamPathTemplate("api/product/products/") - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()))) - .When(x => x.WhenIReplaceTheTemplateVariables()) - .Then(x => x.ThenTheDownstreamUrlPathIsReturned("api/product/products/")) - .BDDfy(); - } - - [Fact] - public void can_replace_url_one_template_variable() - { - var templateVariables = new List() - { - new PlaceholderNameAndValue("{productId}", "1") - }; - - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, - new ReRouteBuilder() - .WithDownstreamPathTemplate("productservice/products/{productId}/") - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()))) - .When(x => x.WhenIReplaceTheTemplateVariables()) - .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/products/1/")) - .BDDfy(); - } - - [Fact] - public void can_replace_url_one_template_variable_with_path_after() - { - var templateVariables = new List() - { - new PlaceholderNameAndValue("{productId}", "1") - }; - - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, - new ReRouteBuilder() - .WithDownstreamPathTemplate("productservice/products/{productId}/variants") - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()))) - .When(x => x.WhenIReplaceTheTemplateVariables()) - .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/products/1/variants")) - .BDDfy(); - } - - [Fact] - public void can_replace_url_two_template_variable() - { - var templateVariables = new List() - { - new PlaceholderNameAndValue("{productId}", "1"), - new PlaceholderNameAndValue("{variantId}", "12") - }; - - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, - new ReRouteBuilder() - .WithDownstreamPathTemplate("productservice/products/{productId}/variants/{variantId}") - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()))) - .When(x => x.WhenIReplaceTheTemplateVariables()) - .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/products/1/variants/12")) - .BDDfy(); - } - - [Fact] - public void can_replace_url_three_template_variable() - { - var templateVariables = new List() - { - new PlaceholderNameAndValue("{productId}", "1"), - new PlaceholderNameAndValue("{variantId}", "12"), - new PlaceholderNameAndValue("{categoryId}", "34") - }; - - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, - new ReRouteBuilder() - .WithDownstreamPathTemplate("productservice/category/{categoryId}/products/{productId}/variants/{variantId}") - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()))) - .When(x => x.WhenIReplaceTheTemplateVariables()) - .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/category/34/products/1/variants/12")) - .BDDfy(); - } - - private void GivenThereIsAUrlMatch(DownstreamRoute downstreamRoute) - { - _downstreamRoute = downstreamRoute; - } - - private void WhenIReplaceTheTemplateVariables() - { - _result = _downstreamPathReplacer.Replace(_downstreamRoute.ReRoute.DownstreamPathTemplate, _downstreamRoute.TemplatePlaceholderNameAndValues); - } - - private void ThenTheDownstreamUrlPathIsReturned(string expected) - { - _result.Data.Value.ShouldBe(expected); - } - } -} +using System.Collections.Generic; +using Ocelot.Configuration.Builder; +using Ocelot.DownstreamRouteFinder; +using Ocelot.DownstreamRouteFinder.UrlMatcher; +using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; +using Ocelot.Responses; +using Ocelot.Values; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer +{ + public class UpstreamUrlPathTemplateVariableReplacerTests + { + private DownstreamRoute _downstreamRoute; + private Response _result; + private readonly IDownstreamPathPlaceholderReplacer _downstreamPathReplacer; + + public UpstreamUrlPathTemplateVariableReplacerTests() + { + _downstreamPathReplacer = new DownstreamTemplatePathPlaceholderReplacer(); + } + + [Fact] + public void can_replace_no_template_variables() + { + this.Given(x => x.GivenThereIsAUrlMatch( + new DownstreamRoute( + new List(), + new ReRouteBuilder() + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()))) + .When(x => x.WhenIReplaceTheTemplateVariables()) + .Then(x => x.ThenTheDownstreamUrlPathIsReturned("")) + .BDDfy(); + } + + [Fact] + public void can_replace_no_template_variables_with_slash() + { + this.Given(x => x.GivenThereIsAUrlMatch( + new DownstreamRoute( + new List(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("/") + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()))) + .When(x => x.WhenIReplaceTheTemplateVariables()) + .Then(x => x.ThenTheDownstreamUrlPathIsReturned("/")) + .BDDfy(); + } + + [Fact] + public void can_replace_url_no_slash() + { + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("api") + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()))) + .When(x => x.WhenIReplaceTheTemplateVariables()) + .Then(x => x.ThenTheDownstreamUrlPathIsReturned("api")) + .BDDfy(); + } + + [Fact] + public void can_replace_url_one_slash() + { + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("api/") + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()))) + .When(x => x.WhenIReplaceTheTemplateVariables()) + .Then(x => x.ThenTheDownstreamUrlPathIsReturned("api/")) + .BDDfy(); + } + + [Fact] + public void can_replace_url_multiple_slash() + { + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("api/product/products/") + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()))) + .When(x => x.WhenIReplaceTheTemplateVariables()) + .Then(x => x.ThenTheDownstreamUrlPathIsReturned("api/product/products/")) + .BDDfy(); + } + + [Fact] + public void can_replace_url_one_template_variable() + { + var templateVariables = new List() + { + new PlaceholderNameAndValue("{productId}", "1") + }; + + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, + new ReRouteBuilder() + .WithDownstreamPathTemplate("productservice/products/{productId}/") + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()))) + .When(x => x.WhenIReplaceTheTemplateVariables()) + .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/products/1/")) + .BDDfy(); + } + + [Fact] + public void can_replace_url_one_template_variable_with_path_after() + { + var templateVariables = new List() + { + new PlaceholderNameAndValue("{productId}", "1") + }; + + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, + new ReRouteBuilder() + .WithDownstreamPathTemplate("productservice/products/{productId}/variants") + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()))) + .When(x => x.WhenIReplaceTheTemplateVariables()) + .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/products/1/variants")) + .BDDfy(); + } + + [Fact] + public void can_replace_url_two_template_variable() + { + var templateVariables = new List() + { + new PlaceholderNameAndValue("{productId}", "1"), + new PlaceholderNameAndValue("{variantId}", "12") + }; + + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, + new ReRouteBuilder() + .WithDownstreamPathTemplate("productservice/products/{productId}/variants/{variantId}") + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()))) + .When(x => x.WhenIReplaceTheTemplateVariables()) + .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/products/1/variants/12")) + .BDDfy(); + } + + [Fact] + public void can_replace_url_three_template_variable() + { + var templateVariables = new List() + { + new PlaceholderNameAndValue("{productId}", "1"), + new PlaceholderNameAndValue("{variantId}", "12"), + new PlaceholderNameAndValue("{categoryId}", "34") + }; + + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, + new ReRouteBuilder() + .WithDownstreamPathTemplate("productservice/category/{categoryId}/products/{productId}/variants/{variantId}") + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()))) + .When(x => x.WhenIReplaceTheTemplateVariables()) + .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/category/34/products/1/variants/12")) + .BDDfy(); + } + + private void GivenThereIsAUrlMatch(DownstreamRoute downstreamRoute) + { + _downstreamRoute = downstreamRoute; + } + + private void WhenIReplaceTheTemplateVariables() + { + _result = _downstreamPathReplacer.Replace(_downstreamRoute.ReRoute.DownstreamPathTemplate, _downstreamRoute.TemplatePlaceholderNameAndValues); + } + + private void ThenTheDownstreamUrlPathIsReturned(string expected) + { + _result.Data.Value.ShouldBe(expected); + } + } +} diff --git a/test/Ocelot.UnitTests/Errors/ErrorTests.cs b/test/Ocelot.UnitTests/Errors/ErrorTests.cs index 69f2d15f5..010ffd266 100644 --- a/test/Ocelot.UnitTests/Errors/ErrorTests.cs +++ b/test/Ocelot.UnitTests/Errors/ErrorTests.cs @@ -1,18 +1,18 @@ -using Ocelot.Errors; -using Ocelot.Infrastructure.RequestData; -using Shouldly; -using Xunit; - -namespace Ocelot.UnitTests.Errors -{ - public class ErrorTests - { - [Fact] - public void should_return_message() - { - var error = new CannotAddDataError("message"); - var result = error.ToString(); - result.ShouldBe("message"); - } - } +using Ocelot.Errors; +using Ocelot.Infrastructure.RequestData; +using Shouldly; +using Xunit; + +namespace Ocelot.UnitTests.Errors +{ + public class ErrorTests + { + [Fact] + public void should_return_message() + { + var error = new CannotAddDataError("message"); + var result = error.ToString(); + result.ShouldBe("message"); + } + } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs b/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs index da854ff72..f685168c2 100644 --- a/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs @@ -1,144 +1,144 @@ -namespace Ocelot.UnitTests.Errors -{ - using System; - using System.Net; - using System.Threading.Tasks; - using Microsoft.AspNetCore.Builder; - using Microsoft.Extensions.DependencyInjection; - using Ocelot.Errors.Middleware; - using Ocelot.Logging; - using Shouldly; - using TestStack.BDDfy; - using Xunit; - using Microsoft.AspNetCore.Http; - using Ocelot.Configuration.Provider; - using Moq; - using Ocelot.Configuration; - using Rafty.Concensus; - - public class ExceptionHandlerMiddlewareTests : ServerHostedMiddlewareTest - { - bool _shouldThrowAnException = false; - private Mock _provider; - - public ExceptionHandlerMiddlewareTests() - { - _provider = new Mock(); - GivenTheTestServerIsConfigured(); - } - - [Fact] - public void NoDownstreamException() - { - var config = new OcelotConfiguration(null, null, null, null); - - this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream()) - .And(_ => GivenTheConfigurationIs(config)) - .When(_ => WhenICallTheMiddleware()) - .Then(_ => ThenTheResponseIsOk()) - .And(_ => TheRequestIdIsNotSet()) - .BDDfy(); - } - - private void TheRequestIdIsNotSet() - { - ScopedRepository.Verify(x => x.Add(It.IsAny(), It.IsAny()), Times.Never); - } - - [Fact] - public void DownstreamException() - { - var config = new OcelotConfiguration(null, null, null, null); - - this.Given(_ => GivenAnExceptionWillBeThrownDownstream()) - .And(_ => GivenTheConfigurationIs(config)) - .When(_ => WhenICallTheMiddleware()) - .Then(_ => ThenTheResponseIsError()) - .BDDfy(); - } - - [Fact] - public void ShouldSetRequestId() - { - var config = new OcelotConfiguration(null, null, null, "requestidkey"); - - this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream()) - .And(_ => GivenTheConfigurationIs(config)) - .When(_ => WhenICallTheMiddlewareWithTheRequestIdKey("requestidkey", "1234")) - .Then(_ => ThenTheResponseIsOk()) - .And(_ => TheRequestIdIsSet("RequestId", "1234")) - .BDDfy(); - } - - [Fact] - public void ShouldNotSetRequestId() - { - var config = new OcelotConfiguration(null, null, null, null); - - this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream()) - .And(_ => GivenTheConfigurationIs(config)) - .When(_ => WhenICallTheMiddlewareWithTheRequestIdKey("requestidkey", "1234")) - .Then(_ => ThenTheResponseIsOk()) - .And(_ => TheRequestIdIsNotSet()) - .BDDfy(); - } - - private void TheRequestIdIsSet(string key, string value) - { - ScopedRepository.Verify(x => x.Add(key, value), Times.Once); - } - - private void GivenTheConfigurationIs(IOcelotConfiguration config) - { - var response = new Ocelot.Responses.OkResponse(config); - _provider - .Setup(x => x.Get()).ReturnsAsync(response); - } - - protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) - { - services.AddSingleton(); - services.AddLogging(); - services.AddSingleton(ScopedRepository.Object); - services.AddSingleton(_provider.Object); - } - - protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) - { - app.UseExceptionHandlerMiddleware(); - app.Run(DownstreamExceptionSimulator); - } - - private async Task DownstreamExceptionSimulator(HttpContext context) - { - await Task.CompletedTask; - - if (_shouldThrowAnException) - { - throw new Exception("BOOM"); - } - - context.Response.StatusCode = (int)HttpStatusCode.OK; - } - - private void GivenAnExceptionWillNotBeThrownDownstream() - { - _shouldThrowAnException = false; - } - - private void GivenAnExceptionWillBeThrownDownstream() - { - _shouldThrowAnException = true; - } - - private void ThenTheResponseIsOk() - { - ResponseMessage.StatusCode.ShouldBe(HttpStatusCode.OK); - } - - private void ThenTheResponseIsError() - { - ResponseMessage.StatusCode.ShouldBe(HttpStatusCode.InternalServerError); - } - } +namespace Ocelot.UnitTests.Errors +{ + using System; + using System.Net; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Builder; + using Microsoft.Extensions.DependencyInjection; + using Ocelot.Errors.Middleware; + using Ocelot.Logging; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + using Microsoft.AspNetCore.Http; + using Ocelot.Configuration.Provider; + using Moq; + using Ocelot.Configuration; + using Rafty.Concensus; + + public class ExceptionHandlerMiddlewareTests : ServerHostedMiddlewareTest + { + bool _shouldThrowAnException = false; + private Mock _provider; + + public ExceptionHandlerMiddlewareTests() + { + _provider = new Mock(); + GivenTheTestServerIsConfigured(); + } + + [Fact] + public void NoDownstreamException() + { + var config = new OcelotConfiguration(null, null, null, null); + + this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream()) + .And(_ => GivenTheConfigurationIs(config)) + .When(_ => WhenICallTheMiddleware()) + .Then(_ => ThenTheResponseIsOk()) + .And(_ => TheRequestIdIsNotSet()) + .BDDfy(); + } + + private void TheRequestIdIsNotSet() + { + ScopedRepository.Verify(x => x.Add(It.IsAny(), It.IsAny()), Times.Never); + } + + [Fact] + public void DownstreamException() + { + var config = new OcelotConfiguration(null, null, null, null); + + this.Given(_ => GivenAnExceptionWillBeThrownDownstream()) + .And(_ => GivenTheConfigurationIs(config)) + .When(_ => WhenICallTheMiddleware()) + .Then(_ => ThenTheResponseIsError()) + .BDDfy(); + } + + [Fact] + public void ShouldSetRequestId() + { + var config = new OcelotConfiguration(null, null, null, "requestidkey"); + + this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream()) + .And(_ => GivenTheConfigurationIs(config)) + .When(_ => WhenICallTheMiddlewareWithTheRequestIdKey("requestidkey", "1234")) + .Then(_ => ThenTheResponseIsOk()) + .And(_ => TheRequestIdIsSet("RequestId", "1234")) + .BDDfy(); + } + + [Fact] + public void ShouldNotSetRequestId() + { + var config = new OcelotConfiguration(null, null, null, null); + + this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream()) + .And(_ => GivenTheConfigurationIs(config)) + .When(_ => WhenICallTheMiddlewareWithTheRequestIdKey("requestidkey", "1234")) + .Then(_ => ThenTheResponseIsOk()) + .And(_ => TheRequestIdIsNotSet()) + .BDDfy(); + } + + private void TheRequestIdIsSet(string key, string value) + { + ScopedRepository.Verify(x => x.Add(key, value), Times.Once); + } + + private void GivenTheConfigurationIs(IOcelotConfiguration config) + { + var response = new Ocelot.Responses.OkResponse(config); + _provider + .Setup(x => x.Get()).ReturnsAsync(response); + } + + protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + { + services.AddSingleton(); + services.AddLogging(); + services.AddSingleton(ScopedRepository.Object); + services.AddSingleton(_provider.Object); + } + + protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) + { + app.UseExceptionHandlerMiddleware(); + app.Run(DownstreamExceptionSimulator); + } + + private async Task DownstreamExceptionSimulator(HttpContext context) + { + await Task.CompletedTask; + + if (_shouldThrowAnException) + { + throw new Exception("BOOM"); + } + + context.Response.StatusCode = (int)HttpStatusCode.OK; + } + + private void GivenAnExceptionWillNotBeThrownDownstream() + { + _shouldThrowAnException = false; + } + + private void GivenAnExceptionWillBeThrownDownstream() + { + _shouldThrowAnException = true; + } + + private void ThenTheResponseIsOk() + { + ResponseMessage.StatusCode.ShouldBe(HttpStatusCode.OK); + } + + private void ThenTheResponseIsError() + { + ResponseMessage.StatusCode.ShouldBe(HttpStatusCode.InternalServerError); + } + } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Headers/AddHeadersToRequestTests.cs b/test/Ocelot.UnitTests/Headers/AddHeadersToRequestTests.cs index d276fd24d..f69c6b9ea 100644 --- a/test/Ocelot.UnitTests/Headers/AddHeadersToRequestTests.cs +++ b/test/Ocelot.UnitTests/Headers/AddHeadersToRequestTests.cs @@ -1,151 +1,151 @@ -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -using Moq; -using Ocelot.Configuration; -using Ocelot.Errors; -using Ocelot.Headers; -using Ocelot.Infrastructure.Claims.Parser; -using Ocelot.Responses; -using Shouldly; -using TestStack.BDDfy; -using Xunit; -using System.Net.Http; - -namespace Ocelot.UnitTests.Headers -{ - public class AddHeadersToRequestTests - { - private readonly AddHeadersToRequest _addHeadersToRequest; - private readonly Mock _parser; - private readonly HttpRequestMessage _downstreamRequest; - private List _claims; - private List _configuration; - private Response _result; - private Response _claimValue; - - public AddHeadersToRequestTests() - { - _parser = new Mock(); - _addHeadersToRequest = new AddHeadersToRequest(_parser.Object); - _downstreamRequest = new HttpRequestMessage(); - } - - [Fact] - public void should_add_headers_to_downstreamRequest() - { - var claims = new List - { - new Claim("test", "data") - }; - - this.Given( - x => x.GivenConfigurationHeaderExtractorProperties(new List - { - new ClaimToThing("header-key", "", "", 0) - })) - .Given(x => x.GivenClaims(claims)) - .And(x => x.GivenTheClaimParserReturns(new OkResponse("value"))) - .When(x => x.WhenIAddHeadersToTheRequest()) - .Then(x => x.ThenTheResultIsSuccess()) - .And(x => x.ThenTheHeaderIsAdded()) - .BDDfy(); - } - - [Fact] - public void should_replace_existing_headers_on_request() - { - this.Given( - x => x.GivenConfigurationHeaderExtractorProperties(new List - { - new ClaimToThing("header-key", "", "", 0) - })) - .Given(x => x.GivenClaims(new List - { - new Claim("test", "data") - })) - .And(x => x.GivenTheClaimParserReturns(new OkResponse("value"))) - .And(x => x.GivenThatTheRequestContainsHeader("header-key", "initial")) - .When(x => x.WhenIAddHeadersToTheRequest()) - .Then(x => x.ThenTheResultIsSuccess()) - .And(x => x.ThenTheHeaderIsAdded()) - .BDDfy(); - } - - [Fact] - public void should_return_error() - { - this.Given( - x => x.GivenConfigurationHeaderExtractorProperties(new List - { - new ClaimToThing("", "", "", 0) - })) - .Given(x => x.GivenClaims(new List())) - .And(x => x.GivenTheClaimParserReturns(new ErrorResponse(new List - { - new AnyError() - }))) - .When(x => x.WhenIAddHeadersToTheRequest()) - .Then(x => x.ThenTheResultIsError()) - .BDDfy(); - } - - private void GivenClaims(List claims) - { - _claims = claims; - } - - private void GivenConfigurationHeaderExtractorProperties(List configuration) - { - _configuration = configuration; - } - - private void GivenThatTheRequestContainsHeader(string key, string value) - { - _downstreamRequest.Headers.Add(key, value); - } - - private void GivenTheClaimParserReturns(Response claimValue) - { - _claimValue = claimValue; - _parser - .Setup( - x => - x.GetValue(It.IsAny>(), - It.IsAny(), - It.IsAny(), - It.IsAny())) - .Returns(_claimValue); - } - - private void WhenIAddHeadersToTheRequest() - { - _result = _addHeadersToRequest.SetHeadersOnDownstreamRequest(_configuration, _claims, _downstreamRequest); - } - - private void ThenTheResultIsSuccess() - { - _result.IsError.ShouldBe(false); - } - - private void ThenTheResultIsError() - { - - _result.IsError.ShouldBe(true); - } - - private void ThenTheHeaderIsAdded() - { - var header = _downstreamRequest.Headers.First(x => x.Key == "header-key"); - header.Value.First().ShouldBe(_claimValue.Data); - } - - class AnyError : Error - { - public AnyError() - : base("blahh", OcelotErrorCode.UnknownError) - { - } - } - } -} +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using Moq; +using Ocelot.Configuration; +using Ocelot.Errors; +using Ocelot.Headers; +using Ocelot.Infrastructure.Claims.Parser; +using Ocelot.Responses; +using Shouldly; +using TestStack.BDDfy; +using Xunit; +using System.Net.Http; + +namespace Ocelot.UnitTests.Headers +{ + public class AddHeadersToRequestTests + { + private readonly AddHeadersToRequest _addHeadersToRequest; + private readonly Mock _parser; + private readonly HttpRequestMessage _downstreamRequest; + private List _claims; + private List _configuration; + private Response _result; + private Response _claimValue; + + public AddHeadersToRequestTests() + { + _parser = new Mock(); + _addHeadersToRequest = new AddHeadersToRequest(_parser.Object); + _downstreamRequest = new HttpRequestMessage(); + } + + [Fact] + public void should_add_headers_to_downstreamRequest() + { + var claims = new List + { + new Claim("test", "data") + }; + + this.Given( + x => x.GivenConfigurationHeaderExtractorProperties(new List + { + new ClaimToThing("header-key", "", "", 0) + })) + .Given(x => x.GivenClaims(claims)) + .And(x => x.GivenTheClaimParserReturns(new OkResponse("value"))) + .When(x => x.WhenIAddHeadersToTheRequest()) + .Then(x => x.ThenTheResultIsSuccess()) + .And(x => x.ThenTheHeaderIsAdded()) + .BDDfy(); + } + + [Fact] + public void should_replace_existing_headers_on_request() + { + this.Given( + x => x.GivenConfigurationHeaderExtractorProperties(new List + { + new ClaimToThing("header-key", "", "", 0) + })) + .Given(x => x.GivenClaims(new List + { + new Claim("test", "data") + })) + .And(x => x.GivenTheClaimParserReturns(new OkResponse("value"))) + .And(x => x.GivenThatTheRequestContainsHeader("header-key", "initial")) + .When(x => x.WhenIAddHeadersToTheRequest()) + .Then(x => x.ThenTheResultIsSuccess()) + .And(x => x.ThenTheHeaderIsAdded()) + .BDDfy(); + } + + [Fact] + public void should_return_error() + { + this.Given( + x => x.GivenConfigurationHeaderExtractorProperties(new List + { + new ClaimToThing("", "", "", 0) + })) + .Given(x => x.GivenClaims(new List())) + .And(x => x.GivenTheClaimParserReturns(new ErrorResponse(new List + { + new AnyError() + }))) + .When(x => x.WhenIAddHeadersToTheRequest()) + .Then(x => x.ThenTheResultIsError()) + .BDDfy(); + } + + private void GivenClaims(List claims) + { + _claims = claims; + } + + private void GivenConfigurationHeaderExtractorProperties(List configuration) + { + _configuration = configuration; + } + + private void GivenThatTheRequestContainsHeader(string key, string value) + { + _downstreamRequest.Headers.Add(key, value); + } + + private void GivenTheClaimParserReturns(Response claimValue) + { + _claimValue = claimValue; + _parser + .Setup( + x => + x.GetValue(It.IsAny>(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns(_claimValue); + } + + private void WhenIAddHeadersToTheRequest() + { + _result = _addHeadersToRequest.SetHeadersOnDownstreamRequest(_configuration, _claims, _downstreamRequest); + } + + private void ThenTheResultIsSuccess() + { + _result.IsError.ShouldBe(false); + } + + private void ThenTheResultIsError() + { + + _result.IsError.ShouldBe(true); + } + + private void ThenTheHeaderIsAdded() + { + var header = _downstreamRequest.Headers.First(x => x.Key == "header-key"); + header.Value.First().ShouldBe(_claimValue.Data); + } + + class AnyError : Error + { + public AnyError() + : base("blahh", OcelotErrorCode.UnknownError) + { + } + } + } +} diff --git a/test/Ocelot.UnitTests/Headers/HttpContextRequestHeaderReplacerTests.cs b/test/Ocelot.UnitTests/Headers/HttpContextRequestHeaderReplacerTests.cs index 26e0b1e25..253678167 100644 --- a/test/Ocelot.UnitTests/Headers/HttpContextRequestHeaderReplacerTests.cs +++ b/test/Ocelot.UnitTests/Headers/HttpContextRequestHeaderReplacerTests.cs @@ -1,91 +1,91 @@ -using Xunit; -using Shouldly; -using Ocelot.Headers.Middleware; -using TestStack.BDDfy; -using Microsoft.AspNetCore.Http; -using System.Collections.Generic; -using Ocelot.Responses; -using Ocelot.Configuration; -using Ocelot.Headers; - -namespace Ocelot.UnitTests.Headers -{ - public class HttpContextRequestHeaderReplacerTests - { - private HttpContext _context; - private List _fAndRs; - private HttpContextRequestHeaderReplacer _replacer; - private Response _result; - - public HttpContextRequestHeaderReplacerTests() - { - _replacer = new HttpContextRequestHeaderReplacer(); - } - - [Fact] - public void should_replace_headers() - { - var context = new DefaultHttpContext(); - context.Request.Headers.Add("test", "test"); - - var fAndRs = new List(); - fAndRs.Add(new HeaderFindAndReplace("test", "test", "chiken", 0)); - - this.Given(x => GivenTheFollowingHttpRequest(context)) - .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) - .When(x => WhenICallTheReplacer()) - .Then(x => ThenTheHeadersAreReplaced()) - .BDDfy(); - } - - [Fact] - public void should_not_replace_headers() - { - var context = new DefaultHttpContext(); - context.Request.Headers.Add("test", "test"); - - var fAndRs = new List(); - - this.Given(x => GivenTheFollowingHttpRequest(context)) - .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) - .When(x => WhenICallTheReplacer()) - .Then(x => ThenTheHeadersAreNotReplaced()) - .BDDfy(); - } - - private void ThenTheHeadersAreNotReplaced() - { - _result.ShouldBeOfType(); - foreach (var f in _fAndRs) - { - _context.Request.Headers.TryGetValue(f.Key, out var values); - values[f.Index].ShouldBe("test"); - } - } - - private void GivenTheFollowingHttpRequest(HttpContext context) - { - _context = context; - } - - private void GivenTheFollowingHeaderReplacements(List fAndRs) - { - _fAndRs = fAndRs; - } - - private void WhenICallTheReplacer() - { - _result = _replacer.Replace(_context, _fAndRs); - } - - private void ThenTheHeadersAreReplaced() - { - _result.ShouldBeOfType(); - foreach (var f in _fAndRs) - { - _context.Request.Headers.TryGetValue(f.Key, out var values); - values[f.Index].ShouldBe(f.Replace); - } - } - } +using Xunit; +using Shouldly; +using Ocelot.Headers.Middleware; +using TestStack.BDDfy; +using Microsoft.AspNetCore.Http; +using System.Collections.Generic; +using Ocelot.Responses; +using Ocelot.Configuration; +using Ocelot.Headers; + +namespace Ocelot.UnitTests.Headers +{ + public class HttpContextRequestHeaderReplacerTests + { + private HttpContext _context; + private List _fAndRs; + private HttpContextRequestHeaderReplacer _replacer; + private Response _result; + + public HttpContextRequestHeaderReplacerTests() + { + _replacer = new HttpContextRequestHeaderReplacer(); + } + + [Fact] + public void should_replace_headers() + { + var context = new DefaultHttpContext(); + context.Request.Headers.Add("test", "test"); + + var fAndRs = new List(); + fAndRs.Add(new HeaderFindAndReplace("test", "test", "chiken", 0)); + + this.Given(x => GivenTheFollowingHttpRequest(context)) + .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) + .When(x => WhenICallTheReplacer()) + .Then(x => ThenTheHeadersAreReplaced()) + .BDDfy(); + } + + [Fact] + public void should_not_replace_headers() + { + var context = new DefaultHttpContext(); + context.Request.Headers.Add("test", "test"); + + var fAndRs = new List(); + + this.Given(x => GivenTheFollowingHttpRequest(context)) + .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) + .When(x => WhenICallTheReplacer()) + .Then(x => ThenTheHeadersAreNotReplaced()) + .BDDfy(); + } + + private void ThenTheHeadersAreNotReplaced() + { + _result.ShouldBeOfType(); + foreach (var f in _fAndRs) + { + _context.Request.Headers.TryGetValue(f.Key, out var values); + values[f.Index].ShouldBe("test"); + } + } + + private void GivenTheFollowingHttpRequest(HttpContext context) + { + _context = context; + } + + private void GivenTheFollowingHeaderReplacements(List fAndRs) + { + _fAndRs = fAndRs; + } + + private void WhenICallTheReplacer() + { + _result = _replacer.Replace(_context, _fAndRs); + } + + private void ThenTheHeadersAreReplaced() + { + _result.ShouldBeOfType(); + foreach (var f in _fAndRs) + { + _context.Request.Headers.TryGetValue(f.Key, out var values); + values[f.Index].ShouldBe(f.Replace); + } + } + } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs b/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs index 39471bc37..0cfd46979 100644 --- a/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs @@ -1,101 +1,101 @@ -using Xunit; -using Shouldly; -using Ocelot.Logging; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.AspNetCore.Builder; -using Ocelot.Headers.Middleware; -using TestStack.BDDfy; -using System.Linq; -using System.Threading.Tasks; -using System; -using Microsoft.AspNetCore.Http; -using System.Collections.Generic; -using Moq; -using Ocelot.Configuration; -using Ocelot.DownstreamRouteFinder; -using Ocelot.Responses; -using Ocelot.Configuration.Builder; -using Ocelot.Headers; -using System.Net.Http; - -namespace Ocelot.UnitTests.Headers -{ - public class HttpHeadersTransformationMiddlewareTests : ServerHostedMiddlewareTest - { - private Mock _preReplacer; - private Mock _postReplacer; - - public HttpHeadersTransformationMiddlewareTests() - { - _preReplacer = new Mock(); - _postReplacer = new Mock(); - - GivenTheTestServerIsConfigured(); - } - - [Fact] - public void should_call_pre_and_post_header_transforms() - { - this.Given(x => GivenTheFollowingRequest()) - .And(x => GivenTheDownstreamRequestIs()) - .And(x => GivenTheReRouteHasPreFindAndReplaceSetUp()) - .And(x => GivenTheHttpResponseMessageIs()) - .When(x => WhenICallTheMiddleware()) - .Then(x => ThenTheIHttpContextRequestHeaderReplacerIsCalledCorrectly()) - .And(x => ThenTheIHttpResponseHeaderReplacerIsCalledCorrectly()) - .BDDfy(); - } - - private void GivenTheDownstreamRequestIs() - { - var request = new HttpRequestMessage(); - var response = new OkResponse(request); - ScopedRepository.Setup(x => x.Get("DownstreamRequest")).Returns(response); - } - - private void GivenTheHttpResponseMessageIs() - { - var httpResponseMessage = new HttpResponseMessage(); - var response = new OkResponse(httpResponseMessage); - ScopedRepository.Setup(x => x.Get("HttpResponseMessage")).Returns(response); - } - - private void GivenTheReRouteHasPreFindAndReplaceSetUp() - { - var fAndRs = new List(); - var reRoute = new ReRouteBuilder().WithUpstreamHeaderFindAndReplace(fAndRs).WithDownstreamHeaderFindAndReplace(fAndRs).Build(); - var dR = new DownstreamRoute(null, reRoute); - var response = new OkResponse(dR); - ScopedRepository.Setup(x => x.Get("DownstreamRoute")).Returns(response); - } - - private void ThenTheIHttpContextRequestHeaderReplacerIsCalledCorrectly() - { - _preReplacer.Verify(x => x.Replace(It.IsAny(), It.IsAny>()), Times.Once); - } - - private void ThenTheIHttpResponseHeaderReplacerIsCalledCorrectly() - { - _postReplacer.Verify(x => x.Replace(It.IsAny(), It.IsAny>(), It.IsAny()), Times.Once); - } - - private void GivenTheFollowingRequest() - { - Client.DefaultRequestHeaders.Add("test", "test"); - } - - protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) - { - services.AddSingleton(); - services.AddLogging(); - services.AddSingleton(ScopedRepository.Object); - services.AddSingleton(_preReplacer.Object); - services.AddSingleton(_postReplacer.Object); - } - - protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) - { - app.UseHttpHeadersTransformationMiddleware(); - } - } +using Xunit; +using Shouldly; +using Ocelot.Logging; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Builder; +using Ocelot.Headers.Middleware; +using TestStack.BDDfy; +using System.Linq; +using System.Threading.Tasks; +using System; +using Microsoft.AspNetCore.Http; +using System.Collections.Generic; +using Moq; +using Ocelot.Configuration; +using Ocelot.DownstreamRouteFinder; +using Ocelot.Responses; +using Ocelot.Configuration.Builder; +using Ocelot.Headers; +using System.Net.Http; + +namespace Ocelot.UnitTests.Headers +{ + public class HttpHeadersTransformationMiddlewareTests : ServerHostedMiddlewareTest + { + private Mock _preReplacer; + private Mock _postReplacer; + + public HttpHeadersTransformationMiddlewareTests() + { + _preReplacer = new Mock(); + _postReplacer = new Mock(); + + GivenTheTestServerIsConfigured(); + } + + [Fact] + public void should_call_pre_and_post_header_transforms() + { + this.Given(x => GivenTheFollowingRequest()) + .And(x => GivenTheDownstreamRequestIs()) + .And(x => GivenTheReRouteHasPreFindAndReplaceSetUp()) + .And(x => GivenTheHttpResponseMessageIs()) + .When(x => WhenICallTheMiddleware()) + .Then(x => ThenTheIHttpContextRequestHeaderReplacerIsCalledCorrectly()) + .And(x => ThenTheIHttpResponseHeaderReplacerIsCalledCorrectly()) + .BDDfy(); + } + + private void GivenTheDownstreamRequestIs() + { + var request = new HttpRequestMessage(); + var response = new OkResponse(request); + ScopedRepository.Setup(x => x.Get("DownstreamRequest")).Returns(response); + } + + private void GivenTheHttpResponseMessageIs() + { + var httpResponseMessage = new HttpResponseMessage(); + var response = new OkResponse(httpResponseMessage); + ScopedRepository.Setup(x => x.Get("HttpResponseMessage")).Returns(response); + } + + private void GivenTheReRouteHasPreFindAndReplaceSetUp() + { + var fAndRs = new List(); + var reRoute = new ReRouteBuilder().WithUpstreamHeaderFindAndReplace(fAndRs).WithDownstreamHeaderFindAndReplace(fAndRs).Build(); + var dR = new DownstreamRoute(null, reRoute); + var response = new OkResponse(dR); + ScopedRepository.Setup(x => x.Get("DownstreamRoute")).Returns(response); + } + + private void ThenTheIHttpContextRequestHeaderReplacerIsCalledCorrectly() + { + _preReplacer.Verify(x => x.Replace(It.IsAny(), It.IsAny>()), Times.Once); + } + + private void ThenTheIHttpResponseHeaderReplacerIsCalledCorrectly() + { + _postReplacer.Verify(x => x.Replace(It.IsAny(), It.IsAny>(), It.IsAny()), Times.Once); + } + + private void GivenTheFollowingRequest() + { + Client.DefaultRequestHeaders.Add("test", "test"); + } + + protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + { + services.AddSingleton(); + services.AddLogging(); + services.AddSingleton(ScopedRepository.Object); + services.AddSingleton(_preReplacer.Object); + services.AddSingleton(_postReplacer.Object); + } + + protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) + { + app.UseHttpHeadersTransformationMiddleware(); + } + } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs index 20dbe84df..f46c9482a 100644 --- a/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs @@ -1,97 +1,97 @@ -namespace Ocelot.UnitTests.Headers -{ - using System.Collections.Generic; - using System.Net.Http; - using Microsoft.AspNetCore.Builder; - using Microsoft.Extensions.DependencyInjection; - using Moq; - using Ocelot.Configuration; - using Ocelot.Configuration.Builder; - using Ocelot.DownstreamRouteFinder; - using Ocelot.DownstreamRouteFinder.UrlMatcher; - using Ocelot.Headers; - using Ocelot.Headers.Middleware; - using Ocelot.Logging; - using Ocelot.Responses; - using TestStack.BDDfy; - using Xunit; - - public class HttpRequestHeadersBuilderMiddlewareTests : ServerHostedMiddlewareTest - { - private readonly Mock _addHeaders; - private readonly HttpRequestMessage _downstreamRequest; - private Response _downstreamRoute; - - public HttpRequestHeadersBuilderMiddlewareTests() - { - _addHeaders = new Mock(); - - _downstreamRequest = new HttpRequestMessage(); - ScopedRepository - .Setup(sr => sr.Get("DownstreamRequest")) - .Returns(new OkResponse(_downstreamRequest)); - - GivenTheTestServerIsConfigured(); - } - - [Fact] - public void should_call_add_headers_to_request_correctly() - { - var downstreamRoute = new DownstreamRoute(new List(), - new ReRouteBuilder() - .WithDownstreamPathTemplate("any old string") - .WithClaimsToHeaders(new List - { - new ClaimToThing("UserId", "Subject", "", 0) - }) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()); - - this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .And(x => x.GivenTheAddHeadersToDownstreamRequestReturnsOk()) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheAddHeadersToRequestIsCalledCorrectly()) - .BDDfy(); - } - - protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) - { - services.AddSingleton(); - services.AddLogging(); - services.AddSingleton(_addHeaders.Object); - services.AddSingleton(ScopedRepository.Object); - } - - protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) - { - app.UseHttpRequestHeadersBuilderMiddleware(); - } - - private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) - { - _downstreamRoute = new OkResponse(downstreamRoute); - ScopedRepository - .Setup(x => x.Get(It.IsAny())) - .Returns(_downstreamRoute); - } - - private void GivenTheAddHeadersToDownstreamRequestReturnsOk() - { - _addHeaders - .Setup(x => x.SetHeadersOnDownstreamRequest( - It.IsAny>(), - It.IsAny>(), - It.IsAny())) - .Returns(new OkResponse()); - } - - private void ThenTheAddHeadersToRequestIsCalledCorrectly() - { - _addHeaders - .Verify(x => x.SetHeadersOnDownstreamRequest( - It.IsAny>(), - It.IsAny>(), - _downstreamRequest), Times.Once); - } - } -} +namespace Ocelot.UnitTests.Headers +{ + using System.Collections.Generic; + using System.Net.Http; + using Microsoft.AspNetCore.Builder; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; + using Ocelot.DownstreamRouteFinder; + using Ocelot.DownstreamRouteFinder.UrlMatcher; + using Ocelot.Headers; + using Ocelot.Headers.Middleware; + using Ocelot.Logging; + using Ocelot.Responses; + using TestStack.BDDfy; + using Xunit; + + public class HttpRequestHeadersBuilderMiddlewareTests : ServerHostedMiddlewareTest + { + private readonly Mock _addHeaders; + private readonly HttpRequestMessage _downstreamRequest; + private Response _downstreamRoute; + + public HttpRequestHeadersBuilderMiddlewareTests() + { + _addHeaders = new Mock(); + + _downstreamRequest = new HttpRequestMessage(); + ScopedRepository + .Setup(sr => sr.Get("DownstreamRequest")) + .Returns(new OkResponse(_downstreamRequest)); + + GivenTheTestServerIsConfigured(); + } + + [Fact] + public void should_call_add_headers_to_request_correctly() + { + var downstreamRoute = new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithClaimsToHeaders(new List + { + new ClaimToThing("UserId", "Subject", "", 0) + }) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()); + + this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => x.GivenTheAddHeadersToDownstreamRequestReturnsOk()) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheAddHeadersToRequestIsCalledCorrectly()) + .BDDfy(); + } + + protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + { + services.AddSingleton(); + services.AddLogging(); + services.AddSingleton(_addHeaders.Object); + services.AddSingleton(ScopedRepository.Object); + } + + protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) + { + app.UseHttpRequestHeadersBuilderMiddleware(); + } + + private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) + { + _downstreamRoute = new OkResponse(downstreamRoute); + ScopedRepository + .Setup(x => x.Get(It.IsAny())) + .Returns(_downstreamRoute); + } + + private void GivenTheAddHeadersToDownstreamRequestReturnsOk() + { + _addHeaders + .Setup(x => x.SetHeadersOnDownstreamRequest( + It.IsAny>(), + It.IsAny>(), + It.IsAny())) + .Returns(new OkResponse()); + } + + private void ThenTheAddHeadersToRequestIsCalledCorrectly() + { + _addHeaders + .Verify(x => x.SetHeadersOnDownstreamRequest( + It.IsAny>(), + It.IsAny>(), + _downstreamRequest), Times.Once); + } + } +} diff --git a/test/Ocelot.UnitTests/Headers/HttpResponseHeaderReplacerTests.cs b/test/Ocelot.UnitTests/Headers/HttpResponseHeaderReplacerTests.cs index 1de6afb4b..de8161c94 100644 --- a/test/Ocelot.UnitTests/Headers/HttpResponseHeaderReplacerTests.cs +++ b/test/Ocelot.UnitTests/Headers/HttpResponseHeaderReplacerTests.cs @@ -1,235 +1,235 @@ -using Xunit; -using Shouldly; -using TestStack.BDDfy; -using System.Net.Http; -using Ocelot.Headers; -using Ocelot.Configuration; -using System.Collections.Generic; -using Ocelot.Responses; -using System.Linq; - -namespace Ocelot.UnitTests.Headers -{ - public class HttpResponseHeaderReplacerTests - { - private HttpResponseMessage _response; - private HttpResponseHeaderReplacer _replacer; - private List _headerFindAndReplaces; - private Response _result; - private HttpRequestMessage _request; - - public HttpResponseHeaderReplacerTests() - { - _replacer = new HttpResponseHeaderReplacer(); - } - [Fact] - public void should_replace_headers() - { - var response = new HttpResponseMessage(); - response.Headers.Add("test", "test"); - - var fAndRs = new List(); - fAndRs.Add(new HeaderFindAndReplace("test", "test", "chiken", 0)); - - this.Given(x => GivenTheHttpResponse(response)) - .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) - .When(x => WhenICallTheReplacer()) - .Then(x => ThenTheHeadersAreReplaced()) - .BDDfy(); - } - - [Fact] - public void should_not_replace_headers() - { - var response = new HttpResponseMessage(); - response.Headers.Add("test", "test"); - - var fAndRs = new List(); - - this.Given(x => GivenTheHttpResponse(response)) - .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) - .When(x => WhenICallTheReplacer()) - .Then(x => ThenTheHeadersAreNotReplaced()) - .BDDfy(); - } - - [Fact] - public void should_replace_downstream_base_url_with_ocelot_base_url() - { - var downstreamUrl = "http://downstream.com/"; - - var request = new HttpRequestMessage(); - request.RequestUri = new System.Uri(downstreamUrl); - - var response = new HttpResponseMessage(); - response.Headers.Add("Location", downstreamUrl); - - var fAndRs = new List(); - fAndRs.Add(new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com/", 0)); - - this.Given(x => GivenTheHttpResponse(response)) - .And(x => GivenTheRequestIs(request)) - .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) - .When(x => WhenICallTheReplacer()) - .Then(x => ThenTheHeaderShouldBe("Location", "http://ocelot.com/")) - .BDDfy(); - } - - [Fact] - public void should_replace_downstream_base_url_with_ocelot_base_url_with_port() - { - var downstreamUrl = "http://downstream.com/"; - - var request = new HttpRequestMessage(); - request.RequestUri = new System.Uri(downstreamUrl); - - var response = new HttpResponseMessage(); - response.Headers.Add("Location", downstreamUrl); - - var fAndRs = new List(); - fAndRs.Add(new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com:123/", 0)); - - this.Given(x => GivenTheHttpResponse(response)) - .And(x => GivenTheRequestIs(request)) - .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) - .When(x => WhenICallTheReplacer()) - .Then(x => ThenTheHeaderShouldBe("Location", "http://ocelot.com:123/")) - .BDDfy(); - } - - - [Fact] - public void should_replace_downstream_base_url_with_ocelot_base_url_and_path() - { - var downstreamUrl = "http://downstream.com/test/product"; - - var request = new HttpRequestMessage(); - request.RequestUri = new System.Uri(downstreamUrl); - - var response = new HttpResponseMessage(); - response.Headers.Add("Location", downstreamUrl); - - var fAndRs = new List(); - fAndRs.Add(new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com/", 0)); - - this.Given(x => GivenTheHttpResponse(response)) - .And(x => GivenTheRequestIs(request)) - .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) - .When(x => WhenICallTheReplacer()) - .Then(x => ThenTheHeaderShouldBe("Location", "http://ocelot.com/test/product")) - .BDDfy(); - } - - [Fact] - public void should_replace_downstream_base_url_with_ocelot_base_url_with_path_and_port() - { - var downstreamUrl = "http://downstream.com/test/product"; - - var request = new HttpRequestMessage(); - request.RequestUri = new System.Uri(downstreamUrl); - - var response = new HttpResponseMessage(); - response.Headers.Add("Location", downstreamUrl); - - var fAndRs = new List(); - fAndRs.Add(new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com:123/", 0)); - - this.Given(x => GivenTheHttpResponse(response)) - .And(x => GivenTheRequestIs(request)) - .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) - .When(x => WhenICallTheReplacer()) - .Then(x => ThenTheHeaderShouldBe("Location", "http://ocelot.com:123/test/product")) - .BDDfy(); - } - - [Fact] - public void should_replace_downstream_base_url_and_port_with_ocelot_base_url() - { - var downstreamUrl = "http://downstream.com:123/test/product"; - - var request = new HttpRequestMessage(); - request.RequestUri = new System.Uri(downstreamUrl); - - var response = new HttpResponseMessage(); - response.Headers.Add("Location", downstreamUrl); - - var fAndRs = new List(); - fAndRs.Add(new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com/", 0)); - - this.Given(x => GivenTheHttpResponse(response)) - .And(x => GivenTheRequestIs(request)) - .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) - .When(x => WhenICallTheReplacer()) - .Then(x => ThenTheHeaderShouldBe("Location", "http://ocelot.com/test/product")) - .BDDfy(); - } - - [Fact] - public void should_replace_downstream_base_url_and_port_with_ocelot_base_url_and_port() - { - var downstreamUrl = "http://downstream.com:123/test/product"; - - var request = new HttpRequestMessage(); - request.RequestUri = new System.Uri(downstreamUrl); - - var response = new HttpResponseMessage(); - response.Headers.Add("Location", downstreamUrl); - - var fAndRs = new List(); - fAndRs.Add(new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com:321/", 0)); - - this.Given(x => GivenTheHttpResponse(response)) - .And(x => GivenTheRequestIs(request)) - .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) - .When(x => WhenICallTheReplacer()) - .Then(x => ThenTheHeaderShouldBe("Location", "http://ocelot.com:321/test/product")) - .BDDfy(); - } - - private void GivenTheRequestIs(HttpRequestMessage request) - { - _request = request; - } - - private void ThenTheHeadersAreNotReplaced() - { - _result.ShouldBeOfType(); - foreach (var f in _headerFindAndReplaces) - { - _response.Headers.TryGetValues(f.Key, out var values); - values.ToList()[f.Index].ShouldBe("test"); - } - } - - private void GivenTheFollowingHeaderReplacements(List fAndRs) - { - _headerFindAndReplaces = fAndRs; - } - - private void GivenTheHttpResponse(HttpResponseMessage response) - { - _response = response; - } - - private void WhenICallTheReplacer() - { - _result = _replacer.Replace(_response, _headerFindAndReplaces, _request); - } - - private void ThenTheHeaderShouldBe(string key, string value) - { - var test = _response.Headers.GetValues(key); - test.First().ShouldBe(value); - } - - private void ThenTheHeadersAreReplaced() - { - _result.ShouldBeOfType(); - foreach (var f in _headerFindAndReplaces) - { - _response.Headers.TryGetValues(f.Key, out var values); - values.ToList()[f.Index].ShouldBe(f.Replace); - } - } - } +using Xunit; +using Shouldly; +using TestStack.BDDfy; +using System.Net.Http; +using Ocelot.Headers; +using Ocelot.Configuration; +using System.Collections.Generic; +using Ocelot.Responses; +using System.Linq; + +namespace Ocelot.UnitTests.Headers +{ + public class HttpResponseHeaderReplacerTests + { + private HttpResponseMessage _response; + private HttpResponseHeaderReplacer _replacer; + private List _headerFindAndReplaces; + private Response _result; + private HttpRequestMessage _request; + + public HttpResponseHeaderReplacerTests() + { + _replacer = new HttpResponseHeaderReplacer(); + } + [Fact] + public void should_replace_headers() + { + var response = new HttpResponseMessage(); + response.Headers.Add("test", "test"); + + var fAndRs = new List(); + fAndRs.Add(new HeaderFindAndReplace("test", "test", "chiken", 0)); + + this.Given(x => GivenTheHttpResponse(response)) + .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) + .When(x => WhenICallTheReplacer()) + .Then(x => ThenTheHeadersAreReplaced()) + .BDDfy(); + } + + [Fact] + public void should_not_replace_headers() + { + var response = new HttpResponseMessage(); + response.Headers.Add("test", "test"); + + var fAndRs = new List(); + + this.Given(x => GivenTheHttpResponse(response)) + .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) + .When(x => WhenICallTheReplacer()) + .Then(x => ThenTheHeadersAreNotReplaced()) + .BDDfy(); + } + + [Fact] + public void should_replace_downstream_base_url_with_ocelot_base_url() + { + var downstreamUrl = "http://downstream.com/"; + + var request = new HttpRequestMessage(); + request.RequestUri = new System.Uri(downstreamUrl); + + var response = new HttpResponseMessage(); + response.Headers.Add("Location", downstreamUrl); + + var fAndRs = new List(); + fAndRs.Add(new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com/", 0)); + + this.Given(x => GivenTheHttpResponse(response)) + .And(x => GivenTheRequestIs(request)) + .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) + .When(x => WhenICallTheReplacer()) + .Then(x => ThenTheHeaderShouldBe("Location", "http://ocelot.com/")) + .BDDfy(); + } + + [Fact] + public void should_replace_downstream_base_url_with_ocelot_base_url_with_port() + { + var downstreamUrl = "http://downstream.com/"; + + var request = new HttpRequestMessage(); + request.RequestUri = new System.Uri(downstreamUrl); + + var response = new HttpResponseMessage(); + response.Headers.Add("Location", downstreamUrl); + + var fAndRs = new List(); + fAndRs.Add(new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com:123/", 0)); + + this.Given(x => GivenTheHttpResponse(response)) + .And(x => GivenTheRequestIs(request)) + .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) + .When(x => WhenICallTheReplacer()) + .Then(x => ThenTheHeaderShouldBe("Location", "http://ocelot.com:123/")) + .BDDfy(); + } + + + [Fact] + public void should_replace_downstream_base_url_with_ocelot_base_url_and_path() + { + var downstreamUrl = "http://downstream.com/test/product"; + + var request = new HttpRequestMessage(); + request.RequestUri = new System.Uri(downstreamUrl); + + var response = new HttpResponseMessage(); + response.Headers.Add("Location", downstreamUrl); + + var fAndRs = new List(); + fAndRs.Add(new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com/", 0)); + + this.Given(x => GivenTheHttpResponse(response)) + .And(x => GivenTheRequestIs(request)) + .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) + .When(x => WhenICallTheReplacer()) + .Then(x => ThenTheHeaderShouldBe("Location", "http://ocelot.com/test/product")) + .BDDfy(); + } + + [Fact] + public void should_replace_downstream_base_url_with_ocelot_base_url_with_path_and_port() + { + var downstreamUrl = "http://downstream.com/test/product"; + + var request = new HttpRequestMessage(); + request.RequestUri = new System.Uri(downstreamUrl); + + var response = new HttpResponseMessage(); + response.Headers.Add("Location", downstreamUrl); + + var fAndRs = new List(); + fAndRs.Add(new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com:123/", 0)); + + this.Given(x => GivenTheHttpResponse(response)) + .And(x => GivenTheRequestIs(request)) + .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) + .When(x => WhenICallTheReplacer()) + .Then(x => ThenTheHeaderShouldBe("Location", "http://ocelot.com:123/test/product")) + .BDDfy(); + } + + [Fact] + public void should_replace_downstream_base_url_and_port_with_ocelot_base_url() + { + var downstreamUrl = "http://downstream.com:123/test/product"; + + var request = new HttpRequestMessage(); + request.RequestUri = new System.Uri(downstreamUrl); + + var response = new HttpResponseMessage(); + response.Headers.Add("Location", downstreamUrl); + + var fAndRs = new List(); + fAndRs.Add(new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com/", 0)); + + this.Given(x => GivenTheHttpResponse(response)) + .And(x => GivenTheRequestIs(request)) + .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) + .When(x => WhenICallTheReplacer()) + .Then(x => ThenTheHeaderShouldBe("Location", "http://ocelot.com/test/product")) + .BDDfy(); + } + + [Fact] + public void should_replace_downstream_base_url_and_port_with_ocelot_base_url_and_port() + { + var downstreamUrl = "http://downstream.com:123/test/product"; + + var request = new HttpRequestMessage(); + request.RequestUri = new System.Uri(downstreamUrl); + + var response = new HttpResponseMessage(); + response.Headers.Add("Location", downstreamUrl); + + var fAndRs = new List(); + fAndRs.Add(new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com:321/", 0)); + + this.Given(x => GivenTheHttpResponse(response)) + .And(x => GivenTheRequestIs(request)) + .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) + .When(x => WhenICallTheReplacer()) + .Then(x => ThenTheHeaderShouldBe("Location", "http://ocelot.com:321/test/product")) + .BDDfy(); + } + + private void GivenTheRequestIs(HttpRequestMessage request) + { + _request = request; + } + + private void ThenTheHeadersAreNotReplaced() + { + _result.ShouldBeOfType(); + foreach (var f in _headerFindAndReplaces) + { + _response.Headers.TryGetValues(f.Key, out var values); + values.ToList()[f.Index].ShouldBe("test"); + } + } + + private void GivenTheFollowingHeaderReplacements(List fAndRs) + { + _headerFindAndReplaces = fAndRs; + } + + private void GivenTheHttpResponse(HttpResponseMessage response) + { + _response = response; + } + + private void WhenICallTheReplacer() + { + _result = _replacer.Replace(_response, _headerFindAndReplaces, _request); + } + + private void ThenTheHeaderShouldBe(string key, string value) + { + var test = _response.Headers.GetValues(key); + test.First().ShouldBe(value); + } + + private void ThenTheHeadersAreReplaced() + { + _result.ShouldBeOfType(); + foreach (var f in _headerFindAndReplaces) + { + _response.Headers.TryGetValues(f.Key, out var values); + values.ToList()[f.Index].ShouldBe(f.Replace); + } + } + } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Headers/RemoveHeadersTests.cs b/test/Ocelot.UnitTests/Headers/RemoveHeadersTests.cs index 7a7bd4cc3..35c792aae 100644 --- a/test/Ocelot.UnitTests/Headers/RemoveHeadersTests.cs +++ b/test/Ocelot.UnitTests/Headers/RemoveHeadersTests.cs @@ -1,52 +1,52 @@ -using System.Net.Http; -using System.Net.Http.Headers; -using Ocelot.Responses; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Headers -{ - public class RemoveHeadersTests - { - private HttpResponseHeaders _headers; - private readonly Ocelot.Headers.RemoveOutputHeaders _removeOutputHeaders; - private Response _result; - - public RemoveHeadersTests() - { - _removeOutputHeaders = new Ocelot.Headers.RemoveOutputHeaders(); - } - - [Fact] - public void should_remove_header() - { - var httpResponse = new HttpResponseMessage() - { - Headers = {{ "Transfer-Encoding", "chunked"}} - }; - - this.Given(x => x.GivenAHttpContext(httpResponse.Headers)) - .When(x => x.WhenIRemoveTheHeaders()) - .Then(x => x.TheHeaderIsNoLongerInTheContext()) - .BDDfy(); - } - - private void GivenAHttpContext(HttpResponseHeaders headers) - { - _headers = headers; - } - - private void WhenIRemoveTheHeaders() - { - _result = _removeOutputHeaders.Remove(_headers); - } - - private void TheHeaderIsNoLongerInTheContext() - { - _result.IsError.ShouldBeFalse(); - _headers.ShouldNotContain(x => x.Key == "Transfer-Encoding"); - _headers.ShouldNotContain(x => x.Key == "transfer-encoding"); - } - } -} +using System.Net.Http; +using System.Net.Http.Headers; +using Ocelot.Responses; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Headers +{ + public class RemoveHeadersTests + { + private HttpResponseHeaders _headers; + private readonly Ocelot.Headers.RemoveOutputHeaders _removeOutputHeaders; + private Response _result; + + public RemoveHeadersTests() + { + _removeOutputHeaders = new Ocelot.Headers.RemoveOutputHeaders(); + } + + [Fact] + public void should_remove_header() + { + var httpResponse = new HttpResponseMessage() + { + Headers = {{ "Transfer-Encoding", "chunked"}} + }; + + this.Given(x => x.GivenAHttpContext(httpResponse.Headers)) + .When(x => x.WhenIRemoveTheHeaders()) + .Then(x => x.TheHeaderIsNoLongerInTheContext()) + .BDDfy(); + } + + private void GivenAHttpContext(HttpResponseHeaders headers) + { + _headers = headers; + } + + private void WhenIRemoveTheHeaders() + { + _result = _removeOutputHeaders.Remove(_headers); + } + + private void TheHeaderIsNoLongerInTheContext() + { + _result.IsError.ShouldBeFalse(); + _headers.ShouldNotContain(x => x.Key == "Transfer-Encoding"); + _headers.ShouldNotContain(x => x.Key == "transfer-encoding"); + } + } +} diff --git a/test/Ocelot.UnitTests/Infrastructure/ClaimParserTests.cs b/test/Ocelot.UnitTests/Infrastructure/ClaimParserTests.cs index 2358166fd..195705542 100644 --- a/test/Ocelot.UnitTests/Infrastructure/ClaimParserTests.cs +++ b/test/Ocelot.UnitTests/Infrastructure/ClaimParserTests.cs @@ -1,124 +1,124 @@ -using Ocelot.Errors; - -namespace Ocelot.UnitTests.Infrastructure -{ - using System.Collections.Generic; - using System.Security.Claims; - using Ocelot.Infrastructure.Claims.Parser; - using Responses; - using Shouldly; - using TestStack.BDDfy; - using Xunit; - - public class ClaimParserTests - { - private readonly IClaimsParser _claimsParser; - private readonly List _claims; - private string _key; - private Response _result; - private string _delimiter; - private int _index; - - public ClaimParserTests() - { - _claims = new List(); - _claimsParser = new ClaimsParser(); - } - - [Fact] - public void can_parse_claims_dictionary_access_string_returning_value_to_function() - { - this.Given(x => x.GivenAClaimOf(new Claim("CustomerId", "1234"))) - .And(x => x.GivenTheKeyIs("CustomerId")) - .When(x => x.WhenICallTheParser()) - .Then(x => x.ThenTheResultIs(new OkResponse("1234"))) - .BDDfy(); - } - - [Fact] - public void should_return_error_response_when_cannot_find_requested_claim() - { - this.Given(x => x.GivenAClaimOf(new Claim("BallsId", "1234"))) - .And(x => x.GivenTheKeyIs("CustomerId")) - .When(x => x.WhenICallTheParser()) - .Then(x => x.ThenTheResultIs(new ErrorResponse(new List - { - new CannotFindClaimError($"Cannot find claim for key: {_key}") - }))) - .BDDfy(); - } - - [Fact] - public void can_parse_claims_dictionary_access_string_using_delimiter_and_retuning_at_correct_index() - { - this.Given(x => x.GivenAClaimOf(new Claim("Subject", "registered|4321"))) - .And(x => x.GivenTheDelimiterIs("|")) - .And(x => x.GivenTheIndexIs(1)) - .And(x => x.GivenTheKeyIs("Subject")) - .When(x => x.WhenICallTheParser()) - .Then(x => x.ThenTheResultIs(new OkResponse("4321"))) - .BDDfy(); - } - - [Fact] - public void should_return_error_response_if_index_too_large() - { - this.Given(x => x.GivenAClaimOf(new Claim("Subject", "registered|4321"))) - .And(x => x.GivenTheDelimiterIs("|")) - .And(x => x.GivenTheIndexIs(24)) - .And(x => x.GivenTheKeyIs("Subject")) - .When(x => x.WhenICallTheParser()) - .Then(x => x.ThenTheResultIs(new ErrorResponse(new List - { - new CannotFindClaimError($"Cannot find claim for key: {_key}, delimiter: {_delimiter}, index: {_index}") - }))) - .BDDfy(); - } - - [Fact] - public void should_return_error_response_if_index_too_small() - { - this.Given(x => x.GivenAClaimOf(new Claim("Subject", "registered|4321"))) - .And(x => x.GivenTheDelimiterIs("|")) - .And(x => x.GivenTheIndexIs(-1)) - .And(x => x.GivenTheKeyIs("Subject")) - .When(x => x.WhenICallTheParser()) - .Then(x => x.ThenTheResultIs(new ErrorResponse(new List - { - new CannotFindClaimError($"Cannot find claim for key: {_key}, delimiter: {_delimiter}, index: {_index}") - }))) - .BDDfy(); - } - - private void GivenTheIndexIs(int index) - { - _index = index; - } - - private void GivenTheDelimiterIs(string delimiter) - { - _delimiter = delimiter; - } - - private void GivenAClaimOf(Claim claim) - { - _claims.Add(claim); - } - - private void GivenTheKeyIs(string key) - { - _key = key; - } - - private void WhenICallTheParser() - { - _result = _claimsParser.GetValue(_claims, _key, _delimiter, _index); - } - - private void ThenTheResultIs(Response expected) - { - _result.Data.ShouldBe(expected.Data); - _result.IsError.ShouldBe(expected.IsError); - } - } -} +using Ocelot.Errors; + +namespace Ocelot.UnitTests.Infrastructure +{ + using System.Collections.Generic; + using System.Security.Claims; + using Ocelot.Infrastructure.Claims.Parser; + using Responses; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + + public class ClaimParserTests + { + private readonly IClaimsParser _claimsParser; + private readonly List _claims; + private string _key; + private Response _result; + private string _delimiter; + private int _index; + + public ClaimParserTests() + { + _claims = new List(); + _claimsParser = new ClaimsParser(); + } + + [Fact] + public void can_parse_claims_dictionary_access_string_returning_value_to_function() + { + this.Given(x => x.GivenAClaimOf(new Claim("CustomerId", "1234"))) + .And(x => x.GivenTheKeyIs("CustomerId")) + .When(x => x.WhenICallTheParser()) + .Then(x => x.ThenTheResultIs(new OkResponse("1234"))) + .BDDfy(); + } + + [Fact] + public void should_return_error_response_when_cannot_find_requested_claim() + { + this.Given(x => x.GivenAClaimOf(new Claim("BallsId", "1234"))) + .And(x => x.GivenTheKeyIs("CustomerId")) + .When(x => x.WhenICallTheParser()) + .Then(x => x.ThenTheResultIs(new ErrorResponse(new List + { + new CannotFindClaimError($"Cannot find claim for key: {_key}") + }))) + .BDDfy(); + } + + [Fact] + public void can_parse_claims_dictionary_access_string_using_delimiter_and_retuning_at_correct_index() + { + this.Given(x => x.GivenAClaimOf(new Claim("Subject", "registered|4321"))) + .And(x => x.GivenTheDelimiterIs("|")) + .And(x => x.GivenTheIndexIs(1)) + .And(x => x.GivenTheKeyIs("Subject")) + .When(x => x.WhenICallTheParser()) + .Then(x => x.ThenTheResultIs(new OkResponse("4321"))) + .BDDfy(); + } + + [Fact] + public void should_return_error_response_if_index_too_large() + { + this.Given(x => x.GivenAClaimOf(new Claim("Subject", "registered|4321"))) + .And(x => x.GivenTheDelimiterIs("|")) + .And(x => x.GivenTheIndexIs(24)) + .And(x => x.GivenTheKeyIs("Subject")) + .When(x => x.WhenICallTheParser()) + .Then(x => x.ThenTheResultIs(new ErrorResponse(new List + { + new CannotFindClaimError($"Cannot find claim for key: {_key}, delimiter: {_delimiter}, index: {_index}") + }))) + .BDDfy(); + } + + [Fact] + public void should_return_error_response_if_index_too_small() + { + this.Given(x => x.GivenAClaimOf(new Claim("Subject", "registered|4321"))) + .And(x => x.GivenTheDelimiterIs("|")) + .And(x => x.GivenTheIndexIs(-1)) + .And(x => x.GivenTheKeyIs("Subject")) + .When(x => x.WhenICallTheParser()) + .Then(x => x.ThenTheResultIs(new ErrorResponse(new List + { + new CannotFindClaimError($"Cannot find claim for key: {_key}, delimiter: {_delimiter}, index: {_index}") + }))) + .BDDfy(); + } + + private void GivenTheIndexIs(int index) + { + _index = index; + } + + private void GivenTheDelimiterIs(string delimiter) + { + _delimiter = delimiter; + } + + private void GivenAClaimOf(Claim claim) + { + _claims.Add(claim); + } + + private void GivenTheKeyIs(string key) + { + _key = key; + } + + private void WhenICallTheParser() + { + _result = _claimsParser.GetValue(_claims, _key, _delimiter, _index); + } + + private void ThenTheResultIs(Response expected) + { + _result.Data.ShouldBe(expected.Data); + _result.IsError.ShouldBe(expected.IsError); + } + } +} diff --git a/test/Ocelot.UnitTests/Infrastructure/ScopesAuthoriserTests.cs b/test/Ocelot.UnitTests/Infrastructure/ScopesAuthoriserTests.cs index eef569826..1d476db3b 100644 --- a/test/Ocelot.UnitTests/Infrastructure/ScopesAuthoriserTests.cs +++ b/test/Ocelot.UnitTests/Infrastructure/ScopesAuthoriserTests.cs @@ -1,124 +1,124 @@ -using Xunit; -using Shouldly; -using Ocelot.Authorisation; -using Ocelot.Infrastructure.Claims.Parser; -using Moq; -using System.Collections.Generic; -using System.Security.Claims; -using Ocelot.Responses; -using TestStack.BDDfy; -using Ocelot.Errors; - -namespace Ocelot.UnitTests.Infrastructure -{ - public class ScopesAuthoriserTests - { - private ScopesAuthoriser _authoriser; - public Mock _parser; - private ClaimsPrincipal _principal; - private List _allowedScopes; - private Response _result; - - public ScopesAuthoriserTests() - { - _parser = new Mock(); - _authoriser = new ScopesAuthoriser(_parser.Object); - } - - [Fact] - public void should_return_ok_if_no_allowed_scopes() - { - this.Given(_ => GivenTheFollowing(new ClaimsPrincipal())) - .And(_ => GivenTheFollowing(new List())) - .When(_ => WhenIAuthorise()) - .Then(_ => ThenTheFollowingIsReturned(new OkResponse(true))) - .BDDfy(); - } - - - [Fact] - public void should_return_ok_if_null_allowed_scopes() - { - this.Given(_ => GivenTheFollowing(new ClaimsPrincipal())) - .And(_ => GivenTheFollowing((List)null)) - .When(_ => WhenIAuthorise()) - .Then(_ => ThenTheFollowingIsReturned(new OkResponse(true))) - .BDDfy(); - } - - [Fact] - public void should_return_error_if_claims_parser_returns_error() - { - var fakeError = new FakeError(); - this.Given(_ => GivenTheFollowing(new ClaimsPrincipal())) - .And(_ => GivenTheParserReturns(new ErrorResponse>(fakeError))) - .And(_ => GivenTheFollowing(new List(){"doesntmatter"})) - .When(_ => WhenIAuthorise()) - .Then(_ => ThenTheFollowingIsReturned(new ErrorResponse(fakeError))) - .BDDfy(); - } - - [Fact] - public void should_match_scopes_and_return_ok_result() - { - var claimsPrincipal = new ClaimsPrincipal(); - var allowedScopes = new List(){"someScope"}; - - this.Given(_ => GivenTheFollowing(claimsPrincipal)) - .And(_ => GivenTheParserReturns(new OkResponse>(allowedScopes))) - .And(_ => GivenTheFollowing(allowedScopes)) - .When(_ => WhenIAuthorise()) - .Then(_ => ThenTheFollowingIsReturned(new OkResponse(true))) - .BDDfy(); - } - - [Fact] - public void should_not_match_scopes_and_return_error_result() - { - var fakeError = new FakeError(); - var claimsPrincipal = new ClaimsPrincipal(); - var allowedScopes = new List(){"someScope"}; - var userScopes = new List(){"anotherScope"}; - - this.Given(_ => GivenTheFollowing(claimsPrincipal)) - .And(_ => GivenTheParserReturns(new OkResponse>(userScopes))) - .And(_ => GivenTheFollowing(allowedScopes)) - .When(_ => WhenIAuthorise()) - .Then(_ => ThenTheFollowingIsReturned(new ErrorResponse(fakeError))) - .BDDfy(); - } - - private void GivenTheParserReturns(Response> response) - { - _parser.Setup(x => x.GetValuesByClaimType(It.IsAny>(), It.IsAny())).Returns(response); - } - - private void GivenTheFollowing(ClaimsPrincipal principal) - { - _principal = principal; - } - - private void GivenTheFollowing(List allowedScopes) - { - _allowedScopes = allowedScopes; - } - - private void WhenIAuthorise() - { - _result = _authoriser.Authorise(_principal, _allowedScopes); - } - - private void ThenTheFollowingIsReturned(Response expected) - { - _result.Data.ShouldBe(expected.Data); - _result.IsError.ShouldBe(expected.IsError); - } - } - - public class FakeError : Error - { - public FakeError() : base("fake error", OcelotErrorCode.CannotAddDataError) - { - } - } +using Xunit; +using Shouldly; +using Ocelot.Authorisation; +using Ocelot.Infrastructure.Claims.Parser; +using Moq; +using System.Collections.Generic; +using System.Security.Claims; +using Ocelot.Responses; +using TestStack.BDDfy; +using Ocelot.Errors; + +namespace Ocelot.UnitTests.Infrastructure +{ + public class ScopesAuthoriserTests + { + private ScopesAuthoriser _authoriser; + public Mock _parser; + private ClaimsPrincipal _principal; + private List _allowedScopes; + private Response _result; + + public ScopesAuthoriserTests() + { + _parser = new Mock(); + _authoriser = new ScopesAuthoriser(_parser.Object); + } + + [Fact] + public void should_return_ok_if_no_allowed_scopes() + { + this.Given(_ => GivenTheFollowing(new ClaimsPrincipal())) + .And(_ => GivenTheFollowing(new List())) + .When(_ => WhenIAuthorise()) + .Then(_ => ThenTheFollowingIsReturned(new OkResponse(true))) + .BDDfy(); + } + + + [Fact] + public void should_return_ok_if_null_allowed_scopes() + { + this.Given(_ => GivenTheFollowing(new ClaimsPrincipal())) + .And(_ => GivenTheFollowing((List)null)) + .When(_ => WhenIAuthorise()) + .Then(_ => ThenTheFollowingIsReturned(new OkResponse(true))) + .BDDfy(); + } + + [Fact] + public void should_return_error_if_claims_parser_returns_error() + { + var fakeError = new FakeError(); + this.Given(_ => GivenTheFollowing(new ClaimsPrincipal())) + .And(_ => GivenTheParserReturns(new ErrorResponse>(fakeError))) + .And(_ => GivenTheFollowing(new List(){"doesntmatter"})) + .When(_ => WhenIAuthorise()) + .Then(_ => ThenTheFollowingIsReturned(new ErrorResponse(fakeError))) + .BDDfy(); + } + + [Fact] + public void should_match_scopes_and_return_ok_result() + { + var claimsPrincipal = new ClaimsPrincipal(); + var allowedScopes = new List(){"someScope"}; + + this.Given(_ => GivenTheFollowing(claimsPrincipal)) + .And(_ => GivenTheParserReturns(new OkResponse>(allowedScopes))) + .And(_ => GivenTheFollowing(allowedScopes)) + .When(_ => WhenIAuthorise()) + .Then(_ => ThenTheFollowingIsReturned(new OkResponse(true))) + .BDDfy(); + } + + [Fact] + public void should_not_match_scopes_and_return_error_result() + { + var fakeError = new FakeError(); + var claimsPrincipal = new ClaimsPrincipal(); + var allowedScopes = new List(){"someScope"}; + var userScopes = new List(){"anotherScope"}; + + this.Given(_ => GivenTheFollowing(claimsPrincipal)) + .And(_ => GivenTheParserReturns(new OkResponse>(userScopes))) + .And(_ => GivenTheFollowing(allowedScopes)) + .When(_ => WhenIAuthorise()) + .Then(_ => ThenTheFollowingIsReturned(new ErrorResponse(fakeError))) + .BDDfy(); + } + + private void GivenTheParserReturns(Response> response) + { + _parser.Setup(x => x.GetValuesByClaimType(It.IsAny>(), It.IsAny())).Returns(response); + } + + private void GivenTheFollowing(ClaimsPrincipal principal) + { + _principal = principal; + } + + private void GivenTheFollowing(List allowedScopes) + { + _allowedScopes = allowedScopes; + } + + private void WhenIAuthorise() + { + _result = _authoriser.Authorise(_principal, _allowedScopes); + } + + private void ThenTheFollowingIsReturned(Response expected) + { + _result.Data.ShouldBe(expected.Data); + _result.IsError.ShouldBe(expected.IsError); + } + } + + public class FakeError : Error + { + public FakeError() : base("fake error", OcelotErrorCode.CannotAddDataError) + { + } + } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs index be1bd1082..e372ac4ea 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs @@ -1,285 +1,285 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Ocelot.LoadBalancer.LoadBalancers; -using Ocelot.Responses; -using Ocelot.Values; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.LoadBalancer -{ - public class LeastConnectionTests - { - private HostAndPort _hostAndPort; - private Response _result; - private LeastConnection _leastConnection; - private List _services; - private Random _random; - - public LeastConnectionTests() - { - _random = new Random(); - } - - [Fact] - public void should_be_able_to_lease_and_release_concurrently() - { - var serviceName = "products"; - - var availableServices = new List - { - new Service(serviceName, new HostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]), - new Service(serviceName, new HostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]), - }; - - _services = availableServices; - _leastConnection = new LeastConnection(() => Task.FromResult(_services), serviceName); - - var tasks = new Task[100]; - - for(var i = 0; i < tasks.Length; i++) - { - tasks[i] = LeaseDelayAndRelease(); - } - - Task.WaitAll(tasks); - } - - [Fact] - public void should_handle_service_returning_to_available() - { - var serviceName = "products"; - - var availableServices = new List - { - new Service(serviceName, new HostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]), - new Service(serviceName, new HostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]), - }; - - _leastConnection = new LeastConnection(() => Task.FromResult(availableServices), serviceName); - - var hostAndPortOne = _leastConnection.Lease().Result; - hostAndPortOne.Data.DownstreamHost.ShouldBe("127.0.0.1"); - var hostAndPortTwo = _leastConnection.Lease().Result; - hostAndPortTwo.Data.DownstreamHost.ShouldBe("127.0.0.2"); - _leastConnection.Release(hostAndPortOne.Data); - _leastConnection.Release(hostAndPortTwo.Data); - - availableServices = new List - { - new Service(serviceName, new HostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]), - }; - - hostAndPortOne = _leastConnection.Lease().Result; - hostAndPortOne.Data.DownstreamHost.ShouldBe("127.0.0.1"); - hostAndPortTwo = _leastConnection.Lease().Result; - hostAndPortTwo.Data.DownstreamHost.ShouldBe("127.0.0.1"); - _leastConnection.Release(hostAndPortOne.Data); - _leastConnection.Release(hostAndPortTwo.Data); - - availableServices = new List - { - new Service(serviceName, new HostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]), - new Service(serviceName, new HostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]), - }; - - hostAndPortOne = _leastConnection.Lease().Result; - hostAndPortOne.Data.DownstreamHost.ShouldBe("127.0.0.1"); - hostAndPortTwo = _leastConnection.Lease().Result; - hostAndPortTwo.Data.DownstreamHost.ShouldBe("127.0.0.2"); - _leastConnection.Release(hostAndPortOne.Data); - _leastConnection.Release(hostAndPortTwo.Data); - - } - - private async Task LeaseDelayAndRelease() - { - var hostAndPort = await _leastConnection.Lease(); - await Task.Delay(_random.Next(1, 100)); - _leastConnection.Release(hostAndPort.Data); - } - - [Fact] - public void should_get_next_url() - { - var serviceName = "products"; - - var hostAndPort = new HostAndPort("localhost", 80); - - var availableServices = new List - { - new Service(serviceName, hostAndPort, string.Empty, string.Empty, new string[0]) - }; - - this.Given(x => x.GivenAHostAndPort(hostAndPort)) - .And(x => x.GivenTheLoadBalancerStarts(availableServices, serviceName)) - .When(x => x.WhenIGetTheNextHostAndPort()) - .Then(x => x.ThenTheNextHostAndPortIsReturned()) - .BDDfy(); - } - - [Fact] - public void should_serve_from_service_with_least_connections() - { - var serviceName = "products"; - - var availableServices = new List - { - new Service(serviceName, new HostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]), - new Service(serviceName, new HostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]), - new Service(serviceName, new HostAndPort("127.0.0.3", 80), string.Empty, string.Empty, new string[0]) - }; - - _services = availableServices; - _leastConnection = new LeastConnection(() => Task.FromResult(_services), serviceName); - - var response = _leastConnection.Lease().Result; - - response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost); - - response = _leastConnection.Lease().Result; - - response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); - - response = _leastConnection.Lease().Result; - - response.Data.DownstreamHost.ShouldBe(availableServices[2].HostAndPort.DownstreamHost); - } - - [Fact] - public void should_build_connections_per_service() - { - var serviceName = "products"; - - var availableServices = new List - { - new Service(serviceName, new HostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]), - new Service(serviceName, new HostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]), - }; - - _services = availableServices; - _leastConnection = new LeastConnection(() => Task.FromResult(_services), serviceName); - - var response = _leastConnection.Lease().Result; - - response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost); - - response = _leastConnection.Lease().Result; - - response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); - - response = _leastConnection.Lease().Result; - - response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost); - - response = _leastConnection.Lease().Result; - - response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); - } - - [Fact] - public void should_release_connection() - { - var serviceName = "products"; - - var availableServices = new List - { - new Service(serviceName, new HostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]), - new Service(serviceName, new HostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]), - }; - - _services = availableServices; - _leastConnection = new LeastConnection(() => Task.FromResult(_services), serviceName); - - var response = _leastConnection.Lease().Result; - - response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost); - - response = _leastConnection.Lease().Result; - - response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); - - response = _leastConnection.Lease().Result; - - response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost); - - response = _leastConnection.Lease().Result; - - response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); - - //release this so 2 should have 1 connection and we should get 2 back as our next host and port - _leastConnection.Release(availableServices[1].HostAndPort); - - response = _leastConnection.Lease().Result; - - response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); - } - - [Fact] - public void should_return_error_if_services_are_null() - { - var serviceName = "products"; - - var hostAndPort = new HostAndPort("localhost", 80); - this.Given(x => x.GivenAHostAndPort(hostAndPort)) - .And(x => x.GivenTheLoadBalancerStarts(null, serviceName)) - .When(x => x.WhenIGetTheNextHostAndPort()) - .Then(x => x.ThenServiceAreNullErrorIsReturned()) - .BDDfy(); - } - - [Fact] - public void should_return_error_if_services_are_empty() - { - var serviceName = "products"; - - var hostAndPort = new HostAndPort("localhost", 80); - this.Given(x => x.GivenAHostAndPort(hostAndPort)) - .And(x => x.GivenTheLoadBalancerStarts(new List(), serviceName)) - .When(x => x.WhenIGetTheNextHostAndPort()) - .Then(x => x.ThenServiceAreEmptyErrorIsReturned()) - .BDDfy(); - } - - private void ThenServiceAreNullErrorIsReturned() - { - _result.IsError.ShouldBeTrue(); - _result.Errors[0].ShouldBeOfType(); - } - - private void ThenServiceAreEmptyErrorIsReturned() - { - _result.IsError.ShouldBeTrue(); - _result.Errors[0].ShouldBeOfType(); - } - - private void GivenTheLoadBalancerStarts(List services, string serviceName) - { - _services = services; - _leastConnection = new LeastConnection(() => Task.FromResult(_services), serviceName); - } - - private void WhenTheLoadBalancerStarts(List services, string serviceName) - { - GivenTheLoadBalancerStarts(services, serviceName); - } - - private void GivenAHostAndPort(HostAndPort hostAndPort) - { - _hostAndPort = hostAndPort; - } - - private void WhenIGetTheNextHostAndPort() - { - _result = _leastConnection.Lease().Result; - } - - private void ThenTheNextHostAndPortIsReturned() - { - _result.Data.DownstreamHost.ShouldBe(_hostAndPort.DownstreamHost); - _result.Data.DownstreamPort.ShouldBe(_hostAndPort.DownstreamPort); - } - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Ocelot.LoadBalancer.LoadBalancers; +using Ocelot.Responses; +using Ocelot.Values; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.LoadBalancer +{ + public class LeastConnectionTests + { + private ServiceHostAndPort _hostAndPort; + private Response _result; + private LeastConnection _leastConnection; + private List _services; + private Random _random; + + public LeastConnectionTests() + { + _random = new Random(); + } + + [Fact] + public void should_be_able_to_lease_and_release_concurrently() + { + var serviceName = "products"; + + var availableServices = new List + { + new Service(serviceName, new ServiceHostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]), + new Service(serviceName, new ServiceHostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]), + }; + + _services = availableServices; + _leastConnection = new LeastConnection(() => Task.FromResult(_services), serviceName); + + var tasks = new Task[100]; + + for(var i = 0; i < tasks.Length; i++) + { + tasks[i] = LeaseDelayAndRelease(); + } + + Task.WaitAll(tasks); + } + + [Fact] + public void should_handle_service_returning_to_available() + { + var serviceName = "products"; + + var availableServices = new List + { + new Service(serviceName, new ServiceHostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]), + new Service(serviceName, new ServiceHostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]), + }; + + _leastConnection = new LeastConnection(() => Task.FromResult(availableServices), serviceName); + + var hostAndPortOne = _leastConnection.Lease().Result; + hostAndPortOne.Data.DownstreamHost.ShouldBe("127.0.0.1"); + var hostAndPortTwo = _leastConnection.Lease().Result; + hostAndPortTwo.Data.DownstreamHost.ShouldBe("127.0.0.2"); + _leastConnection.Release(hostAndPortOne.Data); + _leastConnection.Release(hostAndPortTwo.Data); + + availableServices = new List + { + new Service(serviceName, new ServiceHostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]), + }; + + hostAndPortOne = _leastConnection.Lease().Result; + hostAndPortOne.Data.DownstreamHost.ShouldBe("127.0.0.1"); + hostAndPortTwo = _leastConnection.Lease().Result; + hostAndPortTwo.Data.DownstreamHost.ShouldBe("127.0.0.1"); + _leastConnection.Release(hostAndPortOne.Data); + _leastConnection.Release(hostAndPortTwo.Data); + + availableServices = new List + { + new Service(serviceName, new ServiceHostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]), + new Service(serviceName, new ServiceHostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]), + }; + + hostAndPortOne = _leastConnection.Lease().Result; + hostAndPortOne.Data.DownstreamHost.ShouldBe("127.0.0.1"); + hostAndPortTwo = _leastConnection.Lease().Result; + hostAndPortTwo.Data.DownstreamHost.ShouldBe("127.0.0.2"); + _leastConnection.Release(hostAndPortOne.Data); + _leastConnection.Release(hostAndPortTwo.Data); + + } + + private async Task LeaseDelayAndRelease() + { + var hostAndPort = await _leastConnection.Lease(); + await Task.Delay(_random.Next(1, 100)); + _leastConnection.Release(hostAndPort.Data); + } + + [Fact] + public void should_get_next_url() + { + var serviceName = "products"; + + var hostAndPort = new ServiceHostAndPort("localhost", 80); + + var availableServices = new List + { + new Service(serviceName, hostAndPort, string.Empty, string.Empty, new string[0]) + }; + + this.Given(x => x.GivenAHostAndPort(hostAndPort)) + .And(x => x.GivenTheLoadBalancerStarts(availableServices, serviceName)) + .When(x => x.WhenIGetTheNextHostAndPort()) + .Then(x => x.ThenTheNextHostAndPortIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_serve_from_service_with_least_connections() + { + var serviceName = "products"; + + var availableServices = new List + { + new Service(serviceName, new ServiceHostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]), + new Service(serviceName, new ServiceHostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]), + new Service(serviceName, new ServiceHostAndPort("127.0.0.3", 80), string.Empty, string.Empty, new string[0]) + }; + + _services = availableServices; + _leastConnection = new LeastConnection(() => Task.FromResult(_services), serviceName); + + var response = _leastConnection.Lease().Result; + + response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost); + + response = _leastConnection.Lease().Result; + + response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); + + response = _leastConnection.Lease().Result; + + response.Data.DownstreamHost.ShouldBe(availableServices[2].HostAndPort.DownstreamHost); + } + + [Fact] + public void should_build_connections_per_service() + { + var serviceName = "products"; + + var availableServices = new List + { + new Service(serviceName, new ServiceHostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]), + new Service(serviceName, new ServiceHostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]), + }; + + _services = availableServices; + _leastConnection = new LeastConnection(() => Task.FromResult(_services), serviceName); + + var response = _leastConnection.Lease().Result; + + response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost); + + response = _leastConnection.Lease().Result; + + response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); + + response = _leastConnection.Lease().Result; + + response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost); + + response = _leastConnection.Lease().Result; + + response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); + } + + [Fact] + public void should_release_connection() + { + var serviceName = "products"; + + var availableServices = new List + { + new Service(serviceName, new ServiceHostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]), + new Service(serviceName, new ServiceHostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]), + }; + + _services = availableServices; + _leastConnection = new LeastConnection(() => Task.FromResult(_services), serviceName); + + var response = _leastConnection.Lease().Result; + + response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost); + + response = _leastConnection.Lease().Result; + + response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); + + response = _leastConnection.Lease().Result; + + response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost); + + response = _leastConnection.Lease().Result; + + response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); + + //release this so 2 should have 1 connection and we should get 2 back as our next host and port + _leastConnection.Release(availableServices[1].HostAndPort); + + response = _leastConnection.Lease().Result; + + response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); + } + + [Fact] + public void should_return_error_if_services_are_null() + { + var serviceName = "products"; + + var hostAndPort = new ServiceHostAndPort("localhost", 80); + this.Given(x => x.GivenAHostAndPort(hostAndPort)) + .And(x => x.GivenTheLoadBalancerStarts(null, serviceName)) + .When(x => x.WhenIGetTheNextHostAndPort()) + .Then(x => x.ThenServiceAreNullErrorIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_return_error_if_services_are_empty() + { + var serviceName = "products"; + + var hostAndPort = new ServiceHostAndPort("localhost", 80); + this.Given(x => x.GivenAHostAndPort(hostAndPort)) + .And(x => x.GivenTheLoadBalancerStarts(new List(), serviceName)) + .When(x => x.WhenIGetTheNextHostAndPort()) + .Then(x => x.ThenServiceAreEmptyErrorIsReturned()) + .BDDfy(); + } + + private void ThenServiceAreNullErrorIsReturned() + { + _result.IsError.ShouldBeTrue(); + _result.Errors[0].ShouldBeOfType(); + } + + private void ThenServiceAreEmptyErrorIsReturned() + { + _result.IsError.ShouldBeTrue(); + _result.Errors[0].ShouldBeOfType(); + } + + private void GivenTheLoadBalancerStarts(List services, string serviceName) + { + _services = services; + _leastConnection = new LeastConnection(() => Task.FromResult(_services), serviceName); + } + + private void WhenTheLoadBalancerStarts(List services, string serviceName) + { + GivenTheLoadBalancerStarts(services, serviceName); + } + + private void GivenAHostAndPort(ServiceHostAndPort hostAndPort) + { + _hostAndPort = hostAndPort; + } + + private void WhenIGetTheNextHostAndPort() + { + _result = _leastConnection.Lease().Result; + } + + private void ThenTheNextHostAndPortIsReturned() + { + _result.Data.DownstreamHost.ShouldBe(_hostAndPort.DownstreamHost); + _result.Data.DownstreamPort.ShouldBe(_hostAndPort.DownstreamPort); + } + } +} diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs index d630393b5..e89d62b19 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs @@ -1,125 +1,125 @@ -using Moq; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.LoadBalancer.LoadBalancers; -using Ocelot.ServiceDiscovery; -using Shouldly; -using System.Collections.Generic; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.LoadBalancer -{ - public class LoadBalancerFactoryTests - { - private ReRoute _reRoute; - private LoadBalancerFactory _factory; - private ILoadBalancer _result; - private Mock _serviceProviderFactory; - private Mock _serviceProvider; - private ServiceProviderConfiguration _serviceProviderConfig; - - public LoadBalancerFactoryTests() - { - _serviceProviderFactory = new Mock(); - _serviceProvider = new Mock(); - _factory = new LoadBalancerFactory(_serviceProviderFactory.Object); - } - - [Fact] - public void should_return_no_load_balancer() - { - var reRoute = new ReRouteBuilder() - .WithUpstreamHttpMethod(new List { "Get" }) - .Build(); - - this.Given(x => x.GivenAReRoute(reRoute)) - .And(x => GivenAServiceProviderConfig(new ServiceProviderConfigurationBuilder().Build())) - .And(x => x.GivenTheServiceProviderFactoryReturns()) - .When(x => x.WhenIGetTheLoadBalancer()) - .Then(x => x.ThenTheLoadBalancerIsReturned()) - .BDDfy(); - } - - [Fact] - public void should_return_round_robin_load_balancer() - { - var reRoute = new ReRouteBuilder() - .WithLoadBalancer("RoundRobin") - .WithUpstreamHttpMethod(new List { "Get" }) - .Build(); - - this.Given(x => x.GivenAReRoute(reRoute)) - .And(x => GivenAServiceProviderConfig(new ServiceProviderConfigurationBuilder().Build())) - .And(x => x.GivenTheServiceProviderFactoryReturns()) - .When(x => x.WhenIGetTheLoadBalancer()) - .Then(x => x.ThenTheLoadBalancerIsReturned()) - .BDDfy(); - } - - [Fact] - public void should_return_round_least_connection_balancer() - { - var reRoute = new ReRouteBuilder() - .WithLoadBalancer("LeastConnection") - .WithUpstreamHttpMethod(new List { "Get" }) - .Build(); - - this.Given(x => x.GivenAReRoute(reRoute)) - .And(x => GivenAServiceProviderConfig(new ServiceProviderConfigurationBuilder().Build())) - .And(x => x.GivenTheServiceProviderFactoryReturns()) - .When(x => x.WhenIGetTheLoadBalancer()) - .Then(x => x.ThenTheLoadBalancerIsReturned()) - .BDDfy(); - } - - [Fact] - public void should_call_service_provider() - { - var reRoute = new ReRouteBuilder() - .WithLoadBalancer("RoundRobin") - .WithUpstreamHttpMethod(new List { "Get" }) - .Build(); - - this.Given(x => x.GivenAReRoute(reRoute)) - .And(x => GivenAServiceProviderConfig(new ServiceProviderConfigurationBuilder().Build())) - .And(x => x.GivenTheServiceProviderFactoryReturns()) - .When(x => x.WhenIGetTheLoadBalancer()) - .Then(x => x.ThenTheServiceProviderIsCalledCorrectly()) - .BDDfy(); - } - - private void GivenAServiceProviderConfig(ServiceProviderConfiguration serviceProviderConfig) - { - _serviceProviderConfig = serviceProviderConfig; - } - - private void GivenTheServiceProviderFactoryReturns() - { - _serviceProviderFactory - .Setup(x => x.Get(It.IsAny(), It.IsAny())) - .Returns(_serviceProvider.Object); - } - - private void ThenTheServiceProviderIsCalledCorrectly() - { - _serviceProviderFactory - .Verify(x => x.Get(It.IsAny(), It.IsAny()), Times.Once); - } - - private void GivenAReRoute(ReRoute reRoute) - { - _reRoute = reRoute; - } - - private void WhenIGetTheLoadBalancer() - { - _result = _factory.Get(_reRoute, _serviceProviderConfig).Result; - } - - private void ThenTheLoadBalancerIsReturned() - { - _result.ShouldBeOfType(); - } - } +using Moq; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.LoadBalancer.LoadBalancers; +using Ocelot.ServiceDiscovery; +using Shouldly; +using System.Collections.Generic; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.LoadBalancer +{ + public class LoadBalancerFactoryTests + { + private ReRoute _reRoute; + private LoadBalancerFactory _factory; + private ILoadBalancer _result; + private Mock _serviceProviderFactory; + private Mock _serviceProvider; + private ServiceProviderConfiguration _serviceProviderConfig; + + public LoadBalancerFactoryTests() + { + _serviceProviderFactory = new Mock(); + _serviceProvider = new Mock(); + _factory = new LoadBalancerFactory(_serviceProviderFactory.Object); + } + + [Fact] + public void should_return_no_load_balancer() + { + var reRoute = new ReRouteBuilder() + .WithUpstreamHttpMethod(new List { "Get" }) + .Build(); + + this.Given(x => x.GivenAReRoute(reRoute)) + .And(x => GivenAServiceProviderConfig(new ServiceProviderConfigurationBuilder().Build())) + .And(x => x.GivenTheServiceProviderFactoryReturns()) + .When(x => x.WhenIGetTheLoadBalancer()) + .Then(x => x.ThenTheLoadBalancerIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_return_round_robin_load_balancer() + { + var reRoute = new ReRouteBuilder() + .WithLoadBalancer("RoundRobin") + .WithUpstreamHttpMethod(new List { "Get" }) + .Build(); + + this.Given(x => x.GivenAReRoute(reRoute)) + .And(x => GivenAServiceProviderConfig(new ServiceProviderConfigurationBuilder().Build())) + .And(x => x.GivenTheServiceProviderFactoryReturns()) + .When(x => x.WhenIGetTheLoadBalancer()) + .Then(x => x.ThenTheLoadBalancerIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_return_round_least_connection_balancer() + { + var reRoute = new ReRouteBuilder() + .WithLoadBalancer("LeastConnection") + .WithUpstreamHttpMethod(new List { "Get" }) + .Build(); + + this.Given(x => x.GivenAReRoute(reRoute)) + .And(x => GivenAServiceProviderConfig(new ServiceProviderConfigurationBuilder().Build())) + .And(x => x.GivenTheServiceProviderFactoryReturns()) + .When(x => x.WhenIGetTheLoadBalancer()) + .Then(x => x.ThenTheLoadBalancerIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_call_service_provider() + { + var reRoute = new ReRouteBuilder() + .WithLoadBalancer("RoundRobin") + .WithUpstreamHttpMethod(new List { "Get" }) + .Build(); + + this.Given(x => x.GivenAReRoute(reRoute)) + .And(x => GivenAServiceProviderConfig(new ServiceProviderConfigurationBuilder().Build())) + .And(x => x.GivenTheServiceProviderFactoryReturns()) + .When(x => x.WhenIGetTheLoadBalancer()) + .Then(x => x.ThenTheServiceProviderIsCalledCorrectly()) + .BDDfy(); + } + + private void GivenAServiceProviderConfig(ServiceProviderConfiguration serviceProviderConfig) + { + _serviceProviderConfig = serviceProviderConfig; + } + + private void GivenTheServiceProviderFactoryReturns() + { + _serviceProviderFactory + .Setup(x => x.Get(It.IsAny(), It.IsAny())) + .Returns(_serviceProvider.Object); + } + + private void ThenTheServiceProviderIsCalledCorrectly() + { + _serviceProviderFactory + .Verify(x => x.Get(It.IsAny(), It.IsAny()), Times.Once); + } + + private void GivenAReRoute(ReRoute reRoute) + { + _reRoute = reRoute; + } + + private void WhenIGetTheLoadBalancer() + { + _result = _factory.Get(_reRoute, _serviceProviderConfig).Result; + } + + private void ThenTheLoadBalancerIsReturned() + { + _result.ShouldBeOfType(); + } + } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs index a52118591..06c80eaee 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs @@ -1,165 +1,165 @@ -using System; -using System.Threading.Tasks; -using Moq; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.LoadBalancer.LoadBalancers; -using Ocelot.Responses; -using Ocelot.Values; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.LoadBalancer -{ - public class LoadBalancerHouseTests - { - private ReRoute _reRoute; - private ILoadBalancer _loadBalancer; - private readonly LoadBalancerHouse _loadBalancerHouse; - private Response _addResult; - private Response _getResult; - private string _key; - private Mock _factory; - private ServiceProviderConfiguration _serviceProviderConfig; - - public LoadBalancerHouseTests() - { - _factory = new Mock(); - _loadBalancerHouse = new LoadBalancerHouse(_factory.Object); - } - - [Fact] - public void should_store_load_balancer_on_first_request() - { - var reRoute = new ReRouteBuilder().WithReRouteKey("test").Build(); - - this.Given(x => x.GivenThereIsALoadBalancer(reRoute, new FakeLoadBalancer())) - .Then(x => x.ThenItIsAdded()) - .BDDfy(); - } - - [Fact] - public void should_not_store_load_balancer_on_second_request() - { - var reRoute = new ReRouteBuilder().WithLoadBalancer("FakeLoadBalancer").WithReRouteKey("test").Build(); - - this.Given(x => x.GivenThereIsALoadBalancer(reRoute, new FakeLoadBalancer())) - .When(x => x.WhenWeGetTheLoadBalancer(reRoute)) - .Then(x => x.ThenItIsReturned()) - .BDDfy(); - } - - [Fact] - public void should_store_load_balancers_by_key() - { - var reRoute = new ReRouteBuilder().WithReRouteKey("test").Build(); - var reRouteTwo = new ReRouteBuilder().WithReRouteKey("testtwo").Build(); - - this.Given(x => x.GivenThereIsALoadBalancer(reRoute, new FakeLoadBalancer())) - .And(x => x.GivenThereIsALoadBalancer(reRouteTwo, new FakeRoundRobinLoadBalancer())) - .When(x => x.WhenWeGetTheLoadBalancer(reRoute)) - .Then(x => x.ThenTheLoadBalancerIs()) - .When(x => x.WhenWeGetTheLoadBalancer(reRouteTwo)) - .Then(x => x.ThenTheLoadBalancerIs()) - .BDDfy(); - } - - [Fact] - public void should_return_error_if_exception() - { - var reRoute = new ReRouteBuilder().Build(); - - this.When(x => x.WhenWeGetTheLoadBalancer(reRoute)) - .Then(x => x.ThenAnErrorIsReturned()) - .BDDfy(); - } - - [Fact] - public void should_get_new_load_balancer_if_reroute_load_balancer_has_changed() - { - var reRoute = new ReRouteBuilder().WithLoadBalancer("FakeLoadBalancer").WithReRouteKey("test").Build(); - - var reRouteTwo = new ReRouteBuilder().WithLoadBalancer("LeastConnection").WithReRouteKey("test").Build(); - - this.Given(x => x.GivenThereIsALoadBalancer(reRoute, new FakeLoadBalancer())) - .When(x => x.WhenWeGetTheLoadBalancer(reRoute)) - .Then(x => x.ThenTheLoadBalancerIs()) - .When(x => x.WhenIGetTheReRouteWithTheSameKeyButDifferentLoadBalancer(reRouteTwo)) - .Then(x => x.ThenTheLoadBalancerIs()) - .BDDfy(); - } - - private void WhenIGetTheReRouteWithTheSameKeyButDifferentLoadBalancer(ReRoute reRoute) - { - _reRoute = reRoute; - _factory.Setup(x => x.Get(_reRoute, _serviceProviderConfig)).ReturnsAsync(new LeastConnection(null, null)); - _getResult = _loadBalancerHouse.Get(_reRoute, _serviceProviderConfig).Result; - } - - private void ThenAnErrorIsReturned() - { - _getResult.IsError.ShouldBeTrue(); - _getResult.Errors[0].ShouldBeOfType(); - } - - private void ThenTheLoadBalancerIs() - { - _getResult.Data.ShouldBeOfType(); - } - - private void ThenItIsAdded() - { - _getResult.IsError.ShouldBe(false); - _getResult.ShouldBeOfType>(); - _getResult.Data.ShouldBe(_loadBalancer); - _factory.Verify(x => x.Get(_reRoute, _serviceProviderConfig), Times.Once); - } - - - private void GivenThereIsALoadBalancer(ReRoute reRoute, ILoadBalancer loadBalancer) - { - _reRoute = reRoute; - _loadBalancer = loadBalancer; - _factory.Setup(x => x.Get(_reRoute, _serviceProviderConfig)).ReturnsAsync(loadBalancer); - _getResult = _loadBalancerHouse.Get(reRoute, _serviceProviderConfig).Result; - } - - private void WhenWeGetTheLoadBalancer(ReRoute reRoute) - { - _getResult = _loadBalancerHouse.Get(reRoute, _serviceProviderConfig).Result; - } - - private void ThenItIsReturned() - { - _getResult.Data.ShouldBe(_loadBalancer); - _factory.Verify(x => x.Get(_reRoute, _serviceProviderConfig), Times.Once); - } - - class FakeLoadBalancer : ILoadBalancer - { - public Task> Lease() - { - throw new NotImplementedException(); - } - - public void Release(HostAndPort hostAndPort) - { - throw new NotImplementedException(); - } - } - - class FakeRoundRobinLoadBalancer : ILoadBalancer - { - public Task> Lease() - { - throw new NotImplementedException(); - } - - public void Release(HostAndPort hostAndPort) - { - throw new NotImplementedException(); - } - } - } -} +using System; +using System.Threading.Tasks; +using Moq; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.LoadBalancer.LoadBalancers; +using Ocelot.Responses; +using Ocelot.Values; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.LoadBalancer +{ + public class LoadBalancerHouseTests + { + private ReRoute _reRoute; + private ILoadBalancer _loadBalancer; + private readonly LoadBalancerHouse _loadBalancerHouse; + private Response _addResult; + private Response _getResult; + private string _key; + private Mock _factory; + private ServiceProviderConfiguration _serviceProviderConfig; + + public LoadBalancerHouseTests() + { + _factory = new Mock(); + _loadBalancerHouse = new LoadBalancerHouse(_factory.Object); + } + + [Fact] + public void should_store_load_balancer_on_first_request() + { + var reRoute = new ReRouteBuilder().WithReRouteKey("test").Build(); + + this.Given(x => x.GivenThereIsALoadBalancer(reRoute, new FakeLoadBalancer())) + .Then(x => x.ThenItIsAdded()) + .BDDfy(); + } + + [Fact] + public void should_not_store_load_balancer_on_second_request() + { + var reRoute = new ReRouteBuilder().WithLoadBalancer("FakeLoadBalancer").WithReRouteKey("test").Build(); + + this.Given(x => x.GivenThereIsALoadBalancer(reRoute, new FakeLoadBalancer())) + .When(x => x.WhenWeGetTheLoadBalancer(reRoute)) + .Then(x => x.ThenItIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_store_load_balancers_by_key() + { + var reRoute = new ReRouteBuilder().WithReRouteKey("test").Build(); + var reRouteTwo = new ReRouteBuilder().WithReRouteKey("testtwo").Build(); + + this.Given(x => x.GivenThereIsALoadBalancer(reRoute, new FakeLoadBalancer())) + .And(x => x.GivenThereIsALoadBalancer(reRouteTwo, new FakeRoundRobinLoadBalancer())) + .When(x => x.WhenWeGetTheLoadBalancer(reRoute)) + .Then(x => x.ThenTheLoadBalancerIs()) + .When(x => x.WhenWeGetTheLoadBalancer(reRouteTwo)) + .Then(x => x.ThenTheLoadBalancerIs()) + .BDDfy(); + } + + [Fact] + public void should_return_error_if_exception() + { + var reRoute = new ReRouteBuilder().Build(); + + this.When(x => x.WhenWeGetTheLoadBalancer(reRoute)) + .Then(x => x.ThenAnErrorIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_get_new_load_balancer_if_reroute_load_balancer_has_changed() + { + var reRoute = new ReRouteBuilder().WithLoadBalancer("FakeLoadBalancer").WithReRouteKey("test").Build(); + + var reRouteTwo = new ReRouteBuilder().WithLoadBalancer("LeastConnection").WithReRouteKey("test").Build(); + + this.Given(x => x.GivenThereIsALoadBalancer(reRoute, new FakeLoadBalancer())) + .When(x => x.WhenWeGetTheLoadBalancer(reRoute)) + .Then(x => x.ThenTheLoadBalancerIs()) + .When(x => x.WhenIGetTheReRouteWithTheSameKeyButDifferentLoadBalancer(reRouteTwo)) + .Then(x => x.ThenTheLoadBalancerIs()) + .BDDfy(); + } + + private void WhenIGetTheReRouteWithTheSameKeyButDifferentLoadBalancer(ReRoute reRoute) + { + _reRoute = reRoute; + _factory.Setup(x => x.Get(_reRoute, _serviceProviderConfig)).ReturnsAsync(new LeastConnection(null, null)); + _getResult = _loadBalancerHouse.Get(_reRoute, _serviceProviderConfig).Result; + } + + private void ThenAnErrorIsReturned() + { + _getResult.IsError.ShouldBeTrue(); + _getResult.Errors[0].ShouldBeOfType(); + } + + private void ThenTheLoadBalancerIs() + { + _getResult.Data.ShouldBeOfType(); + } + + private void ThenItIsAdded() + { + _getResult.IsError.ShouldBe(false); + _getResult.ShouldBeOfType>(); + _getResult.Data.ShouldBe(_loadBalancer); + _factory.Verify(x => x.Get(_reRoute, _serviceProviderConfig), Times.Once); + } + + + private void GivenThereIsALoadBalancer(ReRoute reRoute, ILoadBalancer loadBalancer) + { + _reRoute = reRoute; + _loadBalancer = loadBalancer; + _factory.Setup(x => x.Get(_reRoute, _serviceProviderConfig)).ReturnsAsync(loadBalancer); + _getResult = _loadBalancerHouse.Get(reRoute, _serviceProviderConfig).Result; + } + + private void WhenWeGetTheLoadBalancer(ReRoute reRoute) + { + _getResult = _loadBalancerHouse.Get(reRoute, _serviceProviderConfig).Result; + } + + private void ThenItIsReturned() + { + _getResult.Data.ShouldBe(_loadBalancer); + _factory.Verify(x => x.Get(_reRoute, _serviceProviderConfig), Times.Once); + } + + class FakeLoadBalancer : ILoadBalancer + { + public Task> Lease() + { + throw new NotImplementedException(); + } + + public void Release(ServiceHostAndPort hostAndPort) + { + throw new NotImplementedException(); + } + } + + class FakeRoundRobinLoadBalancer : ILoadBalancer + { + public Task> Lease() + { + throw new NotImplementedException(); + } + + public void Release(ServiceHostAndPort hostAndPort) + { + throw new NotImplementedException(); + } + } + } +} diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs index 936e92490..2442e3629 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs @@ -1,209 +1,209 @@ -namespace Ocelot.UnitTests.LoadBalancer -{ - using System.Collections.Generic; - using System.Net.Http; - using Microsoft.AspNetCore.Builder; - using Microsoft.Extensions.DependencyInjection; - using Moq; - using Ocelot.Configuration; - using Ocelot.Configuration.Builder; - using Ocelot.Configuration.Provider; - using Ocelot.DownstreamRouteFinder; - using Ocelot.Errors; - using Ocelot.LoadBalancer.LoadBalancers; - using Ocelot.LoadBalancer.Middleware; - using Ocelot.Logging; - using Ocelot.Responses; - using Ocelot.Values; - using Shouldly; - using TestStack.BDDfy; - using Xunit; - - public class LoadBalancerMiddlewareTests : ServerHostedMiddlewareTest - { - private readonly Mock _loadBalancerHouse; - private readonly Mock _loadBalancer; - private HostAndPort _hostAndPort; - private OkResponse _downstreamRoute; - private ErrorResponse _getLoadBalancerHouseError; - private ErrorResponse _getHostAndPortError; - private HttpRequestMessage _downstreamRequest; - private ServiceProviderConfiguration _config; - - public LoadBalancerMiddlewareTests() - { - _loadBalancerHouse = new Mock(); - _loadBalancer = new Mock(); - _loadBalancerHouse = new Mock(); - _downstreamRequest = new HttpRequestMessage(HttpMethod.Get, ""); - - ScopedRepository - .Setup(sr => sr.Get("DownstreamRequest")) - .Returns(new OkResponse(_downstreamRequest)); - - GivenTheTestServerIsConfigured(); - } - - [Fact] - public void should_call_scoped_data_repository_correctly() - { - var downstreamRoute = new DownstreamRoute(new List(), - new ReRouteBuilder() - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()); - - var serviceProviderConfig = new ServiceProviderConfigurationBuilder() - .Build(); - - this.Given(x => x.GivenTheDownStreamUrlIs("http://my.url/abc?q=123")) - .And(x => GivenTheConfigurationIs(serviceProviderConfig)) - .And(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .And(x => x.GivenTheLoadBalancerHouseReturns()) - .And(x => x.GivenTheLoadBalancerReturns()) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheDownstreamUrlIsReplacedWith("http://127.0.0.1:80/abc?q=123")) - .BDDfy(); - } - - [Fact] - public void should_set_pipeline_error_if_cannot_get_load_balancer() - { - var downstreamRoute = new DownstreamRoute(new List(), - new ReRouteBuilder() - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()); - - var serviceProviderConfig = new ServiceProviderConfigurationBuilder() - .Build(); - - this.Given(x => x.GivenTheDownStreamUrlIs("http://my.url/abc?q=123")) - .And(x => GivenTheConfigurationIs(serviceProviderConfig)) - .And(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .And(x => x.GivenTheLoadBalancerHouseReturnsAnError()) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenAnErrorStatingLoadBalancerCouldNotBeFoundIsSetOnPipeline()) - .BDDfy(); - } - - [Fact] - public void should_set_pipeline_error_if_cannot_get_least() - { - var downstreamRoute = new DownstreamRoute(new List(), - new ReRouteBuilder() - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()); - - var serviceProviderConfig = new ServiceProviderConfigurationBuilder() - .Build(); - - this.Given(x => x.GivenTheDownStreamUrlIs("http://my.url/abc?q=123")) - .And(x => GivenTheConfigurationIs(serviceProviderConfig)) - .And(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .And(x => x.GivenTheLoadBalancerHouseReturns()) - .And(x => x.GivenTheLoadBalancerReturnsAnError()) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenAnErrorStatingHostAndPortCouldNotBeFoundIsSetOnPipeline()) - .BDDfy(); - } - - private void GivenTheConfigurationIs(ServiceProviderConfiguration config) - { - _config = config; - ScopedRepository - .Setup(x => x.Get("ServiceProviderConfiguration")).Returns(new OkResponse(config)); - } - - protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) - { - services.AddSingleton(); - services.AddLogging(); - services.AddSingleton(_loadBalancerHouse.Object); - services.AddSingleton(ScopedRepository.Object); - } - - protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) - { - app.UseLoadBalancingMiddleware(); - } - - private void GivenTheDownStreamUrlIs(string downstreamUrl) - { - _downstreamRequest.RequestUri = new System.Uri(downstreamUrl); - } - - private void GivenTheLoadBalancerReturnsAnError() - { - _getHostAndPortError = new ErrorResponse(new List() { new ServicesAreNullError($"services were null for bah") }); - _loadBalancer - .Setup(x => x.Lease()) - .ReturnsAsync(_getHostAndPortError); - } - - private void GivenTheLoadBalancerReturns() - { - _hostAndPort = new HostAndPort("127.0.0.1", 80); - _loadBalancer - .Setup(x => x.Lease()) - .ReturnsAsync(new OkResponse(_hostAndPort)); - } - - private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) - { - _downstreamRoute = new OkResponse(downstreamRoute); - ScopedRepository - .Setup(x => x.Get(It.IsAny())) - .Returns(_downstreamRoute); - } - - private void GivenTheLoadBalancerHouseReturns() - { - _loadBalancerHouse - .Setup(x => x.Get(It.IsAny(), It.IsAny())) - .ReturnsAsync(new OkResponse(_loadBalancer.Object)); - } - - private void GivenTheLoadBalancerHouseReturnsAnError() - { - _getLoadBalancerHouseError = new ErrorResponse(new List() - { - new UnableToFindLoadBalancerError($"unabe to find load balancer for bah") - }); - - _loadBalancerHouse - .Setup(x => x.Get(It.IsAny(), It.IsAny())) - .ReturnsAsync(_getLoadBalancerHouseError); - } - - private void ThenAnErrorStatingLoadBalancerCouldNotBeFoundIsSetOnPipeline() - { - ScopedRepository - .Verify(x => x.Add("OcelotMiddlewareError", true), Times.Once); - - ScopedRepository - .Verify(x => x.Add("OcelotMiddlewareErrors", _getLoadBalancerHouseError.Errors), Times.Once); - } - - private void ThenAnErrorSayingReleaseFailedIsSetOnThePipeline() - { - ScopedRepository - .Verify(x => x.Add("OcelotMiddlewareError", true), Times.Once); - - ScopedRepository - .Verify(x => x.Add("OcelotMiddlewareErrors", It.IsAny>()), Times.Once); - } - - private void ThenAnErrorStatingHostAndPortCouldNotBeFoundIsSetOnPipeline() - { - ScopedRepository - .Verify(x => x.Add("OcelotMiddlewareError", true), Times.Once); - - ScopedRepository - .Verify(x => x.Add("OcelotMiddlewareErrors", _getHostAndPortError.Errors), Times.Once); - } - - private void ThenTheDownstreamUrlIsReplacedWith(string expectedUri) - { - _downstreamRequest.RequestUri.OriginalString.ShouldBe(expectedUri); - } - } -} \ No newline at end of file +namespace Ocelot.UnitTests.LoadBalancer +{ + using System.Collections.Generic; + using System.Net.Http; + using Microsoft.AspNetCore.Builder; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; + using Ocelot.Configuration.Provider; + using Ocelot.DownstreamRouteFinder; + using Ocelot.Errors; + using Ocelot.LoadBalancer.LoadBalancers; + using Ocelot.LoadBalancer.Middleware; + using Ocelot.Logging; + using Ocelot.Responses; + using Ocelot.Values; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + + public class LoadBalancerMiddlewareTests : ServerHostedMiddlewareTest + { + private readonly Mock _loadBalancerHouse; + private readonly Mock _loadBalancer; + private ServiceHostAndPort _hostAndPort; + private OkResponse _downstreamRoute; + private ErrorResponse _getLoadBalancerHouseError; + private ErrorResponse _getHostAndPortError; + private HttpRequestMessage _downstreamRequest; + private ServiceProviderConfiguration _config; + + public LoadBalancerMiddlewareTests() + { + _loadBalancerHouse = new Mock(); + _loadBalancer = new Mock(); + _loadBalancerHouse = new Mock(); + _downstreamRequest = new HttpRequestMessage(HttpMethod.Get, ""); + + ScopedRepository + .Setup(sr => sr.Get("DownstreamRequest")) + .Returns(new OkResponse(_downstreamRequest)); + + GivenTheTestServerIsConfigured(); + } + + [Fact] + public void should_call_scoped_data_repository_correctly() + { + var downstreamRoute = new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()); + + var serviceProviderConfig = new ServiceProviderConfigurationBuilder() + .Build(); + + this.Given(x => x.GivenTheDownStreamUrlIs("http://my.url/abc?q=123")) + .And(x => GivenTheConfigurationIs(serviceProviderConfig)) + .And(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => x.GivenTheLoadBalancerHouseReturns()) + .And(x => x.GivenTheLoadBalancerReturns()) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheDownstreamUrlIsReplacedWith("http://127.0.0.1:80/abc?q=123")) + .BDDfy(); + } + + [Fact] + public void should_set_pipeline_error_if_cannot_get_load_balancer() + { + var downstreamRoute = new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()); + + var serviceProviderConfig = new ServiceProviderConfigurationBuilder() + .Build(); + + this.Given(x => x.GivenTheDownStreamUrlIs("http://my.url/abc?q=123")) + .And(x => GivenTheConfigurationIs(serviceProviderConfig)) + .And(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => x.GivenTheLoadBalancerHouseReturnsAnError()) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenAnErrorStatingLoadBalancerCouldNotBeFoundIsSetOnPipeline()) + .BDDfy(); + } + + [Fact] + public void should_set_pipeline_error_if_cannot_get_least() + { + var downstreamRoute = new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()); + + var serviceProviderConfig = new ServiceProviderConfigurationBuilder() + .Build(); + + this.Given(x => x.GivenTheDownStreamUrlIs("http://my.url/abc?q=123")) + .And(x => GivenTheConfigurationIs(serviceProviderConfig)) + .And(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => x.GivenTheLoadBalancerHouseReturns()) + .And(x => x.GivenTheLoadBalancerReturnsAnError()) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenAnErrorStatingHostAndPortCouldNotBeFoundIsSetOnPipeline()) + .BDDfy(); + } + + private void GivenTheConfigurationIs(ServiceProviderConfiguration config) + { + _config = config; + ScopedRepository + .Setup(x => x.Get("ServiceProviderConfiguration")).Returns(new OkResponse(config)); + } + + protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + { + services.AddSingleton(); + services.AddLogging(); + services.AddSingleton(_loadBalancerHouse.Object); + services.AddSingleton(ScopedRepository.Object); + } + + protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) + { + app.UseLoadBalancingMiddleware(); + } + + private void GivenTheDownStreamUrlIs(string downstreamUrl) + { + _downstreamRequest.RequestUri = new System.Uri(downstreamUrl); + } + + private void GivenTheLoadBalancerReturnsAnError() + { + _getHostAndPortError = new ErrorResponse(new List() { new ServicesAreNullError($"services were null for bah") }); + _loadBalancer + .Setup(x => x.Lease()) + .ReturnsAsync(_getHostAndPortError); + } + + private void GivenTheLoadBalancerReturns() + { + _hostAndPort = new ServiceHostAndPort("127.0.0.1", 80); + _loadBalancer + .Setup(x => x.Lease()) + .ReturnsAsync(new OkResponse(_hostAndPort)); + } + + private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) + { + _downstreamRoute = new OkResponse(downstreamRoute); + ScopedRepository + .Setup(x => x.Get(It.IsAny())) + .Returns(_downstreamRoute); + } + + private void GivenTheLoadBalancerHouseReturns() + { + _loadBalancerHouse + .Setup(x => x.Get(It.IsAny(), It.IsAny())) + .ReturnsAsync(new OkResponse(_loadBalancer.Object)); + } + + private void GivenTheLoadBalancerHouseReturnsAnError() + { + _getLoadBalancerHouseError = new ErrorResponse(new List() + { + new UnableToFindLoadBalancerError($"unabe to find load balancer for bah") + }); + + _loadBalancerHouse + .Setup(x => x.Get(It.IsAny(), It.IsAny())) + .ReturnsAsync(_getLoadBalancerHouseError); + } + + private void ThenAnErrorStatingLoadBalancerCouldNotBeFoundIsSetOnPipeline() + { + ScopedRepository + .Verify(x => x.Add("OcelotMiddlewareError", true), Times.Once); + + ScopedRepository + .Verify(x => x.Add("OcelotMiddlewareErrors", _getLoadBalancerHouseError.Errors), Times.Once); + } + + private void ThenAnErrorSayingReleaseFailedIsSetOnThePipeline() + { + ScopedRepository + .Verify(x => x.Add("OcelotMiddlewareError", true), Times.Once); + + ScopedRepository + .Verify(x => x.Add("OcelotMiddlewareErrors", It.IsAny>()), Times.Once); + } + + private void ThenAnErrorStatingHostAndPortCouldNotBeFoundIsSetOnPipeline() + { + ScopedRepository + .Verify(x => x.Add("OcelotMiddlewareError", true), Times.Once); + + ScopedRepository + .Verify(x => x.Add("OcelotMiddlewareErrors", _getHostAndPortError.Errors), Times.Once); + } + + private void ThenTheDownstreamUrlIsReplacedWith(string expectedUri) + { + _downstreamRequest.RequestUri.OriginalString.ShouldBe(expectedUri); + } + } +} diff --git a/test/Ocelot.UnitTests/LoadBalancer/NoLoadBalancerTests.cs b/test/Ocelot.UnitTests/LoadBalancer/NoLoadBalancerTests.cs index ac89a6d02..92806cace 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/NoLoadBalancerTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/NoLoadBalancerTests.cs @@ -1,48 +1,48 @@ -using System.Collections.Generic; -using Ocelot.LoadBalancer.LoadBalancers; -using Ocelot.Responses; -using Ocelot.Values; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.LoadBalancer -{ - public class NoLoadBalancerTests - { - private List _services; - private NoLoadBalancer _loadBalancer; - private Response _result; - - [Fact] - public void should_return_host_and_port() - { - var hostAndPort = new HostAndPort("127.0.0.1", 80); - - var services = new List - { - new Service("product", hostAndPort, string.Empty, string.Empty, new string[0]) - }; - this.Given(x => x.GivenServices(services)) - .When(x => x.WhenIGetTheNextHostAndPort()) - .Then(x => x.ThenTheHostAndPortIs(hostAndPort)) - .BDDfy(); - } - - private void GivenServices(List services) - { - _services = services; - } - - private void WhenIGetTheNextHostAndPort() - { - _loadBalancer = new NoLoadBalancer(_services); - _result = _loadBalancer.Lease().Result; - } - - private void ThenTheHostAndPortIs(HostAndPort expected) - { - _result.Data.ShouldBe(expected); - } - } -} \ No newline at end of file +using System.Collections.Generic; +using Ocelot.LoadBalancer.LoadBalancers; +using Ocelot.Responses; +using Ocelot.Values; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.LoadBalancer +{ + public class NoLoadBalancerTests + { + private List _services; + private NoLoadBalancer _loadBalancer; + private Response _result; + + [Fact] + public void should_return_host_and_port() + { + var hostAndPort = new ServiceHostAndPort("127.0.0.1", 80); + + var services = new List + { + new Service("product", hostAndPort, string.Empty, string.Empty, new string[0]) + }; + this.Given(x => x.GivenServices(services)) + .When(x => x.WhenIGetTheNextHostAndPort()) + .Then(x => x.ThenTheHostAndPortIs(hostAndPort)) + .BDDfy(); + } + + private void GivenServices(List services) + { + _services = services; + } + + private void WhenIGetTheNextHostAndPort() + { + _loadBalancer = new NoLoadBalancer(_services); + _result = _loadBalancer.Lease().Result; + } + + private void ThenTheHostAndPortIs(ServiceHostAndPort expected) + { + _result.Data.ShouldBe(expected); + } + } +} diff --git a/test/Ocelot.UnitTests/LoadBalancer/RoundRobinTests.cs b/test/Ocelot.UnitTests/LoadBalancer/RoundRobinTests.cs index 458df6693..2ad1621f6 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/RoundRobinTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/RoundRobinTests.cs @@ -1,69 +1,69 @@ -using System.Collections.Generic; -using System.Diagnostics; -using Ocelot.LoadBalancer.LoadBalancers; -using Ocelot.Responses; -using Ocelot.Values; -using Shouldly; -using TestStack.BDDfy; -using Xunit; -using System.Threading.Tasks; - -namespace Ocelot.UnitTests.LoadBalancer -{ - public class RoundRobinTests - { - private readonly RoundRobin _roundRobin; - private readonly List _services; - private Response _hostAndPort; - - public RoundRobinTests() - { - _services = new List - { - new Service("product", new HostAndPort("127.0.0.1", 5000), string.Empty, string.Empty, new string[0]), - new Service("product", new HostAndPort("127.0.0.1", 5001), string.Empty, string.Empty, new string[0]), - new Service("product", new HostAndPort("127.0.0.1", 5001), string.Empty, string.Empty, new string[0]) - }; - - _roundRobin = new RoundRobin(() => Task.FromResult(_services)); - } - - [Fact] - public void should_get_next_address() - { - this.Given(x => x.GivenIGetTheNextAddress()) - .Then(x => x.ThenTheNextAddressIndexIs(0)) - .Given(x => x.GivenIGetTheNextAddress()) - .Then(x => x.ThenTheNextAddressIndexIs(1)) - .Given(x => x.GivenIGetTheNextAddress()) - .Then(x => x.ThenTheNextAddressIndexIs(2)) - .BDDfy(); - } - - [Fact] - public void should_go_back_to_first_address_after_finished_last() - { - var stopWatch = Stopwatch.StartNew(); - - while (stopWatch.ElapsedMilliseconds < 1000) - { - var address = _roundRobin.Lease().Result; - address.Data.ShouldBe(_services[0].HostAndPort); - address = _roundRobin.Lease().Result; - address.Data.ShouldBe(_services[1].HostAndPort); - address = _roundRobin.Lease().Result; - address.Data.ShouldBe(_services[2].HostAndPort); - } - } - - private void GivenIGetTheNextAddress() - { - _hostAndPort = _roundRobin.Lease().Result; - } - - private void ThenTheNextAddressIndexIs(int index) - { - _hostAndPort.Data.ShouldBe(_services[index].HostAndPort); - } - } -} +using System.Collections.Generic; +using System.Diagnostics; +using Ocelot.LoadBalancer.LoadBalancers; +using Ocelot.Responses; +using Ocelot.Values; +using Shouldly; +using TestStack.BDDfy; +using Xunit; +using System.Threading.Tasks; + +namespace Ocelot.UnitTests.LoadBalancer +{ + public class RoundRobinTests + { + private readonly RoundRobin _roundRobin; + private readonly List _services; + private Response _hostAndPort; + + public RoundRobinTests() + { + _services = new List + { + new Service("product", new ServiceHostAndPort("127.0.0.1", 5000), string.Empty, string.Empty, new string[0]), + new Service("product", new ServiceHostAndPort("127.0.0.1", 5001), string.Empty, string.Empty, new string[0]), + new Service("product", new ServiceHostAndPort("127.0.0.1", 5001), string.Empty, string.Empty, new string[0]) + }; + + _roundRobin = new RoundRobin(() => Task.FromResult(_services)); + } + + [Fact] + public void should_get_next_address() + { + this.Given(x => x.GivenIGetTheNextAddress()) + .Then(x => x.ThenTheNextAddressIndexIs(0)) + .Given(x => x.GivenIGetTheNextAddress()) + .Then(x => x.ThenTheNextAddressIndexIs(1)) + .Given(x => x.GivenIGetTheNextAddress()) + .Then(x => x.ThenTheNextAddressIndexIs(2)) + .BDDfy(); + } + + [Fact] + public void should_go_back_to_first_address_after_finished_last() + { + var stopWatch = Stopwatch.StartNew(); + + while (stopWatch.ElapsedMilliseconds < 1000) + { + var address = _roundRobin.Lease().Result; + address.Data.ShouldBe(_services[0].HostAndPort); + address = _roundRobin.Lease().Result; + address.Data.ShouldBe(_services[1].HostAndPort); + address = _roundRobin.Lease().Result; + address.Data.ShouldBe(_services[2].HostAndPort); + } + } + + private void GivenIGetTheNextAddress() + { + _hostAndPort = _roundRobin.Lease().Result; + } + + private void ThenTheNextAddressIndexIs(int index) + { + _hostAndPort.Data.ShouldBe(_services[index].HostAndPort); + } + } +} diff --git a/test/Ocelot.UnitTests/Middleware/BaseUrlFinderTests.cs b/test/Ocelot.UnitTests/Middleware/BaseUrlFinderTests.cs index c0777a928..d44921e2e 100644 --- a/test/Ocelot.UnitTests/Middleware/BaseUrlFinderTests.cs +++ b/test/Ocelot.UnitTests/Middleware/BaseUrlFinderTests.cs @@ -1,61 +1,61 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Hosting; -using Moq; -using Ocelot.Middleware; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Middleware -{ - public class BaseUrlFinderTests - { - private readonly BaseUrlFinder _baseUrlFinder; - private readonly Mock _webHostBuilder; - private string _result; - - public BaseUrlFinderTests() - { - _webHostBuilder = new Mock(); - _baseUrlFinder = new BaseUrlFinder(_webHostBuilder.Object); - } - - [Fact] - public void should_find_base_url_based_on_webhostbuilder() - { - this.Given(x => GivenTheWebHostBuilderReturns("http://localhost:7000")) - .When(x => WhenIFindTheUrl()) - .Then(x => ThenTheUrlIs("http://localhost:7000")) - .BDDfy(); - } - - [Fact] - public void should_use_default_base_url() - { - this.Given(x => GivenTheWebHostBuilderReturns("")) - .When(x => WhenIFindTheUrl()) - .Then(x => ThenTheUrlIs("http://localhost:5000")) - .BDDfy(); - } - - private void GivenTheWebHostBuilderReturns(string url) - { - _webHostBuilder - .Setup(x => x.GetSetting(WebHostDefaults.ServerUrlsKey)) - .Returns(url); - } - - private void WhenIFindTheUrl() - { - _result = _baseUrlFinder.Find(); - } - - private void ThenTheUrlIs(string expected) - { - _result.ShouldBe(expected); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Moq; +using Ocelot.Middleware; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Middleware +{ + public class BaseUrlFinderTests + { + private readonly BaseUrlFinder _baseUrlFinder; + private readonly Mock _webHostBuilder; + private string _result; + + public BaseUrlFinderTests() + { + _webHostBuilder = new Mock(); + _baseUrlFinder = new BaseUrlFinder(_webHostBuilder.Object); + } + + [Fact] + public void should_find_base_url_based_on_webhostbuilder() + { + this.Given(x => GivenTheWebHostBuilderReturns("http://localhost:7000")) + .When(x => WhenIFindTheUrl()) + .Then(x => ThenTheUrlIs("http://localhost:7000")) + .BDDfy(); + } + + [Fact] + public void should_use_default_base_url() + { + this.Given(x => GivenTheWebHostBuilderReturns("")) + .When(x => WhenIFindTheUrl()) + .Then(x => ThenTheUrlIs("http://localhost:5000")) + .BDDfy(); + } + + private void GivenTheWebHostBuilderReturns(string url) + { + _webHostBuilder + .Setup(x => x.GetSetting(WebHostDefaults.ServerUrlsKey)) + .Returns(url); + } + + private void WhenIFindTheUrl() + { + _result = _baseUrlFinder.Find(); + } + + private void ThenTheUrlIs(string expected) + { + _result.ShouldBe(expected); + } + } +} diff --git a/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj b/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj index f0ad0d8d8..9c0f0a4fe 100644 --- a/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj +++ b/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj @@ -1,59 +1,59 @@ - - - - 0.0.0-dev - netcoreapp2.0 - 2.0.0 - Ocelot.UnitTests - Ocelot.UnitTests - Exe - true - osx.10.11-x64;osx.10.12-x64;win7-x64;win10-x64 - false - false - false - - - - full - True - - - - - - - - - - - - - - - - - PreserveNewest - - - - - - - - - - - - - - - - - - - - - - - + + + + 0.0.0-dev + netcoreapp2.0 + 2.0.0 + Ocelot.UnitTests + Ocelot.UnitTests + Exe + true + osx.10.11-x64;osx.10.12-x64;win7-x64;win10-x64 + false + false + false + + + + full + True + + + + + + + + + + + + + + + + + PreserveNewest + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/Ocelot.UnitTests/Properties/AssemblyInfo.cs b/test/Ocelot.UnitTests/Properties/AssemblyInfo.cs index a2be759ea..b6ccccf5f 100644 --- a/test/Ocelot.UnitTests/Properties/AssemblyInfo.cs +++ b/test/Ocelot.UnitTests/Properties/AssemblyInfo.cs @@ -1,19 +1,19 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Ocelot.UnitTests")] -[assembly: AssemblyTrademark("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("54e84f1a-e525-4443-96ec-039cbd50c263")] +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Ocelot.UnitTests")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("54e84f1a-e525-4443-96ec-039cbd50c263")] diff --git a/test/Ocelot.UnitTests/QueryStrings/AddQueriesToRequestTests.cs b/test/Ocelot.UnitTests/QueryStrings/AddQueriesToRequestTests.cs index ee7769a52..d12d42948 100644 --- a/test/Ocelot.UnitTests/QueryStrings/AddQueriesToRequestTests.cs +++ b/test/Ocelot.UnitTests/QueryStrings/AddQueriesToRequestTests.cs @@ -1,157 +1,157 @@ -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -using Moq; -using Ocelot.Configuration; -using Ocelot.Errors; -using Ocelot.Infrastructure.Claims.Parser; -using Ocelot.QueryStrings; -using Ocelot.Responses; -using Shouldly; -using TestStack.BDDfy; -using Xunit; -using System.Net.Http; -using System; - -namespace Ocelot.UnitTests.QueryStrings -{ - public class AddQueriesToRequestTests - { - private readonly AddQueriesToRequest _addQueriesToRequest; - private readonly HttpRequestMessage _downstreamRequest; - private readonly Mock _parser; - private List _configuration; - private List _claims; - private Response _result; - private Response _claimValue; - - public AddQueriesToRequestTests() - { - _parser = new Mock(); - _addQueriesToRequest = new AddQueriesToRequest(_parser.Object); - _downstreamRequest = new HttpRequestMessage(HttpMethod.Post, "http://my.url/abc?q=123"); - } - - [Fact] - public void should_add_new_queries_to_downstream_request() - { - var claims = new List - { - new Claim("test", "data") - }; - - this.Given( - x => x.GivenAClaimToThing(new List - { - new ClaimToThing("query-key", "", "", 0) - })) - .Given(x => x.GivenClaims(claims)) - .And(x => x.GivenTheClaimParserReturns(new OkResponse("value"))) - .When(x => x.WhenIAddQueriesToTheRequest()) - .Then(x => x.ThenTheResultIsSuccess()) - .And(x => x.ThenTheQueryIsAdded()) - .BDDfy(); - } - - [Fact] - public void should_replace_existing_queries_on_downstream_request() - { - var claims = new List - { - new Claim("test", "data") - }; - - this.Given( - x => x.GivenAClaimToThing(new List - { - new ClaimToThing("query-key", "", "", 0) - })) - .And(x => x.GivenClaims(claims)) - .And(x => x.GivenTheDownstreamRequestHasQueryString("query-key", "initial")) - .And(x => x.GivenTheClaimParserReturns(new OkResponse("value"))) - .When(x => x.WhenIAddQueriesToTheRequest()) - .Then(x => x.ThenTheResultIsSuccess()) - .And(x => x.ThenTheQueryIsAdded()) - .BDDfy(); - } - - [Fact] - public void should_return_error() - { - this.Given( - x => x.GivenAClaimToThing(new List - { - new ClaimToThing("", "", "", 0) - })) - .Given(x => x.GivenClaims(new List())) - .And(x => x.GivenTheClaimParserReturns(new ErrorResponse(new List - { - new AnyError() - }))) - .When(x => x.WhenIAddQueriesToTheRequest()) - .Then(x => x.ThenTheResultIsError()) - .BDDfy(); - } - - private void ThenTheQueryIsAdded() - { - var queries = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(_downstreamRequest.RequestUri.OriginalString); - var query = queries.First(x => x.Key == "query-key"); - query.Value.First().ShouldBe(_claimValue.Data); - } - - private void GivenAClaimToThing(List configuration) - { - _configuration = configuration; - } - - private void GivenClaims(List claims) - { - _claims = claims; - } - - private void GivenTheDownstreamRequestHasQueryString(string key, string value) - { - var newUri = Microsoft.AspNetCore.WebUtilities.QueryHelpers - .AddQueryString(_downstreamRequest.RequestUri.OriginalString, key, value); - - _downstreamRequest.RequestUri = new Uri(newUri); - } - - private void GivenTheClaimParserReturns(Response claimValue) - { - _claimValue = claimValue; - _parser - .Setup( - x => - x.GetValue(It.IsAny>(), - It.IsAny(), - It.IsAny(), - It.IsAny())) - .Returns(_claimValue); - } - - private void WhenIAddQueriesToTheRequest() - { - _result = _addQueriesToRequest.SetQueriesOnDownstreamRequest(_configuration, _claims, _downstreamRequest); - } - - private void ThenTheResultIsSuccess() - { - _result.IsError.ShouldBe(false); - } - - private void ThenTheResultIsError() - { - _result.IsError.ShouldBe(true); - } - - class AnyError : Error - { - public AnyError() - : base("blahh", OcelotErrorCode.UnknownError) - { - } - } - } -} +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using Moq; +using Ocelot.Configuration; +using Ocelot.Errors; +using Ocelot.Infrastructure.Claims.Parser; +using Ocelot.QueryStrings; +using Ocelot.Responses; +using Shouldly; +using TestStack.BDDfy; +using Xunit; +using System.Net.Http; +using System; + +namespace Ocelot.UnitTests.QueryStrings +{ + public class AddQueriesToRequestTests + { + private readonly AddQueriesToRequest _addQueriesToRequest; + private readonly HttpRequestMessage _downstreamRequest; + private readonly Mock _parser; + private List _configuration; + private List _claims; + private Response _result; + private Response _claimValue; + + public AddQueriesToRequestTests() + { + _parser = new Mock(); + _addQueriesToRequest = new AddQueriesToRequest(_parser.Object); + _downstreamRequest = new HttpRequestMessage(HttpMethod.Post, "http://my.url/abc?q=123"); + } + + [Fact] + public void should_add_new_queries_to_downstream_request() + { + var claims = new List + { + new Claim("test", "data") + }; + + this.Given( + x => x.GivenAClaimToThing(new List + { + new ClaimToThing("query-key", "", "", 0) + })) + .Given(x => x.GivenClaims(claims)) + .And(x => x.GivenTheClaimParserReturns(new OkResponse("value"))) + .When(x => x.WhenIAddQueriesToTheRequest()) + .Then(x => x.ThenTheResultIsSuccess()) + .And(x => x.ThenTheQueryIsAdded()) + .BDDfy(); + } + + [Fact] + public void should_replace_existing_queries_on_downstream_request() + { + var claims = new List + { + new Claim("test", "data") + }; + + this.Given( + x => x.GivenAClaimToThing(new List + { + new ClaimToThing("query-key", "", "", 0) + })) + .And(x => x.GivenClaims(claims)) + .And(x => x.GivenTheDownstreamRequestHasQueryString("query-key", "initial")) + .And(x => x.GivenTheClaimParserReturns(new OkResponse("value"))) + .When(x => x.WhenIAddQueriesToTheRequest()) + .Then(x => x.ThenTheResultIsSuccess()) + .And(x => x.ThenTheQueryIsAdded()) + .BDDfy(); + } + + [Fact] + public void should_return_error() + { + this.Given( + x => x.GivenAClaimToThing(new List + { + new ClaimToThing("", "", "", 0) + })) + .Given(x => x.GivenClaims(new List())) + .And(x => x.GivenTheClaimParserReturns(new ErrorResponse(new List + { + new AnyError() + }))) + .When(x => x.WhenIAddQueriesToTheRequest()) + .Then(x => x.ThenTheResultIsError()) + .BDDfy(); + } + + private void ThenTheQueryIsAdded() + { + var queries = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(_downstreamRequest.RequestUri.OriginalString); + var query = queries.First(x => x.Key == "query-key"); + query.Value.First().ShouldBe(_claimValue.Data); + } + + private void GivenAClaimToThing(List configuration) + { + _configuration = configuration; + } + + private void GivenClaims(List claims) + { + _claims = claims; + } + + private void GivenTheDownstreamRequestHasQueryString(string key, string value) + { + var newUri = Microsoft.AspNetCore.WebUtilities.QueryHelpers + .AddQueryString(_downstreamRequest.RequestUri.OriginalString, key, value); + + _downstreamRequest.RequestUri = new Uri(newUri); + } + + private void GivenTheClaimParserReturns(Response claimValue) + { + _claimValue = claimValue; + _parser + .Setup( + x => + x.GetValue(It.IsAny>(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns(_claimValue); + } + + private void WhenIAddQueriesToTheRequest() + { + _result = _addQueriesToRequest.SetQueriesOnDownstreamRequest(_configuration, _claims, _downstreamRequest); + } + + private void ThenTheResultIsSuccess() + { + _result.IsError.ShouldBe(false); + } + + private void ThenTheResultIsError() + { + _result.IsError.ShouldBe(true); + } + + class AnyError : Error + { + public AnyError() + : base("blahh", OcelotErrorCode.UnknownError) + { + } + } + } +} diff --git a/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs index a4a3b1975..b4c851d25 100644 --- a/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs @@ -1,97 +1,97 @@ -namespace Ocelot.UnitTests.QueryStrings -{ - using System.Collections.Generic; - using System.Net.Http; - using Microsoft.Extensions.DependencyInjection; - using Moq; - using Ocelot.Configuration; - using Ocelot.Configuration.Builder; - using Ocelot.DownstreamRouteFinder; - using Ocelot.DownstreamRouteFinder.UrlMatcher; - using Ocelot.Logging; - using Ocelot.QueryStrings; - using Ocelot.QueryStrings.Middleware; - using Ocelot.Responses; - using TestStack.BDDfy; - using Xunit; - using System.Security.Claims; - using Microsoft.AspNetCore.Builder; - - public class QueryStringBuilderMiddlewareTests : ServerHostedMiddlewareTest - { - private readonly Mock _addQueries; - private readonly HttpRequestMessage _downstreamRequest; - private Response _downstreamRoute; - - public QueryStringBuilderMiddlewareTests() - { - _addQueries = new Mock(); - - _downstreamRequest = new HttpRequestMessage(); - ScopedRepository.Setup(sr => sr.Get("DownstreamRequest")) - .Returns(new OkResponse(_downstreamRequest)); - - GivenTheTestServerIsConfigured(); - } - - [Fact] - public void should_call_add_queries_correctly() - { - var downstreamRoute = new DownstreamRoute(new List(), - new ReRouteBuilder() - .WithDownstreamPathTemplate("any old string") - .WithClaimsToQueries(new List - { - new ClaimToThing("UserId", "Subject", "", 0) - }) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()); - - this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .And(x => x.GivenTheAddHeadersToRequestReturnsOk()) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheAddQueriesToRequestIsCalledCorrectly()) - .BDDfy(); - } - - protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) - { - services.AddSingleton(); - services.AddLogging(); - services.AddSingleton(_addQueries.Object); - services.AddSingleton(ScopedRepository.Object); - } - - protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) - { - app.UseQueryStringBuilderMiddleware(); - } - - private void GivenTheAddHeadersToRequestReturnsOk() - { - _addQueries - .Setup(x => x.SetQueriesOnDownstreamRequest( - It.IsAny>(), - It.IsAny>(), - It.IsAny())) - .Returns(new OkResponse()); - } - - private void ThenTheAddQueriesToRequestIsCalledCorrectly() - { - _addQueries - .Verify(x => x.SetQueriesOnDownstreamRequest( - It.IsAny>(), - It.IsAny>(), - _downstreamRequest), Times.Once); - } - - private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) - { - _downstreamRoute = new OkResponse(downstreamRoute); - ScopedRepository - .Setup(x => x.Get(It.IsAny())) - .Returns(_downstreamRoute); - } - } -} +namespace Ocelot.UnitTests.QueryStrings +{ + using System.Collections.Generic; + using System.Net.Http; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; + using Ocelot.DownstreamRouteFinder; + using Ocelot.DownstreamRouteFinder.UrlMatcher; + using Ocelot.Logging; + using Ocelot.QueryStrings; + using Ocelot.QueryStrings.Middleware; + using Ocelot.Responses; + using TestStack.BDDfy; + using Xunit; + using System.Security.Claims; + using Microsoft.AspNetCore.Builder; + + public class QueryStringBuilderMiddlewareTests : ServerHostedMiddlewareTest + { + private readonly Mock _addQueries; + private readonly HttpRequestMessage _downstreamRequest; + private Response _downstreamRoute; + + public QueryStringBuilderMiddlewareTests() + { + _addQueries = new Mock(); + + _downstreamRequest = new HttpRequestMessage(); + ScopedRepository.Setup(sr => sr.Get("DownstreamRequest")) + .Returns(new OkResponse(_downstreamRequest)); + + GivenTheTestServerIsConfigured(); + } + + [Fact] + public void should_call_add_queries_correctly() + { + var downstreamRoute = new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithClaimsToQueries(new List + { + new ClaimToThing("UserId", "Subject", "", 0) + }) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()); + + this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => x.GivenTheAddHeadersToRequestReturnsOk()) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheAddQueriesToRequestIsCalledCorrectly()) + .BDDfy(); + } + + protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + { + services.AddSingleton(); + services.AddLogging(); + services.AddSingleton(_addQueries.Object); + services.AddSingleton(ScopedRepository.Object); + } + + protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) + { + app.UseQueryStringBuilderMiddleware(); + } + + private void GivenTheAddHeadersToRequestReturnsOk() + { + _addQueries + .Setup(x => x.SetQueriesOnDownstreamRequest( + It.IsAny>(), + It.IsAny>(), + It.IsAny())) + .Returns(new OkResponse()); + } + + private void ThenTheAddQueriesToRequestIsCalledCorrectly() + { + _addQueries + .Verify(x => x.SetQueriesOnDownstreamRequest( + It.IsAny>(), + It.IsAny>(), + _downstreamRequest), Times.Once); + } + + private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) + { + _downstreamRoute = new OkResponse(downstreamRoute); + ScopedRepository + .Setup(x => x.Get(It.IsAny())) + .Returns(_downstreamRoute); + } + } +} diff --git a/test/Ocelot.UnitTests/Raft/OcelotFiniteStateMachineTests.cs b/test/Ocelot.UnitTests/Raft/OcelotFiniteStateMachineTests.cs index 1451e8392..669b6bf05 100644 --- a/test/Ocelot.UnitTests/Raft/OcelotFiniteStateMachineTests.cs +++ b/test/Ocelot.UnitTests/Raft/OcelotFiniteStateMachineTests.cs @@ -1,45 +1,45 @@ -using Moq; -using Ocelot.Configuration.Setter; -using Ocelot.Raft; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Raft -{ - public class OcelotFiniteStateMachineTests - { - private UpdateFileConfiguration _command; - private OcelotFiniteStateMachine _fsm; - private Mock _setter; - - public OcelotFiniteStateMachineTests() - { - _setter = new Mock(); - _fsm = new OcelotFiniteStateMachine(_setter.Object); - } - - [Fact] - public void should_handle_update_file_configuration_command() - { - this.Given(x => GivenACommand(new UpdateFileConfiguration(new Ocelot.Configuration.File.FileConfiguration()))) - .When(x => WhenTheCommandIsHandled()) - .Then(x => ThenTheStateIsUpdated()) - .BDDfy(); - } - - private void GivenACommand(UpdateFileConfiguration command) - { - _command = command; - } - - private void WhenTheCommandIsHandled() - { - _fsm.Handle(new Rafty.Log.LogEntry(_command, _command.GetType(), 0)); - } - - private void ThenTheStateIsUpdated() - { - _setter.Verify(x => x.Set(_command.Configuration), Times.Once); - } - } +using Moq; +using Ocelot.Configuration.Setter; +using Ocelot.Raft; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Raft +{ + public class OcelotFiniteStateMachineTests + { + private UpdateFileConfiguration _command; + private OcelotFiniteStateMachine _fsm; + private Mock _setter; + + public OcelotFiniteStateMachineTests() + { + _setter = new Mock(); + _fsm = new OcelotFiniteStateMachine(_setter.Object); + } + + [Fact] + public void should_handle_update_file_configuration_command() + { + this.Given(x => GivenACommand(new UpdateFileConfiguration(new Ocelot.Configuration.File.FileConfiguration()))) + .When(x => WhenTheCommandIsHandled()) + .Then(x => ThenTheStateIsUpdated()) + .BDDfy(); + } + + private void GivenACommand(UpdateFileConfiguration command) + { + _command = command; + } + + private void WhenTheCommandIsHandled() + { + _fsm.Handle(new Rafty.Log.LogEntry(_command, _command.GetType(), 0)); + } + + private void ThenTheStateIsUpdated() + { + _setter.Verify(x => x.Set(_command.Configuration), Times.Once); + } + } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs b/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs index e832760bf..a01a66cda 100644 --- a/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs @@ -1,128 +1,128 @@ -namespace Ocelot.UnitTests.RateLimit -{ - using System.Collections.Generic; - using System.Net.Http; - using Microsoft.AspNetCore.Hosting; - using Microsoft.AspNetCore.Builder; - using Microsoft.AspNetCore.Http; - using Microsoft.Extensions.DependencyInjection; - using Moq; - using Ocelot.Configuration; - using Ocelot.Configuration.Builder; - using Ocelot.DownstreamRouteFinder; - using Ocelot.Logging; - using Ocelot.RateLimit; - using Ocelot.RateLimit.Middleware; - using Ocelot.Responses; - using Shouldly; - using TestStack.BDDfy; - using Xunit; - - public class ClientRateLimitMiddlewareTests : ServerHostedMiddlewareTest - { - private OkResponse _downstreamRoute; - private int responseStatusCode; - - public ClientRateLimitMiddlewareTests() - { - GivenTheTestServerIsConfigured(); - } - - [Fact] - public void should_call_middleware_and_ratelimiting() - { - var downstreamRoute = new DownstreamRoute(new List(), - new ReRouteBuilder().WithEnableRateLimiting(true).WithRateLimitOptions( - new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List(), false, "", "", new Ocelot.Configuration.RateLimitRule("1s", 100, 3), 429)) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()); - - this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .When(x => x.WhenICallTheMiddlewareMultipleTime(2)) - .Then(x => x.ThenresponseStatusCodeIs200()) - .When(x => x.WhenICallTheMiddlewareMultipleTime(2)) - .Then(x => x.ThenresponseStatusCodeIs429()) - .BDDfy(); - } - - [Fact] - public void should_call_middleware_withWhitelistClient() - { - var downstreamRoute = new DownstreamRoute(new List(), - new ReRouteBuilder().WithEnableRateLimiting(true).WithRateLimitOptions( - new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List() { "ocelotclient2" }, false, "", "", new RateLimitRule( "1s", 100,3),429)) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()); - - this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .When(x => x.WhenICallTheMiddlewareWithWhiteClient()) - .Then(x => x.ThenresponseStatusCodeIs200()) - .BDDfy(); - } - - protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) - { - services.AddSingleton(); - services.AddLogging(); - services.AddMemoryCache(); - services.AddSingleton(); - services.AddSingleton(ScopedRepository.Object); - } - - protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) - { - app.UseRateLimiting(); - app.Run(async context => - { - context.Response.StatusCode = 200; - await context.Response.WriteAsync("This is ratelimit test"); - }); - } - - private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) - { - _downstreamRoute = new OkResponse(downstreamRoute); - ScopedRepository - .Setup(x => x.Get(It.IsAny())) - .Returns(_downstreamRoute); - } - - private void WhenICallTheMiddlewareMultipleTime(int times) - { - var clientId = "ocelotclient1"; - - for (int i = 0; i < times; i++) - { - var request = new HttpRequestMessage(new HttpMethod("GET"), Url); - request.Headers.Add("ClientId", clientId); - - var response = Client.SendAsync(request); - responseStatusCode = (int)response.Result.StatusCode; - } - } - - private void WhenICallTheMiddlewareWithWhiteClient() - { - var clientId = "ocelotclient2"; - - for (int i = 0; i < 10; i++) - { - var request = new HttpRequestMessage(new HttpMethod("GET"), Url); - request.Headers.Add("ClientId", clientId); - - var response = Client.SendAsync(request); - responseStatusCode = (int)response.Result.StatusCode; - } - } - - private void ThenresponseStatusCodeIs429() - { - responseStatusCode.ShouldBe(429); - } - - private void ThenresponseStatusCodeIs200() - { - responseStatusCode.ShouldBe(200); - } - } -} +namespace Ocelot.UnitTests.RateLimit +{ + using System.Collections.Generic; + using System.Net.Http; + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; + using Ocelot.DownstreamRouteFinder; + using Ocelot.Logging; + using Ocelot.RateLimit; + using Ocelot.RateLimit.Middleware; + using Ocelot.Responses; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + + public class ClientRateLimitMiddlewareTests : ServerHostedMiddlewareTest + { + private OkResponse _downstreamRoute; + private int responseStatusCode; + + public ClientRateLimitMiddlewareTests() + { + GivenTheTestServerIsConfigured(); + } + + [Fact] + public void should_call_middleware_and_ratelimiting() + { + var downstreamRoute = new DownstreamRoute(new List(), + new ReRouteBuilder().WithEnableRateLimiting(true).WithRateLimitOptions( + new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List(), false, "", "", new Ocelot.Configuration.RateLimitRule("1s", 100, 3), 429)) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()); + + this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .When(x => x.WhenICallTheMiddlewareMultipleTime(2)) + .Then(x => x.ThenresponseStatusCodeIs200()) + .When(x => x.WhenICallTheMiddlewareMultipleTime(2)) + .Then(x => x.ThenresponseStatusCodeIs429()) + .BDDfy(); + } + + [Fact] + public void should_call_middleware_withWhitelistClient() + { + var downstreamRoute = new DownstreamRoute(new List(), + new ReRouteBuilder().WithEnableRateLimiting(true).WithRateLimitOptions( + new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List() { "ocelotclient2" }, false, "", "", new RateLimitRule( "1s", 100,3),429)) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()); + + this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .When(x => x.WhenICallTheMiddlewareWithWhiteClient()) + .Then(x => x.ThenresponseStatusCodeIs200()) + .BDDfy(); + } + + protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + { + services.AddSingleton(); + services.AddLogging(); + services.AddMemoryCache(); + services.AddSingleton(); + services.AddSingleton(ScopedRepository.Object); + } + + protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) + { + app.UseRateLimiting(); + app.Run(async context => + { + context.Response.StatusCode = 200; + await context.Response.WriteAsync("This is ratelimit test"); + }); + } + + private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) + { + _downstreamRoute = new OkResponse(downstreamRoute); + ScopedRepository + .Setup(x => x.Get(It.IsAny())) + .Returns(_downstreamRoute); + } + + private void WhenICallTheMiddlewareMultipleTime(int times) + { + var clientId = "ocelotclient1"; + + for (int i = 0; i < times; i++) + { + var request = new HttpRequestMessage(new HttpMethod("GET"), Url); + request.Headers.Add("ClientId", clientId); + + var response = Client.SendAsync(request); + responseStatusCode = (int)response.Result.StatusCode; + } + } + + private void WhenICallTheMiddlewareWithWhiteClient() + { + var clientId = "ocelotclient2"; + + for (int i = 0; i < 10; i++) + { + var request = new HttpRequestMessage(new HttpMethod("GET"), Url); + request.Headers.Add("ClientId", clientId); + + var response = Client.SendAsync(request); + responseStatusCode = (int)response.Result.StatusCode; + } + } + + private void ThenresponseStatusCodeIs429() + { + responseStatusCode.ShouldBe(429); + } + + private void ThenresponseStatusCodeIs200() + { + responseStatusCode.ShouldBe(200); + } + } +} diff --git a/test/Ocelot.UnitTests/Repository/ScopedRequestDataRepositoryTests.cs b/test/Ocelot.UnitTests/Repository/ScopedRequestDataRepositoryTests.cs index 030c38349..2ecfb2174 100644 --- a/test/Ocelot.UnitTests/Repository/ScopedRequestDataRepositoryTests.cs +++ b/test/Ocelot.UnitTests/Repository/ScopedRequestDataRepositoryTests.cs @@ -1,78 +1,78 @@ -using Microsoft.AspNetCore.Http; -using Ocelot.Infrastructure.RequestData; -using Ocelot.Responses; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Repository -{ - public class ScopedRequestDataRepositoryTests - { - private IRequestScopedDataRepository _requestScopedDataRepository; - private IHttpContextAccessor _httpContextAccesor; - private string _key; - private object _toAdd; - private Response _result; - - public ScopedRequestDataRepositoryTests() - { - _httpContextAccesor = new HttpContextAccessor(); - _httpContextAccesor.HttpContext = new DefaultHttpContext(); - _requestScopedDataRepository = new HttpDataRepository(_httpContextAccesor); - } - - [Fact] - public void should_add_item() - { - this.Given(x => x.GivenIHaveAnItemToAdd("blahh", new [] {1,2,3,4})) - .When(x => x.WhenIAddTheItem()) - .Then(x => x.ThenTheItemIsAdded()) - .BDDfy(); - } - - [Fact] - public void should_get_item() - { - this.Given(x => x.GivenThereIsAnItemInTheContext("chest")) - .When(x => x.WhenIGetTheItem()) - .Then(x => x.ThenTheItemIsReturned()) - .BDDfy(); - } - - private void ThenTheItemIsReturned() - { - _result.IsError.ShouldBeFalse(); - _result.Data.ShouldNotBeNull(); - } - - private void WhenIGetTheItem() - { - _result = _requestScopedDataRepository.Get(_key); - } - - private void GivenThereIsAnItemInTheContext(string key) - { - _key = key; - var data = new[] {5435345}; - _httpContextAccesor.HttpContext.Items.Add(key, data); - } - - private void GivenIHaveAnItemToAdd(string key, object toAdd) - { - _key = key; - _toAdd = toAdd; - } - - private void WhenIAddTheItem() - { - _requestScopedDataRepository.Add(_key, _toAdd); - } - - private void ThenTheItemIsAdded() - { - object obj; - _httpContextAccesor.HttpContext.Items.TryGetValue(_key, out obj).ShouldBeTrue(); - } - } -} +using Microsoft.AspNetCore.Http; +using Ocelot.Infrastructure.RequestData; +using Ocelot.Responses; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Repository +{ + public class ScopedRequestDataRepositoryTests + { + private IRequestScopedDataRepository _requestScopedDataRepository; + private IHttpContextAccessor _httpContextAccesor; + private string _key; + private object _toAdd; + private Response _result; + + public ScopedRequestDataRepositoryTests() + { + _httpContextAccesor = new HttpContextAccessor(); + _httpContextAccesor.HttpContext = new DefaultHttpContext(); + _requestScopedDataRepository = new HttpDataRepository(_httpContextAccesor); + } + + [Fact] + public void should_add_item() + { + this.Given(x => x.GivenIHaveAnItemToAdd("blahh", new [] {1,2,3,4})) + .When(x => x.WhenIAddTheItem()) + .Then(x => x.ThenTheItemIsAdded()) + .BDDfy(); + } + + [Fact] + public void should_get_item() + { + this.Given(x => x.GivenThereIsAnItemInTheContext("chest")) + .When(x => x.WhenIGetTheItem()) + .Then(x => x.ThenTheItemIsReturned()) + .BDDfy(); + } + + private void ThenTheItemIsReturned() + { + _result.IsError.ShouldBeFalse(); + _result.Data.ShouldNotBeNull(); + } + + private void WhenIGetTheItem() + { + _result = _requestScopedDataRepository.Get(_key); + } + + private void GivenThereIsAnItemInTheContext(string key) + { + _key = key; + var data = new[] {5435345}; + _httpContextAccesor.HttpContext.Items.Add(key, data); + } + + private void GivenIHaveAnItemToAdd(string key, object toAdd) + { + _key = key; + _toAdd = toAdd; + } + + private void WhenIAddTheItem() + { + _requestScopedDataRepository.Add(_key, _toAdd); + } + + private void ThenTheItemIsAdded() + { + object obj; + _httpContextAccesor.HttpContext.Items.TryGetValue(_key, out obj).ShouldBeTrue(); + } + } +} diff --git a/test/Ocelot.UnitTests/Request/DownstreamRequestInitialiserMiddlewareTests.cs b/test/Ocelot.UnitTests/Request/DownstreamRequestInitialiserMiddlewareTests.cs index 91c1d0113..0e62e6cda 100644 --- a/test/Ocelot.UnitTests/Request/DownstreamRequestInitialiserMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Request/DownstreamRequestInitialiserMiddlewareTests.cs @@ -1,142 +1,142 @@ -namespace Ocelot.UnitTests.Request -{ - using System.Net.Http; - using Microsoft.AspNetCore.Http; - using Moq; - using Ocelot.Logging; - using Ocelot.Request.Mapper; - using Ocelot.Request.Middleware; - using Ocelot.Infrastructure.RequestData; - using TestStack.BDDfy; - using Xunit; - using Ocelot.Responses; - - public class DownstreamRequestInitialiserMiddlewareTests - { - readonly DownstreamRequestInitialiserMiddleware _middleware; - - readonly Mock _httpContext; - - readonly Mock _httpRequest; - - readonly Mock _next; - - readonly Mock _requestMapper; - - readonly Mock _repo; - - readonly Mock _loggerFactory; - - readonly Mock _logger; - - Response _mappedRequest; - - public DownstreamRequestInitialiserMiddlewareTests() - { - - _httpContext = new Mock(); - _httpRequest = new Mock(); - _requestMapper = new Mock(); - _repo = new Mock(); - _next = new Mock(); - _logger = new Mock(); - - _loggerFactory = new Mock(); - _loggerFactory - .Setup(lf => lf.CreateLogger()) - .Returns(_logger.Object); - - _middleware = new DownstreamRequestInitialiserMiddleware( - _next.Object, - _loggerFactory.Object, - _repo.Object, - _requestMapper.Object); - } - - [Fact] - public void Should_handle_valid_httpRequest() - { - this.Given(_ => GivenTheHttpContextContainsARequest()) - .And(_ => GivenTheMapperWillReturnAMappedRequest()) - .When(_ => WhenTheMiddlewareIsInvoked()) - .Then(_ => ThenTheContexRequestIsMappedToADownstreamRequest()) - .And(_ => ThenTheDownstreamRequestIsStored()) - .And(_ => ThenTheNextMiddlewareIsInvoked()) - .BDDfy(); - } - - [Fact] - public void Should_handle_mapping_failure() - { - this.Given(_ => GivenTheHttpContextContainsARequest()) - .And(_ => GivenTheMapperWillReturnAnError()) - .When(_ => WhenTheMiddlewareIsInvoked()) - .And(_ => ThenTheDownstreamRequestIsNotStored()) - .And(_ => ThenAPipelineErrorIsStored()) - .And(_ => ThenTheNextMiddlewareIsNotInvoked()) - .BDDfy(); - } - - private void GivenTheHttpContextContainsARequest() - { - _httpContext - .Setup(hc => hc.Request) - .Returns(_httpRequest.Object); - } - - private void GivenTheMapperWillReturnAMappedRequest() - { - _mappedRequest = new OkResponse(new HttpRequestMessage()); - - _requestMapper - .Setup(rm => rm.Map(It.IsAny())) - .ReturnsAsync(_mappedRequest); - } - - private void GivenTheMapperWillReturnAnError() - { - _mappedRequest = new ErrorResponse(new UnmappableRequestError(new System.Exception("boooom!"))); - - _requestMapper - .Setup(rm => rm.Map(It.IsAny())) - .ReturnsAsync(_mappedRequest); - } - - private void WhenTheMiddlewareIsInvoked() - { - _middleware.Invoke(_httpContext.Object).GetAwaiter().GetResult(); - } - - private void ThenTheContexRequestIsMappedToADownstreamRequest() - { - _requestMapper.Verify(rm => rm.Map(_httpRequest.Object), Times.Once); - } - - private void ThenTheDownstreamRequestIsStored() - { - _repo.Verify(r => r.Add("DownstreamRequest", _mappedRequest.Data), Times.Once); - } - - private void ThenTheDownstreamRequestIsNotStored() - { - _repo.Verify(r => r.Add("DownstreamRequest", It.IsAny()), Times.Never); - } - - private void ThenAPipelineErrorIsStored() - { - _repo.Verify(r => r.Add("OcelotMiddlewareError", true), Times.Once); - _repo.Verify(r => r.Add("OcelotMiddlewareErrors", _mappedRequest.Errors), Times.Once); - } - - private void ThenTheNextMiddlewareIsInvoked() - { - _next.Verify(n => n(_httpContext.Object), Times.Once); - } - - private void ThenTheNextMiddlewareIsNotInvoked() - { - _next.Verify(n => n(It.IsAny()), Times.Never); - } - - } -} +namespace Ocelot.UnitTests.Request +{ + using System.Net.Http; + using Microsoft.AspNetCore.Http; + using Moq; + using Ocelot.Logging; + using Ocelot.Request.Mapper; + using Ocelot.Request.Middleware; + using Ocelot.Infrastructure.RequestData; + using TestStack.BDDfy; + using Xunit; + using Ocelot.Responses; + + public class DownstreamRequestInitialiserMiddlewareTests + { + readonly DownstreamRequestInitialiserMiddleware _middleware; + + readonly Mock _httpContext; + + readonly Mock _httpRequest; + + readonly Mock _next; + + readonly Mock _requestMapper; + + readonly Mock _repo; + + readonly Mock _loggerFactory; + + readonly Mock _logger; + + Response _mappedRequest; + + public DownstreamRequestInitialiserMiddlewareTests() + { + + _httpContext = new Mock(); + _httpRequest = new Mock(); + _requestMapper = new Mock(); + _repo = new Mock(); + _next = new Mock(); + _logger = new Mock(); + + _loggerFactory = new Mock(); + _loggerFactory + .Setup(lf => lf.CreateLogger()) + .Returns(_logger.Object); + + _middleware = new DownstreamRequestInitialiserMiddleware( + _next.Object, + _loggerFactory.Object, + _repo.Object, + _requestMapper.Object); + } + + [Fact] + public void Should_handle_valid_httpRequest() + { + this.Given(_ => GivenTheHttpContextContainsARequest()) + .And(_ => GivenTheMapperWillReturnAMappedRequest()) + .When(_ => WhenTheMiddlewareIsInvoked()) + .Then(_ => ThenTheContexRequestIsMappedToADownstreamRequest()) + .And(_ => ThenTheDownstreamRequestIsStored()) + .And(_ => ThenTheNextMiddlewareIsInvoked()) + .BDDfy(); + } + + [Fact] + public void Should_handle_mapping_failure() + { + this.Given(_ => GivenTheHttpContextContainsARequest()) + .And(_ => GivenTheMapperWillReturnAnError()) + .When(_ => WhenTheMiddlewareIsInvoked()) + .And(_ => ThenTheDownstreamRequestIsNotStored()) + .And(_ => ThenAPipelineErrorIsStored()) + .And(_ => ThenTheNextMiddlewareIsNotInvoked()) + .BDDfy(); + } + + private void GivenTheHttpContextContainsARequest() + { + _httpContext + .Setup(hc => hc.Request) + .Returns(_httpRequest.Object); + } + + private void GivenTheMapperWillReturnAMappedRequest() + { + _mappedRequest = new OkResponse(new HttpRequestMessage()); + + _requestMapper + .Setup(rm => rm.Map(It.IsAny())) + .ReturnsAsync(_mappedRequest); + } + + private void GivenTheMapperWillReturnAnError() + { + _mappedRequest = new ErrorResponse(new UnmappableRequestError(new System.Exception("boooom!"))); + + _requestMapper + .Setup(rm => rm.Map(It.IsAny())) + .ReturnsAsync(_mappedRequest); + } + + private void WhenTheMiddlewareIsInvoked() + { + _middleware.Invoke(_httpContext.Object).GetAwaiter().GetResult(); + } + + private void ThenTheContexRequestIsMappedToADownstreamRequest() + { + _requestMapper.Verify(rm => rm.Map(_httpRequest.Object), Times.Once); + } + + private void ThenTheDownstreamRequestIsStored() + { + _repo.Verify(r => r.Add("DownstreamRequest", _mappedRequest.Data), Times.Once); + } + + private void ThenTheDownstreamRequestIsNotStored() + { + _repo.Verify(r => r.Add("DownstreamRequest", It.IsAny()), Times.Never); + } + + private void ThenAPipelineErrorIsStored() + { + _repo.Verify(r => r.Add("OcelotMiddlewareError", true), Times.Once); + _repo.Verify(r => r.Add("OcelotMiddlewareErrors", _mappedRequest.Errors), Times.Once); + } + + private void ThenTheNextMiddlewareIsInvoked() + { + _next.Verify(n => n(_httpContext.Object), Times.Once); + } + + private void ThenTheNextMiddlewareIsNotInvoked() + { + _next.Verify(n => n(It.IsAny()), Times.Never); + } + + } +} diff --git a/test/Ocelot.UnitTests/Request/Mapper/RequestMapperTests.cs b/test/Ocelot.UnitTests/Request/Mapper/RequestMapperTests.cs index ca086c22f..e30a772ad 100644 --- a/test/Ocelot.UnitTests/Request/Mapper/RequestMapperTests.cs +++ b/test/Ocelot.UnitTests/Request/Mapper/RequestMapperTests.cs @@ -1,287 +1,287 @@ -namespace Ocelot.UnitTests.Request.Mapper -{ - using System.Collections.Generic; - using System.Linq; - using System.Net.Http; - - using Microsoft.AspNetCore.Http; - using Microsoft.AspNetCore.Http.Internal; - using Microsoft.Extensions.Primitives; - using Ocelot.Request.Mapper; - using Ocelot.Responses; - using TestStack.BDDfy; - using Xunit; - using Shouldly; - using System; - using System.IO; - using System.Text; - - public class RequestMapperTests - { - readonly HttpRequest _inputRequest; - - readonly RequestMapper _requestMapper; - - Response _mappedRequest; - - List> _inputHeaders = null; - - public RequestMapperTests() - { - _inputRequest = new DefaultHttpRequest(new DefaultHttpContext()); - - _requestMapper = new RequestMapper(); - } - - [Theory] - [InlineData("https", "my.url:123", "/abc/DEF", "?a=1&b=2", "https://my.url:123/abc/DEF?a=1&b=2")] - [InlineData("http", "blah.com", "/d ef", "?abc=123", "http://blah.com/d%20ef?abc=123")] // note! the input is encoded when building the input request - [InlineData("http", "myusername:mypassword@abc.co.uk", null, null, "http://myusername:mypassword@abc.co.uk/")] - [InlineData("http", "點看.com", null, null, "http://xn--c1yn36f.com/")] - [InlineData("http", "xn--c1yn36f.com", null, null, "http://xn--c1yn36f.com/")] - public void Should_map_valid_request_uri(string scheme, string host, string path, string queryString, string expectedUri) - { - this.Given(_ => GivenTheInputRequestHasMethod("GET")) - .And(_ => GivenTheInputRequestHasScheme(scheme)) - .And(_ => GivenTheInputRequestHasHost(host)) - .And(_ => GivenTheInputRequestHasPath(path)) - .And(_ => GivenTheInputRequestHasQueryString(queryString)) - .When(_ => WhenMapped()) - .Then(_ => ThenNoErrorIsReturned()) - .And(_ => ThenTheMappedRequestHasUri(expectedUri)) - .BDDfy(); - } - - [Theory] - [InlineData("ftp", "google.com", "/abc/DEF", "?a=1&b=2")] - public void Should_error_on_unsupported_request_uri(string scheme, string host, string path, string queryString) - { - this.Given(_ => GivenTheInputRequestHasMethod("GET")) - .And(_ => GivenTheInputRequestHasScheme(scheme)) - .And(_ => GivenTheInputRequestHasHost(host)) - .And(_ => GivenTheInputRequestHasPath(path)) - .And(_ => GivenTheInputRequestHasQueryString(queryString)) - .When(_ => WhenMapped()) - .Then(_ => ThenAnErrorIsReturned()) - .And(_ => ThenTheMappedRequestIsNull()) - .BDDfy(); - } - - [Theory] - [InlineData("GET")] - [InlineData("POST")] - [InlineData("WHATEVER")] - public void Should_map_method(string method) - { - this.Given(_ => GivenTheInputRequestHasMethod(method)) - .And(_ => GivenTheInputRequestHasAValidUri()) - .When(_ => WhenMapped()) - .Then(_ => ThenNoErrorIsReturned()) - .And(_ => ThenTheMappedRequestHasMethod(method)) - .BDDfy(); - } - - [Fact] - public void Should_map_all_headers() - { - this.Given(_ => GivenTheInputRequestHasHeaders()) - .And(_ => GivenTheInputRequestHasMethod("GET")) - .And(_ => GivenTheInputRequestHasAValidUri()) - .When(_ => WhenMapped()) - .Then(_ => ThenNoErrorIsReturned()) - .And(_ => ThenTheMappedRequestHasEachHeader()) - .BDDfy(); - } - - [Fact] - public void Should_handle_no_headers() - { - this.Given(_ => GivenTheInputRequestHasNoHeaders()) - .And(_ => GivenTheInputRequestHasMethod("GET")) - .And(_ => GivenTheInputRequestHasAValidUri()) - .When(_ => WhenMapped()) - .Then(_ => ThenNoErrorIsReturned()) - .And(_ => ThenTheMappedRequestHasNoHeaders()) - .BDDfy(); - } - - [Fact] - public void Should_map_content() - { - this.Given(_ => GivenTheInputRequestHasContent("This is my content")) - .And(_ => GivenTheInputRequestHasMethod("GET")) - .And(_ => GivenTheInputRequestHasAValidUri()) - .When(_ => WhenMapped()) - .Then(_ => ThenNoErrorIsReturned()) - .And(_ => ThenTheMappedRequestHasContent("This is my content")) - .BDDfy(); - } - - [Fact] - public void Should_map_content_type_header() - { - this.Given(_ => GivenTheInputRequestHasContent("This is my content")) - .And(_ => GivenTheContentTypeIs("application/json")) - .And(_ => GivenTheInputRequestHasMethod("GET")) - .And(_ => GivenTheInputRequestHasAValidUri()) - .When(_ => WhenMapped()) - .Then(_ => ThenNoErrorIsReturned()) - .And(_ => ThenTheMappedRequestHasContentTypeHeader("application/json")) - .And(_ => ThenTheMappedRequestHasContentSize("This is my content".Length)) - .BDDfy(); - } - - private void GivenTheContentTypeIs(string contentType) - { - _inputRequest.ContentType = contentType; - } - - private void ThenTheMappedRequestHasContentTypeHeader(string expected) - { - _mappedRequest.Data.Content.Headers.ContentType.MediaType.ShouldBe(expected); - } - - private void ThenTheMappedRequestHasContentSize(long expected) - { - _mappedRequest.Data.Content.Headers.ContentLength.ShouldBe(expected); - } - - [Fact] - public void Should_handle_no_content() - { - this.Given(_ => GivenTheInputRequestHasNoContent()) - .And(_ => GivenTheInputRequestHasMethod("GET")) - .And(_ => GivenTheInputRequestHasAValidUri()) - .When(_ => WhenMapped()) - .Then(_ => ThenNoErrorIsReturned()) - .And(_ => ThenTheMappedRequestHasNoContent()) - .BDDfy(); - } - - private void GivenTheInputRequestHasMethod(string method) - { - _inputRequest.Method = method; - } - - private void GivenTheInputRequestHasScheme(string scheme) - { - _inputRequest.Scheme = scheme; - } - - private void GivenTheInputRequestHasHost(string host) - { - _inputRequest.Host = new HostString(host); - } - - private void GivenTheInputRequestHasPath(string path) - { - if (path != null) - { - _inputRequest.Path = path; - } - } - - private void GivenTheInputRequestHasQueryString(string querystring) - { - if (querystring != null) - { - _inputRequest.QueryString = new QueryString(querystring); - } - } - - private void GivenTheInputRequestHasAValidUri() - { - GivenTheInputRequestHasScheme("http"); - GivenTheInputRequestHasHost("www.google.com"); - } - - private void GivenTheInputRequestHasHeaders() - { - _inputHeaders = new List>() - { - new KeyValuePair("abc", new StringValues(new string[]{"123","456" })), - new KeyValuePair("def", new StringValues(new string[]{"789","012" })), - }; - - foreach (var inputHeader in _inputHeaders) - { - _inputRequest.Headers.Add(inputHeader); - } - } - - private void GivenTheInputRequestHasNoHeaders() - { - _inputRequest.Headers.Clear(); - } - - private void GivenTheInputRequestHasContent(string content) - { - _inputRequest.Body = new MemoryStream(Encoding.UTF8.GetBytes(content)); - } - - private void GivenTheInputRequestHasNoContent() - { - _inputRequest.Body = null; - } - - private void WhenMapped() - { - _mappedRequest = _requestMapper.Map(_inputRequest).GetAwaiter().GetResult(); - } - - private void ThenNoErrorIsReturned() - { - _mappedRequest.IsError.ShouldBeFalse(); - } - - private void ThenAnErrorIsReturned() - { - _mappedRequest.IsError.ShouldBeTrue(); - } - - private void ThenTheMappedRequestHasUri(string expectedUri) - { - _mappedRequest.Data.RequestUri.OriginalString.ShouldBe(expectedUri); - } - - private void ThenTheMappedRequestHasMethod(string expectedMethod) - { - _mappedRequest.Data.Method.ToString().ShouldBe(expectedMethod); - } - - private void ThenTheMappedRequestHasEachHeader() - { - _mappedRequest.Data.Headers.Count().ShouldBe(_inputHeaders.Count); - foreach(var header in _mappedRequest.Data.Headers) - { - var inputHeader = _inputHeaders.First(h => h.Key == header.Key); - inputHeader.ShouldNotBeNull(); - inputHeader.Value.Count().ShouldBe(header.Value.Count()); - foreach(var inputHeaderValue in inputHeader.Value) - { - header.Value.Any(v => v == inputHeaderValue); - } - } - } - - private void ThenTheMappedRequestHasNoHeaders() - { - _mappedRequest.Data.Headers.Count().ShouldBe(0); - } - - private void ThenTheMappedRequestHasContent(string expectedContent) - { - _mappedRequest.Data.Content.ReadAsStringAsync().GetAwaiter().GetResult().ShouldBe(expectedContent); - } - - private void ThenTheMappedRequestHasNoContent() - { - _mappedRequest.Data.Content.ShouldBeNull(); - } - - private void ThenTheMappedRequestIsNull() - { - _mappedRequest.Data.ShouldBeNull(); - } - } -} +namespace Ocelot.UnitTests.Request.Mapper +{ + using System.Collections.Generic; + using System.Linq; + using System.Net.Http; + + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Http.Internal; + using Microsoft.Extensions.Primitives; + using Ocelot.Request.Mapper; + using Ocelot.Responses; + using TestStack.BDDfy; + using Xunit; + using Shouldly; + using System; + using System.IO; + using System.Text; + + public class RequestMapperTests + { + readonly HttpRequest _inputRequest; + + readonly RequestMapper _requestMapper; + + Response _mappedRequest; + + List> _inputHeaders = null; + + public RequestMapperTests() + { + _inputRequest = new DefaultHttpRequest(new DefaultHttpContext()); + + _requestMapper = new RequestMapper(); + } + + [Theory] + [InlineData("https", "my.url:123", "/abc/DEF", "?a=1&b=2", "https://my.url:123/abc/DEF?a=1&b=2")] + [InlineData("http", "blah.com", "/d ef", "?abc=123", "http://blah.com/d%20ef?abc=123")] // note! the input is encoded when building the input request + [InlineData("http", "myusername:mypassword@abc.co.uk", null, null, "http://myusername:mypassword@abc.co.uk/")] + [InlineData("http", "點看.com", null, null, "http://xn--c1yn36f.com/")] + [InlineData("http", "xn--c1yn36f.com", null, null, "http://xn--c1yn36f.com/")] + public void Should_map_valid_request_uri(string scheme, string host, string path, string queryString, string expectedUri) + { + this.Given(_ => GivenTheInputRequestHasMethod("GET")) + .And(_ => GivenTheInputRequestHasScheme(scheme)) + .And(_ => GivenTheInputRequestHasHost(host)) + .And(_ => GivenTheInputRequestHasPath(path)) + .And(_ => GivenTheInputRequestHasQueryString(queryString)) + .When(_ => WhenMapped()) + .Then(_ => ThenNoErrorIsReturned()) + .And(_ => ThenTheMappedRequestHasUri(expectedUri)) + .BDDfy(); + } + + [Theory] + [InlineData("ftp", "google.com", "/abc/DEF", "?a=1&b=2")] + public void Should_error_on_unsupported_request_uri(string scheme, string host, string path, string queryString) + { + this.Given(_ => GivenTheInputRequestHasMethod("GET")) + .And(_ => GivenTheInputRequestHasScheme(scheme)) + .And(_ => GivenTheInputRequestHasHost(host)) + .And(_ => GivenTheInputRequestHasPath(path)) + .And(_ => GivenTheInputRequestHasQueryString(queryString)) + .When(_ => WhenMapped()) + .Then(_ => ThenAnErrorIsReturned()) + .And(_ => ThenTheMappedRequestIsNull()) + .BDDfy(); + } + + [Theory] + [InlineData("GET")] + [InlineData("POST")] + [InlineData("WHATEVER")] + public void Should_map_method(string method) + { + this.Given(_ => GivenTheInputRequestHasMethod(method)) + .And(_ => GivenTheInputRequestHasAValidUri()) + .When(_ => WhenMapped()) + .Then(_ => ThenNoErrorIsReturned()) + .And(_ => ThenTheMappedRequestHasMethod(method)) + .BDDfy(); + } + + [Fact] + public void Should_map_all_headers() + { + this.Given(_ => GivenTheInputRequestHasHeaders()) + .And(_ => GivenTheInputRequestHasMethod("GET")) + .And(_ => GivenTheInputRequestHasAValidUri()) + .When(_ => WhenMapped()) + .Then(_ => ThenNoErrorIsReturned()) + .And(_ => ThenTheMappedRequestHasEachHeader()) + .BDDfy(); + } + + [Fact] + public void Should_handle_no_headers() + { + this.Given(_ => GivenTheInputRequestHasNoHeaders()) + .And(_ => GivenTheInputRequestHasMethod("GET")) + .And(_ => GivenTheInputRequestHasAValidUri()) + .When(_ => WhenMapped()) + .Then(_ => ThenNoErrorIsReturned()) + .And(_ => ThenTheMappedRequestHasNoHeaders()) + .BDDfy(); + } + + [Fact] + public void Should_map_content() + { + this.Given(_ => GivenTheInputRequestHasContent("This is my content")) + .And(_ => GivenTheInputRequestHasMethod("GET")) + .And(_ => GivenTheInputRequestHasAValidUri()) + .When(_ => WhenMapped()) + .Then(_ => ThenNoErrorIsReturned()) + .And(_ => ThenTheMappedRequestHasContent("This is my content")) + .BDDfy(); + } + + [Fact] + public void Should_map_content_type_header() + { + this.Given(_ => GivenTheInputRequestHasContent("This is my content")) + .And(_ => GivenTheContentTypeIs("application/json")) + .And(_ => GivenTheInputRequestHasMethod("GET")) + .And(_ => GivenTheInputRequestHasAValidUri()) + .When(_ => WhenMapped()) + .Then(_ => ThenNoErrorIsReturned()) + .And(_ => ThenTheMappedRequestHasContentTypeHeader("application/json")) + .And(_ => ThenTheMappedRequestHasContentSize("This is my content".Length)) + .BDDfy(); + } + + private void GivenTheContentTypeIs(string contentType) + { + _inputRequest.ContentType = contentType; + } + + private void ThenTheMappedRequestHasContentTypeHeader(string expected) + { + _mappedRequest.Data.Content.Headers.ContentType.MediaType.ShouldBe(expected); + } + + private void ThenTheMappedRequestHasContentSize(long expected) + { + _mappedRequest.Data.Content.Headers.ContentLength.ShouldBe(expected); + } + + [Fact] + public void Should_handle_no_content() + { + this.Given(_ => GivenTheInputRequestHasNoContent()) + .And(_ => GivenTheInputRequestHasMethod("GET")) + .And(_ => GivenTheInputRequestHasAValidUri()) + .When(_ => WhenMapped()) + .Then(_ => ThenNoErrorIsReturned()) + .And(_ => ThenTheMappedRequestHasNoContent()) + .BDDfy(); + } + + private void GivenTheInputRequestHasMethod(string method) + { + _inputRequest.Method = method; + } + + private void GivenTheInputRequestHasScheme(string scheme) + { + _inputRequest.Scheme = scheme; + } + + private void GivenTheInputRequestHasHost(string host) + { + _inputRequest.Host = new HostString(host); + } + + private void GivenTheInputRequestHasPath(string path) + { + if (path != null) + { + _inputRequest.Path = path; + } + } + + private void GivenTheInputRequestHasQueryString(string querystring) + { + if (querystring != null) + { + _inputRequest.QueryString = new QueryString(querystring); + } + } + + private void GivenTheInputRequestHasAValidUri() + { + GivenTheInputRequestHasScheme("http"); + GivenTheInputRequestHasHost("www.google.com"); + } + + private void GivenTheInputRequestHasHeaders() + { + _inputHeaders = new List>() + { + new KeyValuePair("abc", new StringValues(new string[]{"123","456" })), + new KeyValuePair("def", new StringValues(new string[]{"789","012" })), + }; + + foreach (var inputHeader in _inputHeaders) + { + _inputRequest.Headers.Add(inputHeader); + } + } + + private void GivenTheInputRequestHasNoHeaders() + { + _inputRequest.Headers.Clear(); + } + + private void GivenTheInputRequestHasContent(string content) + { + _inputRequest.Body = new MemoryStream(Encoding.UTF8.GetBytes(content)); + } + + private void GivenTheInputRequestHasNoContent() + { + _inputRequest.Body = null; + } + + private void WhenMapped() + { + _mappedRequest = _requestMapper.Map(_inputRequest).GetAwaiter().GetResult(); + } + + private void ThenNoErrorIsReturned() + { + _mappedRequest.IsError.ShouldBeFalse(); + } + + private void ThenAnErrorIsReturned() + { + _mappedRequest.IsError.ShouldBeTrue(); + } + + private void ThenTheMappedRequestHasUri(string expectedUri) + { + _mappedRequest.Data.RequestUri.OriginalString.ShouldBe(expectedUri); + } + + private void ThenTheMappedRequestHasMethod(string expectedMethod) + { + _mappedRequest.Data.Method.ToString().ShouldBe(expectedMethod); + } + + private void ThenTheMappedRequestHasEachHeader() + { + _mappedRequest.Data.Headers.Count().ShouldBe(_inputHeaders.Count); + foreach(var header in _mappedRequest.Data.Headers) + { + var inputHeader = _inputHeaders.First(h => h.Key == header.Key); + inputHeader.ShouldNotBeNull(); + inputHeader.Value.Count().ShouldBe(header.Value.Count()); + foreach(var inputHeaderValue in inputHeader.Value) + { + header.Value.Any(v => v == inputHeaderValue); + } + } + } + + private void ThenTheMappedRequestHasNoHeaders() + { + _mappedRequest.Data.Headers.Count().ShouldBe(0); + } + + private void ThenTheMappedRequestHasContent(string expectedContent) + { + _mappedRequest.Data.Content.ReadAsStringAsync().GetAwaiter().GetResult().ShouldBe(expectedContent); + } + + private void ThenTheMappedRequestHasNoContent() + { + _mappedRequest.Data.Content.ShouldBeNull(); + } + + private void ThenTheMappedRequestIsNull() + { + _mappedRequest.Data.ShouldBeNull(); + } + } +} diff --git a/test/Ocelot.UnitTests/RequestId/ReRouteRequestIdMiddlewareTests.cs b/test/Ocelot.UnitTests/RequestId/ReRouteRequestIdMiddlewareTests.cs index 0acfc862e..bd3130de6 100644 --- a/test/Ocelot.UnitTests/RequestId/ReRouteRequestIdMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/RequestId/ReRouteRequestIdMiddlewareTests.cs @@ -1,182 +1,182 @@ -namespace Ocelot.UnitTests.RequestId -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Net.Http; - using System.Threading.Tasks; - using Microsoft.AspNetCore.Builder; - using Microsoft.AspNetCore.Hosting; - using Microsoft.Extensions.DependencyInjection; - using Moq; - using Ocelot.Configuration.Builder; - using Ocelot.DownstreamRouteFinder; - using Ocelot.DownstreamRouteFinder.UrlMatcher; - using Ocelot.Logging; - using Ocelot.RequestId.Middleware; - using Ocelot.Responses; - using Shouldly; - using TestStack.BDDfy; - using Xunit; - - public class ReRouteRequestIdMiddlewareTests : ServerHostedMiddlewareTest - { - private readonly HttpRequestMessage _downstreamRequest; - private Response _downstreamRoute; - private string _value; - private string _key; - - public ReRouteRequestIdMiddlewareTests() - { - _downstreamRequest = new HttpRequestMessage(); - - ScopedRepository - .Setup(sr => sr.Get("DownstreamRequest")) - .Returns(new OkResponse(_downstreamRequest)); - - GivenTheTestServerIsConfigured(); - } - - [Fact] - public void should_pass_down_request_id_from_upstream_request() - { - var downstreamRoute = new DownstreamRoute(new List(), - new ReRouteBuilder() - .WithDownstreamPathTemplate("any old string") - .WithRequestIdKey("LSRequestId") - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()); - - var requestId = Guid.NewGuid().ToString(); - - this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .And(x => GivenThereIsNoGlobalRequestId()) - .And(x => x.GivenTheRequestIdIsAddedToTheRequest("LSRequestId", requestId)) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheTraceIdIs(requestId)) - .BDDfy(); - } - - [Fact] - public void should_add_request_id_when_not_on_upstream_request() - { - var downstreamRoute = new DownstreamRoute(new List(), - new ReRouteBuilder() - .WithDownstreamPathTemplate("any old string") - .WithRequestIdKey("LSRequestId") - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()); - - this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .And(x => GivenThereIsNoGlobalRequestId()) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheTraceIdIsAnything()) - .BDDfy(); - } - - [Fact] - public void should_add_request_id_scoped_repo_for_logging_later() - { - var downstreamRoute = new DownstreamRoute(new List(), - new ReRouteBuilder() - .WithDownstreamPathTemplate("any old string") - .WithRequestIdKey("LSRequestId") - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()); - - var requestId = Guid.NewGuid().ToString(); - - this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .And(x => GivenThereIsNoGlobalRequestId()) - .And(x => x.GivenTheRequestIdIsAddedToTheRequest("LSRequestId", requestId)) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheTraceIdIs(requestId)) - .And(x => ThenTheRequestIdIsSaved()) - .BDDfy(); - } - - [Fact] - public void should_update_request_id_scoped_repo_for_logging_later() - { - var downstreamRoute = new DownstreamRoute(new List(), - new ReRouteBuilder() - .WithDownstreamPathTemplate("any old string") - .WithRequestIdKey("LSRequestId") - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()); - - var requestId = Guid.NewGuid().ToString(); - - this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .And(x => GivenTheRequestIdWasSetGlobally()) - .And(x => x.GivenTheRequestIdIsAddedToTheRequest("LSRequestId", requestId)) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheTraceIdIs(requestId)) - .And(x => ThenTheRequestIdIsUpdated()) - .BDDfy(); - } - - private void GivenThereIsNoGlobalRequestId() - { - ScopedRepository.Setup(x => x.Get("RequestId")).Returns(new OkResponse(null)); - } - - private void GivenTheRequestIdWasSetGlobally() - { - ScopedRepository.Setup(x => x.Get("RequestId")).Returns(new OkResponse("alreadyset")); - } - - private void ThenTheRequestIdIsSaved() - { - ScopedRepository.Verify(x => x.Add("RequestId", _value), Times.Once); - } - - private void ThenTheRequestIdIsUpdated() - { - ScopedRepository.Verify(x => x.Update("RequestId", _value), Times.Once); - } - - protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) - { - services.AddSingleton(); - services.AddLogging(); - services.AddSingleton(ScopedRepository.Object); - } - - protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) - { - app.UseRequestIdMiddleware(); - - app.Run(x => - { - x.Response.Headers.Add("LSRequestId", x.TraceIdentifier); - return Task.CompletedTask; - }); - } - - private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) - { - _downstreamRoute = new OkResponse(downstreamRoute); - ScopedRepository - .Setup(x => x.Get(It.IsAny())) - .Returns(_downstreamRoute); - } - - private void GivenTheRequestIdIsAddedToTheRequest(string key, string value) - { - _key = key; - _value = value; - Client.DefaultRequestHeaders.TryAddWithoutValidation(_key, _value); - } - - private void ThenTheTraceIdIsAnything() - { - ResponseMessage.Headers.GetValues("LSRequestId").First().ShouldNotBeNullOrEmpty(); - } - - private void ThenTheTraceIdIs(string expected) - { - ResponseMessage.Headers.GetValues("LSRequestId").First().ShouldBe(expected); - } - } -} +namespace Ocelot.UnitTests.RequestId +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net.Http; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Hosting; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using Ocelot.Configuration.Builder; + using Ocelot.DownstreamRouteFinder; + using Ocelot.DownstreamRouteFinder.UrlMatcher; + using Ocelot.Logging; + using Ocelot.RequestId.Middleware; + using Ocelot.Responses; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + + public class ReRouteRequestIdMiddlewareTests : ServerHostedMiddlewareTest + { + private readonly HttpRequestMessage _downstreamRequest; + private Response _downstreamRoute; + private string _value; + private string _key; + + public ReRouteRequestIdMiddlewareTests() + { + _downstreamRequest = new HttpRequestMessage(); + + ScopedRepository + .Setup(sr => sr.Get("DownstreamRequest")) + .Returns(new OkResponse(_downstreamRequest)); + + GivenTheTestServerIsConfigured(); + } + + [Fact] + public void should_pass_down_request_id_from_upstream_request() + { + var downstreamRoute = new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithRequestIdKey("LSRequestId") + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()); + + var requestId = Guid.NewGuid().ToString(); + + this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => GivenThereIsNoGlobalRequestId()) + .And(x => x.GivenTheRequestIdIsAddedToTheRequest("LSRequestId", requestId)) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheTraceIdIs(requestId)) + .BDDfy(); + } + + [Fact] + public void should_add_request_id_when_not_on_upstream_request() + { + var downstreamRoute = new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithRequestIdKey("LSRequestId") + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()); + + this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => GivenThereIsNoGlobalRequestId()) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheTraceIdIsAnything()) + .BDDfy(); + } + + [Fact] + public void should_add_request_id_scoped_repo_for_logging_later() + { + var downstreamRoute = new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithRequestIdKey("LSRequestId") + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()); + + var requestId = Guid.NewGuid().ToString(); + + this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => GivenThereIsNoGlobalRequestId()) + .And(x => x.GivenTheRequestIdIsAddedToTheRequest("LSRequestId", requestId)) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheTraceIdIs(requestId)) + .And(x => ThenTheRequestIdIsSaved()) + .BDDfy(); + } + + [Fact] + public void should_update_request_id_scoped_repo_for_logging_later() + { + var downstreamRoute = new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithRequestIdKey("LSRequestId") + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()); + + var requestId = Guid.NewGuid().ToString(); + + this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => GivenTheRequestIdWasSetGlobally()) + .And(x => x.GivenTheRequestIdIsAddedToTheRequest("LSRequestId", requestId)) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheTraceIdIs(requestId)) + .And(x => ThenTheRequestIdIsUpdated()) + .BDDfy(); + } + + private void GivenThereIsNoGlobalRequestId() + { + ScopedRepository.Setup(x => x.Get("RequestId")).Returns(new OkResponse(null)); + } + + private void GivenTheRequestIdWasSetGlobally() + { + ScopedRepository.Setup(x => x.Get("RequestId")).Returns(new OkResponse("alreadyset")); + } + + private void ThenTheRequestIdIsSaved() + { + ScopedRepository.Verify(x => x.Add("RequestId", _value), Times.Once); + } + + private void ThenTheRequestIdIsUpdated() + { + ScopedRepository.Verify(x => x.Update("RequestId", _value), Times.Once); + } + + protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + { + services.AddSingleton(); + services.AddLogging(); + services.AddSingleton(ScopedRepository.Object); + } + + protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) + { + app.UseRequestIdMiddleware(); + + app.Run(x => + { + x.Response.Headers.Add("LSRequestId", x.TraceIdentifier); + return Task.CompletedTask; + }); + } + + private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) + { + _downstreamRoute = new OkResponse(downstreamRoute); + ScopedRepository + .Setup(x => x.Get(It.IsAny())) + .Returns(_downstreamRoute); + } + + private void GivenTheRequestIdIsAddedToTheRequest(string key, string value) + { + _key = key; + _value = value; + Client.DefaultRequestHeaders.TryAddWithoutValidation(_key, _value); + } + + private void ThenTheTraceIdIsAnything() + { + ResponseMessage.Headers.GetValues("LSRequestId").First().ShouldNotBeNullOrEmpty(); + } + + private void ThenTheTraceIdIs(string expected) + { + ResponseMessage.Headers.GetValues("LSRequestId").First().ShouldBe(expected); + } + } +} diff --git a/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs b/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs index 45aafecd6..7c6bd4874 100644 --- a/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs @@ -1,81 +1,81 @@ -namespace Ocelot.UnitTests.Requester -{ - using System.Net.Http; - using Microsoft.AspNetCore.Builder; - using Microsoft.Extensions.DependencyInjection; - using Moq; - using Ocelot.Logging; - using Ocelot.Requester; - using Ocelot.Requester.Middleware; - using Ocelot.Requester.QoS; - using Ocelot.Responses; - using TestStack.BDDfy; - using Xunit; - - public class HttpRequesterMiddlewareTests : ServerHostedMiddlewareTest - { - private readonly Mock _requester; - private OkResponse _response; - private OkResponse _request; - - public HttpRequesterMiddlewareTests() - { - _requester = new Mock(); - - GivenTheTestServerIsConfigured(); - } - - [Fact] - public void should_call_scoped_data_repository_correctly() - { - this.Given(x => x.GivenTheRequestIs(new Ocelot.Request.Request(new HttpRequestMessage(),true, new NoQoSProvider(), false, false))) - .And(x => x.GivenTheRequesterReturns(new HttpResponseMessage())) - .And(x => x.GivenTheScopedRepoReturns()) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheScopedRepoIsCalledCorrectly()) - .BDDfy(); - } - - protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) - { - services.AddSingleton(); - services.AddLogging(); - services.AddSingleton(_requester.Object); - services.AddSingleton(ScopedRepository.Object); - } - - protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) - { - app.UseHttpRequesterMiddleware(); - } - - private void GivenTheRequestIs(Ocelot.Request.Request request) - { - _request = new OkResponse(request); - ScopedRepository - .Setup(x => x.Get(It.IsAny())) - .Returns(_request); - } - - private void GivenTheRequesterReturns(HttpResponseMessage response) - { - _response = new OkResponse(response); - _requester - .Setup(x => x.GetResponse(It.IsAny())) - .ReturnsAsync(_response); - } - - private void GivenTheScopedRepoReturns() - { - ScopedRepository - .Setup(x => x.Add(It.IsAny(), _response.Data)) - .Returns(new OkResponse()); - } - - private void ThenTheScopedRepoIsCalledCorrectly() - { - ScopedRepository - .Verify(x => x.Add("HttpResponseMessage", _response.Data), Times.Once()); - } - } -} +namespace Ocelot.UnitTests.Requester +{ + using System.Net.Http; + using Microsoft.AspNetCore.Builder; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using Ocelot.Logging; + using Ocelot.Requester; + using Ocelot.Requester.Middleware; + using Ocelot.Requester.QoS; + using Ocelot.Responses; + using TestStack.BDDfy; + using Xunit; + + public class HttpRequesterMiddlewareTests : ServerHostedMiddlewareTest + { + private readonly Mock _requester; + private OkResponse _response; + private OkResponse _request; + + public HttpRequesterMiddlewareTests() + { + _requester = new Mock(); + + GivenTheTestServerIsConfigured(); + } + + [Fact] + public void should_call_scoped_data_repository_correctly() + { + this.Given(x => x.GivenTheRequestIs(new Ocelot.Request.Request(new HttpRequestMessage(),true, new NoQoSProvider(), false, false))) + .And(x => x.GivenTheRequesterReturns(new HttpResponseMessage())) + .And(x => x.GivenTheScopedRepoReturns()) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheScopedRepoIsCalledCorrectly()) + .BDDfy(); + } + + protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + { + services.AddSingleton(); + services.AddLogging(); + services.AddSingleton(_requester.Object); + services.AddSingleton(ScopedRepository.Object); + } + + protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) + { + app.UseHttpRequesterMiddleware(); + } + + private void GivenTheRequestIs(Ocelot.Request.Request request) + { + _request = new OkResponse(request); + ScopedRepository + .Setup(x => x.Get(It.IsAny())) + .Returns(_request); + } + + private void GivenTheRequesterReturns(HttpResponseMessage response) + { + _response = new OkResponse(response); + _requester + .Setup(x => x.GetResponse(It.IsAny())) + .ReturnsAsync(_response); + } + + private void GivenTheScopedRepoReturns() + { + ScopedRepository + .Setup(x => x.Add(It.IsAny(), _response.Data)) + .Returns(new OkResponse()); + } + + private void ThenTheScopedRepoIsCalledCorrectly() + { + ScopedRepository + .Verify(x => x.Add("HttpResponseMessage", _response.Data), Times.Once()); + } + } +} diff --git a/test/Ocelot.UnitTests/Requester/QoSProviderFactoryTests.cs b/test/Ocelot.UnitTests/Requester/QoSProviderFactoryTests.cs index 082e7177d..2c595f6c3 100644 --- a/test/Ocelot.UnitTests/Requester/QoSProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/Requester/QoSProviderFactoryTests.cs @@ -1,81 +1,81 @@ -using Moq; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.Logging; -using Ocelot.Requester.QoS; -using Shouldly; -using System.Collections.Generic; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Requester -{ - public class QoSProviderFactoryTests - { - private readonly IQoSProviderFactory _factory; - private ReRoute _reRoute; - private IQoSProvider _result; - private Mock _loggerFactory; - private Mock _logger; - - public QoSProviderFactoryTests() - { - _logger = new Mock(); - _loggerFactory = new Mock(); - _loggerFactory - .Setup(x => x.CreateLogger()) - .Returns(_logger.Object); - _factory = new QoSProviderFactory(_loggerFactory.Object); - } - - [Fact] - public void should_return_no_qos_provider() - { - var reRoute = new ReRouteBuilder() - .WithUpstreamHttpMethod(new List { "get" }) - .WithIsQos(false) - .Build(); - - this.Given(x => x.GivenAReRoute(reRoute)) - .When(x => x.WhenIGetTheQoSProvider()) - .Then(x => x.ThenTheQoSProviderIsReturned()) - .BDDfy(); - } - - [Fact] - public void should_return_polly_qos_provider() - { - var qosOptions = new QoSOptionsBuilder() - .WithTimeoutValue(100) - .WithDurationOfBreak(100) - .WithExceptionsAllowedBeforeBreaking(100) - .Build(); - - var reRoute = new ReRouteBuilder() - .WithUpstreamHttpMethod(new List { "get" }) - .WithIsQos(true) - .WithQosOptions(qosOptions) - .Build(); - - this.Given(x => x.GivenAReRoute(reRoute)) - .When(x => x.WhenIGetTheQoSProvider()) - .Then(x => x.ThenTheQoSProviderIsReturned()) - .BDDfy(); - } - - private void GivenAReRoute(ReRoute reRoute) - { - _reRoute = reRoute; - } - - private void WhenIGetTheQoSProvider() - { - _result = _factory.Get(_reRoute); - } - - private void ThenTheQoSProviderIsReturned() - { - _result.ShouldBeOfType(); - } - } -} +using Moq; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.Logging; +using Ocelot.Requester.QoS; +using Shouldly; +using System.Collections.Generic; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Requester +{ + public class QoSProviderFactoryTests + { + private readonly IQoSProviderFactory _factory; + private ReRoute _reRoute; + private IQoSProvider _result; + private Mock _loggerFactory; + private Mock _logger; + + public QoSProviderFactoryTests() + { + _logger = new Mock(); + _loggerFactory = new Mock(); + _loggerFactory + .Setup(x => x.CreateLogger()) + .Returns(_logger.Object); + _factory = new QoSProviderFactory(_loggerFactory.Object); + } + + [Fact] + public void should_return_no_qos_provider() + { + var reRoute = new ReRouteBuilder() + .WithUpstreamHttpMethod(new List { "get" }) + .WithIsQos(false) + .Build(); + + this.Given(x => x.GivenAReRoute(reRoute)) + .When(x => x.WhenIGetTheQoSProvider()) + .Then(x => x.ThenTheQoSProviderIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_return_polly_qos_provider() + { + var qosOptions = new QoSOptionsBuilder() + .WithTimeoutValue(100) + .WithDurationOfBreak(100) + .WithExceptionsAllowedBeforeBreaking(100) + .Build(); + + var reRoute = new ReRouteBuilder() + .WithUpstreamHttpMethod(new List { "get" }) + .WithIsQos(true) + .WithQosOptions(qosOptions) + .Build(); + + this.Given(x => x.GivenAReRoute(reRoute)) + .When(x => x.WhenIGetTheQoSProvider()) + .Then(x => x.ThenTheQoSProviderIsReturned()) + .BDDfy(); + } + + private void GivenAReRoute(ReRoute reRoute) + { + _reRoute = reRoute; + } + + private void WhenIGetTheQoSProvider() + { + _result = _factory.Get(_reRoute); + } + + private void ThenTheQoSProviderIsReturned() + { + _result.ShouldBeOfType(); + } + } +} diff --git a/test/Ocelot.UnitTests/Requester/QosProviderHouseTests.cs b/test/Ocelot.UnitTests/Requester/QosProviderHouseTests.cs index 4756ae8f9..719294046 100644 --- a/test/Ocelot.UnitTests/Requester/QosProviderHouseTests.cs +++ b/test/Ocelot.UnitTests/Requester/QosProviderHouseTests.cs @@ -1,146 +1,146 @@ -using Moq; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.LoadBalancer.LoadBalancers; -using Ocelot.Requester.QoS; -using Ocelot.Responses; -using Ocelot.UnitTests.LoadBalancer; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Requester -{ - public class QosProviderHouseTests - { - private IQoSProvider _qoSProvider; - private readonly QosProviderHouse _qosProviderHouse; - private Response _addResult; - private Response _getResult; - private ReRoute _reRoute; - private readonly Mock _factory; - - public QosProviderHouseTests() - { - _factory = new Mock(); - _qosProviderHouse = new QosProviderHouse(_factory.Object); - } - - [Fact] - public void should_store_qos_provider_on_first_request() - { - var reRoute = new ReRouteBuilder().WithReRouteKey("test").Build(); - - this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider())) - .Then(x => x.ThenItIsAdded()) - .BDDfy(); - } - - [Fact] - public void should_not_store_qos_provider_on_first_request() - { - var reRoute = new ReRouteBuilder().WithReRouteKey("test").Build(); - - this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider())) - .When(x => x.WhenWeGetTheQoSProvider(reRoute)) - .Then(x => x.ThenItIsReturned()) - .BDDfy(); - } - - [Fact] - public void should_store_qos_providers_by_key() - { - var reRoute = new ReRouteBuilder().WithReRouteKey("test").Build(); - var reRouteTwo = new ReRouteBuilder().WithReRouteKey("testTwo").Build(); - - this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider())) - .And(x => x.GivenThereIsAQoSProvider(reRouteTwo, new FakePollyQoSProvider())) - .When(x => x.WhenWeGetTheQoSProvider(reRoute)) - .Then(x => x.ThenTheQoSProviderIs()) - .When(x => x.WhenWeGetTheQoSProvider(reRouteTwo)) - .Then(x => x.ThenTheQoSProviderIs()) - .BDDfy(); - } - - [Fact] - public void should_return_error_if_no_qos_provider_with_key() - { - var reRoute = new ReRouteBuilder().Build(); - - this.When(x => x.WhenWeGetTheQoSProvider(reRoute)) - .Then(x => x.ThenAnErrorIsReturned()) - .BDDfy(); - } - - [Fact] - public void should_get_new_qos_provider_if_reroute_qos_provider_has_changed() - { - var reRoute = new ReRouteBuilder().WithReRouteKey("test").Build(); - - var reRouteTwo = new ReRouteBuilder().WithReRouteKey("test").WithIsQos(true).Build(); - - this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider())) - .When(x => x.WhenWeGetTheQoSProvider(reRoute)) - .Then(x => x.ThenTheQoSProviderIs()) - .When(x => x.WhenIGetTheReRouteWithTheSameKeyButDifferentQosProvider(reRouteTwo)) - .Then(x => x.ThenTheQoSProviderIs()) - .BDDfy(); - } - - private void WhenIGetTheReRouteWithTheSameKeyButDifferentQosProvider(ReRoute reRoute) - { - _reRoute = reRoute; - _factory.Setup(x => x.Get(_reRoute)).Returns(new FakePollyQoSProvider()); - _getResult = _qosProviderHouse.Get(_reRoute); - } - - private void ThenAnErrorIsReturned() - { - _getResult.IsError.ShouldBeTrue(); - _getResult.Errors[0].ShouldBeOfType(); - } - - private void ThenTheQoSProviderIs() - { - _getResult.Data.ShouldBeOfType(); - } - - private void ThenItIsAdded() - { - _getResult.IsError.ShouldBe(false); - _getResult.ShouldBeOfType>(); - _factory.Verify(x => x.Get(_reRoute), Times.Once); - _getResult.Data.ShouldBe(_qoSProvider); - } - - - private void GivenThereIsAQoSProvider(ReRoute reRoute, IQoSProvider qoSProvider) - { - _reRoute = reRoute; - _qoSProvider = qoSProvider; - _factory.Setup(x => x.Get(_reRoute)).Returns(_qoSProvider); - _getResult = _qosProviderHouse.Get(reRoute); - } - - private void WhenWeGetTheQoSProvider(ReRoute reRoute) - { - _getResult = _qosProviderHouse.Get(reRoute); - } - - private void ThenItIsReturned() - { - _getResult.Data.ShouldBe(_qoSProvider); - _factory.Verify(x => x.Get(_reRoute), Times.Once); - } - - class FakeQoSProvider : IQoSProvider - { - public CircuitBreaker CircuitBreaker { get; } - } - - class FakePollyQoSProvider : IQoSProvider - { - public CircuitBreaker CircuitBreaker { get; } - } - } -} +using Moq; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.LoadBalancer.LoadBalancers; +using Ocelot.Requester.QoS; +using Ocelot.Responses; +using Ocelot.UnitTests.LoadBalancer; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Requester +{ + public class QosProviderHouseTests + { + private IQoSProvider _qoSProvider; + private readonly QosProviderHouse _qosProviderHouse; + private Response _addResult; + private Response _getResult; + private ReRoute _reRoute; + private readonly Mock _factory; + + public QosProviderHouseTests() + { + _factory = new Mock(); + _qosProviderHouse = new QosProviderHouse(_factory.Object); + } + + [Fact] + public void should_store_qos_provider_on_first_request() + { + var reRoute = new ReRouteBuilder().WithReRouteKey("test").Build(); + + this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider())) + .Then(x => x.ThenItIsAdded()) + .BDDfy(); + } + + [Fact] + public void should_not_store_qos_provider_on_first_request() + { + var reRoute = new ReRouteBuilder().WithReRouteKey("test").Build(); + + this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider())) + .When(x => x.WhenWeGetTheQoSProvider(reRoute)) + .Then(x => x.ThenItIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_store_qos_providers_by_key() + { + var reRoute = new ReRouteBuilder().WithReRouteKey("test").Build(); + var reRouteTwo = new ReRouteBuilder().WithReRouteKey("testTwo").Build(); + + this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider())) + .And(x => x.GivenThereIsAQoSProvider(reRouteTwo, new FakePollyQoSProvider())) + .When(x => x.WhenWeGetTheQoSProvider(reRoute)) + .Then(x => x.ThenTheQoSProviderIs()) + .When(x => x.WhenWeGetTheQoSProvider(reRouteTwo)) + .Then(x => x.ThenTheQoSProviderIs()) + .BDDfy(); + } + + [Fact] + public void should_return_error_if_no_qos_provider_with_key() + { + var reRoute = new ReRouteBuilder().Build(); + + this.When(x => x.WhenWeGetTheQoSProvider(reRoute)) + .Then(x => x.ThenAnErrorIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_get_new_qos_provider_if_reroute_qos_provider_has_changed() + { + var reRoute = new ReRouteBuilder().WithReRouteKey("test").Build(); + + var reRouteTwo = new ReRouteBuilder().WithReRouteKey("test").WithIsQos(true).Build(); + + this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider())) + .When(x => x.WhenWeGetTheQoSProvider(reRoute)) + .Then(x => x.ThenTheQoSProviderIs()) + .When(x => x.WhenIGetTheReRouteWithTheSameKeyButDifferentQosProvider(reRouteTwo)) + .Then(x => x.ThenTheQoSProviderIs()) + .BDDfy(); + } + + private void WhenIGetTheReRouteWithTheSameKeyButDifferentQosProvider(ReRoute reRoute) + { + _reRoute = reRoute; + _factory.Setup(x => x.Get(_reRoute)).Returns(new FakePollyQoSProvider()); + _getResult = _qosProviderHouse.Get(_reRoute); + } + + private void ThenAnErrorIsReturned() + { + _getResult.IsError.ShouldBeTrue(); + _getResult.Errors[0].ShouldBeOfType(); + } + + private void ThenTheQoSProviderIs() + { + _getResult.Data.ShouldBeOfType(); + } + + private void ThenItIsAdded() + { + _getResult.IsError.ShouldBe(false); + _getResult.ShouldBeOfType>(); + _factory.Verify(x => x.Get(_reRoute), Times.Once); + _getResult.Data.ShouldBe(_qoSProvider); + } + + + private void GivenThereIsAQoSProvider(ReRoute reRoute, IQoSProvider qoSProvider) + { + _reRoute = reRoute; + _qoSProvider = qoSProvider; + _factory.Setup(x => x.Get(_reRoute)).Returns(_qoSProvider); + _getResult = _qosProviderHouse.Get(reRoute); + } + + private void WhenWeGetTheQoSProvider(ReRoute reRoute) + { + _getResult = _qosProviderHouse.Get(reRoute); + } + + private void ThenItIsReturned() + { + _getResult.Data.ShouldBe(_qoSProvider); + _factory.Verify(x => x.Get(_reRoute), Times.Once); + } + + class FakeQoSProvider : IQoSProvider + { + public CircuitBreaker CircuitBreaker { get; } + } + + class FakePollyQoSProvider : IQoSProvider + { + public CircuitBreaker CircuitBreaker { get; } + } + } +} diff --git a/test/Ocelot.UnitTests/Responder/AnyError.cs b/test/Ocelot.UnitTests/Responder/AnyError.cs index a4b35df03..1dd466a46 100644 --- a/test/Ocelot.UnitTests/Responder/AnyError.cs +++ b/test/Ocelot.UnitTests/Responder/AnyError.cs @@ -1,15 +1,15 @@ -using Ocelot.Errors; - -namespace Ocelot.UnitTests.Responder -{ - class AnyError : Error - { - public AnyError() : base("blahh", OcelotErrorCode.UnknownError) - { - } - - public AnyError(OcelotErrorCode errorCode) : base("blah", errorCode) - { - } - } -} +using Ocelot.Errors; + +namespace Ocelot.UnitTests.Responder +{ + class AnyError : Error + { + public AnyError() : base("blahh", OcelotErrorCode.UnknownError) + { + } + + public AnyError(OcelotErrorCode errorCode) : base("blah", errorCode) + { + } + } +} diff --git a/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs b/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs index 23809fd8a..06eebb4be 100644 --- a/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs +++ b/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs @@ -1,167 +1,167 @@ -using System; -using System.Collections.Generic; -using System.Net; -using Ocelot.Errors; -using Ocelot.Responder; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Responder -{ - public class ErrorsToHttpStatusCodeMapperTests - { - private readonly IErrorsToHttpStatusCodeMapper _codeMapper; - private int _result; - private List _errors; - - public ErrorsToHttpStatusCodeMapperTests() - { - _codeMapper = new ErrorsToHttpStatusCodeMapper(); - } - - [Theory] - [InlineData(OcelotErrorCode.UnauthenticatedError)] - public void should_return_unauthorized(OcelotErrorCode errorCode) - { - ShouldMapErrorToStatusCode(errorCode, HttpStatusCode.Unauthorized); - } - - [Theory] - [InlineData(OcelotErrorCode.CannotFindClaimError)] - [InlineData(OcelotErrorCode.ClaimValueNotAuthorisedError)] - [InlineData(OcelotErrorCode.ScopeNotAuthorisedError)] - [InlineData(OcelotErrorCode.UnauthorizedError)] - [InlineData(OcelotErrorCode.UserDoesNotHaveClaimError)] - public void should_return_forbidden(OcelotErrorCode errorCode) - { - ShouldMapErrorToStatusCode(errorCode, HttpStatusCode.Forbidden); - } - - [Theory] - [InlineData(OcelotErrorCode.RequestTimedOutError)] - public void should_return_service_unavailable(OcelotErrorCode errorCode) - { - ShouldMapErrorToStatusCode(OcelotErrorCode.RequestTimedOutError, HttpStatusCode.ServiceUnavailable); - } - - - [Theory] - [InlineData(OcelotErrorCode.CannotAddDataError)] - [InlineData(OcelotErrorCode.CannotFindDataError)] - [InlineData(OcelotErrorCode.DownstreamHostNullOrEmptyError)] - [InlineData(OcelotErrorCode.DownstreamPathNullOrEmptyError)] - [InlineData(OcelotErrorCode.DownstreampathTemplateAlreadyUsedError)] - [InlineData(OcelotErrorCode.DownstreamPathTemplateContainsSchemeError)] - [InlineData(OcelotErrorCode.DownstreamSchemeNullOrEmptyError)] - [InlineData(OcelotErrorCode.FileValidationFailedError)] - [InlineData(OcelotErrorCode.InstructionNotForClaimsError)] - [InlineData(OcelotErrorCode.NoInstructionsError)] - [InlineData(OcelotErrorCode.ParsingConfigurationHeaderError)] - [InlineData(OcelotErrorCode.RateLimitOptionsError)] - [InlineData(OcelotErrorCode.ServicesAreEmptyError)] - [InlineData(OcelotErrorCode.ServicesAreNullError)] - [InlineData(OcelotErrorCode.UnableToCompleteRequestError)] - [InlineData(OcelotErrorCode.UnableToCreateAuthenticationHandlerError)] - [InlineData(OcelotErrorCode.UnableToFindDownstreamRouteError)] - [InlineData(OcelotErrorCode.UnableToFindLoadBalancerError)] - [InlineData(OcelotErrorCode.UnableToFindServiceDiscoveryProviderError)] - [InlineData(OcelotErrorCode.UnableToFindQoSProviderError)] - [InlineData(OcelotErrorCode.UnableToSetConfigInConsulError)] - [InlineData(OcelotErrorCode.UnknownError)] - [InlineData(OcelotErrorCode.UnmappableRequestError)] - [InlineData(OcelotErrorCode.UnsupportedAuthenticationProviderError)] - public void should_return_not_found(OcelotErrorCode errorCode) - { - ShouldMapErrorToStatusCode(errorCode, HttpStatusCode.NotFound); - } - - [Fact] - public void AuthenticationErrorsHaveHighestPriority() - { - var errors = new List - { - OcelotErrorCode.CannotAddDataError, - OcelotErrorCode.CannotFindClaimError, - OcelotErrorCode.UnauthenticatedError, - OcelotErrorCode.RequestTimedOutError, - }; - - ShouldMapErrorsToStatusCode(errors, HttpStatusCode.Unauthorized); - } - - [Fact] - public void AuthorisationErrorsHaveSecondHighestPriority() - { - var errors = new List - { - OcelotErrorCode.CannotAddDataError, - OcelotErrorCode.CannotFindClaimError, - OcelotErrorCode.RequestTimedOutError - }; - - ShouldMapErrorsToStatusCode(errors, HttpStatusCode.Forbidden); - } - - [Fact] - public void ServiceUnavailableErrorsHaveThirdHighestPriority() - { - var errors = new List - { - OcelotErrorCode.CannotAddDataError, - OcelotErrorCode.RequestTimedOutError - }; - - ShouldMapErrorsToStatusCode(errors, HttpStatusCode.ServiceUnavailable); - } - - [Fact] - public void check_we_have_considered_all_errors_in_these_tests() - { - // If this test fails then it's because the number of error codes has changed. - // You should make the appropriate changes to the test cases here to ensure - // they cover all the error codes, and then modify this assertion. - Enum.GetNames(typeof(OcelotErrorCode)).Length.ShouldBe(32, "Looks like the number of error codes has changed. Do you need to modify ErrorsToHttpStatusCodeMapper?"); - } - - private void ShouldMapErrorToStatusCode(OcelotErrorCode errorCode, HttpStatusCode expectedHttpStatusCode) - { - ShouldMapErrorsToStatusCode(new List { errorCode }, expectedHttpStatusCode); - } - - private void ShouldMapErrorsToStatusCode(List errorCodes, HttpStatusCode expectedHttpStatusCode) - { - var errors = new List(); - - foreach(var errorCode in errorCodes) - { - errors.Add(new AnyError(errorCode)); - } - - this.Given(x => x.GivenThereAreErrors(errors)) - .When(x => x.WhenIGetErrorStatusCode()) - .Then(x => x.ThenTheResponseIsStatusCodeIs(expectedHttpStatusCode)) - .BDDfy(); - } - - private void GivenThereAreErrors(List errors) - { - _errors = errors; - } - - private void WhenIGetErrorStatusCode() - { - _result = _codeMapper.Map(_errors); - } - - private void ThenTheResponseIsStatusCodeIs(int expectedCode) - { - _result.ShouldBe(expectedCode); - } - - private void ThenTheResponseIsStatusCodeIs(HttpStatusCode expectedCode) - { - _result.ShouldBe((int)expectedCode); - } - } -} +using System; +using System.Collections.Generic; +using System.Net; +using Ocelot.Errors; +using Ocelot.Responder; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Responder +{ + public class ErrorsToHttpStatusCodeMapperTests + { + private readonly IErrorsToHttpStatusCodeMapper _codeMapper; + private int _result; + private List _errors; + + public ErrorsToHttpStatusCodeMapperTests() + { + _codeMapper = new ErrorsToHttpStatusCodeMapper(); + } + + [Theory] + [InlineData(OcelotErrorCode.UnauthenticatedError)] + public void should_return_unauthorized(OcelotErrorCode errorCode) + { + ShouldMapErrorToStatusCode(errorCode, HttpStatusCode.Unauthorized); + } + + [Theory] + [InlineData(OcelotErrorCode.CannotFindClaimError)] + [InlineData(OcelotErrorCode.ClaimValueNotAuthorisedError)] + [InlineData(OcelotErrorCode.ScopeNotAuthorisedError)] + [InlineData(OcelotErrorCode.UnauthorizedError)] + [InlineData(OcelotErrorCode.UserDoesNotHaveClaimError)] + public void should_return_forbidden(OcelotErrorCode errorCode) + { + ShouldMapErrorToStatusCode(errorCode, HttpStatusCode.Forbidden); + } + + [Theory] + [InlineData(OcelotErrorCode.RequestTimedOutError)] + public void should_return_service_unavailable(OcelotErrorCode errorCode) + { + ShouldMapErrorToStatusCode(OcelotErrorCode.RequestTimedOutError, HttpStatusCode.ServiceUnavailable); + } + + + [Theory] + [InlineData(OcelotErrorCode.CannotAddDataError)] + [InlineData(OcelotErrorCode.CannotFindDataError)] + [InlineData(OcelotErrorCode.DownstreamHostNullOrEmptyError)] + [InlineData(OcelotErrorCode.DownstreamPathNullOrEmptyError)] + [InlineData(OcelotErrorCode.DownstreampathTemplateAlreadyUsedError)] + [InlineData(OcelotErrorCode.DownstreamPathTemplateContainsSchemeError)] + [InlineData(OcelotErrorCode.DownstreamSchemeNullOrEmptyError)] + [InlineData(OcelotErrorCode.FileValidationFailedError)] + [InlineData(OcelotErrorCode.InstructionNotForClaimsError)] + [InlineData(OcelotErrorCode.NoInstructionsError)] + [InlineData(OcelotErrorCode.ParsingConfigurationHeaderError)] + [InlineData(OcelotErrorCode.RateLimitOptionsError)] + [InlineData(OcelotErrorCode.ServicesAreEmptyError)] + [InlineData(OcelotErrorCode.ServicesAreNullError)] + [InlineData(OcelotErrorCode.UnableToCompleteRequestError)] + [InlineData(OcelotErrorCode.UnableToCreateAuthenticationHandlerError)] + [InlineData(OcelotErrorCode.UnableToFindDownstreamRouteError)] + [InlineData(OcelotErrorCode.UnableToFindLoadBalancerError)] + [InlineData(OcelotErrorCode.UnableToFindServiceDiscoveryProviderError)] + [InlineData(OcelotErrorCode.UnableToFindQoSProviderError)] + [InlineData(OcelotErrorCode.UnableToSetConfigInConsulError)] + [InlineData(OcelotErrorCode.UnknownError)] + [InlineData(OcelotErrorCode.UnmappableRequestError)] + [InlineData(OcelotErrorCode.UnsupportedAuthenticationProviderError)] + public void should_return_not_found(OcelotErrorCode errorCode) + { + ShouldMapErrorToStatusCode(errorCode, HttpStatusCode.NotFound); + } + + [Fact] + public void AuthenticationErrorsHaveHighestPriority() + { + var errors = new List + { + OcelotErrorCode.CannotAddDataError, + OcelotErrorCode.CannotFindClaimError, + OcelotErrorCode.UnauthenticatedError, + OcelotErrorCode.RequestTimedOutError, + }; + + ShouldMapErrorsToStatusCode(errors, HttpStatusCode.Unauthorized); + } + + [Fact] + public void AuthorisationErrorsHaveSecondHighestPriority() + { + var errors = new List + { + OcelotErrorCode.CannotAddDataError, + OcelotErrorCode.CannotFindClaimError, + OcelotErrorCode.RequestTimedOutError + }; + + ShouldMapErrorsToStatusCode(errors, HttpStatusCode.Forbidden); + } + + [Fact] + public void ServiceUnavailableErrorsHaveThirdHighestPriority() + { + var errors = new List + { + OcelotErrorCode.CannotAddDataError, + OcelotErrorCode.RequestTimedOutError + }; + + ShouldMapErrorsToStatusCode(errors, HttpStatusCode.ServiceUnavailable); + } + + [Fact] + public void check_we_have_considered_all_errors_in_these_tests() + { + // If this test fails then it's because the number of error codes has changed. + // You should make the appropriate changes to the test cases here to ensure + // they cover all the error codes, and then modify this assertion. + Enum.GetNames(typeof(OcelotErrorCode)).Length.ShouldBe(32, "Looks like the number of error codes has changed. Do you need to modify ErrorsToHttpStatusCodeMapper?"); + } + + private void ShouldMapErrorToStatusCode(OcelotErrorCode errorCode, HttpStatusCode expectedHttpStatusCode) + { + ShouldMapErrorsToStatusCode(new List { errorCode }, expectedHttpStatusCode); + } + + private void ShouldMapErrorsToStatusCode(List errorCodes, HttpStatusCode expectedHttpStatusCode) + { + var errors = new List(); + + foreach(var errorCode in errorCodes) + { + errors.Add(new AnyError(errorCode)); + } + + this.Given(x => x.GivenThereAreErrors(errors)) + .When(x => x.WhenIGetErrorStatusCode()) + .Then(x => x.ThenTheResponseIsStatusCodeIs(expectedHttpStatusCode)) + .BDDfy(); + } + + private void GivenThereAreErrors(List errors) + { + _errors = errors; + } + + private void WhenIGetErrorStatusCode() + { + _result = _codeMapper.Map(_errors); + } + + private void ThenTheResponseIsStatusCodeIs(int expectedCode) + { + _result.ShouldBe(expectedCode); + } + + private void ThenTheResponseIsStatusCodeIs(HttpStatusCode expectedCode) + { + _result.ShouldBe((int)expectedCode); + } + } +} diff --git a/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs b/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs index 3cfa00e5f..a8bf54754 100644 --- a/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs @@ -1,72 +1,72 @@ -namespace Ocelot.UnitTests.Responder -{ - using System.Net.Http; - using Microsoft.AspNetCore.Builder; - using Microsoft.Extensions.DependencyInjection; - using Moq; - using Ocelot.Logging; - using Ocelot.Responder; - using Ocelot.Responder.Middleware; - using Ocelot.Responses; - using TestStack.BDDfy; - using Xunit; - - public class ResponderMiddlewareTests : ServerHostedMiddlewareTest - { - private readonly Mock _responder; - private readonly Mock _codeMapper; - private OkResponse _response; - - public ResponderMiddlewareTests() - { - _responder = new Mock(); - _codeMapper = new Mock(); - - GivenTheTestServerIsConfigured(); - } - - [Fact] - public void should_not_return_any_errors() - { - this.Given(x => x.GivenTheHttpResponseMessageIs(new HttpResponseMessage())) - .And(x => x.GivenThereAreNoPipelineErrors()) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenThereAreNoErrors()) - .BDDfy(); - } - - protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) - { - services.AddSingleton(); - services.AddLogging(); - services.AddSingleton(_codeMapper.Object); - services.AddSingleton(_responder.Object); - services.AddSingleton(ScopedRepository.Object); - } - - protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) - { - app.UseResponderMiddleware(); - } - - private void GivenTheHttpResponseMessageIs(HttpResponseMessage response) - { - _response = new OkResponse(response); - ScopedRepository - .Setup(x => x.Get(It.IsAny())) - .Returns(_response); - } - - private void GivenThereAreNoPipelineErrors() - { - ScopedRepository - .Setup(x => x.Get(It.IsAny())) - .Returns(new OkResponse(false)); - } - - private void ThenThereAreNoErrors() - { - //todo a better assert? - } - } -} +namespace Ocelot.UnitTests.Responder +{ + using System.Net.Http; + using Microsoft.AspNetCore.Builder; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using Ocelot.Logging; + using Ocelot.Responder; + using Ocelot.Responder.Middleware; + using Ocelot.Responses; + using TestStack.BDDfy; + using Xunit; + + public class ResponderMiddlewareTests : ServerHostedMiddlewareTest + { + private readonly Mock _responder; + private readonly Mock _codeMapper; + private OkResponse _response; + + public ResponderMiddlewareTests() + { + _responder = new Mock(); + _codeMapper = new Mock(); + + GivenTheTestServerIsConfigured(); + } + + [Fact] + public void should_not_return_any_errors() + { + this.Given(x => x.GivenTheHttpResponseMessageIs(new HttpResponseMessage())) + .And(x => x.GivenThereAreNoPipelineErrors()) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenThereAreNoErrors()) + .BDDfy(); + } + + protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + { + services.AddSingleton(); + services.AddLogging(); + services.AddSingleton(_codeMapper.Object); + services.AddSingleton(_responder.Object); + services.AddSingleton(ScopedRepository.Object); + } + + protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) + { + app.UseResponderMiddleware(); + } + + private void GivenTheHttpResponseMessageIs(HttpResponseMessage response) + { + _response = new OkResponse(response); + ScopedRepository + .Setup(x => x.Get(It.IsAny())) + .Returns(_response); + } + + private void GivenThereAreNoPipelineErrors() + { + ScopedRepository + .Setup(x => x.Get(It.IsAny())) + .Returns(new OkResponse(false)); + } + + private void ThenThereAreNoErrors() + { + //todo a better assert? + } + } +} diff --git a/test/Ocelot.UnitTests/ServerHostedMiddlewareTest.cs b/test/Ocelot.UnitTests/ServerHostedMiddlewareTest.cs index a5b8016ab..15a2d6739 100644 --- a/test/Ocelot.UnitTests/ServerHostedMiddlewareTest.cs +++ b/test/Ocelot.UnitTests/ServerHostedMiddlewareTest.cs @@ -1,68 +1,68 @@ -namespace Ocelot.UnitTests -{ - using System; - using System.IO; - using System.Net.Http; - using Microsoft.AspNetCore.TestHost; - using Microsoft.AspNetCore.Hosting; - using Microsoft.Extensions.DependencyInjection; - using Microsoft.AspNetCore.Builder; - using Moq; - using Ocelot.Infrastructure.RequestData; - - public abstract class ServerHostedMiddlewareTest : IDisposable - { - protected TestServer Server { get; private set; } - protected HttpClient Client { get; private set; } - protected string Url { get; private set; } - protected HttpResponseMessage ResponseMessage { get; private set; } - protected Mock ScopedRepository { get; private set; } - - public ServerHostedMiddlewareTest() - { - Url = "http://localhost:51879"; - ScopedRepository = new Mock(); - } - - protected virtual void GivenTheTestServerIsConfigured() - { - var builder = new WebHostBuilder() - .ConfigureServices(x => GivenTheTestServerServicesAreConfigured(x)) - .UseUrls(Url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .Configure(app => GivenTheTestServerPipelineIsConfigured(app)); - - Server = new TestServer(builder); - Client = Server.CreateClient(); - } - - protected virtual void GivenTheTestServerServicesAreConfigured(IServiceCollection services) - { - // override this in your test fixture to set up service dependencies - } - - protected virtual void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) - { - // override this in your test fixture to set up the test server pipeline - } - - protected void WhenICallTheMiddleware() - { - ResponseMessage = Client.GetAsync(Url).Result; - } - - protected void WhenICallTheMiddlewareWithTheRequestIdKey(string requestIdKey, string value) - { - Client.DefaultRequestHeaders.Add(requestIdKey, value); - ResponseMessage = Client.GetAsync(Url).Result; - } - - public void Dispose() - { - Client.Dispose(); - Server.Dispose(); - } - } -} +namespace Ocelot.UnitTests +{ + using System; + using System.IO; + using System.Net.Http; + using Microsoft.AspNetCore.TestHost; + using Microsoft.AspNetCore.Hosting; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.AspNetCore.Builder; + using Moq; + using Ocelot.Infrastructure.RequestData; + + public abstract class ServerHostedMiddlewareTest : IDisposable + { + protected TestServer Server { get; private set; } + protected HttpClient Client { get; private set; } + protected string Url { get; private set; } + protected HttpResponseMessage ResponseMessage { get; private set; } + protected Mock ScopedRepository { get; private set; } + + public ServerHostedMiddlewareTest() + { + Url = "http://localhost:51879"; + ScopedRepository = new Mock(); + } + + protected virtual void GivenTheTestServerIsConfigured() + { + var builder = new WebHostBuilder() + .ConfigureServices(x => GivenTheTestServerServicesAreConfigured(x)) + .UseUrls(Url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .Configure(app => GivenTheTestServerPipelineIsConfigured(app)); + + Server = new TestServer(builder); + Client = Server.CreateClient(); + } + + protected virtual void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + { + // override this in your test fixture to set up service dependencies + } + + protected virtual void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) + { + // override this in your test fixture to set up the test server pipeline + } + + protected void WhenICallTheMiddleware() + { + ResponseMessage = Client.GetAsync(Url).Result; + } + + protected void WhenICallTheMiddlewareWithTheRequestIdKey(string requestIdKey, string value) + { + Client.DefaultRequestHeaders.Add(requestIdKey, value); + ResponseMessage = Client.GetAsync(Url).Result; + } + + public void Dispose() + { + Client.Dispose(); + Server.Dispose(); + } + } +} diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ConfigurationServiceProviderTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ConfigurationServiceProviderTests.cs index 282f8ec12..08b67820c 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ConfigurationServiceProviderTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ConfigurationServiceProviderTests.cs @@ -1,52 +1,52 @@ -using System.Collections.Generic; -using Ocelot.ServiceDiscovery; -using Ocelot.Values; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.ServiceDiscovery -{ - public class ConfigurationServiceProviderTests - { - private ConfigurationServiceProvider _serviceProvider; - private List _result; - private List _expected; - - [Fact] - public void should_return_services() - { - var hostAndPort = new HostAndPort("127.0.0.1", 80); - - var services = new List - { - new Service("product", hostAndPort, string.Empty, string.Empty, new string[0]) - }; - - this.Given(x => x.GivenServices(services)) - .When(x => x.WhenIGetTheService()) - .Then(x => x.ThenTheFollowingIsReturned(services)) - .BDDfy(); - } - - private void GivenServices(List services) - { - _expected = services; - } - - private void WhenIGetTheService() - { - _serviceProvider = new ConfigurationServiceProvider(_expected); - _result = _serviceProvider.Get().Result; - } - - private void ThenTheFollowingIsReturned(List services) - { - _result[0].HostAndPort.DownstreamHost.ShouldBe(services[0].HostAndPort.DownstreamHost); - - _result[0].HostAndPort.DownstreamPort.ShouldBe(services[0].HostAndPort.DownstreamPort); - - _result[0].Name.ShouldBe(services[0].Name); - } - } -} \ No newline at end of file +using System.Collections.Generic; +using Ocelot.ServiceDiscovery; +using Ocelot.Values; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.ServiceDiscovery +{ + public class ConfigurationServiceProviderTests + { + private ConfigurationServiceProvider _serviceProvider; + private List _result; + private List _expected; + + [Fact] + public void should_return_services() + { + var hostAndPort = new ServiceHostAndPort("127.0.0.1", 80); + + var services = new List + { + new Service("product", hostAndPort, string.Empty, string.Empty, new string[0]) + }; + + this.Given(x => x.GivenServices(services)) + .When(x => x.WhenIGetTheService()) + .Then(x => x.ThenTheFollowingIsReturned(services)) + .BDDfy(); + } + + private void GivenServices(List services) + { + _expected = services; + } + + private void WhenIGetTheService() + { + _serviceProvider = new ConfigurationServiceProvider(_expected); + _result = _serviceProvider.Get().Result; + } + + private void ThenTheFollowingIsReturned(List services) + { + _result[0].HostAndPort.DownstreamHost.ShouldBe(services[0].HostAndPort.DownstreamHost); + + _result[0].HostAndPort.DownstreamPort.ShouldBe(services[0].HostAndPort.DownstreamPort); + + _result[0].Name.ShouldBe(services[0].Name); + } + } +} diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs index f1db9f80d..55ecf5022 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs @@ -1,69 +1,107 @@ -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.ServiceDiscovery; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.ServiceDiscovery -{ - public class ServiceProviderFactoryTests - { - private ServiceProviderConfiguration _serviceConfig; - private IServiceDiscoveryProvider _result; - private readonly ServiceDiscoveryProviderFactory _factory; - private ReRoute _reRoute; - - public ServiceProviderFactoryTests() - { - _factory = new ServiceDiscoveryProviderFactory(); - } - - [Fact] - public void should_return_no_service_provider() - { - var serviceConfig = new ServiceProviderConfigurationBuilder() - .Build(); - - var reRoute = new ReRouteBuilder().Build(); - - this.Given(x => x.GivenTheReRoute(serviceConfig, reRoute)) - .When(x => x.WhenIGetTheServiceProvider()) - .Then(x => x.ThenTheServiceProviderIs()) - .BDDfy(); - } - - [Fact] - public void should_return_consul_service_provider() - { - var reRoute = new ReRouteBuilder() - .WithServiceName("product") - .WithUseServiceDiscovery(true) - .Build(); - - var serviceConfig = new ServiceProviderConfigurationBuilder() - .Build(); - - this.Given(x => x.GivenTheReRoute(serviceConfig, reRoute)) - .When(x => x.WhenIGetTheServiceProvider()) - .Then(x => x.ThenTheServiceProviderIs()) - .BDDfy(); - } - - private void GivenTheReRoute(ServiceProviderConfiguration serviceConfig, ReRoute reRoute) - { - _serviceConfig = serviceConfig; - _reRoute = reRoute; - } - - private void WhenIGetTheServiceProvider() - { - _result = _factory.Get(_serviceConfig, _reRoute); - } - - private void ThenTheServiceProviderIs() - { - _result.ShouldBeOfType(); - } - } +using System; +using System.Collections.Generic; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.ServiceDiscovery; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.ServiceDiscovery +{ + public class ServiceProviderFactoryTests + { + private ServiceProviderConfiguration _serviceConfig; + private IServiceDiscoveryProvider _result; + private readonly ServiceDiscoveryProviderFactory _factory; + private ReRoute _reRoute; + + public ServiceProviderFactoryTests() + { + _factory = new ServiceDiscoveryProviderFactory(); + } + + [Fact] + public void should_return_no_service_provider() + { + var serviceConfig = new ServiceProviderConfigurationBuilder() + .Build(); + + var reRoute = new ReRouteBuilder().Build(); + + this.Given(x => x.GivenTheReRoute(serviceConfig, reRoute)) + .When(x => x.WhenIGetTheServiceProvider()) + .Then(x => x.ThenTheServiceProviderIs()) + .BDDfy(); + } + + [Fact] + public void should_return_list_of_configuration_services() + { + var serviceConfig = new ServiceProviderConfigurationBuilder() + .Build(); + + var downstreamAddresses = new List() + { + new DownstreamHostAndPort("asdf.com", 80), + new DownstreamHostAndPort("abc.com", 80) + }; + + var reRoute = new ReRouteBuilder().WithDownstreamAddresses(downstreamAddresses).Build(); + + this.Given(x => x.GivenTheReRoute(serviceConfig, reRoute)) + .When(x => x.WhenIGetTheServiceProvider()) + .Then(x => x.ThenTheServiceProviderIs()) + .Then(x => ThenTheFollowingServicesAreReturned(downstreamAddresses)) + .BDDfy(); + } + + private void ThenTheFollowingServicesAreReturned(List downstreamAddresses) + { + var result = (ConfigurationServiceProvider)_result; + var services = result.Get().Result; + + for (int i = 0; i < services.Count; i++) + { + var service = services[i]; + var downstreamAddress = downstreamAddresses[i]; + + service.HostAndPort.DownstreamHost.ShouldBe(downstreamAddress.Host); + service.HostAndPort.DownstreamPort.ShouldBe(downstreamAddress.Port); + } + } + + [Fact] + public void should_return_consul_service_provider() + { + var reRoute = new ReRouteBuilder() + .WithServiceName("product") + .WithUseServiceDiscovery(true) + .Build(); + + var serviceConfig = new ServiceProviderConfigurationBuilder() + .Build(); + + this.Given(x => x.GivenTheReRoute(serviceConfig, reRoute)) + .When(x => x.WhenIGetTheServiceProvider()) + .Then(x => x.ThenTheServiceProviderIs()) + .BDDfy(); + } + + private void GivenTheReRoute(ServiceProviderConfiguration serviceConfig, ReRoute reRoute) + { + _serviceConfig = serviceConfig; + _reRoute = reRoute; + } + + private void WhenIGetTheServiceProvider() + { + _result = _factory.Get(_serviceConfig, _reRoute); + } + + private void ThenTheServiceProviderIs() + { + _result.ShouldBeOfType(); + } + } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceRegistryTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceRegistryTests.cs index 30a25810c..8a41fe7e7 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceRegistryTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceRegistryTests.cs @@ -1,138 +1,138 @@ -using System.Collections.Generic; -using Ocelot.Values; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -// nothing in use -namespace Ocelot.UnitTests.ServiceDiscovery -{ - public class ServiceRegistryTests - { - private Service _service; - private List _services; - private ServiceRegistry _serviceRegistry; - private ServiceRepository _serviceRepository; - - public ServiceRegistryTests() - { - _serviceRepository = new ServiceRepository(); - _serviceRegistry = new ServiceRegistry(_serviceRepository); - } - - [Fact] - public void should_register_service() - { - this.Given(x => x.GivenAServiceToRegister("product", "localhost:5000", 80)) - .When(x => x.WhenIRegisterTheService()) - .Then(x => x.ThenTheServiceIsRegistered()) - .BDDfy(); - } - - [Fact] - public void should_lookup_service() - { - this.Given(x => x.GivenAServiceIsRegistered("product", "localhost:600", 80)) - .When(x => x.WhenILookupTheService("product")) - .Then(x => x.ThenTheServiceDetailsAreReturned()) - .BDDfy(); - } - - private void ThenTheServiceDetailsAreReturned() - { - _services[0].HostAndPort.DownstreamHost.ShouldBe(_service.HostAndPort.DownstreamHost); - _services[0].HostAndPort.DownstreamPort.ShouldBe(_service.HostAndPort.DownstreamPort); - _services[0].Name.ShouldBe(_service.Name); - } - - private void WhenILookupTheService(string name) - { - _services = _serviceRegistry.Lookup(name); - } - - private void GivenAServiceIsRegistered(string name, string address, int port) - { - _service = new Service(name, new HostAndPort(address, port), string.Empty, string.Empty, new string[0]); - _serviceRepository.Set(_service); - } - - private void GivenAServiceToRegister(string name, string address, int port) - { - _service = new Service(name, new HostAndPort(address, port), string.Empty, string.Empty, new string[0]); - } - - private void WhenIRegisterTheService() - { - _serviceRegistry.Register(_service); - } - - private void ThenTheServiceIsRegistered() - { - var serviceNameAndAddress = _serviceRepository.Get(_service.Name); - serviceNameAndAddress[0].HostAndPort.DownstreamHost.ShouldBe(_service.HostAndPort.DownstreamHost); - serviceNameAndAddress[0].HostAndPort.DownstreamPort.ShouldBe(_service.HostAndPort.DownstreamPort); - serviceNameAndAddress[0].Name.ShouldBe(_service.Name); - } - } - - public interface IServiceRegistry - { - void Register(Service serviceNameAndAddress); - List Lookup(string name); - } - - public class ServiceRegistry : IServiceRegistry - { - private readonly IServiceRepository _repository; - public ServiceRegistry(IServiceRepository repository) - { - _repository = repository; - } - - public void Register(Service serviceNameAndAddress) - { - _repository.Set(serviceNameAndAddress); - } - - public List Lookup(string name) - { - return _repository.Get(name); - } - } - - public interface IServiceRepository - { - List Get(string serviceName); - void Set(Service serviceNameAndAddress); - } - - public class ServiceRepository : IServiceRepository - { - private Dictionary> _registeredServices; - - public ServiceRepository() - { - _registeredServices = new Dictionary>(); - } - - public List Get(string serviceName) - { - return _registeredServices[serviceName]; - } - - public void Set(Service serviceNameAndAddress) - { - List services; - if(_registeredServices.TryGetValue(serviceNameAndAddress.Name, out services)) - { - services.Add(serviceNameAndAddress); - _registeredServices[serviceNameAndAddress.Name] = services; - } - else - { - _registeredServices[serviceNameAndAddress.Name] = new List(){ serviceNameAndAddress }; - } - - } - } -} \ No newline at end of file +using System.Collections.Generic; +using Ocelot.Values; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +// nothing in use +namespace Ocelot.UnitTests.ServiceDiscovery +{ + public class ServiceRegistryTests + { + private Service _service; + private List _services; + private ServiceRegistry _serviceRegistry; + private ServiceRepository _serviceRepository; + + public ServiceRegistryTests() + { + _serviceRepository = new ServiceRepository(); + _serviceRegistry = new ServiceRegistry(_serviceRepository); + } + + [Fact] + public void should_register_service() + { + this.Given(x => x.GivenAServiceToRegister("product", "localhost:5000", 80)) + .When(x => x.WhenIRegisterTheService()) + .Then(x => x.ThenTheServiceIsRegistered()) + .BDDfy(); + } + + [Fact] + public void should_lookup_service() + { + this.Given(x => x.GivenAServiceIsRegistered("product", "localhost:600", 80)) + .When(x => x.WhenILookupTheService("product")) + .Then(x => x.ThenTheServiceDetailsAreReturned()) + .BDDfy(); + } + + private void ThenTheServiceDetailsAreReturned() + { + _services[0].HostAndPort.DownstreamHost.ShouldBe(_service.HostAndPort.DownstreamHost); + _services[0].HostAndPort.DownstreamPort.ShouldBe(_service.HostAndPort.DownstreamPort); + _services[0].Name.ShouldBe(_service.Name); + } + + private void WhenILookupTheService(string name) + { + _services = _serviceRegistry.Lookup(name); + } + + private void GivenAServiceIsRegistered(string name, string address, int port) + { + _service = new Service(name, new ServiceHostAndPort(address, port), string.Empty, string.Empty, new string[0]); + _serviceRepository.Set(_service); + } + + private void GivenAServiceToRegister(string name, string address, int port) + { + _service = new Service(name, new ServiceHostAndPort(address, port), string.Empty, string.Empty, new string[0]); + } + + private void WhenIRegisterTheService() + { + _serviceRegistry.Register(_service); + } + + private void ThenTheServiceIsRegistered() + { + var serviceNameAndAddress = _serviceRepository.Get(_service.Name); + serviceNameAndAddress[0].HostAndPort.DownstreamHost.ShouldBe(_service.HostAndPort.DownstreamHost); + serviceNameAndAddress[0].HostAndPort.DownstreamPort.ShouldBe(_service.HostAndPort.DownstreamPort); + serviceNameAndAddress[0].Name.ShouldBe(_service.Name); + } + } + + public interface IServiceRegistry + { + void Register(Service serviceNameAndAddress); + List Lookup(string name); + } + + public class ServiceRegistry : IServiceRegistry + { + private readonly IServiceRepository _repository; + public ServiceRegistry(IServiceRepository repository) + { + _repository = repository; + } + + public void Register(Service serviceNameAndAddress) + { + _repository.Set(serviceNameAndAddress); + } + + public List Lookup(string name) + { + return _repository.Get(name); + } + } + + public interface IServiceRepository + { + List Get(string serviceName); + void Set(Service serviceNameAndAddress); + } + + public class ServiceRepository : IServiceRepository + { + private Dictionary> _registeredServices; + + public ServiceRepository() + { + _registeredServices = new Dictionary>(); + } + + public List Get(string serviceName) + { + return _registeredServices[serviceName]; + } + + public void Set(Service serviceNameAndAddress) + { + List services; + if(_registeredServices.TryGetValue(serviceNameAndAddress.Name, out services)) + { + services.Add(serviceNameAndAddress); + _registeredServices[serviceNameAndAddress.Name] = services; + } + else + { + _registeredServices[serviceNameAndAddress.Name] = new List(){ serviceNameAndAddress }; + } + + } + } +} diff --git a/test/Ocelot.UnitTests/TestData/AuthenticationConfigTestData.cs b/test/Ocelot.UnitTests/TestData/AuthenticationConfigTestData.cs index ef9a454a8..0ae4096a4 100644 --- a/test/Ocelot.UnitTests/TestData/AuthenticationConfigTestData.cs +++ b/test/Ocelot.UnitTests/TestData/AuthenticationConfigTestData.cs @@ -1,63 +1,63 @@ -namespace Ocelot.UnitTests.TestData -{ - using System.Collections.Generic; - using Ocelot.Configuration.File; - - public class AuthenticationConfigTestData - { - public static IEnumerable GetAuthenticationData() - { - yield return new object[] - { - new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - UpstreamPathTemplate = "/api/products/{productId}", - DownstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = new List { "Get" }, - ReRouteIsCaseSensitive = true, - AuthenticationOptions = new FileAuthenticationOptions - { - AuthenticationProviderKey = "Test", - AllowedScopes = new List(), - }, - AddHeadersToRequest = - { - { "CustomerId", "Claims[CustomerId] > value" }, - } - } - } - } - }; - - yield return new object[] - { - new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - UpstreamPathTemplate = "/api/products/{productId}", - DownstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = new List { "Get" }, - ReRouteIsCaseSensitive = true, - AuthenticationOptions = new FileAuthenticationOptions - { - AuthenticationProviderKey = "Test", - AllowedScopes = new List(), - }, - AddHeadersToRequest = - { - { "CustomerId", "Claims[CustomerId] > value" }, - } - } - } - } - }; - } - } -} +namespace Ocelot.UnitTests.TestData +{ + using System.Collections.Generic; + using Ocelot.Configuration.File; + + public class AuthenticationConfigTestData + { + public static IEnumerable GetAuthenticationData() + { + yield return new object[] + { + new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + UpstreamPathTemplate = "/api/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", + UpstreamHttpMethod = new List { "Get" }, + ReRouteIsCaseSensitive = true, + AuthenticationOptions = new FileAuthenticationOptions + { + AuthenticationProviderKey = "Test", + AllowedScopes = new List(), + }, + AddHeadersToRequest = + { + { "CustomerId", "Claims[CustomerId] > value" }, + } + } + } + } + }; + + yield return new object[] + { + new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + UpstreamPathTemplate = "/api/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", + UpstreamHttpMethod = new List { "Get" }, + ReRouteIsCaseSensitive = true, + AuthenticationOptions = new FileAuthenticationOptions + { + AuthenticationProviderKey = "Test", + AllowedScopes = new List(), + }, + AddHeadersToRequest = + { + { "CustomerId", "Claims[CustomerId] > value" }, + } + } + } + } + }; + } + } +} diff --git a/test/Ocelot.UnitTests/Waiter.cs b/test/Ocelot.UnitTests/Waiter.cs index d76a0109f..8fb51ae53 100644 --- a/test/Ocelot.UnitTests/Waiter.cs +++ b/test/Ocelot.UnitTests/Waiter.cs @@ -1,55 +1,55 @@ -using System; -using System.Diagnostics; - -namespace Ocelot.UnitTests -{ - public class Wait - { - public static Waiter WaitFor(int milliSeconds) - { - return new Waiter(milliSeconds); - } - } - - public class Waiter - { - private readonly int _milliSeconds; - - public Waiter(int milliSeconds) - { - _milliSeconds = milliSeconds; - } - - public bool Until(Func condition) - { - var stopwatch = Stopwatch.StartNew(); - var passed = false; - while (stopwatch.ElapsedMilliseconds < _milliSeconds) - { - if (condition.Invoke()) - { - passed = true; - break; - } - } - - return passed; - } - - public bool Until(Func condition) - { - var stopwatch = Stopwatch.StartNew(); - var passed = false; - while (stopwatch.ElapsedMilliseconds < _milliSeconds) - { - if (condition.Invoke()) - { - passed = true; - break; - } - } - - return passed; - } - } +using System; +using System.Diagnostics; + +namespace Ocelot.UnitTests +{ + public class Wait + { + public static Waiter WaitFor(int milliSeconds) + { + return new Waiter(milliSeconds); + } + } + + public class Waiter + { + private readonly int _milliSeconds; + + public Waiter(int milliSeconds) + { + _milliSeconds = milliSeconds; + } + + public bool Until(Func condition) + { + var stopwatch = Stopwatch.StartNew(); + var passed = false; + while (stopwatch.ElapsedMilliseconds < _milliSeconds) + { + if (condition.Invoke()) + { + passed = true; + break; + } + } + + return passed; + } + + public bool Until(Func condition) + { + var stopwatch = Stopwatch.StartNew(); + var passed = false; + while (stopwatch.ElapsedMilliseconds < _milliSeconds) + { + if (condition.Invoke()) + { + passed = true; + break; + } + } + + return passed; + } + } } \ No newline at end of file diff --git a/tools/packages.config b/tools/packages.config index e52a2c7e9..1d2d15349 100644 --- a/tools/packages.config +++ b/tools/packages.config @@ -1,4 +1,4 @@ - - - - + + + +