docker build works on windows

This commit is contained in:
TomPallister 2020-01-16 21:37:51 -08:00
parent 0e6f44b342
commit 92ce87c61c
26 changed files with 4240 additions and 4240 deletions

View File

@ -1,30 +1,30 @@
version: 2.1 version: 2.1
jobs: jobs:
build: build:
docker: docker:
- image: mijitt0m/ocelot-build:0.0.1 - image: mijitt0m/ocelot-build:0.0.1
steps: steps:
- checkout - checkout
- run: make build - run: make build
release: release:
docker: docker:
- image: mijitt0m/ocelot-build:0.0.1 - image: mijitt0m/ocelot-build:0.0.1
steps: steps:
- checkout - checkout
- run: make release - run: make release
workflows: workflows:
version: 2 version: 2
master: master:
jobs: jobs:
- release: - release:
filters: filters:
branches: branches:
only: master only: master
pr: pr:
jobs: jobs:
- build: - build:
filters: filters:
branches: branches:
ignore: ignore:
- master - master
- develop - develop

View File

@ -1,46 +1,46 @@
# Contributor Covenant Code of Conduct # Contributor Covenant Code of Conduct
## Our Pledge ## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards ## Our Standards
Examples of behavior that contributes to creating a positive environment include: Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language * Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences * Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism * Gracefully accepting constructive criticism
* Focusing on what is best for the community * Focusing on what is best for the community
* Showing empathy towards other community members * Showing empathy towards other community members
Examples of unacceptable behavior by participants include: Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances * The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks * Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment * Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission * Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting * Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities ## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope ## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement ## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at tom@threemammals.co.uk. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at tom@threemammals.co.uk. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution ## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org [homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/ [version]: http://contributor-covenant.org/version/1/4/

View File

@ -1,9 +1,9 @@
We love to receive contributions from the community so please keep them coming :) We love to receive contributions from the community so please keep them coming :)
Pull requests, issues and commentary welcome! Pull requests, issues and commentary welcome!
Please complete the relevant template for issues and PRs. Sometimes it's worth getting in touch with us to discuss changes Please complete the relevant template for issues and PRs. Sometimes it's worth getting in touch with us to discuss changes
before doing any work incase this is something we are already doing or it might not make sense. We can also give before doing any work incase this is something we are already doing or it might not make sense. We can also give
advice on the easiest way to do things :) advice on the easiest way to do things :)
Finally we mark all existing issues as help wanted, small, medium and large effort. If you want to contribute for the first time I suggest looking at a help wanted & small effort issue :) Finally we mark all existing issues as help wanted, small, medium and large effort. If you want to contribute for the first time I suggest looking at a help wanted & small effort issue :)

View File

@ -1,17 +1,17 @@
## Expected Behavior / New Feature ## Expected Behavior / New Feature
## Actual Behavior / Motivation for New Feature ## Actual Behavior / Motivation for New Feature
## Steps to Reproduce the Problem ## Steps to Reproduce the Problem
1. 1.
1. 1.
1. 1.
## Specifications ## Specifications
- Version: - Version:
- Platform: - Platform:
- Subsystem: - Subsystem:

View File

@ -1,7 +1,7 @@
Fixes / New Feature # Fixes / New Feature #
## Proposed Changes ## Proposed Changes
- -
- -
- -

196
README.md
View File

@ -1,98 +1,98 @@
[<img src="https://threemammals.com/ocelot_logo.png">](https://threemammals.com/ocelot) [<img src="https://threemammals.com/ocelot_logo.png">](https://threemammals.com/ocelot)
[![CircleCI](https://circleci.com/gh/ThreeMammals/Ocelot.svg?style=svg)](https://circleci.com/gh/ThreeMammals/Ocelot) [![CircleCI](https://circleci.com/gh/ThreeMammals/Ocelot.svg?style=svg)](https://circleci.com/gh/ThreeMammals/Ocelot)
[![Coverage Status](https://coveralls.io/repos/github/ThreeMammals/Ocelot/badge.svg?branch=master)](https://coveralls.io/github/ThreeMammals/Ocelot?branch=master) [![Coverage Status](https://coveralls.io/repos/github/ThreeMammals/Ocelot/badge.svg?branch=master)](https://coveralls.io/github/ThreeMammals/Ocelot?branch=master)
# Ocelot # Ocelot
Ocelot is a .NET API Gateway. This project is aimed at people using .NET running Ocelot is a .NET API Gateway. This project is aimed at people using .NET running
a micro services / service oriented architecture a micro services / service oriented architecture
that need a unified point of entry into their system. However it will work with anything that speaks HTTP and run on any platform that ASP.NET Core supports. that need a unified point of entry into their system. However it will work with anything that speaks HTTP and run on any platform that ASP.NET Core supports.
In particular I want easy integration with In particular I want easy integration with
IdentityServer reference and bearer tokens. IdentityServer reference and bearer tokens.
We have been unable to find this in my current workplace We have been unable to find this in my current workplace
without having to write our own Javascript middlewares without having to write our own Javascript middlewares
to handle the IdentityServer reference tokens. We would to handle the IdentityServer reference tokens. We would
rather use the IdentityServer code that already exists rather use the IdentityServer code that already exists
to do this. to do this.
Ocelot is a bunch of middlewares in a specific order. Ocelot is a bunch of middlewares in a specific order.
Ocelot manipulates the HttpRequest object into a state specified by its configuration until 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 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 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 last thing in the Ocelot pipeline. It does not call the next middleware.
The response from the downstream service is retrieved as the requests goes back up the Ocelot pipeline. The response from the downstream service is retrieved as the requests goes back up the Ocelot pipeline.
There is a piece of middleware that maps the HttpResponseMessage onto the HttpResponse object and that 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! is returned to the client. That is basically it with a bunch of other features!
## Features ## Features
A quick list of Ocelot's capabilities for more information see the [documentation](https://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 * Routing
* Request Aggregation * Request Aggregation
* Service Discovery with Consul & Eureka * Service Discovery with Consul & Eureka
* Service Fabric * Service Fabric
* Kubernetes * Kubernetes
* WebSockets * WebSockets
* Authentication * Authentication
* Authorisation * Authorisation
* Rate Limiting * Rate Limiting
* Caching * Caching
* Retry policies / QoS * Retry policies / QoS
* Load Balancing * Load Balancing
* Logging / Tracing / Correlation * Logging / Tracing / Correlation
* Headers / Query String / Claims Transformation * Headers / Query String / Claims Transformation
* Custom Middleware / Delegating Handlers * Custom Middleware / Delegating Handlers
* Configuration / Administration REST API * Configuration / Administration REST API
* Platform / Cloud Agnostic * Platform / Cloud Agnostic
## How to install ## How to install
Ocelot is designed to work with ASP.NET Core only and it targets `netstandard2.0`. This means it can be used anywhere `.NET Standard 2.0` is supported, including `.NET Core 2.1` and `.NET Framework 4.7.2` and up. [This](https://docs.microsoft.com/en-us/dotnet/standard/net-standard) documentation may prove helpful when working out if Ocelot would be suitable for you. Ocelot is designed to work with ASP.NET Core only and it targets `netstandard2.0`. This means it can be used anywhere `.NET Standard 2.0` is supported, including `.NET Core 2.1` and `.NET Framework 4.7.2` and up. [This](https://docs.microsoft.com/en-us/dotnet/standard/net-standard) documentation may prove helpful when working out if Ocelot would be suitable for you.
Install Ocelot and it's dependencies using NuGet. Install Ocelot and it's dependencies using NuGet.
`Install-Package Ocelot` `Install-Package Ocelot`
Or via the .NET Core CLI: Or via the .NET Core CLI:
`dotnet add package ocelot` `dotnet add package ocelot`
All versions can be found [here](https://www.nuget.org/packages/Ocelot/) All versions can be found [here](https://www.nuget.org/packages/Ocelot/)
## Documentation ## Documentation
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. 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 ## Coming up
You can see what we are working on [here](https://github.com/ThreeMammals/Ocelot/issues). You can see what we are working on [here](https://github.com/ThreeMammals/Ocelot/issues).
## Contributing ## Contributing
We love to receive contributions from the community so please keep them coming :) We love to receive contributions from the community so please keep them coming :)
Pull requests, issues and commentary welcome! Pull requests, issues and commentary welcome!
Please complete the relevant template for issues and PRs. Sometimes it's worth getting in touch with us to discuss changes Please complete the relevant template for issues and PRs. Sometimes it's worth getting in touch with us to discuss changes
before doing any work incase this is something we are already doing or it might not make sense. We can also give before doing any work incase this is something we are already doing or it might not make sense. We can also give
advice on the easiest way to do things :) advice on the easiest way to do things :)
Finally we mark all existing issues as help wanted, small, medium and large effort. If you want to contribute for the first time I suggest looking at a help wanted & small effort issue :) Finally we mark all existing issues as help wanted, small, medium and large effort. If you want to contribute for the first time I suggest looking at a help wanted & small effort issue :)
## Donate ## Donate
If you think this project is worth supporting financially please make a contribution using the button below! If you think this project is worth supporting financially please make a contribution using the button below!
[![Support via PayPal](https://cdn.rawgit.com/twolfson/paypal-github-button/1.0.0/dist/button.svg)](https://www.paypal.me/ThreeMammals/) [![Support via PayPal](https://cdn.rawgit.com/twolfson/paypal-github-button/1.0.0/dist/button.svg)](https://www.paypal.me/ThreeMammals/)
## Things that are currently annoying me ## Things that are currently annoying me
[![](https://codescene.io/projects/697/status.svg) Get more details at **codescene.io**.](https://codescene.io/projects/697/jobs/latest-successful/results) [![](https://codescene.io/projects/697/status.svg) Get more details at **codescene.io**.](https://codescene.io/projects/697/jobs/latest-successful/results)

View File

@ -1,497 +1,497 @@
#tool "nuget:?package=GitVersion.CommandLine&version=5.0.1" #tool "nuget:?package=GitVersion.CommandLine&version=5.0.1"
#tool "nuget:?package=GitReleaseNotes" #tool "nuget:?package=GitReleaseNotes"
#addin nuget:?package=Cake.Json #addin nuget:?package=Cake.Json
#addin nuget:?package=Newtonsoft.Json #addin nuget:?package=Newtonsoft.Json
#addin nuget:?package=System.Net.Http #addin nuget:?package=System.Net.Http
#tool "nuget:?package=ReportGenerator" #tool "nuget:?package=ReportGenerator"
#tool "nuget:?package=coveralls.net&version=0.7.0" #tool "nuget:?package=coveralls.net&version=0.7.0"
#addin Cake.Coveralls&version=0.10.1 #addin Cake.Coveralls&version=0.10.1
// compile // compile
var compileConfig = Argument("configuration", "Release"); var compileConfig = Argument("configuration", "Release");
var slnFile = "./Ocelot.sln"; var slnFile = "./Ocelot.sln";
// build artifacts // build artifacts
var artifactsDir = Directory("artifacts"); var artifactsDir = Directory("artifacts");
// unit testing // unit testing
var artifactsForUnitTestsDir = artifactsDir + Directory("UnitTests"); var artifactsForUnitTestsDir = artifactsDir + Directory("UnitTests");
var unitTestAssemblies = @"./test/Ocelot.UnitTests/Ocelot.UnitTests.csproj"; var unitTestAssemblies = @"./test/Ocelot.UnitTests/Ocelot.UnitTests.csproj";
var minCodeCoverage = 80d; var minCodeCoverage = 80d;
var coverallsRepoToken = "coveralls-repo-token-ocelot"; var coverallsRepoToken = "coveralls-repo-token-ocelot";
var coverallsRepo = "https://coveralls.io/github/TomPallister/Ocelot"; var coverallsRepo = "https://coveralls.io/github/TomPallister/Ocelot";
// acceptance testing // acceptance testing
var artifactsForAcceptanceTestsDir = artifactsDir + Directory("AcceptanceTests"); var artifactsForAcceptanceTestsDir = artifactsDir + Directory("AcceptanceTests");
var acceptanceTestAssemblies = @"./test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj"; var acceptanceTestAssemblies = @"./test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj";
// integration testing // integration testing
var artifactsForIntegrationTestsDir = artifactsDir + Directory("IntegrationTests"); var artifactsForIntegrationTestsDir = artifactsDir + Directory("IntegrationTests");
var integrationTestAssemblies = @"./test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj"; var integrationTestAssemblies = @"./test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj";
// benchmark testing // benchmark testing
var artifactsForBenchmarkTestsDir = artifactsDir + Directory("BenchmarkTests"); var artifactsForBenchmarkTestsDir = artifactsDir + Directory("BenchmarkTests");
var benchmarkTestAssemblies = @"./test/Ocelot.Benchmarks"; var benchmarkTestAssemblies = @"./test/Ocelot.Benchmarks";
// packaging // packaging
var packagesDir = artifactsDir + Directory("Packages"); var packagesDir = artifactsDir + Directory("Packages");
var releaseNotesFile = packagesDir + File("releasenotes.md"); var releaseNotesFile = packagesDir + File("releasenotes.md");
var artifactsFile = packagesDir + File("artifacts.txt"); var artifactsFile = packagesDir + File("artifacts.txt");
// stable releases // stable releases
var tagsUrl = "https://api.github.com/repos/tompallister/ocelot/releases/tags/"; var tagsUrl = "https://api.github.com/repos/tompallister/ocelot/releases/tags/";
var nugetFeedStableKey = EnvironmentVariable("OCELOT_NUTGET_API_KEY"); var nugetFeedStableKey = EnvironmentVariable("OCELOT_NUTGET_API_KEY");
var nugetFeedStableUploadUrl = "https://www.nuget.org/api/v2/package"; var nugetFeedStableUploadUrl = "https://www.nuget.org/api/v2/package";
var nugetFeedStableSymbolsUploadUrl = "https://www.nuget.org/api/v2/package"; var nugetFeedStableSymbolsUploadUrl = "https://www.nuget.org/api/v2/package";
// internal build variables - don't change these. // internal build variables - don't change these.
string committedVersion = "0.0.0-dev"; string committedVersion = "0.0.0-dev";
GitVersion versioning = null; GitVersion versioning = null;
int releaseId = 0; int releaseId = 0;
string gitHubUsername = "TomPallister"; string gitHubUsername = "TomPallister";
string gitHubPassword = Environment.GetEnvironmentVariable("OCELOT_GITHUB_API_KEY"); string gitHubPassword = Environment.GetEnvironmentVariable("OCELOT_GITHUB_API_KEY");
var target = Argument("target", "Default"); var target = Argument("target", "Default");
Information("target is " + target); Information("target is " + target);
Information("Build configuration is " + compileConfig); Information("Build configuration is " + compileConfig);
Task("Default") Task("Default")
.IsDependentOn("Build"); .IsDependentOn("Build");
Task("Build") Task("Build")
.IsDependentOn("RunTests"); .IsDependentOn("RunTests");
Task("RunTests") Task("RunTests")
.IsDependentOn("RunUnitTests") .IsDependentOn("RunUnitTests")
.IsDependentOn("RunAcceptanceTests") .IsDependentOn("RunAcceptanceTests")
.IsDependentOn("RunIntegrationTests"); .IsDependentOn("RunIntegrationTests");
Task("Release") Task("Release")
.IsDependentOn("Build") .IsDependentOn("Build")
.IsDependentOn("CreateArtifacts") .IsDependentOn("CreateArtifacts")
.IsDependentOn("PublishGitHubRelease") .IsDependentOn("PublishGitHubRelease")
.IsDependentOn("PublishToNuget"); .IsDependentOn("PublishToNuget");
Task("Compile") Task("Compile")
.IsDependentOn("Clean") .IsDependentOn("Clean")
.IsDependentOn("Version") .IsDependentOn("Version")
.Does(() => .Does(() =>
{ {
var settings = new DotNetCoreBuildSettings var settings = new DotNetCoreBuildSettings
{ {
Configuration = compileConfig, Configuration = compileConfig,
}; };
DotNetCoreBuild(slnFile, settings); DotNetCoreBuild(slnFile, settings);
}); });
Task("Clean") Task("Clean")
.Does(() => .Does(() =>
{ {
if (DirectoryExists(artifactsDir)) if (DirectoryExists(artifactsDir))
{ {
DeleteDirectory(artifactsDir, recursive:true); DeleteDirectory(artifactsDir, recursive:true);
} }
CreateDirectory(artifactsDir); CreateDirectory(artifactsDir);
}); });
Task("Version") Task("Version")
.Does(() => .Does(() =>
{ {
versioning = GetNuGetVersionForCommit(); versioning = GetNuGetVersionForCommit();
var nugetVersion = versioning.NuGetVersion; var nugetVersion = versioning.NuGetVersion;
Information("SemVer version number: " + nugetVersion); Information("SemVer version number: " + nugetVersion);
if (IsRunningOnCircleCI()) if (IsRunningOnCircleCI())
{ {
Information("Persisting version number..."); Information("Persisting version number...");
PersistVersion(committedVersion, nugetVersion); PersistVersion(committedVersion, nugetVersion);
} }
else else
{ {
Information("We are not running on build server, so we won't persist the version number."); Information("We are not running on build server, so we won't persist the version number.");
} }
}); });
Task("RunUnitTests") Task("RunUnitTests")
.IsDependentOn("Compile") .IsDependentOn("Compile")
.Does(() => .Does(() =>
{ {
var testSettings = new DotNetCoreTestSettings var testSettings = new DotNetCoreTestSettings
{ {
Configuration = compileConfig, Configuration = compileConfig,
ResultsDirectory = artifactsForUnitTestsDir, ResultsDirectory = artifactsForUnitTestsDir,
ArgumentCustomization = args => args ArgumentCustomization = args => args
.Append("--settings test/Ocelot.UnitTests/UnitTests.runsettings") .Append("--settings test/Ocelot.UnitTests/UnitTests.runsettings")
}; };
EnsureDirectoryExists(artifactsForUnitTestsDir); EnsureDirectoryExists(artifactsForUnitTestsDir);
DotNetCoreTest(unitTestAssemblies, testSettings); DotNetCoreTest(unitTestAssemblies, testSettings);
if (IsRunningOnWindows()) if (IsRunningOnWindows())
{ {
var coverageSummaryFile = GetSubDirectories(artifactsForUnitTestsDir).First().CombineWithFilePath(File("coverage.opencover.xml")); var coverageSummaryFile = GetSubDirectories(artifactsForUnitTestsDir).First().CombineWithFilePath(File("coverage.opencover.xml"));
ReportGenerator(coverageSummaryFile, artifactsForUnitTestsDir); ReportGenerator(coverageSummaryFile, artifactsForUnitTestsDir);
if (IsRunningOnCircleCI()) if (IsRunningOnCircleCI())
{ {
var repoToken = EnvironmentVariable(coverallsRepoToken); var repoToken = EnvironmentVariable(coverallsRepoToken);
if (string.IsNullOrEmpty(repoToken)) if (string.IsNullOrEmpty(repoToken))
{ {
throw new Exception(string.Format("Coveralls repo token not found. Set environment variable '{0}'", coverallsRepoToken)); throw new Exception(string.Format("Coveralls repo token not found. Set environment variable '{0}'", coverallsRepoToken));
} }
Information(string.Format("Uploading test coverage to {0}", coverallsRepo)); Information(string.Format("Uploading test coverage to {0}", coverallsRepo));
CoverallsNet(coverageSummaryFile, CoverallsNetReportType.OpenCover, new CoverallsNetSettings() CoverallsNet(coverageSummaryFile, CoverallsNetReportType.OpenCover, new CoverallsNetSettings()
{ {
RepoToken = repoToken RepoToken = repoToken
}); });
} }
else else
{ {
Information("We are not running on the build server so we won't publish the coverage report to coveralls.io"); Information("We are not running on the build server so we won't publish the coverage report to coveralls.io");
} }
var sequenceCoverage = XmlPeek(coverageSummaryFile, "//CoverageSession/Summary/@sequenceCoverage"); var sequenceCoverage = XmlPeek(coverageSummaryFile, "//CoverageSession/Summary/@sequenceCoverage");
var branchCoverage = XmlPeek(coverageSummaryFile, "//CoverageSession/Summary/@branchCoverage"); var branchCoverage = XmlPeek(coverageSummaryFile, "//CoverageSession/Summary/@branchCoverage");
Information("Sequence Coverage: " + sequenceCoverage); Information("Sequence Coverage: " + sequenceCoverage);
if(double.Parse(sequenceCoverage) < minCodeCoverage) if(double.Parse(sequenceCoverage) < minCodeCoverage)
{ {
var whereToCheck = !IsRunningOnCircleCI() ? coverallsRepo : artifactsForUnitTestsDir; var whereToCheck = !IsRunningOnCircleCI() ? 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)); throw new Exception(string.Format("Code coverage fell below the threshold of {0}%. You can find the code coverage report at {1}", minCodeCoverage, whereToCheck));
}; };
} }
}); });
Task("RunAcceptanceTests") Task("RunAcceptanceTests")
.IsDependentOn("Compile") .IsDependentOn("Compile")
.Does(() => .Does(() =>
{ {
var settings = new DotNetCoreTestSettings var settings = new DotNetCoreTestSettings
{ {
Configuration = compileConfig, Configuration = compileConfig,
ArgumentCustomization = args => args ArgumentCustomization = args => args
.Append("--no-restore") .Append("--no-restore")
.Append("--no-build") .Append("--no-build")
}; };
EnsureDirectoryExists(artifactsForAcceptanceTestsDir); EnsureDirectoryExists(artifactsForAcceptanceTestsDir);
DotNetCoreTest(acceptanceTestAssemblies, settings); DotNetCoreTest(acceptanceTestAssemblies, settings);
}); });
Task("RunIntegrationTests") Task("RunIntegrationTests")
.IsDependentOn("Compile") .IsDependentOn("Compile")
.Does(() => .Does(() =>
{ {
var settings = new DotNetCoreTestSettings var settings = new DotNetCoreTestSettings
{ {
Configuration = compileConfig, Configuration = compileConfig,
ArgumentCustomization = args => args ArgumentCustomization = args => args
.Append("--no-restore") .Append("--no-restore")
.Append("--no-build") .Append("--no-build")
}; };
EnsureDirectoryExists(artifactsForIntegrationTestsDir); EnsureDirectoryExists(artifactsForIntegrationTestsDir);
DotNetCoreTest(integrationTestAssemblies, settings); DotNetCoreTest(integrationTestAssemblies, settings);
}); });
Task("CreateArtifacts") Task("CreateArtifacts")
.IsDependentOn("Compile") .IsDependentOn("Compile")
.Does(() => .Does(() =>
{ {
EnsureDirectoryExists(packagesDir); EnsureDirectoryExists(packagesDir);
CopyFiles("./src/**/Release/Ocelot.*.nupkg", packagesDir); CopyFiles("./src/**/Release/Ocelot.*.nupkg", packagesDir);
// todo fix this for docker build // todo fix this for docker build
//GenerateReleaseNotes(releaseNotesFile); //GenerateReleaseNotes(releaseNotesFile);
var projectFiles = GetFiles("./src/**/Release/Ocelot.*.nupkg"); var projectFiles = GetFiles("./src/**/Release/Ocelot.*.nupkg");
foreach(var projectFile in projectFiles) foreach(var projectFile in projectFiles)
{ {
System.IO.File.AppendAllLines(artifactsFile, new[]{ System.IO.File.AppendAllLines(artifactsFile, new[]{
projectFile.GetFilename().FullPath, projectFile.GetFilename().FullPath,
// todo fix this for docker build // todo fix this for docker build
//"releaseNotes:releasenotes.md" //"releaseNotes:releasenotes.md"
}); });
} }
var artifacts = System.IO.File var artifacts = System.IO.File
.ReadAllLines(artifactsFile) .ReadAllLines(artifactsFile)
.Distinct(); .Distinct();
foreach(var artifact in artifacts) foreach(var artifact in artifacts)
{ {
var codePackage = packagesDir + File(artifact); var codePackage = packagesDir + File(artifact);
Information("Created package " + codePackage); Information("Created package " + codePackage);
} }
}); });
Task("PublishGitHubRelease") Task("PublishGitHubRelease")
.IsDependentOn("CreateArtifacts") .IsDependentOn("CreateArtifacts")
.Does(() => .Does(() =>
{ {
if (IsRunningOnCircleCI()) if (IsRunningOnCircleCI())
{ {
var path = packagesDir.ToString() + @"/**/*"; var path = packagesDir.ToString() + @"/**/*";
CreateGitHubRelease(); CreateGitHubRelease();
foreach (var file in GetFiles(path)) foreach (var file in GetFiles(path))
{ {
UploadFileToGitHubRelease(file); UploadFileToGitHubRelease(file);
} }
CompleteGitHubRelease(); CompleteGitHubRelease();
} }
}); });
Task("EnsureStableReleaseRequirements") Task("EnsureStableReleaseRequirements")
.Does(() => .Does(() =>
{ {
Information("Check if stable release..."); Information("Check if stable release...");
if (!IsRunningOnCircleCI()) if (!IsRunningOnCircleCI())
{ {
throw new Exception("Stable release should happen via circleci"); throw new Exception("Stable release should happen via circleci");
} }
Information("Release is stable..."); Information("Release is stable...");
}); });
Task("DownloadGitHubReleaseArtifacts") Task("DownloadGitHubReleaseArtifacts")
.Does(() => .Does(() =>
{ {
try try
{ {
EnsureDirectoryExists(packagesDir); EnsureDirectoryExists(packagesDir);
var releaseUrl = tagsUrl + versioning.NuGetVersion; var releaseUrl = tagsUrl + versioning.NuGetVersion;
var assets_url = Newtonsoft.Json.Linq.JObject.Parse(GetResource(releaseUrl)) var assets_url = Newtonsoft.Json.Linq.JObject.Parse(GetResource(releaseUrl))
.Value<string>("assets_url"); .Value<string>("assets_url");
var assets = GetResource(assets_url); var assets = GetResource(assets_url);
foreach(var asset in Newtonsoft.Json.JsonConvert.DeserializeObject<Newtonsoft.Json.Linq.JArray>(assets)) foreach(var asset in Newtonsoft.Json.JsonConvert.DeserializeObject<Newtonsoft.Json.Linq.JArray>(assets))
{ {
var file = packagesDir + File(asset.Value<string>("name")); var file = packagesDir + File(asset.Value<string>("name"));
DownloadFile(asset.Value<string>("browser_download_url"), file); DownloadFile(asset.Value<string>("browser_download_url"), file);
} }
} }
catch(Exception exception) catch(Exception exception)
{ {
Information("There was an exception " + exception); Information("There was an exception " + exception);
throw; throw;
} }
}); });
Task("PublishToNuget") Task("PublishToNuget")
.IsDependentOn("DownloadGitHubReleaseArtifacts") .IsDependentOn("DownloadGitHubReleaseArtifacts")
.Does(() => .Does(() =>
{ {
if (IsRunningOnCircleCI()) if (IsRunningOnCircleCI())
{ {
PublishPackages(packagesDir, artifactsFile, nugetFeedStableKey, nugetFeedStableUploadUrl, nugetFeedStableSymbolsUploadUrl); PublishPackages(packagesDir, artifactsFile, nugetFeedStableKey, nugetFeedStableUploadUrl, nugetFeedStableSymbolsUploadUrl);
} }
}); });
RunTarget(target); RunTarget(target);
/// Gets nuique nuget version for this commit /// Gets nuique nuget version for this commit
private GitVersion GetNuGetVersionForCommit() private GitVersion GetNuGetVersionForCommit()
{ {
GitVersion(new GitVersionSettings{ GitVersion(new GitVersionSettings{
UpdateAssemblyInfo = false, UpdateAssemblyInfo = false,
OutputType = GitVersionOutput.BuildServer OutputType = GitVersionOutput.BuildServer
}); });
return GitVersion(new GitVersionSettings{ OutputType = GitVersionOutput.Json }); return GitVersion(new GitVersionSettings{ OutputType = GitVersionOutput.Json });
} }
/// Updates project version in all of our projects /// Updates project version in all of our projects
private void PersistVersion(string committedVersion, string newVersion) private void PersistVersion(string committedVersion, string newVersion)
{ {
Information(string.Format("We'll search all csproj files for {0} and replace with {1}...", committedVersion, newVersion)); Information(string.Format("We'll search all csproj files for {0} and replace with {1}...", committedVersion, newVersion));
var projectFiles = GetFiles("./**/*.csproj"); var projectFiles = GetFiles("./**/*.csproj");
foreach(var projectFile in projectFiles) foreach(var projectFile in projectFiles)
{ {
var file = projectFile.ToString(); var file = projectFile.ToString();
Information(string.Format("Updating {0}...", file)); Information(string.Format("Updating {0}...", file));
var updatedProjectFile = System.IO.File.ReadAllText(file) var updatedProjectFile = System.IO.File.ReadAllText(file)
.Replace(committedVersion, newVersion); .Replace(committedVersion, newVersion);
System.IO.File.WriteAllText(file, updatedProjectFile); System.IO.File.WriteAllText(file, updatedProjectFile);
} }
} }
/// generates release notes based on issues closed in GitHub since the last release /// generates release notes based on issues closed in GitHub since the last release
private void GenerateReleaseNotes(ConvertableFilePath file) private void GenerateReleaseNotes(ConvertableFilePath file)
{ {
if(!IsRunningOnWindows()) if(!IsRunningOnWindows())
{ {
Warning("We are not running on Windows so we cannot generate release notes."); Warning("We are not running on Windows so we cannot generate release notes.");
return; return;
} }
Information("Generating release notes at " + file); Information("Generating release notes at " + file);
var releaseNotesExitCode = StartProcess( var releaseNotesExitCode = StartProcess(
@"tools/GitReleaseNotes/tools/gitreleasenotes.exe", @"tools/GitReleaseNotes/tools/gitreleasenotes.exe",
new ProcessSettings { Arguments = ". /o " + file }); new ProcessSettings { Arguments = ". /o " + file });
if (string.IsNullOrEmpty(System.IO.File.ReadAllText(file))) if (string.IsNullOrEmpty(System.IO.File.ReadAllText(file)))
{ {
System.IO.File.WriteAllText(file, "No issues closed since last release"); System.IO.File.WriteAllText(file, "No issues closed since last release");
} }
if (releaseNotesExitCode != 0) if (releaseNotesExitCode != 0)
{ {
throw new Exception("Failed to generate release notes"); throw new Exception("Failed to generate release notes");
} }
} }
/// Publishes code and symbols packages to nuget feed, based on contents of artifacts file /// Publishes code and symbols packages to nuget feed, based on contents of artifacts file
private void PublishPackages(ConvertableDirectoryPath packagesDir, ConvertableFilePath artifactsFile, string feedApiKey, string codeFeedUrl, string symbolFeedUrl) private void PublishPackages(ConvertableDirectoryPath packagesDir, ConvertableFilePath artifactsFile, string feedApiKey, string codeFeedUrl, string symbolFeedUrl)
{ {
Information("PublishPackages"); Information("PublishPackages");
var artifacts = System.IO.File var artifacts = System.IO.File
.ReadAllLines(artifactsFile) .ReadAllLines(artifactsFile)
.Distinct(); .Distinct();
foreach(var artifact in artifacts) foreach(var artifact in artifacts)
{ {
var codePackage = packagesDir + File(artifact); var codePackage = packagesDir + File(artifact);
Information("Pushing package " + codePackage); Information("Pushing package " + codePackage);
Information("Calling NuGetPush"); Information("Calling NuGetPush");
NuGetPush( NuGetPush(
codePackage, codePackage,
new NuGetPushSettings { new NuGetPushSettings {
ApiKey = feedApiKey, ApiKey = feedApiKey,
Source = codeFeedUrl Source = codeFeedUrl
}); });
} }
} }
private void CreateGitHubRelease() private void CreateGitHubRelease()
{ {
var json = $"{{ \"tag_name\": \"{versioning.NuGetVersion}\", \"target_commitish\": \"master\", \"name\": \"{versioning.NuGetVersion}\", \"body\": \"todo: notes coming\", \"draft\": true, \"prerelease\": true }}"; var json = $"{{ \"tag_name\": \"{versioning.NuGetVersion}\", \"target_commitish\": \"master\", \"name\": \"{versioning.NuGetVersion}\", \"body\": \"todo: notes coming\", \"draft\": true, \"prerelease\": true }}";
var content = new System.Net.Http.StringContent(json, System.Text.Encoding.UTF8, "application/json"); var content = new System.Net.Http.StringContent(json, System.Text.Encoding.UTF8, "application/json");
using(var client = new System.Net.Http.HttpClient()) using(var client = new System.Net.Http.HttpClient())
{ {
client.DefaultRequestHeaders.Authorization = client.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue( new System.Net.Http.Headers.AuthenticationHeaderValue(
"Basic", Convert.ToBase64String( "Basic", Convert.ToBase64String(
System.Text.ASCIIEncoding.ASCII.GetBytes( System.Text.ASCIIEncoding.ASCII.GetBytes(
$"{gitHubUsername}:{gitHubPassword}"))); $"{gitHubUsername}:{gitHubPassword}")));
client.DefaultRequestHeaders.Add("User-Agent", "Ocelot Release"); client.DefaultRequestHeaders.Add("User-Agent", "Ocelot Release");
var result = client.PostAsync("https://api.github.com/repos/ThreeMammals/Ocelot/releases", content).Result; var result = client.PostAsync("https://api.github.com/repos/ThreeMammals/Ocelot/releases", content).Result;
if(result.StatusCode != System.Net.HttpStatusCode.Created) if(result.StatusCode != System.Net.HttpStatusCode.Created)
{ {
throw new Exception("CreateGitHubRelease result.StatusCode = " + result.StatusCode); throw new Exception("CreateGitHubRelease result.StatusCode = " + result.StatusCode);
} }
var returnValue = result.Content.ReadAsStringAsync().Result; var returnValue = result.Content.ReadAsStringAsync().Result;
dynamic test = Newtonsoft.Json.JsonConvert.DeserializeObject<Newtonsoft.Json.Linq.JObject>(returnValue); dynamic test = Newtonsoft.Json.JsonConvert.DeserializeObject<Newtonsoft.Json.Linq.JObject>(returnValue);
releaseId = test.id; releaseId = test.id;
} }
} }
private void UploadFileToGitHubRelease(FilePath file) private void UploadFileToGitHubRelease(FilePath file)
{ {
var data = System.IO.File.ReadAllBytes(file.FullPath); var data = System.IO.File.ReadAllBytes(file.FullPath);
var content = new System.Net.Http.ByteArrayContent(data); var content = new System.Net.Http.ByteArrayContent(data);
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream"); content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream");
using(var client = new System.Net.Http.HttpClient()) using(var client = new System.Net.Http.HttpClient())
{ {
client.DefaultRequestHeaders.Authorization = client.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue( new System.Net.Http.Headers.AuthenticationHeaderValue(
"Basic", Convert.ToBase64String( "Basic", Convert.ToBase64String(
System.Text.ASCIIEncoding.ASCII.GetBytes( System.Text.ASCIIEncoding.ASCII.GetBytes(
$"{gitHubUsername}:{gitHubPassword}"))); $"{gitHubUsername}:{gitHubPassword}")));
client.DefaultRequestHeaders.Add("User-Agent", "Ocelot Release"); client.DefaultRequestHeaders.Add("User-Agent", "Ocelot Release");
var result = client.PostAsync($"https://uploads.github.com/repos/ThreeMammals/Ocelot/releases/{releaseId}/assets?name={file.GetFilename()}", content).Result; var result = client.PostAsync($"https://uploads.github.com/repos/ThreeMammals/Ocelot/releases/{releaseId}/assets?name={file.GetFilename()}", content).Result;
if(result.StatusCode != System.Net.HttpStatusCode.Created) if(result.StatusCode != System.Net.HttpStatusCode.Created)
{ {
throw new Exception("UploadFileToGitHubRelease result.StatusCode = " + result.StatusCode); throw new Exception("UploadFileToGitHubRelease result.StatusCode = " + result.StatusCode);
} }
} }
} }
private void CompleteGitHubRelease() private void CompleteGitHubRelease()
{ {
var json = $"{{ \"tag_name\": \"{versioning.NuGetVersion}\", \"target_commitish\": \"master\", \"name\": \"{versioning.NuGetVersion}\", \"body\": \"todo: notes coming\", \"draft\": false, \"prerelease\": false }}"; var json = $"{{ \"tag_name\": \"{versioning.NuGetVersion}\", \"target_commitish\": \"master\", \"name\": \"{versioning.NuGetVersion}\", \"body\": \"todo: notes coming\", \"draft\": false, \"prerelease\": false }}";
var content = new System.Net.Http.StringContent(json, System.Text.Encoding.UTF8, "application/json"); var content = new System.Net.Http.StringContent(json, System.Text.Encoding.UTF8, "application/json");
using(var client = new System.Net.Http.HttpClient()) using(var client = new System.Net.Http.HttpClient())
{ {
client.DefaultRequestHeaders.Authorization = client.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue( new System.Net.Http.Headers.AuthenticationHeaderValue(
"Basic", Convert.ToBase64String( "Basic", Convert.ToBase64String(
System.Text.ASCIIEncoding.ASCII.GetBytes( System.Text.ASCIIEncoding.ASCII.GetBytes(
$"{gitHubUsername}:{gitHubPassword}"))); $"{gitHubUsername}:{gitHubPassword}")));
client.DefaultRequestHeaders.Add("User-Agent", "Ocelot Release"); client.DefaultRequestHeaders.Add("User-Agent", "Ocelot Release");
var result = client.PatchAsync($"https://api.github.com/repos/ThreeMammals/Ocelot/releases/{releaseId}", content).Result; var result = client.PatchAsync($"https://api.github.com/repos/ThreeMammals/Ocelot/releases/{releaseId}", content).Result;
if(result.StatusCode != System.Net.HttpStatusCode.OK) if(result.StatusCode != System.Net.HttpStatusCode.OK)
{ {
throw new Exception("CompleteGitHubRelease result.StatusCode = " + result.StatusCode); throw new Exception("CompleteGitHubRelease result.StatusCode = " + result.StatusCode);
} }
} }
} }
/// gets the resource from the specified url /// gets the resource from the specified url
private string GetResource(string url) private string GetResource(string url)
{ {
try try
{ {
Information("Getting resource from " + url); Information("Getting resource from " + url);
var assetsRequest = System.Net.WebRequest.CreateHttp(url); var assetsRequest = System.Net.WebRequest.CreateHttp(url);
assetsRequest.Method = "GET"; assetsRequest.Method = "GET";
assetsRequest.Accept = "application/vnd.github.v3+json"; assetsRequest.Accept = "application/vnd.github.v3+json";
assetsRequest.UserAgent = "BuildScript"; assetsRequest.UserAgent = "BuildScript";
using (var assetsResponse = assetsRequest.GetResponse()) using (var assetsResponse = assetsRequest.GetResponse())
{ {
var assetsStream = assetsResponse.GetResponseStream(); var assetsStream = assetsResponse.GetResponseStream();
var assetsReader = new StreamReader(assetsStream); var assetsReader = new StreamReader(assetsStream);
var response = assetsReader.ReadToEnd(); var response = assetsReader.ReadToEnd();
Information("Response is " + response); Information("Response is " + response);
return response; return response;
} }
} }
catch(Exception exception) catch(Exception exception)
{ {
Information("There was an exception " + exception); Information("There was an exception " + exception);
throw; throw;
} }
} }
private bool IsRunningOnCircleCI() private bool IsRunningOnCircleCI()
{ {
return !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("CIRCLECI")); return !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("CIRCLECI"));
} }

View File

@ -1,9 +1,9 @@
# this is the dockerfile that create the ocelot build container # this is the dockerfile that create the ocelot build container
# build with the docker-build.sh file in this folder # build with the docker-build.sh file in this folder
FROM mcr.microsoft.com/dotnet/core/sdk:3.1-bionic AS build FROM mcr.microsoft.com/dotnet/core/sdk:3.1-bionic AS build
RUN apt install gnupg ca-certificates RUN apt install gnupg ca-certificates
RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF
RUN echo "deb https://download.mono-project.com/repo/ubuntu stable-bionic main" | tee /etc/apt/sources.list.d/mono-official-stable.list RUN echo "deb https://download.mono-project.com/repo/ubuntu stable-bionic main" | tee /etc/apt/sources.list.d/mono-official-stable.list
RUN apt update RUN apt update
RUN apt-get -y install mono-devel RUN apt-get -y install mono-devel

View File

@ -1,12 +1,12 @@
# call from ocelot repo root with # call from ocelot repo root with
# docker build -f ./docker/Dockerfile.build . # docker build -f ./docker/Dockerfile.build .
FROM mijitt0m/ocelot-build:0.0.1 FROM mijitt0m/ocelot-build:0.0.1
WORKDIR /src WORKDIR /src
COPY ./. . COPY ./. .
RUN chmod u+x build.sh RUN chmod u+x build.sh
RUN make build RUN make build

View File

@ -1,18 +1,18 @@
# call from ocelot repo root with # call from ocelot repo root with
# docker build --build-arg OCELOT_NUTGET_API_KEY=$OCELOT_NUTGET_API_KEY --build-arg OCELOT_GITHUB_API_KEY=$OCELOT_GITHUB_API_KEY -f ./docker/Dockerfile.release . # docker build --build-arg OCELOT_NUTGET_API_KEY=$OCELOT_NUTGET_API_KEY --build-arg OCELOT_GITHUB_API_KEY=$OCELOT_GITHUB_API_KEY -f ./docker/Dockerfile.release .
FROM mijitt0m/ocelot-build:0.0.1 FROM mijitt0m/ocelot-build:0.0.1
ARG OCELOT_GITHUB_API_KEY ARG OCELOT_GITHUB_API_KEY
ARG OCELOT_NUTGET_API_KEY ARG OCELOT_NUTGET_API_KEY
ENV OCELOT_NUTGET_API_KEY=$OCELOT_NUTGET_API_KEY ENV OCELOT_NUTGET_API_KEY=$OCELOT_NUTGET_API_KEY
ENV OCELOT_GITHUB_API_KEY=$OCELOT_GITHUB_API_KEY ENV OCELOT_GITHUB_API_KEY=$OCELOT_GITHUB_API_KEY
WORKDIR /src WORKDIR /src
COPY ./. . COPY ./. .
RUN chmod u+x build.sh RUN chmod u+x build.sh
RUN make release RUN make release

View File

@ -1,3 +1,3 @@
# docker build # docker build
This folder contains the dockerfile and script to create the ocelot build container. This folder contains the dockerfile and script to create the ocelot build container.

View File

@ -1,6 +1,6 @@
# this script build the ocelot docker file # this script build the ocelot docker file
docker build -t mijitt0m/ocelot-build -f Dockerfile.base . docker build -t mijitt0m/ocelot-build -f Dockerfile.base .
echo $DOCKER_PASS | docker login -u $DOCKER_USER --password-stdin echo $DOCKER_PASS | docker login -u $DOCKER_USER --password-stdin
docker tag mijitt0m/ocelot-build mijitt0m/ocelot-build:0.0.1 docker tag mijitt0m/ocelot-build mijitt0m/ocelot-build:0.0.1
docker push mijitt0m/ocelot-build:latest docker push mijitt0m/ocelot-build:latest
docker push mijitt0m/ocelot-build:0.0.1 docker push mijitt0m/ocelot-build:0.0.1

View File

@ -1,35 +1,35 @@
Release process Release process
=============== ===============
* The release process works best with GitHubFlow branching. * The release process works best with GitHubFlow branching.
* Contributors can do whatever they want on PRs and merges to master will result in packages being released to GitHub and NuGet. * Contributors can do whatever they want on PRs and merges to master will result in packages being released to GitHub and NuGet.
Ocelot uses the following process to accept work into the NuGet packages. Ocelot uses the following process to accept work into the NuGet packages.
1. User creates an issue or picks up an existing issue in GitHub. 1. User creates an issue or picks up an existing issue in GitHub.
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 :) 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 :)
3. When the user is happy with their work they can create a pull request against master in GitHub with their changes. The user must follow the `SemVer <https://semver.org/>`_ support for this is provided by `GitVersion <https://gitversion.readthedocs.io/en/latest/>`_. 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. 3. When the user is happy with their work they can create a pull request against master in GitHub with their changes. The user must follow the `SemVer <https://semver.org/>`_ support for this is provided by `GitVersion <https://gitversion.readthedocs.io/en/latest/>`_. 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.
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. 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 covered all my changes with tests at unit and acceptance level?
- Have I updated any documentation that my changes may have affected? - 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? - 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. In order for a PR to be merged the following must have occured.
- All new code is covered by unit tests. - All new code is covered by unit tests.
- All new code has at least 1 acceptance test covering the happy path. - All new code has at least 1 acceptance test covering the happy path.
- Tests must have passed. - Tests must have passed.
- Build must not have slowed down dramatically. - Build must not have slowed down dramatically.
- The main Ocelot package must not have taken on any non MS dependencies. - The main Ocelot package must not have taken on any non MS dependencies.
5. After the PR is merged to master the release process will begin which builds the code, versions it, pushes artifacts to GitHub and NuGet packages to NuGet. 5. After the PR is merged to master the release process will begin which builds the code, versions it, pushes artifacts to GitHub and NuGet packages to NuGet.
6. The final step is to go back to GitHub and close any issues that are now fixed. You should see something like this in`GitHub <https://github.com/ThreeMammals/Ocelot/releases/tag/13.0.0>`_ and this in `NuGet <https://www.nuget.org/packages/Ocelot/13.0.0>`_. 6. The final step is to go back to GitHub and close any issues that are now fixed. You should see something like this in`GitHub <https://github.com/ThreeMammals/Ocelot/releases/tag/13.0.0>`_ and this in `NuGet <https://www.nuget.org/packages/Ocelot/13.0.0>`_.
Notes Notes
----- -----
All NuGet package builds & releases are done with CircleCI `here <https://circleci.com/gh/ThreeMammals>_` and all releases are done from `here <https://ci.appveyor.com/project/TomPallister/ocelot-ayj4w>_`. All NuGet package builds & releases are done with CircleCI `here <https://circleci.com/gh/ThreeMammals>_` and all releases are done from `here <https://ci.appveyor.com/project/TomPallister/ocelot-ayj4w>_`.
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. 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.

View File

@ -1,54 +1,54 @@
Caching Caching
======= =======
Ocelot supports some very rudimentary caching at the moment provider by Ocelot supports some very rudimentary caching at the moment provider by
the `CacheManager <https://github.com/MichaCo/CacheManager>`_ project. This is an amazing project the `CacheManager <https://github.com/MichaCo/CacheManager>`_ project. This is an amazing project
that is solving a lot of caching problems. I would reccomend using this package to that is solving a lot of caching problems. I would reccomend using this package to
cache with Ocelot. cache with Ocelot.
The following example shows how to add CacheManager to Ocelot so that you can do output caching. The following example shows how to add CacheManager to Ocelot so that you can do output caching.
First of all add the following NuGet package. First of all add the following NuGet package.
``Install-Package Ocelot.Cache.CacheManager`` ``Install-Package Ocelot.Cache.CacheManager``
This will give you access to the Ocelot cache manager extension methods. This will give you access to the Ocelot cache manager extension methods.
The second thing you need to do something like the following to your ConfigureServices.. The second thing you need to do something like the following to your ConfigureServices..
.. code-block:: csharp .. code-block:: csharp
s.AddOcelot() s.AddOcelot()
.AddCacheManager(x => .AddCacheManager(x =>
{ {
x.WithDictionaryHandle(); x.WithDictionaryHandle();
}) })
Finally in order to use caching on a route in your ReRoute configuration add this setting. Finally in order to use caching on a route in your ReRoute configuration add this setting.
.. code-block:: json .. code-block:: json
"FileCacheOptions": { "TtlSeconds": 15, "Region": "somename" } "FileCacheOptions": { "TtlSeconds": 15, "Region": "somename" }
In this example ttl seconds is set to 15 which means the cache will expire after 15 seconds. In this example ttl seconds is set to 15 which means the cache will expire after 15 seconds.
If you look at the example `here <https://github.com/ThreeMammals/Ocelot/blob/master/test/Ocelot.ManualTest/Program.cs>`_ you can see how the cache manager If you look at the example `here <https://github.com/ThreeMammals/Ocelot/blob/master/test/Ocelot.ManualTest/Program.cs>`_ you can see how the cache manager
is setup and then passed into the Ocelot AddCacheManager configuration method. You can use any settings supported by is setup and then passed into the Ocelot AddCacheManager configuration method. You can use any settings supported by
the CacheManager package and just pass them in. the CacheManager package and just pass them in.
Anyway Ocelot currently supports caching on the URL of the downstream service Anyway Ocelot currently supports caching on the URL of the downstream service
and setting a TTL in seconds to expire the cache. You can also clear the cache for a region and setting a TTL in seconds to expire the cache. You can also clear the cache for a region
by calling Ocelot's administration API. by calling Ocelot's administration API.
Your own caching Your own caching
^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^
If you want to add your own caching method implement the following interfaces and register them in DI If you want to add your own caching method implement the following interfaces and register them in DI
e.g. ``services.AddSingleton<IOcelotCache<CachedResponse>, MyCache>()`` e.g. ``services.AddSingleton<IOcelotCache<CachedResponse>, MyCache>()``
``IOcelotCache<CachedResponse>`` this is for output caching. ``IOcelotCache<CachedResponse>`` this is for output caching.
``IOcelotCache<FileConfiguration>`` this is for caching the file configuration if you are calling something remote to get your config such as Consul. ``IOcelotCache<FileConfiguration>`` this is for caching the file configuration if you are calling something remote to get your config such as Consul.
Please dig into the Ocelot source code to find more. I would really appreciate it if anyone wants to implement Redis, memcache etc.. Please dig into the Ocelot source code to find more. I would really appreciate it if anyone wants to implement Redis, memcache etc..

View File

@ -1,224 +1,224 @@
Configuration Configuration
============ ============
An example configuration can be found `here <https://github.com/ThreeMammals/Ocelot/blob/master/test/Ocelot.ManualTest/ocelot.json>`_. An example configuration can be found `here <https://github.com/ThreeMammals/Ocelot/blob/master/test/Ocelot.ManualTest/ocelot.json>`_.
There are two sections to the configuration. An array of ReRoutes and a GlobalConfiguration. There are two sections to the configuration. An array of ReRoutes and a GlobalConfiguration.
The ReRoutes are the objects that tell Ocelot how to treat an upstream request. The Global The ReRoutes are the objects that tell Ocelot how to treat an upstream request. The Global
configuration is a bit hacky and allows overrides of ReRoute specific settings. It's useful configuration is a bit hacky and allows overrides of ReRoute specific settings. It's useful
if you don't want to manage lots of ReRoute specific settings. if you don't want to manage lots of ReRoute specific settings.
.. code-block:: json .. code-block:: json
{ {
"ReRoutes": [], "ReRoutes": [],
"GlobalConfiguration": {} "GlobalConfiguration": {}
} }
Here is an example ReRoute configuration, You don't need to set all of these things but this is everything that is available at the moment: Here is an example ReRoute configuration, You don't need to set all of these things but this is everything that is available at the moment:
.. code-block:: json .. code-block:: json
{ {
"DownstreamPathTemplate": "/", "DownstreamPathTemplate": "/",
"UpstreamPathTemplate": "/", "UpstreamPathTemplate": "/",
"UpstreamHttpMethod": [ "UpstreamHttpMethod": [
"Get" "Get"
], ],
"AddHeadersToRequest": {}, "AddHeadersToRequest": {},
"AddClaimsToRequest": {}, "AddClaimsToRequest": {},
"RouteClaimsRequirement": {}, "RouteClaimsRequirement": {},
"AddQueriesToRequest": {}, "AddQueriesToRequest": {},
"RequestIdKey": "", "RequestIdKey": "",
"FileCacheOptions": { "FileCacheOptions": {
"TtlSeconds": 0, "TtlSeconds": 0,
"Region": "" "Region": ""
}, },
"ReRouteIsCaseSensitive": false, "ReRouteIsCaseSensitive": false,
"ServiceName": "", "ServiceName": "",
"DownstreamScheme": "http", "DownstreamScheme": "http",
"DownstreamHostAndPorts": [ "DownstreamHostAndPorts": [
{ {
"Host": "localhost", "Host": "localhost",
"Port": 51876, "Port": 51876,
} }
], ],
"QoSOptions": { "QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 0, "ExceptionsAllowedBeforeBreaking": 0,
"DurationOfBreak": 0, "DurationOfBreak": 0,
"TimeoutValue": 0 "TimeoutValue": 0
}, },
"LoadBalancer": "", "LoadBalancer": "",
"RateLimitOptions": { "RateLimitOptions": {
"ClientWhitelist": [], "ClientWhitelist": [],
"EnableRateLimiting": false, "EnableRateLimiting": false,
"Period": "", "Period": "",
"PeriodTimespan": 0, "PeriodTimespan": 0,
"Limit": 0 "Limit": 0
}, },
"AuthenticationOptions": { "AuthenticationOptions": {
"AuthenticationProviderKey": "", "AuthenticationProviderKey": "",
"AllowedScopes": [] "AllowedScopes": []
}, },
"HttpHandlerOptions": { "HttpHandlerOptions": {
"AllowAutoRedirect": true, "AllowAutoRedirect": true,
"UseCookieContainer": true, "UseCookieContainer": true,
"UseTracing": true "UseTracing": true
}, },
"DangerousAcceptAnyServerCertificateValidator": false "DangerousAcceptAnyServerCertificateValidator": false
} }
More information on how to use these options is below.. More information on how to use these options is below..
Multiple environments Multiple environments
^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^
Like any other asp.net core project Ocelot supports configuration file names such as configuration.dev.json, configuration.test.json etc. In order to implement this add the following Like any other asp.net core project Ocelot supports configuration file names such as configuration.dev.json, configuration.test.json etc. In order to implement this add the following
to you to you
.. code-block:: csharp .. code-block:: csharp
.ConfigureAppConfiguration((hostingContext, config) => .ConfigureAppConfiguration((hostingContext, config) =>
{ {
config config
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
.AddJsonFile("appsettings.json", true, true) .AddJsonFile("appsettings.json", true, true)
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
.AddJsonFile("ocelot.json") .AddJsonFile("ocelot.json")
.AddJsonFile($"configuration.{hostingContext.HostingEnvironment.EnvironmentName}.json") .AddJsonFile($"configuration.{hostingContext.HostingEnvironment.EnvironmentName}.json")
.AddEnvironmentVariables(); .AddEnvironmentVariables();
}) })
Ocelot will now use the environment specific configuration and fall back to ocelot.json if there isn't 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 <https://docs.microsoft.com/en-us/aspnet/core/fundamentals/environments>`_. 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 <https://docs.microsoft.com/en-us/aspnet/core/fundamentals/environments>`_.
Merging configuration files Merging configuration files
^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
This feature was requested in `Issue 296 <https://github.com/ThreeMammals/Ocelot/issues/296>`_ and allows users to have multiple configuration files to make managing large configurations easier. This feature was requested in `Issue 296 <https://github.com/ThreeMammals/Ocelot/issues/296>`_ and allows users to have multiple configuration files to make managing large configurations easier.
Instead of adding the configuration directly e.g. AddJsonFile("ocelot.json") you can call AddOcelot() like below. Instead of adding the configuration directly e.g. AddJsonFile("ocelot.json") you can call AddOcelot() like below.
.. code-block:: csharp .. code-block:: csharp
.ConfigureAppConfiguration((hostingContext, config) => .ConfigureAppConfiguration((hostingContext, config) =>
{ {
config config
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
.AddJsonFile("appsettings.json", true, true) .AddJsonFile("appsettings.json", true, true)
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
.AddOcelot(hostingContext.HostingEnvironment) .AddOcelot(hostingContext.HostingEnvironment)
.AddEnvironmentVariables(); .AddEnvironmentVariables();
}) })
In this scenario Ocelot will look for any files that match the pattern (?i)ocelot.([a-zA-Z0-9]*).json and then merge these together. If you want to set the GlobalConfiguration property you must have a file called ocelot.global.json. In this scenario Ocelot will look for any files that match the pattern (?i)ocelot.([a-zA-Z0-9]*).json and then merge these together. If you want to set the GlobalConfiguration property you must have a file called ocelot.global.json.
The way Ocelot merges the files is basically load them, loop over them, add any ReRoutes, add any AggregateReRoutes and if the file is called ocelot.global.json add the GlobalConfiguration aswell as any ReRoutes or AggregateReRoutes. Ocelot will then save the merged configuration to a file called ocelot.json and this will be used as the source of truth while ocelot is running. The way Ocelot merges the files is basically load them, loop over them, add any ReRoutes, add any AggregateReRoutes and if the file is called ocelot.global.json add the GlobalConfiguration aswell as any ReRoutes or AggregateReRoutes. Ocelot will then save the merged configuration to a file called ocelot.json and this will be used as the source of truth while ocelot is running.
At the moment there is no validation at this stage it only happens when Ocelot validates the final merged configuration. This is something to be aware of when you are investigating problems. I would advise always checking what is in ocelot.json if you have any problems. At the moment there is no validation at this stage it only happens when Ocelot validates the final merged configuration. This is something to be aware of when you are investigating problems. I would advise always checking what is in ocelot.json if you have any problems.
You can also give Ocelot a specific path to look in for the configuration files like below. You can also give Ocelot a specific path to look in for the configuration files like below.
.. code-block:: csharp .. code-block:: csharp
.ConfigureAppConfiguration((hostingContext, config) => .ConfigureAppConfiguration((hostingContext, config) =>
{ {
config config
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
.AddJsonFile("appsettings.json", true, true) .AddJsonFile("appsettings.json", true, true)
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
.AddOcelot("/foo/bar", hostingContext.HostingEnvironment) .AddOcelot("/foo/bar", hostingContext.HostingEnvironment)
.AddEnvironmentVariables(); .AddEnvironmentVariables();
}) })
Ocelot needs the HostingEnvironment so it knows 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 Store configuration in consul
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The first thing you need to do is install the NuGet package that provides Consul support in Ocelot. The first thing you need to do is install the NuGet package that provides Consul support in Ocelot.
``Install-Package Ocelot.Provider.Consul`` ``Install-Package Ocelot.Provider.Consul``
Then you add the following when you register your services Ocelot will attempt to store and retrieve its configuration in consul KV store. Then you add the following when you register your services Ocelot will attempt to store and retrieve its configuration in consul KV store.
.. code-block:: csharp .. code-block:: csharp
services services
.AddOcelot() .AddOcelot()
.AddConsul() .AddConsul()
.AddConfigStoredInConsul(); .AddConfigStoredInConsul();
You also need to add the following to your ocelot.json. This is how Ocelot You also need to add the following to your ocelot.json. This is how Ocelot
finds your Consul agent and interacts to load and store the configuration from Consul. finds your Consul agent and interacts to load and store the configuration from Consul.
.. code-block:: json .. code-block:: json
"GlobalConfiguration": { "GlobalConfiguration": {
"ServiceDiscoveryProvider": { "ServiceDiscoveryProvider": {
"Host": "localhost", "Host": "localhost",
"Port": 9500 "Port": 9500
} }
} }
I decided to create this feature after working on the Raft consensus algorithm and finding out its super hard. Why not take advantage of the fact Consul already gives you this! I 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. 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. This feature has a 3 second ttl cache before making a new request to your local consul agent.
Reload JSON config on change Reload JSON config on change
^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Ocelot supports reloading the json configuration file on change. e.g. the following will recreate Ocelots internal configuration when the ocelot.json file is updated Ocelot supports reloading the json configuration file on change. e.g. the following will recreate Ocelots internal configuration when the ocelot.json file is updated
manually. manually.
.. code-block:: json .. code-block:: json
config.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true); config.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true);
Configuration Key Configuration Key
----------------- -----------------
If you are using Consul for configuration (or other providers in the future) you might want to key your configurations so you can have multiple configurations :) This feature was requested in `issue 346 <https://github.com/ThreeMammals/Ocelot/issues/346>`_! In order to specify the key you need to set the ConfigurationKey property in the ServiceDiscoveryProvider section of the configuration json file e.g. If you are using Consul for configuration (or other providers in the future) you might want to key your configurations so you can have multiple configurations :) This feature was requested in `issue 346 <https://github.com/ThreeMammals/Ocelot/issues/346>`_! In order to specify the key you need to set the ConfigurationKey property in the ServiceDiscoveryProvider section of the configuration json file e.g.
.. code-block:: json .. code-block:: json
"GlobalConfiguration": { "GlobalConfiguration": {
"ServiceDiscoveryProvider": { "ServiceDiscoveryProvider": {
"Host": "localhost", "Host": "localhost",
"Port": 9500, "Port": 9500,
"ConfigurationKey": "Oceolot_A" "ConfigurationKey": "Oceolot_A"
} }
} }
In this example Ocelot will use Oceolot_A as the key for your configuration when looking it up in Consul. In this example Ocelot will use Oceolot_A as the key for your configuration when looking it up in Consul.
If you do not set the ConfigurationKey Ocelot will use the string InternalConfiguration as the key. If you do not set the ConfigurationKey Ocelot will use the string InternalConfiguration as the key.
Follow Redirects / Use CookieContainer Follow Redirects / Use CookieContainer
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Use HttpHandlerOptions in ReRoute configuration to set up HttpHandler behavior: 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 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. 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 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 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 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 <https://github.com/ThreeMammals/Ocelot/issues/274>`_ was created because a user to that DownstreamService will share the same cookies. `Issue 274 <https://github.com/ThreeMammals/Ocelot/issues/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 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 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 don't 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! 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 SSL Errors
^^^^^^^^^^ ^^^^^^^^^^
If 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 .. code-block:: json
"DangerousAcceptAnyServerCertificateValidator": true "DangerousAcceptAnyServerCertificateValidator": true
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. 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.

View File

@ -1,15 +1,15 @@
GraphQL GraphQL
======= =======
OK you got me Ocelot doesn't directly support GraphQL but so many people have asked about it I wanted to show how easy it is to integrate OK you got me Ocelot doesn't directly support GraphQL but so many people have asked about it I wanted to show how easy it is to integrate
the `graphql-dotnet <https://github.com/graphql-dotnet/graphql-dotnet>`_ library. the `graphql-dotnet <https://github.com/graphql-dotnet/graphql-dotnet>`_ library.
Please see the sample project `OcelotGraphQL <https://github.com/ThreeMammals/Ocelot/tree/master/samples/OcelotGraphQL>`_. Please see the sample project `OcelotGraphQL <https://github.com/ThreeMammals/Ocelot/tree/master/samples/OcelotGraphQL>`_.
Using a combination of the graphql-dotnet project and Ocelot's DelegatingHandler features this is pretty easy to do. Using a combination of the graphql-dotnet project and Ocelot's DelegatingHandler features this is pretty easy to do.
However I do not intend to integrate more closely with GraphQL at the moment. Check out the samples readme and that should give However I do not intend to integrate more closely with GraphQL at the moment. Check out the samples readme and that should give
you enough instruction on how to do this! you enough instruction on how to do this!
Good luck and have fun :> Good luck and have fun :>

View File

@ -1,24 +1,24 @@
version: "3.4" version: "3.4"
services: services:
tests: tests:
build: build:
context: . context: .
target: builder target: builder
volumes: volumes:
- type: bind - type: bind
source: . source: .
target: /results target: /results
command: test --logger:trx -r /results command: test --logger:trx -r /results
benchmarks: benchmarks:
build: build:
context: . context: .
target: builder target: builder
args: args:
build_configuration: Release build_configuration: Release
command: run -c Release --project test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj 0 1 2 3 4 command: run -c Release --project test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj 0 1 2 3 4
manual-test: manual-test:
build: . build: .
ports: [ "5000:80" ] ports: [ "5000:80" ]

View File

@ -1,48 +1,48 @@
#This is the base image used for any ran images #This is the base image used for any ran images
FROM mcr.microsoft.com/dotnet/core/aspnet:2.2-stretch-slim AS base FROM mcr.microsoft.com/dotnet/core/aspnet:2.2-stretch-slim AS base
WORKDIR /app WORKDIR /app
EXPOSE 80 EXPOSE 80
#This image is used to build the source for the runnable app #This image is used to build the source for the runnable app
#It can also be used to run other CLI commands on the project, such as packing/deploying nuget packages. Some examples: #It can also be used to run other CLI commands on the project, such as packing/deploying nuget packages. Some examples:
#Run tests: docker build --target builder -t ocelot-build . && docker run ocelot-build test --logger:trx;LogFileName=results.trx #Run tests: docker build --target builder -t ocelot-build . && docker run ocelot-build test --logger:trx;LogFileName=results.trx
#Run benchmarks: docker build --target builder --build-arg build_configuration=Release -t ocelot-build . && docker run ocelot-build run -c Release --project test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj #Run benchmarks: docker build --target builder --build-arg build_configuration=Release -t ocelot-build . && docker run ocelot-build run -c Release --project test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj
FROM mcr.microsoft.com/dotnet/core/sdk:2.2-stretch AS builder FROM mcr.microsoft.com/dotnet/core/sdk:2.2-stretch AS builder
WORKDIR /build WORKDIR /build
#First we add only the project files so that we can cache nuget packages with dotnet restore #First we add only the project files so that we can cache nuget packages with dotnet restore
COPY Ocelot.sln Ocelot.sln COPY Ocelot.sln Ocelot.sln
COPY src/Ocelot/Ocelot.csproj src/Ocelot/Ocelot.csproj 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.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.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.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.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.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.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.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 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.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.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.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.UnitTests/Ocelot.UnitTests.csproj test/Ocelot.UnitTests/Ocelot.UnitTests.csproj
COPY test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj COPY test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj
RUN dotnet restore 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 #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 COPY codeanalysis.ruleset codeanalysis.ruleset
COPY src src COPY src src
COPY test test COPY test test
ARG build_configuration=Debug ARG build_configuration=Debug
RUN dotnet build --no-restore -c ${build_configuration} RUN dotnet build --no-restore -c ${build_configuration}
ENTRYPOINT ["dotnet"] ENTRYPOINT ["dotnet"]
#This is just for holding the published manual tests... #This is just for holding the published manual tests...
FROM builder AS manual-test-publish FROM builder AS manual-test-publish
ARG build_configuration=Debug ARG build_configuration=Debug
RUN dotnet publish --no-build -c ${build_configuration} -o /app test/Ocelot.ManualTest RUN dotnet publish --no-build -c ${build_configuration} -o /app test/Ocelot.ManualTest
#Run manual tests! This is the default run option. #Run manual tests! This is the default run option.
#docker build -t ocelot-manual-test . && docker run --net host ocelot-manual-test #docker build -t ocelot-manual-test . && docker run --net host ocelot-manual-test
FROM base AS manual-test FROM base AS manual-test
ENV ASPNETCORE_ENVIRONMENT=Development ENV ASPNETCORE_ENVIRONMENT=Development
COPY --from=manual-test-publish /app . COPY --from=manual-test-publish /app .
ENTRYPOINT ["dotnet", "Ocelot.ManualTest.dll"] ENTRYPOINT ["dotnet", "Ocelot.ManualTest.dll"]

File diff suppressed because it is too large Load Diff

View File

@ -1,225 +1,225 @@
namespace Ocelot.AcceptanceTests namespace Ocelot.AcceptanceTests
{ {
using Configuration.File; using Configuration.File;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
using System.Threading; using System.Threading;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
public class CachingTests : IDisposable public class CachingTests : IDisposable
{ {
private readonly Steps _steps; private readonly Steps _steps;
private readonly ServiceHandler _serviceHandler; private readonly ServiceHandler _serviceHandler;
public CachingTests() public CachingTests()
{ {
_serviceHandler = new ServiceHandler(); _serviceHandler = new ServiceHandler();
_steps = new Steps(); _steps = new Steps();
} }
[Fact] [Fact]
public void should_return_cached_response() public void should_return_cached_response()
{ {
var configuration = new FileConfiguration var configuration = new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
{ {
new FileReRoute new FileReRoute
{ {
DownstreamPathTemplate = "/", DownstreamPathTemplate = "/",
DownstreamHostAndPorts = new List<FileHostAndPort> DownstreamHostAndPorts = new List<FileHostAndPort>
{ {
new FileHostAndPort new FileHostAndPort
{ {
Host = "localhost", Host = "localhost",
Port = 57899, Port = 57899,
} }
}, },
DownstreamScheme = "http", DownstreamScheme = "http",
UpstreamPathTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" }, UpstreamHttpMethod = new List<string> { "Get" },
FileCacheOptions = new FileCacheOptions FileCacheOptions = new FileCacheOptions
{ {
TtlSeconds = 100 TtlSeconds = 100
} }
} }
} }
}; };
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:57899", 200, "Hello from Laura", null, null)) this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:57899", 200, "Hello from Laura", null, null))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning()) .And(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.Given(x => x.GivenTheServiceNowReturns("http://localhost:57899", 200, "Hello from Tom")) .Given(x => x.GivenTheServiceNowReturns("http://localhost:57899", 200, "Hello from Tom"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.And(x => _steps.ThenTheContentLengthIs(16)) .And(x => _steps.ThenTheContentLengthIs(16))
.BDDfy(); .BDDfy();
} }
[Fact] [Fact]
public void should_return_cached_response_with_expires_header() public void should_return_cached_response_with_expires_header()
{ {
var configuration = new FileConfiguration var configuration = new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
{ {
new FileReRoute new FileReRoute
{ {
DownstreamPathTemplate = "/", DownstreamPathTemplate = "/",
DownstreamHostAndPorts = new List<FileHostAndPort> DownstreamHostAndPorts = new List<FileHostAndPort>
{ {
new FileHostAndPort new FileHostAndPort
{ {
Host = "localhost", Host = "localhost",
Port = 52839, Port = 52839,
} }
}, },
DownstreamScheme = "http", DownstreamScheme = "http",
UpstreamPathTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" }, UpstreamHttpMethod = new List<string> { "Get" },
FileCacheOptions = new FileCacheOptions FileCacheOptions = new FileCacheOptions
{ {
TtlSeconds = 100 TtlSeconds = 100
} }
} }
} }
}; };
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:52839", 200, "Hello from Laura", "Expires", "-1")) this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:52839", 200, "Hello from Laura", "Expires", "-1"))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning()) .And(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.Given(x => x.GivenTheServiceNowReturns("http://localhost:52839", 200, "Hello from Tom")) .Given(x => x.GivenTheServiceNowReturns("http://localhost:52839", 200, "Hello from Tom"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.And(x => _steps.ThenTheContentLengthIs(16)) .And(x => _steps.ThenTheContentLengthIs(16))
.And(x => _steps.ThenTheResponseBodyHeaderIs("Expires", "-1")) .And(x => _steps.ThenTheResponseBodyHeaderIs("Expires", "-1"))
.BDDfy(); .BDDfy();
} }
[Fact] [Fact]
public void should_return_cached_response_when_using_jsonserialized_cache() public void should_return_cached_response_when_using_jsonserialized_cache()
{ {
var configuration = new FileConfiguration var configuration = new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
{ {
new FileReRoute new FileReRoute
{ {
DownstreamPathTemplate = "/", DownstreamPathTemplate = "/",
DownstreamHostAndPorts = new List<FileHostAndPort> DownstreamHostAndPorts = new List<FileHostAndPort>
{ {
new FileHostAndPort new FileHostAndPort
{ {
Host = "localhost", Host = "localhost",
Port = 57879, Port = 57879,
} }
}, },
DownstreamScheme = "http", DownstreamScheme = "http",
UpstreamPathTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" }, UpstreamHttpMethod = new List<string> { "Get" },
FileCacheOptions = new FileCacheOptions FileCacheOptions = new FileCacheOptions
{ {
TtlSeconds = 100 TtlSeconds = 100
} }
} }
} }
}; };
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:57879", 200, "Hello from Laura", null, null)) this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:57879", 200, "Hello from Laura", null, null))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunningUsingJsonSerializedCache()) .And(x => _steps.GivenOcelotIsRunningUsingJsonSerializedCache())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.Given(x => x.GivenTheServiceNowReturns("http://localhost:57879", 200, "Hello from Tom")) .Given(x => x.GivenTheServiceNowReturns("http://localhost:57879", 200, "Hello from Tom"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.BDDfy(); .BDDfy();
} }
[Fact] [Fact]
public void should_not_return_cached_response_as_ttl_expires() public void should_not_return_cached_response_as_ttl_expires()
{ {
var configuration = new FileConfiguration var configuration = new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
{ {
new FileReRoute new FileReRoute
{ {
DownstreamPathTemplate = "/", DownstreamPathTemplate = "/",
DownstreamHostAndPorts = new List<FileHostAndPort> DownstreamHostAndPorts = new List<FileHostAndPort>
{ {
new FileHostAndPort new FileHostAndPort
{ {
Host = "localhost", Host = "localhost",
Port = 57873, Port = 57873,
} }
}, },
DownstreamScheme = "http", DownstreamScheme = "http",
UpstreamPathTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" }, UpstreamHttpMethod = new List<string> { "Get" },
FileCacheOptions = new FileCacheOptions FileCacheOptions = new FileCacheOptions
{ {
TtlSeconds = 1 TtlSeconds = 1
} }
} }
} }
}; };
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:57873", 200, "Hello from Laura", null, null)) this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:57873", 200, "Hello from Laura", null, null))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning()) .And(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.Given(x => x.GivenTheServiceNowReturns("http://localhost:57873", 200, "Hello from Tom")) .Given(x => x.GivenTheServiceNowReturns("http://localhost:57873", 200, "Hello from Tom"))
.And(x => x.GivenTheCacheExpires()) .And(x => x.GivenTheCacheExpires())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Tom")) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Tom"))
.BDDfy(); .BDDfy();
} }
private void GivenTheCacheExpires() private void GivenTheCacheExpires()
{ {
Thread.Sleep(1000); Thread.Sleep(1000);
} }
private void GivenTheServiceNowReturns(string url, int statusCode, string responseBody) private void GivenTheServiceNowReturns(string url, int statusCode, string responseBody)
{ {
_serviceHandler.Dispose(); _serviceHandler.Dispose();
GivenThereIsAServiceRunningOn(url, statusCode, responseBody, null, null); GivenThereIsAServiceRunningOn(url, statusCode, responseBody, null, null);
} }
private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody, string key, string value) private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody, string key, string value)
{ {
_serviceHandler.GivenThereIsAServiceRunningOn(url, async context => _serviceHandler.GivenThereIsAServiceRunningOn(url, async context =>
{ {
if (!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(key)) if (!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(key))
{ {
context.Response.Headers.Add(key, value); context.Response.Headers.Add(key, value);
} }
context.Response.StatusCode = statusCode; context.Response.StatusCode = statusCode;
await context.Response.WriteAsync(responseBody); await context.Response.WriteAsync(responseBody);
}); });
} }
public void Dispose() public void Dispose()
{ {
_serviceHandler?.Dispose(); _serviceHandler?.Dispose();
_steps.Dispose(); _steps.Dispose();
} }
} }
} }

View File

@ -1,446 +1,446 @@
namespace Ocelot.AcceptanceTests namespace Ocelot.AcceptanceTests
{ {
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Ocelot.Configuration.File; using Ocelot.Configuration.File;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
public class HeaderTests : IDisposable public class HeaderTests : IDisposable
{ {
private int _count; private int _count;
private readonly Steps _steps; private readonly Steps _steps;
private readonly ServiceHandler _serviceHandler; private readonly ServiceHandler _serviceHandler;
public HeaderTests() public HeaderTests()
{ {
_serviceHandler = new ServiceHandler(); _serviceHandler = new ServiceHandler();
_steps = new Steps(); _steps = new Steps();
} }
[Fact] [Fact]
public void should_transform_upstream_header() public void should_transform_upstream_header()
{ {
var configuration = new FileConfiguration var configuration = new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
{ {
new FileReRoute new FileReRoute
{ {
DownstreamPathTemplate = "/", DownstreamPathTemplate = "/",
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort> DownstreamHostAndPorts = new List<FileHostAndPort>
{ {
new FileHostAndPort new FileHostAndPort
{ {
Host = "localhost", Host = "localhost",
Port = 51871, Port = 51871,
} }
}, },
UpstreamPathTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" }, UpstreamHttpMethod = new List<string> { "Get" },
UpstreamHeaderTransform = new Dictionary<string,string> UpstreamHeaderTransform = new Dictionary<string,string>
{ {
{"Laz", "D, GP"} {"Laz", "D, GP"}
} }
} }
} }
}; };
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51871", "/", 200, "Laz")) this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51871", "/", 200, "Laz"))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning()) .And(x => _steps.GivenOcelotIsRunning())
.And(x => _steps.GivenIAddAHeader("Laz", "D")) .And(x => _steps.GivenIAddAHeader("Laz", "D"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("GP")) .And(x => _steps.ThenTheResponseBodyShouldBe("GP"))
.BDDfy(); .BDDfy();
} }
[Fact] [Fact]
public void should_transform_downstream_header() public void should_transform_downstream_header()
{ {
var configuration = new FileConfiguration var configuration = new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
{ {
new FileReRoute new FileReRoute
{ {
DownstreamPathTemplate = "/", DownstreamPathTemplate = "/",
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort> DownstreamHostAndPorts = new List<FileHostAndPort>
{ {
new FileHostAndPort new FileHostAndPort
{ {
Host = "localhost", Host = "localhost",
Port = 51871, Port = 51871,
} }
}, },
UpstreamPathTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" }, UpstreamHttpMethod = new List<string> { "Get" },
DownstreamHeaderTransform = new Dictionary<string,string> DownstreamHeaderTransform = new Dictionary<string,string>
{ {
{"Location", "http://www.bbc.co.uk/, http://ocelot.com/"} {"Location", "http://www.bbc.co.uk/, http://ocelot.com/"}
} }
} }
} }
}; };
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51871", "/", 200, "Location", "http://www.bbc.co.uk/")) this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51871", "/", 200, "Location", "http://www.bbc.co.uk/"))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning()) .And(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseHeaderIs("Location", "http://ocelot.com/")) .And(x => _steps.ThenTheResponseHeaderIs("Location", "http://ocelot.com/"))
.BDDfy(); .BDDfy();
} }
[Fact] [Fact]
public void should_fix_issue_190() public void should_fix_issue_190()
{ {
var configuration = new FileConfiguration var configuration = new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
{ {
new FileReRoute new FileReRoute
{ {
DownstreamPathTemplate = "/", DownstreamPathTemplate = "/",
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort> DownstreamHostAndPorts = new List<FileHostAndPort>
{ {
new FileHostAndPort new FileHostAndPort
{ {
Host = "localhost", Host = "localhost",
Port = 6773, Port = 6773,
} }
}, },
UpstreamPathTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" }, UpstreamHttpMethod = new List<string> { "Get" },
DownstreamHeaderTransform = new Dictionary<string,string> DownstreamHeaderTransform = new Dictionary<string,string>
{ {
{"Location", "http://localhost:6773, {BaseUrl}"} {"Location", "http://localhost:6773, {BaseUrl}"}
}, },
HttpHandlerOptions = new FileHttpHandlerOptions HttpHandlerOptions = new FileHttpHandlerOptions
{ {
AllowAutoRedirect = false AllowAutoRedirect = false
} }
} }
} }
}; };
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:6773", "/", 302, "Location", "http://localhost:6773/pay/Receive")) this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:6773", "/", 302, "Location", "http://localhost:6773/pay/Receive"))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning()) .And(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Redirect)) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Redirect))
.And(x => _steps.ThenTheResponseHeaderIs("Location", "http://localhost:5000/pay/Receive")) .And(x => _steps.ThenTheResponseHeaderIs("Location", "http://localhost:5000/pay/Receive"))
.BDDfy(); .BDDfy();
} }
[Fact] [Fact]
public void should_fix_issue_205() public void should_fix_issue_205()
{ {
var configuration = new FileConfiguration var configuration = new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
{ {
new FileReRoute new FileReRoute
{ {
DownstreamPathTemplate = "/", DownstreamPathTemplate = "/",
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort> DownstreamHostAndPorts = new List<FileHostAndPort>
{ {
new FileHostAndPort new FileHostAndPort
{ {
Host = "localhost", Host = "localhost",
Port = 6773, Port = 6773,
} }
}, },
UpstreamPathTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" }, UpstreamHttpMethod = new List<string> { "Get" },
DownstreamHeaderTransform = new Dictionary<string,string> DownstreamHeaderTransform = new Dictionary<string,string>
{ {
{"Location", "{DownstreamBaseUrl}, {BaseUrl}"} {"Location", "{DownstreamBaseUrl}, {BaseUrl}"}
}, },
HttpHandlerOptions = new FileHttpHandlerOptions HttpHandlerOptions = new FileHttpHandlerOptions
{ {
AllowAutoRedirect = false AllowAutoRedirect = false
} }
} }
} }
}; };
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:6773", "/", 302, "Location", "http://localhost:6773/pay/Receive")) this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:6773", "/", 302, "Location", "http://localhost:6773/pay/Receive"))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning()) .And(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Redirect)) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Redirect))
.And(x => _steps.ThenTheResponseHeaderIs("Location", "http://localhost:5000/pay/Receive")) .And(x => _steps.ThenTheResponseHeaderIs("Location", "http://localhost:5000/pay/Receive"))
.BDDfy(); .BDDfy();
} }
[Fact] [Fact]
public void should_fix_issue_417() public void should_fix_issue_417()
{ {
var configuration = new FileConfiguration var configuration = new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
{ {
new FileReRoute new FileReRoute
{ {
DownstreamPathTemplate = "/", DownstreamPathTemplate = "/",
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort> DownstreamHostAndPorts = new List<FileHostAndPort>
{ {
new FileHostAndPort new FileHostAndPort
{ {
Host = "localhost", Host = "localhost",
Port = 6773, Port = 6773,
} }
}, },
UpstreamPathTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" }, UpstreamHttpMethod = new List<string> { "Get" },
DownstreamHeaderTransform = new Dictionary<string,string> DownstreamHeaderTransform = new Dictionary<string,string>
{ {
{"Location", "{DownstreamBaseUrl}, {BaseUrl}"} {"Location", "{DownstreamBaseUrl}, {BaseUrl}"}
}, },
HttpHandlerOptions = new FileHttpHandlerOptions HttpHandlerOptions = new FileHttpHandlerOptions
{ {
AllowAutoRedirect = false AllowAutoRedirect = false
} }
} }
}, },
GlobalConfiguration = new FileGlobalConfiguration GlobalConfiguration = new FileGlobalConfiguration
{ {
BaseUrl = "http://anotherapp.azurewebsites.net" BaseUrl = "http://anotherapp.azurewebsites.net"
} }
}; };
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:6773", "/", 302, "Location", "http://localhost:6773/pay/Receive")) this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:6773", "/", 302, "Location", "http://localhost:6773/pay/Receive"))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning()) .And(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Redirect)) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Redirect))
.And(x => _steps.ThenTheResponseHeaderIs("Location", "http://anotherapp.azurewebsites.net/pay/Receive")) .And(x => _steps.ThenTheResponseHeaderIs("Location", "http://anotherapp.azurewebsites.net/pay/Receive"))
.BDDfy(); .BDDfy();
} }
[Fact] [Fact]
public void request_should_reuse_cookies_with_cookie_container() public void request_should_reuse_cookies_with_cookie_container()
{ {
var configuration = new FileConfiguration var configuration = new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
{ {
new FileReRoute new FileReRoute
{ {
DownstreamPathTemplate = "/sso/{everything}", DownstreamPathTemplate = "/sso/{everything}",
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort> DownstreamHostAndPorts = new List<FileHostAndPort>
{ {
new FileHostAndPort new FileHostAndPort
{ {
Host = "localhost", Host = "localhost",
Port = 6774, Port = 6774,
} }
}, },
UpstreamPathTemplate = "/sso/{everything}", UpstreamPathTemplate = "/sso/{everything}",
UpstreamHttpMethod = new List<string> { "Get", "Post", "Options" }, UpstreamHttpMethod = new List<string> { "Get", "Post", "Options" },
HttpHandlerOptions = new FileHttpHandlerOptions HttpHandlerOptions = new FileHttpHandlerOptions
{ {
UseCookieContainer = true UseCookieContainer = true
} }
} }
} }
}; };
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:6774", "/sso/test", 200)) this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:6774", "/sso/test", 200))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning()) .And(x => _steps.GivenOcelotIsRunning())
.And(x => _steps.WhenIGetUrlOnTheApiGateway("/sso/test")) .And(x => _steps.WhenIGetUrlOnTheApiGateway("/sso/test"))
.And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseHeaderIs("Set-Cookie", "test=0; path=/")) .And(x => _steps.ThenTheResponseHeaderIs("Set-Cookie", "test=0; path=/"))
.And(x => _steps.GivenIAddCookieToMyRequest("test=1; path=/")) .And(x => _steps.GivenIAddCookieToMyRequest("test=1; path=/"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/sso/test")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/sso/test"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.BDDfy(); .BDDfy();
} }
[Fact] [Fact]
public void request_should_have_own_cookies_no_cookie_container() public void request_should_have_own_cookies_no_cookie_container()
{ {
var configuration = new FileConfiguration var configuration = new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
{ {
new FileReRoute new FileReRoute
{ {
DownstreamPathTemplate = "/sso/{everything}", DownstreamPathTemplate = "/sso/{everything}",
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort> DownstreamHostAndPorts = new List<FileHostAndPort>
{ {
new FileHostAndPort new FileHostAndPort
{ {
Host = "localhost", Host = "localhost",
Port = 6775, Port = 6775,
} }
}, },
UpstreamPathTemplate = "/sso/{everything}", UpstreamPathTemplate = "/sso/{everything}",
UpstreamHttpMethod = new List<string> { "Get", "Post", "Options" }, UpstreamHttpMethod = new List<string> { "Get", "Post", "Options" },
HttpHandlerOptions = new FileHttpHandlerOptions HttpHandlerOptions = new FileHttpHandlerOptions
{ {
UseCookieContainer = false UseCookieContainer = false
} }
} }
} }
}; };
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:6775", "/sso/test", 200)) this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:6775", "/sso/test", 200))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning()) .And(x => _steps.GivenOcelotIsRunning())
.And(x => _steps.WhenIGetUrlOnTheApiGateway("/sso/test")) .And(x => _steps.WhenIGetUrlOnTheApiGateway("/sso/test"))
.And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseHeaderIs("Set-Cookie", "test=0; path=/")) .And(x => _steps.ThenTheResponseHeaderIs("Set-Cookie", "test=0; path=/"))
.And(x => _steps.GivenIAddCookieToMyRequest("test=1; path=/")) .And(x => _steps.GivenIAddCookieToMyRequest("test=1; path=/"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/sso/test")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/sso/test"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.BDDfy(); .BDDfy();
} }
[Fact] [Fact]
public void issue_474_should_not_put_spaces_in_header() public void issue_474_should_not_put_spaces_in_header()
{ {
var configuration = new FileConfiguration var configuration = new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
{ {
new FileReRoute new FileReRoute
{ {
DownstreamPathTemplate = "/", DownstreamPathTemplate = "/",
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort> DownstreamHostAndPorts = new List<FileHostAndPort>
{ {
new FileHostAndPort new FileHostAndPort
{ {
Host = "localhost", Host = "localhost",
Port = 52866, Port = 52866,
} }
}, },
UpstreamPathTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" }, UpstreamHttpMethod = new List<string> { "Get" },
} }
} }
}; };
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:52866", "/", 200, "Accept")) this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:52866", "/", 200, "Accept"))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning()) .And(x => _steps.GivenOcelotIsRunning())
.And(x => _steps.GivenIAddAHeader("Accept", "text/html,application/xhtml+xml,application/xml;")) .And(x => _steps.GivenIAddAHeader("Accept", "text/html,application/xhtml+xml,application/xml;"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("text/html,application/xhtml+xml,application/xml;")) .And(x => _steps.ThenTheResponseBodyShouldBe("text/html,application/xhtml+xml,application/xml;"))
.BDDfy(); .BDDfy();
} }
[Fact] [Fact]
public void issue_474_should_put_spaces_in_header() public void issue_474_should_put_spaces_in_header()
{ {
var configuration = new FileConfiguration var configuration = new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
{ {
new FileReRoute new FileReRoute
{ {
DownstreamPathTemplate = "/", DownstreamPathTemplate = "/",
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort> DownstreamHostAndPorts = new List<FileHostAndPort>
{ {
new FileHostAndPort new FileHostAndPort
{ {
Host = "localhost", Host = "localhost",
Port = 51874, Port = 51874,
} }
}, },
UpstreamPathTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" }, UpstreamHttpMethod = new List<string> { "Get" },
} }
} }
}; };
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51874", "/", 200, "Accept")) this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51874", "/", 200, "Accept"))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning()) .And(x => _steps.GivenOcelotIsRunning())
.And(x => _steps.GivenIAddAHeader("Accept", "text/html")) .And(x => _steps.GivenIAddAHeader("Accept", "text/html"))
.And(x => _steps.GivenIAddAHeader("Accept", "application/xhtml+xml")) .And(x => _steps.GivenIAddAHeader("Accept", "application/xhtml+xml"))
.And(x => _steps.GivenIAddAHeader("Accept", "application/xml")) .And(x => _steps.GivenIAddAHeader("Accept", "application/xml"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("text/html, application/xhtml+xml, application/xml")) .And(x => _steps.ThenTheResponseBodyShouldBe("text/html, application/xhtml+xml, application/xml"))
.BDDfy(); .BDDfy();
} }
private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode) private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode)
{ {
_serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, context => _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, context =>
{ {
if (_count == 0) if (_count == 0)
{ {
context.Response.Cookies.Append("test", "0"); context.Response.Cookies.Append("test", "0");
_count++; _count++;
context.Response.StatusCode = statusCode; context.Response.StatusCode = statusCode;
return Task.CompletedTask; return Task.CompletedTask;
} }
if (context.Request.Cookies.TryGetValue("test", out var cookieValue) || context.Request.Headers.TryGetValue("Set-Cookie", out var headerValue)) if (context.Request.Cookies.TryGetValue("test", out var cookieValue) || context.Request.Headers.TryGetValue("Set-Cookie", out var headerValue))
{ {
if (cookieValue == "0" || headerValue == "test=1; path=/") if (cookieValue == "0" || headerValue == "test=1; path=/")
{ {
context.Response.StatusCode = statusCode; context.Response.StatusCode = statusCode;
return Task.CompletedTask; return Task.CompletedTask;
} }
} }
context.Response.StatusCode = 500; context.Response.StatusCode = 500;
return Task.CompletedTask; return Task.CompletedTask;
}); });
} }
private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string headerKey) private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string headerKey)
{ {
_serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context => _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context =>
{ {
if (context.Request.Headers.TryGetValue(headerKey, out var values)) if (context.Request.Headers.TryGetValue(headerKey, out var values))
{ {
var result = values.First(); var result = values.First();
context.Response.StatusCode = statusCode; context.Response.StatusCode = statusCode;
await context.Response.WriteAsync(result); await context.Response.WriteAsync(result);
} }
}); });
} }
private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string headerKey, string headerValue) private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string headerKey, string headerValue)
{ {
_serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, context => _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, context =>
{ {
context.Response.OnStarting(() => context.Response.OnStarting(() =>
{ {
context.Response.Headers.Add(headerKey, headerValue); context.Response.Headers.Add(headerKey, headerValue);
context.Response.StatusCode = statusCode; context.Response.StatusCode = statusCode;
return Task.CompletedTask; return Task.CompletedTask;
}); });
return Task.CompletedTask; return Task.CompletedTask;
}); });
} }
public void Dispose() public void Dispose()
{ {
_serviceHandler?.Dispose(); _serviceHandler?.Dispose();
_steps.Dispose(); _steps.Dispose();
} }
} }
} }

View File

@ -1,163 +1,163 @@
namespace Ocelot.AcceptanceTests namespace Ocelot.AcceptanceTests
{ {
using Configuration; using Configuration;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Ocelot.Configuration.File; using Ocelot.Configuration.File;
using Requester; using Requester;
using Shouldly; using Shouldly;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
public class HttpClientCachingTests : IDisposable public class HttpClientCachingTests : IDisposable
{ {
private readonly Steps _steps; private readonly Steps _steps;
private string _downstreamPath; private string _downstreamPath;
private readonly ServiceHandler _serviceHandler; private readonly ServiceHandler _serviceHandler;
public HttpClientCachingTests() public HttpClientCachingTests()
{ {
_serviceHandler = new ServiceHandler(); _serviceHandler = new ServiceHandler();
_steps = new Steps(); _steps = new Steps();
} }
[Fact] [Fact]
public void should_cache_one_http_client_same_re_route() public void should_cache_one_http_client_same_re_route()
{ {
var configuration = new FileConfiguration var configuration = new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
{ {
new FileReRoute new FileReRoute
{ {
DownstreamPathTemplate = "/", DownstreamPathTemplate = "/",
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort> DownstreamHostAndPorts = new List<FileHostAndPort>
{ {
new FileHostAndPort new FileHostAndPort
{ {
Host = "localhost", Host = "localhost",
Port = 58814, Port = 58814,
} }
}, },
UpstreamPathTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" }, UpstreamHttpMethod = new List<string> { "Get" },
} }
} }
}; };
var cache = new FakeHttpClientCache(); var cache = new FakeHttpClientCache();
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:58814", 200, "Hello from Laura")) this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:58814", 200, "Hello from Laura"))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunningWithFakeHttpClientCache(cache)) .And(x => _steps.GivenOcelotIsRunningWithFakeHttpClientCache(cache))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.And(x => cache.Count.ShouldBe(1)) .And(x => cache.Count.ShouldBe(1))
.BDDfy(); .BDDfy();
} }
[Fact] [Fact]
public void should_cache_two_http_client_different_re_route() public void should_cache_two_http_client_different_re_route()
{ {
var configuration = new FileConfiguration var configuration = new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
{ {
new FileReRoute new FileReRoute
{ {
DownstreamPathTemplate = "/", DownstreamPathTemplate = "/",
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort> DownstreamHostAndPorts = new List<FileHostAndPort>
{ {
new FileHostAndPort new FileHostAndPort
{ {
Host = "localhost", Host = "localhost",
Port = 58817, Port = 58817,
} }
}, },
UpstreamPathTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" }, UpstreamHttpMethod = new List<string> { "Get" },
}, },
new FileReRoute new FileReRoute
{ {
DownstreamPathTemplate = "/two", DownstreamPathTemplate = "/two",
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort> DownstreamHostAndPorts = new List<FileHostAndPort>
{ {
new FileHostAndPort new FileHostAndPort
{ {
Host = "localhost", Host = "localhost",
Port = 58817, Port = 58817,
} }
}, },
UpstreamPathTemplate = "/two", UpstreamPathTemplate = "/two",
UpstreamHttpMethod = new List<string> { "Get" }, UpstreamHttpMethod = new List<string> { "Get" },
} }
} }
}; };
var cache = new FakeHttpClientCache(); var cache = new FakeHttpClientCache();
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:58817", 200, "Hello from Laura")) this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:58817", 200, "Hello from Laura"))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunningWithFakeHttpClientCache(cache)) .And(x => _steps.GivenOcelotIsRunningWithFakeHttpClientCache(cache))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/two")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/two"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/two")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/two"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/two")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/two"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.And(x => cache.Count.ShouldBe(2)) .And(x => cache.Count.ShouldBe(2))
.BDDfy(); .BDDfy();
} }
private void GivenThereIsAServiceRunningOn(string baseUrl, int statusCode, string responseBody) private void GivenThereIsAServiceRunningOn(string baseUrl, int statusCode, string responseBody)
{ {
_serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, async context => _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, async context =>
{ {
context.Response.StatusCode = statusCode; context.Response.StatusCode = statusCode;
await context.Response.WriteAsync(responseBody); await context.Response.WriteAsync(responseBody);
}); });
} }
public void Dispose() public void Dispose()
{ {
_serviceHandler.Dispose(); _serviceHandler.Dispose();
_steps.Dispose(); _steps.Dispose();
} }
public class FakeHttpClientCache : IHttpClientCache public class FakeHttpClientCache : IHttpClientCache
{ {
private readonly ConcurrentDictionary<DownstreamReRoute, IHttpClient> _httpClientsCache; private readonly ConcurrentDictionary<DownstreamReRoute, IHttpClient> _httpClientsCache;
public FakeHttpClientCache() public FakeHttpClientCache()
{ {
_httpClientsCache = new ConcurrentDictionary<DownstreamReRoute, IHttpClient>(); _httpClientsCache = new ConcurrentDictionary<DownstreamReRoute, IHttpClient>();
} }
public void Set(DownstreamReRoute key, IHttpClient client, TimeSpan expirationTime) public void Set(DownstreamReRoute key, IHttpClient client, TimeSpan expirationTime)
{ {
_httpClientsCache.AddOrUpdate(key, client, (k, oldValue) => client); _httpClientsCache.AddOrUpdate(key, client, (k, oldValue) => client);
} }
public IHttpClient Get(DownstreamReRoute key) public IHttpClient Get(DownstreamReRoute key)
{ {
//todo handle error? //todo handle error?
return _httpClientsCache.TryGetValue(key, out var client) ? client : null; return _httpClientsCache.TryGetValue(key, out var client) ? client : null;
} }
public int Count => _httpClientsCache.Count; public int Count => _httpClientsCache.Count;
} }
} }
} }

View File

@ -1,274 +1,274 @@
namespace Ocelot.AcceptanceTests namespace Ocelot.AcceptanceTests
{ {
using Configuration.File; using Configuration.File;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
public class PollyQoSTests : IDisposable public class PollyQoSTests : IDisposable
{ {
private readonly Steps _steps; private readonly Steps _steps;
private int _requestCount; private int _requestCount;
private readonly ServiceHandler _serviceHandler; private readonly ServiceHandler _serviceHandler;
public PollyQoSTests() public PollyQoSTests()
{ {
_serviceHandler = new ServiceHandler(); _serviceHandler = new ServiceHandler();
_steps = new Steps(); _steps = new Steps();
} }
[Fact] [Fact]
public void should_not_timeout() public void should_not_timeout()
{ {
var configuration = new FileConfiguration var configuration = new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
{ {
new FileReRoute new FileReRoute
{ {
DownstreamPathTemplate = "/", DownstreamPathTemplate = "/",
DownstreamHostAndPorts = new List<FileHostAndPort> DownstreamHostAndPorts = new List<FileHostAndPort>
{ {
new FileHostAndPort new FileHostAndPort
{ {
Host = "localhost", Host = "localhost",
Port = 51569, Port = 51569,
} }
}, },
DownstreamScheme = "http", DownstreamScheme = "http",
UpstreamPathTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Post" }, UpstreamHttpMethod = new List<string> { "Post" },
QoSOptions = new FileQoSOptions QoSOptions = new FileQoSOptions
{ {
TimeoutValue = 1000, TimeoutValue = 1000,
ExceptionsAllowedBeforeBreaking = 10 ExceptionsAllowedBeforeBreaking = 10
} }
} }
} }
}; };
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51569", 200, string.Empty, 10)) this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51569", 200, string.Empty, 10))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunningWithPolly()) .And(x => _steps.GivenOcelotIsRunningWithPolly())
.And(x => _steps.GivenThePostHasContent("postContent")) .And(x => _steps.GivenThePostHasContent("postContent"))
.When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) .When(x => _steps.WhenIPostUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.BDDfy(); .BDDfy();
} }
[Fact] [Fact]
public void should_timeout() public void should_timeout()
{ {
var configuration = new FileConfiguration var configuration = new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
{ {
new FileReRoute new FileReRoute
{ {
DownstreamPathTemplate = "/", DownstreamPathTemplate = "/",
DownstreamHostAndPorts = new List<FileHostAndPort> DownstreamHostAndPorts = new List<FileHostAndPort>
{ {
new FileHostAndPort new FileHostAndPort
{ {
Host = "localhost", Host = "localhost",
Port = 51579, Port = 51579,
} }
}, },
DownstreamScheme = "http", DownstreamScheme = "http",
UpstreamPathTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Post" }, UpstreamHttpMethod = new List<string> { "Post" },
QoSOptions = new FileQoSOptions QoSOptions = new FileQoSOptions
{ {
TimeoutValue = 10, TimeoutValue = 10,
ExceptionsAllowedBeforeBreaking = 10 ExceptionsAllowedBeforeBreaking = 10
} }
} }
} }
}; };
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51579", 201, string.Empty, 1000)) this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51579", 201, string.Empty, 1000))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunningWithPolly()) .And(x => _steps.GivenOcelotIsRunningWithPolly())
.And(x => _steps.GivenThePostHasContent("postContent")) .And(x => _steps.GivenThePostHasContent("postContent"))
.When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) .When(x => _steps.WhenIPostUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable))
.BDDfy(); .BDDfy();
} }
[Fact] [Fact]
public void should_open_circuit_breaker_then_close() public void should_open_circuit_breaker_then_close()
{ {
var configuration = new FileConfiguration var configuration = new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
{ {
new FileReRoute new FileReRoute
{ {
DownstreamPathTemplate = "/", DownstreamPathTemplate = "/",
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort> DownstreamHostAndPorts = new List<FileHostAndPort>
{ {
new FileHostAndPort new FileHostAndPort
{ {
Host = "localhost", Host = "localhost",
Port = 51892, Port = 51892,
} }
}, },
UpstreamPathTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" }, UpstreamHttpMethod = new List<string> { "Get" },
QoSOptions = new FileQoSOptions QoSOptions = new FileQoSOptions
{ {
ExceptionsAllowedBeforeBreaking = 1, ExceptionsAllowedBeforeBreaking = 1,
TimeoutValue = 500, TimeoutValue = 500,
DurationOfBreak = 1000 DurationOfBreak = 1000
}, },
} }
} }
}; };
this.Given(x => x.GivenThereIsAPossiblyBrokenServiceRunningOn("http://localhost:51892", "Hello from Laura")) this.Given(x => x.GivenThereIsAPossiblyBrokenServiceRunningOn("http://localhost:51892", "Hello from Laura"))
.Given(x => _steps.GivenThereIsAConfiguration(configuration)) .Given(x => _steps.GivenThereIsAConfiguration(configuration))
.Given(x => _steps.GivenOcelotIsRunningWithPolly()) .Given(x => _steps.GivenOcelotIsRunningWithPolly())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.Given(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Given(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) .Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable))
.Given(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Given(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) .Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable))
.Given(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Given(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) .Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable))
.Given(x => x.GivenIWaitMilliseconds(3000)) .Given(x => x.GivenIWaitMilliseconds(3000))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.BDDfy(); .BDDfy();
} }
[Fact] [Fact]
public void open_circuit_should_not_effect_different_reRoute() public void open_circuit_should_not_effect_different_reRoute()
{ {
var configuration = new FileConfiguration var configuration = new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
{ {
new FileReRoute new FileReRoute
{ {
DownstreamPathTemplate = "/", DownstreamPathTemplate = "/",
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort> DownstreamHostAndPorts = new List<FileHostAndPort>
{ {
new FileHostAndPort new FileHostAndPort
{ {
Host = "localhost", Host = "localhost",
Port = 51870, Port = 51870,
} }
}, },
UpstreamPathTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" }, UpstreamHttpMethod = new List<string> { "Get" },
QoSOptions = new FileQoSOptions QoSOptions = new FileQoSOptions
{ {
ExceptionsAllowedBeforeBreaking = 1, ExceptionsAllowedBeforeBreaking = 1,
TimeoutValue = 500, TimeoutValue = 500,
DurationOfBreak = 1000 DurationOfBreak = 1000
} }
}, },
new FileReRoute new FileReRoute
{ {
DownstreamPathTemplate = "/", DownstreamPathTemplate = "/",
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort> DownstreamHostAndPorts = new List<FileHostAndPort>
{ {
new FileHostAndPort new FileHostAndPort
{ {
Host = "localhost", Host = "localhost",
Port = 51880, Port = 51880,
} }
}, },
UpstreamPathTemplate = "/working", UpstreamPathTemplate = "/working",
UpstreamHttpMethod = new List<string> { "Get" }, UpstreamHttpMethod = new List<string> { "Get" },
} }
} }
}; };
this.Given(x => x.GivenThereIsAPossiblyBrokenServiceRunningOn("http://localhost:51870", "Hello from Laura")) this.Given(x => x.GivenThereIsAPossiblyBrokenServiceRunningOn("http://localhost:51870", "Hello from Laura"))
.And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51880/", 200, "Hello from Tom", 0)) .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51880/", 200, "Hello from Tom", 0))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunningWithPolly()) .And(x => _steps.GivenOcelotIsRunningWithPolly())
.And(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .And(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.And(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .And(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable))
.And(x => _steps.WhenIGetUrlOnTheApiGateway("/working")) .And(x => _steps.WhenIGetUrlOnTheApiGateway("/working"))
.And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Tom")) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Tom"))
.And(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .And(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable))
.And(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .And(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable))
.And(x => x.GivenIWaitMilliseconds(3000)) .And(x => x.GivenIWaitMilliseconds(3000))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.BDDfy(); .BDDfy();
} }
private void GivenIWaitMilliseconds(int ms) private void GivenIWaitMilliseconds(int ms)
{ {
Thread.Sleep(ms); Thread.Sleep(ms);
} }
private void GivenThereIsAPossiblyBrokenServiceRunningOn(string url, string responseBody) private void GivenThereIsAPossiblyBrokenServiceRunningOn(string url, string responseBody)
{ {
_serviceHandler.GivenThereIsAServiceRunningOn(url, async context => _serviceHandler.GivenThereIsAServiceRunningOn(url, async context =>
{ {
//circuit starts closed //circuit starts closed
if (_requestCount == 0) if (_requestCount == 0)
{ {
_requestCount++; _requestCount++;
context.Response.StatusCode = 200; context.Response.StatusCode = 200;
await context.Response.WriteAsync(responseBody); await context.Response.WriteAsync(responseBody);
return; return;
} }
//request one times out and polly throws exception, circuit opens //request one times out and polly throws exception, circuit opens
if (_requestCount == 1) if (_requestCount == 1)
{ {
_requestCount++; _requestCount++;
await Task.Delay(1000); await Task.Delay(1000);
context.Response.StatusCode = 200; context.Response.StatusCode = 200;
return; return;
} }
//after break closes we return 200 OK //after break closes we return 200 OK
if (_requestCount == 2) if (_requestCount == 2)
{ {
context.Response.StatusCode = 200; context.Response.StatusCode = 200;
await context.Response.WriteAsync(responseBody); await context.Response.WriteAsync(responseBody);
} }
}); });
} }
private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody, int timeout) private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody, int timeout)
{ {
_serviceHandler.GivenThereIsAServiceRunningOn(url, async context => _serviceHandler.GivenThereIsAServiceRunningOn(url, async context =>
{ {
Thread.Sleep(timeout); Thread.Sleep(timeout);
context.Response.StatusCode = statusCode; context.Response.StatusCode = statusCode;
await context.Response.WriteAsync(responseBody); await context.Response.WriteAsync(responseBody);
}); });
} }
public void Dispose() public void Dispose()
{ {
_serviceHandler?.Dispose(); _serviceHandler?.Dispose();
_steps.Dispose(); _steps.Dispose();
} }
} }
} }

View File

@ -1,68 +1,68 @@
namespace Ocelot.AcceptanceTests namespace Ocelot.AcceptanceTests
{ {
using Ocelot.Configuration.File; using Ocelot.Configuration.File;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
public class ResponseCodeTests : IDisposable public class ResponseCodeTests : IDisposable
{ {
private readonly Steps _steps; private readonly Steps _steps;
private readonly ServiceHandler _serviceHandler; private readonly ServiceHandler _serviceHandler;
public ResponseCodeTests() public ResponseCodeTests()
{ {
_serviceHandler = new ServiceHandler(); _serviceHandler = new ServiceHandler();
_steps = new Steps(); _steps = new Steps();
} }
[Fact] [Fact]
public void should_return_response_304_when_service_returns_304() public void should_return_response_304_when_service_returns_304()
{ {
var configuration = new FileConfiguration var configuration = new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
{ {
new FileReRoute new FileReRoute
{ {
DownstreamPathTemplate = "/{everything}", DownstreamPathTemplate = "/{everything}",
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort> DownstreamHostAndPorts = new List<FileHostAndPort>
{ {
new FileHostAndPort new FileHostAndPort
{ {
Host = "localhost", Host = "localhost",
Port = 59892, Port = 59892,
} }
}, },
UpstreamPathTemplate = "/{everything}", UpstreamPathTemplate = "/{everything}",
UpstreamHttpMethod = new List<string> { "Get" }, UpstreamHttpMethod = new List<string> { "Get" },
} }
} }
}; };
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:59892", "/inline.132.bundle.js", 304)) this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:59892", "/inline.132.bundle.js", 304))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning()) .And(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/inline.132.bundle.js")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/inline.132.bundle.js"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotModified)) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotModified))
.BDDfy(); .BDDfy();
} }
private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode) private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode)
{ {
_serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context => _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context =>
{ {
context.Response.StatusCode = statusCode; context.Response.StatusCode = statusCode;
}); });
} }
public void Dispose() public void Dispose()
{ {
_serviceHandler?.Dispose(); _serviceHandler?.Dispose();
_steps.Dispose(); _steps.Dispose();
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,204 +1,204 @@
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Newtonsoft.Json; using Newtonsoft.Json;
using Ocelot.Configuration.File; using Ocelot.Configuration.File;
using Ocelot.DependencyInjection; using Ocelot.DependencyInjection;
using Ocelot.Middleware; using Ocelot.Middleware;
using Shouldly; using Shouldly;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
namespace Ocelot.IntegrationTests namespace Ocelot.IntegrationTests
{ {
public class ThreadSafeHeadersTests : IDisposable public class ThreadSafeHeadersTests : IDisposable
{ {
private readonly HttpClient _httpClient; private readonly HttpClient _httpClient;
private IWebHost _builder; private IWebHost _builder;
private IWebHostBuilder _webHostBuilder; private IWebHostBuilder _webHostBuilder;
private readonly string _ocelotBaseUrl; private readonly string _ocelotBaseUrl;
private IWebHost _downstreamBuilder; private IWebHost _downstreamBuilder;
private readonly Random _random; private readonly Random _random;
private readonly ConcurrentBag<ThreadSafeHeadersTestResult> _results; private readonly ConcurrentBag<ThreadSafeHeadersTestResult> _results;
public ThreadSafeHeadersTests() public ThreadSafeHeadersTests()
{ {
_results = new ConcurrentBag<ThreadSafeHeadersTestResult>(); _results = new ConcurrentBag<ThreadSafeHeadersTestResult>();
_random = new Random(); _random = new Random();
_httpClient = new HttpClient(); _httpClient = new HttpClient();
_ocelotBaseUrl = "http://localhost:5001"; _ocelotBaseUrl = "http://localhost:5001";
_httpClient.BaseAddress = new Uri(_ocelotBaseUrl); _httpClient.BaseAddress = new Uri(_ocelotBaseUrl);
} }
[Fact] [Fact]
public void should_return_same_response_for_each_different_header_under_load_to_downsteam_service() public void should_return_same_response_for_each_different_header_under_load_to_downsteam_service()
{ {
var configuration = new FileConfiguration var configuration = new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
{ {
new FileReRoute new FileReRoute
{ {
DownstreamPathTemplate = "/", DownstreamPathTemplate = "/",
DownstreamScheme = "http", DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort> DownstreamHostAndPorts = new List<FileHostAndPort>
{ {
new FileHostAndPort new FileHostAndPort
{ {
Host = "localhost", Host = "localhost",
Port = 51611, Port = 51611,
}, },
}, },
UpstreamPathTemplate = "/", UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" }, UpstreamHttpMethod = new List<string> { "Get" },
}, },
}, },
}; };
this.Given(x => GivenThereIsAConfiguration(configuration)) this.Given(x => GivenThereIsAConfiguration(configuration))
.And(x => GivenThereIsAServiceRunningOn("http://localhost:51611")) .And(x => GivenThereIsAServiceRunningOn("http://localhost:51611"))
.And(x => GivenOcelotIsRunning()) .And(x => GivenOcelotIsRunning())
.When(x => WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues("/", 300)) .When(x => WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues("/", 300))
.Then(x => ThenTheSameHeaderValuesAreReturnedByTheDownstreamService()) .Then(x => ThenTheSameHeaderValuesAreReturnedByTheDownstreamService())
.BDDfy(); .BDDfy();
} }
private void GivenThereIsAServiceRunningOn(string url) private void GivenThereIsAServiceRunningOn(string url)
{ {
_downstreamBuilder = new WebHostBuilder() _downstreamBuilder = new WebHostBuilder()
.UseUrls(url) .UseUrls(url)
.UseKestrel() .UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory()) .UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration() .UseIISIntegration()
.UseUrls(url) .UseUrls(url)
.Configure(app => .Configure(app =>
{ {
app.Run(async context => app.Run(async context =>
{ {
var header = context.Request.Headers["ThreadSafeHeadersTest"]; var header = context.Request.Headers["ThreadSafeHeadersTest"];
context.Response.StatusCode = 200; context.Response.StatusCode = 200;
await context.Response.WriteAsync(header[0]); await context.Response.WriteAsync(header[0]);
}); });
}) })
.Build(); .Build();
_downstreamBuilder.Start(); _downstreamBuilder.Start();
} }
private void GivenOcelotIsRunning() private void GivenOcelotIsRunning()
{ {
_webHostBuilder = new WebHostBuilder() _webHostBuilder = new WebHostBuilder()
.UseUrls(_ocelotBaseUrl) .UseUrls(_ocelotBaseUrl)
.UseKestrel() .UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory()) .UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration((hostingContext, config) => .ConfigureAppConfiguration((hostingContext, config) =>
{ {
config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath);
var env = hostingContext.HostingEnvironment; var env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) 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.AddJsonFile("ocelot.json", false, false);
config.AddEnvironmentVariables(); config.AddEnvironmentVariables();
}) })
.ConfigureServices(x => .ConfigureServices(x =>
{ {
x.AddOcelot(); x.AddOcelot();
}) })
.Configure(app => .Configure(app =>
{ {
app.UseOcelot().Wait(); app.UseOcelot().Wait();
}); });
_builder = _webHostBuilder.Build(); _builder = _webHostBuilder.Build();
_builder.Start(); _builder.Start();
} }
private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration)
{ {
var configurationPath = $"{Directory.GetCurrentDirectory()}/ocelot.json"; var configurationPath = $"{Directory.GetCurrentDirectory()}/ocelot.json";
var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration);
if (File.Exists(configurationPath)) if (File.Exists(configurationPath))
{ {
File.Delete(configurationPath); File.Delete(configurationPath);
} }
File.WriteAllText(configurationPath, jsonConfiguration); File.WriteAllText(configurationPath, jsonConfiguration);
var text = File.ReadAllText(configurationPath); var text = File.ReadAllText(configurationPath);
configurationPath = $"{AppContext.BaseDirectory}/ocelot.json"; configurationPath = $"{AppContext.BaseDirectory}/ocelot.json";
if (File.Exists(configurationPath)) if (File.Exists(configurationPath))
{ {
File.Delete(configurationPath); File.Delete(configurationPath);
} }
File.WriteAllText(configurationPath, jsonConfiguration); File.WriteAllText(configurationPath, jsonConfiguration);
text = File.ReadAllText(configurationPath); text = File.ReadAllText(configurationPath);
} }
private void WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues(string url, int times) private void WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues(string url, int times)
{ {
var tasks = new Task[times]; var tasks = new Task[times];
for (int i = 0; i < times; i++) for (int i = 0; i < times; i++)
{ {
var urlCopy = url; var urlCopy = url;
var random = _random.Next(0, 50); var random = _random.Next(0, 50);
tasks[i] = GetForThreadSafeHeadersTest(urlCopy, random); tasks[i] = GetForThreadSafeHeadersTest(urlCopy, random);
} }
Task.WaitAll(tasks); Task.WaitAll(tasks);
} }
private async Task GetForThreadSafeHeadersTest(string url, int random) private async Task GetForThreadSafeHeadersTest(string url, int random)
{ {
var request = new HttpRequestMessage(HttpMethod.Get, url); var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Add("ThreadSafeHeadersTest", new List<string> { random.ToString() }); request.Headers.Add("ThreadSafeHeadersTest", new List<string> { random.ToString() });
var response = await _httpClient.SendAsync(request); var response = await _httpClient.SendAsync(request);
var content = await response.Content.ReadAsStringAsync(); var content = await response.Content.ReadAsStringAsync();
int result = int.Parse(content); int result = int.Parse(content);
var tshtr = new ThreadSafeHeadersTestResult(result, random); var tshtr = new ThreadSafeHeadersTestResult(result, random);
_results.Add(tshtr); _results.Add(tshtr);
} }
private void ThenTheSameHeaderValuesAreReturnedByTheDownstreamService() private void ThenTheSameHeaderValuesAreReturnedByTheDownstreamService()
{ {
foreach (var result in _results) foreach (var result in _results)
{ {
result.Result.ShouldBe(result.Random); result.Result.ShouldBe(result.Random);
} }
} }
public void Dispose() public void Dispose()
{ {
_builder?.Dispose(); _builder?.Dispose();
_httpClient?.Dispose(); _httpClient?.Dispose();
_downstreamBuilder?.Dispose(); _downstreamBuilder?.Dispose();
} }
private class ThreadSafeHeadersTestResult private class ThreadSafeHeadersTestResult
{ {
public ThreadSafeHeadersTestResult(int result, int random) public ThreadSafeHeadersTestResult(int result, int random)
{ {
Result = result; Result = result;
Random = random; Random = random;
} }
public int Result { get; private set; } public int Result { get; private set; }
public int Random { get; private set; } public int Random { get; private set; }
} }
} }
} }