From cfa3fedc5390bc96e9bf5afe0d0e9300eface3ab Mon Sep 17 00:00:00 2001 From: Thiago Loureiro Date: Fri, 22 Mar 2019 23:39:53 +0100 Subject: [PATCH 1/7] Release/13.2.0 (#834) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix formatting in getting started page (#752) * updated release docs (#745) * Update README.md (#756) Fixed typo "Ocleot" * Fixed typo there => their (#763) * Some Typo fixes (#765) * Typo algorythm => algorithm (#764) * Typo querystring => query string (#766) * Typo usual => usually (#767) * Typos (#768) * kubernetes provider (#772) * feat: Kubernetes ServiceDiscoveryProvider * 编写k8s测试例子 * feat:fix kube config * feat: remove port * feat : complete the k8s test * feat : add kubeserviceDiscovery test * feat : add kube provider unittest * feat :add kubetnetes docs how to use ocelot with kubetnetes docs * keep the configuration as simple as possible, no qos, no cache * fix: use http * add PollingKubeServiceDiscovery * feat : refactor logger * feat : add pollkube docs * feat:Remove unnecessary code * feat : code-block json * fix issue #661 for Advanced aggregations (#704) * Add Advanced Aggregation Feature * fix overwrite error * distinct data for better performance * remove constructor parameter * fix tests issue * fix tests * fix tests issue * Add UnitTest and AcceptanceTest * fix responseKeys typo * Update SimpleJsonResponseAggregator.cs * change port * Fix code example for SSL Errors (#780) DangerousAcceptAnyServerCertificateValidator has to be set to "true" to disable certification validation, not "false". * Changed wording for ease of reading (#776) Just some wording changes for clarification. * Ignore response content if null (fix #785) (#786) * fix bug #791 (#795) * Update loadbalancer.rst (#796) * UriBuilder - remove leading question mark #747 (#794) * Update qualityofservice.rst (#801) Tiny typo * K8s package (#804) * feat: Kubernetes ServiceDiscoveryProvider * 编写k8s测试例子 * feat:fix kube config * feat: remove port * feat : complete the k8s test * feat : add kubeserviceDiscovery test * feat : add kube provider unittest * feat :add kubetnetes docs how to use ocelot with kubetnetes docs * keep the configuration as simple as possible, no qos, no cache * fix: use http * add PollingKubeServiceDiscovery * feat : refactor logger * feat : add pollkube docs * feat:Remove unnecessary code * feat : code-block json * feat: publish package Ocelot.Provider.Kubernetes * Okta integration (#807) Okta integration * update cliamsParser (#798) * update cliamsParser * update using * IOcelotBuilder opens the IMvcCoreBuilder property for easy customization (#790) * IOcelotBuilder opens the IMvcCoreBuilder property for easy customization * Adjustment code * nuget package (#809) * feat: Kubernetes ServiceDiscoveryProvider * 编写k8s测试例子 * feat:fix kube config * feat: remove port * feat : complete the k8s test * feat : add kubeserviceDiscovery test * feat : add kube provider unittest * feat :add kubetnetes docs how to use ocelot with kubetnetes docs * keep the configuration as simple as possible, no qos, no cache * fix: use http * add PollingKubeServiceDiscovery * feat : refactor logger * feat : add pollkube docs * feat:Remove unnecessary code * feat : code-block json * feat: publish package Ocelot.Provider.Kubernetes * feat : nuget package * fix: Namesapce Spelling wrong * fix:Namesapce Spelling Wrong * Fix: errors when using rate limiting (#811) * Fix: errors when using rate limiting Add: QuotaExceededError class for requesting too much Add: QuotaExceededError error code Add: Add an error when limit is reached Reflact: Extract GetResponseMessage method for getting default or configured response message for requ * Fix: modify check_we_have_considered_all_errors_in_these_tests for adding a new OcelotErrorCode * added missing COPY csproj files (#821) * Add note on In-Process hosting (#816) When using ASP.NET Core 2.2 with In-Process hosting in IIS it's important to use .UseIIS() instead of .UseIISIntegration(). * Fix bug: (#810) If the registered Consul node is unexpectedly down and not restarted immediately, other services should continue to find the registered service. * Fixed Dockerfile (missing Kubernetes) * Revert "Fix bug: (#810)" (#823) This reverts commit 19c80afb05290fac3a144f652cd663c8b513a559. * remove duplicate `IHttpRequester` register (#819) * remove duplicate `IHttpRequester` register * reserve the first * fix HttpRequesterMiddleware does not call next bug (#830) call next so that we can do something with the response, such as add some custom header etc... * Removed Packing to fix issues, will be sorted out after create a nuget package on Nuget.Org (#831) * Allows access to unpass node (#825) * Fix bug: If the registered Consul node is unexpectedly down and not restarted immediately, other services should continue to find the registered service. * fix bug: If the registered Consul node is unexpectedly down and not restarted immediately, other services should continue to find the registered service. * Updated FluentValidations Nuget Package (#833) --- Dockerfile | 9 ++ Ocelot.sln | 7 + README.md | 2 +- docs/building/releaseprocess.rst | 35 +++-- docs/features/authentication.rst | 22 ++- docs/features/configuration.rst | 15 +- docs/features/kubernetes.rst | 63 ++++++++ docs/features/loadbalancer.rst | 2 +- docs/features/qualityofservice.rst | 2 +- docs/features/raft.rst | 2 +- docs/features/requestaggregation.rst | 2 +- docs/features/routing.rst | 4 +- docs/features/servicediscovery.rst | 12 +- docs/index.rst | 3 +- docs/introduction/gettingstarted.rst | 8 +- docs/introduction/notsupported.rst | 2 +- samples/OelotKube/.dockerignore | 9 ++ .../OelotKube/ApiGateway/ApiGateway.csproj | 20 +++ samples/OelotKube/ApiGateway/Dockerfile | 21 +++ samples/OelotKube/ApiGateway/Program.cs | 32 ++++ .../ApiGateway/Properties/launchSettings.json | 32 ++++ samples/OelotKube/ApiGateway/Startup.cs | 36 +++++ .../ApiGateway/appsettings.Development.json | 9 ++ samples/OelotKube/ApiGateway/appsettings.json | 8 + samples/OelotKube/ApiGateway/ocelot.json | 20 +++ samples/OelotKube/Dockerfile | 22 +++ .../Controllers/ValuesController.cs | 45 ++++++ .../OelotKube/DownstreamService/Dockerfile | 19 +++ .../DownstreamService/Dockerfile.develop | 15 ++ .../DownstreamService.csproj | 15 ++ .../OelotKube/DownstreamService/Program.cs | 24 +++ .../Properties/launchSettings.json | 35 +++++ .../OelotKube/DownstreamService/Startup.cs | 41 +++++ .../appsettings.Development.json | 9 ++ .../DownstreamService/appsettings.json | 8 + samples/OelotKube/OelotKube.sln | 43 +++++ src/Ocelot.Provider.Consul/Consul.cs | 2 +- .../IKubeApiClientFactory.cs | 12 ++ .../KubeApiClientFactory.cs | 22 +++ .../KubeProvider.cs | 63 ++++++++ .../KubeRegistryConfiguration.cs | 22 +++ .../KubernetesProviderFactory.cs | 38 +++++ .../Ocelot.Provider.Kubernetes.csproj | 35 +++++ .../OcelotBuilderExtensions.cs | 15 ++ src/Ocelot.Provider.Kubernetes/PollKube.cs | 50 ++++++ .../Configuration/Builder/ReRouteBuilder.cs | 14 +- .../ServiceProviderConfigurationBuilder.cs | 9 +- .../Creator/AggregatesCreator.cs | 17 +- .../ServiceProviderConfigurationCreator.cs | 2 + .../File/AggregateReRouteConfig.cs | 13 ++ .../File/FileAggregateReRoute.cs | 3 +- .../File/FileServiceDiscoveryProvider.cs | 1 + .../Configuration/LoadBalancerOptions.cs | 6 +- src/Ocelot/Configuration/ReRoute.cs | 8 +- .../ServiceProviderConfiguration.cs | 10 +- .../DependencyInjection/IOcelotBuilder.cs | 2 + .../DependencyInjection/OcelotBuilder.cs | 15 +- src/Ocelot/Errors/OcelotErrorCode.cs | 3 +- .../Claims/Parser/ClaimsParser.cs | 10 +- .../InMemoryResponseAggregatorFactory.cs | 6 +- .../Middleware/Multiplexer/Multiplexer.cs | 114 +++++++++++--- .../SimpleJsonResponseAggregator.cs | 62 ++++++-- src/Ocelot/Ocelot.csproj | 2 +- .../Middleware/ClientRateLimitMiddleware.cs | 13 +- src/Ocelot/RateLimit/QuotaExceededError.cs | 12 ++ .../Request/Middleware/DownstreamRequest.cs | 14 +- .../Middleware/HttpRequesterMiddleware.cs | 4 +- src/Ocelot/Responder/HttpContextResponder.cs | 13 +- test/Ocelot.AcceptanceTests/AggregateTests.cs | 115 ++++++++++++-- .../ServiceDiscoveryTests.cs | 3 +- .../ServiceProviderCreatorTests.cs | 7 +- .../Kubernetes/KubeProviderFactoryTests.cs | 35 +++++ .../KubeServiceDiscoveryProviderTests.cs | 147 ++++++++++++++++++ .../OcelotBuilderExtensionsTests.cs | 70 +++++++++ ...ollingKubeServiceDiscoveryProviderTests.cs | 82 ++++++++++ .../ResponseAggregatorFactoryTests.cs | 3 +- .../SimpleJsonResponseAggregatorTests.cs | 56 ++++++- test/Ocelot.UnitTests/Ocelot.UnitTests.csproj | 3 +- .../ErrorsToHttpStatusCodeMapperTests.cs | 2 +- .../Responder/HttpContextResponderTests.cs | 17 ++ 80 files changed, 1677 insertions(+), 128 deletions(-) create mode 100644 docs/features/kubernetes.rst create mode 100644 samples/OelotKube/.dockerignore create mode 100644 samples/OelotKube/ApiGateway/ApiGateway.csproj create mode 100644 samples/OelotKube/ApiGateway/Dockerfile create mode 100644 samples/OelotKube/ApiGateway/Program.cs create mode 100644 samples/OelotKube/ApiGateway/Properties/launchSettings.json create mode 100644 samples/OelotKube/ApiGateway/Startup.cs create mode 100644 samples/OelotKube/ApiGateway/appsettings.Development.json create mode 100644 samples/OelotKube/ApiGateway/appsettings.json create mode 100644 samples/OelotKube/ApiGateway/ocelot.json create mode 100644 samples/OelotKube/Dockerfile create mode 100644 samples/OelotKube/DownstreamService/Controllers/ValuesController.cs create mode 100644 samples/OelotKube/DownstreamService/Dockerfile create mode 100644 samples/OelotKube/DownstreamService/Dockerfile.develop create mode 100644 samples/OelotKube/DownstreamService/DownstreamService.csproj create mode 100644 samples/OelotKube/DownstreamService/Program.cs create mode 100644 samples/OelotKube/DownstreamService/Properties/launchSettings.json create mode 100644 samples/OelotKube/DownstreamService/Startup.cs create mode 100644 samples/OelotKube/DownstreamService/appsettings.Development.json create mode 100644 samples/OelotKube/DownstreamService/appsettings.json create mode 100644 samples/OelotKube/OelotKube.sln create mode 100644 src/Ocelot.Provider.Kubernetes/IKubeApiClientFactory.cs create mode 100644 src/Ocelot.Provider.Kubernetes/KubeApiClientFactory.cs create mode 100644 src/Ocelot.Provider.Kubernetes/KubeProvider.cs create mode 100644 src/Ocelot.Provider.Kubernetes/KubeRegistryConfiguration.cs create mode 100644 src/Ocelot.Provider.Kubernetes/KubernetesProviderFactory.cs create mode 100644 src/Ocelot.Provider.Kubernetes/Ocelot.Provider.Kubernetes.csproj create mode 100644 src/Ocelot.Provider.Kubernetes/OcelotBuilderExtensions.cs create mode 100644 src/Ocelot.Provider.Kubernetes/PollKube.cs create mode 100644 src/Ocelot/Configuration/File/AggregateReRouteConfig.cs create mode 100644 src/Ocelot/RateLimit/QuotaExceededError.cs create mode 100644 test/Ocelot.UnitTests/Kubernetes/KubeProviderFactoryTests.cs create mode 100644 test/Ocelot.UnitTests/Kubernetes/KubeServiceDiscoveryProviderTests.cs create mode 100644 test/Ocelot.UnitTests/Kubernetes/OcelotBuilderExtensionsTests.cs create mode 100644 test/Ocelot.UnitTests/Kubernetes/PollingKubeServiceDiscoveryProviderTests.cs diff --git a/Dockerfile b/Dockerfile index 47814325..256d2278 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,11 +12,20 @@ WORKDIR /build #First we add only the project files so that we can cache nuget packages with dotnet restore COPY Ocelot.sln Ocelot.sln COPY src/Ocelot/Ocelot.csproj src/Ocelot/Ocelot.csproj +COPY src/Ocelot.Administration/Ocelot.Administration.csproj src/Ocelot.Administration/Ocelot.Administration.csproj +COPY src/Ocelot.Cache.CacheManager/Ocelot.Cache.CacheManager.csproj src/Ocelot.Cache.CacheManager/Ocelot.Cache.CacheManager.csproj +COPY src/Ocelot.Provider.Consul/Ocelot.Provider.Consul.csproj src/Ocelot.Provider.Consul/Ocelot.Provider.Consul.csproj +COPY src/Ocelot.Provider.Eureka/Ocelot.Provider.Eureka.csproj src/Ocelot.Provider.Eureka/Ocelot.Provider.Eureka.csproj +COPY src/Ocelot.Provider.Polly/Ocelot.Provider.Polly.csproj src/Ocelot.Provider.Polly/Ocelot.Provider.Polly.csproj +COPY src/Ocelot.Provider.Rafty/Ocelot.Provider.Rafty.csproj src/Ocelot.Provider.Rafty/Ocelot.Provider.Rafty.csproj +COPY src/Ocelot.Tracing.Butterfly/Ocelot.Tracing.Butterfly.csproj src/Ocelot.Tracing.Butterfly/Ocelot.Tracing.Butterfly.csproj +COPY src/Ocelot.Provider.Kubernetes/Ocelot.Provider.Kubernetes.csproj src/Ocelot.Provider.Kubernetes/Ocelot.Provider.Kubernetes.csproj COPY test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj COPY test/Ocelot.ManualTest/Ocelot.ManualTest.csproj test/Ocelot.ManualTest/Ocelot.ManualTest.csproj COPY test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj COPY test/Ocelot.UnitTests/Ocelot.UnitTests.csproj test/Ocelot.UnitTests/Ocelot.UnitTests.csproj COPY test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj + RUN dotnet restore #Now we add the rest of the source and run a complete build... --no-restore is used because nuget should be resolved at this point COPY codeanalysis.ruleset codeanalysis.ruleset diff --git a/Ocelot.sln b/Ocelot.sln index f4b3aef3..088d7be0 100644 --- a/Ocelot.sln +++ b/Ocelot.sln @@ -56,6 +56,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Provider.Rafty", "sr EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Tracing.Butterfly", "src\Ocelot.Tracing.Butterfly\Ocelot.Tracing.Butterfly.csproj", "{6045E23D-669C-4F27-AF8E-8EEE6DB3557F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ocelot.Provider.Kubernetes", "src\Ocelot.Provider.Kubernetes\Ocelot.Provider.Kubernetes.csproj", "{72C8E528-B4F5-45CE-8A06-CD3787364856}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -114,6 +116,10 @@ Global {6045E23D-669C-4F27-AF8E-8EEE6DB3557F}.Debug|Any CPU.Build.0 = Debug|Any CPU {6045E23D-669C-4F27-AF8E-8EEE6DB3557F}.Release|Any CPU.ActiveCfg = Release|Any CPU {6045E23D-669C-4F27-AF8E-8EEE6DB3557F}.Release|Any CPU.Build.0 = Release|Any CPU + {72C8E528-B4F5-45CE-8A06-CD3787364856}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {72C8E528-B4F5-45CE-8A06-CD3787364856}.Debug|Any CPU.Build.0 = Debug|Any CPU + {72C8E528-B4F5-45CE-8A06-CD3787364856}.Release|Any CPU.ActiveCfg = Release|Any CPU + {72C8E528-B4F5-45CE-8A06-CD3787364856}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -132,6 +138,7 @@ Global {1F6E5DCF-8A2E-4E24-A25D-064362DE8D0E} = {5CFB79B7-C9DC-45A4-9A75-625D92471702} {AC153C67-EF18-47E6-A230-F0D3CF5F0A98} = {5CFB79B7-C9DC-45A4-9A75-625D92471702} {6045E23D-669C-4F27-AF8E-8EEE6DB3557F} = {5CFB79B7-C9DC-45A4-9A75-625D92471702} + {72C8E528-B4F5-45CE-8A06-CD3787364856} = {5CFB79B7-C9DC-45A4-9A75-625D92471702} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {21476EFF-778A-4F97-8A56-D1AF1CEC0C48} diff --git a/README.md b/README.md index 702948e0..4c69c3cc 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ 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. +Please click [here](http://ocelot.readthedocs.io/en/latest/) for the Ocelot documentation. This includes lots of information and will be helpful if you want to understand the features Ocelot currently offers. ## Coming up diff --git a/docs/building/releaseprocess.rst b/docs/building/releaseprocess.rst index b107afc0..b0b45571 100644 --- a/docs/building/releaseprocess.rst +++ b/docs/building/releaseprocess.rst @@ -1,23 +1,38 @@ Release process =============== -This section defines the release process for the maintainers of the project. -* Merge pull requests to the `release` branch. +Ocelot uses the following process to accept work into the NuGet packages. -* 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. +1. User creates an issue or picks up an existing issue in GitHub. -* 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. +2. User creates a fork and branches from this (unless a member of core team, they can just create a branch on the main repo) e.g. feat/xxx, fix/xxx etc. It doesn't really matter what the xxx is. It might make sense to use the issue number and maybe a short description. I don't care as long as it has (feat, fix, refactor)/xxx :) -* 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. +3. When the user is happy with their work they can create a pull request in GitHub with their changes. The user must follow the `SemVer `_ support for this is provided by `GitVersion `_. So if you need to make breaking changes please make sure you use the correct commit message so GitVersion uses the correct semver tags. Do not manually tag the Ocelot repo this will break things. -* In Github, navigate to the `release `_. Modify the release name and tag as desired. +4. The Ocelot team will review the PR and if all is good merge it, else they will suggest feedback that the user will need to act on. In order to speed up getting a PR the user should think about the following. + - Have I covered all my changes with tests at unit and acceptance level? + - Have I updated any documentation that my changes may have affected? + - Does my feature make sense, have I checked all of Ocelot's other features to make sure it doesn't already exist? +In order for a PR to be merged the following must have occured. + - All new code is covered by unit tests. + - All new code has at least 1 acceptance test covering the happy path. + - Builds for Windows, Mac and Linux must have passed. + - Builds for Windows, Mac and Linux must not have slowed down dramatically. + - The main Ocelot package must not have taken on any non MS dependencies. -* When you're ready, publish the release. This will tag the commit with the specified release number. +5. After the PR is merged the GitHub issue must be labelled as merged. The merge will trigger an alpha build on the develop branch with these changes that will get pushed to NuGet. You can import this and test it manually should you wish. -* 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. +6. When the Ocelot team is ready to create a release they will checkout a new release branch with the version from the latest develop build. So look in AppVeyor for the latest develop semver number and checkout a new branch e.g. git checkout -b release/13.1.0 and push this to the remote. Wait for it to build and then merge this branch back into master. -* 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. +7. Wait for the master build to complete. When it has go to the AppVeyor UI and find the build and click the Deploy link in the top right hand corner. Select release preparation from the environment drop down and click deploy. This will send the release information to GitHub releases. -* 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. +8. Go to GitHub releases and find the version you have just released. It will look something like 13.0.0+31.build.1783. Remove +31.build.1783 (or whatever you get) from all the input fields so you are just left with 13.0.0. Untick This is a pre release and click release. This will trigger a build from AppVeyor that downloads the release artifacts from GitHub and publishes them to the stable NuGet feed. All being well you should find your new package on NuGet within 30 minutes. You might also want to manually add the issue numbers of what has been merged so people can see what changed in this release. +9. The final step is to go back to GitHub and close any issues that were labelled as merged. You should see something like this in`GitHub `_ and this in `NuGet `_. +Notes +----- + +All NuGet package builds are done with AppVeyor `here _` and all releases are done from `here _`. We also build Ocelot on Travis `here `_ but this is only to make sure it is building on multiple OS. + +Only Ocelot core team members can merge PRs and create release branches. Only TomPallister can merge releases into master at the moment. This is to ensure there is a final quality gate in place. Tom is mainly looking for security issues on the final merge. diff --git a/docs/features/authentication.rst b/docs/features/authentication.rst index a7599c32..cb2ec08d 100644 --- a/docs/features/authentication.rst +++ b/docs/features/authentication.rst @@ -138,8 +138,28 @@ Then map the authentication provider key to a ReRoute in your configuration e.g. Okta ^^^^ +Add nuget package : `"Okta.AspNetCore" https://www.nuget.org/packages/Okta.AspNetCore/`_ -I have not had time to write this up but we have `Issue 446 `_ that contains some code and examples that might help with Okta integration. +In a StartUp.cs file add to a method Configure next lines: +app.UseAuthentication(); +app.UseOcelot().Wait(); + +In a StartUp.cs file add to a method ConfigureServices lines: + +services.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = OktaDefaults.ApiAuthenticationScheme; + options.DefaultChallengeScheme = OktaDefaults.ApiAuthenticationScheme; + options.DefaultSignInScheme = OktaDefaults.ApiAuthenticationScheme; + }) + .AddOktaWebApi(new OktaWebApiOptions + { + OktaDomain = _cfg["Okta:OktaDomain"] + + }); +services.AddOcelot(_cfg); + +`Issue 446 `_ that contains some code and examples that might help with Okta integration. Allowed Scopes ^^^^^^^^^^^^^ diff --git a/docs/features/configuration.rst b/docs/features/configuration.rst index 8e295032..408eb450 100644 --- a/docs/features/configuration.rst +++ b/docs/features/configuration.rst @@ -88,7 +88,7 @@ to you .AddEnvironmentVariables(); }) -Ocelot will now use the environment specific configuration and fall back to ocelot.json if there isnt one. +Ocelot will now use the environment specific configuration and fall back to ocelot.json if there isn't one. You also need to set the corresponding environment variable which is ASPNETCORE_ENVIRONMENT. More info on this can be found in the `asp.net core docs `_. @@ -131,7 +131,7 @@ You can also give Ocelot a specific path to look in for the configuration files .AddEnvironmentVariables(); }) -Ocelot needs the HostingEnvironment so it know's to exclude anything environment specific from the algorithm. +Ocelot needs the HostingEnvironment so it knows to exclude anything environment specific from the algorithm. Store configuration in consul ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -161,7 +161,7 @@ finds your Consul agent and interacts to load and store the configuration from C } } -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 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. @@ -202,22 +202,23 @@ Use HttpHandlerOptions in ReRoute configuration to set up HttpHandler behavior: 1. 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 false. + 2. 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 false. Please note that if you are using the CookieContainer Ocelot caches the HttpClient for each downstream service. This means that all requests to that DownstreamService will share the same cookies. `Issue 274 `_ was created because a user noticed that the cookies were being shared. I tried to think of a nice way to handle this but I think it is impossible. If you don't cache the clients that means each request gets a new client and therefore a new cookie container. If you clear the cookies from the cached client container you get race conditions due to inflight -requests. This would also mean that subsequent requests dont use the cookies from the previous response! All in all not a great situation. I would avoid setting +requests. This would also mean that subsequent requests don't use the cookies from the previous response! All in all not a great situation. I would avoid setting UseCookieContainer to true unless you have a really really good reason. Just look at your response headers and forward the cookies back with your next request! SSL Errors ^^^^^^^^^^ -Id you want to ignore SSL warnings / errors set the following in your ReRoute config. +If you want to ignore SSL warnings / errors set the following in your ReRoute config. .. code-block:: json - "DangerousAcceptAnyServerCertificateValidator": false + "DangerousAcceptAnyServerCertificateValidator": true -I don't reccomend doing this, I suggest creating your own certificate and then getting it trusted by your local / remote machine if you can. +I don't recommend doing this, I suggest creating your own certificate and then getting it trusted by your local / remote machine if you can. diff --git a/docs/features/kubernetes.rst b/docs/features/kubernetes.rst new file mode 100644 index 00000000..289bb211 --- /dev/null +++ b/docs/features/kubernetes.rst @@ -0,0 +1,63 @@ +Kubernetes +============== + +This feature was requested as part of `Issue 345 `_ . to add support for kubernetes's service discovery provider. + +The first thing you need to do is install the NuGet package that provides kubernetes support in Ocelot. + +``Install-Package Ocelot.Provider.Kubernetes`` + +Then add the following to your ConfigureServices method. + +.. code-block:: csharp + + s.AddOcelot() + .AddKubernetes(); + +If you have services deployed in kubernetes you will normally use the naming service to access them. + +The following example shows how to set up a ReRoute that will work in kubernetes. The most important thing is the ServiceName which is made up of the +kubernetes service name. We also need to set up the ServiceDiscoveryProvider in +GlobalConfiguration. The example here shows a typical configuration. It assumes kubernetes api server is running on 192.168.0.13 and that api service is on port 443. + + +.. code-block:: json + + { + "ReRoutes": [ + { + "DownstreamPathTemplate": "/api/values", + "DownstreamScheme": "http", + "UpstreamPathTemplate": "/values", + "ServiceName": "downstreamservice", + "UpstreamHttpMethod": [ "Get" ] + } + ], + "GlobalConfiguration": { + "ServiceDiscoveryProvider": { + "Host": "192.168.0.13", + "Port": 443, + "Token": "txpc696iUhbVoudg164r93CxDTrKRVWG", + "Namespace": "dev", + "Type": "kube" + } + } +} + +You use Ocelot to poll kubernetes for latest service information rather than per request. If you want to poll kubernetes for the latest services rather than per request (default behaviour) then you need to set the following configuration. + +.. code-block:: json + +"ServiceDiscoveryProvider": { + "Host": "192.168.0.13", + "Port": 443, + "Token": "txpc696iUhbVoudg164r93CxDTrKRVWG", + "Namespace": "dev", + "Type": "pollkube" + "PollingInterval": 100 +} + +The polling interval is in milliseconds and tells Ocelot how often to call kubernetes for changes in service configuration. + +Please note there are tradeoffs here. If you poll kubernetes it is possible Ocelot will not know if a service is down depending on your polling interval and you might get more errors than if you get the latest services per request. This really depends on how volatile your services are. I doubt it will matter for most people and polling may give a tiny performance improvement over calling kubernetes per request. +There is no way for Ocelot to work these out for you. diff --git a/docs/features/loadbalancer.rst b/docs/features/loadbalancer.rst index 841d1661..947a6ab2 100644 --- a/docs/features/loadbalancer.rst +++ b/docs/features/loadbalancer.rst @@ -18,7 +18,7 @@ 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 ocelot.json and then select the LeadConnection load balancer. This is the simplest way to get load balancing set up. +The following shows how to set up multiple downstream services for a ReRoute using ocelot.json and then select the LeastConnection load balancer. This is the simplest way to get load balancing set up. .. code-block:: json diff --git a/docs/features/qualityofservice.rst b/docs/features/qualityofservice.rst index fc3770ed..512ffaad 100644 --- a/docs/features/qualityofservice.rst +++ b/docs/features/qualityofservice.rst @@ -5,7 +5,7 @@ Ocelot supports one QoS capability at the current time. You can set on a per ReR want to use a circuit breaker when making requests to a downstream service. This uses an awesome .NET library called Polly check them out `here `_. -The first thing you need to do if you want to use the administration API is bring in the relavent NuGet package.. +The first thing you need to do if you want to use the administration API is bring in the relevant NuGet package.. ``Install-Package Ocelot.Provider.Polly`` diff --git a/docs/features/raft.rst b/docs/features/raft.rst index 0ce09947..fdbc1ea2 100644 --- a/docs/features/raft.rst +++ b/docs/features/raft.rst @@ -3,7 +3,7 @@ Raft (EXPERIMENTAL DO NOT USE IN PRODUCTION) Ocelot has recently 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). +Raft is a distributed concensus algorithm that allows a cluster of servers (Ocelots) to maintain local state without having a centralised database for storing state (e.g. SQL Server). To get Raft support you must first install the Ocelot Rafty package. diff --git a/docs/features/requestaggregation.rst b/docs/features/requestaggregation.rst index 0c5c992d..6281728c 100644 --- a/docs/features/requestaggregation.rst +++ b/docs/features/requestaggregation.rst @@ -1,7 +1,7 @@ Request Aggregation =================== -Ocelot allows you to specify Aggregate ReRoutes that compose multiple normal ReRoutes and map their responses into one object. This is usual where you have +Ocelot allows you to specify Aggregate ReRoutes that compose multiple normal ReRoutes and map their responses into one object. This is usually where you have a client that is making multiple requests to a server where it could just be one. This feature allows you to start implementing back end for a front end type architecture with Ocelot. diff --git a/docs/features/routing.rst b/docs/features/routing.rst index f866f60e..9a52fb19 100644 --- a/docs/features/routing.rst +++ b/docs/features/routing.rst @@ -189,7 +189,7 @@ this sounds interesting to you. Query Strings ^^^^^^^^^^^^^ -Ocelot allows you to specify a querystring as part of the DownstreamPathTemplate like the example below. +Ocelot allows you to specify a query string as part of the DownstreamPathTemplate like the example below. .. code-block:: json @@ -241,5 +241,5 @@ Ocelot will also allow you to put query string parameters in the UpstreamPathTem } } -In this example Ocelot will only match requests that have a matching url path and the querystring starts with unitId=something. You can have other queries after this +In this example Ocelot will only match requests that have a matching url path and the query string starts with unitId=something. You can have other queries after this but you must start with the matching parameter. Also Ocelot will swap the {unitId} parameter from the query string and use it in the downstream request path. diff --git a/docs/features/servicediscovery.rst b/docs/features/servicediscovery.rst index 0bcb611e..55d446ab 100644 --- a/docs/features/servicediscovery.rst +++ b/docs/features/servicediscovery.rst @@ -54,7 +54,7 @@ and LeastConnection algorithm you can use. If no load balancer is specified Ocel 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. -A lot of people have asked me to implement a feature where Ocelot polls consul for latest service information rather than per request. If you want to poll consul for the latest services rather than per request (default behaviour) then you need to set the following configuration. +A lot of people have asked me to implement a feature where Ocelot polls Consul for latest service information rather than per request. If you want to poll Consul for the latest services rather than per request (default behaviour) then you need to set the following configuration. .. code-block:: json @@ -67,9 +67,9 @@ A lot of people have asked me to implement a feature where Ocelot polls consul f The polling interval is in milliseconds and tells Ocelot how often to call Consul for changes in service configuration. -Please note there are tradeoffs here. If you poll Consul it is possible Ocelot will not know if a service is down depending on your polling interval and you might get more errors than if you get the latest services per request. This really depends on how volitile your services are. I doubt it will matter for most people and polling may give a tiny performance improvement over calling consul per request (as sidecar agent). If you are calling a remote consul agent then polling will be a good performance improvement. +Please note there are tradeoffs here. If you poll Consul it is possible Ocelot will not know if a service is down depending on your polling interval and you might get more errors than if you get the latest services per request. This really depends on how volatile your services are. I doubt it will matter for most people and polling may give a tiny performance improvement over calling Consul per request (as sidecar agent). If you are calling a remote Consul agent then polling will be a good performance improvement. -You services need to be added to Consul something like below (c# style but hopefully this make sense)...The only important thing to note +Your services need to be added to Consul something like below (C# style but hopefully this make sense)...The only important thing to note is not to add http or https to the Address field. I have been contacted before about not accepting scheme in Address and accepting scheme in address. After reading `this `_ I don't think the scheme should be in there. @@ -108,7 +108,7 @@ If you are using ACL with Consul Ocelot supports adding the X-Consul-Token heade "Type": "Consul" } -Ocelot will add this token to the consul client that it uses to make requests and that is then used for every request. +Ocelot will add this token to the Consul client that it uses to make requests and that is then used for every request. Eureka ^^^^^^ @@ -160,8 +160,8 @@ Dynamic Routing This feature was requested in `issue 340 `_. The idea is to enable dynamic routing when using a service discovery provider (see that section of the docs for more info). In this mode Ocelot will use the first segment of the upstream path to lookup the downstream service with the service discovery provider. -An example of this would be calling ocelot with a url like https://api.mywebsite.com/product/products. Ocelot will take the first segment of -the path which is product and use it as a key to look up the service in consul. If consul returns a service Ocelot will request it on whatever host and port comes back from consul plus the remaining path segments in this case products thus making the downstream call http://hostfromconsul:portfromconsul/products. Ocelot will apprend any query string to the downstream url as normal. +An example of this would be calling Ocelot with a url like https://api.mywebsite.com/product/products. Ocelot will take the first segment of +the path which is product and use it as a key to look up the service in Consul. If Consul returns a service Ocelot will request it on whatever host and port comes back from Consul plus the remaining path segments in this case products thus making the downstream call http://hostfromconsul:portfromconsul/products. Ocelot will apprend any query string to the downstream url as normal. In order to enable dynamic routing you need to have 0 ReRoutes in your config. At the moment you cannot mix dynamic and configuration ReRoutes. In addition to this you need to specify the Service Discovery provider details as outlined above and the downstream http/https scheme as DownstreamScheme. diff --git a/docs/index.rst b/docs/index.rst index 7989fcdd..4aba33fd 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -23,7 +23,8 @@ Thanks for taking a look at the Ocelot documentation. Please use the left hand n features/requestaggregation features/graphql features/servicediscovery - features/servicefabric + features/servicefabric + features/kubernetes features/authentication features/authorisation features/websockets diff --git a/docs/introduction/gettingstarted.rst b/docs/introduction/gettingstarted.rst index 0bb0d5d1..bb8d8bd6 100644 --- a/docs/introduction/gettingstarted.rst +++ b/docs/introduction/gettingstarted.rst @@ -2,7 +2,7 @@ Getting Started =============== Ocelot is designed to work with .NET Core only and is currently -built to netstandard2.0 `this `_ documentation may prove helpful when working out if Ocelot would be suitable for you. +built to netstandard2.0. `This `_ documentation may prove helpful when working out if Ocelot would be suitable for you. .NET Core 2.1 ^^^^^^^^^^^^^ @@ -32,8 +32,8 @@ The following is a very basic ocelot.json. It won't do anything but should get O The most important thing to note here is BaseUrl. Ocelot needs to know the URL it is running under in order to do Header find & replace and for certain administration configurations. When setting this URL it should be the external URL that clients will see Ocelot running on e.g. If you are running containers Ocelot might run on the url http://123.12.1.1:6543 but has something like nginx in front of it responding on https://api.mybusiness.com. In this case the Ocelot base url should be https://api.mybusiness.com. -If for some reason you are using containers and do want Ocelot to respond to client on http://123.12.1.1:6543 -then you can do this but if you are deploying multiple Ocelot's you will probably want to pass this on the command line in some kind of script. Hopefully whatever scheduler you are using can pass the IP. +If you are using containers and require Ocelot to respond to clients on http://123.12.1.1:6543 +then you can do this, however if you are deploying multiple Ocelot's you will probably want to pass this on the command line in some kind of script. Hopefully whatever scheduler you are using can pass the IP. **Program** @@ -74,6 +74,8 @@ AddOcelot() (adds ocelot services), UseOcelot().Wait() (sets up all the Ocelot m .Run(); } } + + **Note:** When using ASP.NET Core 2.2 and you want to use In-Process hosting, replace **.UseIISIntegration()** with **.UseIIS()**, otherwise you'll get startup errors. .NET Core 1.0 ^^^^^^^^^^^^^ diff --git a/docs/introduction/notsupported.rst b/docs/introduction/notsupported.rst index ea23ab40..404b8c70 100644 --- a/docs/introduction/notsupported.rst +++ b/docs/introduction/notsupported.rst @@ -32,7 +32,7 @@ The main reasons why I don't think Swagger makes sense is we already hand roll o If we want people developing against Ocelot to be able to see what routes are available then either share the ocelot.json with them (This should be as easy as granting access to a repo etc) or use the Ocelot administration API so that they can query Ocelot for the configuration. -In addition to this many people will configure Ocelot to proxy all traffic like /products/{everything} to there product service +In addition to this many people will configure Ocelot to proxy all traffic like /products/{everything} to their product service and you would not be describing what is actually available if you parsed this and turned it into a Swagger path. Also Ocelot has no concept of the models that the downstream services can return and linking to the above problem the same endpoint can return multiple models. Ocelot does not know what models might be used in POST, PUT etc so it all gets a bit messy and finally the Swashbuckle diff --git a/samples/OelotKube/.dockerignore b/samples/OelotKube/.dockerignore new file mode 100644 index 00000000..df2e0fe5 --- /dev/null +++ b/samples/OelotKube/.dockerignore @@ -0,0 +1,9 @@ +.dockerignore +.env +.git +.gitignore +.vs +.vscode +*/bin +*/obj +**/.toolstarget \ No newline at end of file diff --git a/samples/OelotKube/ApiGateway/ApiGateway.csproj b/samples/OelotKube/ApiGateway/ApiGateway.csproj new file mode 100644 index 00000000..aef8c150 --- /dev/null +++ b/samples/OelotKube/ApiGateway/ApiGateway.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp2.1 + InProcess + Linux + + + + + + + + + + + + + + diff --git a/samples/OelotKube/ApiGateway/Dockerfile b/samples/OelotKube/ApiGateway/Dockerfile new file mode 100644 index 00000000..19bd33c2 --- /dev/null +++ b/samples/OelotKube/ApiGateway/Dockerfile @@ -0,0 +1,21 @@ +FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base +WORKDIR /app +EXPOSE 80 + +FROM microsoft/dotnet:2.1-sdk AS build +WORKDIR /src +COPY ["ApiGateway/ApiGateway.csproj", "ApiGateway/"] +COPY ["../../src/Ocelot/Ocelot.csproj", "../../src/Ocelot/"] +COPY ["../../src/Ocelot.Provider.Kubernetes/Ocelot.Provider.Kubernetes.csproj", "../../src/Ocelot.Provider.Kubernetes/"] +RUN dotnet restore "ApiGateway/ApiGateway.csproj" +COPY . . +WORKDIR "/src/ApiGateway" +RUN dotnet build "ApiGateway.csproj" -c Release -o /app + +FROM build AS publish +RUN dotnet publish "ApiGateway.csproj" -c Release -o /app + +FROM base AS final +WORKDIR /app +COPY --from=publish /app . +ENTRYPOINT ["dotnet", "ApiGateway.dll"] diff --git a/samples/OelotKube/ApiGateway/Program.cs b/samples/OelotKube/ApiGateway/Program.cs new file mode 100644 index 00000000..978c8ccc --- /dev/null +++ b/samples/OelotKube/ApiGateway/Program.cs @@ -0,0 +1,32 @@ +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Ocelot.DependencyInjection; +using Ocelot.Middleware; +using Ocelot.Provider.Kubernetes; + +namespace ApiGateway +{ + public class Program + { + public static void Main(string[] args) + { + BuildWebHost(args).Run(); + } + + public static IWebHost BuildWebHost(string[] args) => + WebHost.CreateDefaultBuilder(args) + .ConfigureAppConfiguration((hostingContext, config) => + { + config + .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) + .AddJsonFile("appsettings.json", true, true) + .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) + .AddJsonFile("ocelot.json", false, false) + .AddEnvironmentVariables(); + }) + .UseStartup() + .Build(); + } +} + diff --git a/samples/OelotKube/ApiGateway/Properties/launchSettings.json b/samples/OelotKube/ApiGateway/Properties/launchSettings.json new file mode 100644 index 00000000..5315ab2c --- /dev/null +++ b/samples/OelotKube/ApiGateway/Properties/launchSettings.json @@ -0,0 +1,32 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:52363", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "ApiGateway": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "http://localhost:5000" + }, + "Docker": { + "commandName": "Docker", + "launchBrowser": true, + "launchUrl": "{Scheme}://localhost:{ServicePort}" + } + } +} \ No newline at end of file diff --git a/samples/OelotKube/ApiGateway/Startup.cs b/samples/OelotKube/ApiGateway/Startup.cs new file mode 100644 index 00000000..4743e836 --- /dev/null +++ b/samples/OelotKube/ApiGateway/Startup.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Ocelot.DependencyInjection; +using Ocelot.Middleware; +using Ocelot.Provider.Kubernetes; + +namespace ApiGateway +{ + public class Startup + { + // This method gets called by the runtime. Use this method to add services to the container. + // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 + public void ConfigureServices(IServiceCollection services) + { + services.AddOcelot() + .AddKubernetes(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseOcelot().Wait(); + } + } +} diff --git a/samples/OelotKube/ApiGateway/appsettings.Development.json b/samples/OelotKube/ApiGateway/appsettings.Development.json new file mode 100644 index 00000000..e203e940 --- /dev/null +++ b/samples/OelotKube/ApiGateway/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/samples/OelotKube/ApiGateway/appsettings.json b/samples/OelotKube/ApiGateway/appsettings.json new file mode 100644 index 00000000..def9159a --- /dev/null +++ b/samples/OelotKube/ApiGateway/appsettings.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/samples/OelotKube/ApiGateway/ocelot.json b/samples/OelotKube/ApiGateway/ocelot.json new file mode 100644 index 00000000..ec70503e --- /dev/null +++ b/samples/OelotKube/ApiGateway/ocelot.json @@ -0,0 +1,20 @@ +{ + "ReRoutes": [ + { + "DownstreamPathTemplate": "/api/values", + "DownstreamScheme": "http", + "UpstreamPathTemplate": "/values", + "ServiceName": "downstreamservice", + "UpstreamHttpMethod": [ "Get" ] + } + ], + "GlobalConfiguration": { + "ServiceDiscoveryProvider": { + "Host": "192.168.0.13", + "Port": 443, + "Token": "txpc696iUhbVoudg164r93CxDTrKRVWG", + "Namespace": "dev", + "Type": "kube" + } + } +} diff --git a/samples/OelotKube/Dockerfile b/samples/OelotKube/Dockerfile new file mode 100644 index 00000000..1ec13d2a --- /dev/null +++ b/samples/OelotKube/Dockerfile @@ -0,0 +1,22 @@ +FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base +WORKDIR /app +EXPOSE 80 + +FROM microsoft/dotnet:2.1-sdk AS build +WORKDIR /src +COPY ["ApiGateway/ApiGateway.csproj", "ApiGateway/"] +COPY ["../../src/Ocelot.Provider.Polly/Ocelot.Provider.Polly.csproj", "../../src/Ocelot.Provider.Polly/"] +COPY ["../../src/Ocelot/Ocelot.csproj", "../../src/Ocelot/"] +COPY ["../../src/Ocelot.Provider.Kubernetes/Ocelot.Provider.Kubernetes.csproj", "../../src/Ocelot.Provider.Kubernetes/"] +RUN dotnet restore "ApiGateway/ApiGateway.csproj" +COPY . . +WORKDIR "/src/ApiGateway" +RUN dotnet build "ApiGateway.csproj" -c Release -o /app + +FROM build AS publish +RUN dotnet publish "ApiGateway.csproj" -c Release -o /app + +FROM base AS final +WORKDIR /app +COPY --from=publish /app . +ENTRYPOINT ["dotnet", "ApiGateway.dll"] diff --git a/samples/OelotKube/DownstreamService/Controllers/ValuesController.cs b/samples/OelotKube/DownstreamService/Controllers/ValuesController.cs new file mode 100644 index 00000000..425cf18d --- /dev/null +++ b/samples/OelotKube/DownstreamService/Controllers/ValuesController.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; + +namespace DownstreamService.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class ValuesController : ControllerBase + { + // GET api/values + [HttpGet] + public ActionResult> Get() + { + return new string[] { "value1", "value2" }; + } + + // GET api/values/5 + [HttpGet("{id}")] + public ActionResult Get(int id) + { + return "value"; + } + + // POST api/values + [HttpPost] + public void Post([FromBody] string value) + { + } + + // PUT api/values/5 + [HttpPut("{id}")] + public void Put(int id, [FromBody] string value) + { + } + + // DELETE api/values/5 + [HttpDelete("{id}")] + public void Delete(int id) + { + } + } +} diff --git a/samples/OelotKube/DownstreamService/Dockerfile b/samples/OelotKube/DownstreamService/Dockerfile new file mode 100644 index 00000000..a9695515 --- /dev/null +++ b/samples/OelotKube/DownstreamService/Dockerfile @@ -0,0 +1,19 @@ +FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base +WORKDIR /app +EXPOSE 80 + +FROM microsoft/dotnet:2.1-sdk AS build +WORKDIR /src +COPY ["DownstreamService/DownstreamService.csproj", "DownstreamService/"] +RUN dotnet restore "DownstreamService/DownstreamService.csproj" +COPY . . +WORKDIR "/src/DownstreamService" +RUN dotnet build "DownstreamService.csproj" -c Release -o /app + +FROM build AS publish +RUN dotnet publish "DownstreamService.csproj" -c Release -o /app + +FROM base AS final +WORKDIR /app +COPY --from=publish /app . +ENTRYPOINT ["dotnet", "DownstreamService.dll"] diff --git a/samples/OelotKube/DownstreamService/Dockerfile.develop b/samples/OelotKube/DownstreamService/Dockerfile.develop new file mode 100644 index 00000000..6f49a290 --- /dev/null +++ b/samples/OelotKube/DownstreamService/Dockerfile.develop @@ -0,0 +1,15 @@ +FROM microsoft/dotnet:2.1-sdk +ARG BUILD_CONFIGURATION=Debug +ENV ASPNETCORE_ENVIRONMENT=Development +ENV DOTNET_USE_POLLING_FILE_WATCHER=true +EXPOSE 80 + +WORKDIR /src +COPY ["DownstreamService/DownstreamService.csproj", "DownstreamService/"] + +RUN dotnet restore "DownstreamService/DownstreamService.csproj" +COPY . . +WORKDIR "/src/DownstreamService" +RUN dotnet build --no-restore "DownstreamService.csproj" -c $BUILD_CONFIGURATION + +ENTRYPOINT ["dotnet", "run", "--no-build", "--no-launch-profile", "-c", "$BUILD_CONFIGURATION", "--"] \ No newline at end of file diff --git a/samples/OelotKube/DownstreamService/DownstreamService.csproj b/samples/OelotKube/DownstreamService/DownstreamService.csproj new file mode 100644 index 00000000..c32dd048 --- /dev/null +++ b/samples/OelotKube/DownstreamService/DownstreamService.csproj @@ -0,0 +1,15 @@ + + + + netcoreapp2.1 + InProcess + Linux + + + + + + + + + diff --git a/samples/OelotKube/DownstreamService/Program.cs b/samples/OelotKube/DownstreamService/Program.cs new file mode 100644 index 00000000..03e1b8ae --- /dev/null +++ b/samples/OelotKube/DownstreamService/Program.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace DownstreamService +{ + public class Program + { + public static void Main(string[] args) + { + CreateWebHostBuilder(args).Build().Run(); + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup(); + } +} diff --git a/samples/OelotKube/DownstreamService/Properties/launchSettings.json b/samples/OelotKube/DownstreamService/Properties/launchSettings.json new file mode 100644 index 00000000..30d6118c --- /dev/null +++ b/samples/OelotKube/DownstreamService/Properties/launchSettings.json @@ -0,0 +1,35 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:56411", + "sslPort": 0 + } + }, + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "api/values", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "DownstreamService": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "api/values", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "http://localhost:5000" + }, + "Docker": { + "commandName": "Docker", + "launchBrowser": true, + "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/api/values" + } + } +} \ No newline at end of file diff --git a/samples/OelotKube/DownstreamService/Startup.cs b/samples/OelotKube/DownstreamService/Startup.cs new file mode 100644 index 00000000..9a927a37 --- /dev/null +++ b/samples/OelotKube/DownstreamService/Startup.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace DownstreamService +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseMvc(); + } + } +} diff --git a/samples/OelotKube/DownstreamService/appsettings.Development.json b/samples/OelotKube/DownstreamService/appsettings.Development.json new file mode 100644 index 00000000..e203e940 --- /dev/null +++ b/samples/OelotKube/DownstreamService/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/samples/OelotKube/DownstreamService/appsettings.json b/samples/OelotKube/DownstreamService/appsettings.json new file mode 100644 index 00000000..def9159a --- /dev/null +++ b/samples/OelotKube/DownstreamService/appsettings.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/samples/OelotKube/OelotKube.sln b/samples/OelotKube/OelotKube.sln new file mode 100644 index 00000000..aa57e0dd --- /dev/null +++ b/samples/OelotKube/OelotKube.sln @@ -0,0 +1,43 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28010.2048 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApiGateway", "ApiGateway\ApiGateway.csproj", "{E9AFBFD7-EF20-48E5-BB30-5C63C59D7C1C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot", "..\..\src\Ocelot\Ocelot.csproj", "{E8551073-622E-45FA-AD09-038EB8AAFFBC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Provider.Kubernetes", "..\..\src\Ocelot.Provider.Kubernetes\Ocelot.Provider.Kubernetes.csproj", "{EF973868-98A6-4864-BF66-65B5A8C123FE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DownstreamService", "DownstreamService\DownstreamService.csproj", "{86FFAE3C-648F-4CDE-A260-37C8EBFBF4F2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E9AFBFD7-EF20-48E5-BB30-5C63C59D7C1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E9AFBFD7-EF20-48E5-BB30-5C63C59D7C1C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E9AFBFD7-EF20-48E5-BB30-5C63C59D7C1C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E9AFBFD7-EF20-48E5-BB30-5C63C59D7C1C}.Release|Any CPU.Build.0 = Release|Any CPU + {E8551073-622E-45FA-AD09-038EB8AAFFBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E8551073-622E-45FA-AD09-038EB8AAFFBC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E8551073-622E-45FA-AD09-038EB8AAFFBC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E8551073-622E-45FA-AD09-038EB8AAFFBC}.Release|Any CPU.Build.0 = Release|Any CPU + {EF973868-98A6-4864-BF66-65B5A8C123FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EF973868-98A6-4864-BF66-65B5A8C123FE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EF973868-98A6-4864-BF66-65B5A8C123FE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EF973868-98A6-4864-BF66-65B5A8C123FE}.Release|Any CPU.Build.0 = Release|Any CPU + {86FFAE3C-648F-4CDE-A260-37C8EBFBF4F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {86FFAE3C-648F-4CDE-A260-37C8EBFBF4F2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {86FFAE3C-648F-4CDE-A260-37C8EBFBF4F2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {86FFAE3C-648F-4CDE-A260-37C8EBFBF4F2}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D29790E8-4BA9-4E60-8D7D-327E21320CC9} + EndGlobalSection +EndGlobal diff --git a/src/Ocelot.Provider.Consul/Consul.cs b/src/Ocelot.Provider.Consul/Consul.cs index b202cd57..3aac63b6 100644 --- a/src/Ocelot.Provider.Consul/Consul.cs +++ b/src/Ocelot.Provider.Consul/Consul.cs @@ -27,7 +27,7 @@ public async Task> Get() { - var queryResult = await _consul.Health.Service(_config.KeyOfServiceInConsul, string.Empty, true); + var queryResult = await _consul.Health.Service(_config.KeyOfServiceInConsul, string.Empty, false); var services = new List(); diff --git a/src/Ocelot.Provider.Kubernetes/IKubeApiClientFactory.cs b/src/Ocelot.Provider.Kubernetes/IKubeApiClientFactory.cs new file mode 100644 index 00000000..bd9089d0 --- /dev/null +++ b/src/Ocelot.Provider.Kubernetes/IKubeApiClientFactory.cs @@ -0,0 +1,12 @@ +using KubeClient; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ocelot.Provider.Kubernetes +{ + public interface IKubeApiClientFactory + { + IKubeApiClient Get(KubeRegistryConfiguration config); + } +} diff --git a/src/Ocelot.Provider.Kubernetes/KubeApiClientFactory.cs b/src/Ocelot.Provider.Kubernetes/KubeApiClientFactory.cs new file mode 100644 index 00000000..fbb832ba --- /dev/null +++ b/src/Ocelot.Provider.Kubernetes/KubeApiClientFactory.cs @@ -0,0 +1,22 @@ +using KubeClient; + +namespace Ocelot.Provider.Kubernetes +{ + public class KubeApiClientFactory : IKubeApiClientFactory + { + public IKubeApiClient Get(KubeRegistryConfiguration config) + { + var option = new KubeClientOptions + { + ApiEndPoint = config.ApiEndPoint + }; + if(!string.IsNullOrEmpty(config?.AccessToken)) + { + option.AccessToken = config.AccessToken; + option.AuthStrategy = config.AuthStrategy; + option.AllowInsecure = config.AllowInsecure; + } + return KubeApiClient.Create(option); + } + } +} diff --git a/src/Ocelot.Provider.Kubernetes/KubeProvider.cs b/src/Ocelot.Provider.Kubernetes/KubeProvider.cs new file mode 100644 index 00000000..0d1f0fc4 --- /dev/null +++ b/src/Ocelot.Provider.Kubernetes/KubeProvider.cs @@ -0,0 +1,63 @@ +using KubeClient; +using KubeClient.Models; +using Ocelot.Logging; +using Ocelot.ServiceDiscovery.Providers; +using Ocelot.Values; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.Provider.Kubernetes +{ + public class Kube : IServiceDiscoveryProvider + { + private KubeRegistryConfiguration kubeRegistryConfiguration; + private IOcelotLogger logger; + private IKubeApiClient kubeApi; + + public Kube(KubeRegistryConfiguration kubeRegistryConfiguration, IOcelotLoggerFactory factory, IKubeApiClientFactory kubeClientFactory) + { + this.kubeRegistryConfiguration = kubeRegistryConfiguration; + this.logger = factory.CreateLogger(); + this.kubeApi = kubeClientFactory.Get(kubeRegistryConfiguration); + } + + public async Task> Get() + { + var service = await kubeApi.ServicesV1() + .Get(kubeRegistryConfiguration.KeyOfServiceInK8s, kubeRegistryConfiguration.KubeNamespace); + var services = new List(); + if (IsValid(service)) + { + services.Add(BuildService(service)); + } + else + { + logger.LogWarning($"namespace:{kubeRegistryConfiguration.KubeNamespace }service:{kubeRegistryConfiguration.KeyOfServiceInK8s} Unable to use ,it is invalid. Address must contain host only e.g. localhost and port must be greater than 0"); + } + return services; + } + + private bool IsValid(ServiceV1 service) + { + if (string.IsNullOrEmpty(service.Spec.ClusterIP) || service.Spec.Ports.Count <= 0) + { + return false; + } + + return true; + } + + private Service BuildService(ServiceV1 serviceEntry) + { + var servicePort = serviceEntry.Spec.Ports.FirstOrDefault(); + return new Service( + serviceEntry.Metadata.Name, + new ServiceHostAndPort(serviceEntry.Spec.ClusterIP, servicePort.Port), + serviceEntry.Metadata.Uid, + string.Empty, + Enumerable.Empty()); + } + } +} diff --git a/src/Ocelot.Provider.Kubernetes/KubeRegistryConfiguration.cs b/src/Ocelot.Provider.Kubernetes/KubeRegistryConfiguration.cs new file mode 100644 index 00000000..929c6e32 --- /dev/null +++ b/src/Ocelot.Provider.Kubernetes/KubeRegistryConfiguration.cs @@ -0,0 +1,22 @@ +using KubeClient; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ocelot.Provider.Kubernetes +{ + public class KubeRegistryConfiguration + { + public Uri ApiEndPoint { get; set; } + + public string KubeNamespace { get; set; } + + public string KeyOfServiceInK8s { get; set; } + + public KubeAuthStrategy AuthStrategy { get; set; } + + public string AccessToken { get; set; } + + public bool AllowInsecure { get; set; } + } +} diff --git a/src/Ocelot.Provider.Kubernetes/KubernetesProviderFactory.cs b/src/Ocelot.Provider.Kubernetes/KubernetesProviderFactory.cs new file mode 100644 index 00000000..30ed2c85 --- /dev/null +++ b/src/Ocelot.Provider.Kubernetes/KubernetesProviderFactory.cs @@ -0,0 +1,38 @@ +using KubeClient; +using Microsoft.Extensions.DependencyInjection; +using Ocelot.Logging; +using Ocelot.ServiceDiscovery; +using System; + +namespace Ocelot.Provider.Kubernetes +{ + public static class KubernetesProviderFactory + { + public static ServiceDiscoveryFinderDelegate Get = (provider, config, name) => + { + var factory = provider.GetService(); + return GetkubeProvider(provider, config, name, factory); + }; + + private static ServiceDiscovery.Providers.IServiceDiscoveryProvider GetkubeProvider(IServiceProvider provider, Configuration.ServiceProviderConfiguration config, string name, IOcelotLoggerFactory factory) + { + var kubeClientFactory = provider.GetService(); + var k8sRegistryConfiguration = new KubeRegistryConfiguration() + { + ApiEndPoint = new Uri($"https://{config.Host}:{config.Port}"), + KeyOfServiceInK8s = name, + KubeNamespace = config.Namespace, + AuthStrategy = KubeAuthStrategy.BearerToken, + AccessToken = config.Token, + AllowInsecure = true // Don't validate server certificate + }; + + var k8sServiceDiscoveryProvider = new Kube(k8sRegistryConfiguration, factory, kubeClientFactory); + if (config.Type?.ToLower() == "pollkube") + { + return new PollKube(config.PollingInterval, factory, k8sServiceDiscoveryProvider); + } + return k8sServiceDiscoveryProvider; + } + } +} diff --git a/src/Ocelot.Provider.Kubernetes/Ocelot.Provider.Kubernetes.csproj b/src/Ocelot.Provider.Kubernetes/Ocelot.Provider.Kubernetes.csproj new file mode 100644 index 00000000..ffb981d3 --- /dev/null +++ b/src/Ocelot.Provider.Kubernetes/Ocelot.Provider.Kubernetes.csproj @@ -0,0 +1,35 @@ + + + + netstandard2.0 + 2.0.0 + 2.0.0 + true + Ocelot + Provides Ocelot extensions to use kubernetes + https://github.com/ThreeMammals/Ocelot + http://threemammals.com/images/ocelot_logo.png + + Ocelot.Provider.Kubernetes + Ocelot.Provider.Kubernetes + API Gateway;.NET core + win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64 + false + false + false + false + 0.0.0-dev + geffzhang + + ..\..\codeanalysis.ruleset + + + + + + + + + + + diff --git a/src/Ocelot.Provider.Kubernetes/OcelotBuilderExtensions.cs b/src/Ocelot.Provider.Kubernetes/OcelotBuilderExtensions.cs new file mode 100644 index 00000000..80772167 --- /dev/null +++ b/src/Ocelot.Provider.Kubernetes/OcelotBuilderExtensions.cs @@ -0,0 +1,15 @@ +using Microsoft.Extensions.DependencyInjection; +using Ocelot.DependencyInjection; + +namespace Ocelot.Provider.Kubernetes +{ + public static class OcelotBuilderExtensions + { + public static IOcelotBuilder AddKubernetes(this IOcelotBuilder builder) + { + builder.Services.AddSingleton(KubernetesProviderFactory.Get); + builder.Services.AddSingleton(); + return builder; + } + } +} diff --git a/src/Ocelot.Provider.Kubernetes/PollKube.cs b/src/Ocelot.Provider.Kubernetes/PollKube.cs new file mode 100644 index 00000000..a2096e9e --- /dev/null +++ b/src/Ocelot.Provider.Kubernetes/PollKube.cs @@ -0,0 +1,50 @@ +using Ocelot.Logging; +using Ocelot.ServiceDiscovery.Providers; +using Ocelot.Values; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Ocelot.Provider.Kubernetes +{ + public class PollKube : IServiceDiscoveryProvider + { + private readonly IOcelotLogger _logger; + private readonly IServiceDiscoveryProvider _kubeServiceDiscoveryProvider; + private readonly Timer _timer; + private bool _polling; + private List _services; + + public PollKube(int pollingInterval, IOcelotLoggerFactory factory, IServiceDiscoveryProvider kubeServiceDiscoveryProvider) + { + _logger = factory.CreateLogger(); + _kubeServiceDiscoveryProvider = kubeServiceDiscoveryProvider; + _services = new List(); + + _timer = new Timer(async x => + { + if (_polling) + { + return; + } + + _polling = true; + await Poll(); + _polling = false; + }, null, pollingInterval, pollingInterval); + } + + public Task> Get() + { + return Task.FromResult(_services); + } + + private async Task Poll() + { + _services = await _kubeServiceDiscoveryProvider.Get(); + } + } +} + diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index 824fe09d..dae8c11e 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -4,18 +4,21 @@ using System.Net.Http; using Ocelot.Values; using System.Linq; - + using Ocelot.Configuration.File; + public class ReRouteBuilder { private UpstreamPathTemplate _upstreamTemplatePattern; private List _upstreamHttpMethod; private string _upstreamHost; private List _downstreamReRoutes; + private List _downstreamReRoutesConfig; private string _aggregator; public ReRouteBuilder() { _downstreamReRoutes = new List(); + _downstreamReRoutesConfig = new List(); } public ReRouteBuilder WithDownstreamReRoute(DownstreamReRoute value) @@ -48,6 +51,12 @@ return this; } + public ReRouteBuilder WithAggregateReRouteConfig(List aggregateReRouteConfigs) + { + _downstreamReRoutesConfig = aggregateReRouteConfigs; + return this; + } + public ReRouteBuilder WithAggregator(string aggregator) { _aggregator = aggregator; @@ -57,7 +66,8 @@ public ReRoute Build() { return new ReRoute( - _downstreamReRoutes, + _downstreamReRoutes, + _downstreamReRoutesConfig, _upstreamHttpMethod, _upstreamTemplatePattern, _upstreamHost, diff --git a/src/Ocelot/Configuration/Builder/ServiceProviderConfigurationBuilder.cs b/src/Ocelot/Configuration/Builder/ServiceProviderConfigurationBuilder.cs index 1ea18067..16ec15d0 100644 --- a/src/Ocelot/Configuration/Builder/ServiceProviderConfigurationBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ServiceProviderConfigurationBuilder.cs @@ -8,6 +8,7 @@ namespace Ocelot.Configuration.Builder private string _token; private string _configurationKey; private int _pollingInterval; + private string _namespace; public ServiceProviderConfigurationBuilder WithHost(string serviceDiscoveryProviderHost) { @@ -45,9 +46,15 @@ namespace Ocelot.Configuration.Builder return this; } + public ServiceProviderConfigurationBuilder WithNamespace(string @namespace) + { + _namespace = @namespace; + return this; + } + public ServiceProviderConfiguration Build() { - return new ServiceProviderConfiguration(_type, _serviceDiscoveryProviderHost, _serviceDiscoveryProviderPort, _token, _configurationKey, _pollingInterval); + return new ServiceProviderConfiguration(_type, _serviceDiscoveryProviderHost, _serviceDiscoveryProviderPort, _token, _configurationKey, _pollingInterval, _namespace); } } } diff --git a/src/Ocelot/Configuration/Creator/AggregatesCreator.cs b/src/Ocelot/Configuration/Creator/AggregatesCreator.cs index ed43f598..9acef561 100644 --- a/src/Ocelot/Configuration/Creator/AggregatesCreator.cs +++ b/src/Ocelot/Configuration/Creator/AggregatesCreator.cs @@ -24,14 +24,18 @@ namespace Ocelot.Configuration.Creator private ReRoute SetUpAggregateReRoute(IEnumerable reRoutes, FileAggregateReRoute aggregateReRoute, FileGlobalConfiguration globalConfiguration) { - var applicableReRoutes = reRoutes - .SelectMany(x => x.DownstreamReRoute) - .Where(r => aggregateReRoute.ReRouteKeys.Contains(r.Key)) - .ToList(); + var applicableReRoutes = new List(); + var allReRoutes = reRoutes.SelectMany(x => x.DownstreamReRoute); - if (applicableReRoutes.Count != aggregateReRoute.ReRouteKeys.Count) + foreach (var reRouteKey in aggregateReRoute.ReRouteKeys) { - return null; + var selec = allReRoutes.FirstOrDefault(q => q.Key == reRouteKey); + if (selec == null) + { + return null; + } + + applicableReRoutes.Add(selec); } var upstreamTemplatePattern = _creator.Create(aggregateReRoute); @@ -40,6 +44,7 @@ namespace Ocelot.Configuration.Creator .WithUpstreamHttpMethod(aggregateReRoute.UpstreamHttpMethod) .WithUpstreamPathTemplate(upstreamTemplatePattern) .WithDownstreamReRoutes(applicableReRoutes) + .WithAggregateReRouteConfig(aggregateReRoute.ReRouteKeysConfig) .WithUpstreamHost(aggregateReRoute.UpstreamHost) .WithAggregator(aggregateReRoute.Aggregator) .Build(); diff --git a/src/Ocelot/Configuration/Creator/ServiceProviderConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/ServiceProviderConfigurationCreator.cs index a236fb22..3b0ee225 100644 --- a/src/Ocelot/Configuration/Creator/ServiceProviderConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/ServiceProviderConfigurationCreator.cs @@ -13,6 +13,7 @@ namespace Ocelot.Configuration.Creator ? globalConfiguration?.ServiceDiscoveryProvider?.Type : "consul"; var pollingInterval = globalConfiguration?.ServiceDiscoveryProvider?.PollingInterval ?? 0; + var k8snamespace = globalConfiguration?.ServiceDiscoveryProvider?.Namespace ?? string.Empty; return new ServiceProviderConfigurationBuilder() .WithHost(host) @@ -21,6 +22,7 @@ namespace Ocelot.Configuration.Creator .WithToken(globalConfiguration?.ServiceDiscoveryProvider?.Token) .WithConfigurationKey(globalConfiguration?.ServiceDiscoveryProvider?.ConfigurationKey) .WithPollingInterval(pollingInterval) + .WithNamespace(k8snamespace) .Build(); } } diff --git a/src/Ocelot/Configuration/File/AggregateReRouteConfig.cs b/src/Ocelot/Configuration/File/AggregateReRouteConfig.cs new file mode 100644 index 00000000..8a155c1b --- /dev/null +++ b/src/Ocelot/Configuration/File/AggregateReRouteConfig.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ocelot.Configuration.File +{ + public class AggregateReRouteConfig + { + public string ReRouteKey { get; set; } + public string Parameter { get; set; } + public string JsonPath { get; set; } + } +} diff --git a/src/Ocelot/Configuration/File/FileAggregateReRoute.cs b/src/Ocelot/Configuration/File/FileAggregateReRoute.cs index c862094a..5c3b551c 100644 --- a/src/Ocelot/Configuration/File/FileAggregateReRoute.cs +++ b/src/Ocelot/Configuration/File/FileAggregateReRoute.cs @@ -5,6 +5,7 @@ namespace Ocelot.Configuration.File public class FileAggregateReRoute : IReRoute { public List ReRouteKeys { get;set; } + public List ReRouteKeysConfig { get;set; } public string UpstreamPathTemplate { get;set; } public string UpstreamHost { get; set; } public bool ReRouteIsCaseSensitive { get; set; } @@ -17,5 +18,5 @@ namespace Ocelot.Configuration.File } public int Priority {get;set;} = 1; - } + } } diff --git a/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs b/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs index e6edeee7..153145bd 100644 --- a/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs +++ b/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs @@ -8,5 +8,6 @@ namespace Ocelot.Configuration.File public string Token { get; set; } public string ConfigurationKey { get; set; } public int PollingInterval { get; set; } + public string Namespace { get; set; } } } diff --git a/src/Ocelot/Configuration/LoadBalancerOptions.cs b/src/Ocelot/Configuration/LoadBalancerOptions.cs index 08dc3b87..cbf9c59e 100644 --- a/src/Ocelot/Configuration/LoadBalancerOptions.cs +++ b/src/Ocelot/Configuration/LoadBalancerOptions.cs @@ -6,7 +6,7 @@ namespace Ocelot.Configuration { public LoadBalancerOptions(string type, string key, int expiryInMs) { - Type = type ?? nameof(NoLoadBalancer); + Type = string.IsNullOrWhiteSpace(type) ? nameof(NoLoadBalancer) : type; Key = key; ExpiryInMs = expiryInMs; } @@ -14,7 +14,7 @@ namespace Ocelot.Configuration public string Type { get; } public string Key { get; } - - public int ExpiryInMs { get; } + + public int ExpiryInMs { get; } } } diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index f1faf975..31392770 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -2,11 +2,13 @@ { using System.Collections.Generic; using System.Net.Http; + using Ocelot.Configuration.File; using Ocelot.Values; - + public class ReRoute { - public ReRoute(List downstreamReRoute, + public ReRoute(List downstreamReRoute, + List downstreamReRouteConfig, List upstreamHttpMethod, UpstreamPathTemplate upstreamTemplatePattern, string upstreamHost, @@ -14,6 +16,7 @@ { UpstreamHost = upstreamHost; DownstreamReRoute = downstreamReRoute; + DownstreamReRouteConfig = downstreamReRouteConfig; UpstreamHttpMethod = upstreamHttpMethod; UpstreamTemplatePattern = upstreamTemplatePattern; Aggregator = aggregator; @@ -23,6 +26,7 @@ public List UpstreamHttpMethod { get; private set; } public string UpstreamHost { get; private set; } public List DownstreamReRoute { get; private set; } + public List DownstreamReRouteConfig { get; private set; } public string Aggregator {get; private set;} } } diff --git a/src/Ocelot/Configuration/ServiceProviderConfiguration.cs b/src/Ocelot/Configuration/ServiceProviderConfiguration.cs index 72c3abfb..86178c6e 100644 --- a/src/Ocelot/Configuration/ServiceProviderConfiguration.cs +++ b/src/Ocelot/Configuration/ServiceProviderConfiguration.cs @@ -2,7 +2,7 @@ { public class ServiceProviderConfiguration { - public ServiceProviderConfiguration(string type, string host, int port, string token, string configurationKey, int pollingInterval) + public ServiceProviderConfiguration(string type, string host, int port, string token, string configurationKey, int pollingInterval, string @namespace = "") { ConfigurationKey = configurationKey; Host = host; @@ -10,13 +10,21 @@ Token = token; Type = type; PollingInterval = pollingInterval; + Namespace = @namespace; } public string Host { get; } + public int Port { get; } + public string Type { get; } + public string Token { get; } + public string ConfigurationKey { get; } + public int PollingInterval { get; } + + public string Namespace { get; } } } diff --git a/src/Ocelot/DependencyInjection/IOcelotBuilder.cs b/src/Ocelot/DependencyInjection/IOcelotBuilder.cs index 5e22f8f6..34841c80 100644 --- a/src/Ocelot/DependencyInjection/IOcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/IOcelotBuilder.cs @@ -12,6 +12,8 @@ namespace Ocelot.DependencyInjection IConfiguration Configuration { get; } + IMvcCoreBuilder MvcCoreBuilder { get; } + IOcelotBuilder AddDelegatingHandler(bool global = false) where T : DelegatingHandler; diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index 753bf0e0..6a1722e1 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -42,6 +42,7 @@ namespace Ocelot.DependencyInjection { public IServiceCollection Services { get; } public IConfiguration Configuration { get; } + public IMvcCoreBuilder MvcCoreBuilder { get; } public OcelotBuilder(IServiceCollection services, IConfiguration configurationRoot) { @@ -106,7 +107,6 @@ namespace Ocelot.DependencyInjection 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 @@ -133,17 +133,18 @@ namespace Ocelot.DependencyInjection //add asp.net services.. var assembly = typeof(FileConfigurationController).GetTypeInfo().Assembly; - Services.AddMvcCore() - .AddApplicationPart(assembly) - .AddControllersAsServices() - .AddAuthorization() - .AddJsonFormatters(); + this.MvcCoreBuilder = Services.AddMvcCore() + .AddApplicationPart(assembly) + .AddControllersAsServices() + .AddAuthorization() + .AddJsonFormatters(); Services.AddLogging(); Services.AddMiddlewareAnalysis(); Services.AddWebEncoders(); } + public IOcelotBuilder AddSingletonDefinedAggregator() where T : class, IDefinedAggregator { @@ -170,7 +171,7 @@ namespace Ocelot.DependencyInjection if(global) { Services.AddTransient(); - Services.AddTransient(s => { + Services.AddTransient(s =>{ var service = s.GetService(); return new GlobalDelegatingHandler(service); }); diff --git a/src/Ocelot/Errors/OcelotErrorCode.cs b/src/Ocelot/Errors/OcelotErrorCode.cs index a971c8cd..dd2c249a 100644 --- a/src/Ocelot/Errors/OcelotErrorCode.cs +++ b/src/Ocelot/Errors/OcelotErrorCode.cs @@ -37,6 +37,7 @@ CouldNotFindPlaceholderError, CouldNotFindAggregatorError, CannotAddPlaceholderError, - CannotRemovePlaceholderError + CannotRemovePlaceholderError, + QuotaExceededError } } diff --git a/src/Ocelot/Infrastructure/Claims/Parser/ClaimsParser.cs b/src/Ocelot/Infrastructure/Claims/Parser/ClaimsParser.cs index 773b4375..e25a58c8 100644 --- a/src/Ocelot/Infrastructure/Claims/Parser/ClaimsParser.cs +++ b/src/Ocelot/Infrastructure/Claims/Parser/ClaimsParser.cs @@ -1,6 +1,6 @@ namespace Ocelot.Infrastructure.Claims.Parser { - using Errors; + using Microsoft.Extensions.Primitives; using Responses; using System.Collections.Generic; using System.Linq; @@ -45,14 +45,14 @@ private Response GetValue(IEnumerable claims, string key) { - var claim = claims.FirstOrDefault(c => c.Type == key); + var claimValues = claims.Where(c => c.Type == key).Select(c => c.Value).ToArray(); - if (claim != null) + if (claimValues.Length > 0) { - return new OkResponse(claim.Value); + return new OkResponse(new StringValues(claimValues).ToString()); } return new ErrorResponse(new CannotFindClaimError($"Cannot find claim for key: {key}")); } } -} +} diff --git a/src/Ocelot/Middleware/Multiplexer/InMemoryResponseAggregatorFactory.cs b/src/Ocelot/Middleware/Multiplexer/InMemoryResponseAggregatorFactory.cs index a74386db..b9721e1b 100644 --- a/src/Ocelot/Middleware/Multiplexer/InMemoryResponseAggregatorFactory.cs +++ b/src/Ocelot/Middleware/Multiplexer/InMemoryResponseAggregatorFactory.cs @@ -5,12 +5,12 @@ namespace Ocelot.Middleware.Multiplexer public class InMemoryResponseAggregatorFactory : IResponseAggregatorFactory { private readonly UserDefinedResponseAggregator _userDefined; - private readonly SimpleJsonResponseAggregator _simple; + private readonly IResponseAggregator _simple; - public InMemoryResponseAggregatorFactory(IDefinedAggregatorProvider provider) + public InMemoryResponseAggregatorFactory(IDefinedAggregatorProvider provider, IResponseAggregator responseAggregator) { _userDefined = new UserDefinedResponseAggregator(provider); - _simple = new SimpleJsonResponseAggregator(); + _simple = responseAggregator; } public IResponseAggregator Get(ReRoute reRoute) diff --git a/src/Ocelot/Middleware/Multiplexer/Multiplexer.cs b/src/Ocelot/Middleware/Multiplexer/Multiplexer.cs index 351f8471..d61678e3 100644 --- a/src/Ocelot/Middleware/Multiplexer/Multiplexer.cs +++ b/src/Ocelot/Middleware/Multiplexer/Multiplexer.cs @@ -1,7 +1,9 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Newtonsoft.Json.Linq; using Ocelot.Configuration; +using Ocelot.DownstreamRouteFinder.UrlMatcher; namespace Ocelot.Middleware.Multiplexer { @@ -16,31 +18,105 @@ namespace Ocelot.Middleware.Multiplexer public async Task Multiplex(DownstreamContext context, ReRoute reRoute, OcelotRequestDelegate next) { - var tasks = new Task[reRoute.DownstreamReRoute.Count]; - - for (var i = 0; i < reRoute.DownstreamReRoute.Count; i++) + var reRouteKeysConfigs = reRoute.DownstreamReRouteConfig; + if (reRouteKeysConfigs == null || !reRouteKeysConfigs.Any()) { - var downstreamContext = new DownstreamContext(context.HttpContext) + var tasks = new Task[reRoute.DownstreamReRoute.Count]; + + for (var i = 0; i < reRoute.DownstreamReRoute.Count; i++) + { + var downstreamContext = new DownstreamContext(context.HttpContext) + { + TemplatePlaceholderNameAndValues = context.TemplatePlaceholderNameAndValues, + Configuration = context.Configuration, + DownstreamReRoute = reRoute.DownstreamReRoute[i], + }; + + tasks[i] = Fire(downstreamContext, next); + } + + await Task.WhenAll(tasks); + + var contexts = new List(); + + foreach (var task in tasks) + { + var finished = await task; + contexts.Add(finished); + } + + await Map(reRoute, context, contexts); + } + else + { + var downstreamContextMain = new DownstreamContext(context.HttpContext) { TemplatePlaceholderNameAndValues = context.TemplatePlaceholderNameAndValues, Configuration = context.Configuration, - DownstreamReRoute = reRoute.DownstreamReRoute[i], + DownstreamReRoute = reRoute.DownstreamReRoute[0], }; + var mainResponse = await Fire(downstreamContextMain, next); - tasks[i] = Fire(downstreamContext, next); + if (reRoute.DownstreamReRoute.Count == 1) + { + MapNotAggregate(context, new List() { mainResponse }); + return; + } + + var tasks = new List>(); + if (mainResponse.DownstreamResponse == null) + { + return; + } + + var content = await mainResponse.DownstreamResponse.Content.ReadAsStringAsync(); + var jObject = Newtonsoft.Json.Linq.JToken.Parse(content); + + for (var i = 1; i < reRoute.DownstreamReRoute.Count; i++) + { + var templatePlaceholderNameAndValues = context.TemplatePlaceholderNameAndValues; + var downstreamReRoute = reRoute.DownstreamReRoute[i]; + var matchAdvancedAgg = reRouteKeysConfigs.FirstOrDefault(q => q.ReRouteKey == downstreamReRoute.Key); + if (matchAdvancedAgg != null) + { + var values = jObject.SelectTokens(matchAdvancedAgg.JsonPath).Select(s => s.ToString()).Distinct().ToList(); + + foreach (var value in values) + { + var downstreamContext = new DownstreamContext(context.HttpContext) + { + TemplatePlaceholderNameAndValues = new List(templatePlaceholderNameAndValues), + Configuration = context.Configuration, + DownstreamReRoute = downstreamReRoute, + }; + downstreamContext.TemplatePlaceholderNameAndValues.Add(new PlaceholderNameAndValue("{" + matchAdvancedAgg.Parameter + "}", value.ToString())); + tasks.Add(Fire(downstreamContext, next)); + } + } + else + { + var downstreamContext = new DownstreamContext(context.HttpContext) + { + TemplatePlaceholderNameAndValues = new List(templatePlaceholderNameAndValues), + Configuration = context.Configuration, + DownstreamReRoute = downstreamReRoute, + }; + tasks.Add(Fire(downstreamContext, next)); + } + } + + await Task.WhenAll(tasks); + + var contexts = new List() { mainResponse }; + + foreach (var task in tasks) + { + var finished = await task; + contexts.Add(finished); + } + + await Map(reRoute, context, contexts); } - - await Task.WhenAll(tasks); - - var contexts = new List(); - - foreach (var task in tasks) - { - var finished = await task; - contexts.Add(finished); - } - - await Map(reRoute, context, contexts); } private async Task Map(ReRoute reRoute, DownstreamContext context, List contexts) @@ -55,7 +131,7 @@ namespace Ocelot.Middleware.Multiplexer MapNotAggregate(context, contexts); } } - + private void MapNotAggregate(DownstreamContext originalContext, List downstreamContexts) { //assume at least one..if this errors then it will be caught by global exception handler diff --git a/src/Ocelot/Middleware/Multiplexer/SimpleJsonResponseAggregator.cs b/src/Ocelot/Middleware/Multiplexer/SimpleJsonResponseAggregator.cs index ef2a00f5..d505e7b8 100644 --- a/src/Ocelot/Middleware/Multiplexer/SimpleJsonResponseAggregator.cs +++ b/src/Ocelot/Middleware/Multiplexer/SimpleJsonResponseAggregator.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -21,19 +22,54 @@ namespace Ocelot.Middleware.Multiplexer contentBuilder.Append("{"); - for (var i = 0; i < downstreamContexts.Count; i++) + var responseKeys = downstreamContexts.Select(s => s.DownstreamReRoute.Key).Distinct().ToList(); + + for (var k = 0; k < responseKeys.Count; k++) { - if (downstreamContexts[i].IsError) + var contexts = downstreamContexts.Where(w => w.DownstreamReRoute.Key == responseKeys[k]).ToList(); + if (contexts.Count == 1) { - MapAggregateError(originalContext, downstreamContexts, i); - return; + if (contexts[0].IsError) + { + MapAggregateError(originalContext, contexts[0]); + return; + } + + var content = await contexts[0].DownstreamResponse.Content.ReadAsStringAsync(); + contentBuilder.Append($"\"{responseKeys[k]}\":{content}"); + + } + else + { + contentBuilder.Append($"\"{responseKeys[k]}\":"); + contentBuilder.Append("["); + + for (var i = 0; i < contexts.Count; i++) + { + if (contexts[i].IsError) + { + MapAggregateError(originalContext, contexts[i]); + return; + } + + var content = await contexts[i].DownstreamResponse.Content.ReadAsStringAsync(); + if (string.IsNullOrWhiteSpace(content)) + { + continue; + } + + contentBuilder.Append($"{content}"); + + if (i + 1 < contexts.Count) + { + contentBuilder.Append(","); + } + } + + contentBuilder.Append("]"); } - var content = await downstreamContexts[i].DownstreamResponse.Content.ReadAsStringAsync(); - - contentBuilder.Append($"\"{downstreamContexts[i].DownstreamReRoute.Key}\":{content}"); - - if (i + 1 < downstreamContexts.Count) + if (k + 1 < responseKeys.Count) { contentBuilder.Append(","); } @@ -43,16 +79,16 @@ namespace Ocelot.Middleware.Multiplexer var stringContent = new StringContent(contentBuilder.ToString()) { - Headers = {ContentType = new MediaTypeHeaderValue("application/json")} + Headers = { ContentType = new MediaTypeHeaderValue("application/json") } }; originalContext.DownstreamResponse = new DownstreamResponse(stringContent, HttpStatusCode.OK, new List>>(), "cannot return from aggregate..which reason phrase would you use?"); } - private static void MapAggregateError(DownstreamContext originalContext, List downstreamContexts, int i) + private static void MapAggregateError(DownstreamContext originalContext, DownstreamContext downstreamContext) { - originalContext.Errors.AddRange(downstreamContexts[i].Errors); - originalContext.DownstreamResponse = downstreamContexts[i].DownstreamResponse; + originalContext.Errors.AddRange(downstreamContext.Errors); + originalContext.DownstreamResponse = downstreamContext.DownstreamResponse; } } } diff --git a/src/Ocelot/Ocelot.csproj b/src/Ocelot/Ocelot.csproj index 5892aa96..3b0b9b34 100644 --- a/src/Ocelot/Ocelot.csproj +++ b/src/Ocelot/Ocelot.csproj @@ -26,7 +26,7 @@ True - + diff --git a/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs b/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs index c32fdfb9..4647644e 100644 --- a/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs +++ b/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs @@ -69,6 +69,9 @@ namespace Ocelot.RateLimit.Middleware // break execution await ReturnQuotaExceededResponse(context.HttpContext, options, retrystring); + + // Set Error + context.Errors.Add(new QuotaExceededError(this.GetResponseMessage(options))); return; } @@ -117,7 +120,7 @@ namespace Ocelot.RateLimit.Middleware public virtual Task ReturnQuotaExceededResponse(HttpContext httpContext, RateLimitOptions option, string retryAfter) { - var message = string.IsNullOrEmpty(option.QuotaExceededMessage) ? $"API calls quota exceeded! maximum admitted {option.RateLimitRule.Limit} per {option.RateLimitRule.Period}." : option.QuotaExceededMessage; + var message = this.GetResponseMessage(option); if (!option.DisableRateLimitHeaders) { @@ -128,6 +131,14 @@ namespace Ocelot.RateLimit.Middleware return httpContext.Response.WriteAsync(message); } + private string GetResponseMessage(RateLimitOptions option) + { + var message = string.IsNullOrEmpty(option.QuotaExceededMessage) + ? $"API calls quota exceeded! maximum admitted {option.RateLimitRule.Limit} per {option.RateLimitRule.Period}." + : option.QuotaExceededMessage; + return message; + } + private Task SetRateLimitHeaders(object rateLimitHeaders) { var headers = (RateLimitHeaders)rateLimitHeaders; diff --git a/src/Ocelot/RateLimit/QuotaExceededError.cs b/src/Ocelot/RateLimit/QuotaExceededError.cs new file mode 100644 index 00000000..f025161c --- /dev/null +++ b/src/Ocelot/RateLimit/QuotaExceededError.cs @@ -0,0 +1,12 @@ +using Ocelot.Errors; + +namespace Ocelot.RateLimit +{ + public class QuotaExceededError : Error + { + public QuotaExceededError(string message) + : base(message, OcelotErrorCode.QuotaExceededError) + { + } + } +} diff --git a/src/Ocelot/Request/Middleware/DownstreamRequest.cs b/src/Ocelot/Request/Middleware/DownstreamRequest.cs index 75070bfd..665039d0 100644 --- a/src/Ocelot/Request/Middleware/DownstreamRequest.cs +++ b/src/Ocelot/Request/Middleware/DownstreamRequest.cs @@ -44,7 +44,7 @@ namespace Ocelot.Request.Middleware Port = Port, Host = Host, Path = AbsolutePath, - Query = Query, + Query = RemoveLeadingQuestionMark(Query), Scheme = Scheme }; @@ -59,7 +59,7 @@ namespace Ocelot.Request.Middleware Port = Port, Host = Host, Path = AbsolutePath, - Query = Query, + Query = RemoveLeadingQuestionMark(Query), Scheme = Scheme }; @@ -70,5 +70,15 @@ namespace Ocelot.Request.Middleware { return ToUri(); } + + private string RemoveLeadingQuestionMark(string query) + { + if (!string.IsNullOrEmpty(query) && query.StartsWith("?")) + { + return query.Substring(1); + } + + return query; + } } } diff --git a/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs b/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs index 4204374b..92d3128e 100644 --- a/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs +++ b/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs @@ -33,6 +33,8 @@ namespace Ocelot.Requester.Middleware Logger.LogDebug("setting http response message"); context.DownstreamResponse = new DownstreamResponse(response.Data); + + await _next.Invoke(context); } } -} +} diff --git a/src/Ocelot/Responder/HttpContextResponder.cs b/src/Ocelot/Responder/HttpContextResponder.cs index 0e964c7a..ebb09ab2 100644 --- a/src/Ocelot/Responder/HttpContextResponder.cs +++ b/src/Ocelot/Responder/HttpContextResponder.cs @@ -32,6 +32,15 @@ namespace Ocelot.Responder AddHeaderIfDoesntExist(context, httpResponseHeader); } + SetStatusCode(context, (int)response.StatusCode); + + context.Response.HttpContext.Features.Get().ReasonPhrase = response.ReasonPhrase; + + if (response.Content is null) + { + return; + } + foreach (var httpResponseHeader in response.Content.Headers) { AddHeaderIfDoesntExist(context, new Header(httpResponseHeader.Key, httpResponseHeader.Value)); @@ -44,10 +53,6 @@ namespace Ocelot.Responder AddHeaderIfDoesntExist(context, new Header("Content-Length", new []{ response.Content.Headers.ContentLength.ToString() }) ); } - SetStatusCode(context, (int)response.StatusCode); - - context.Response.HttpContext.Features.Get().ReasonPhrase = response.ReasonPhrase; - using(content) { if (response.StatusCode != HttpStatusCode.NotModified && context.Response.ContentLength != 0) diff --git a/test/Ocelot.AcceptanceTests/AggregateTests.cs b/test/Ocelot.AcceptanceTests/AggregateTests.cs index a98fd3ef..e3d99854 100644 --- a/test/Ocelot.AcceptanceTests/AggregateTests.cs +++ b/test/Ocelot.AcceptanceTests/AggregateTests.cs @@ -141,6 +141,100 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } + [Fact] + public void should_return_response_200_with_advanced_aggregate_configs() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51889, + } + }, + UpstreamPathTemplate = "/Comments", + UpstreamHttpMethod = new List { "Get" }, + Key = "Comments" + }, + new FileReRoute + { + DownstreamPathTemplate = "/users/{userId}", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51890, + } + }, + UpstreamPathTemplate = "/UserDetails", + UpstreamHttpMethod = new List { "Get" }, + Key = "UserDetails" + }, + new FileReRoute + { + DownstreamPathTemplate = "/posts/{postId}", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51887, + } + }, + UpstreamPathTemplate = "/PostDetails", + UpstreamHttpMethod = new List { "Get" }, + Key = "PostDetails" + } + }, + Aggregates = new List + { + new FileAggregateReRoute + { + UpstreamPathTemplate = "/", + UpstreamHost = "localhost", + ReRouteKeys = new List + { + "Comments", + "UserDetails", + "PostDetails" + }, + ReRouteKeysConfig = new List() + { + new AggregateReRouteConfig(){ReRouteKey = "UserDetails",JsonPath = "$[*].writerId",Parameter = "userId"}, + new AggregateReRouteConfig(){ReRouteKey = "PostDetails",JsonPath = "$[*].postId",Parameter = "postId"} + }, + } + } + }; + + var userDetailsResponseContent = @"{""id"":1,""firstName"":""abolfazl"",""lastName"":""rajabpour""}"; + var postDetailsResponseContent = @"{""id"":1,""title"":""post1""}"; + var commentsResponseContent = @"[{""id"":1,""writerId"":1,""postId"":2,""text"":""text1""},{""id"":2,""writerId"":1,""postId"":2,""text"":""text2""}]"; + + var expected = "{\"Comments\":" + commentsResponseContent + ",\"UserDetails\":" + userDetailsResponseContent + ",\"PostDetails\":" + postDetailsResponseContent + "}"; + + this.Given(x => x.GivenServiceOneIsRunning("http://localhost:51889", "/", 200, commentsResponseContent)) + .Given(x => x.GivenServiceTwoIsRunning("http://localhost:51890", "/users/1", 200, userDetailsResponseContent)) + .Given(x => x.GivenServiceTwoIsRunning("http://localhost:51887", "/posts/2", 200, postDetailsResponseContent)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe(expected)) + .BDDfy(); + } + [Fact] public void should_return_response_200_with_simple_url_user_defined_aggregate() { @@ -189,8 +283,8 @@ namespace Ocelot.AcceptanceTests UpstreamHost = "localhost", ReRouteKeys = new List { - "Tom", - "Laura" + "Laura", + "Tom" }, Aggregator = "FakeDefinedAggregator" } @@ -258,8 +352,8 @@ namespace Ocelot.AcceptanceTests UpstreamHost = "localhost", ReRouteKeys = new List { - "Tom", - "Laura" + "Laura", + "Tom" } } } @@ -326,8 +420,9 @@ namespace Ocelot.AcceptanceTests UpstreamHost = "localhost", ReRouteKeys = new List { - "Tom", - "Laura" + "Laura", + "Tom" + } } } @@ -394,8 +489,8 @@ namespace Ocelot.AcceptanceTests UpstreamHost = "localhost", ReRouteKeys = new List { - "Tom", - "Laura" + "Laura", + "Tom" } } } @@ -462,8 +557,8 @@ namespace Ocelot.AcceptanceTests UpstreamHost = "localhost", ReRouteKeys = new List { - "Tom", - "Laura" + "Laura", + "Tom" } } } diff --git a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs index 4bdf014b..d93e6911 100644 --- a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs +++ b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs @@ -434,7 +434,8 @@ Host = "localhost", Port = consulPort, Type = "PollConsul", - PollingInterval = 0 + PollingInterval = 0, + Namespace = string.Empty } } }; diff --git a/test/Ocelot.UnitTests/Configuration/ServiceProviderCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/ServiceProviderCreatorTests.cs index f1ec3a07..2094a172 100644 --- a/test/Ocelot.UnitTests/Configuration/ServiceProviderCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/ServiceProviderCreatorTests.cs @@ -30,7 +30,8 @@ namespace Ocelot.UnitTests.Configuration Port = 1234, Type = "ServiceFabric", Token = "testtoken", - ConfigurationKey = "woo" + ConfigurationKey = "woo", + Namespace ="default" } }; @@ -40,13 +41,14 @@ namespace Ocelot.UnitTests.Configuration .WithType("ServiceFabric") .WithToken("testtoken") .WithConfigurationKey("woo") + .WithNamespace("default") .Build(); this.Given(x => x.GivenTheFollowingGlobalConfig(globalConfig)) .When(x => x.WhenICreate()) .Then(x => x.ThenTheConfigIs(expected)) .BDDfy(); - } + } private void GivenTheFollowingGlobalConfig(FileGlobalConfiguration fileGlobalConfig) { @@ -64,6 +66,7 @@ namespace Ocelot.UnitTests.Configuration _result.Port.ShouldBe(expected.Port); _result.Token.ShouldBe(expected.Token); _result.Type.ShouldBe(expected.Type); + _result.Namespace.ShouldBe(expected.Namespace); _result.ConfigurationKey.ShouldBe(expected.ConfigurationKey); } } diff --git a/test/Ocelot.UnitTests/Kubernetes/KubeProviderFactoryTests.cs b/test/Ocelot.UnitTests/Kubernetes/KubeProviderFactoryTests.cs new file mode 100644 index 00000000..0627e7ba --- /dev/null +++ b/test/Ocelot.UnitTests/Kubernetes/KubeProviderFactoryTests.cs @@ -0,0 +1,35 @@ +using Microsoft.Extensions.DependencyInjection; +using Moq; +using Ocelot.Configuration; +using Ocelot.Logging; +using Ocelot.Provider.Kubernetes; +using Shouldly; +using System; +using Xunit; + +namespace Ocelot.UnitTests.Kubernetes +{ + public class KubeProviderFactoryTests + { + private readonly IServiceProvider _provider; + + public KubeProviderFactoryTests() + { + var services = new ServiceCollection(); + var loggerFactory = new Mock(); + var logger = new Mock(); + loggerFactory.Setup(x => x.CreateLogger()).Returns(logger.Object); + var kubeFactory = new Mock(); + services.AddSingleton(kubeFactory.Object); + services.AddSingleton(loggerFactory.Object); + _provider = services.BuildServiceProvider(); + } + + [Fact] + public void should_return_KubeServiceDiscoveryProvider() + { + var provider = KubernetesProviderFactory.Get(_provider, new ServiceProviderConfiguration("kube", "localhost", 443, "", "", 1,"dev"), ""); + provider.ShouldBeOfType(); + } + } +} diff --git a/test/Ocelot.UnitTests/Kubernetes/KubeServiceDiscoveryProviderTests.cs b/test/Ocelot.UnitTests/Kubernetes/KubeServiceDiscoveryProviderTests.cs new file mode 100644 index 00000000..ccf44fcd --- /dev/null +++ b/test/Ocelot.UnitTests/Kubernetes/KubeServiceDiscoveryProviderTests.cs @@ -0,0 +1,147 @@ +using KubeClient.Models; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Moq; +using Newtonsoft.Json; +using Ocelot.Logging; +using Ocelot.Provider.Kubernetes; +using Ocelot.Values; +using Shouldly; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Kubernetes +{ + public class KubeServiceDiscoveryProviderTests : IDisposable + { + private IWebHost _fakeKubeBuilder; + private ServiceV1 _serviceEntries; + private Kube _provider; + private readonly string _serviceName; + private readonly string _namespaces; + private readonly int _port; + private readonly string _kubeHost; + private readonly string _fakekubeServiceDiscoveryUrl; + private List _services; + private readonly Mock _factory; + private readonly Mock _logger; + private string _receivedToken; + private readonly IKubeApiClientFactory _clientFactory; + + public KubeServiceDiscoveryProviderTests() + { + _serviceName = "test"; + _namespaces = "dev"; + _port = 8001; + _kubeHost = "localhost"; + _fakekubeServiceDiscoveryUrl = $"http://{_kubeHost}:{_port}"; + _serviceEntries = new ServiceV1(); + _factory = new Mock(); + _clientFactory = new KubeApiClientFactory(); + _logger = new Mock(); + _factory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + var config = new KubeRegistryConfiguration() + { + ApiEndPoint = new Uri(_fakekubeServiceDiscoveryUrl), + AccessToken = "txpc696iUhbVoudg164r93CxDTrKRVWG", + AllowInsecure = true, + AuthStrategy = KubeClient.KubeAuthStrategy.BearerToken, + KeyOfServiceInK8s = _serviceName, + KubeNamespace = _namespaces + }; + _provider = new Kube(config, _factory.Object, _clientFactory); + } + + [Fact] + public void should_return_service_from_k8s() + { + var token = "Bearer txpc696iUhbVoudg164r93CxDTrKRVWG"; + var serviceEntryOne = new ServiceV1() + { + Kind = "service", + ApiVersion = "1.0", + Metadata = new ObjectMetaV1() + { + Namespace = "dev" + }, + Spec = new ServiceSpecV1() + { + ClusterIP = "localhost" + }, + Status = new ServiceStatusV1() { + LoadBalancer = new LoadBalancerStatusV1() + } + }; + + serviceEntryOne.Spec.Ports.Add( + new ServicePortV1() + { + Port = 80 + } + ); + + this.Given(x => GivenThereIsAFakeKubeServiceDiscoveryProvider(_fakekubeServiceDiscoveryUrl, _serviceName, _namespaces)) + .And(x => GivenTheServicesAreRegisteredWithKube(serviceEntryOne)) + .When(x => WhenIGetTheServices()) + .Then(x => ThenTheCountIs(1)) + .And(_ => _receivedToken.ShouldBe(token)) + .BDDfy(); + } + + private void ThenTheCountIs(int count) + { + _services.Count.ShouldBe(count); + } + + private void WhenIGetTheServices() + { + _services = _provider.Get().GetAwaiter().GetResult(); + } + + private void GivenTheServicesAreRegisteredWithKube(ServiceV1 serviceEntries) + { + _serviceEntries = serviceEntries; + } + + + private void GivenThereIsAFakeKubeServiceDiscoveryProvider(string url, string serviceName, string namespaces) + { + _fakeKubeBuilder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(async context => + { + if (context.Request.Path.Value == $"/api/v1/namespaces/{namespaces}/services/{serviceName}") + { + if (context.Request.Headers.TryGetValue("Authorization", out var values)) + { + _receivedToken = values.First(); + } + + var json = JsonConvert.SerializeObject(_serviceEntries); + context.Response.Headers.Add("Content-Type", "application/json"); + await context.Response.WriteAsync(json); + } + }); + }) + .Build(); + + _fakeKubeBuilder.Start(); + } + + public void Dispose() + { + _fakeKubeBuilder?.Dispose(); + } + } +} diff --git a/test/Ocelot.UnitTests/Kubernetes/OcelotBuilderExtensionsTests.cs b/test/Ocelot.UnitTests/Kubernetes/OcelotBuilderExtensionsTests.cs new file mode 100644 index 00000000..103f94aa --- /dev/null +++ b/test/Ocelot.UnitTests/Kubernetes/OcelotBuilderExtensionsTests.cs @@ -0,0 +1,70 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Internal; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Ocelot.DependencyInjection; +using Ocelot.Provider.Kubernetes; +using Shouldly; +using System; +using System.Collections.Generic; +using System.Text; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Kubernetes +{ + public class OcelotBuilderExtensionsTests + { + private readonly IServiceCollection _services; + private IServiceProvider _serviceProvider; + private readonly IConfiguration _configRoot; + private IOcelotBuilder _ocelotBuilder; + private Exception _ex; + + public OcelotBuilderExtensionsTests() + { + _configRoot = new ConfigurationRoot(new List()); + _services = new ServiceCollection(); + _services.AddSingleton(); + _services.AddSingleton(_configRoot); + } + + [Fact] + public void should_set_up_kubernetes() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => WhenISetUpKubernetes()) + .Then(x => ThenAnExceptionIsntThrown()) + .BDDfy(); + } + + private void WhenISetUpOcelotServices() + { + try + { + _ocelotBuilder = _services.AddOcelot(_configRoot); + } + catch (Exception e) + { + _ex = e; + } + } + + private void WhenISetUpKubernetes() + { + try + { + _ocelotBuilder.AddKubernetes(); + } + catch (Exception e) + { + _ex = e; + } + } + + private void ThenAnExceptionIsntThrown() + { + _ex.ShouldBeNull(); + } + } +} diff --git a/test/Ocelot.UnitTests/Kubernetes/PollingKubeServiceDiscoveryProviderTests.cs b/test/Ocelot.UnitTests/Kubernetes/PollingKubeServiceDiscoveryProviderTests.cs new file mode 100644 index 00000000..49f63afe --- /dev/null +++ b/test/Ocelot.UnitTests/Kubernetes/PollingKubeServiceDiscoveryProviderTests.cs @@ -0,0 +1,82 @@ +using Moq; +using Ocelot.Infrastructure; +using Ocelot.Logging; +using Ocelot.Provider.Kubernetes; +using Ocelot.ServiceDiscovery.Providers; +using Ocelot.Values; +using Shouldly; +using System; +using System.Collections.Generic; +using System.Text; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Kubernetes +{ + public class PollingKubeServiceDiscoveryProviderTests + { + private readonly int _delay; + private PollKube _provider; + private readonly List _services; + private readonly Mock _factory; + private readonly Mock _logger; + private readonly Mock _kubeServiceDiscoveryProvider; + private List _result; + + public PollingKubeServiceDiscoveryProviderTests() + { + _services = new List(); + _delay = 1; + _factory = new Mock(); + _logger = new Mock(); + _factory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _kubeServiceDiscoveryProvider = new Mock(); + } + + [Fact] + public void should_return_service_from_kube() + { + var service = new Service("", new ServiceHostAndPort("", 0), "", "", new List()); + + this.Given(x => GivenKubeReturns(service)) + .When(x => WhenIGetTheServices(1)) + .Then(x => ThenTheCountIs(1)) + .BDDfy(); + } + + private void GivenKubeReturns(Service service) + { + _services.Add(service); + _kubeServiceDiscoveryProvider.Setup(x => x.Get()).ReturnsAsync(_services); + } + + private void ThenTheCountIs(int count) + { + _result.Count.ShouldBe(count); + } + + private void WhenIGetTheServices(int expected) + { + _provider = new PollKube(_delay, _factory.Object, _kubeServiceDiscoveryProvider.Object); + + var result = Wait.WaitFor(3000).Until(() => { + try + { + _result = _provider.Get().GetAwaiter().GetResult(); + if (_result.Count == expected) + { + return true; + } + + return false; + } + catch (Exception) + { + return false; + } + }); + + result.ShouldBeTrue(); + } + } +} diff --git a/test/Ocelot.UnitTests/Middleware/ResponseAggregatorFactoryTests.cs b/test/Ocelot.UnitTests/Middleware/ResponseAggregatorFactoryTests.cs index b4413b45..16a682ce 100644 --- a/test/Ocelot.UnitTests/Middleware/ResponseAggregatorFactoryTests.cs +++ b/test/Ocelot.UnitTests/Middleware/ResponseAggregatorFactoryTests.cs @@ -18,7 +18,8 @@ namespace Ocelot.UnitTests.Middleware public ResponseAggregatorFactoryTests() { _provider = new Mock(); - _factory = new InMemoryResponseAggregatorFactory(_provider.Object); + _aggregator = new SimpleJsonResponseAggregator(); + _factory = new InMemoryResponseAggregatorFactory(_provider.Object, _aggregator); } [Fact] diff --git a/test/Ocelot.UnitTests/Middleware/SimpleJsonResponseAggregatorTests.cs b/test/Ocelot.UnitTests/Middleware/SimpleJsonResponseAggregatorTests.cs index 7d2e5edd..a391ce59 100644 --- a/test/Ocelot.UnitTests/Middleware/SimpleJsonResponseAggregatorTests.cs +++ b/test/Ocelot.UnitTests/Middleware/SimpleJsonResponseAggregatorTests.cs @@ -7,11 +7,11 @@ using Castle.Components.DictionaryAdapter; using Microsoft.AspNetCore.Http; using Ocelot.Configuration; using Ocelot.Configuration.Builder; -using Ocelot.Errors; +using Ocelot.Configuration.File; using Ocelot.Middleware; using Ocelot.Middleware.Multiplexer; -using Ocelot.Request.Middleware; using Ocelot.UnitTests.Responder; +using Ocelot.Values; using Shouldly; using TestStack.BDDfy; using Xunit; @@ -30,6 +30,58 @@ namespace Ocelot.UnitTests.Middleware _aggregator = new SimpleJsonResponseAggregator(); } + [Fact] + public void should_aggregate_n_responses_and_set_response_content_on_upstream_context_withConfig() + { + var commentsDownstreamReRoute = new DownstreamReRouteBuilder().WithKey("Comments").Build(); + + var userDetailsDownstreamReRoute = new DownstreamReRouteBuilder().WithKey("UserDetails") + .WithUpstreamPathTemplate(new UpstreamPathTemplate("", 0, false, "/v1/users/{userId}")) + .Build(); + + var downstreamReRoutes = new List + { + commentsDownstreamReRoute, + userDetailsDownstreamReRoute + }; + + var reRoute = new ReRouteBuilder() + .WithDownstreamReRoutes(downstreamReRoutes) + .WithAggregateReRouteConfig(new List() + { + new AggregateReRouteConfig(){ReRouteKey = "UserDetails",JsonPath = "$[*].writerId",Parameter = "userId"} + }) + .Build(); + + var commentsResponseContent = @"[{""id"":1,""writerId"":1,""postId"":1,""text"":""text1""},{""id"":2,""writerId"":2,""postId"":2,""text"":""text2""},{""id"":3,""writerId"":2,""postId"":1,""text"":""text21""}]"; + var commentsDownstreamContext = new DownstreamContext(new DefaultHttpContext()) + { + DownstreamResponse = new DownstreamResponse(new StringContent(commentsResponseContent, Encoding.UTF8, "application/json"), HttpStatusCode.OK, new EditableList>>(), "some reason"), + DownstreamReRoute = commentsDownstreamReRoute + }; + + var userDetailsResponseContent = @"[{""id"":1,""firstName"":""abolfazl"",""lastName"":""rajabpour""},{""id"":2,""firstName"":""reza"",""lastName"":""rezaei""}]"; + var userDetailsDownstreamContext = new DownstreamContext(new DefaultHttpContext()) + { + DownstreamResponse = new DownstreamResponse(new StringContent(userDetailsResponseContent, Encoding.UTF8, "application/json"), HttpStatusCode.OK, new List>>(), "some reason"), + DownstreamReRoute = userDetailsDownstreamReRoute + }; + + var downstreamContexts = new List { commentsDownstreamContext, userDetailsDownstreamContext }; + + var expected = "{\"Comments\":" + commentsResponseContent + ",\"UserDetails\":" + userDetailsResponseContent + "}"; + + this.Given(x => GivenTheUpstreamContext(new DownstreamContext(new DefaultHttpContext()))) + .And(x => GivenTheReRoute(reRoute)) + .And(x => GivenTheDownstreamContext(downstreamContexts)) + .When(x => WhenIAggregate()) + .Then(x => ThenTheContentIs(expected)) + .And(x => ThenTheContentTypeIs("application/json")) + .And(x => ThenTheReasonPhraseIs("cannot return from aggregate..which reason phrase would you use?")) + .BDDfy(); + } + + [Fact] public void should_aggregate_n_responses_and_set_response_content_on_upstream_context() { diff --git a/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj b/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj index 67c264e8..ae11a695 100644 --- a/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj +++ b/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj @@ -20,6 +20,7 @@ + @@ -72,7 +73,7 @@ - + diff --git a/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs b/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs index 54e39eac..67acc123 100644 --- a/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs +++ b/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs @@ -125,7 +125,7 @@ namespace Ocelot.UnitTests.Responder // 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(36, "Looks like the number of error codes has changed. Do you need to modify ErrorsToHttpStatusCodeMapper?"); + Enum.GetNames(typeof(OcelotErrorCode)).Length.ShouldBe(37, "Looks like the number of error codes has changed. Do you need to modify ErrorsToHttpStatusCodeMapper?"); } private void ShouldMapErrorToStatusCode(OcelotErrorCode errorCode, HttpStatusCode expectedHttpStatusCode) diff --git a/test/Ocelot.UnitTests/Responder/HttpContextResponderTests.cs b/test/Ocelot.UnitTests/Responder/HttpContextResponderTests.cs index a5716908..89e8a045 100644 --- a/test/Ocelot.UnitTests/Responder/HttpContextResponderTests.cs +++ b/test/Ocelot.UnitTests/Responder/HttpContextResponderTests.cs @@ -39,6 +39,23 @@ namespace Ocelot.UnitTests.Responder header.ShouldBeEmpty(); } + [Fact] + public void should_ignore_content_if_null() + { + var httpContext = new DefaultHttpContext(); + var response = new DownstreamResponse(null, HttpStatusCode.OK, + new List>>(), "some reason"); + + Should.NotThrow(() => + { + _responder + .SetResponseOnHttpContext(httpContext, response) + .GetAwaiter() + .GetResult() + ; + }); + } + [Fact] public void should_have_content_length() { From 821492fccfb7056d9af374dbae4287ef7226ced4 Mon Sep 17 00:00:00 2001 From: Thiago Loureiro de Azevedo Date: Sat, 23 Mar 2019 22:31:27 +0100 Subject: [PATCH 2/7] Removed Warnings --- src/Ocelot/DependencyInjection/OcelotBuilder.cs | 10 +++++----- .../Multiplexer/SimpleJsonResponseAggregator.cs | 1 - 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index 6a1722e1..9f81e11d 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -72,7 +72,7 @@ namespace Ocelot.DependencyInjection Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); - Services.TryAddSingleton(); + Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); @@ -127,7 +127,7 @@ namespace Ocelot.DependencyInjection Services.TryAddSingleton(); Services.TryAddSingleton(); - //add security + //add security this.AddSecurity(); //add asp.net services.. @@ -144,7 +144,6 @@ namespace Ocelot.DependencyInjection Services.AddWebEncoders(); } - public IOcelotBuilder AddSingletonDefinedAggregator() where T : class, IDefinedAggregator { @@ -168,10 +167,11 @@ namespace Ocelot.DependencyInjection public IOcelotBuilder AddDelegatingHandler(bool global = false) where THandler : DelegatingHandler { - if(global) + if (global) { Services.AddTransient(); - Services.AddTransient(s =>{ + Services.AddTransient(s => + { var service = s.GetService(); return new GlobalDelegatingHandler(service); }); diff --git a/src/Ocelot/Middleware/Multiplexer/SimpleJsonResponseAggregator.cs b/src/Ocelot/Middleware/Multiplexer/SimpleJsonResponseAggregator.cs index d505e7b8..8524519e 100644 --- a/src/Ocelot/Middleware/Multiplexer/SimpleJsonResponseAggregator.cs +++ b/src/Ocelot/Middleware/Multiplexer/SimpleJsonResponseAggregator.cs @@ -37,7 +37,6 @@ namespace Ocelot.Middleware.Multiplexer var content = await contexts[0].DownstreamResponse.Content.ReadAsStringAsync(); contentBuilder.Append($"\"{responseKeys[k]}\":{content}"); - } else { From 57580afa7448c4a1c0260672a48f82b336684882 Mon Sep 17 00:00:00 2001 From: Thiago Loureiro Date: Mon, 20 May 2019 16:25:44 +0800 Subject: [PATCH 3/7] Release/13.6.0 (#895) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fixed Format Issue for Kubernetes ServiceDiscoveryProvider * Fixes broken links (#858) * Fix link to issue 262 * Fixes broken link to issue 340 * Fixed broken link to issue 340 (#857) * Update information for Okta Authorization (#853) * +dynamic claim variables (#855) incl. tests * IOcelotPipelineBuilder.Use(): Return IOcelotPipelineBuilder (#875) Fixes ThreeMammals/Ocelot#685 * Fix UpstreamHost checking when reroutes duplicate validation (#864) * Format json in reame (#877) Format json file in AdministrationApi ReadMe * kubernetes use in cluster (#882) * refactor :kubernetes use in cluster * feat:delete KubeClient * add more flexible method to config ocelot pipeline (#880) * update k8s doc & samples (#885) * refactor :kubernetes use in cluster * feat:delete KubeClient * feat : update k8s doc & samples * Update kubernetes.rst * Fix/issue666 (#889) * cache key now can generate from query string for request with Get Methods and request content for requests with post methods * MD5Helper Added. OutputCacheMiddleware now can generate cache key using method, url and content * unit test created for CacheKeyGenerator * CacheKeyGenerator Registered in OcelotBuilder as singletone * Fix issue #890 IDefinedAggregator can't handle error codes from downstream requests (#892) * Release/13.2.0 (#834) * Fix formatting in getting started page (#752) * updated release docs (#745) * Update README.md (#756) Fixed typo "Ocleot" * Fixed typo there => their (#763) * Some Typo fixes (#765) * Typo algorythm => algorithm (#764) * Typo querystring => query string (#766) * Typo usual => usually (#767) * Typos (#768) * kubernetes provider (#772) * feat: Kubernetes ServiceDiscoveryProvider * 编写k8s测试例子 * feat:fix kube config * feat: remove port * feat : complete the k8s test * feat : add kubeserviceDiscovery test * feat : add kube provider unittest * feat :add kubetnetes docs how to use ocelot with kubetnetes docs * keep the configuration as simple as possible, no qos, no cache * fix: use http * add PollingKubeServiceDiscovery * feat : refactor logger * feat : add pollkube docs * feat:Remove unnecessary code * feat : code-block json * fix issue #661 for Advanced aggregations (#704) * Add Advanced Aggregation Feature * fix overwrite error * distinct data for better performance * remove constructor parameter * fix tests issue * fix tests * fix tests issue * Add UnitTest and AcceptanceTest * fix responseKeys typo * Update SimpleJsonResponseAggregator.cs * change port * Fix code example for SSL Errors (#780) DangerousAcceptAnyServerCertificateValidator has to be set to "true" to disable certification validation, not "false". * Changed wording for ease of reading (#776) Just some wording changes for clarification. * Ignore response content if null (fix #785) (#786) * fix bug #791 (#795) * Update loadbalancer.rst (#796) * UriBuilder - remove leading question mark #747 (#794) * Update qualityofservice.rst (#801) Tiny typo * K8s package (#804) * feat: Kubernetes ServiceDiscoveryProvider * 编写k8s测试例子 * feat:fix kube config * feat: remove port * feat : complete the k8s test * feat : add kubeserviceDiscovery test * feat : add kube provider unittest * feat :add kubetnetes docs how to use ocelot with kubetnetes docs * keep the configuration as simple as possible, no qos, no cache * fix: use http * add PollingKubeServiceDiscovery * feat : refactor logger * feat : add pollkube docs * feat:Remove unnecessary code * feat : code-block json * feat: publish package Ocelot.Provider.Kubernetes * Okta integration (#807) Okta integration * update cliamsParser (#798) * update cliamsParser * update using * IOcelotBuilder opens the IMvcCoreBuilder property for easy customization (#790) * IOcelotBuilder opens the IMvcCoreBuilder property for easy customization * Adjustment code * nuget package (#809) * feat: Kubernetes ServiceDiscoveryProvider * 编写k8s测试例子 * feat:fix kube config * feat: remove port * feat : complete the k8s test * feat : add kubeserviceDiscovery test * feat : add kube provider unittest * feat :add kubetnetes docs how to use ocelot with kubetnetes docs * keep the configuration as simple as possible, no qos, no cache * fix: use http * add PollingKubeServiceDiscovery * feat : refactor logger * feat : add pollkube docs * feat:Remove unnecessary code * feat : code-block json * feat: publish package Ocelot.Provider.Kubernetes * feat : nuget package * fix: Namesapce Spelling wrong * fix:Namesapce Spelling Wrong * Fix: errors when using rate limiting (#811) * Fix: errors when using rate limiting Add: QuotaExceededError class for requesting too much Add: QuotaExceededError error code Add: Add an error when limit is reached Reflact: Extract GetResponseMessage method for getting default or configured response message for requ * Fix: modify check_we_have_considered_all_errors_in_these_tests for adding a new OcelotErrorCode * added missing COPY csproj files (#821) * Add note on In-Process hosting (#816) When using ASP.NET Core 2.2 with In-Process hosting in IIS it's important to use .UseIIS() instead of .UseIISIntegration(). * Fix bug: (#810) If the registered Consul node is unexpectedly down and not restarted immediately, other services should continue to find the registered service. * Fixed Dockerfile (missing Kubernetes) * Revert "Fix bug: (#810)" (#823) This reverts commit 19c80afb05290fac3a144f652cd663c8b513a559. * remove duplicate `IHttpRequester` register (#819) * remove duplicate `IHttpRequester` register * reserve the first * fix HttpRequesterMiddleware does not call next bug (#830) call next so that we can do something with the response, such as add some custom header etc... * Removed Packing to fix issues, will be sorted out after create a nuget package on Nuget.Org (#831) * Allows access to unpass node (#825) * Fix bug: If the registered Consul node is unexpectedly down and not restarted immediately, other services should continue to find the registered service. * fix bug: If the registered Consul node is unexpectedly down and not restarted immediately, other services should continue to find the registered service. * Updated FluentValidations Nuget Package (#833) * Removed Warnings * Make the full DownstreamContext available to user defined aggregators This allows error codes to be handled --- README.md | 1 + docs/features/authentication.rst | 47 +++++++++----- docs/features/kubernetes.rst | 33 +++++++--- docs/features/routing.rst | 2 +- docs/features/servicediscovery.rst | 4 +- samples/AdministrationApi/README.md | 4 +- .../OelotKube/ApiGateway/ApiGateway.csproj | 7 +- samples/OelotKube/ApiGateway/Dockerfile | 8 +-- samples/OelotKube/OelotKube.sln | 16 +---- .../OcelotBuilderExtensions.cs | 4 ++ .../KubeProvider.cs | 5 +- .../KubeRegistryConfiguration.cs | 8 --- .../KubernetesProviderFactory.cs | 8 +-- .../Ocelot.Provider.Kubernetes.csproj | 8 ++- .../OcelotBuilderExtensions.cs | 7 +- src/Ocelot/Authorisation/ClaimsAuthoriser.cs | 64 ++++++++++++++++--- src/Ocelot/Authorisation/IClaimsAuthoriser.cs | 10 ++- .../Middleware/AuthorisationMiddleware.cs | 4 +- src/Ocelot/Cache/CacheKeyGenerator.cs | 19 ++++++ src/Ocelot/Cache/ICacheKeyGenerator.cs | 7 ++ src/Ocelot/Cache/MD5Helper.cs | 22 +++++++ .../Cache/Middleware/OutputCacheMiddleware.cs | 14 ++-- .../FileConfigurationFluentValidator.cs | 2 +- .../DependencyInjection/OcelotBuilder.cs | 1 + .../Multiplexer/IDefinedAggregator.cs | 2 +- .../UserDefinedResponseAggregator.cs | 3 +- .../Middleware/OcelotMiddlewareExtensions.cs | 40 +++++++++--- .../Pipeline/IOcelotPipelineBuilder.cs | 2 +- .../Pipeline/OcelotPipelineBuilder.cs | 2 +- .../Request/Middleware/DownstreamRequest.cs | 3 + test/Ocelot.AcceptanceTests/AggregateTests.cs | 8 +-- .../AuthorisationMiddlewareTests.cs | 12 +++- .../Authorization/ClaimsAuthoriserTests.cs | 52 ++++++++++++++- .../Cache/CacheKeyGeneratorTests.cs | 34 ++++++++++ .../Cache/OutputCacheMiddlewareTests.cs | 5 +- .../OutputCacheMiddlewareRealCacheTests.cs | 9 ++- .../FileConfigurationFluentValidatorTests.cs | 2 +- .../KubeServiceDiscoveryProviderTests.cs | 20 ++++-- .../UserDefinedResponseAggregatorTests.cs | 8 +-- test/Ocelot.UnitTests/Ocelot.UnitTests.csproj | 4 ++ 40 files changed, 383 insertions(+), 128 deletions(-) create mode 100644 src/Ocelot/Cache/CacheKeyGenerator.cs create mode 100644 src/Ocelot/Cache/ICacheKeyGenerator.cs create mode 100644 src/Ocelot/Cache/MD5Helper.cs create mode 100644 test/Ocelot.UnitTests/Cache/CacheKeyGeneratorTests.cs diff --git a/README.md b/README.md index 4c69c3cc..222d9abc 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ A quick list of Ocelot's capabilities for more information see the [documentatio * Request Aggregation * Service Discovery with Consul & Eureka * Service Fabric +* Kubernetes * WebSockets * Authentication * Authorisation diff --git a/docs/features/authentication.rst b/docs/features/authentication.rst index cb2ec08d..ba230a81 100644 --- a/docs/features/authentication.rst +++ b/docs/features/authentication.rst @@ -138,26 +138,39 @@ Then map the authentication provider key to a ReRoute in your configuration e.g. Okta ^^^^ -Add nuget package : `"Okta.AspNetCore" https://www.nuget.org/packages/Okta.AspNetCore/`_ +Add the following to your startup Configure method: -In a StartUp.cs file add to a method Configure next lines: -app.UseAuthentication(); -app.UseOcelot().Wait(); +.. code-block:: csharp -In a StartUp.cs file add to a method ConfigureServices lines: + app + .UseAuthentication() + .UseOcelot() + .Wait(); + + +Add the following, at minimum, to your startup ConfigureServices method: + +.. code-block:: csharp + + services + .AddAuthentication() + .AddJwtBearer(oktaProviderKey, options => + { + options.Audience = configuration["Authentication:Okta:Audience"]; // Okta Authorization server Audience + options.Authority = configuration["Authentication:Okta:Server"]; // Okta Authorization Issuer URI URL e.g. https://{subdomain}.okta.com/oauth2/{authidentifier} + }); + services.AddOcelot(configuration); + + +NOTE: In order to get Ocelot to view the scope claim from Okta properly, you have to add the following to map the default Okta "scp" claim to "scope" + + +.. code-block:: csharp + + // Map Okta scp to scope claims instead of http://schemas.microsoft.com/identity/claims/scope to allow ocelot to read/verify them + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("scp"); + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("scp", "scope"); -services.AddAuthentication(options => - { - options.DefaultAuthenticateScheme = OktaDefaults.ApiAuthenticationScheme; - options.DefaultChallengeScheme = OktaDefaults.ApiAuthenticationScheme; - options.DefaultSignInScheme = OktaDefaults.ApiAuthenticationScheme; - }) - .AddOktaWebApi(new OktaWebApiOptions - { - OktaDomain = _cfg["Okta:OktaDomain"] - - }); -services.AddOcelot(_cfg); `Issue 446 `_ that contains some code and examples that might help with Okta integration. diff --git a/docs/features/kubernetes.rst b/docs/features/kubernetes.rst index 289bb211..9f8c8f9f 100644 --- a/docs/features/kubernetes.rst +++ b/docs/features/kubernetes.rst @@ -14,11 +14,22 @@ Then add the following to your ConfigureServices method. s.AddOcelot() .AddKubernetes(); -If you have services deployed in kubernetes you will normally use the naming service to access them. +If you have services deployed in kubernetes you will normally use the naming service to access them. Default usePodServiceAccount = True, which means that ServiceAccount using Pod to access the service of the k8s cluster needs to be ServiceAccount based on RABC authorization + +.. code-block::csharp + public static class OcelotBuilderExtensions + { + public static IOcelotBuilder AddKubernetes(this IOcelotBuilder builder, bool usePodServiceAccount = true); + } + +You can replicate a Permissive. Using RBAC role bindings. +`Permissive RBAC Permissions `_, k8s api server and token will read from pod . + +.. code-block::json +kubectl create clusterrolebinding permissive-binding --clusterrole=cluster-admin --user=admin --user=kubelet --group=system:serviceaccounts The following example shows how to set up a ReRoute that will work in kubernetes. The most important thing is the ServiceName which is made up of the -kubernetes service name. We also need to set up the ServiceDiscoveryProvider in -GlobalConfiguration. The example here shows a typical configuration. It assumes kubernetes api server is running on 192.168.0.13 and that api service is on port 443. +kubernetes service name. We also need to set up the ServiceDiscoveryProvider in GlobalConfiguration. The example here shows a typical configuration. .. code-block:: json @@ -43,19 +54,21 @@ GlobalConfiguration. The example here shows a typical configuration. It assumes } } } +Service deployment in Namespace Dev , ServiceDiscoveryProvider type is kube, you also can set pollkube ServiceDiscoveryProvider type. + Note: Host、 Port and Token are no longer in use。 You use Ocelot to poll kubernetes for latest service information rather than per request. If you want to poll kubernetes for the latest services rather than per request (default behaviour) then you need to set the following configuration. .. code-block:: json -"ServiceDiscoveryProvider": { + "ServiceDiscoveryProvider": { "Host": "192.168.0.13", - "Port": 443, - "Token": "txpc696iUhbVoudg164r93CxDTrKRVWG", - "Namespace": "dev", - "Type": "pollkube" - "PollingInterval": 100 -} + "Port": 443, + "Token": "txpc696iUhbVoudg164r93CxDTrKRVWG", + "Namespace": "dev", + "Type": "pollkube" + "PollingInterval": 100 + } The polling interval is in milliseconds and tells Ocelot how often to call kubernetes for changes in service configuration. diff --git a/docs/features/routing.rst b/docs/features/routing.rst index 9a52fb19..0f2e5efc 100644 --- a/docs/features/routing.rst +++ b/docs/features/routing.rst @@ -181,7 +181,7 @@ matched /goods/{catchAll} (because this is the first ReRoute in the list!). Dynamic Routing ^^^^^^^^^^^^^^^ -This feature was requested in `issue 340 `_. +This feature was requested in `issue 340 `_. The idea is to enable dynamic routing when using a service discovery provider so you don't have to provide the ReRoute config. See the docs :ref:`service-discovery` if this sounds interesting to you. diff --git a/docs/features/servicediscovery.rst b/docs/features/servicediscovery.rst index 55d446ab..f36f9f78 100644 --- a/docs/features/servicediscovery.rst +++ b/docs/features/servicediscovery.rst @@ -113,7 +113,7 @@ Ocelot will add this token to the Consul client that it uses to make requests an Eureka ^^^^^^ -This feature was requested as part of `Issue 262 `_ . to add support for Netflix's +This feature was requested as part of `Issue 262 `_ . to add support for Netflix's Eureka service discovery provider. The main reason for this is it is a key part of `Steeltoe `_ which is something to do with `Pivotal `_! Anyway enough of the background. @@ -158,7 +158,7 @@ is provided by the Pivotal.Discovery.Client NuGet package so big thanks to them Dynamic Routing ^^^^^^^^^^^^^^^ -This feature was requested in `issue 340 `_. The idea is to enable dynamic routing when using a service discovery provider (see that section of the docs for more info). In this mode Ocelot will use the first segment of the upstream path to lookup the downstream service with the service discovery provider. +This feature was requested in `issue 340 `_. The idea is to enable dynamic routing when using a service discovery provider (see that section of the docs for more info). In this mode Ocelot will use the first segment of the upstream path to lookup the downstream service with the service discovery provider. An example of this would be calling Ocelot with a url like https://api.mywebsite.com/product/products. Ocelot will take the first segment of the path which is product and use it as a key to look up the service in Consul. If Consul returns a service Ocelot will request it on whatever host and port comes back from Consul plus the remaining path segments in this case products thus making the downstream call http://hostfromconsul:portfromconsul/products. Ocelot will apprend any query string to the downstream url as normal. diff --git a/samples/AdministrationApi/README.md b/samples/AdministrationApi/README.md index 4477bf9b..36ed2a67 100644 --- a/samples/AdministrationApi/README.md +++ b/samples/AdministrationApi/README.md @@ -1,3 +1,4 @@ +```json { "reRoutes": [ { @@ -89,4 +90,5 @@ "useProxy": true } } -} \ No newline at end of file +} +``` diff --git a/samples/OelotKube/ApiGateway/ApiGateway.csproj b/samples/OelotKube/ApiGateway/ApiGateway.csproj index aef8c150..0dc4f632 100644 --- a/samples/OelotKube/ApiGateway/ApiGateway.csproj +++ b/samples/OelotKube/ApiGateway/ApiGateway.csproj @@ -10,11 +10,8 @@ - - - - - + + diff --git a/samples/OelotKube/ApiGateway/Dockerfile b/samples/OelotKube/ApiGateway/Dockerfile index 19bd33c2..1e3fcd11 100644 --- a/samples/OelotKube/ApiGateway/Dockerfile +++ b/samples/OelotKube/ApiGateway/Dockerfile @@ -1,12 +1,10 @@ -FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base +FROM mcr.microsoft.com/dotnet/core/aspnet:2.1-stretch-slim AS base WORKDIR /app EXPOSE 80 -FROM microsoft/dotnet:2.1-sdk AS build +FROM mcr.microsoft.com/dotnet/core/sdk:2.1-stretch AS build WORKDIR /src COPY ["ApiGateway/ApiGateway.csproj", "ApiGateway/"] -COPY ["../../src/Ocelot/Ocelot.csproj", "../../src/Ocelot/"] -COPY ["../../src/Ocelot.Provider.Kubernetes/Ocelot.Provider.Kubernetes.csproj", "../../src/Ocelot.Provider.Kubernetes/"] RUN dotnet restore "ApiGateway/ApiGateway.csproj" COPY . . WORKDIR "/src/ApiGateway" @@ -18,4 +16,4 @@ RUN dotnet publish "ApiGateway.csproj" -c Release -o /app FROM base AS final WORKDIR /app COPY --from=publish /app . -ENTRYPOINT ["dotnet", "ApiGateway.dll"] +ENTRYPOINT ["dotnet", "ApiGateway.dll"] \ No newline at end of file diff --git a/samples/OelotKube/OelotKube.sln b/samples/OelotKube/OelotKube.sln index aa57e0dd..295fe248 100644 --- a/samples/OelotKube/OelotKube.sln +++ b/samples/OelotKube/OelotKube.sln @@ -1,14 +1,10 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.28010.2048 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28803.202 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApiGateway", "ApiGateway\ApiGateway.csproj", "{E9AFBFD7-EF20-48E5-BB30-5C63C59D7C1C}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot", "..\..\src\Ocelot\Ocelot.csproj", "{E8551073-622E-45FA-AD09-038EB8AAFFBC}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Provider.Kubernetes", "..\..\src\Ocelot.Provider.Kubernetes\Ocelot.Provider.Kubernetes.csproj", "{EF973868-98A6-4864-BF66-65B5A8C123FE}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DownstreamService", "DownstreamService\DownstreamService.csproj", "{86FFAE3C-648F-4CDE-A260-37C8EBFBF4F2}" EndProject Global @@ -21,14 +17,6 @@ Global {E9AFBFD7-EF20-48E5-BB30-5C63C59D7C1C}.Debug|Any CPU.Build.0 = Debug|Any CPU {E9AFBFD7-EF20-48E5-BB30-5C63C59D7C1C}.Release|Any CPU.ActiveCfg = Release|Any CPU {E9AFBFD7-EF20-48E5-BB30-5C63C59D7C1C}.Release|Any CPU.Build.0 = Release|Any CPU - {E8551073-622E-45FA-AD09-038EB8AAFFBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E8551073-622E-45FA-AD09-038EB8AAFFBC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E8551073-622E-45FA-AD09-038EB8AAFFBC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E8551073-622E-45FA-AD09-038EB8AAFFBC}.Release|Any CPU.Build.0 = Release|Any CPU - {EF973868-98A6-4864-BF66-65B5A8C123FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EF973868-98A6-4864-BF66-65B5A8C123FE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EF973868-98A6-4864-BF66-65B5A8C123FE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EF973868-98A6-4864-BF66-65B5A8C123FE}.Release|Any CPU.Build.0 = Release|Any CPU {86FFAE3C-648F-4CDE-A260-37C8EBFBF4F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {86FFAE3C-648F-4CDE-A260-37C8EBFBF4F2}.Debug|Any CPU.Build.0 = Debug|Any CPU {86FFAE3C-648F-4CDE-A260-37C8EBFBF4F2}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/src/Ocelot.Cache.CacheManager/OcelotBuilderExtensions.cs b/src/Ocelot.Cache.CacheManager/OcelotBuilderExtensions.cs index d96ce919..e9841890 100644 --- a/src/Ocelot.Cache.CacheManager/OcelotBuilderExtensions.cs +++ b/src/Ocelot.Cache.CacheManager/OcelotBuilderExtensions.cs @@ -33,6 +33,10 @@ builder.Services.RemoveAll(typeof(IOcelotCache)); builder.Services.AddSingleton>(fileConfigCacheManagerOutputCache); builder.Services.AddSingleton>(fileConfigCacheManager); + + builder.Services.RemoveAll(typeof(ICacheKeyGenerator)); + builder.Services.AddSingleton(); + return builder; } } diff --git a/src/Ocelot.Provider.Kubernetes/KubeProvider.cs b/src/Ocelot.Provider.Kubernetes/KubeProvider.cs index 388dfb6a..5baa7e31 100644 --- a/src/Ocelot.Provider.Kubernetes/KubeProvider.cs +++ b/src/Ocelot.Provider.Kubernetes/KubeProvider.cs @@ -15,13 +15,14 @@ namespace Ocelot.Provider.Kubernetes private IOcelotLogger logger; private IKubeApiClient kubeApi; - public Kube(KubeRegistryConfiguration kubeRegistryConfiguration, IOcelotLoggerFactory factory, IKubeApiClientFactory kubeClientFactory) + public Kube(KubeRegistryConfiguration kubeRegistryConfiguration, IOcelotLoggerFactory factory, IKubeApiClient kubeApi) { this.kubeRegistryConfiguration = kubeRegistryConfiguration; this.logger = factory.CreateLogger(); - this.kubeApi = kubeClientFactory.Get(kubeRegistryConfiguration); + this.kubeApi = kubeApi; } + public async Task> Get() { var service = await kubeApi.ServicesV1() diff --git a/src/Ocelot.Provider.Kubernetes/KubeRegistryConfiguration.cs b/src/Ocelot.Provider.Kubernetes/KubeRegistryConfiguration.cs index 8e5f800e..5c35bbeb 100644 --- a/src/Ocelot.Provider.Kubernetes/KubeRegistryConfiguration.cs +++ b/src/Ocelot.Provider.Kubernetes/KubeRegistryConfiguration.cs @@ -5,16 +5,8 @@ namespace Ocelot.Provider.Kubernetes { public class KubeRegistryConfiguration { - public Uri ApiEndPoint { get; set; } - public string KubeNamespace { get; set; } public string KeyOfServiceInK8s { get; set; } - - public KubeAuthStrategy AuthStrategy { get; set; } - - public string AccessToken { get; set; } - - public bool AllowInsecure { get; set; } } } diff --git a/src/Ocelot.Provider.Kubernetes/KubernetesProviderFactory.cs b/src/Ocelot.Provider.Kubernetes/KubernetesProviderFactory.cs index b0bb35d7..c33839c6 100644 --- a/src/Ocelot.Provider.Kubernetes/KubernetesProviderFactory.cs +++ b/src/Ocelot.Provider.Kubernetes/KubernetesProviderFactory.cs @@ -16,18 +16,14 @@ namespace Ocelot.Provider.Kubernetes private static ServiceDiscovery.Providers.IServiceDiscoveryProvider GetkubeProvider(IServiceProvider provider, Configuration.ServiceProviderConfiguration config, string name, IOcelotLoggerFactory factory) { - var kubeClientFactory = provider.GetService(); + var kubeClient = provider.GetService(); var k8sRegistryConfiguration = new KubeRegistryConfiguration() { - ApiEndPoint = new Uri($"https://{config.Host}:{config.Port}"), KeyOfServiceInK8s = name, KubeNamespace = config.Namespace, - AuthStrategy = KubeAuthStrategy.BearerToken, - AccessToken = config.Token, - AllowInsecure = true // Don't validate server certificate }; - var k8sServiceDiscoveryProvider = new Kube(k8sRegistryConfiguration, factory, kubeClientFactory); + var k8sServiceDiscoveryProvider = new Kube(k8sRegistryConfiguration, factory, kubeClient); if (config.Type?.ToLower() == "pollkube") { return new PollKube(config.PollingInterval, factory, k8sServiceDiscoveryProvider); diff --git a/src/Ocelot.Provider.Kubernetes/Ocelot.Provider.Kubernetes.csproj b/src/Ocelot.Provider.Kubernetes/Ocelot.Provider.Kubernetes.csproj index dbe39207..13a73143 100644 --- a/src/Ocelot.Provider.Kubernetes/Ocelot.Provider.Kubernetes.csproj +++ b/src/Ocelot.Provider.Kubernetes/Ocelot.Provider.Kubernetes.csproj @@ -25,7 +25,13 @@ - + + + + + + + diff --git a/src/Ocelot.Provider.Kubernetes/OcelotBuilderExtensions.cs b/src/Ocelot.Provider.Kubernetes/OcelotBuilderExtensions.cs index 80772167..f979586a 100644 --- a/src/Ocelot.Provider.Kubernetes/OcelotBuilderExtensions.cs +++ b/src/Ocelot.Provider.Kubernetes/OcelotBuilderExtensions.cs @@ -1,14 +1,15 @@ -using Microsoft.Extensions.DependencyInjection; +using KubeClient; +using Microsoft.Extensions.DependencyInjection; using Ocelot.DependencyInjection; namespace Ocelot.Provider.Kubernetes { public static class OcelotBuilderExtensions { - public static IOcelotBuilder AddKubernetes(this IOcelotBuilder builder) + public static IOcelotBuilder AddKubernetes(this IOcelotBuilder builder, bool usePodServiceAccount = true) { builder.Services.AddSingleton(KubernetesProviderFactory.Get); - builder.Services.AddSingleton(); + builder.Services.AddKubeClient(usePodServiceAccount); return builder; } } diff --git a/src/Ocelot/Authorisation/ClaimsAuthoriser.cs b/src/Ocelot/Authorisation/ClaimsAuthoriser.cs index 8fd910c8..75a166f2 100644 --- a/src/Ocelot/Authorisation/ClaimsAuthoriser.cs +++ b/src/Ocelot/Authorisation/ClaimsAuthoriser.cs @@ -1,6 +1,13 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; using System.Security.Claims; +using System.Text.RegularExpressions; + +using Ocelot.DownstreamRouteFinder.UrlMatcher; +using Ocelot.Middleware; using Ocelot.Responses; +using Ocelot.Values; namespace Ocelot.Authorisation { @@ -15,8 +22,11 @@ namespace Ocelot.Authorisation _claimsParser = claimsParser; } - public Response Authorise(ClaimsPrincipal claimsPrincipal, Dictionary routeClaimsRequirement) - { + public Response Authorise( + ClaimsPrincipal claimsPrincipal, + Dictionary routeClaimsRequirement, + List urlPathPlaceholderNameAndValues + ){ foreach (var required in routeClaimsRequirement) { var values = _claimsParser.GetValuesByClaimType(claimsPrincipal.Claims, required.Key); @@ -27,12 +37,50 @@ namespace Ocelot.Authorisation } if (values.Data != null) - { - var authorised = values.Data.Contains(required.Value); - if (!authorised) + { + // dynamic claim + var match = Regex.Match(required.Value, @"^{(?.+)}$"); + if (match.Success) { - return new ErrorResponse(new ClaimValueNotAuthorisedError( - $"claim value: {string.Join(", ", values.Data)} is not the same as required value: {required.Value} for type: {required.Key}")); + var variableName = match.Captures[0].Value; + + var matchingPlaceholders = urlPathPlaceholderNameAndValues.Where(p => p.Name.Equals(variableName)).Take(2).ToArray(); + if (matchingPlaceholders.Length == 1) + { + // match + var actualValue = matchingPlaceholders[0].Value; + var authorised = values.Data.Contains(actualValue); + if (!authorised) + { + return new ErrorResponse(new ClaimValueNotAuthorisedError( + $"dynamic claim value for {variableName} of {string.Join(", ", values.Data)} is not the same as required value: {actualValue}")); + } + } + else + { + // config error + if (matchingPlaceholders.Length == 0) + { + return new ErrorResponse(new ClaimValueNotAuthorisedError( + $"config error: requires variable claim value: {variableName} placeholders does not contain that variable: {string.Join(", ", urlPathPlaceholderNameAndValues.Select(p=>p.Name))}")); + } + else + { + return new ErrorResponse(new ClaimValueNotAuthorisedError( + $"config error: requires variable claim value: {required.Value} but placeholders are ambiguous: {string.Join(", ", urlPathPlaceholderNameAndValues.Where(p=>p.Name.Equals(variableName)).Select(p => p.Value))}")); + } + } + + } + else + { + // static claim + var authorised = values.Data.Contains(required.Value); + if (!authorised) + { + return new ErrorResponse(new ClaimValueNotAuthorisedError( + $"claim value: {string.Join(", ", values.Data)} is not the same as required value: {required.Value} for type: {required.Key}")); + } } } else diff --git a/src/Ocelot/Authorisation/IClaimsAuthoriser.cs b/src/Ocelot/Authorisation/IClaimsAuthoriser.cs index 4abd799e..9f9ce3f8 100644 --- a/src/Ocelot/Authorisation/IClaimsAuthoriser.cs +++ b/src/Ocelot/Authorisation/IClaimsAuthoriser.cs @@ -1,5 +1,9 @@ using System.Security.Claims; + +using Ocelot.Configuration; +using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Responses; +using Ocelot.Values; namespace Ocelot.Authorisation { @@ -7,6 +11,10 @@ namespace Ocelot.Authorisation public interface IClaimsAuthoriser { - Response Authorise(ClaimsPrincipal claimsPrincipal, Dictionary routeClaimsRequirement); + Response Authorise( + ClaimsPrincipal claimsPrincipal, + Dictionary routeClaimsRequirement, + List urlPathPlaceholderNameAndValues + ); } } diff --git a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs index f3e4e21d..8e906d5c 100644 --- a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs +++ b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs @@ -57,8 +57,8 @@ if (IsAuthorisedRoute(context.DownstreamReRoute)) { Logger.LogInformation("route is authorised"); - - var authorised = _claimsAuthoriser.Authorise(context.HttpContext.User, context.DownstreamReRoute.RouteClaimsRequirement); + + var authorised = _claimsAuthoriser.Authorise(context.HttpContext.User, context.DownstreamReRoute.RouteClaimsRequirement, context.TemplatePlaceholderNameAndValues); if (authorised.IsError) { diff --git a/src/Ocelot/Cache/CacheKeyGenerator.cs b/src/Ocelot/Cache/CacheKeyGenerator.cs new file mode 100644 index 00000000..c33c6104 --- /dev/null +++ b/src/Ocelot/Cache/CacheKeyGenerator.cs @@ -0,0 +1,19 @@ +using System.Text; +using System.Threading.Tasks; +using Ocelot.Middleware; + +namespace Ocelot.Cache { + public class CacheKeyGenerator : ICacheKeyGenerator { + public string GenerateRequestCacheKey(DownstreamContext context) { + string hashedContent = null; + StringBuilder downStreamUrlKeyBuilder = new StringBuilder($"{context.DownstreamRequest.Method}-{context.DownstreamRequest.OriginalString}"); + if (context.DownstreamRequest.Content != null) { + string requestContentString = Task.Run(async () => await context.DownstreamRequest.Content.ReadAsStringAsync()).Result; + downStreamUrlKeyBuilder.Append(requestContentString); + } + + hashedContent = MD5Helper.GenerateMd5(downStreamUrlKeyBuilder.ToString()); + return hashedContent; + } + } +} diff --git a/src/Ocelot/Cache/ICacheKeyGenerator.cs b/src/Ocelot/Cache/ICacheKeyGenerator.cs new file mode 100644 index 00000000..5a65eb8a --- /dev/null +++ b/src/Ocelot/Cache/ICacheKeyGenerator.cs @@ -0,0 +1,7 @@ +using Ocelot.Middleware; + +namespace Ocelot.Cache { + public interface ICacheKeyGenerator { + string GenerateRequestCacheKey(DownstreamContext context); + } +} diff --git a/src/Ocelot/Cache/MD5Helper.cs b/src/Ocelot/Cache/MD5Helper.cs new file mode 100644 index 00000000..d98bb8c5 --- /dev/null +++ b/src/Ocelot/Cache/MD5Helper.cs @@ -0,0 +1,22 @@ +using System.Security.Cryptography; +using System.Text; + +namespace Ocelot.Cache { + public static class MD5Helper { + public static string GenerateMd5(byte[] contentBytes) { + MD5 md5 = MD5.Create(); + byte[] hash = md5.ComputeHash(contentBytes); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < hash.Length; i++) { + sb.Append(hash[i].ToString("X2")); + } + + return sb.ToString(); + } + + public static string GenerateMd5(string contentString) { + byte[] contentBytes = Encoding.Unicode.GetBytes(contentString); + return GenerateMd5(contentBytes); + } + } +} diff --git a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs index 5b96e79c..925e90c6 100644 --- a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs +++ b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs @@ -7,19 +7,23 @@ using Ocelot.Logging; using Ocelot.Middleware; using System.IO; + using System.Text; public class OutputCacheMiddleware : OcelotMiddleware { private readonly OcelotRequestDelegate _next; private readonly IOcelotCache _outputCache; + private readonly ICacheKeyGenerator _cacheGeneratot; public OutputCacheMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory, - IOcelotCache outputCache) + IOcelotCache outputCache, + ICacheKeyGenerator cacheGeneratot) :base(loggerFactory.CreateLogger()) { _next = next; _outputCache = outputCache; + _cacheGeneratot = cacheGeneratot; } public async Task Invoke(DownstreamContext context) @@ -31,10 +35,11 @@ } var downstreamUrlKey = $"{context.DownstreamRequest.Method}-{context.DownstreamRequest.OriginalString}"; + string downStreamRequestCacheKey = _cacheGeneratot.GenerateRequestCacheKey(context); Logger.LogDebug($"Started checking cache for {downstreamUrlKey}"); - var cached = _outputCache.Get(downstreamUrlKey, context.DownstreamReRoute.CacheOptions.Region); + var cached = _outputCache.Get(downStreamRequestCacheKey, context.DownstreamReRoute.CacheOptions.Region); if (cached != null) { @@ -61,12 +66,13 @@ cached = await CreateCachedResponse(context.DownstreamResponse); - _outputCache.Add(downstreamUrlKey, cached, TimeSpan.FromSeconds(context.DownstreamReRoute.CacheOptions.TtlSeconds), context.DownstreamReRoute.CacheOptions.Region); + _outputCache.Add(downStreamRequestCacheKey, cached, TimeSpan.FromSeconds(context.DownstreamReRoute.CacheOptions.TtlSeconds), context.DownstreamReRoute.CacheOptions.Region); Logger.LogDebug($"finished response added to cache for {downstreamUrlKey}"); } - private void SetHttpResponseMessageThisRequest(DownstreamContext context, DownstreamResponse response) + private void SetHttpResponseMessageThisRequest(DownstreamContext context, + DownstreamResponse response) { context.DownstreamResponse = response; } diff --git a/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs b/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs index 3b1588d2..728c218f 100644 --- a/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs +++ b/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs @@ -122,7 +122,7 @@ { var matchingReRoutes = reRoutes .Where(r => r.UpstreamPathTemplate == reRoute.UpstreamPathTemplate - && (r.UpstreamHost != reRoute.UpstreamHost || reRoute.UpstreamHost == null)) + && (r.UpstreamHost == reRoute.UpstreamHost || reRoute.UpstreamHost == null)) .ToList(); if (matchingReRoutes.Count == 1) diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index 9f81e11d..3fee15e7 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -107,6 +107,7 @@ namespace Ocelot.DependencyInjection 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 diff --git a/src/Ocelot/Middleware/Multiplexer/IDefinedAggregator.cs b/src/Ocelot/Middleware/Multiplexer/IDefinedAggregator.cs index b1585165..48234782 100644 --- a/src/Ocelot/Middleware/Multiplexer/IDefinedAggregator.cs +++ b/src/Ocelot/Middleware/Multiplexer/IDefinedAggregator.cs @@ -5,6 +5,6 @@ namespace Ocelot.Middleware.Multiplexer { public interface IDefinedAggregator { - Task Aggregate(List responses); + Task Aggregate(List responses); } } diff --git a/src/Ocelot/Middleware/Multiplexer/UserDefinedResponseAggregator.cs b/src/Ocelot/Middleware/Multiplexer/UserDefinedResponseAggregator.cs index aa44466d..0080b60e 100644 --- a/src/Ocelot/Middleware/Multiplexer/UserDefinedResponseAggregator.cs +++ b/src/Ocelot/Middleware/Multiplexer/UserDefinedResponseAggregator.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Ocelot.Configuration; @@ -21,7 +20,7 @@ namespace Ocelot.Middleware.Multiplexer if (!aggregator.IsError) { var aggregateResponse = await aggregator.Data - .Aggregate(downstreamResponses.Select(x => x.DownstreamResponse).ToList()); + .Aggregate(downstreamResponses); originalContext.DownstreamResponse = aggregateResponse; } diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 78d32346..a841df5a 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -1,22 +1,22 @@ namespace Ocelot.Middleware { - using System; - using System.Linq; - using System.Threading.Tasks; - using Microsoft.AspNetCore.Hosting; - using Microsoft.Extensions.Options; - using System.Diagnostics; using DependencyInjection; using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Hosting; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Options; using Ocelot.Configuration; using Ocelot.Configuration.Creator; using Ocelot.Configuration.File; using Ocelot.Configuration.Repository; using Ocelot.Configuration.Setter; - using Ocelot.Responses; using Ocelot.Logging; using Ocelot.Middleware.Pipeline; - using Microsoft.Extensions.DependencyInjection; + using Ocelot.Responses; + using System; + using System.Diagnostics; + using System.Linq; + using System.Threading.Tasks; public static class OcelotMiddlewareExtensions { @@ -42,6 +42,30 @@ return CreateOcelotPipeline(builder, pipelineConfiguration); } + public static Task UseOcelot(this IApplicationBuilder app, Action builderAction) + => UseOcelot(app, builderAction, new OcelotPipelineConfiguration()); + + public static async Task UseOcelot(this IApplicationBuilder app, Action builderAction, OcelotPipelineConfiguration configuration) + { + await CreateConfiguration(app); // initConfiguration + + ConfigureDiagnosticListener(app); + + var ocelotPipelineBuilder = new OcelotPipelineBuilder(app.ApplicationServices); + builderAction?.Invoke(ocelotPipelineBuilder, configuration ?? new OcelotPipelineConfiguration()); + + var ocelotDelegate = ocelotPipelineBuilder.Build(); + app.Properties["analysis.NextMiddlewareName"] = "TransitionToOcelotMiddleware"; + + app.Use(async (context, task) => + { + var downstreamContext = new DownstreamContext(context); + await ocelotDelegate.Invoke(downstreamContext); + }); + + return app; + } + private static IApplicationBuilder CreateOcelotPipeline(IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration) { var pipelineBuilder = new OcelotPipelineBuilder(builder.ApplicationServices); diff --git a/src/Ocelot/Middleware/Pipeline/IOcelotPipelineBuilder.cs b/src/Ocelot/Middleware/Pipeline/IOcelotPipelineBuilder.cs index 9cb0db56..a10a2369 100644 --- a/src/Ocelot/Middleware/Pipeline/IOcelotPipelineBuilder.cs +++ b/src/Ocelot/Middleware/Pipeline/IOcelotPipelineBuilder.cs @@ -9,7 +9,7 @@ namespace Ocelot.Middleware.Pipeline public interface IOcelotPipelineBuilder { IServiceProvider ApplicationServices { get; } - OcelotPipelineBuilder Use(Func middleware); + IOcelotPipelineBuilder Use(Func middleware); OcelotRequestDelegate Build(); IOcelotPipelineBuilder New(); } diff --git a/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilder.cs b/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilder.cs index 5877ab62..58f71122 100644 --- a/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilder.cs +++ b/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilder.cs @@ -27,7 +27,7 @@ namespace Ocelot.Middleware.Pipeline public IServiceProvider ApplicationServices { get; } - public OcelotPipelineBuilder Use(Func middleware) + public IOcelotPipelineBuilder Use(Func middleware) { _middlewares.Add(middleware); return this; diff --git a/src/Ocelot/Request/Middleware/DownstreamRequest.cs b/src/Ocelot/Request/Middleware/DownstreamRequest.cs index 665039d0..49d27efe 100644 --- a/src/Ocelot/Request/Middleware/DownstreamRequest.cs +++ b/src/Ocelot/Request/Middleware/DownstreamRequest.cs @@ -19,6 +19,7 @@ namespace Ocelot.Request.Middleware Headers = _request.Headers; AbsolutePath = _request.RequestUri.AbsolutePath; Query = _request.RequestUri.Query; + Content = _request.Content; } public HttpRequestHeaders Headers { get; } @@ -37,6 +38,8 @@ namespace Ocelot.Request.Middleware public string Query { get; set; } + public HttpContent Content { get; set; } + public HttpRequestMessage ToHttpRequestMessage() { var uriBuilder = new UriBuilder diff --git a/test/Ocelot.AcceptanceTests/AggregateTests.cs b/test/Ocelot.AcceptanceTests/AggregateTests.cs index e3d99854..6de4d8c7 100644 --- a/test/Ocelot.AcceptanceTests/AggregateTests.cs +++ b/test/Ocelot.AcceptanceTests/AggregateTests.cs @@ -646,14 +646,14 @@ namespace Ocelot.AcceptanceTests _dep = dep; } - public async Task Aggregate(List responses) + public async Task Aggregate(List responses) { - var one = await responses[0].Content.ReadAsStringAsync(); - var two = await responses[1].Content.ReadAsStringAsync(); + var one = await responses[0].DownstreamResponse.Content.ReadAsStringAsync(); + var two = await responses[1].DownstreamResponse.Content.ReadAsStringAsync(); var merge = $"{one}, {two}"; merge = merge.Replace("Hello", "Bye").Replace("{", "").Replace("}", ""); - var headers = responses.SelectMany(x => x.Headers).ToList(); + var headers = responses.SelectMany(x => x.DownstreamResponse.Headers).ToList(); return new DownstreamResponse(new StringContent(merge), HttpStatusCode.OK, headers, "some reason"); } } diff --git a/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs b/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs index a2c2fadd..eaa2441f 100644 --- a/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs @@ -69,15 +69,21 @@ namespace Ocelot.UnitTests.Authorization private void GivenTheAuthServiceReturns(Response expected) { _authService - .Setup(x => x.Authorise(It.IsAny(), It.IsAny>())) + .Setup(x => x.Authorise( + It.IsAny(), + It.IsAny>(), + It.IsAny>())) .Returns(expected); } private void ThenTheAuthServiceIsCalledCorrectly() { _authService - .Verify(x => x.Authorise(It.IsAny(), - It.IsAny>()), Times.Once); + .Verify(x => x.Authorise( + It.IsAny(), + It.IsAny>(), + It.IsAny>()) + , Times.Once); } } } diff --git a/test/Ocelot.UnitTests/Authorization/ClaimsAuthoriserTests.cs b/test/Ocelot.UnitTests/Authorization/ClaimsAuthoriserTests.cs index e2990864..11fb341d 100644 --- a/test/Ocelot.UnitTests/Authorization/ClaimsAuthoriserTests.cs +++ b/test/Ocelot.UnitTests/Authorization/ClaimsAuthoriserTests.cs @@ -1,7 +1,11 @@ using System.Collections.Generic; using System.Security.Claims; using Ocelot.Authorisation; +using Ocelot.Configuration; +using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Responses; +using Ocelot.Values; + using Shouldly; using TestStack.BDDfy; using Xunit; @@ -15,6 +19,7 @@ namespace Ocelot.UnitTests.Authorization private readonly ClaimsAuthoriser _claimsAuthoriser; private ClaimsPrincipal _claimsPrincipal; private Dictionary _requirement; + private List _urlPathPlaceholderNameAndValues; private Response _result; public ClaimsAuthoriserTests() @@ -38,6 +43,46 @@ namespace Ocelot.UnitTests.Authorization .BDDfy(); } + [Fact] + public void should_authorize_dynamic_user() + { + this.Given(x => x.GivenAClaimsPrincipal(new ClaimsPrincipal(new ClaimsIdentity(new List + { + new Claim("userid", "14"), + })))) + .And(x => x.GivenARouteClaimsRequirement(new Dictionary + { + {"userid", "{userId}"} + })) + .And(x => x.GivenAPlaceHolderNameAndValueList(new List + { + new PlaceholderNameAndValue("{userId}", "14") + })) + .When(x => x.WhenICallTheAuthoriser()) + .Then(x => x.ThenTheUserIsAuthorised()) + .BDDfy(); + } + + [Fact] + public void should_not_authorize_dynamic_user() + { + this.Given(x => x.GivenAClaimsPrincipal(new ClaimsPrincipal(new ClaimsIdentity(new List + { + new Claim("userid", "15"), + })))) + .And(x => x.GivenARouteClaimsRequirement(new Dictionary + { + {"userid", "{userId}"} + })) + .And(x => x.GivenAPlaceHolderNameAndValueList(new List + { + new PlaceholderNameAndValue("{userId}", "14") + })) + .When(x => x.WhenICallTheAuthoriser()) + .Then(x => x.ThenTheUserIsntAuthorised()) + .BDDfy(); + } + [Fact] public void should_authorise_user_multiple_claims_of_same_type() { @@ -78,9 +123,14 @@ namespace Ocelot.UnitTests.Authorization _requirement = requirement; } + private void GivenAPlaceHolderNameAndValueList(List urlPathPlaceholderNameAndValues) + { + _urlPathPlaceholderNameAndValues = urlPathPlaceholderNameAndValues; + } + private void WhenICallTheAuthoriser() { - _result = _claimsAuthoriser.Authorise(_claimsPrincipal, _requirement); + _result = _claimsAuthoriser.Authorise(_claimsPrincipal, _requirement, _urlPathPlaceholderNameAndValues); } private void ThenTheUserIsAuthorised() diff --git a/test/Ocelot.UnitTests/Cache/CacheKeyGeneratorTests.cs b/test/Ocelot.UnitTests/Cache/CacheKeyGeneratorTests.cs new file mode 100644 index 00000000..362fcc9a --- /dev/null +++ b/test/Ocelot.UnitTests/Cache/CacheKeyGeneratorTests.cs @@ -0,0 +1,34 @@ +using System.Net.Http; +using Microsoft.AspNetCore.Http; +using Ocelot.Cache; +using Ocelot.Middleware; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Cache { + public class CacheKeyGeneratorTests { + private readonly ICacheKeyGenerator _cacheKeyGenerator; + private readonly DownstreamContext _downstreamContext; + + public CacheKeyGeneratorTests() { + _cacheKeyGenerator = new CacheKeyGenerator(); + _cacheKeyGenerator = new CacheKeyGenerator(); + _downstreamContext = new DownstreamContext(new DefaultHttpContext()) { + DownstreamRequest = new Ocelot.Request.Middleware.DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "https://some.url/blah?abcd=123")) + }; + } + + [Fact] + public void should_generate_cache_key_from_context() { + this.Given(x => x.GivenCacheKeyFromContext(_downstreamContext)) + .BDDfy(); + } + + private void GivenCacheKeyFromContext(DownstreamContext context) { + string generatedCacheKey = _cacheKeyGenerator.GenerateRequestCacheKey(context); + string cachekey = MD5Helper.GenerateMd5("GET-https://some.url/blah?abcd=123"); + generatedCacheKey.ShouldBe(cachekey); + } + } +} diff --git a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs index 6f32739b..62d972f9 100644 --- a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs @@ -27,14 +27,17 @@ private OutputCacheMiddleware _middleware; private readonly DownstreamContext _downstreamContext; private readonly OcelotRequestDelegate _next; + private readonly ICacheKeyGenerator _cacheKeyGenerator; private CachedResponse _response; + public OutputCacheMiddlewareTests() { _cache = new Mock>(); _downstreamContext = new DownstreamContext(new DefaultHttpContext()); _loggerFactory = new Mock(); _logger = new Mock(); + _cacheKeyGenerator = new CacheKeyGenerator(); _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); _next = context => Task.CompletedTask; _downstreamContext.DownstreamRequest = new Ocelot.Request.Middleware.DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "https://some.url/blah?abcd=123")); @@ -89,7 +92,7 @@ private void WhenICallTheMiddleware() { - _middleware = new OutputCacheMiddleware(_next, _loggerFactory.Object, _cache.Object); + _middleware = new OutputCacheMiddleware(_next, _loggerFactory.Object, _cache.Object, _cacheKeyGenerator); _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); } diff --git a/test/Ocelot.UnitTests/CacheManager/OutputCacheMiddlewareRealCacheTests.cs b/test/Ocelot.UnitTests/CacheManager/OutputCacheMiddlewareRealCacheTests.cs index 424c8efd..853354ef 100644 --- a/test/Ocelot.UnitTests/CacheManager/OutputCacheMiddlewareRealCacheTests.cs +++ b/test/Ocelot.UnitTests/CacheManager/OutputCacheMiddlewareRealCacheTests.cs @@ -23,6 +23,7 @@ public class OutputCacheMiddlewareRealCacheTests { private readonly IOcelotCache _cacheManager; + private readonly ICacheKeyGenerator _cacheKeyGenerator; private readonly OutputCacheMiddleware _middleware; private readonly DownstreamContext _downstreamContext; private OcelotRequestDelegate _next; @@ -32,17 +33,18 @@ public OutputCacheMiddlewareRealCacheTests() { _loggerFactory = new Mock(); - _logger = new Mock(); + _logger = new Mock(); _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); var cacheManagerOutputCache = CacheFactory.Build("OcelotOutputCache", x => { x.WithDictionaryHandle(); }); _cacheManager = new OcelotCacheManagerCache(cacheManagerOutputCache); + _cacheKeyGenerator = new CacheKeyGenerator(); _downstreamContext = new DownstreamContext(new DefaultHttpContext()); _downstreamContext.DownstreamRequest = new Ocelot.Request.Middleware.DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "https://some.url/blah?abcd=123")); _next = context => Task.CompletedTask; - _middleware = new OutputCacheMiddleware(_next, _loggerFactory.Object, _cacheManager); + _middleware = new OutputCacheMiddleware(_next, _loggerFactory.Object, _cacheManager, _cacheKeyGenerator); } [Fact] @@ -69,7 +71,8 @@ private void ThenTheContentTypeHeaderIsCached() { - var result = _cacheManager.Get("GET-https://some.url/blah?abcd=123", "kanken"); + string cacheKey = MD5Helper.GenerateMd5("GET-https://some.url/blah?abcd=123"); + var result = _cacheManager.Get(cacheKey, "kanken"); var header = result.ContentHeaders["Content-Type"]; header.First().ShouldBe("application/json"); } diff --git a/test/Ocelot.UnitTests/Configuration/Validation/FileConfigurationFluentValidatorTests.cs b/test/Ocelot.UnitTests/Configuration/Validation/FileConfigurationFluentValidatorTests.cs index c7ffbb05..a77d7c60 100644 --- a/test/Ocelot.UnitTests/Configuration/Validation/FileConfigurationFluentValidatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/Validation/FileConfigurationFluentValidatorTests.cs @@ -1023,7 +1023,7 @@ Host = "bb.co.uk" } }, - UpstreamHost = "host1" + UpstreamHost = "host2" } } })) diff --git a/test/Ocelot.UnitTests/Kubernetes/KubeServiceDiscoveryProviderTests.cs b/test/Ocelot.UnitTests/Kubernetes/KubeServiceDiscoveryProviderTests.cs index ccf44fcd..d2abded6 100644 --- a/test/Ocelot.UnitTests/Kubernetes/KubeServiceDiscoveryProviderTests.cs +++ b/test/Ocelot.UnitTests/Kubernetes/KubeServiceDiscoveryProviderTests.cs @@ -1,4 +1,5 @@ -using KubeClient.Models; +using KubeClient; +using KubeClient.Models; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; @@ -31,7 +32,7 @@ namespace Ocelot.UnitTests.Kubernetes private readonly Mock _factory; private readonly Mock _logger; private string _receivedToken; - private readonly IKubeApiClientFactory _clientFactory; + private readonly IKubeApiClient _clientFactory; public KubeServiceDiscoveryProviderTests() { @@ -42,15 +43,20 @@ namespace Ocelot.UnitTests.Kubernetes _fakekubeServiceDiscoveryUrl = $"http://{_kubeHost}:{_port}"; _serviceEntries = new ServiceV1(); _factory = new Mock(); - _clientFactory = new KubeApiClientFactory(); + + var option = new KubeClientOptions + { + ApiEndPoint = new Uri(_fakekubeServiceDiscoveryUrl), + AccessToken = "txpc696iUhbVoudg164r93CxDTrKRVWG", + AuthStrategy = KubeClient.KubeAuthStrategy.BearerToken, + AllowInsecure = true + }; + + _clientFactory = KubeApiClient.Create(option); _logger = new Mock(); _factory.Setup(x => x.CreateLogger()).Returns(_logger.Object); var config = new KubeRegistryConfiguration() { - ApiEndPoint = new Uri(_fakekubeServiceDiscoveryUrl), - AccessToken = "txpc696iUhbVoudg164r93CxDTrKRVWG", - AllowInsecure = true, - AuthStrategy = KubeClient.KubeAuthStrategy.BearerToken, KeyOfServiceInK8s = _serviceName, KubeNamespace = _namespaces }; diff --git a/test/Ocelot.UnitTests/Middleware/UserDefinedResponseAggregatorTests.cs b/test/Ocelot.UnitTests/Middleware/UserDefinedResponseAggregatorTests.cs index daef2a2c..4ebb592c 100644 --- a/test/Ocelot.UnitTests/Middleware/UserDefinedResponseAggregatorTests.cs +++ b/test/Ocelot.UnitTests/Middleware/UserDefinedResponseAggregatorTests.cs @@ -140,12 +140,12 @@ namespace Ocelot.UnitTests.Middleware public class TestDefinedAggregator : IDefinedAggregator { - public async Task Aggregate(List responses) + public async Task Aggregate(List responses) { - var tom = await responses[0].Content.ReadAsStringAsync(); - var laura = await responses[1].Content.ReadAsStringAsync(); + var tom = await responses[0].DownstreamResponse.Content.ReadAsStringAsync(); + var laura = await responses[1].DownstreamResponse.Content.ReadAsStringAsync(); var content = $"{tom}, {laura}"; - var headers = responses.SelectMany(x => x.Headers).ToList(); + var headers = responses.SelectMany(x => x.DownstreamResponse.Headers).ToList(); return new DownstreamResponse(new StringContent(content), HttpStatusCode.OK, headers, "some reason"); } } diff --git a/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj b/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj index ae11a695..f4222635 100644 --- a/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj +++ b/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj @@ -19,6 +19,10 @@ True + + + + From 920d8332dfe98b9461556827ec20a7b1bdc6dd87 Mon Sep 17 00:00:00 2001 From: Thiago Loureiro Date: Sat, 1 Jun 2019 19:57:54 +0800 Subject: [PATCH 4/7] Re-run build --- test/Ocelot.Benchmarks/AllTheThingsBenchmarks.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Ocelot.Benchmarks/AllTheThingsBenchmarks.cs b/test/Ocelot.Benchmarks/AllTheThingsBenchmarks.cs index c6b12356..38cb3a06 100644 --- a/test/Ocelot.Benchmarks/AllTheThingsBenchmarks.cs +++ b/test/Ocelot.Benchmarks/AllTheThingsBenchmarks.cs @@ -74,7 +74,7 @@ namespace Ocelot.Benchmarks response.EnsureSuccessStatusCode(); } - /* * Summary* + /* * Summary* BenchmarkDotNet = v0.10.13, OS = macOS 10.12.6 (16G1212) [Darwin 16.7.0] Intel Core i5-4278U CPU 2.60GHz(Haswell), 1 CPU, 4 logical cores and 2 physical cores .NET Core SDK = 2.1.4 From 903b380a5b93d36039989f2d5e6b3c467f249ff5 Mon Sep 17 00:00:00 2001 From: geffzhang Date: Mon, 28 Oct 2019 15:24:30 +0800 Subject: [PATCH 5/7] update .net core 3.0 RTM (#1025) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: update to asp.net core 3.0 preview 9 * fix : AspDotNetLogger unittest * feat: update generic host and useMvc 1、Using 'UseMvc' to configure MVC is not supported while using Endpoint Routing https://github.com/aspnet/AspNetCore/issues/9542 2、 use IHost and IHostBuilder * feat : update .net core 3.0 rc1 * eureka extension * fixed logger formatter error * fixed synchronous operations are disallowed of ReadToEnd method * fix log tests * Flush method of FakeStream should do nothing * Update ContentTests.cs * Fixed ws tests * feat: delelte comment code * feat: update .net core 3.0 RTM * Update OcelotBuilderTests.cs * Update .travis.yml mono 6.0.0 and dotnet 3.0.100 * Update Ocelot.IntegrationTests.csproj update Microsoft.Data.SQLite 3.0.0 * Update .travis.yml * feat: remove FrameworkReference 1、 remove FrameworkReference 2、 update package * add appveyor configuration to use version of VS2019 with dotnet core 3 sdk support * update obsoleted SetCollectionValidator method * Swap out OpenCover for Coverlet * Bump Cake to 0.35.0 * Downgrade coveralls.net to 0.7.0 Fix disposing of PollConsul instance * Remove environment specific path separator * Do not return ReportGenerator on Mac/Linux * Remove direct dependency on IInternalConfiguration * Fix ordering of variable assignment * Fix broken tests * Fix acceptance tests for Consul --- .travis.yml | 6 +- appveyor.yml | 1 + build.cake | 45 +-- .../Ocelot.Administration.csproj | 13 +- .../OcelotBuilderExtensions.cs | 30 +- .../Ocelot.Cache.CacheManager.csproj | 15 +- .../ConsulFileConfigurationRepository.cs | 22 +- .../Ocelot.Provider.Consul.csproj | 9 +- .../PollingConsulServiceDiscoveryProvider.cs | 11 +- .../Ocelot.Provider.Eureka.csproj | 11 +- .../OcelotBuilderExtensions.cs | 4 +- .../Ocelot.Provider.Kubernetes.csproj | 12 +- .../Ocelot.Provider.Polly.csproj | 11 +- .../Ocelot.Provider.Rafty.csproj | 13 +- .../Ocelot.Tracing.Butterfly.csproj | 7 +- .../DiskFileConfigurationRepository.cs | 2 +- .../FileConfigurationFluentValidator.cs | 4 +- .../Validator/ReRouteFluentValidator.cs | 4 +- .../ConfigurationBuilderExtensions.cs | 4 +- .../DependencyInjection/OcelotBuilder.cs | 2 +- .../ServiceCollectionExtensions.cs | 6 +- src/Ocelot/Logging/AspDotNetLogger.cs | 64 +++- .../Middleware/OcelotMiddlewareExtensions.cs | 2 +- src/Ocelot/Ocelot.csproj | 20 +- .../ConfigurationInConsulTests.cs | 49 +-- .../ConsulConfigurationInConsulTests.cs | 4 +- .../ConsulWebSocketTests.cs | 16 +- test/Ocelot.AcceptanceTests/ContentTests.cs | 6 +- test/Ocelot.AcceptanceTests/GzipTests.cs | 4 +- .../Ocelot.AcceptanceTests.csproj | 39 ++- test/Ocelot.AcceptanceTests/Steps.cs | 6 +- test/Ocelot.AcceptanceTests/WebSocketTests.cs | 20 +- .../Ocelot.Benchmarks.csproj | 8 +- .../AdministrationTests.cs | 322 ++++++++++-------- .../CacheManagerTests.cs | 60 ++-- .../Ocelot.IntegrationTests.csproj | 35 +- .../ThreadSafeHeadersTests.cs | 6 +- .../Ocelot.ManualTest.csproj | 22 +- test/Ocelot.ManualTest/Program.cs | 12 +- .../OcelotAdministrationBuilderTests.cs | 15 +- .../OcelotBuilderExtensionsTests.cs | 16 +- .../DiskFileConfigurationRepositoryTests.cs | 4 +- .../ConsulFileConfigurationRepositoryTests.cs | 30 +- .../Consul/OcelotBuilderExtensionsTests.cs | 16 +- ...lingConsulServiceDiscoveryProviderTests.cs | 34 +- .../Consul/ProviderFactoryTests.cs | 4 +- .../ConfigurationBuilderExtensionsTests.cs | 4 +- .../DependencyInjection/OcelotBuilderTests.cs | 17 +- ...ekaMiddlewareConfigurationProviderTests.cs | 2 +- .../OcelotBuilderExtensionsTests.cs | 15 +- .../Logging/AspDotNetLoggerTests.cs | 16 +- .../Middleware/OcelotPiplineBuilderTests.cs | 16 +- test/Ocelot.UnitTests/Ocelot.UnitTests.csproj | 50 +-- ...lotAdministrationBuilderExtensionsTests.cs | 15 +- .../ClientRateLimitMiddlewareTests.cs | 7 +- .../Request/Mapper/RequestMapperTests.cs | 6 +- test/Ocelot.UnitTests/UnitTests.runsettings | 23 ++ tools/packages.config | 2 +- 58 files changed, 711 insertions(+), 508 deletions(-) create mode 100644 appveyor.yml create mode 100644 test/Ocelot.UnitTests/UnitTests.runsettings diff --git a/.travis.yml b/.travis.yml index bd3ea077..813fa571 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,12 +8,12 @@ sudo: required dist: bionic # OS X 10.12 -osx_image: xcode9.2 +osx_image: xcode9.4 mono: - - 5.10.0 + - 6.0.0 -dotnet: 2.2.105 +dotnet: 3.0.100 before_install: - git fetch --unshallow # Travis always does a shallow clone, but GitVersion needs the full history including branches and tags diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 00000000..6e79d909 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1 @@ +image: Visual Studio 2019 \ No newline at end of file diff --git a/build.cake b/build.cake index aabc6a0f..5eb3d77c 100644 --- a/build.cake +++ b/build.cake @@ -2,10 +2,9 @@ #tool "nuget:?package=GitReleaseNotes" #addin nuget:?package=Cake.Json #addin nuget:?package=Newtonsoft.Json -#tool "nuget:?package=OpenCover" #tool "nuget:?package=ReportGenerator" #tool "nuget:?package=coveralls.net&version=0.7.0" -#addin Cake.Coveralls&version=0.7.0 +#addin Cake.Coveralls&version=0.10.1 // compile var compileConfig = Argument("configuration", "Release"); @@ -119,27 +118,20 @@ Task("RunUnitTests") .IsDependentOn("Compile") .Does(() => { + var testSettings = new DotNetCoreTestSettings + { + Configuration = compileConfig, + ResultsDirectory = artifactsForUnitTestsDir, + ArgumentCustomization = args => args + .Append("--settings test/Ocelot.UnitTests/UnitTests.runsettings") + }; + + EnsureDirectoryExists(artifactsForUnitTestsDir); + DotNetCoreTest(unitTestAssemblies, testSettings); + 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]*") - ); - + var coverageSummaryFile = GetSubDirectories(artifactsForUnitTestsDir).First().CombineWithFilePath(File("coverage.opencover.xml")); ReportGenerator(coverageSummaryFile, artifactsForUnitTestsDir); if (AppVeyor.IsRunningOnAppVeyor) @@ -171,17 +163,6 @@ Task("RunUnitTests") 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); } }); diff --git a/src/Ocelot.Administration/Ocelot.Administration.csproj b/src/Ocelot.Administration/Ocelot.Administration.csproj index a4aabbd8..abf0f33f 100644 --- a/src/Ocelot.Administration/Ocelot.Administration.csproj +++ b/src/Ocelot.Administration/Ocelot.Administration.csproj @@ -1,8 +1,6 @@  - netstandard2.0 - 2.0.0 - 2.0.0 + netcoreapp3.0 true Provides Ocelot extensions to use the administration API and IdentityService dependencies that come with it Ocelot.Administration @@ -29,10 +27,13 @@ - + all - - + + + + + diff --git a/src/Ocelot.Administration/OcelotBuilderExtensions.cs b/src/Ocelot.Administration/OcelotBuilderExtensions.cs index 207b3bcf..da25beaa 100644 --- a/src/Ocelot.Administration/OcelotBuilderExtensions.cs +++ b/src/Ocelot.Administration/OcelotBuilderExtensions.cs @@ -1,18 +1,18 @@ -namespace Ocelot.Administration -{ - using DependencyInjection; - using IdentityServer4.AccessTokenValidation; - using IdentityServer4.Models; - using Microsoft.AspNetCore.Builder; - using Microsoft.Extensions.Configuration; - using Microsoft.Extensions.DependencyInjection; - using Microsoft.Extensions.DependencyInjection.Extensions; - using Ocelot.Middleware; - using System; - using System.Collections.Generic; - using System.IdentityModel.Tokens.Jwt; - using System.Security.Cryptography.X509Certificates; +using Ocelot.DependencyInjection; +using IdentityServer4.AccessTokenValidation; +using IdentityServer4.Models; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Ocelot.Middleware; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Cryptography.X509Certificates; +namespace Ocelot.Administration +{ public static class OcelotBuilderExtensions { public static IOcelotAdministrationBuilder AddAdministration(this IOcelotBuilder builder, string path, string secret) @@ -86,7 +86,7 @@ else { //todo - refactor so calls method? - var cert = new X509Certificate2(identityServerConfiguration.CredentialsSigningCertificateLocation, identityServerConfiguration.CredentialsSigningCertificatePassword); + var cert = new X509Certificate2(identityServerConfiguration.CredentialsSigningCertificateLocation, identityServerConfiguration.CredentialsSigningCertificatePassword, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable); identityServerBuilder.AddSigningCredential(cert); } } diff --git a/src/Ocelot.Cache.CacheManager/Ocelot.Cache.CacheManager.csproj b/src/Ocelot.Cache.CacheManager/Ocelot.Cache.CacheManager.csproj index 7941dcab..dab96133 100644 --- a/src/Ocelot.Cache.CacheManager/Ocelot.Cache.CacheManager.csproj +++ b/src/Ocelot.Cache.CacheManager/Ocelot.Cache.CacheManager.csproj @@ -1,8 +1,6 @@  - netstandard2.0 - 2.0.0 - 2.0.0 + netcoreapp3.0 true Provides Ocelot extensions to use CacheManager.Net Ocelot.Cache.CacheManager @@ -28,11 +26,14 @@ - + all - - - + + + + + + diff --git a/src/Ocelot.Provider.Consul/ConsulFileConfigurationRepository.cs b/src/Ocelot.Provider.Consul/ConsulFileConfigurationRepository.cs index 7d7f4d44..1044b7b6 100644 --- a/src/Ocelot.Provider.Consul/ConsulFileConfigurationRepository.cs +++ b/src/Ocelot.Provider.Consul/ConsulFileConfigurationRepository.cs @@ -4,6 +4,7 @@ using Configuration.Repository; using global::Consul; using Logging; + using Microsoft.Extensions.Options; using Newtonsoft.Json; using Responses; using System; @@ -18,29 +19,20 @@ private readonly IOcelotLogger _logger; public ConsulFileConfigurationRepository( + IOptions fileConfiguration, Cache.IOcelotCache cache, - IInternalConfigurationRepository repo, IConsulClientFactory factory, IOcelotLoggerFactory loggerFactory) { _logger = loggerFactory.CreateLogger(); _cache = cache; - var internalConfig = repo.Get(); + var serviceDiscoveryProvider = fileConfiguration.Value.GlobalConfiguration.ServiceDiscoveryProvider; + _configurationKey = string.IsNullOrWhiteSpace(serviceDiscoveryProvider.ConfigurationKey) ? "InternalConfiguration" : + serviceDiscoveryProvider.ConfigurationKey; - _configurationKey = "InternalConfiguration"; - - string token = null; - - if (!internalConfig.IsError) - { - token = internalConfig.Data.ServiceProviderConfiguration.Token; - _configurationKey = !string.IsNullOrEmpty(internalConfig.Data.ServiceProviderConfiguration.ConfigurationKey) ? - internalConfig.Data.ServiceProviderConfiguration.ConfigurationKey : _configurationKey; - } - - var config = new ConsulRegistryConfiguration(internalConfig.Data.ServiceProviderConfiguration.Host, - internalConfig.Data.ServiceProviderConfiguration.Port, _configurationKey, token); + var config = new ConsulRegistryConfiguration(serviceDiscoveryProvider.Host, + serviceDiscoveryProvider.Port, _configurationKey, serviceDiscoveryProvider.Token); _consul = factory.Get(config); } diff --git a/src/Ocelot.Provider.Consul/Ocelot.Provider.Consul.csproj b/src/Ocelot.Provider.Consul/Ocelot.Provider.Consul.csproj index 2df87ad4..61df395b 100644 --- a/src/Ocelot.Provider.Consul/Ocelot.Provider.Consul.csproj +++ b/src/Ocelot.Provider.Consul/Ocelot.Provider.Consul.csproj @@ -1,8 +1,6 @@  - netstandard2.0 - 2.0.0 - 2.0.0 + netcoreapp3.0 true Provides Ocelot extensions to use Consul Ocelot.Provider.Consul @@ -30,8 +28,11 @@ - + all + + + diff --git a/src/Ocelot.Provider.Consul/PollingConsulServiceDiscoveryProvider.cs b/src/Ocelot.Provider.Consul/PollingConsulServiceDiscoveryProvider.cs index 15abc777..5bace802 100644 --- a/src/Ocelot.Provider.Consul/PollingConsulServiceDiscoveryProvider.cs +++ b/src/Ocelot.Provider.Consul/PollingConsulServiceDiscoveryProvider.cs @@ -2,16 +2,17 @@ { using Logging; using ServiceDiscovery.Providers; + using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Values; - public class PollConsul : IServiceDiscoveryProvider + public sealed class PollConsul : IServiceDiscoveryProvider, IDisposable { private readonly IOcelotLogger _logger; private readonly IServiceDiscoveryProvider _consulServiceDiscoveryProvider; - private readonly Timer _timer; + private Timer _timer; private bool _polling; private List _services; @@ -34,6 +35,12 @@ }, null, pollingInterval, pollingInterval); } + public void Dispose() + { + _timer?.Dispose(); + _timer = null; + } + public Task> Get() { return Task.FromResult(_services); diff --git a/src/Ocelot.Provider.Eureka/Ocelot.Provider.Eureka.csproj b/src/Ocelot.Provider.Eureka/Ocelot.Provider.Eureka.csproj index d25efc5a..2254cc2a 100644 --- a/src/Ocelot.Provider.Eureka/Ocelot.Provider.Eureka.csproj +++ b/src/Ocelot.Provider.Eureka/Ocelot.Provider.Eureka.csproj @@ -1,8 +1,6 @@  - netstandard2.0 - 2.0.0 - 2.0.0 + netcoreapp3.0 true Provides Ocelot extensions to use Eureka Ocelot.Provider.Eureka @@ -29,9 +27,12 @@ - - + + all + + + diff --git a/src/Ocelot.Provider.Eureka/OcelotBuilderExtensions.cs b/src/Ocelot.Provider.Eureka/OcelotBuilderExtensions.cs index 4a246e4f..e141d194 100644 --- a/src/Ocelot.Provider.Eureka/OcelotBuilderExtensions.cs +++ b/src/Ocelot.Provider.Eureka/OcelotBuilderExtensions.cs @@ -12,9 +12,7 @@ { public static IOcelotBuilder AddEureka(this IOcelotBuilder builder) { - var service = builder.Services.First(x => x.ServiceType == typeof(IConfiguration)); - var configuration = (IConfiguration)service.ImplementationInstance; - builder.Services.AddDiscoveryClient(configuration); + builder.Services.AddDiscoveryClient(builder.Configuration); builder.Services.AddSingleton(EurekaProviderFactory.Get); builder.Services.AddSingleton(EurekaMiddlewareConfigurationProvider.Get); return builder; diff --git a/src/Ocelot.Provider.Kubernetes/Ocelot.Provider.Kubernetes.csproj b/src/Ocelot.Provider.Kubernetes/Ocelot.Provider.Kubernetes.csproj index 436585da..6dba88d1 100644 --- a/src/Ocelot.Provider.Kubernetes/Ocelot.Provider.Kubernetes.csproj +++ b/src/Ocelot.Provider.Kubernetes/Ocelot.Provider.Kubernetes.csproj @@ -1,9 +1,7 @@  - netstandard2.0 - 2.0.0 - 2.0.0 + netcoreapp3.0 true Ocelot Provides Ocelot extensions to use kubernetes @@ -30,12 +28,16 @@ - - + + + + + + diff --git a/src/Ocelot.Provider.Polly/Ocelot.Provider.Polly.csproj b/src/Ocelot.Provider.Polly/Ocelot.Provider.Polly.csproj index fa15a189..36143432 100644 --- a/src/Ocelot.Provider.Polly/Ocelot.Provider.Polly.csproj +++ b/src/Ocelot.Provider.Polly/Ocelot.Provider.Polly.csproj @@ -1,8 +1,6 @@  - netstandard2.0 - 2.0.0 - 2.0.0 + netcoreapp3.0 true Provides Ocelot extensions to use Polly.NET Ocelot.Provider.Polly @@ -29,9 +27,12 @@ - + all - + + + + diff --git a/src/Ocelot.Provider.Rafty/Ocelot.Provider.Rafty.csproj b/src/Ocelot.Provider.Rafty/Ocelot.Provider.Rafty.csproj index f7e0996c..e6699562 100644 --- a/src/Ocelot.Provider.Rafty/Ocelot.Provider.Rafty.csproj +++ b/src/Ocelot.Provider.Rafty/Ocelot.Provider.Rafty.csproj @@ -1,8 +1,6 @@ - + - netstandard2.0 - 2.0.0 - 2.0.0 + netcoreapp3.0 true Provides Ocelot extensions to use Rafty Ocelot.Provider.Rafty @@ -30,10 +28,13 @@ - + - + all + + + diff --git a/src/Ocelot.Tracing.Butterfly/Ocelot.Tracing.Butterfly.csproj b/src/Ocelot.Tracing.Butterfly/Ocelot.Tracing.Butterfly.csproj index c161f183..6f2e8141 100644 --- a/src/Ocelot.Tracing.Butterfly/Ocelot.Tracing.Butterfly.csproj +++ b/src/Ocelot.Tracing.Butterfly/Ocelot.Tracing.Butterfly.csproj @@ -1,9 +1,7 @@  - netstandard2.0 - 2.0.0 - 2.0.0 + netcoreapp3.0 true This package provides methods to integrate Butterfly tracing with Ocelot. Ocelot.Tracing.Butterfly @@ -33,5 +31,8 @@ + + + diff --git a/src/Ocelot/Configuration/Repository/DiskFileConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/DiskFileConfigurationRepository.cs index 8df82935..770d7b7a 100644 --- a/src/Ocelot/Configuration/Repository/DiskFileConfigurationRepository.cs +++ b/src/Ocelot/Configuration/Repository/DiskFileConfigurationRepository.cs @@ -14,7 +14,7 @@ namespace Ocelot.Configuration.Repository private static readonly object _lock = new object(); private const string ConfigurationFileName = "ocelot"; - public DiskFileConfigurationRepository(IHostingEnvironment hostingEnvironment) + public DiskFileConfigurationRepository(IWebHostEnvironment hostingEnvironment) { _environmentFilePath = $"{AppContext.BaseDirectory}{ConfigurationFileName}{(string.IsNullOrEmpty(hostingEnvironment.EnvironmentName) ? string.Empty : ".")}{hostingEnvironment.EnvironmentName}.json"; diff --git a/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs b/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs index 49e92355..add4fe0b 100644 --- a/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs +++ b/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs @@ -21,8 +21,8 @@ .GetServices() .ToList(); - RuleFor(configuration => configuration.ReRoutes) - .SetCollectionValidator(reRouteFluentValidator); + RuleForEach(configuration => configuration.ReRoutes) + .SetValidator(reRouteFluentValidator); RuleFor(configuration => configuration.GlobalConfiguration) .SetValidator(fileGlobalConfigurationFluentValidator); diff --git a/src/Ocelot/Configuration/Validator/ReRouteFluentValidator.cs b/src/Ocelot/Configuration/Validator/ReRouteFluentValidator.cs index 724d92bb..746c430c 100644 --- a/src/Ocelot/Configuration/Validator/ReRouteFluentValidator.cs +++ b/src/Ocelot/Configuration/Validator/ReRouteFluentValidator.cs @@ -80,8 +80,8 @@ When(reRoute => string.IsNullOrEmpty(reRoute.ServiceName), () => { - RuleFor(reRoute => reRoute.DownstreamHostAndPorts) - .SetCollectionValidator(hostAndPortValidator); + RuleForEach(reRoute => reRoute.DownstreamHostAndPorts) + .SetValidator(hostAndPortValidator); }); } diff --git a/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs b/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs index 54137b14..3d29ef51 100644 --- a/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs +++ b/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs @@ -29,12 +29,12 @@ namespace Ocelot.DependencyInjection return builder; } - public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, IHostingEnvironment env) + public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, IWebHostEnvironment env) { return builder.AddOcelot(".", env); } - public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, string folder, IHostingEnvironment env) + public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, string folder, IWebHostEnvironment env) { const string primaryConfigFile = "ocelot.json"; diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index 6733b54c..8f120997 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -142,7 +142,7 @@ namespace Ocelot.DependencyInjection .AddApplicationPart(assembly) .AddControllersAsServices() .AddAuthorization() - .AddJsonFormatters(); + .AddNewtonsoftJson(); Services.AddLogging(); Services.AddMiddlewareAnalysis(); diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 030e9957..0d7a3bb7 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -7,9 +7,9 @@ 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; + { + var configuration = services.BuildServiceProvider() + .GetRequiredService(); return new OcelotBuilder(services, configuration); } diff --git a/src/Ocelot/Logging/AspDotNetLogger.cs b/src/Ocelot/Logging/AspDotNetLogger.cs index 598cbdd0..35375d45 100644 --- a/src/Ocelot/Logging/AspDotNetLogger.cs +++ b/src/Ocelot/Logging/AspDotNetLogger.cs @@ -8,53 +8,83 @@ namespace Ocelot.Logging { private readonly ILogger _logger; private readonly IRequestScopedDataRepository _scopedDataRepository; + private readonly Func _func; public AspDotNetLogger(ILogger logger, IRequestScopedDataRepository scopedDataRepository) { _logger = logger; - _scopedDataRepository = scopedDataRepository; + _scopedDataRepository = scopedDataRepository; + _func = (state, exception) => + { + if (exception == null) + { + return state; + } + else + { + return $"{state}, exception: {exception}"; + } + }; } public void LogTrace(string message) - { + { var requestId = GetOcelotRequestId(); - var previousRequestId = GetOcelotPreviousRequestId(); - _logger.LogTrace("requestId: {requestId}, previousRequestId: {previousRequestId}, message: {message}", requestId, previousRequestId, message); + var previousRequestId = GetOcelotPreviousRequestId(); + + var state = $"requestId: {requestId}, previousRequestId: {previousRequestId}, message: {message}"; + + _logger.Log(LogLevel.Trace, default(EventId), state, null, _func); } public void LogDebug(string message) - { + { var requestId = GetOcelotRequestId(); - var previousRequestId = GetOcelotPreviousRequestId(); - _logger.LogDebug("requestId: {requestId}, previousRequestId: {previousRequestId}, message: {message}", requestId, previousRequestId, message); + var previousRequestId = GetOcelotPreviousRequestId(); + + var state = $"requestId: {requestId}, previousRequestId: {previousRequestId}, message: {message}"; + + _logger.Log(LogLevel.Debug, default(EventId), state, null, _func); } public void LogInformation(string message) - { + { var requestId = GetOcelotRequestId(); - var previousRequestId = GetOcelotPreviousRequestId(); - _logger.LogInformation("requestId: {requestId}, previousRequestId: {previousRequestId}, message: {message}", requestId, previousRequestId, message); + var previousRequestId = GetOcelotPreviousRequestId(); + + var state = $"requestId: {requestId}, previousRequestId: {previousRequestId}, message: {message}"; + + _logger.Log(LogLevel.Information, default(EventId), state, null, _func); } public void LogWarning(string message) { var requestId = GetOcelotRequestId(); - var previousRequestId = GetOcelotPreviousRequestId(); - _logger.LogWarning("requestId: {requestId}, previousRequestId: {previousRequestId}, message: {message}", requestId, previousRequestId, message); + var previousRequestId = GetOcelotPreviousRequestId(); + + var state = $"requestId: {requestId}, previousRequestId: {previousRequestId}, message: {message}"; + + _logger.Log(LogLevel.Warning, default(EventId), state, null, _func); } public void LogError(string message, Exception exception) { var requestId = GetOcelotRequestId(); - var previousRequestId = GetOcelotPreviousRequestId(); - _logger.LogError("requestId: {requestId}, previousRequestId: {previousRequestId}, message: {message}, exception: {exception}", requestId, previousRequestId, message, exception); + var previousRequestId = GetOcelotPreviousRequestId(); + + var state = $"requestId: {requestId}, previousRequestId: {previousRequestId}, message: {message}"; + + _logger.Log(LogLevel.Error,default(EventId), state, exception, _func); } public void LogCritical(string message, Exception exception) { var requestId = GetOcelotRequestId(); - var previousRequestId = GetOcelotPreviousRequestId(); - _logger.LogCritical("requestId: {requestId}, previousRequestId: {previousRequestId}, message: {message}, exception: {exception}", requestId, previousRequestId, message, exception); + var previousRequestId = GetOcelotPreviousRequestId(); + + var state = $"requestId: {requestId}, previousRequestId: {previousRequestId}, message: {message}"; + + _logger.Log(LogLevel.Critical, default(EventId), state, exception, _func); } private string GetOcelotRequestId() @@ -81,4 +111,4 @@ namespace Ocelot.Logging return requestId.Data; } } -} +} diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index a841df5a..42bc6b71 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -178,7 +178,7 @@ private static void ConfigureDiagnosticListener(IApplicationBuilder builder) { - var env = builder.ApplicationServices.GetService(); + var env = builder.ApplicationServices.GetService(); var listener = builder.ApplicationServices.GetService(); var diagnosticListener = builder.ApplicationServices.GetService(); diagnosticListener.SubscribeWithAdapter(listener); diff --git a/src/Ocelot/Ocelot.csproj b/src/Ocelot/Ocelot.csproj index b9d9d2ce..fb0d541a 100644 --- a/src/Ocelot/Ocelot.csproj +++ b/src/Ocelot/Ocelot.csproj @@ -1,8 +1,6 @@  - netstandard2.0 - 2.0.0 - 2.0.0 + netcoreapp3.0 true Ocelot is an API Gateway. The 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. reference 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. Ocelot @@ -25,16 +23,20 @@ full True + - - - - - + + + NU1701 - + + all + + + + diff --git a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs b/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs index ae7d6b0a..e72aab33 100644 --- a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs +++ b/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs @@ -5,6 +5,7 @@ namespace Ocelot.AcceptanceTests using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.Hosting; using Newtonsoft.Json; using System; using System.Collections.Generic; @@ -16,9 +17,9 @@ namespace Ocelot.AcceptanceTests public class ConfigurationInConsulTests : IDisposable { - private IWebHost _builder; + private IHost _builder; private readonly Steps _steps; - private IWebHost _fakeConsulBuilder; + private IHost _fakeConsulBuilder; private FileConfiguration _config; private readonly List _consulServices; @@ -75,8 +76,10 @@ namespace Ocelot.AcceptanceTests private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url, string serviceName) { - _fakeConsulBuilder = new WebHostBuilder() - .UseUrls(url) + _fakeConsulBuilder = Host.CreateDefaultBuilder() + .ConfigureWebHost(webBuilder => + { + webBuilder.UseUrls(url) .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() @@ -103,7 +106,9 @@ namespace Ocelot.AcceptanceTests { var reader = new StreamReader(context.Request.Body); - var json = reader.ReadToEnd(); + // Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead. + // var json = reader.ReadToEnd(); + var json = await reader.ReadToEndAsync(); _config = JsonConvert.DeserializeObject(json); @@ -122,8 +127,8 @@ namespace Ocelot.AcceptanceTests await context.Response.WriteJsonAsync(_consulServices); } }); - }) - .Build(); + }); + }).Build(); _fakeConsulBuilder.Start(); } @@ -146,22 +151,24 @@ namespace Ocelot.AcceptanceTests 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 => + _builder = Host.CreateDefaultBuilder() + .ConfigureWebHost(webBuilder => { - app.UsePathBase(basePath); - - app.Run(async context => + webBuilder.UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - }); - }) + app.UsePathBase(basePath); + app.Run(async context => + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + }); + }); + }) .Build(); _builder.Start(); diff --git a/test/Ocelot.AcceptanceTests/ConsulConfigurationInConsulTests.cs b/test/Ocelot.AcceptanceTests/ConsulConfigurationInConsulTests.cs index 27f8dee8..976bd909 100644 --- a/test/Ocelot.AcceptanceTests/ConsulConfigurationInConsulTests.cs +++ b/test/Ocelot.AcceptanceTests/ConsulConfigurationInConsulTests.cs @@ -373,7 +373,9 @@ { var reader = new StreamReader(context.Request.Body); - var json = reader.ReadToEnd(); + // Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead. + // var json = reader.ReadToEnd(); + var json = await reader.ReadToEndAsync(); _config = JsonConvert.DeserializeObject(json); diff --git a/test/Ocelot.AcceptanceTests/ConsulWebSocketTests.cs b/test/Ocelot.AcceptanceTests/ConsulWebSocketTests.cs index 8ed9b140..7ebb4737 100644 --- a/test/Ocelot.AcceptanceTests/ConsulWebSocketTests.cs +++ b/test/Ocelot.AcceptanceTests/ConsulWebSocketTests.cs @@ -181,7 +181,13 @@ } else if (result.MessageType == WebSocketMessageType.Close) { - await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); + if (client.State != WebSocketState.Closed) + { + // Last version, the client state is CloseReceived + // Valid states are: Open, CloseReceived, CloseSent + await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); + } + break; } } @@ -227,7 +233,13 @@ } else if (result.MessageType == WebSocketMessageType.Close) { - await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); + if (client.State != WebSocketState.Closed) + { + // Last version, the client state is CloseReceived + // Valid states are: Open, CloseReceived, CloseSent + await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); + } + break; } } diff --git a/test/Ocelot.AcceptanceTests/ContentTests.cs b/test/Ocelot.AcceptanceTests/ContentTests.cs index 39299f74..7a4d8365 100644 --- a/test/Ocelot.AcceptanceTests/ContentTests.cs +++ b/test/Ocelot.AcceptanceTests/ContentTests.cs @@ -55,7 +55,7 @@ namespace Ocelot.AcceptanceTests .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) .And(x => ThenTheContentTypeShouldBeEmpty()) - .And(x => ThenTheContentLengthShouldBeEmpty()) + .And(x => ThenTheContentLengthShouldBeZero()) .BDDfy(); } @@ -139,9 +139,9 @@ namespace Ocelot.AcceptanceTests _contentType.ShouldBe(expected); } - private void ThenTheContentLengthShouldBeEmpty() + private void ThenTheContentLengthShouldBeZero() { - _contentLength.ShouldBeNull(); + _contentLength.ShouldBeEquivalentTo(0L); } private void ThenTheContentLengthIs(int expected) diff --git a/test/Ocelot.AcceptanceTests/GzipTests.cs b/test/Ocelot.AcceptanceTests/GzipTests.cs index bb03c9c5..51ab1723 100644 --- a/test/Ocelot.AcceptanceTests/GzipTests.cs +++ b/test/Ocelot.AcceptanceTests/GzipTests.cs @@ -73,7 +73,9 @@ namespace Ocelot.AcceptanceTests { using (var sr = new StreamReader(decompress)) { - text = sr.ReadToEnd(); + // Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead. + // text = sr.ReadToEnd(); + text = await sr.ReadToEndAsync(); } } diff --git a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj index 762448e7..a41029d4 100644 --- a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj +++ b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj @@ -1,7 +1,7 @@  0.0.0-dev - netcoreapp2.2 + netcoreapp3.0 Ocelot.AcceptanceTests Exe Ocelot.AcceptanceTests @@ -36,36 +36,39 @@ + - - - - + + + all all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - + + + + + + + + - + - - + + - - + + + + + \ No newline at end of file diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 6b9870bd..5bddcd92 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -37,7 +37,7 @@ using System.Threading.Tasks; using static Ocelot.AcceptanceTests.HttpDelegatingHandlersTests; using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; - using CookieHeaderValue = System.Net.Http.Headers.CookieHeaderValue; + using CookieHeaderValue = Microsoft.Net.Http.Headers.CookieHeaderValue; using MediaTypeHeaderValue = System.Net.Http.Headers.MediaTypeHeaderValue; public class Steps : IDisposable @@ -338,7 +338,7 @@ { x.WithMicrosoftLogging(log => { - log.AddConsole(LogLevel.Debug); + //log.AddConsole(LogLevel.Debug); }) .WithJsonSerializer() .WithHandle(typeof(InMemoryJsonHandle<>)); @@ -424,7 +424,7 @@ { x.WithMicrosoftLogging(log => { - log.AddConsole(LogLevel.Debug); + //log.AddConsole(LogLevel.Debug); }) .WithJsonSerializer() .WithHandle(typeof(InMemoryJsonHandle<>)); diff --git a/test/Ocelot.AcceptanceTests/WebSocketTests.cs b/test/Ocelot.AcceptanceTests/WebSocketTests.cs index e98a1bf9..425416e2 100644 --- a/test/Ocelot.AcceptanceTests/WebSocketTests.cs +++ b/test/Ocelot.AcceptanceTests/WebSocketTests.cs @@ -164,7 +164,13 @@ namespace Ocelot.AcceptanceTests } else if (result.MessageType == WebSocketMessageType.Close) { - await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); + if (client.State != WebSocketState.Closed) + { + // Last version, the client state is CloseReceived + // Valid states are: Open, CloseReceived, CloseSent + await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); + } + break; } } @@ -210,7 +216,13 @@ namespace Ocelot.AcceptanceTests } else if (result.MessageType == WebSocketMessageType.Close) { - await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); + if (client.State != WebSocketState.Closed) + { + // Last version, the client state is CloseReceived + // Valid states are: Open, CloseReceived, CloseSent + await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); + } + break; } } @@ -273,13 +285,13 @@ namespace Ocelot.AcceptanceTests var result = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); - while (!result.CloseStatus.HasValue) + while (!result.CloseStatus.HasValue) { await webSocket.SendAsync(new ArraySegment(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None); result = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); } - + await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); } catch (Exception e) diff --git a/test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj b/test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj index 32f4f556..48df8a92 100644 --- a/test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj +++ b/test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj @@ -2,7 +2,7 @@ 0.0.0-dev - netcoreapp2.2 + netcoreapp3.0 Ocelot.Benchmarks Exe Ocelot.Benchmarks @@ -19,8 +19,12 @@ - + all + + + + \ No newline at end of file diff --git a/test/Ocelot.IntegrationTests/AdministrationTests.cs b/test/Ocelot.IntegrationTests/AdministrationTests.cs index 5b5895cd..b874f787 100644 --- a/test/Ocelot.IntegrationTests/AdministrationTests.cs +++ b/test/Ocelot.IntegrationTests/AdministrationTests.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Newtonsoft.Json; using Ocelot.Administration; using Ocelot.Cache; @@ -29,15 +30,15 @@ namespace Ocelot.IntegrationTests private HttpClient _httpClient; private readonly HttpClient _httpClientTwo; private HttpResponseMessage _response; - private IWebHost _builder; - private IWebHostBuilder _webHostBuilder; + private IHost _builder; + private IHostBuilder _webHostBuilder; private string _ocelotBaseUrl; private BearerToken _token; - private IWebHostBuilder _webHostBuilderTwo; - private IWebHost _builderTwo; - private IWebHost _identityServerBuilder; - private IWebHost _fooServiceBuilder; - private IWebHost _barServiceBuilder; + private IHostBuilder _webHostBuilderTwo; + private IHost _builderTwo; + private IHost _identityServerBuilder; + private IHost _fooServiceBuilder; + private IHost _barServiceBuilder; public AdministrationTests() { @@ -220,7 +221,7 @@ namespace Ocelot.IntegrationTests UpstreamHttpMethod = new List { "get" }, UpstreamPathTemplate = "/test" } - } + }, }; var updatedConfiguration = new FileConfiguration @@ -476,56 +477,59 @@ namespace Ocelot.IntegrationTests private void GivenThereIsAnIdentityServerOn(string url, string apiName) { - _identityServerBuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureServices(services => + _identityServerBuilder = Host.CreateDefaultBuilder() + .ConfigureWebHost(webBuilder => { - services.AddLogging(); - services.AddIdentityServer() - .AddDeveloperSigningCredential() + webBuilder.UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .ConfigureServices(services => + { + services.AddLogging(); + services.AddIdentityServer() + .AddDeveloperSigningCredential() .AddInMemoryApiResources(new List { - new ApiResource + new ApiResource + { + Name = apiName, + Description = apiName, + Enabled = true, + DisplayName = apiName, + Scopes = new List() { - Name = apiName, - Description = apiName, - Enabled = true, - DisplayName = apiName, - Scopes = new List() - { - new Scope(apiName) - } - } + new Scope(apiName), + }, + }, }) .AddInMemoryClients(new List { - new Client - { - ClientId = apiName, - AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, - ClientSecrets = new List {new Secret("secret".Sha256())}, - AllowedScopes = new List { apiName }, - AccessTokenType = AccessTokenType.Jwt, - Enabled = true - } + new Client + { + ClientId = apiName, + AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, + ClientSecrets = new List { new Secret("secret".Sha256()) }, + AllowedScopes = new List { apiName }, + AccessTokenType = AccessTokenType.Jwt, + Enabled = true + }, }) .AddTestUsers(new List { - new TestUser - { - Username = "test", - Password = "test", - SubjectId = "1231231" - } + new TestUser + { + Username = "test", + Password = "test", + SubjectId = "1231231" + }, }); - }) - .Configure(app => - { - app.UseIdentityServer(); - }) - .Build(); + }) + .Configure(app => + { + app.UseIdentityServer(); + } + ); + }).Build(); _identityServerBuilder.Start(); @@ -540,28 +544,32 @@ namespace Ocelot.IntegrationTests { _httpClientTwo.BaseAddress = new Uri(baseUrl); - _webHostBuilderTwo = new WebHostBuilder() - .UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); - config.AddJsonFile("ocelot.json", false, false); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(x => - { - x.AddOcelot() + _webHostBuilderTwo = Host.CreateDefaultBuilder() + .ConfigureWebHost(webBuilder => + { + webBuilder.UseUrls(baseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); + config.AddJsonFile("ocelot.json", false, false); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(x => + { + x.AddMvc(option => option.EnableEndpointRouting = false); + x.AddOcelot() .AddAdministration("/administration", "secret"); - }) - .Configure(app => - { - app.UseOcelot().Wait(); - }); + }) + .Configure(app => + { + app.UseOcelot().Wait(); + }); + }); _builderTwo = _webHostBuilderTwo.Build(); @@ -654,29 +662,33 @@ namespace Ocelot.IntegrationTests private void GivenOcelotIsRunningWithIdentityServerSettings(Action configOptions) { - _webHostBuilder = new WebHostBuilder() - .UseUrls(_ocelotBaseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureAppConfiguration((hostingContext, config) => + _webHostBuilder = Host.CreateDefaultBuilder() + .ConfigureWebHost(webBuilder => { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + webBuilder.UseUrls(_ocelotBaseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); - config.AddJsonFile("ocelot.json", false, false); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(x => - { - x.AddSingleton(_webHostBuilder); - x.AddOcelot() - .AddAdministration("/administration", configOptions); - }) + config.AddJsonFile("ocelot.json", false, false); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(x => + { + x.AddMvc(option => option.EnableEndpointRouting = false); + x.AddSingleton(_webHostBuilder); + x.AddOcelot() + .AddAdministration("/administration", configOptions); + }) .Configure(app => { app.UseOcelot().Wait(); }); + }); _builder = _webHostBuilder.Build(); @@ -685,27 +697,31 @@ namespace Ocelot.IntegrationTests private void GivenOcelotIsRunning() { - _webHostBuilder = new WebHostBuilder() - .UseUrls(_ocelotBaseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureAppConfiguration((hostingContext, config) => + _webHostBuilder = Host.CreateDefaultBuilder() + .ConfigureWebHost(webBuilder => { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + webBuilder.UseUrls(_ocelotBaseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); - config.AddJsonFile("ocelot.json", false, false); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(x => - { - x.AddOcelot() + config.AddJsonFile("ocelot.json", false, false); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(x => + { + x.AddMvc(s => s.EnableEndpointRouting = false); + x.AddOcelot() .AddAdministration("/administration", "secret"); - }) - .Configure(app => - { - app.UseOcelot().Wait(); + }) + .Configure(app => + { + app.UseOcelot().Wait(); + }); }); _builder = _webHostBuilder.Build(); @@ -715,30 +731,34 @@ namespace Ocelot.IntegrationTests private void GivenOcelotIsRunningWithNoWebHostBuilder(string baseUrl) { - _webHostBuilder = new WebHostBuilder() - .UseUrls(_ocelotBaseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureAppConfiguration((hostingContext, config) => + _webHostBuilder = Host.CreateDefaultBuilder() + .ConfigureWebHost(webBuilder => { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + webBuilder.UseUrls(_ocelotBaseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); - config.AddJsonFile("ocelot.json", false, false); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(x => - { - x.AddSingleton(_webHostBuilder); - x.AddOcelot() + config.AddJsonFile("ocelot.json", false, false); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(x => + { + x.AddMvc(option => option.EnableEndpointRouting = false); + x.AddSingleton(_webHostBuilder); + x.AddOcelot() .AddAdministration("/administration", "secret"); - }) - .Configure(app => - { - app.UseOcelot().Wait(); - }); - + }) + .Configure(app => + { + app.UseOcelot().Wait(); + }); + }); + _builder = _webHostBuilder.Build(); _builder.Start(); @@ -797,42 +817,46 @@ namespace Ocelot.IntegrationTests private void GivenThereIsAFooServiceRunningOn(string baseUrl) { - _fooServiceBuilder = new WebHostBuilder() - .UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .Configure(app => + _fooServiceBuilder = Host.CreateDefaultBuilder() + .ConfigureWebHost(webBuilder => { - app.UsePathBase("/foo"); - app.Run(async context => + webBuilder.UseUrls(baseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .Configure(app => { - context.Response.StatusCode = 200; - await context.Response.WriteAsync("foo"); + app.UsePathBase("/foo"); + app.Run(async context => + { + context.Response.StatusCode = 200; + await context.Response.WriteAsync("foo"); + }); }); - }) - .Build(); - + }).Build(); + _fooServiceBuilder.Start(); } private void GivenThereIsABarServiceRunningOn(string baseUrl) { - _barServiceBuilder = new WebHostBuilder() - .UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .Configure(app => + _barServiceBuilder = Host.CreateDefaultBuilder() + .ConfigureWebHost(webBuilder => { - app.UsePathBase("/bar"); - app.Run(async context => + webBuilder.UseUrls(baseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .Configure(app => { - context.Response.StatusCode = 200; - await context.Response.WriteAsync("bar"); + app.UsePathBase("/bar"); + app.Run(async context => + { + context.Response.StatusCode = 200; + await context.Response.WriteAsync("bar"); + }); }); - }) - .Build(); + }).Build(); _barServiceBuilder.Start(); } diff --git a/test/Ocelot.IntegrationTests/CacheManagerTests.cs b/test/Ocelot.IntegrationTests/CacheManagerTests.cs index beab8ca3..6be90f01 100644 --- a/test/Ocelot.IntegrationTests/CacheManagerTests.cs +++ b/test/Ocelot.IntegrationTests/CacheManagerTests.cs @@ -5,6 +5,8 @@ namespace Ocelot.IntegrationTests using global::CacheManager.Core; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Ocelot.Administration; @@ -25,15 +27,10 @@ namespace Ocelot.IntegrationTests private HttpClient _httpClient; private readonly HttpClient _httpClientTwo; private HttpResponseMessage _response; - private IWebHost _builder; - private IWebHostBuilder _webHostBuilder; + private IHost _builder; + private IHostBuilder _webHostBuilder; private string _ocelotBaseUrl; private BearerToken _token; - private IWebHostBuilder _webHostBuilderTwo; - private IWebHost _builderTwo; - private IWebHost _identityServerBuilder; - private IWebHost _fooServiceBuilder; - private IWebHost _barServiceBuilder; public CacheManagerTests() { @@ -61,7 +58,7 @@ namespace Ocelot.IntegrationTests { Host = "localhost", Port = 80, - } + }, }, DownstreamScheme = "https", DownstreamPathTemplate = "/", @@ -69,8 +66,8 @@ namespace Ocelot.IntegrationTests UpstreamPathTemplate = "/", FileCacheOptions = new FileCacheOptions { - TtlSeconds = 10 - } + TtlSeconds = 10, + }, }, new FileReRoute() { @@ -80,7 +77,7 @@ namespace Ocelot.IntegrationTests { Host = "localhost", Port = 80, - } + }, }, DownstreamScheme = "https", DownstreamPathTemplate = "/", @@ -88,10 +85,10 @@ namespace Ocelot.IntegrationTests UpstreamPathTemplate = "/test", FileCacheOptions = new FileCacheOptions { - TtlSeconds = 10 - } - } - } + TtlSeconds = 10, + }, + }, + }, }; var regionToClear = "gettest"; @@ -118,7 +115,7 @@ namespace Ocelot.IntegrationTests new KeyValuePair("client_id", "admin"), new KeyValuePair("client_secret", "secret"), new KeyValuePair("scope", "admin"), - new KeyValuePair("grant_type", "client_credentials") + new KeyValuePair("grant_type", "client_credentials"), }; var content = new FormUrlEncodedContent(formData); @@ -133,16 +130,13 @@ namespace Ocelot.IntegrationTests private void GivenOcelotIsRunning() { - _webHostBuilder = new WebHostBuilder() - .UseUrls(_ocelotBaseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) + _webHostBuilder = Host.CreateDefaultBuilder() .ConfigureAppConfiguration((hostingContext, config) => { config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); config.AddJsonFile("ocelot.json", false, false); config.AddEnvironmentVariables(); }) @@ -151,20 +145,26 @@ namespace Ocelot.IntegrationTests Action settings = (s) => { s.WithMicrosoftLogging(log => - { - log.AddConsole(LogLevel.Debug); - }) - .WithDictionaryHandle(); + { + //log.AddConsole(LogLevel.Debug); + }) + .WithDictionaryHandle(); }; - + x.AddMvc(option => option.EnableEndpointRouting = false); x.AddOcelot() - .AddCacheManager(settings) - .AddAdministration("/administration", "secret"); - }) + .AddCacheManager(settings) + .AddAdministration("/administration", "secret"); + }) + .ConfigureWebHost(webBuilder => + { + webBuilder.UseUrls(_ocelotBaseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) .Configure(app => { app.UseOcelot().Wait(); }); + }); _builder = _webHostBuilder.Build(); @@ -214,7 +214,7 @@ namespace Ocelot.IntegrationTests Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD", ""); _builder?.Dispose(); _httpClient?.Dispose(); - _identityServerBuilder?.Dispose(); + //_identityServerBuilder?.Dispose(); } } } diff --git a/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj b/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj index 81c50e05..f38ede57 100644 --- a/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj +++ b/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj @@ -1,7 +1,7 @@  0.0.0-dev - netcoreapp2.2 + netcoreapp3.0 Ocelot.IntegrationTests Exe Ocelot.IntegrationTests @@ -29,30 +29,33 @@ + - - - - + + + all all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - + + + + + + + - + - - - + + + + + + \ No newline at end of file diff --git a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs index 2f170229..644d1d90 100644 --- a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs +++ b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs @@ -54,12 +54,12 @@ namespace Ocelot.IntegrationTests { Host = "localhost", Port = 51879, - } + }, }, UpstreamPathTemplate = "/", UpstreamHttpMethod = new List { "Get" }, - } - } + }, + }, }; this.Given(x => GivenThereIsAConfiguration(configuration)) diff --git a/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj b/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj index 478d425f..ccfdce53 100644 --- a/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj +++ b/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj @@ -1,7 +1,7 @@  0.0.0-dev - netcoreapp2.2 + netcoreapp3.0 true Ocelot.ManualTest Exe @@ -28,16 +28,18 @@ - - - - - - - - - + + + + + + + + all + + + \ No newline at end of file diff --git a/test/Ocelot.ManualTest/Program.cs b/test/Ocelot.ManualTest/Program.cs index 659e250a..7d3fc969 100644 --- a/test/Ocelot.ManualTest/Program.cs +++ b/test/Ocelot.ManualTest/Program.cs @@ -32,12 +32,12 @@ namespace Ocelot.ManualTest }) .ConfigureServices(s => { - s.AddAuthentication() - .AddJwtBearer("TestKey", x => - { - x.Authority = "test"; - x.Audience = "test"; - }); + s.AddAuthentication(); + //.AddJwtBearer("TestKey", x => + //{ + // x.Authority = "test"; + // x.Audience = "test"; + //}); s.AddSingleton((x, t) => new FakeHandler()); s.AddOcelot() diff --git a/test/Ocelot.UnitTests/Administration/OcelotAdministrationBuilderTests.cs b/test/Ocelot.UnitTests/Administration/OcelotAdministrationBuilderTests.cs index 1eea1619..e19025d1 100644 --- a/test/Ocelot.UnitTests/Administration/OcelotAdministrationBuilderTests.cs +++ b/test/Ocelot.UnitTests/Administration/OcelotAdministrationBuilderTests.cs @@ -2,14 +2,15 @@ namespace Ocelot.UnitTests.Administration { using IdentityServer4.AccessTokenValidation; using Microsoft.AspNetCore.Hosting; - using Microsoft.AspNetCore.Hosting.Internal; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; + using Moq; using Ocelot.Administration; using Ocelot.DependencyInjection; using Shouldly; using System; using System.Collections.Generic; + using System.Reflection; using TestStack.BDDfy; using Xunit; @@ -25,9 +26,19 @@ namespace Ocelot.UnitTests.Administration { _configRoot = new ConfigurationRoot(new List()); _services = new ServiceCollection(); - _services.AddSingleton(); + _services.AddSingleton(GetHostingEnvironment()); _services.AddSingleton(_configRoot); } + + private IWebHostEnvironment GetHostingEnvironment() + { + var environment = new Mock(); + environment + .Setup(e => e.ApplicationName) + .Returns(typeof(OcelotAdministrationBuilderTests).GetTypeInfo().Assembly.GetName().Name); + + return environment.Object; + } //keep [Fact] diff --git a/test/Ocelot.UnitTests/CacheManager/OcelotBuilderExtensionsTests.cs b/test/Ocelot.UnitTests/CacheManager/OcelotBuilderExtensionsTests.cs index efa5631c..b85ee1f4 100644 --- a/test/Ocelot.UnitTests/CacheManager/OcelotBuilderExtensionsTests.cs +++ b/test/Ocelot.UnitTests/CacheManager/OcelotBuilderExtensionsTests.cs @@ -2,9 +2,10 @@ { using global::CacheManager.Core; using Microsoft.AspNetCore.Hosting; - using Microsoft.AspNetCore.Hosting.Internal; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Hosting.Internal; + using Moq; using Ocelot.Cache; using Ocelot.Cache.CacheManager; using Ocelot.Configuration; @@ -14,6 +15,7 @@ using System; using System.Collections.Generic; using System.Linq; + using System.Reflection; using TestStack.BDDfy; using Xunit; @@ -30,11 +32,21 @@ { _configRoot = new ConfigurationRoot(new List()); _services = new ServiceCollection(); - _services.AddSingleton(); + _services.AddSingleton(GetHostingEnvironment()); _services.AddSingleton(_configRoot); _maxRetries = 100; } + private IWebHostEnvironment GetHostingEnvironment() + { + var environment = new Mock(); + environment + .Setup(e => e.ApplicationName) + .Returns(typeof(OcelotBuilderExtensionsTests).GetTypeInfo().Assembly.GetName().Name); + + return environment.Object; + } + [Fact] public void should_set_up_cache_manager() { diff --git a/test/Ocelot.UnitTests/Configuration/DiskFileConfigurationRepositoryTests.cs b/test/Ocelot.UnitTests/Configuration/DiskFileConfigurationRepositoryTests.cs index d084c2d6..b2eb06b5 100644 --- a/test/Ocelot.UnitTests/Configuration/DiskFileConfigurationRepositoryTests.cs +++ b/test/Ocelot.UnitTests/Configuration/DiskFileConfigurationRepositoryTests.cs @@ -15,7 +15,7 @@ namespace Ocelot.UnitTests.Configuration public class DiskFileConfigurationRepositoryTests : IDisposable { - private readonly Mock _hostingEnvironment; + private readonly Mock _hostingEnvironment; private IFileConfigurationRepository _repo; private string _environmentSpecificPath; private string _ocelotJsonPath; @@ -33,7 +33,7 @@ namespace Ocelot.UnitTests.Configuration { _semaphore = new SemaphoreSlim(1, 1); _semaphore.Wait(); - _hostingEnvironment = new Mock(); + _hostingEnvironment = new Mock(); _hostingEnvironment.Setup(he => he.EnvironmentName).Returns(_environmentName); _repo = new DiskFileConfigurationRepository(_hostingEnvironment.Object); } diff --git a/test/Ocelot.UnitTests/Consul/ConsulFileConfigurationRepositoryTests.cs b/test/Ocelot.UnitTests/Consul/ConsulFileConfigurationRepositoryTests.cs index 8a011026..e655f123 100644 --- a/test/Ocelot.UnitTests/Consul/ConsulFileConfigurationRepositoryTests.cs +++ b/test/Ocelot.UnitTests/Consul/ConsulFileConfigurationRepositoryTests.cs @@ -1,6 +1,7 @@ namespace Ocelot.UnitTests.Consul { using global::Consul; + using Microsoft.Extensions.Options; using Moq; using Newtonsoft.Json; using Ocelot.Cache; @@ -23,8 +24,8 @@ public class ConsulFileConfigurationRepositoryTests { private ConsulFileConfigurationRepository _repo; + private Mock> _options; private Mock> _cache; - private Mock _internalRepo; private Mock _factory; private Mock _loggerFactory; private Mock _client; @@ -36,9 +37,9 @@ public ConsulFileConfigurationRepositoryTests() { _cache = new Mock>(); - _internalRepo = new Mock(); _loggerFactory = new Mock(); + _options = new Mock>(); _factory = new Mock(); _client = new Mock(); _kvEndpoint = new Mock(); @@ -51,11 +52,9 @@ .Setup(x => x.Get(It.IsAny())) .Returns(_client.Object); - _internalRepo - .Setup(x => x.Get()) - .Returns(new OkResponse(new InternalConfiguration(new List(), "", new ServiceProviderConfigurationBuilder().Build(), "", It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()))); - - _repo = new ConsulFileConfigurationRepository(_cache.Object, _internalRepo.Object, _factory.Object, _loggerFactory.Object); + _options + .SetupGet(x => x.Value) + .Returns(() => _fileConfiguration); } [Fact] @@ -85,7 +84,10 @@ [Fact] public void should_get_null_config() { - this.Given(_ => GivenFetchFromConsulReturnsNull()) + var config = FakeFileConfiguration(); + + this.Given(_ => GivenIHaveAConfiguration(config)) + .Given(_ => GivenFetchFromConsulReturnsNull()) .When(_ => WhenIGetTheConfiguration()) .Then(_ => ThenTheConfigurationIsNull()) .BDDfy(); @@ -136,14 +138,8 @@ private void GivenTheConfigKeyComesFromFileConfig(string key) { - _internalRepo - .Setup(x => x.Get()) - .Returns(new OkResponse(new InternalConfiguration(new List(), "", - new ServiceProviderConfigurationBuilder().WithConfigurationKey(key).Build(), "", - new LoadBalancerOptionsBuilder().Build(), "", new QoSOptionsBuilder().Build(), - new HttpHandlerOptionsBuilder().Build()))); - - _repo = new ConsulFileConfigurationRepository(_cache.Object, _internalRepo.Object, _factory.Object, _loggerFactory.Object); + _fileConfiguration.GlobalConfiguration.ServiceDiscoveryProvider.ConfigurationKey = key; + _repo = new ConsulFileConfigurationRepository(_options.Object, _cache.Object, _factory.Object, _loggerFactory.Object); } private void ThenTheConfigurationIsNull() @@ -221,6 +217,8 @@ private void GivenIHaveAConfiguration(FileConfiguration config) { _fileConfiguration = config; + + _repo = new ConsulFileConfigurationRepository(_options.Object, _cache.Object, _factory.Object, _loggerFactory.Object); } private FileConfiguration FakeFileConfiguration() diff --git a/test/Ocelot.UnitTests/Consul/OcelotBuilderExtensionsTests.cs b/test/Ocelot.UnitTests/Consul/OcelotBuilderExtensionsTests.cs index 58a4f8a9..cb5fc0b5 100644 --- a/test/Ocelot.UnitTests/Consul/OcelotBuilderExtensionsTests.cs +++ b/test/Ocelot.UnitTests/Consul/OcelotBuilderExtensionsTests.cs @@ -1,14 +1,15 @@ namespace Ocelot.UnitTests.Consul { using Microsoft.AspNetCore.Hosting; - using Microsoft.AspNetCore.Hosting.Internal; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; + using Moq; using Ocelot.DependencyInjection; using Provider.Consul; using Shouldly; using System; using System.Collections.Generic; + using System.Reflection; using TestStack.BDDfy; using Xunit; @@ -24,10 +25,21 @@ { _configRoot = new ConfigurationRoot(new List()); _services = new ServiceCollection(); - _services.AddSingleton(); + _services.AddSingleton(GetHostingEnvironment()); _services.AddSingleton(_configRoot); } + + private IWebHostEnvironment GetHostingEnvironment() + { + var environment = new Mock(); + environment + .Setup(e => e.ApplicationName) + .Returns(typeof(OcelotBuilderExtensionsTests).GetTypeInfo().Assembly.GetName().Name); + + return environment.Object; + } + [Fact] public void should_set_up_consul() { diff --git a/test/Ocelot.UnitTests/Consul/PollingConsulServiceDiscoveryProviderTests.cs b/test/Ocelot.UnitTests/Consul/PollingConsulServiceDiscoveryProviderTests.cs index ab77d7d0..cc38e29c 100644 --- a/test/Ocelot.UnitTests/Consul/PollingConsulServiceDiscoveryProviderTests.cs +++ b/test/Ocelot.UnitTests/Consul/PollingConsulServiceDiscoveryProviderTests.cs @@ -15,7 +15,6 @@ public class PollingConsulServiceDiscoveryProviderTests { private readonly int _delay; - private PollConsul _provider; private readonly List _services; private readonly Mock _factory; private readonly Mock _logger; @@ -56,27 +55,28 @@ private void WhenIGetTheServices(int expected) { - _provider = new PollConsul(_delay, _factory.Object, _consulServiceDiscoveryProvider.Object); - - var result = Wait.WaitFor(3000).Until(() => + using (var provider = new PollConsul(_delay, _factory.Object, _consulServiceDiscoveryProvider.Object)) { - try + var result = Wait.WaitFor(3000).Until(() => { - _result = _provider.Get().GetAwaiter().GetResult(); - if (_result.Count == expected) + try { - return true; + _result = provider.Get().GetAwaiter().GetResult(); + if (_result.Count == expected) + { + return true; + } + + return false; } + catch (Exception) + { + return false; + } + }); - return false; - } - catch (Exception) - { - return false; - } - }); - - result.ShouldBeTrue(); + result.ShouldBeTrue(); + } } } } diff --git a/test/Ocelot.UnitTests/Consul/ProviderFactoryTests.cs b/test/Ocelot.UnitTests/Consul/ProviderFactoryTests.cs index 5d4939d2..c51e9cd7 100644 --- a/test/Ocelot.UnitTests/Consul/ProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/Consul/ProviderFactoryTests.cs @@ -49,7 +49,9 @@ namespace Ocelot.UnitTests.Consul .Build(); var provider = ConsulProviderFactory.Get(_provider, new ServiceProviderConfiguration("pollconsul", "", 1, "", "", stopsPollerFromPolling), reRoute); - provider.ShouldBeOfType(); + var pollProvider = provider as PollConsul; + pollProvider.ShouldNotBeNull(); + pollProvider.Dispose(); } } } diff --git a/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs b/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs index efb8ff9a..1d408ba4 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs @@ -22,11 +22,11 @@ private FileConfiguration _reRouteB; private FileConfiguration _aggregate; private FileConfiguration _envSpecific; - private Mock _hostingEnvironment; + private Mock _hostingEnvironment; public ConfigurationBuilderExtensionsTests() { - _hostingEnvironment = new Mock(); + _hostingEnvironment = new Mock(); // Clean up config files before each test var subConfigFiles = new DirectoryInfo(".").GetFiles("ocelot.*.json"); diff --git a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs index 3ec06a8b..0f1ddb60 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs @@ -1,11 +1,12 @@ namespace Ocelot.UnitTests.DependencyInjection { using Microsoft.AspNetCore.Hosting; - using Microsoft.AspNetCore.Hosting.Internal; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; + using Moq; using Ocelot.Configuration.Setter; using Ocelot.DependencyInjection; + using Ocelot.Infrastructure; using Ocelot.Middleware.Multiplexer; using Ocelot.Requester; using Ocelot.UnitTests.Requester; @@ -14,7 +15,7 @@ namespace Ocelot.UnitTests.DependencyInjection using System.Collections.Generic; using System.Linq; using System.Net.Http; - using Ocelot.Infrastructure; + using System.Reflection; using TestStack.BDDfy; using Xunit; using static Ocelot.UnitTests.Middleware.UserDefinedResponseAggregatorTests; @@ -32,11 +33,21 @@ namespace Ocelot.UnitTests.DependencyInjection { _configRoot = new ConfigurationRoot(new List()); _services = new ServiceCollection(); - _services.AddSingleton(); + _services.AddSingleton(GetHostingEnvironment()); _services.AddSingleton(_configRoot); _maxRetries = 100; } + private IWebHostEnvironment GetHostingEnvironment() + { + var environment = new Mock(); + environment + .Setup(e => e.ApplicationName) + .Returns(typeof(OcelotBuilderTests).GetTypeInfo().Assembly.GetName().Name); + + return environment.Object; + } + [Fact] public void should_add_specific_delegating_handlers_transient() { diff --git a/test/Ocelot.UnitTests/Eureka/EurekaMiddlewareConfigurationProviderTests.cs b/test/Ocelot.UnitTests/Eureka/EurekaMiddlewareConfigurationProviderTests.cs index 3f1a6da0..fc7f3c82 100644 --- a/test/Ocelot.UnitTests/Eureka/EurekaMiddlewareConfigurationProviderTests.cs +++ b/test/Ocelot.UnitTests/Eureka/EurekaMiddlewareConfigurationProviderTests.cs @@ -1,6 +1,6 @@ namespace Ocelot.UnitTests.Eureka { - using Microsoft.AspNetCore.Builder.Internal; + using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Moq; using Ocelot.Configuration; diff --git a/test/Ocelot.UnitTests/Kubernetes/OcelotBuilderExtensionsTests.cs b/test/Ocelot.UnitTests/Kubernetes/OcelotBuilderExtensionsTests.cs index a2cb9e94..f858c1be 100644 --- a/test/Ocelot.UnitTests/Kubernetes/OcelotBuilderExtensionsTests.cs +++ b/test/Ocelot.UnitTests/Kubernetes/OcelotBuilderExtensionsTests.cs @@ -1,12 +1,13 @@ using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Hosting.Internal; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Moq; using Ocelot.DependencyInjection; using Ocelot.Provider.Kubernetes; using Shouldly; using System; using System.Collections.Generic; +using System.Reflection; using TestStack.BDDfy; using Xunit; @@ -24,10 +25,20 @@ namespace Ocelot.UnitTests.Kubernetes { _configRoot = new ConfigurationRoot(new List()); _services = new ServiceCollection(); - _services.AddSingleton(); + _services.AddSingleton(GetHostingEnvironment()); _services.AddSingleton(_configRoot); } + private IWebHostEnvironment GetHostingEnvironment() + { + var environment = new Mock(); + environment + .Setup(e => e.ApplicationName) + .Returns(typeof(OcelotBuilderExtensionsTests).GetTypeInfo().Assembly.GetName().Name); + + return environment.Object; + } + [Fact] public void should_set_up_kubernetes() { diff --git a/test/Ocelot.UnitTests/Logging/AspDotNetLoggerTests.cs b/test/Ocelot.UnitTests/Logging/AspDotNetLoggerTests.cs index 13d9f874..c4e2d8bf 100644 --- a/test/Ocelot.UnitTests/Logging/AspDotNetLoggerTests.cs +++ b/test/Ocelot.UnitTests/Logging/AspDotNetLoggerTests.cs @@ -10,8 +10,8 @@ namespace Ocelot.UnitTests.Logging public class AspDotNetLoggerTests { private readonly Mock> _coreLogger; + private readonly Mock _repo; private readonly AspDotNetLogger _logger; - private Mock _repo; private readonly string _b; private readonly string _a; private readonly Exception _ex; @@ -55,7 +55,7 @@ namespace Ocelot.UnitTests.Logging { _logger.LogError($"a message from {_a} to {_b}", _ex); - ThenLevelIsLogged("requestId: no request id, previousRequestId: no previous request id, message: a message from tom to laura, exception: System.Exception: oh no", LogLevel.Error); + ThenLevelIsLogged("requestId: no request id, previousRequestId: no previous request id, message: a message from tom to laura", LogLevel.Error, _ex); } [Fact] @@ -63,18 +63,18 @@ namespace Ocelot.UnitTests.Logging { _logger.LogCritical($"a message from {_a} to {_b}", _ex); - ThenLevelIsLogged("requestId: no request id, previousRequestId: no previous request id, message: a message from tom to laura, exception: System.Exception: oh no", LogLevel.Critical); + ThenLevelIsLogged("requestId: no request id, previousRequestId: no previous request id, message: a message from tom to laura", LogLevel.Critical, _ex); } - private void ThenLevelIsLogged(string expected, LogLevel expectedLogLevel) + private void ThenLevelIsLogged(string expected, LogLevel expectedLogLevel, Exception ex = null) { _coreLogger.Verify( x => x.Log( expectedLogLevel, - It.IsAny(), - It.Is(o => o.ToString() == expected), - It.IsAny(), - It.IsAny>()), Times.Once); + default(EventId), + expected, + ex, + It.IsAny>()), Times.Once); } } } diff --git a/test/Ocelot.UnitTests/Middleware/OcelotPiplineBuilderTests.cs b/test/Ocelot.UnitTests/Middleware/OcelotPiplineBuilderTests.cs index bafc3532..21e8dd63 100644 --- a/test/Ocelot.UnitTests/Middleware/OcelotPiplineBuilderTests.cs +++ b/test/Ocelot.UnitTests/Middleware/OcelotPiplineBuilderTests.cs @@ -4,16 +4,17 @@ using System.Threading.Tasks; namespace Ocelot.UnitTests.Middleware { using Microsoft.AspNetCore.Hosting; - using Microsoft.AspNetCore.Hosting.Internal; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; + using Moq; using Ocelot.DependencyInjection; using Ocelot.Logging; using Ocelot.Middleware; using Ocelot.Middleware.Pipeline; using Shouldly; using System.Collections.Generic; + using System.Reflection; using TestStack.BDDfy; using Xunit; @@ -28,11 +29,22 @@ namespace Ocelot.UnitTests.Middleware { _configRoot = new ConfigurationRoot(new List()); _services = new ServiceCollection(); - _services.AddSingleton(); + _services.AddSingleton(GetHostingEnvironment()); _services.AddSingleton(_configRoot); _services.AddOcelot(); } + + private IWebHostEnvironment GetHostingEnvironment() + { + var environment = new Mock(); + environment + .Setup(e => e.ApplicationName) + .Returns(typeof(OcelotPiplineBuilderTests).GetTypeInfo().Assembly.GetName().Name); + + return environment.Object; + } + [Fact] public void should_build_generic() { diff --git a/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj b/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj index 0809d120..d067d869 100644 --- a/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj +++ b/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj @@ -2,7 +2,7 @@ 0.0.0-dev - netcoreapp2.2 + netcoreapp3.0 Ocelot.UnitTests Ocelot.UnitTests Exe @@ -46,13 +46,13 @@ PreserveNewest - + - - - - + + + + all @@ -60,30 +60,38 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - + + + + + + + + + - - + + - - - - + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Rafty/OcelotAdministrationBuilderExtensionsTests.cs b/test/Ocelot.UnitTests/Rafty/OcelotAdministrationBuilderExtensionsTests.cs index feb8603c..212d9293 100644 --- a/test/Ocelot.UnitTests/Rafty/OcelotAdministrationBuilderExtensionsTests.cs +++ b/test/Ocelot.UnitTests/Rafty/OcelotAdministrationBuilderExtensionsTests.cs @@ -1,15 +1,16 @@ namespace Ocelot.UnitTests.Rafty { using Microsoft.AspNetCore.Hosting; - using Microsoft.AspNetCore.Hosting.Internal; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; + using Moq; using Ocelot.Administration; using Ocelot.DependencyInjection; using Provider.Rafty; using Shouldly; using System; using System.Collections.Generic; + using System.Reflection; using TestStack.BDDfy; using Xunit; @@ -25,10 +26,20 @@ { _configRoot = new ConfigurationRoot(new List()); _services = new ServiceCollection(); - _services.AddSingleton(); + _services.AddSingleton(GetHostingEnvironment()); _services.AddSingleton(_configRoot); } + private IWebHostEnvironment GetHostingEnvironment() + { + var environment = new Mock(); + environment + .Setup(e => e.ApplicationName) + .Returns(typeof(OcelotAdministrationBuilderExtensionsTests).GetTypeInfo().Assembly.GetName().Name); + + return environment.Object; + } + [Fact] public void should_set_up_rafty() { diff --git a/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs b/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs index 1f928ff5..2b54bbfa 100644 --- a/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs @@ -69,7 +69,7 @@ namespace Ocelot.UnitTests.RateLimit this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) .When(x => x.WhenICallTheMiddlewareMultipleTime(2)) .Then(x => x.ThenresponseStatusCodeIs200()) - .When(x => x.WhenICallTheMiddlewareMultipleTime(2)) + .When(x => x.WhenICallTheMiddlewareMultipleTime(3)) .Then(x => x.ThenresponseStatusCodeIs429()) .BDDfy(); } @@ -145,8 +145,9 @@ namespace Ocelot.UnitTests.RateLimit internal class FakeStream : Stream { public override void Flush() - { - throw new System.NotImplementedException(); + { + //do nothing + //throw new System.NotImplementedException(); } public override int Read(byte[] buffer, int offset, int count) diff --git a/test/Ocelot.UnitTests/Request/Mapper/RequestMapperTests.cs b/test/Ocelot.UnitTests/Request/Mapper/RequestMapperTests.cs index 1727d503..ac81a4d4 100644 --- a/test/Ocelot.UnitTests/Request/Mapper/RequestMapperTests.cs +++ b/test/Ocelot.UnitTests/Request/Mapper/RequestMapperTests.cs @@ -1,7 +1,6 @@ namespace Ocelot.UnitTests.Request.Mapper { using Microsoft.AspNetCore.Http; - using Microsoft.AspNetCore.Http.Internal; using Microsoft.Extensions.Primitives; using Ocelot.Request.Mapper; using Ocelot.Responses; @@ -19,6 +18,7 @@ public class RequestMapperTests { + private readonly HttpContext _httpContext; private readonly HttpRequest _inputRequest; private readonly RequestMapper _requestMapper; @@ -29,8 +29,8 @@ public RequestMapperTests() { - _inputRequest = new DefaultHttpRequest(new DefaultHttpContext()); - + _httpContext = new DefaultHttpContext(); + _inputRequest = _httpContext.Request; _requestMapper = new RequestMapper(); } diff --git a/test/Ocelot.UnitTests/UnitTests.runsettings b/test/Ocelot.UnitTests/UnitTests.runsettings new file mode 100644 index 00000000..1cba21cc --- /dev/null +++ b/test/Ocelot.UnitTests/UnitTests.runsettings @@ -0,0 +1,23 @@ + + + + + + + opencover + false + true + + + + + + + + + + + \ No newline at end of file diff --git a/tools/packages.config b/tools/packages.config index 1d2d1534..238f21ca 100644 --- a/tools/packages.config +++ b/tools/packages.config @@ -1,4 +1,4 @@ - + From 1b72b58560734b94d3e50e73c66666a26b1ae743 Mon Sep 17 00:00:00 2001 From: Thiago Loureiro Date: Tue, 5 Nov 2019 23:40:56 +0800 Subject: [PATCH 6/7] Update README.md --- README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/README.md b/README.md index 9b13aaa0..4f0cf5c2 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,6 @@ [![Build Status](https://travis-ci.org/ThreeMammals/Ocelot.svg?branch=develop)](https://travis-ci.org/ThreeMammals/Ocelot) Linux & OSX (Travis) -[![Build Status](https://dev.azure.com/ThreeMammals/Ocelot/_apis/build/status/Ocelot%20-%20Windows?branchName=develop)](https://dev.azure.com/ThreeMammals/Ocelot/_build/latest?definitionId=4&branchName=develop) Windows - -[![Build Status](https://dev.azure.com/ThreeMammals/Ocelot/_apis/build/status/Ocelot%20-%20MacOS?branchName=develop)](https://dev.azure.com/ThreeMammals/Ocelot/_build/latest?definitionId=3&branchName=develop) MacOS - -[![Build Status](https://dev.azure.com/ThreeMammals/Ocelot/_apis/build/status/Ocelot%20-%20Linux?branchName=develop)](https://dev.azure.com/ThreeMammals/Ocelot/_build/latest?definitionId=2&branchName=develop) Linux - [![Windows Build history](https://buildstats.info/appveyor/chart/TomPallister/ocelot-fcfpb?branch=develop&includeBuildsFromPullRequest=false)](https://ci.appveyor.com/project/TomPallister/ocelot-fcfpb/history?branch=develop) [![Coverage Status](https://coveralls.io/repos/github/ThreeMammals/Ocelot/badge.svg?branch=develop)](https://coveralls.io/github/ThreeMammals/Ocelot?branch=develop) From 41519b1e5649f1ae85be81353f9f8b7c0aef85b7 Mon Sep 17 00:00:00 2001 From: Vladimir Rets Date: Tue, 5 Nov 2019 17:41:52 +0200 Subject: [PATCH 7/7] Update README.md (#1043) --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4f0cf5c2..6d26bee4 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[](http://threemammals.com/ocelot) +[](https://threemammals.com/ocelot) [![Build status](https://ci.appveyor.com/api/projects/status/r6sv51qx36sis1je?branch=develop&svg=true)](https://ci.appveyor.com/project/TomPallister/ocelot-fcfpb) Windows (AppVeyor) @@ -37,7 +37,7 @@ is returned to the client. That is basically it with a bunch of other features! ## Features -A quick list of Ocelot's capabilities for more information see the [documentation](http://ocelot.readthedocs.io/en/latest/). +A quick list of Ocelot's capabilities for more information see the [documentation](https://ocelot.readthedocs.io/en/latest/). * Routing * Request Aggregation @@ -73,7 +73,7 @@ All versions can be found [here](https://www.nuget.org/packages/Ocelot/) ## Documentation -Please click [here](http://ocelot.readthedocs.io/en/latest/) for the Ocelot documentation. This includes lots of information and will be helpful if you want to understand the features Ocelot currently offers. +Please click [here](https://ocelot.readthedocs.io/en/latest/) for the Ocelot documentation. This includes lots of information and will be helpful if you want to understand the features Ocelot currently offers. ## Coming up