mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-22 06:22:50 +08:00
commit
08c2ac1b05
2
.dockerignore
Normal file
2
.dockerignore
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*/*/bin
|
||||||
|
*/*/obj
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -252,3 +252,6 @@ _templates/
|
|||||||
|
|
||||||
# JetBrains Rider
|
# JetBrains Rider
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
|
# Test Results
|
||||||
|
*.trx
|
||||||
|
@ -11,9 +11,9 @@ dist: trusty
|
|||||||
osx_image: xcode9.2
|
osx_image: xcode9.2
|
||||||
|
|
||||||
mono:
|
mono:
|
||||||
- 4.4.2
|
- 5.10.0
|
||||||
|
|
||||||
dotnet: 2.1.301
|
dotnet: 2.1.500
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- git fetch --unshallow # Travis always does a shallow clone, but GitVersion needs the full history including branches and tags
|
- git fetch --unshallow # Travis always does a shallow clone, but GitVersion needs the full history including branches and tags
|
||||||
|
39
Dockerfile
Normal file
39
Dockerfile
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
#This is the base image used for any ran images
|
||||||
|
FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base
|
||||||
|
WORKDIR /app
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
#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:
|
||||||
|
#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
|
||||||
|
FROM microsoft/dotnet:2.1.502-sdk AS builder
|
||||||
|
WORKDIR /build
|
||||||
|
#First we add only the project files so that we can cache nuget packages with dotnet restore
|
||||||
|
COPY Ocelot.sln Ocelot.sln
|
||||||
|
COPY src/Ocelot/Ocelot.csproj src/Ocelot/Ocelot.csproj
|
||||||
|
COPY test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj
|
||||||
|
COPY test/Ocelot.ManualTest/Ocelot.ManualTest.csproj test/Ocelot.ManualTest/Ocelot.ManualTest.csproj
|
||||||
|
COPY test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj
|
||||||
|
COPY test/Ocelot.UnitTests/Ocelot.UnitTests.csproj test/Ocelot.UnitTests/Ocelot.UnitTests.csproj
|
||||||
|
COPY test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj
|
||||||
|
RUN dotnet restore
|
||||||
|
#Now we add the rest of the source and run a complete build... --no-restore is used because nuget should be resolved at this point
|
||||||
|
COPY codeanalysis.ruleset codeanalysis.ruleset
|
||||||
|
COPY src src
|
||||||
|
COPY test test
|
||||||
|
ARG build_configuration=Debug
|
||||||
|
RUN dotnet build --no-restore -c ${build_configuration}
|
||||||
|
ENTRYPOINT ["dotnet"]
|
||||||
|
|
||||||
|
#This is just for holding the published manual tests...
|
||||||
|
FROM builder AS manual-test-publish
|
||||||
|
ARG build_configuration=Debug
|
||||||
|
RUN dotnet publish --no-build -c ${build_configuration} -o /app test/Ocelot.ManualTest
|
||||||
|
|
||||||
|
#Run manual tests! This is the default run option.
|
||||||
|
#docker build -t ocelot-manual-test . && docker run --net host ocelot-manual-test
|
||||||
|
FROM base AS manual-test
|
||||||
|
ENV ASPNETCORE_ENVIRONMENT=Development
|
||||||
|
COPY --from=manual-test-publish /app .
|
||||||
|
ENTRYPOINT ["dotnet", "Ocelot.ManualTest.dll"]
|
53
Ocelot.sln
53
Ocelot.sln
@ -7,16 +7,18 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5CFB79B7-C9D
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3FA7C349-DBE8-4904-A2CE-015B8869CE6C}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3FA7C349-DBE8-4904-A2CE-015B8869CE6C}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
.dockerignore = .dockerignore
|
||||||
.gitignore = .gitignore
|
.gitignore = .gitignore
|
||||||
build-and-release-unstable.ps1 = build-and-release-unstable.ps1
|
build-and-release-unstable.ps1 = build-and-release-unstable.ps1
|
||||||
build-and-run-tests.ps1 = build-and-run-tests.ps1
|
build-and-run-tests.ps1 = build-and-run-tests.ps1
|
||||||
build.cake = build.cake
|
build.cake = build.cake
|
||||||
build.ps1 = build.ps1
|
build.ps1 = build.ps1
|
||||||
codeanalysis.ruleset = codeanalysis.ruleset
|
codeanalysis.ruleset = codeanalysis.ruleset
|
||||||
|
docker-compose.yaml = docker-compose.yaml
|
||||||
|
Dockerfile = Dockerfile
|
||||||
GitVersion.yml = GitVersion.yml
|
GitVersion.yml = GitVersion.yml
|
||||||
global.json = global.json
|
global.json = global.json
|
||||||
LICENSE.md = LICENSE.md
|
LICENSE.md = LICENSE.md
|
||||||
ocelot.postman_collection.json = ocelot.postman_collection.json
|
|
||||||
README.md = README.md
|
README.md = README.md
|
||||||
release.ps1 = release.ps1
|
release.ps1 = release.ps1
|
||||||
ReleaseNotes.md = ReleaseNotes.md
|
ReleaseNotes.md = ReleaseNotes.md
|
||||||
@ -40,6 +42,20 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Benchmarks", "test\O
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.IntegrationTests", "test\Ocelot.IntegrationTests\Ocelot.IntegrationTests.csproj", "{D4575572-99CA-4530-8737-C296EDA326F8}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.IntegrationTests", "test\Ocelot.IntegrationTests\Ocelot.IntegrationTests.csproj", "{D4575572-99CA-4530-8737-C296EDA326F8}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Administration", "src\Ocelot.Administration\Ocelot.Administration.csproj", "{F69CEF43-27D2-4940-A47A-FCA879E371BC}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Cache.CacheManager", "src\Ocelot.Cache.CacheManager\Ocelot.Cache.CacheManager.csproj", "{EB9F438F-062E-499F-B6EA-4412BEF6D74C}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Provider.Consul", "src\Ocelot.Provider.Consul\Ocelot.Provider.Consul.csproj", "{02F5AE4D-9C36-4E58-B7C6-012CBBDEFDE0}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Provider.Eureka", "src\Ocelot.Provider.Eureka\Ocelot.Provider.Eureka.csproj", "{9BBD3586-145C-4FA0-91C5-9ED58287D753}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Provider.Polly", "src\Ocelot.Provider.Polly\Ocelot.Provider.Polly.csproj", "{1F6E5DCF-8A2E-4E24-A25D-064362DE8D0E}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Provider.Rafty", "src\Ocelot.Provider.Rafty\Ocelot.Provider.Rafty.csproj", "{AC153C67-EF18-47E6-A230-F0D3CF5F0A98}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Tracing.Butterfly", "src\Ocelot.Tracing.Butterfly\Ocelot.Tracing.Butterfly.csproj", "{6045E23D-669C-4F27-AF8E-8EEE6DB3557F}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -70,6 +86,34 @@ Global
|
|||||||
{D4575572-99CA-4530-8737-C296EDA326F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{D4575572-99CA-4530-8737-C296EDA326F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{D4575572-99CA-4530-8737-C296EDA326F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{D4575572-99CA-4530-8737-C296EDA326F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{D4575572-99CA-4530-8737-C296EDA326F8}.Release|Any CPU.Build.0 = Release|Any CPU
|
{D4575572-99CA-4530-8737-C296EDA326F8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{F69CEF43-27D2-4940-A47A-FCA879E371BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{F69CEF43-27D2-4940-A47A-FCA879E371BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{F69CEF43-27D2-4940-A47A-FCA879E371BC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{F69CEF43-27D2-4940-A47A-FCA879E371BC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{EB9F438F-062E-499F-B6EA-4412BEF6D74C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{EB9F438F-062E-499F-B6EA-4412BEF6D74C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{EB9F438F-062E-499F-B6EA-4412BEF6D74C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{EB9F438F-062E-499F-B6EA-4412BEF6D74C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{02F5AE4D-9C36-4E58-B7C6-012CBBDEFDE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{02F5AE4D-9C36-4E58-B7C6-012CBBDEFDE0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{02F5AE4D-9C36-4E58-B7C6-012CBBDEFDE0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{02F5AE4D-9C36-4E58-B7C6-012CBBDEFDE0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{9BBD3586-145C-4FA0-91C5-9ED58287D753}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{9BBD3586-145C-4FA0-91C5-9ED58287D753}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{9BBD3586-145C-4FA0-91C5-9ED58287D753}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{9BBD3586-145C-4FA0-91C5-9ED58287D753}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{1F6E5DCF-8A2E-4E24-A25D-064362DE8D0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{1F6E5DCF-8A2E-4E24-A25D-064362DE8D0E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{1F6E5DCF-8A2E-4E24-A25D-064362DE8D0E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{1F6E5DCF-8A2E-4E24-A25D-064362DE8D0E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{AC153C67-EF18-47E6-A230-F0D3CF5F0A98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{AC153C67-EF18-47E6-A230-F0D3CF5F0A98}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{AC153C67-EF18-47E6-A230-F0D3CF5F0A98}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{AC153C67-EF18-47E6-A230-F0D3CF5F0A98}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{6045E23D-669C-4F27-AF8E-8EEE6DB3557F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{6045E23D-669C-4F27-AF8E-8EEE6DB3557F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{6045E23D-669C-4F27-AF8E-8EEE6DB3557F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{6045E23D-669C-4F27-AF8E-8EEE6DB3557F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@ -81,6 +125,13 @@ Global
|
|||||||
{02BBF4C5-517E-4157-8D21-4B8B9E118B7A} = {5B401523-36DA-4491-B73A-7590A26E420B}
|
{02BBF4C5-517E-4157-8D21-4B8B9E118B7A} = {5B401523-36DA-4491-B73A-7590A26E420B}
|
||||||
{106B49E6-95F6-4A7B-B81C-96BFA74AF035} = {5B401523-36DA-4491-B73A-7590A26E420B}
|
{106B49E6-95F6-4A7B-B81C-96BFA74AF035} = {5B401523-36DA-4491-B73A-7590A26E420B}
|
||||||
{D4575572-99CA-4530-8737-C296EDA326F8} = {5B401523-36DA-4491-B73A-7590A26E420B}
|
{D4575572-99CA-4530-8737-C296EDA326F8} = {5B401523-36DA-4491-B73A-7590A26E420B}
|
||||||
|
{F69CEF43-27D2-4940-A47A-FCA879E371BC} = {5CFB79B7-C9DC-45A4-9A75-625D92471702}
|
||||||
|
{EB9F438F-062E-499F-B6EA-4412BEF6D74C} = {5CFB79B7-C9DC-45A4-9A75-625D92471702}
|
||||||
|
{02F5AE4D-9C36-4E58-B7C6-012CBBDEFDE0} = {5CFB79B7-C9DC-45A4-9A75-625D92471702}
|
||||||
|
{9BBD3586-145C-4FA0-91C5-9ED58287D753} = {5CFB79B7-C9DC-45A4-9A75-625D92471702}
|
||||||
|
{1F6E5DCF-8A2E-4E24-A25D-064362DE8D0E} = {5CFB79B7-C9DC-45A4-9A75-625D92471702}
|
||||||
|
{AC153C67-EF18-47E6-A230-F0D3CF5F0A98} = {5CFB79B7-C9DC-45A4-9A75-625D92471702}
|
||||||
|
{6045E23D-669C-4F27-AF8E-8EEE6DB3557F} = {5CFB79B7-C9DC-45A4-9A75-625D92471702}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {21476EFF-778A-4F97-8A56-D1AF1CEC0C48}
|
SolutionGuid = {21476EFF-778A-4F97-8A56-D1AF1CEC0C48}
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
# 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 orientated 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
|
||||||
@ -96,6 +96,3 @@ If you think this project is worth supporting financially please make a contribu
|
|||||||
## Things that are currently annoying me
|
## Things that are currently annoying me
|
||||||
|
|
||||||
[ Get more details at **codescene.io**.](https://codescene.io/projects/697/jobs/latest-successful/results)
|
[ Get more details at **codescene.io**.](https://codescene.io/projects/697/jobs/latest-successful/results)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
55
build.cake
55
build.cake
@ -17,7 +17,7 @@ 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 = 82d;
|
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";
|
||||||
|
|
||||||
@ -263,14 +263,31 @@ Task("CreatePackages")
|
|||||||
.Does(() =>
|
.Does(() =>
|
||||||
{
|
{
|
||||||
EnsureDirectoryExists(packagesDir);
|
EnsureDirectoryExists(packagesDir);
|
||||||
CopyFiles("./src/**/Ocelot.*.nupkg", packagesDir);
|
|
||||||
|
CopyFiles("./src/**/Release/Ocelot.*.nupkg", packagesDir);
|
||||||
|
|
||||||
//GenerateReleaseNotes(releaseNotesFile);
|
//GenerateReleaseNotes(releaseNotesFile);
|
||||||
|
|
||||||
System.IO.File.WriteAllLines(artifactsFile, new[]{
|
var projectFiles = GetFiles("./src/**/Release/Ocelot.*.nupkg");
|
||||||
"nuget:Ocelot." + buildVersion + ".nupkg",
|
|
||||||
//"releaseNotes:releasenotes.md"
|
foreach(var projectFile in projectFiles)
|
||||||
});
|
{
|
||||||
|
System.IO.File.AppendAllLines(artifactsFile, new[]{
|
||||||
|
projectFile.GetFilename().FullPath,
|
||||||
|
//"releaseNotes:releasenotes.md"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var artifacts = System.IO.File
|
||||||
|
.ReadAllLines(artifactsFile)
|
||||||
|
.Distinct();
|
||||||
|
|
||||||
|
foreach(var artifact in artifacts)
|
||||||
|
{
|
||||||
|
var codePackage = packagesDir + File(artifact);
|
||||||
|
|
||||||
|
Information("Created package " + codePackage);
|
||||||
|
}
|
||||||
|
|
||||||
if (AppVeyor.IsRunningOnAppVeyor)
|
if (AppVeyor.IsRunningOnAppVeyor)
|
||||||
{
|
{
|
||||||
@ -345,8 +362,6 @@ Task("DownloadGitHubReleaseArtifacts")
|
|||||||
|
|
||||||
Information("Release url " + releaseUrl);
|
Information("Release url " + releaseUrl);
|
||||||
|
|
||||||
//var releaseJson = Newtonsoft.Json.Linq.JObject.Parse(GetResource(releaseUrl));
|
|
||||||
|
|
||||||
var assets_url = Newtonsoft.Json.Linq.JObject.Parse(GetResource(releaseUrl))
|
var assets_url = Newtonsoft.Json.Linq.JObject.Parse(GetResource(releaseUrl))
|
||||||
.GetValue("assets_url")
|
.GetValue("assets_url")
|
||||||
.Value<string>();
|
.Value<string>();
|
||||||
@ -451,21 +466,23 @@ private void PublishPackages(ConvertableDirectoryPath packagesDir, ConvertableFi
|
|||||||
{
|
{
|
||||||
var artifacts = System.IO.File
|
var artifacts = System.IO.File
|
||||||
.ReadAllLines(artifactsFile)
|
.ReadAllLines(artifactsFile)
|
||||||
.Select(l => l.Split(':'))
|
.Distinct();
|
||||||
.ToDictionary(v => v[0], v => v[1]);
|
|
||||||
|
|
||||||
var codePackage = packagesDir + File(artifacts["nuget"]);
|
foreach(var artifact in artifacts)
|
||||||
|
{
|
||||||
|
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
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// gets the resource from the specified url
|
/// gets the resource from the specified url
|
||||||
|
25
build.ps1
25
build.ps1
@ -25,10 +25,6 @@ Specifies the amount of information to be displayed.
|
|||||||
Shows description about tasks.
|
Shows description about tasks.
|
||||||
.PARAMETER DryRun
|
.PARAMETER DryRun
|
||||||
Performs a dry run.
|
Performs a dry run.
|
||||||
.PARAMETER Experimental
|
|
||||||
Uses the nightly builds of the Roslyn script engine.
|
|
||||||
.PARAMETER Mono
|
|
||||||
Uses the Mono Compiler rather than the Roslyn script engine.
|
|
||||||
.PARAMETER SkipToolPackageRestore
|
.PARAMETER SkipToolPackageRestore
|
||||||
Skips restoring of packages.
|
Skips restoring of packages.
|
||||||
.PARAMETER ScriptArgs
|
.PARAMETER ScriptArgs
|
||||||
@ -49,13 +45,25 @@ Param(
|
|||||||
[switch]$ShowDescription,
|
[switch]$ShowDescription,
|
||||||
[Alias("WhatIf", "Noop")]
|
[Alias("WhatIf", "Noop")]
|
||||||
[switch]$DryRun,
|
[switch]$DryRun,
|
||||||
[switch]$Experimental,
|
|
||||||
[switch]$Mono,
|
|
||||||
[switch]$SkipToolPackageRestore,
|
[switch]$SkipToolPackageRestore,
|
||||||
[Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)]
|
[Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)]
|
||||||
[string[]]$ScriptArgs
|
[string[]]$ScriptArgs
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Attempt to set highest encryption available for SecurityProtocol.
|
||||||
|
# PowerShell will not set this by default (until maybe .NET 4.6.x). This
|
||||||
|
# will typically produce a message for PowerShell v2 (just an info
|
||||||
|
# message though)
|
||||||
|
try {
|
||||||
|
# Set TLS 1.2 (3072), then TLS 1.1 (768), then TLS 1.0 (192), finally SSL 3.0 (48)
|
||||||
|
# Use integers because the enumeration values for TLS 1.2 and TLS 1.1 won't
|
||||||
|
# exist in .NET 4.0, even though they are addressable if .NET 4.5+ is
|
||||||
|
# installed (.NET 4.5 is an in-place upgrade).
|
||||||
|
[System.Net.ServicePointManager]::SecurityProtocol = 3072 -bor 768 -bor 192 -bor 48
|
||||||
|
} catch {
|
||||||
|
Write-Output 'Unable to set PowerShell to use TLS 1.2 and TLS 1.1 due to old .NET Framework installed. If you see underlying connection closed or trust errors, you may need to upgrade to .NET Framework 4.5+ and PowerShell v3'
|
||||||
|
}
|
||||||
|
|
||||||
[Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null
|
[Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null
|
||||||
function MD5HashFile([string] $filePath)
|
function MD5HashFile([string] $filePath)
|
||||||
{
|
{
|
||||||
@ -118,7 +126,8 @@ if (!(Test-Path $PACKAGES_CONFIG)) {
|
|||||||
Write-Verbose -Message "Downloading packages.config..."
|
Write-Verbose -Message "Downloading packages.config..."
|
||||||
try {
|
try {
|
||||||
$wc = GetProxyEnabledWebClient
|
$wc = GetProxyEnabledWebClient
|
||||||
$wc.DownloadFile("https://cakebuild.net/download/bootstrapper/packages", $PACKAGES_CONFIG) } catch {
|
$wc.DownloadFile("https://cakebuild.net/download/bootstrapper/packages", $PACKAGES_CONFIG)
|
||||||
|
} catch {
|
||||||
Throw "Could not download packages.config."
|
Throw "Could not download packages.config."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -225,8 +234,6 @@ if ($Configuration) { $cakeArguments += "-configuration=$Configuration" }
|
|||||||
if ($Verbosity) { $cakeArguments += "-verbosity=$Verbosity" }
|
if ($Verbosity) { $cakeArguments += "-verbosity=$Verbosity" }
|
||||||
if ($ShowDescription) { $cakeArguments += "-showdescription" }
|
if ($ShowDescription) { $cakeArguments += "-showdescription" }
|
||||||
if ($DryRun) { $cakeArguments += "-dryrun" }
|
if ($DryRun) { $cakeArguments += "-dryrun" }
|
||||||
if ($Experimental) { $cakeArguments += "-experimental" }
|
|
||||||
if ($Mono) { $cakeArguments += "-mono" }
|
|
||||||
$cakeArguments += $ScriptArgs
|
$cakeArguments += $ScriptArgs
|
||||||
|
|
||||||
# Start Cake
|
# Start Cake
|
||||||
|
62
build.sh
62
build.sh
@ -9,10 +9,14 @@
|
|||||||
# Define directories.
|
# Define directories.
|
||||||
SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
|
SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
|
||||||
TOOLS_DIR=$SCRIPT_DIR/tools
|
TOOLS_DIR=$SCRIPT_DIR/tools
|
||||||
|
ADDINS_DIR=$TOOLS_DIR/Addins
|
||||||
|
MODULES_DIR=$TOOLS_DIR/Modules
|
||||||
NUGET_EXE=$TOOLS_DIR/nuget.exe
|
NUGET_EXE=$TOOLS_DIR/nuget.exe
|
||||||
CAKE_EXE=$TOOLS_DIR/Cake/Cake.exe
|
CAKE_EXE=$TOOLS_DIR/Cake/Cake.exe
|
||||||
PACKAGES_CONFIG=$TOOLS_DIR/packages.config
|
PACKAGES_CONFIG=$TOOLS_DIR/packages.config
|
||||||
PACKAGES_CONFIG_MD5=$TOOLS_DIR/packages.config.md5sum
|
PACKAGES_CONFIG_MD5=$TOOLS_DIR/packages.config.md5sum
|
||||||
|
ADDINS_PACKAGES_CONFIG=$ADDINS_DIR/packages.config
|
||||||
|
MODULES_PACKAGES_CONFIG=$MODULES_DIR/packages.config
|
||||||
|
|
||||||
# Define md5sum or md5 depending on Linux/OSX
|
# Define md5sum or md5 depending on Linux/OSX
|
||||||
MD5_EXE=
|
MD5_EXE=
|
||||||
@ -24,24 +28,14 @@ fi
|
|||||||
|
|
||||||
# Define default arguments.
|
# Define default arguments.
|
||||||
SCRIPT="build.cake"
|
SCRIPT="build.cake"
|
||||||
TARGET="Default"
|
CAKE_ARGUMENTS=()
|
||||||
CONFIGURATION="Release"
|
|
||||||
VERBOSITY="verbose"
|
|
||||||
DRYRUN=
|
|
||||||
SHOW_VERSION=false
|
|
||||||
SCRIPT_ARGUMENTS=()
|
|
||||||
|
|
||||||
# Parse arguments.
|
# Parse arguments.
|
||||||
for i in "$@"; do
|
for i in "$@"; do
|
||||||
case $1 in
|
case $1 in
|
||||||
-s|--script) SCRIPT="$2"; shift ;;
|
-s|--script) SCRIPT="$2"; shift ;;
|
||||||
-t|--target) TARGET="$2"; shift ;;
|
--) shift; CAKE_ARGUMENTS+=("$@"); break ;;
|
||||||
-c|--configuration) CONFIGURATION="$2"; shift ;;
|
*) CAKE_ARGUMENTS+=("$1") ;;
|
||||||
-v|--verbosity) VERBOSITY="$2"; shift ;;
|
|
||||||
-d|--dryrun) DRYRUN="-dryrun" ;;
|
|
||||||
--version) SHOW_VERSION=true ;;
|
|
||||||
--) shift; SCRIPT_ARGUMENTS+=("$@"); break ;;
|
|
||||||
*) SCRIPT_ARGUMENTS+=("$1") ;;
|
|
||||||
esac
|
esac
|
||||||
shift
|
shift
|
||||||
done
|
done
|
||||||
@ -54,9 +48,9 @@ fi
|
|||||||
# Make sure that packages.config exist.
|
# Make sure that packages.config exist.
|
||||||
if [ ! -f "$TOOLS_DIR/packages.config" ]; then
|
if [ ! -f "$TOOLS_DIR/packages.config" ]; then
|
||||||
echo "Downloading packages.config..."
|
echo "Downloading packages.config..."
|
||||||
curl -Lsfo "$TOOLS_DIR/packages.config" http://cakebuild.net/download/bootstrapper/packages
|
curl -Lsfo "$TOOLS_DIR/packages.config" https://cakebuild.net/download/bootstrapper/packages
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "An error occured while downloading packages.config."
|
echo "An error occurred while downloading packages.config."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@ -66,7 +60,7 @@ if [ ! -f "$NUGET_EXE" ]; then
|
|||||||
echo "Downloading NuGet..."
|
echo "Downloading NuGet..."
|
||||||
curl -Lsfo "$NUGET_EXE" https://dist.nuget.org/win-x86-commandline/latest/nuget.exe
|
curl -Lsfo "$NUGET_EXE" https://dist.nuget.org/win-x86-commandline/latest/nuget.exe
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "An error occured while downloading nuget.exe."
|
echo "An error occurred while downloading nuget.exe."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@ -74,12 +68,12 @@ fi
|
|||||||
# Restore tools from NuGet.
|
# Restore tools from NuGet.
|
||||||
pushd "$TOOLS_DIR" >/dev/null
|
pushd "$TOOLS_DIR" >/dev/null
|
||||||
if [ ! -f "$PACKAGES_CONFIG_MD5" ] || [ "$( cat "$PACKAGES_CONFIG_MD5" | sed 's/\r$//' )" != "$( $MD5_EXE "$PACKAGES_CONFIG" | awk '{ print $1 }' )" ]; then
|
if [ ! -f "$PACKAGES_CONFIG_MD5" ] || [ "$( cat "$PACKAGES_CONFIG_MD5" | sed 's/\r$//' )" != "$( $MD5_EXE "$PACKAGES_CONFIG" | awk '{ print $1 }' )" ]; then
|
||||||
find . -type d ! -name . | xargs rm -rf
|
find . -type d ! -name . ! -name 'Cake.Bakery' | xargs rm -rf
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mono "$NUGET_EXE" install -ExcludeVersion
|
mono "$NUGET_EXE" install -ExcludeVersion
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "Could not restore NuGet packages."
|
echo "Could not restore NuGet tools."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -87,6 +81,32 @@ $MD5_EXE "$PACKAGES_CONFIG" | awk '{ print $1 }' >| "$PACKAGES_CONFIG_MD5"
|
|||||||
|
|
||||||
popd >/dev/null
|
popd >/dev/null
|
||||||
|
|
||||||
|
# Restore addins from NuGet.
|
||||||
|
if [ -f "$ADDINS_PACKAGES_CONFIG" ]; then
|
||||||
|
pushd "$ADDINS_DIR" >/dev/null
|
||||||
|
|
||||||
|
mono "$NUGET_EXE" install -ExcludeVersion
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "Could not restore NuGet addins."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
popd >/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Restore modules from NuGet.
|
||||||
|
if [ -f "$MODULES_PACKAGES_CONFIG" ]; then
|
||||||
|
pushd "$MODULES_DIR" >/dev/null
|
||||||
|
|
||||||
|
mono "$NUGET_EXE" install -ExcludeVersion
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "Could not restore NuGet modules."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
popd >/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
# Make sure that Cake has been installed.
|
# Make sure that Cake has been installed.
|
||||||
if [ ! -f "$CAKE_EXE" ]; then
|
if [ ! -f "$CAKE_EXE" ]; then
|
||||||
echo "Could not find Cake.exe at '$CAKE_EXE'."
|
echo "Could not find Cake.exe at '$CAKE_EXE'."
|
||||||
@ -94,8 +114,4 @@ if [ ! -f "$CAKE_EXE" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Start Cake
|
# Start Cake
|
||||||
if $SHOW_VERSION; then
|
exec mono "$CAKE_EXE" $SCRIPT "${CAKE_ARGUMENTS[@]}"
|
||||||
exec mono "$CAKE_EXE" -version
|
|
||||||
else
|
|
||||||
exec mono "$CAKE_EXE" $SCRIPT -verbosity=$VERBOSITY -configuration=$CONFIGURATION -target=$TARGET $DRYRUN "${SCRIPT_ARGUMENTS[@]}"
|
|
||||||
fi
|
|
||||||
|
24
docker-compose.yaml
Normal file
24
docker-compose.yaml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
version: "3.4"
|
||||||
|
services:
|
||||||
|
|
||||||
|
tests:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
target: builder
|
||||||
|
volumes:
|
||||||
|
- type: bind
|
||||||
|
source: .
|
||||||
|
target: /results
|
||||||
|
command: test --logger:trx -r /results
|
||||||
|
|
||||||
|
benchmarks:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
target: builder
|
||||||
|
args:
|
||||||
|
build_configuration: Release
|
||||||
|
command: run -c Release --project test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj 0 1 2 3 4
|
||||||
|
|
||||||
|
manual-test:
|
||||||
|
build: .
|
||||||
|
ports: [ "5000:80" ]
|
@ -10,7 +10,7 @@ This section defines the release process for the maintainers of the project.
|
|||||||
|
|
||||||
* When the `release` branch has built successfully in Appveyor, select the build and then Deploy to the `GitHub Release` environment. This will create a new release in GitHub.
|
* When the `release` branch has built successfully in Appveyor, select the build and then Deploy to the `GitHub Release` environment. This will create a new release in GitHub.
|
||||||
|
|
||||||
* In Github, navigate to the `release <https://github.com/TomPallister/Ocelot/releases>`_. Modify the release name and tag as desired.
|
* In Github, navigate to the `release <https://github.com/ThreeMammals/Ocelot/releases>`_. Modify the release name and tag as desired.
|
||||||
|
|
||||||
* When you're ready, publish the release. This will tag the commit with the specified release number.
|
* When you're ready, publish the release. This will tag the commit with the specified release number.
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ All you need to do to hook into your own IdentityServer is add the following to
|
|||||||
|
|
||||||
You now need to get a token from your IdentityServer and use in subsequent requests to Ocelot's administration API.
|
You now need to get a token from your IdentityServer and use in subsequent requests to Ocelot's administration API.
|
||||||
|
|
||||||
This feature was implemented for `issue 228 <https://github.com/TomPallister/Ocelot/issues/228>`_. It is useful because the IdentityServer authentication
|
This feature was implemented for `issue 228 <https://github.com/ThreeMammals/Ocelot/issues/228>`_. It is useful because the IdentityServer authentication
|
||||||
middleware needs the URL of the IdentityServer. If you are using the internal IdentityServer it might not alaways be possible to have the Ocelot URL.
|
middleware needs the URL of the IdentityServer. If you are using the internal IdentityServer it might not alaways be possible to have the Ocelot URL.
|
||||||
|
|
||||||
Internal IdentityServer
|
Internal IdentityServer
|
||||||
|
@ -32,7 +32,7 @@ Finally in order to use caching on a route in your ReRoute configuration add thi
|
|||||||
|
|
||||||
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/TomPallister/Ocelot/blob/develop/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/develop/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.
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
Configuration
|
Configuration
|
||||||
============
|
============
|
||||||
|
|
||||||
An example configuration can be found `here <https://github.com/TomPallister/Ocelot/blob/develop/test/Ocelot.ManualTest/ocelot.json>`_.
|
An example configuration can be found `here <https://github.com/ThreeMammals/Ocelot/blob/develop/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
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
Delegating Handlers
|
Delegating Handlers
|
||||||
===================
|
===================
|
||||||
|
|
||||||
Ocelot allows the user to add delegating handlers to the HttpClient transport. This feature was requested `GitHub #208 <https://github.com/TomPallister/Ocelot/issues/208>`_
|
Ocelot allows the user to add delegating handlers to the HttpClient transport. This feature was requested `GitHub #208 <https://github.com/ThreeMammals/Ocelot/issues/208>`_
|
||||||
and I decided that it was going to be useful in various ways. Since then we extended it in `GitHub #264 <https://github.com/TomPallister/Ocelot/issues/264>`_.
|
and I decided that it was going to be useful in various ways. Since then we extended it in `GitHub #264 <https://github.com/ThreeMammals/Ocelot/issues/264>`_.
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
^^^^^
|
^^^^^
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
Headers Transformation
|
Headers Transformation
|
||||||
======================
|
======================
|
||||||
|
|
||||||
Ocelot allows the user to transform headers pre and post downstream request. At the moment Ocelot only supports find and replace. This feature was requested `GitHub #190 <https://github.com/TomPallister/Ocelot/issues/190>`_ and I decided that it was going to be useful in various ways.
|
Ocelot allows the user to transform headers pre and post downstream request. At the moment Ocelot only supports find and replace. This feature was requested `GitHub #190 <https://github.com/ThreeMammals/Ocelot/issues/190>`_ and I decided that it was going to be useful in various ways.
|
||||||
|
|
||||||
Add to Request
|
Add to Request
|
||||||
^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^
|
||||||
@ -23,7 +23,7 @@ Placeholders are supported too (see below).
|
|||||||
Add to Response
|
Add to Response
|
||||||
^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
This feature was requested in `GitHub #280 <https://github.com/TomPallister/Ocelot/issues/280>`_.
|
This feature was requested in `GitHub #280 <https://github.com/ThreeMammals/Ocelot/issues/280>`_.
|
||||||
|
|
||||||
If you want to add a header to your downstream response please add the following to a ReRoute in ocelot.json..
|
If you want to add a header to your downstream response please add the following to a ReRoute in ocelot.json..
|
||||||
|
|
||||||
|
@ -1,112 +1,3 @@
|
|||||||
|
|
||||||
Ocelot can load balance across available downstream services for each ReRoute. This means you can scale your downstream services and Ocelot can use them effectively.
|
|
||||||
|
|
||||||
The type of load balancer available are:
|
|
||||||
|
|
||||||
LeastConnection - tracks which services are dealing with requests and sends new requests to service with least existing requests. The algorythm state is not distributed across a cluster of Ocelot's.
|
|
||||||
|
|
||||||
RoundRobin - loops through available services and sends requests. The algorythm state is not distributed across a cluster of Ocelot's.
|
|
||||||
|
|
||||||
NoLoadBalancer - takes the first available service from config or service discovery.
|
|
||||||
|
|
||||||
CookieStickySessions - uses a cookie to stick all requests to a specific server. More info below.
|
|
||||||
|
|
||||||
You must choose in your configuration which load balancer to use.
|
|
||||||
|
|
||||||
Configuration
|
|
||||||
^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
The following shows how to set up multiple downstream services for a ReRoute using ocelot.json and then select the LeadConnection load balancer. This is the simplest way to get load balancing set up.
|
|
||||||
|
|
||||||
.. code-block:: json
|
|
||||||
|
|
||||||
{
|
|
||||||
"DownstreamPathTemplate": "/api/posts/{postId}",
|
|
||||||
"DownstreamScheme": "https",
|
|
||||||
"DownstreamHostAndPorts": [
|
|
||||||
{
|
|
||||||
"Host": "10.0.1.10",
|
|
||||||
"Port": 5000,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Host": "10.0.1.11",
|
|
||||||
"Port": 5000,
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"UpstreamPathTemplate": "/posts/{postId}",
|
|
||||||
"LoadBalancerOptions": {
|
|
||||||
"Type": "LeastConnection"
|
|
||||||
},
|
|
||||||
"UpstreamHttpMethod": [ "Put", "Delete" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Service Discovery
|
|
||||||
^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
The following shows how to set up a ReRoute using service discovery then select the LeadConnection load balancer.
|
|
||||||
|
|
||||||
.. code-block:: json
|
|
||||||
|
|
||||||
{
|
|
||||||
"DownstreamPathTemplate": "/api/posts/{postId}",
|
|
||||||
"DownstreamScheme": "https",
|
|
||||||
"UpstreamPathTemplate": "/posts/{postId}",
|
|
||||||
"UpstreamHttpMethod": [ "Put" ],
|
|
||||||
"ServiceName": "product",
|
|
||||||
"LoadBalancerOptions": {
|
|
||||||
"Type": "LeastConnection"
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
When this is set up Ocelot will lookup the downstream host and port from the service discover provider and load balance requests across any available services. If you add and remove services from the
|
|
||||||
service discovery provider (consul) then Ocelot should respect this and stop calling services that have been removed and start calling services that have been added.
|
|
||||||
|
|
||||||
CookieStickySessions
|
|
||||||
^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
I've implemented a really basic sticky session type of load balancer. The scenario it is meant to support is you have a bunch of downstream
|
|
||||||
servers that don't share session state so if you get more than one request for one of these servers then it should go to the same box each
|
|
||||||
time or the session state might be incorrect for the given user. This feature was requested in `Issue #322 <https://github.com/ThreeMammals/Ocelot/issues/322>`_
|
|
||||||
though what the user wants is more complicated than just sticky sessions :) anyway I thought this would be a nice feature to have!
|
|
||||||
|
|
||||||
In order to set up CookieStickySessions load balancer you need to do something like the following.
|
|
||||||
|
|
||||||
.. code-block:: json
|
|
||||||
|
|
||||||
{
|
|
||||||
"DownstreamPathTemplate": "/api/posts/{postId}",
|
|
||||||
"DownstreamScheme": "https",
|
|
||||||
"DownstreamHostAndPorts": [
|
|
||||||
{
|
|
||||||
"Host": "10.0.1.10",
|
|
||||||
"Port": 5000,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Host": "10.0.1.11",
|
|
||||||
"Port": 5000,
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"UpstreamPathTemplate": "/posts/{postId}",
|
|
||||||
"LoadBalancerOptions": {
|
|
||||||
"Type": "CookieStickySessions",
|
|
||||||
"Key": "ASP.NET_SessionId",
|
|
||||||
"Expiry": 1800000
|
|
||||||
},
|
|
||||||
"UpstreamHttpMethod": [ "Put", "Delete" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
The LoadBalancerOptions are Type this needs to be CookieStickySessions, Key this is the key of the cookie you
|
|
||||||
wish to use for the sticky sessions, Expiry this is how long in milliseconds you want to the session to be stuck for. Remember this
|
|
||||||
refreshes on every request which is meant to mimick how sessions work usually.
|
|
||||||
|
|
||||||
If you have multiple ReRoutes with the same LoadBalancerOptions then all of those ReRoutes will use the same load balancer for there
|
|
||||||
subsequent requests. This means the sessions will be stuck across ReRoutes.
|
|
||||||
|
|
||||||
Please note that if you give more than one DownstreamHostAndPort or you are using a Service Discovery provider such as Consul
|
|
||||||
and this returns more than one service then CookieStickySessions uses round robin to select the next server. This is hard coded at the
|
|
||||||
moment but could be changed.
|
|
||||||
||||||| merged common ancestors
|
|
||||||
Load Balancer
|
Load Balancer
|
||||||
=============
|
=============
|
||||||
|
|
||||||
@ -217,114 +108,3 @@ subsequent requests. This means the sessions will be stuck across ReRoutes.
|
|||||||
Please note that if you give more than one DownstreamHostAndPort or you are using a Service Discovery provider such as Consul
|
Please note that if you give more than one DownstreamHostAndPort or you are using a Service Discovery provider such as Consul
|
||||||
and this returns more than one service then CookieStickySessions uses round robin to select the next server. This is hard coded at the
|
and this returns more than one service then CookieStickySessions uses round robin to select the next server. This is hard coded at the
|
||||||
moment but could be changed.
|
moment but could be changed.
|
||||||
=======
|
|
||||||
Load Balancer
|
|
||||||
=============
|
|
||||||
|
|
||||||
Ocelot can load balance across available downstream services for each ReRoute. This means you can scale your downstream services and Ocelot can use them effectively.
|
|
||||||
|
|
||||||
The type of load balancer available are:
|
|
||||||
|
|
||||||
LeastConnection - tracks which services are dealing with requests and sends new requests to service with least existing requests. The algorythm state is not distributed across a cluster of Ocelot's.
|
|
||||||
|
|
||||||
RoundRobin - loops through available services and sends requests. The algorythm state is not distributed across a cluster of Ocelot's.
|
|
||||||
|
|
||||||
NoLoadBalancer - takes the first available service from config or service discovery.
|
|
||||||
|
|
||||||
CookieStickySessions - uses a cookie to stick all requests to a specific server. More info below.
|
|
||||||
|
|
||||||
You must choose in your configuration which load balancer to use.
|
|
||||||
|
|
||||||
Configuration
|
|
||||||
^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
The following shows how to set up multiple downstream services for a ReRoute using ocelot.json and then select the LeastConnection load balancer. This is the simplest way to get load balancing set up.
|
|
||||||
|
|
||||||
.. code-block:: json
|
|
||||||
|
|
||||||
{
|
|
||||||
"DownstreamPathTemplate": "/api/posts/{postId}",
|
|
||||||
"DownstreamScheme": "https",
|
|
||||||
"DownstreamHostAndPorts": [
|
|
||||||
{
|
|
||||||
"Host": "10.0.1.10",
|
|
||||||
"Port": 5000,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Host": "10.0.1.11",
|
|
||||||
"Port": 5000,
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"UpstreamPathTemplate": "/posts/{postId}",
|
|
||||||
"LoadBalancerOptions": {
|
|
||||||
"Type": "LeastConnection"
|
|
||||||
},
|
|
||||||
"UpstreamHttpMethod": [ "Put", "Delete" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Service Discovery
|
|
||||||
^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
The following shows how to set up a ReRoute using service discovery then select the LeastConnection load balancer.
|
|
||||||
|
|
||||||
.. code-block:: json
|
|
||||||
|
|
||||||
{
|
|
||||||
"DownstreamPathTemplate": "/api/posts/{postId}",
|
|
||||||
"DownstreamScheme": "https",
|
|
||||||
"UpstreamPathTemplate": "/posts/{postId}",
|
|
||||||
"UpstreamHttpMethod": [ "Put" ],
|
|
||||||
"ServiceName": "product",
|
|
||||||
"LoadBalancerOptions": {
|
|
||||||
"Type": "LeastConnection"
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
When this is set up Ocelot will lookup the downstream host and port from the service discover provider and load balance requests across any available services. If you add and remove services from the
|
|
||||||
service discovery provider (consul) then Ocelot should respect this and stop calling services that have been removed and start calling services that have been added.
|
|
||||||
|
|
||||||
CookieStickySessions
|
|
||||||
^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
I've implemented a really basic sticky session type of load balancer. The scenario it is meant to support is you have a bunch of downstream
|
|
||||||
servers that don't share session state so if you get more than one request for one of these servers then it should go to the same box each
|
|
||||||
time or the session state might be incorrect for the given user. This feature was requested in `Issue #322 <https://github.com/ThreeMammals/Ocelot/issues/322>`_
|
|
||||||
though what the user wants is more complicated than just sticky sessions :) anyway I thought this would be a nice feature to have!
|
|
||||||
|
|
||||||
In order to set up CookieStickySessions load balancer you need to do something like the following.
|
|
||||||
|
|
||||||
.. code-block:: json
|
|
||||||
|
|
||||||
{
|
|
||||||
"DownstreamPathTemplate": "/api/posts/{postId}",
|
|
||||||
"DownstreamScheme": "https",
|
|
||||||
"DownstreamHostAndPorts": [
|
|
||||||
{
|
|
||||||
"Host": "10.0.1.10",
|
|
||||||
"Port": 5000,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Host": "10.0.1.11",
|
|
||||||
"Port": 5000,
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"UpstreamPathTemplate": "/posts/{postId}",
|
|
||||||
"LoadBalancerOptions": {
|
|
||||||
"Type": "CookieStickySessions",
|
|
||||||
"Key": "ASP.NET_SessionId",
|
|
||||||
"Expiry": 1800000
|
|
||||||
},
|
|
||||||
"UpstreamHttpMethod": [ "Put", "Delete" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
The LoadBalancerOptions are Type this needs to be CookieStickySessions, Key this is the key of the cookie you
|
|
||||||
wish to use for the sticky sessions, Expiry this is how long in milliseconds you want to the session to be stuck for. Remember this
|
|
||||||
refreshes on every request which is meant to mimick how sessions work usually.
|
|
||||||
|
|
||||||
If you have multiple ReRoutes with the same LoadBalancerOptions then all of those ReRoutes will use the same load balancer for there
|
|
||||||
subsequent requests. This means the sessions will be stuck across ReRoutes.
|
|
||||||
|
|
||||||
Please note that if you give more than one DownstreamHostAndPort or you are using a Service Discovery provider such as Consul
|
|
||||||
and this returns more than one service then CookieStickySessions uses round robin to select the next server. This is hard coded at the
|
|
||||||
moment but could be changed.
|
|
||||||
|
@ -2,7 +2,7 @@ Quality of Service
|
|||||||
==================
|
==================
|
||||||
|
|
||||||
Ocelot supports one QoS capability at the current time. You can set on a per ReRoute basis if you
|
Ocelot supports one QoS capability at the current time. You can set on a per ReRoute basis if you
|
||||||
want to use a circuit breaker when making requests to a downstream service. This uses the an awesome
|
want to use a circuit breaker when making requests to a downstream service. This uses an awesome
|
||||||
.NET library called Polly check them out `here <https://github.com/App-vNext/Polly>`_.
|
.NET library called Polly check them out `here <https://github.com/App-vNext/Polly>`_.
|
||||||
|
|
||||||
The first thing you need to do if you want to use the administration API is bring in the relavent NuGet package..
|
The first thing you need to do if you want to use the administration API is bring in the relavent NuGet package..
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
Raft (EXPERIMENTAL DO NOT USE IN PRODUCTION)
|
Raft (EXPERIMENTAL DO NOT USE IN PRODUCTION)
|
||||||
============================================
|
============================================
|
||||||
|
|
||||||
Ocelot has recently integrated `Rafty <https://github.com/TomPallister/Rafty>`_ which is an implementation of Raft that I have also been working on over the last year. This project is very experimental so please do not use this feature of Ocelot in production until I think it's OK.
|
Ocelot has recently integrated `Rafty <https://github.com/ThreeMammals/Rafty>`_ which is an implementation of Raft that I have also been working on over the last year. This project is very experimental so please do not use this feature of Ocelot in production until I think it's OK.
|
||||||
|
|
||||||
Raft is a distributed concensus algorythm that allows a cluster of servers (Ocelots) to maintain local state without having a centralised database for storing state (e.g. SQL Server).
|
Raft is a distributed concensus algorythm that allows a cluster of servers (Ocelots) to maintain local state without having a centralised database for storing state (e.g. SQL Server).
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
Request Aggregation
|
Request Aggregation
|
||||||
===================
|
===================
|
||||||
|
|
||||||
Ocelot allow's you to specify Aggregate ReRoutes that compose multiple normal ReRoutes and map their responses into one object. This is usual where you have
|
Ocelot allows you to specify Aggregate ReRoutes that compose multiple normal ReRoutes and map their responses into one object. This is usual where you have
|
||||||
a client that is making multiple requests to a server where it could just be one. This feature allows you to start implementing back end for a front end type
|
a client that is making multiple requests to a server where it could just be one. This feature allows you to start implementing back end for a front end type
|
||||||
architecture with Ocelot.
|
architecture with Ocelot.
|
||||||
|
|
||||||
This feature was requested as part of `Issue 79 <https://github.com/TomPallister/Ocelot/pull/79>`_ and further improvements were made as part of `Issue 298 <https://github.com/TomPallister/Ocelot/issue/298>`_.
|
This feature was requested as part of `Issue 79 <https://github.com/ThreeMammals/Ocelot/pull/79>`_ and further improvements were made as part of `Issue 298 <https://github.com/ThreeMammals/Ocelot/issue/298>`_.
|
||||||
|
|
||||||
In order to set this up you must do something like the following in your ocelot.json. Here we have specified two normal ReRoutes and each one has a Key property.
|
In order to set this up you must do something like the following in your ocelot.json. Here we have specified two normal ReRoutes and each one has a Key property.
|
||||||
We then specify an Aggregate that composes the two ReRoutes using their keys in the ReRouteKeys list and says then we have the UpstreamPathTemplate which works like a normal ReRoute.
|
We then specify an Aggregate that composes the two ReRoutes using their keys in the ReRouteKeys list and says then we have the UpstreamPathTemplate which works like a normal ReRoute.
|
||||||
|
@ -140,13 +140,13 @@ The ReRoute above will only be matched when the host header value is somedomain.
|
|||||||
|
|
||||||
If you do not set UpstreamHost on a ReRoute then any host header will match it. This means that if you have two ReRoutes that are the same, apart from the UpstreamHost, where one is null and the other set Ocelot will favour the one that has been set.
|
If you do not set UpstreamHost on a ReRoute then any host header will match it. This means that if you have two ReRoutes that are the same, apart from the UpstreamHost, where one is null and the other set Ocelot will favour the one that has been set.
|
||||||
|
|
||||||
This feature was requested as part of `Issue 216 <https://github.com/TomPallister/Ocelot/pull/216>`_ .
|
This feature was requested as part of `Issue 216 <https://github.com/ThreeMammals/Ocelot/pull/216>`_ .
|
||||||
|
|
||||||
Priority
|
Priority
|
||||||
^^^^^^^^
|
^^^^^^^^
|
||||||
|
|
||||||
You can define the order you want your ReRoutes to match the Upstream HttpRequest by including a "Priority" property in ocelot.json
|
You can define the order you want your ReRoutes to match the Upstream HttpRequest by including a "Priority" property in ocelot.json
|
||||||
See `Issue 270 <https://github.com/TomPallister/Ocelot/pull/270>`_ for reference
|
See `Issue 270 <https://github.com/ThreeMammals/Ocelot/pull/270>`_ for reference
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
@ -181,7 +181,7 @@ matched /goods/{catchAll} (because this is the first ReRoute in the list!).
|
|||||||
Dynamic Routing
|
Dynamic Routing
|
||||||
^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
This feature was requested in `issue 340 <https://github.com/TomPallister/Ocelot/issue/340>`_.
|
This feature was requested in `issue 340 <https://github.com/ThreeMammals/Ocelot/issue/340>`_.
|
||||||
|
|
||||||
The idea is to enable dynamic routing when using a service discovery provider so you don't have to provide the ReRoute config. See the docs :ref:`service-discovery` if
|
The idea is to enable dynamic routing when using a service discovery provider so you don't have to provide the ReRoute config. See the docs :ref:`service-discovery` if
|
||||||
this sounds interesting to you.
|
this sounds interesting to you.
|
||||||
|
@ -113,7 +113,7 @@ Ocelot will add this token to the consul client that it uses to make requests an
|
|||||||
Eureka
|
Eureka
|
||||||
^^^^^^
|
^^^^^^
|
||||||
|
|
||||||
This feature was requested as part of `Issue 262 <https://github.com/TomPallister/Ocelot/issue/262>`_ . to add support for Netflix's
|
This feature was requested as part of `Issue 262 <https://github.com/ThreeMammals/Ocelot/issue/262>`_ . to add support for Netflix's
|
||||||
Eureka service discovery provider. The main reason for this is it is a key part of `Steeltoe <https://steeltoe.io/>`_ which is something
|
Eureka service discovery provider. The main reason for this is it is a key part of `Steeltoe <https://steeltoe.io/>`_ which is something
|
||||||
to do with `Pivotal <https://pivotal.io/platform>`_! Anyway enough of the background.
|
to do with `Pivotal <https://pivotal.io/platform>`_! Anyway enough of the background.
|
||||||
|
|
||||||
@ -158,7 +158,7 @@ is provided by the Pivotal.Discovery.Client NuGet package so big thanks to them
|
|||||||
Dynamic Routing
|
Dynamic Routing
|
||||||
^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
This feature was requested in `issue 340 <https://github.com/TomPallister/Ocelot/issue/340>`_. The idea is to enable dynamic routing when using a service discovery provider (see that section of the docs for more info). In this mode Ocelot will use the first segment of the upstream path to lookup the downstream service with the service discovery provider.
|
This feature was requested in `issue 340 <https://github.com/ThreeMammals/Ocelot/issue/340>`_. The idea is to enable dynamic routing when using a service discovery provider (see that section of the docs for more info). In this mode Ocelot will use the first segment of the upstream path to lookup the downstream service with the service discovery provider.
|
||||||
|
|
||||||
An example of this would be calling ocelot with a url like https://api.mywebsite.com/product/products. Ocelot will take the first segment of
|
An example of this would be calling ocelot with a url like https://api.mywebsite.com/product/products. Ocelot will take the first segment of
|
||||||
the path which is product and use it as a key to look up the service in consul. If consul returns a service Ocelot will request it on whatever host and port comes back from consul plus the remaining path segments in this case products thus making the downstream call http://hostfromconsul:portfromconsul/products. Ocelot will apprend any query string to the downstream url as normal.
|
the path which is product and use it as a key to look up the service in consul. If consul returns a service Ocelot will request it on whatever host and port comes back from consul plus the remaining path segments in this case products thus making the downstream call http://hostfromconsul:portfromconsul/products. Ocelot will apprend any query string to the downstream url as normal.
|
||||||
|
@ -8,9 +8,9 @@ Ocelot does not support...
|
|||||||
* Forwarding a host header - The host header that you send to Ocelot will not be forwarded to the downstream service. Obviously this would break everything :(
|
* Forwarding a host header - The host header that you send to Ocelot will not be forwarded to the downstream service. Obviously this would break everything :(
|
||||||
|
|
||||||
* Swagger - I have looked multiple times at building swagger.json out of the Ocelot ocelot.json but it doesnt fit into the vision
|
* Swagger - I have looked multiple times at building swagger.json out of the Ocelot ocelot.json but it doesnt fit into the vision
|
||||||
I have for Ocelot. If you would like to have Swagger in Ocelot then you must roll your own swagger.json and do the following in your
|
I have for Ocelot. If you would like to have Swagger in Ocelot then you must roll your own swagger.json and do the following in your
|
||||||
Startup.cs or Program.cs. The code sample below registers a piece of middleware that loads your hand rolled swagger.json and returns
|
Startup.cs or Program.cs. The code sample below registers a piece of middleware that loads your hand rolled swagger.json and returns
|
||||||
it on /swagger/v1/swagger.json. It then registers the SwaggerUI middleware from Swashbuckle.AspNetCore
|
it on /swagger/v1/swagger.json. It then registers the SwaggerUI middleware from Swashbuckle.AspNetCore
|
||||||
|
|
||||||
.. code-block:: csharp
|
.. code-block:: csharp
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"projects": [ "src", "test" ],
|
"projects": [ "src", "test" ],
|
||||||
"sdk": {
|
"sdk": {
|
||||||
"version": "2.1.301"
|
"version": "2.1.500"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
13
samples/AdministrationApi/AdministrationApi.csproj
Normal file
13
samples/AdministrationApi/AdministrationApi.csproj
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="wwwroot\"/>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.App"/>
|
||||||
|
<PackageReference Include="Ocelot" Version="12.0.1"/>
|
||||||
|
<PackageReference Include="Ocelot.Administration" Version="0.1.0"/>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
150
samples/AdministrationApi/Issue645.postman_collection.json
Normal file
150
samples/AdministrationApi/Issue645.postman_collection.json
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
{
|
||||||
|
"info": {
|
||||||
|
"_postman_id": "6234b40a-e363-4c73-8577-1c9074abb951",
|
||||||
|
"name": "Issue645",
|
||||||
|
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
||||||
|
},
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"name": "1. GET http://localhost: 55580/administration/.well-known/openid-configuration",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Authorization",
|
||||||
|
"value": "Bearer {{AccessToken}}"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {},
|
||||||
|
"url": {
|
||||||
|
"raw": "http://localhost:5000/administration/.well-known/openid-configuration",
|
||||||
|
"protocol": "http",
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
],
|
||||||
|
"port": "5000",
|
||||||
|
"path": [
|
||||||
|
"administration",
|
||||||
|
".well-known",
|
||||||
|
"openid-configuration"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "3. GET http://localhost: 55580/administration/configuration",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Authorization",
|
||||||
|
"value": "Bearer {{AccessToken}}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\r\n \"reRoutes\": [\r\n {\r\n \"downstreamPathTemplate\": \"/{everything}\",\r\n \"upstreamPathTemplate\": \"/templates/{everything}\",\r\n \"upstreamHttpMethod\": [\r\n \"GET\"\r\n ],\r\n \"addHeadersToRequest\": {},\r\n \"upstreamHeaderTransform\": {},\r\n \"downstreamHeaderTransform\": {},\r\n \"addClaimsToRequest\": {},\r\n \"routeClaimsRequirement\": {},\r\n \"addQueriesToRequest\": {},\r\n \"requestIdKey\": null,\r\n \"fileCacheOptions\": {\r\n \"ttlSeconds\": 0,\r\n \"region\": null\r\n },\r\n \"reRouteIsCaseSensitive\": false,\r\n \"downstreamScheme\": \"http\",\r\n \"qoSOptions\": {\r\n \"exceptionsAllowedBeforeBreaking\": 0,\r\n \"durationOfBreak\": 0,\r\n \"timeoutValue\": 0\r\n },\r\n \"loadBalancerOptions\": {\r\n \"type\": null,\r\n \"key\": null,\r\n \"expiry\": 0\r\n },\r\n \"rateLimitOptions\": {\r\n \"clientWhitelist\": [],\r\n \"enableRateLimiting\": false,\r\n \"period\": null,\r\n \"periodTimespan\": 0,\r\n \"limit\": 0\r\n },\r\n \"authenticationOptions\": {\r\n \"authenticationProviderKey\": null,\r\n \"allowedScopes\": []\r\n },\r\n \"httpHandlerOptions\": {\r\n \"allowAutoRedirect\": false,\r\n \"useCookieContainer\": false,\r\n \"useTracing\": false,\r\n \"useProxy\": true\r\n },\r\n \"downstreamHostAndPorts\": [\r\n {\r\n \"host\": \"localhost\",\r\n \"port\": 50689\r\n }\r\n ],\r\n \"upstreamHost\": null,\r\n \"key\": null,\r\n \"delegatingHandlers\": [],\r\n \"priority\": 1,\r\n \"timeout\": 0,\r\n \"dangerousAcceptAnyServerCertificateValidator\": false\r\n }\r\n ],\r\n \"aggregates\": [],\r\n \"globalConfiguration\": {\r\n \"requestIdKey\": \"Request-Id\",\r\n \"rateLimitOptions\": {\r\n \"clientIdHeader\": \"ClientId\",\r\n \"quotaExceededMessage\": null,\r\n \"rateLimitCounterPrefix\": \"ocelot\",\r\n \"disableRateLimitHeaders\": false,\r\n \"httpStatusCode\": 429\r\n },\r\n \"qoSOptions\": {\r\n \"exceptionsAllowedBeforeBreaking\": 0,\r\n \"durationOfBreak\": 0,\r\n \"timeoutValue\": 0\r\n },\r\n \"baseUrl\": \"http://localhost:55580\",\r\n \"loadBalancerOptions\": {\r\n \"type\": null,\r\n \"key\": null,\r\n \"expiry\": 0\r\n },\r\n \"downstreamScheme\": null,\r\n \"httpHandlerOptions\": {\r\n \"allowAutoRedirect\": false,\r\n \"useCookieContainer\": false,\r\n \"useTracing\": false,\r\n \"useProxy\": true\r\n }\r\n }\r\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "http://localhost:5000/administration/configuration",
|
||||||
|
"protocol": "http",
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
],
|
||||||
|
"port": "5000",
|
||||||
|
"path": [
|
||||||
|
"administration",
|
||||||
|
"configuration"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "2. POST http://localhost: 55580/administration/connect/token",
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"type": "text/javascript",
|
||||||
|
"exec": [
|
||||||
|
"var jsonData = JSON.parse(responseBody);",
|
||||||
|
"postman.setGlobalVariable(\"AccessToken\", jsonData.access_token);",
|
||||||
|
"postman.setGlobalVariable(\"RefreshToken\", jsonData.refresh_token);"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [],
|
||||||
|
"body": {
|
||||||
|
"mode": "formdata",
|
||||||
|
"formdata": [
|
||||||
|
{
|
||||||
|
"key": "client_id",
|
||||||
|
"value": "admin",
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "client_secret",
|
||||||
|
"value": "secret",
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "scope",
|
||||||
|
"value": "admin",
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "grant_type",
|
||||||
|
"value": "client_credentials",
|
||||||
|
"type": "text"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "http://localhost:5000/administration/connect/token",
|
||||||
|
"protocol": "http",
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
],
|
||||||
|
"port": "5000",
|
||||||
|
"path": [
|
||||||
|
"administration",
|
||||||
|
"connect",
|
||||||
|
"token"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "prerequest",
|
||||||
|
"script": {
|
||||||
|
"id": "0f60e7b3-e4f1-4458-bbc4-fc4809e86b2d",
|
||||||
|
"type": "text/javascript",
|
||||||
|
"exec": [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"id": "1279a2cf-b771-4a86-9dfa-302b240fac62",
|
||||||
|
"type": "text/javascript",
|
||||||
|
"exec": [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
50
samples/AdministrationApi/Program.cs
Normal file
50
samples/AdministrationApi/Program.cs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Ocelot.DependencyInjection;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
using Ocelot.Administration;
|
||||||
|
|
||||||
|
namespace AdministrationApi
|
||||||
|
{
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
new WebHostBuilder()
|
||||||
|
.UseKestrel()
|
||||||
|
.UseUrls("http://localhost:5000")
|
||||||
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
|
.ConfigureAppConfiguration((hostingContext, config) =>
|
||||||
|
{
|
||||||
|
config
|
||||||
|
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
|
||||||
|
.AddJsonFile("appsettings.json", true, true)
|
||||||
|
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
|
||||||
|
.AddJsonFile("ocelot.json")
|
||||||
|
.AddEnvironmentVariables();
|
||||||
|
})
|
||||||
|
.ConfigureServices(s => {
|
||||||
|
s.AddOcelot()
|
||||||
|
.AddAdministration("/administration", "secret");
|
||||||
|
})
|
||||||
|
.ConfigureLogging((hostingContext, logging) =>
|
||||||
|
{
|
||||||
|
logging.AddConsole();
|
||||||
|
})
|
||||||
|
.UseIISIntegration()
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.UseOcelot().Wait();
|
||||||
|
})
|
||||||
|
.Build()
|
||||||
|
.Run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
samples/AdministrationApi/Properties/launchSettings.json
Normal file
27
samples/AdministrationApi/Properties/launchSettings.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"iisSettings": {
|
||||||
|
"windowsAuthentication": false,
|
||||||
|
"anonymousAuthentication": true,
|
||||||
|
"iisExpress": {
|
||||||
|
"applicationUrl": "http://localhost:9943",
|
||||||
|
"sslPort": 44396
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"IIS Express": {
|
||||||
|
"commandName": "IISExpress",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AdministrationApi": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"applicationUrl": "https://localhost:5001;http://localhost:5000",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
92
samples/AdministrationApi/README.md
Normal file
92
samples/AdministrationApi/README.md
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
{
|
||||||
|
"reRoutes": [
|
||||||
|
{
|
||||||
|
"downstreamPathTemplate": "/{everything}",
|
||||||
|
"upstreamPathTemplate": "/templates/{everything}",
|
||||||
|
"upstreamHttpMethod": [
|
||||||
|
"GET"
|
||||||
|
],
|
||||||
|
"addHeadersToRequest": {},
|
||||||
|
"upstreamHeaderTransform": {},
|
||||||
|
"downstreamHeaderTransform": {},
|
||||||
|
"addClaimsToRequest": {},
|
||||||
|
"routeClaimsRequirement": {},
|
||||||
|
"addQueriesToRequest": {},
|
||||||
|
"requestIdKey": null,
|
||||||
|
"fileCacheOptions": {
|
||||||
|
"ttlSeconds": 0,
|
||||||
|
"region": null
|
||||||
|
},
|
||||||
|
"reRouteIsCaseSensitive": false,
|
||||||
|
"downstreamScheme": "http",
|
||||||
|
"qoSOptions": {
|
||||||
|
"exceptionsAllowedBeforeBreaking": 0,
|
||||||
|
"durationOfBreak": 0,
|
||||||
|
"timeoutValue": 0
|
||||||
|
},
|
||||||
|
"loadBalancerOptions": {
|
||||||
|
"type": null,
|
||||||
|
"key": null,
|
||||||
|
"expiry": 0
|
||||||
|
},
|
||||||
|
"rateLimitOptions": {
|
||||||
|
"clientWhitelist": [],
|
||||||
|
"enableRateLimiting": false,
|
||||||
|
"period": null,
|
||||||
|
"periodTimespan": 0,
|
||||||
|
"limit": 0
|
||||||
|
},
|
||||||
|
"authenticationOptions": {
|
||||||
|
"authenticationProviderKey": null,
|
||||||
|
"allowedScopes": []
|
||||||
|
},
|
||||||
|
"httpHandlerOptions": {
|
||||||
|
"allowAutoRedirect": false,
|
||||||
|
"useCookieContainer": false,
|
||||||
|
"useTracing": false,
|
||||||
|
"useProxy": true
|
||||||
|
},
|
||||||
|
"downstreamHostAndPorts": [
|
||||||
|
{
|
||||||
|
"host": "localhost",
|
||||||
|
"port": 50689
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"upstreamHost": null,
|
||||||
|
"key": null,
|
||||||
|
"delegatingHandlers": [],
|
||||||
|
"priority": 1,
|
||||||
|
"timeout": 0,
|
||||||
|
"dangerousAcceptAnyServerCertificateValidator": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"aggregates": [],
|
||||||
|
"globalConfiguration": {
|
||||||
|
"requestIdKey": "Request-Id",
|
||||||
|
"rateLimitOptions": {
|
||||||
|
"clientIdHeader": "ClientId",
|
||||||
|
"quotaExceededMessage": null,
|
||||||
|
"rateLimitCounterPrefix": "ocelot",
|
||||||
|
"disableRateLimitHeaders": false,
|
||||||
|
"httpStatusCode": 429
|
||||||
|
},
|
||||||
|
"qoSOptions": {
|
||||||
|
"exceptionsAllowedBeforeBreaking": 0,
|
||||||
|
"durationOfBreak": 0,
|
||||||
|
"timeoutValue": 0
|
||||||
|
},
|
||||||
|
"baseUrl": "http://localhost:55580",
|
||||||
|
"loadBalancerOptions": {
|
||||||
|
"type": null,
|
||||||
|
"key": null,
|
||||||
|
"expiry": 0
|
||||||
|
},
|
||||||
|
"downstreamScheme": null,
|
||||||
|
"httpHandlerOptions": {
|
||||||
|
"allowAutoRedirect": false,
|
||||||
|
"useCookieContainer": false,
|
||||||
|
"useTracing": false,
|
||||||
|
"useProxy": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
samples/AdministrationApi/appsettings.json
Normal file
10
samples/AdministrationApi/appsettings.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"IncludeScopes": true,
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Trace",
|
||||||
|
"System": "Trace",
|
||||||
|
"Microsoft": "Trace"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
samples/AdministrationApi/ocelot.json
Normal file
18
samples/AdministrationApi/ocelot.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"ReRoutes": [
|
||||||
|
{
|
||||||
|
"DownstreamPathTemplate": "/service/stats/collected",
|
||||||
|
"DownstreamScheme": "http",
|
||||||
|
"DownstreamHostAndPorts": [
|
||||||
|
{
|
||||||
|
"Host": "localhost",
|
||||||
|
"Port": 5100
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"UpstreamPathTemplate": "/api/stats/collected"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"GlobalConfiguration": {
|
||||||
|
"BaseUrl": "http://localhost:5000"
|
||||||
|
}
|
||||||
|
}
|
1
samples/AdministrationApi/tempkey.rsa
Normal file
1
samples/AdministrationApi/tempkey.rsa
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"KeyId":"44cc0f50fec6905aa63902a5a5aa9f6b","Parameters":{"D":"Utr6ZW4f9G5IOmkibdGWtujp3J38XuxyDeBxxmR4WhYzknu6QLGPJOuYpzF9bGXcpBoZoPVw1bXtDQXr3eoQt14/ndY3nZOka3+vXeEBc4C9BLHOv7QYzS+lvxduyS1xpRsUX/z5iL/UrvpllO0Ii4JiurBuO1Vi642M1930oT7Iw9sI7o8nNe7bCgMEeTAGgOG/c1SRFV1oA1eg5QaLlBVL4633p5XIhY9ZX7x0RVCfbxcgHYor6YAfK0f792j5qkHJa69WoTLf3kF7QSHDeg5vCG6tzUvSmVt+TgpU7w8wIch+9zkQhoFv5i7wGBDwWu7YFdc2fd7ESbfu7r3EOQ==","DP":"rah2ooiX6Ldf0g+Wwh7E7t7uvIlJVcYX/oGFrHjX2Zv7uNimMYxwQqJGxWSyDbqNaqVNH73KHuvJXbVz0Bch8VM16pJEhcw/cMehiW0/QvjVKwe0B8r7C5iCff3w56N303NdynObv4XwPCXKDLqbWjHBeNtVx3ffCUAm1FOyYjc=","DQ":"Gjkt8WCO68zHnLYJ3MYPUrrVwTxEThrN7D7zHCF24bldIu4aDd2SF9Ne/nOn3pXipQT98h+3i545W/9GDdj8LA+mLJU0RSByBQsq+KFjJbHVlG7XuNPIClB4o3JGKQ3BT29sN/we4vW4KOdTB3UlBLdw5oa0XmrhO4EockLjJGs=","Exponent":"AQAB","InverseQ":"pNDcSxe3RS1gQ4ORDCPy0EfLifTGjli/4OsaTC/F3THTrq8tqpq7qDlAn95h2bLDFDjK29X3u1NyJgzSgEP2LdhNloRgTVCDoFOmE40DvGmVg1PPaeaLXFnV+zQpam3gL34/GNdt1dFXzVE5yb7VSwqsRTJHXoylEddU/LKG8hs=","Modulus":"u8aKNe9Ma7P6w/Atz9eH0j8SqDvaOZ68cI59GEszYrGiNCdG16XqEUTnrhRCn7HkyWdTS3gcSROEldAG1TAp8E3SvwUzU14M2K13QjQDFdCE6H6oiCSecBP/WfiCdSPOqQ5WLksefGi4sMLMRuo3xrtXWXUFpViHRryQc6zlYcmbGoxCz3bDL0/ATTWf+kxCf4BFGV6TDFOQDzF2tTOz823dqpb3+/bjuiY6FkcUFtYIY+jrPwIvjzDU1DDufsFJHPfvvsFfUFX2BziyZMFifzAnd+Nggq2LS2rem/S9BZe/0NnMHp603IPNiumi2DuWXasTqDUPSAbipXwRqhUuYQ==","P":"1Ley2G5UXKYmMu+Yz2Et2c6oUP8w1W/JIYs0VMKR3IF2nSS6aAYr5CBe93nLPElMeKWXK6V2NhsXwQbTWd4RBsQzdFe/ncghGLCWqwzNdZ12g3YPVmw3cG91ASyhxXb9drOi1ukFCMJp+UNdp6qj0zhDOqQtqQLUiSC7qUryh8s=","Q":"4fuhKLbcxJOOAMvPN6QJ2xyYbJg0UsdZARKpJ5J/atFoMajZL8D7tGCAyPwBSYsZcgBNQiJ1dU316Kjq/7tlunK6baZrmFhM7yzfPXIBhH5GkI8i5X3By7TUvdFDiYG2UfVIqA1tRQ+wx8z3Ts8JqAXYJJxskHPJJM5Hi8av5QM="}}
|
@ -15,7 +15,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.6" />
|
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.9" />
|
||||||
<PackageReference Include="Ocelot" Version="5.5.7" />
|
<PackageReference Include="Ocelot" Version="5.5.7" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.6" />
|
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.9" />
|
||||||
<PackageReference Include="Steeltoe.Discovery.Client" Version="1.1.0" />
|
<PackageReference Include="Steeltoe.Discovery.Client" Version="1.1.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
<Folder Include="wwwroot\"/>
|
<Folder Include="wwwroot\"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.6"/>
|
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.9"/>
|
||||||
<PackageReference Include="Ocelot" Version="5.5.1"/>
|
<PackageReference Include="Ocelot" Version="5.5.1"/>
|
||||||
<PackageReference Include="GraphQL" Version="2.0.0-alpha-870"/>
|
<PackageReference Include="GraphQL" Version="2.0.0-alpha-870"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
14
src/Ocelot.Administration/IIdentityServerConfiguration.cs
Normal file
14
src/Ocelot.Administration/IIdentityServerConfiguration.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
namespace Ocelot.Administration
|
||||||
|
{
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
public interface IIdentityServerConfiguration
|
||||||
|
{
|
||||||
|
string ApiName { get; }
|
||||||
|
string ApiSecret { get; }
|
||||||
|
bool RequireHttps { get; }
|
||||||
|
List<string> AllowedScopes { get; }
|
||||||
|
string CredentialsSigningCertificateLocation { get; }
|
||||||
|
string CredentialsSigningCertificatePassword { get; }
|
||||||
|
}
|
||||||
|
}
|
30
src/Ocelot.Administration/IdentityServerConfiguration.cs
Normal file
30
src/Ocelot.Administration/IdentityServerConfiguration.cs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
namespace Ocelot.Administration
|
||||||
|
{
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
public class IdentityServerConfiguration : IIdentityServerConfiguration
|
||||||
|
{
|
||||||
|
public IdentityServerConfiguration(
|
||||||
|
string apiName,
|
||||||
|
bool requireHttps,
|
||||||
|
string apiSecret,
|
||||||
|
List<string> allowedScopes,
|
||||||
|
string credentialsSigningCertificateLocation,
|
||||||
|
string credentialsSigningCertificatePassword)
|
||||||
|
{
|
||||||
|
ApiName = apiName;
|
||||||
|
RequireHttps = requireHttps;
|
||||||
|
ApiSecret = apiSecret;
|
||||||
|
AllowedScopes = allowedScopes;
|
||||||
|
CredentialsSigningCertificateLocation = credentialsSigningCertificateLocation;
|
||||||
|
CredentialsSigningCertificatePassword = credentialsSigningCertificatePassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ApiName { get; }
|
||||||
|
public bool RequireHttps { get; }
|
||||||
|
public List<string> AllowedScopes { get; }
|
||||||
|
public string ApiSecret { get; }
|
||||||
|
public string CredentialsSigningCertificateLocation { get; }
|
||||||
|
public string CredentialsSigningCertificatePassword { get; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Ocelot.Administration
|
||||||
|
{
|
||||||
|
public static class IdentityServerConfigurationCreator
|
||||||
|
{
|
||||||
|
public static IdentityServerConfiguration GetIdentityServerConfiguration(string secret)
|
||||||
|
{
|
||||||
|
var credentialsSigningCertificateLocation = Environment.GetEnvironmentVariable("OCELOT_CERTIFICATE");
|
||||||
|
var credentialsSigningCertificatePassword = Environment.GetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD");
|
||||||
|
|
||||||
|
return new IdentityServerConfiguration(
|
||||||
|
"admin",
|
||||||
|
false,
|
||||||
|
secret,
|
||||||
|
new List<string> { "admin", "openid", "offline_access" },
|
||||||
|
credentialsSigningCertificateLocation,
|
||||||
|
credentialsSigningCertificatePassword
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
namespace Ocelot.Administration
|
||||||
|
{
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Configuration;
|
||||||
|
using Configuration.Repository;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
|
||||||
|
public static class IdentityServerMiddlewareConfigurationProvider
|
||||||
|
{
|
||||||
|
public static OcelotMiddlewareConfigurationDelegate Get = builder =>
|
||||||
|
{
|
||||||
|
var internalConfigRepo = builder.ApplicationServices.GetService<IInternalConfigurationRepository>();
|
||||||
|
|
||||||
|
var config = internalConfigRepo.Get();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(config.Data.AdministrationPath))
|
||||||
|
{
|
||||||
|
builder.Map(config.Data.AdministrationPath, app =>
|
||||||
|
{
|
||||||
|
//todo - hack so we know that we are using internal identity server
|
||||||
|
var identityServerConfiguration = builder.ApplicationServices.GetService<IIdentityServerConfiguration>();
|
||||||
|
|
||||||
|
if (identityServerConfiguration != null)
|
||||||
|
{
|
||||||
|
app.UseIdentityServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.UseAuthentication();
|
||||||
|
app.UseMvc();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
38
src/Ocelot.Administration/Ocelot.Administration.csproj
Normal file
38
src/Ocelot.Administration/Ocelot.Administration.csproj
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
||||||
|
<NETStandardImplicitPackageVersion>2.0.0</NETStandardImplicitPackageVersion>
|
||||||
|
<NoPackageAnalysis>true</NoPackageAnalysis>
|
||||||
|
<Description>Provides Ocelot extensions to use the administration API and IdentityService dependencies that come with it</Description>
|
||||||
|
<AssemblyTitle>Ocelot.Administration</AssemblyTitle>
|
||||||
|
<VersionPrefix>0.0.0-dev</VersionPrefix>
|
||||||
|
<AssemblyName>Ocelot.Administration</AssemblyName>
|
||||||
|
<PackageId>Ocelot.Administration</PackageId>
|
||||||
|
<PackageTags>API Gateway;.NET core</PackageTags>
|
||||||
|
<PackageProjectUrl>https://github.com/ThreeMammals/Ocelot.Administration</PackageProjectUrl>
|
||||||
|
<PackageProjectUrl>https://github.com/ThreeMammals/Ocelot.Administration</PackageProjectUrl>
|
||||||
|
<PackageIconUrl>http://threemammals.com/images/ocelot_logo.png</PackageIconUrl>
|
||||||
|
<RuntimeIdentifiers>win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64</RuntimeIdentifiers>
|
||||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
||||||
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
|
||||||
|
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||||
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
|
||||||
|
<Authors>Tom Pallister</Authors>
|
||||||
|
<CodeAnalysisRuleSet>..\..\codeanalysis.ruleset</CodeAnalysisRuleSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<DebugSymbols>True</DebugSymbols>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Ocelot\Ocelot.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="2.6.0" />
|
||||||
|
<PackageReference Include="IdentityServer4" Version="2.2.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
128
src/Ocelot.Administration/OcelotBuilderExtensions.cs
Normal file
128
src/Ocelot.Administration/OcelotBuilderExtensions.cs
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
namespace Ocelot.Administration
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using Configuration;
|
||||||
|
using Configuration.Creator;
|
||||||
|
using DependencyInjection;
|
||||||
|
using IdentityModel;
|
||||||
|
using IdentityServer4.AccessTokenValidation;
|
||||||
|
using IdentityServer4.Models;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
|
||||||
|
public static class OcelotBuilderExtensions
|
||||||
|
{
|
||||||
|
public static IOcelotAdministrationBuilder AddAdministration(this IOcelotBuilder builder, string path, string secret)
|
||||||
|
{
|
||||||
|
var administrationPath = new AdministrationPath(path);
|
||||||
|
builder.Services.AddSingleton<OcelotMiddlewareConfigurationDelegate>(IdentityServerMiddlewareConfigurationProvider.Get);
|
||||||
|
|
||||||
|
//add identity server for admin area
|
||||||
|
var identityServerConfiguration = IdentityServerConfigurationCreator.GetIdentityServerConfiguration(secret);
|
||||||
|
|
||||||
|
if (identityServerConfiguration != null)
|
||||||
|
{
|
||||||
|
AddIdentityServer(identityServerConfiguration, administrationPath, builder, builder.Configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.Services.AddSingleton<IAdministrationPath>(administrationPath);
|
||||||
|
return new OcelotAdministrationBuilder(builder.Services, builder.Configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IOcelotAdministrationBuilder AddAdministration(this IOcelotBuilder builder, string path, Action<IdentityServerAuthenticationOptions> configureOptions)
|
||||||
|
{
|
||||||
|
var administrationPath = new AdministrationPath(path);
|
||||||
|
builder.Services.AddSingleton<OcelotMiddlewareConfigurationDelegate>(IdentityServerMiddlewareConfigurationProvider.Get);
|
||||||
|
|
||||||
|
if (configureOptions != null)
|
||||||
|
{
|
||||||
|
AddIdentityServer(configureOptions, builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.Services.AddSingleton<IAdministrationPath>(administrationPath);
|
||||||
|
return new OcelotAdministrationBuilder(builder.Services, builder.Configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddIdentityServer(Action<IdentityServerAuthenticationOptions> configOptions, IOcelotBuilder builder)
|
||||||
|
{
|
||||||
|
builder.Services
|
||||||
|
.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
|
||||||
|
.AddIdentityServerAuthentication(configOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddIdentityServer(IIdentityServerConfiguration identityServerConfiguration, IAdministrationPath adminPath, IOcelotBuilder builder, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
builder.Services.TryAddSingleton<IIdentityServerConfiguration>(identityServerConfiguration);
|
||||||
|
var identityServerBuilder = builder.Services
|
||||||
|
.AddIdentityServer(o => {
|
||||||
|
o.IssuerUri = "Ocelot";
|
||||||
|
})
|
||||||
|
.AddInMemoryApiResources(Resources(identityServerConfiguration))
|
||||||
|
.AddInMemoryClients(Client(identityServerConfiguration));
|
||||||
|
|
||||||
|
var urlFinder = new BaseUrlFinder(configuration);
|
||||||
|
var baseSchemeUrlAndPort = urlFinder.Find();
|
||||||
|
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
|
||||||
|
|
||||||
|
builder.Services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
|
||||||
|
.AddIdentityServerAuthentication(o =>
|
||||||
|
{
|
||||||
|
o.Authority = baseSchemeUrlAndPort + adminPath.Path;
|
||||||
|
o.ApiName = identityServerConfiguration.ApiName;
|
||||||
|
o.RequireHttpsMetadata = identityServerConfiguration.RequireHttps;
|
||||||
|
o.SupportedTokens = SupportedTokens.Both;
|
||||||
|
o.ApiSecret = identityServerConfiguration.ApiSecret;
|
||||||
|
});
|
||||||
|
|
||||||
|
//todo - refactor naming..
|
||||||
|
if (string.IsNullOrEmpty(identityServerConfiguration.CredentialsSigningCertificateLocation) || string.IsNullOrEmpty(identityServerConfiguration.CredentialsSigningCertificatePassword))
|
||||||
|
{
|
||||||
|
identityServerBuilder.AddDeveloperSigningCredential();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//todo - refactor so calls method?
|
||||||
|
var cert = new X509Certificate2(identityServerConfiguration.CredentialsSigningCertificateLocation, identityServerConfiguration.CredentialsSigningCertificatePassword);
|
||||||
|
identityServerBuilder.AddSigningCredential(cert);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<ApiResource> Resources(IIdentityServerConfiguration identityServerConfiguration)
|
||||||
|
{
|
||||||
|
return new List<ApiResource>
|
||||||
|
{
|
||||||
|
new ApiResource(identityServerConfiguration.ApiName, identityServerConfiguration.ApiName)
|
||||||
|
{
|
||||||
|
ApiSecrets = new List<Secret>
|
||||||
|
{
|
||||||
|
new Secret
|
||||||
|
{
|
||||||
|
Value = identityServerConfiguration.ApiSecret.Sha256()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Client> Client(IIdentityServerConfiguration identityServerConfiguration)
|
||||||
|
{
|
||||||
|
return new List<Client>
|
||||||
|
{
|
||||||
|
new Client
|
||||||
|
{
|
||||||
|
ClientId = identityServerConfiguration.ApiName,
|
||||||
|
AllowedGrantTypes = GrantTypes.ClientCredentials,
|
||||||
|
ClientSecrets = new List<Secret> {new Secret(identityServerConfiguration.ApiSecret.Sha256())},
|
||||||
|
AllowedScopes = { identityServerConfiguration.ApiName }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
src/Ocelot.Administration/Properties/AssemblyInfo.cs
Normal file
18
src/Ocelot.Administration/Properties/AssemblyInfo.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
// General Information about an assembly is controlled through the following
|
||||||
|
// set of attributes. Change these attribute values to modify the information
|
||||||
|
// associated with an assembly.
|
||||||
|
[assembly: AssemblyConfiguration("")]
|
||||||
|
[assembly: AssemblyCompany("")]
|
||||||
|
[assembly: AssemblyProduct("Ocelot")]
|
||||||
|
[assembly: AssemblyTrademark("")]
|
||||||
|
|
||||||
|
// Setting ComVisible to false makes the types in this assembly not visible
|
||||||
|
// to COM components. If you need to access a type in this assembly from
|
||||||
|
// COM, set the ComVisible attribute to true on that type.
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
|
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||||
|
[assembly: Guid("d6df4206-0dba-41d8-884d-c3e08290fdbb")]
|
@ -0,0 +1,38 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
||||||
|
<NETStandardImplicitPackageVersion>2.0.0</NETStandardImplicitPackageVersion>
|
||||||
|
<NoPackageAnalysis>true</NoPackageAnalysis>
|
||||||
|
<Description>Provides Ocelot extensions to use CacheManager.Net</Description>
|
||||||
|
<AssemblyTitle>Ocelot.Cache.CacheManager</AssemblyTitle>
|
||||||
|
<VersionPrefix>0.0.0-dev</VersionPrefix>
|
||||||
|
<AssemblyName>Ocelot.Cache.CacheManager</AssemblyName>
|
||||||
|
<PackageId>Ocelot.Cache.CacheManager</PackageId>
|
||||||
|
<PackageTags>API Gateway;.NET core</PackageTags>
|
||||||
|
<PackageProjectUrl>https://github.com/ThreeMammals/Ocelot.Cache.CacheManager</PackageProjectUrl>
|
||||||
|
<PackageProjectUrl>https://github.com/ThreeMammals/Ocelot.Cache.CacheManager</PackageProjectUrl>
|
||||||
|
<RuntimeIdentifiers>win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64</RuntimeIdentifiers>
|
||||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
||||||
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
|
||||||
|
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||||
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
|
||||||
|
<Authors>Tom Pallister</Authors>
|
||||||
|
<CodeAnalysisRuleSet>..\..\codeanalysis.ruleset</CodeAnalysisRuleSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<DebugSymbols>True</DebugSymbols>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Ocelot\Ocelot.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="CacheManager.Core" Version="1.1.2" />
|
||||||
|
<PackageReference Include="CacheManager.Microsoft.Extensions.Configuration" Version="1.1.2" />
|
||||||
|
<PackageReference Include="CacheManager.Microsoft.Extensions.Logging" Version="1.1.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
39
src/Ocelot.Cache.CacheManager/OcelotBuilderExtensions.cs
Normal file
39
src/Ocelot.Cache.CacheManager/OcelotBuilderExtensions.cs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
namespace Ocelot.Cache.CacheManager
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using Configuration;
|
||||||
|
using Configuration.File;
|
||||||
|
using DependencyInjection;
|
||||||
|
using global::CacheManager.Core;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
|
|
||||||
|
public static class OcelotBuilderExtensions
|
||||||
|
{
|
||||||
|
public static IOcelotBuilder AddCacheManager(this IOcelotBuilder builder, Action<ConfigurationBuilderCachePart> settings)
|
||||||
|
{
|
||||||
|
var cacheManagerOutputCache = CacheFactory.Build<CachedResponse>("OcelotOutputCache", settings);
|
||||||
|
var ocelotOutputCacheManager = new OcelotCacheManagerCache<CachedResponse>(cacheManagerOutputCache);
|
||||||
|
|
||||||
|
builder.Services.RemoveAll(typeof(ICacheManager<CachedResponse>));
|
||||||
|
builder.Services.RemoveAll(typeof(IOcelotCache<CachedResponse>));
|
||||||
|
builder.Services.AddSingleton<ICacheManager<CachedResponse>>(cacheManagerOutputCache);
|
||||||
|
builder.Services.AddSingleton<IOcelotCache<CachedResponse>>(ocelotOutputCacheManager);
|
||||||
|
|
||||||
|
var ocelotConfigCacheManagerOutputCache = CacheFactory.Build<IInternalConfiguration>("OcelotConfigurationCache", settings);
|
||||||
|
var ocelotConfigCacheManager = new OcelotCacheManagerCache<IInternalConfiguration>(ocelotConfigCacheManagerOutputCache);
|
||||||
|
builder.Services.RemoveAll(typeof(ICacheManager<IInternalConfiguration>));
|
||||||
|
builder.Services.RemoveAll(typeof(IOcelotCache<IInternalConfiguration>));
|
||||||
|
builder.Services.AddSingleton<ICacheManager<IInternalConfiguration>>(ocelotConfigCacheManagerOutputCache);
|
||||||
|
builder.Services.AddSingleton<IOcelotCache<IInternalConfiguration>>(ocelotConfigCacheManager);
|
||||||
|
|
||||||
|
var fileConfigCacheManagerOutputCache = CacheFactory.Build<FileConfiguration>("FileConfigurationCache", settings);
|
||||||
|
var fileConfigCacheManager = new OcelotCacheManagerCache<FileConfiguration>(fileConfigCacheManagerOutputCache);
|
||||||
|
builder.Services.RemoveAll(typeof(ICacheManager<FileConfiguration>));
|
||||||
|
builder.Services.RemoveAll(typeof(IOcelotCache<FileConfiguration>));
|
||||||
|
builder.Services.AddSingleton<ICacheManager<FileConfiguration>>(fileConfigCacheManagerOutputCache);
|
||||||
|
builder.Services.AddSingleton<IOcelotCache<FileConfiguration>>(fileConfigCacheManager);
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
42
src/Ocelot.Cache.CacheManager/OcelotCacheManagerCache.cs
Normal file
42
src/Ocelot.Cache.CacheManager/OcelotCacheManagerCache.cs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
namespace Ocelot.Cache.CacheManager
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using global::CacheManager.Core;
|
||||||
|
|
||||||
|
public class OcelotCacheManagerCache<T> : IOcelotCache<T>
|
||||||
|
{
|
||||||
|
private readonly ICacheManager<T> _cacheManager;
|
||||||
|
|
||||||
|
public OcelotCacheManagerCache(ICacheManager<T> cacheManager)
|
||||||
|
{
|
||||||
|
_cacheManager = cacheManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(string key, T value, TimeSpan ttl, string region)
|
||||||
|
{
|
||||||
|
_cacheManager.Add(new CacheItem<T>(key, region, value, ExpirationMode.Absolute, ttl));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddAndDelete(string key, T value, TimeSpan ttl, string region)
|
||||||
|
{
|
||||||
|
var exists = _cacheManager.Get(key);
|
||||||
|
|
||||||
|
if (exists != null)
|
||||||
|
{
|
||||||
|
_cacheManager.Remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
Add(key, value, ttl, region);
|
||||||
|
}
|
||||||
|
|
||||||
|
public T Get(string key, string region)
|
||||||
|
{
|
||||||
|
return _cacheManager.Get<T>(key, region);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearRegion(string region)
|
||||||
|
{
|
||||||
|
_cacheManager.ClearRegion(region);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
src/Ocelot.Cache.CacheManager/Properties/AssemblyInfo.cs
Normal file
18
src/Ocelot.Cache.CacheManager/Properties/AssemblyInfo.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
// General Information about an assembly is controlled through the following
|
||||||
|
// set of attributes. Change these attribute values to modify the information
|
||||||
|
// associated with an assembly.
|
||||||
|
[assembly: AssemblyConfiguration("")]
|
||||||
|
[assembly: AssemblyCompany("")]
|
||||||
|
[assembly: AssemblyProduct("Ocelot")]
|
||||||
|
[assembly: AssemblyTrademark("")]
|
||||||
|
|
||||||
|
// Setting ComVisible to false makes the types in this assembly not visible
|
||||||
|
// to COM components. If you need to access a type in this assembly from
|
||||||
|
// COM, set the ComVisible attribute to true on that type.
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
|
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||||
|
[assembly: Guid("d6df4206-0dba-41d8-884d-c3e08290fdbb")]
|
76
src/Ocelot.Provider.Consul/Consul.cs
Normal file
76
src/Ocelot.Provider.Consul/Consul.cs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
namespace Ocelot.Provider.Consul
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using global::Consul;
|
||||||
|
using Infrastructure.Extensions;
|
||||||
|
using Logging;
|
||||||
|
using ServiceDiscovery.Providers;
|
||||||
|
using Values;
|
||||||
|
|
||||||
|
|
||||||
|
public class Consul : IServiceDiscoveryProvider
|
||||||
|
{
|
||||||
|
private readonly ConsulRegistryConfiguration _config;
|
||||||
|
private readonly IOcelotLogger _logger;
|
||||||
|
private readonly IConsulClient _consul;
|
||||||
|
private const string VersionPrefix = "version-";
|
||||||
|
|
||||||
|
public Consul(ConsulRegistryConfiguration config, IOcelotLoggerFactory factory, IConsulClientFactory clientFactory)
|
||||||
|
{
|
||||||
|
_logger = factory.CreateLogger<Consul>();
|
||||||
|
_config = config;
|
||||||
|
_consul = clientFactory.Get(_config);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<Service>> Get()
|
||||||
|
{
|
||||||
|
var queryResult = await _consul.Health.Service(_config.KeyOfServiceInConsul, string.Empty, true);
|
||||||
|
|
||||||
|
var services = new List<Service>();
|
||||||
|
|
||||||
|
foreach (var serviceEntry in queryResult.Response)
|
||||||
|
{
|
||||||
|
if (IsValid(serviceEntry))
|
||||||
|
{
|
||||||
|
services.Add(BuildService(serviceEntry));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogWarning($"Unable to use service Address: {serviceEntry.Service.Address} and Port: {serviceEntry.Service.Port} as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return services.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Service BuildService(ServiceEntry serviceEntry)
|
||||||
|
{
|
||||||
|
return new Service(
|
||||||
|
serviceEntry.Service.Service,
|
||||||
|
new ServiceHostAndPort(serviceEntry.Service.Address, serviceEntry.Service.Port),
|
||||||
|
serviceEntry.Service.ID,
|
||||||
|
GetVersionFromStrings(serviceEntry.Service.Tags),
|
||||||
|
serviceEntry.Service.Tags ?? Enumerable.Empty<string>());
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsValid(ServiceEntry serviceEntry)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(serviceEntry.Service.Address) || serviceEntry.Service.Address.Contains("http://") || serviceEntry.Service.Address.Contains("https://") || serviceEntry.Service.Port <= 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetVersionFromStrings(IEnumerable<string> strings)
|
||||||
|
{
|
||||||
|
return strings
|
||||||
|
?.FirstOrDefault(x => x.StartsWith(VersionPrefix, StringComparison.Ordinal))
|
||||||
|
.TrimStart(VersionPrefix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
src/Ocelot.Provider.Consul/ConsulClientFactory.cs
Normal file
21
src/Ocelot.Provider.Consul/ConsulClientFactory.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
namespace Ocelot.Provider.Consul
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using global::Consul;
|
||||||
|
|
||||||
|
public class ConsulClientFactory : IConsulClientFactory
|
||||||
|
{
|
||||||
|
public IConsulClient Get(ConsulRegistryConfiguration config)
|
||||||
|
{
|
||||||
|
return new ConsulClient(c =>
|
||||||
|
{
|
||||||
|
c.Address = new Uri($"http://{config.Host}:{config.Port}");
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(config?.Token))
|
||||||
|
{
|
||||||
|
c.Token = config.Token;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
namespace Ocelot.Provider.Consul
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Configuration.File;
|
||||||
|
using Configuration.Repository;
|
||||||
|
using global::Consul;
|
||||||
|
using Logging;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Responses;
|
||||||
|
|
||||||
|
public class ConsulFileConfigurationRepository : IFileConfigurationRepository
|
||||||
|
{
|
||||||
|
private readonly IConsulClient _consul;
|
||||||
|
private readonly string _configurationKey;
|
||||||
|
private readonly Cache.IOcelotCache<FileConfiguration> _cache;
|
||||||
|
private readonly IOcelotLogger _logger;
|
||||||
|
|
||||||
|
public ConsulFileConfigurationRepository(
|
||||||
|
Cache.IOcelotCache<FileConfiguration> cache,
|
||||||
|
IInternalConfigurationRepository repo,
|
||||||
|
IConsulClientFactory factory,
|
||||||
|
IOcelotLoggerFactory loggerFactory)
|
||||||
|
{
|
||||||
|
_logger = loggerFactory.CreateLogger<ConsulFileConfigurationRepository>();
|
||||||
|
_cache = cache;
|
||||||
|
|
||||||
|
var internalConfig = repo.Get();
|
||||||
|
|
||||||
|
_configurationKey = "InternalConfiguration";
|
||||||
|
|
||||||
|
string token = null;
|
||||||
|
|
||||||
|
if (!internalConfig.IsError)
|
||||||
|
{
|
||||||
|
token = internalConfig.Data.ServiceProviderConfiguration.Token;
|
||||||
|
_configurationKey = !string.IsNullOrEmpty(internalConfig.Data.ServiceProviderConfiguration.ConfigurationKey) ?
|
||||||
|
internalConfig.Data.ServiceProviderConfiguration.ConfigurationKey : _configurationKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = new ConsulRegistryConfiguration(internalConfig.Data.ServiceProviderConfiguration.Host,
|
||||||
|
internalConfig.Data.ServiceProviderConfiguration.Port, _configurationKey, token);
|
||||||
|
|
||||||
|
_consul = factory.Get(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Response<FileConfiguration>> Get()
|
||||||
|
{
|
||||||
|
var config = _cache.Get(_configurationKey, _configurationKey);
|
||||||
|
|
||||||
|
if (config != null)
|
||||||
|
{
|
||||||
|
return new OkResponse<FileConfiguration>(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
var queryResult = await _consul.KV.Get(_configurationKey);
|
||||||
|
|
||||||
|
if (queryResult.Response == null)
|
||||||
|
{
|
||||||
|
return new OkResponse<FileConfiguration>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes = queryResult.Response.Value;
|
||||||
|
|
||||||
|
var json = Encoding.UTF8.GetString(bytes);
|
||||||
|
|
||||||
|
var consulConfig = JsonConvert.DeserializeObject<FileConfiguration>(json);
|
||||||
|
|
||||||
|
return new OkResponse<FileConfiguration>(consulConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Response> Set(FileConfiguration ocelotConfiguration)
|
||||||
|
{
|
||||||
|
var json = JsonConvert.SerializeObject(ocelotConfiguration, Formatting.Indented);
|
||||||
|
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(json);
|
||||||
|
|
||||||
|
var kvPair = new KVPair(_configurationKey)
|
||||||
|
{
|
||||||
|
Value = bytes
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await _consul.KV.Put(kvPair);
|
||||||
|
|
||||||
|
if (result.Response)
|
||||||
|
{
|
||||||
|
_cache.AddAndDelete(_configurationKey, ocelotConfiguration, TimeSpan.FromSeconds(3), _configurationKey);
|
||||||
|
|
||||||
|
return new OkResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ErrorResponse(new UnableToSetConfigInConsulError($"Unable to set FileConfiguration in consul, response status code from consul was {result.StatusCode}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
namespace Ocelot.Provider.Consul
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Configuration.Creator;
|
||||||
|
using Configuration.File;
|
||||||
|
using Configuration.Repository;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Middleware;
|
||||||
|
using Responses;
|
||||||
|
|
||||||
|
public static class ConsulMiddlewareConfigurationProvider
|
||||||
|
{
|
||||||
|
public static OcelotMiddlewareConfigurationDelegate Get = async builder =>
|
||||||
|
{
|
||||||
|
var fileConfigRepo = builder.ApplicationServices.GetService<IFileConfigurationRepository>();
|
||||||
|
var fileConfig = builder.ApplicationServices.GetService<IOptionsMonitor<FileConfiguration>>();
|
||||||
|
var internalConfigCreator = builder.ApplicationServices.GetService<IInternalConfigurationCreator>();
|
||||||
|
var internalConfigRepo = builder.ApplicationServices.GetService<IInternalConfigurationRepository>();
|
||||||
|
|
||||||
|
if (UsingConsul(fileConfigRepo))
|
||||||
|
{
|
||||||
|
await SetFileConfigInConsul(builder, fileConfigRepo, fileConfig, internalConfigCreator, internalConfigRepo);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static bool UsingConsul(IFileConfigurationRepository fileConfigRepo)
|
||||||
|
{
|
||||||
|
return fileConfigRepo.GetType() == typeof(ConsulFileConfigurationRepository);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task SetFileConfigInConsul(IApplicationBuilder builder,
|
||||||
|
IFileConfigurationRepository fileConfigRepo, IOptionsMonitor<FileConfiguration> fileConfig,
|
||||||
|
IInternalConfigurationCreator internalConfigCreator, IInternalConfigurationRepository internalConfigRepo)
|
||||||
|
{
|
||||||
|
// get the config from consul.
|
||||||
|
var fileConfigFromConsul = await fileConfigRepo.Get();
|
||||||
|
|
||||||
|
if (IsError(fileConfigFromConsul))
|
||||||
|
{
|
||||||
|
ThrowToStopOcelotStarting(fileConfigFromConsul);
|
||||||
|
}
|
||||||
|
else if (ConfigNotStoredInConsul(fileConfigFromConsul))
|
||||||
|
{
|
||||||
|
//there was no config in consul set the file in config in consul
|
||||||
|
await fileConfigRepo.Set(fileConfig.CurrentValue);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// create the internal config from consul data
|
||||||
|
var internalConfig = await internalConfigCreator.Create(fileConfigFromConsul.Data);
|
||||||
|
|
||||||
|
if (IsError(internalConfig))
|
||||||
|
{
|
||||||
|
ThrowToStopOcelotStarting(internalConfig);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// add the internal config to the internal repo
|
||||||
|
var response = internalConfigRepo.AddOrReplace(internalConfig.Data);
|
||||||
|
|
||||||
|
if (IsError(response))
|
||||||
|
{
|
||||||
|
ThrowToStopOcelotStarting(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsError(internalConfig))
|
||||||
|
{
|
||||||
|
ThrowToStopOcelotStarting(internalConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ThrowToStopOcelotStarting(Response config)
|
||||||
|
{
|
||||||
|
throw new Exception($"Unable to start Ocelot, errors are: {string.Join(",", config.Errors.Select(x => x.ToString()))}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsError(Response response)
|
||||||
|
{
|
||||||
|
return response == null || response.IsError;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ConfigNotStoredInConsul(Response<FileConfiguration> fileConfigFromConsul)
|
||||||
|
{
|
||||||
|
return fileConfigFromConsul.Data == null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
src/Ocelot.Provider.Consul/ConsulProviderFactory.cs
Normal file
29
src/Ocelot.Provider.Consul/ConsulProviderFactory.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
namespace Ocelot.Provider.Consul
|
||||||
|
{
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Logging;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using ServiceDiscovery;
|
||||||
|
|
||||||
|
public static class ConsulProviderFactory
|
||||||
|
{
|
||||||
|
public static ServiceDiscoveryFinderDelegate Get = (provider, config, name) =>
|
||||||
|
{
|
||||||
|
var factory = provider.GetService<IOcelotLoggerFactory>();
|
||||||
|
|
||||||
|
var consulFactory = provider.GetService<IConsulClientFactory>();
|
||||||
|
|
||||||
|
var consulRegistryConfiguration = new ConsulRegistryConfiguration(config.Host, config.Port, name, config.Token);
|
||||||
|
|
||||||
|
var consulServiceDiscoveryProvider = new Consul(consulRegistryConfiguration, factory, consulFactory);
|
||||||
|
|
||||||
|
if (config.Type?.ToLower() == "pollconsul")
|
||||||
|
{
|
||||||
|
return new PollConsul(config.PollingInterval, factory, consulServiceDiscoveryProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
return consulServiceDiscoveryProvider;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
18
src/Ocelot.Provider.Consul/ConsulRegistryConfiguration.cs
Normal file
18
src/Ocelot.Provider.Consul/ConsulRegistryConfiguration.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
namespace Ocelot.Provider.Consul
|
||||||
|
{
|
||||||
|
public class ConsulRegistryConfiguration
|
||||||
|
{
|
||||||
|
public ConsulRegistryConfiguration(string host, int port, string keyOfServiceInConsul, string token)
|
||||||
|
{
|
||||||
|
Host = string.IsNullOrEmpty(host) ? "localhost" : host;
|
||||||
|
Port = port > 0 ? port : 8500;
|
||||||
|
KeyOfServiceInConsul = keyOfServiceInConsul;
|
||||||
|
Token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string KeyOfServiceInConsul { get; }
|
||||||
|
public string Host { get; }
|
||||||
|
public int Port { get; }
|
||||||
|
public string Token { get; }
|
||||||
|
}
|
||||||
|
}
|
9
src/Ocelot.Provider.Consul/IConsulClientFactory.cs
Normal file
9
src/Ocelot.Provider.Consul/IConsulClientFactory.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace Ocelot.Provider.Consul
|
||||||
|
{
|
||||||
|
using global::Consul;
|
||||||
|
|
||||||
|
public interface IConsulClientFactory
|
||||||
|
{
|
||||||
|
IConsulClient Get(ConsulRegistryConfiguration config);
|
||||||
|
}
|
||||||
|
}
|
37
src/Ocelot.Provider.Consul/Ocelot.Provider.Consul.csproj
Normal file
37
src/Ocelot.Provider.Consul/Ocelot.Provider.Consul.csproj
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
||||||
|
<NETStandardImplicitPackageVersion>2.0.0</NETStandardImplicitPackageVersion>
|
||||||
|
<NoPackageAnalysis>true</NoPackageAnalysis>
|
||||||
|
<Description>Provides Ocelot extensions to use Consul</Description>
|
||||||
|
<AssemblyTitle>Ocelot.Provider.Consul</AssemblyTitle>
|
||||||
|
<VersionPrefix>0.0.0-dev</VersionPrefix>
|
||||||
|
<AssemblyName>Ocelot.Provider.Consul</AssemblyName>
|
||||||
|
<PackageId>Ocelot.Provider.Consul</PackageId>
|
||||||
|
<PackageTags>API Gateway;.NET core</PackageTags>
|
||||||
|
<PackageProjectUrl>https://github.com/ThreeMammals/Ocelot.Provider.Consul</PackageProjectUrl>
|
||||||
|
<PackageProjectUrl>https://github.com/ThreeMammals/Ocelot.Provider.Consul</PackageProjectUrl>
|
||||||
|
<PackageIconUrl>http://threemammals.com/images/ocelot_logo.png</PackageIconUrl>
|
||||||
|
<RuntimeIdentifiers>win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64</RuntimeIdentifiers>
|
||||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
||||||
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
|
||||||
|
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||||
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
|
||||||
|
<Authors>Tom Pallister</Authors>
|
||||||
|
<CodeAnalysisRuleSet>..\..\codeanalysis.ruleset</CodeAnalysisRuleSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<DebugSymbols>True</DebugSymbols>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Ocelot\Ocelot.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Consul" Version="0.7.2.6" />
|
||||||
|
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
26
src/Ocelot.Provider.Consul/OcelotBuilderExtensions.cs
Normal file
26
src/Ocelot.Provider.Consul/OcelotBuilderExtensions.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
namespace Ocelot.Provider.Consul
|
||||||
|
{
|
||||||
|
using Configuration.Repository;
|
||||||
|
using DependencyInjection;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Middleware;
|
||||||
|
using ServiceDiscovery;
|
||||||
|
|
||||||
|
public static class OcelotBuilderExtensions
|
||||||
|
{
|
||||||
|
public static IOcelotBuilder AddConsul(this IOcelotBuilder builder)
|
||||||
|
{
|
||||||
|
builder.Services.AddSingleton<ServiceDiscoveryFinderDelegate>(ConsulProviderFactory.Get);
|
||||||
|
builder.Services.AddSingleton<IConsulClientFactory, ConsulClientFactory>();
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IOcelotBuilder AddConfigStoredInConsul(this IOcelotBuilder builder)
|
||||||
|
{
|
||||||
|
builder.Services.AddSingleton<OcelotMiddlewareConfigurationDelegate>(ConsulMiddlewareConfigurationProvider.Get);
|
||||||
|
builder.Services.AddHostedService<FileConfigurationPoller>();
|
||||||
|
builder.Services.AddSingleton<IFileConfigurationRepository, ConsulFileConfigurationRepository>();
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
namespace Ocelot.Provider.Consul
|
||||||
|
{
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Logging;
|
||||||
|
using ServiceDiscovery.Providers;
|
||||||
|
using Values;
|
||||||
|
|
||||||
|
public class PollConsul : IServiceDiscoveryProvider
|
||||||
|
{
|
||||||
|
private readonly IOcelotLogger _logger;
|
||||||
|
private readonly IServiceDiscoveryProvider _consulServiceDiscoveryProvider;
|
||||||
|
private readonly Timer _timer;
|
||||||
|
private bool _polling;
|
||||||
|
private List<Service> _services;
|
||||||
|
|
||||||
|
public PollConsul(int pollingInterval, IOcelotLoggerFactory factory, IServiceDiscoveryProvider consulServiceDiscoveryProvider)
|
||||||
|
{
|
||||||
|
_logger = factory.CreateLogger<PollConsul>();
|
||||||
|
_consulServiceDiscoveryProvider = consulServiceDiscoveryProvider;
|
||||||
|
_services = new List<Service>();
|
||||||
|
|
||||||
|
_timer = new Timer(async x =>
|
||||||
|
{
|
||||||
|
if (_polling)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_polling = true;
|
||||||
|
await Poll();
|
||||||
|
_polling = false;
|
||||||
|
}, null, pollingInterval, pollingInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<List<Service>> Get()
|
||||||
|
{
|
||||||
|
return Task.FromResult(_services);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Poll()
|
||||||
|
{
|
||||||
|
_services = await _consulServiceDiscoveryProvider.Get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
src/Ocelot.Provider.Consul/Properties/AssemblyInfo.cs
Normal file
18
src/Ocelot.Provider.Consul/Properties/AssemblyInfo.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
// General Information about an assembly is controlled through the following
|
||||||
|
// set of attributes. Change these attribute values to modify the information
|
||||||
|
// associated with an assembly.
|
||||||
|
[assembly: AssemblyConfiguration("")]
|
||||||
|
[assembly: AssemblyCompany("")]
|
||||||
|
[assembly: AssemblyProduct("Ocelot")]
|
||||||
|
[assembly: AssemblyTrademark("")]
|
||||||
|
|
||||||
|
// Setting ComVisible to false makes the types in this assembly not visible
|
||||||
|
// to COM components. If you need to access a type in this assembly from
|
||||||
|
// COM, set the ComVisible attribute to true on that type.
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
|
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||||
|
[assembly: Guid("d6df4206-0dba-41d8-884d-c3e08290fdbb")]
|
12
src/Ocelot.Provider.Consul/UnableToSetConfigInConsulError.cs
Normal file
12
src/Ocelot.Provider.Consul/UnableToSetConfigInConsulError.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
namespace Ocelot.Provider.Consul
|
||||||
|
{
|
||||||
|
using Errors;
|
||||||
|
|
||||||
|
public class UnableToSetConfigInConsulError : Error
|
||||||
|
{
|
||||||
|
public UnableToSetConfigInConsulError(string s)
|
||||||
|
: base(s, OcelotErrorCode.UnknownError)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
src/Ocelot.Provider.Eureka/Eureka.cs
Normal file
35
src/Ocelot.Provider.Eureka/Eureka.cs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
namespace Ocelot.Provider.Eureka
|
||||||
|
{
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using ServiceDiscovery.Providers;
|
||||||
|
using Steeltoe.Common.Discovery;
|
||||||
|
using Values;
|
||||||
|
|
||||||
|
public class Eureka : IServiceDiscoveryProvider
|
||||||
|
{
|
||||||
|
private readonly IDiscoveryClient _client;
|
||||||
|
private readonly string _serviceName;
|
||||||
|
|
||||||
|
public Eureka(string serviceName, IDiscoveryClient client)
|
||||||
|
{
|
||||||
|
_client = client;
|
||||||
|
_serviceName = serviceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<List<Service>> Get()
|
||||||
|
{
|
||||||
|
var services = new List<Service>();
|
||||||
|
|
||||||
|
var instances = _client.GetInstances(_serviceName);
|
||||||
|
|
||||||
|
if (instances != null && instances.Any())
|
||||||
|
{
|
||||||
|
services.AddRange(instances.Select(i => new Service(i.ServiceId, new ServiceHostAndPort(i.Host, i.Port), "", "", new List<string>())));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(services);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
namespace Ocelot.Provider.Eureka
|
||||||
|
{
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Configuration;
|
||||||
|
using Configuration.Repository;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Middleware;
|
||||||
|
using Pivotal.Discovery.Client;
|
||||||
|
|
||||||
|
public class EurekaMiddlewareConfigurationProvider
|
||||||
|
{
|
||||||
|
public static OcelotMiddlewareConfigurationDelegate Get = builder =>
|
||||||
|
{
|
||||||
|
var internalConfigRepo = builder.ApplicationServices.GetService<IInternalConfigurationRepository>();
|
||||||
|
|
||||||
|
var config = internalConfigRepo.Get();
|
||||||
|
|
||||||
|
if (UsingEurekaServiceDiscoveryProvider(config.Data))
|
||||||
|
{
|
||||||
|
builder.UseDiscoveryClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
private static bool UsingEurekaServiceDiscoveryProvider(IInternalConfiguration configuration)
|
||||||
|
{
|
||||||
|
return configuration?.ServiceProviderConfiguration != null && configuration.ServiceProviderConfiguration.Type?.ToLower() == "eureka";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
src/Ocelot.Provider.Eureka/EurekaProviderFactory.cs
Normal file
22
src/Ocelot.Provider.Eureka/EurekaProviderFactory.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
namespace Ocelot.Provider.Eureka
|
||||||
|
{
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using ServiceDiscovery;
|
||||||
|
using ServiceDiscovery.Providers;
|
||||||
|
using Steeltoe.Common.Discovery;
|
||||||
|
|
||||||
|
public static class EurekaProviderFactory
|
||||||
|
{
|
||||||
|
public static ServiceDiscoveryFinderDelegate Get = (provider, config, name) =>
|
||||||
|
{
|
||||||
|
var client = provider.GetService<IDiscoveryClient>();
|
||||||
|
|
||||||
|
if (config.Type?.ToLower() == "eureka" && client != null)
|
||||||
|
{
|
||||||
|
return new Eureka(name, client);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
37
src/Ocelot.Provider.Eureka/Ocelot.Provider.Eureka.csproj
Normal file
37
src/Ocelot.Provider.Eureka/Ocelot.Provider.Eureka.csproj
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
||||||
|
<NETStandardImplicitPackageVersion>2.0.0</NETStandardImplicitPackageVersion>
|
||||||
|
<NoPackageAnalysis>true</NoPackageAnalysis>
|
||||||
|
<Description>Provides Ocelot extensions to use Eureka</Description>
|
||||||
|
<AssemblyTitle>Ocelot.Provider.Eureka</AssemblyTitle>
|
||||||
|
<VersionPrefix>0.0.0-dev</VersionPrefix>
|
||||||
|
<AssemblyName>Ocelot.Provider.Eureka</AssemblyName>
|
||||||
|
<PackageId>Ocelot.Provider.Eureka</PackageId>
|
||||||
|
<PackageTags>API Gateway;.NET core</PackageTags>
|
||||||
|
<PackageProjectUrl>https://github.com/ThreeMammals/Ocelot.Provider.Eureka</PackageProjectUrl>
|
||||||
|
<PackageProjectUrl>https://github.com/ThreeMammals/Ocelot.Provider.Eureka</PackageProjectUrl>
|
||||||
|
<PackageIconUrl>http://threemammals.com/images/ocelot_logo.png</PackageIconUrl>
|
||||||
|
<RuntimeIdentifiers>win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64</RuntimeIdentifiers>
|
||||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
||||||
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
|
||||||
|
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||||
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
|
||||||
|
<Authors>Tom Pallister</Authors>
|
||||||
|
<CodeAnalysisRuleSet>..\..\codeanalysis.ruleset</CodeAnalysisRuleSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<DebugSymbols>True</DebugSymbols>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Ocelot\Ocelot.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Pivotal.Discovery.ClientCore" Version="2.0.1" />
|
||||||
|
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
23
src/Ocelot.Provider.Eureka/OcelotBuilderExtensions.cs
Normal file
23
src/Ocelot.Provider.Eureka/OcelotBuilderExtensions.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
namespace Ocelot.Provider.Eureka
|
||||||
|
{
|
||||||
|
using System.Linq;
|
||||||
|
using DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Middleware;
|
||||||
|
using Pivotal.Discovery.Client;
|
||||||
|
using ServiceDiscovery;
|
||||||
|
|
||||||
|
public static class OcelotBuilderExtensions
|
||||||
|
{
|
||||||
|
public static IOcelotBuilder AddEureka(this IOcelotBuilder builder)
|
||||||
|
{
|
||||||
|
var service = builder.Services.First(x => x.ServiceType == typeof(IConfiguration));
|
||||||
|
var configuration = (IConfiguration)service.ImplementationInstance;
|
||||||
|
builder.Services.AddDiscoveryClient(configuration);
|
||||||
|
builder.Services.AddSingleton<ServiceDiscoveryFinderDelegate>(EurekaProviderFactory.Get);
|
||||||
|
builder.Services.AddSingleton<OcelotMiddlewareConfigurationDelegate>(EurekaMiddlewareConfigurationProvider.Get);
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
src/Ocelot.Provider.Eureka/Properties/AssemblyInfo.cs
Normal file
18
src/Ocelot.Provider.Eureka/Properties/AssemblyInfo.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
// General Information about an assembly is controlled through the following
|
||||||
|
// set of attributes. Change these attribute values to modify the information
|
||||||
|
// associated with an assembly.
|
||||||
|
[assembly: AssemblyConfiguration("")]
|
||||||
|
[assembly: AssemblyCompany("")]
|
||||||
|
[assembly: AssemblyProduct("Ocelot")]
|
||||||
|
[assembly: AssemblyTrademark("")]
|
||||||
|
|
||||||
|
// Setting ComVisible to false makes the types in this assembly not visible
|
||||||
|
// to COM components. If you need to access a type in this assembly from
|
||||||
|
// COM, set the ComVisible attribute to true on that type.
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
|
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||||
|
[assembly: Guid("d6df4206-0dba-41d8-884d-c3e08290fdbb")]
|
17
src/Ocelot.Provider.Polly/CircuitBreaker.cs
Normal file
17
src/Ocelot.Provider.Polly/CircuitBreaker.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
using Polly.CircuitBreaker;
|
||||||
|
using Polly.Timeout;
|
||||||
|
|
||||||
|
namespace Ocelot.Provider.Polly
|
||||||
|
{
|
||||||
|
public class CircuitBreaker
|
||||||
|
{
|
||||||
|
public CircuitBreaker(CircuitBreakerPolicy circuitBreakerPolicy, TimeoutPolicy timeoutPolicy)
|
||||||
|
{
|
||||||
|
CircuitBreakerPolicy = circuitBreakerPolicy;
|
||||||
|
TimeoutPolicy = timeoutPolicy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CircuitBreakerPolicy CircuitBreakerPolicy { get; private set; }
|
||||||
|
public TimeoutPolicy TimeoutPolicy { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
37
src/Ocelot.Provider.Polly/Ocelot.Provider.Polly.csproj
Normal file
37
src/Ocelot.Provider.Polly/Ocelot.Provider.Polly.csproj
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
||||||
|
<NETStandardImplicitPackageVersion>2.0.0</NETStandardImplicitPackageVersion>
|
||||||
|
<NoPackageAnalysis>true</NoPackageAnalysis>
|
||||||
|
<Description>Provides Ocelot extensions to use Polly.NET</Description>
|
||||||
|
<AssemblyTitle>Ocelot.Provider.Polly</AssemblyTitle>
|
||||||
|
<VersionPrefix>0.0.0-dev</VersionPrefix>
|
||||||
|
<AssemblyName>Ocelot.Provider.Polly</AssemblyName>
|
||||||
|
<PackageId>Ocelot.Provider.Polly</PackageId>
|
||||||
|
<PackageTags>API Gateway;.NET core</PackageTags>
|
||||||
|
<PackageProjectUrl>https://github.com/ThreeMammals/Ocelot.Provider.Polly</PackageProjectUrl>
|
||||||
|
<PackageProjectUrl>https://github.com/ThreeMammals/Ocelot.Provider.Polly</PackageProjectUrl>
|
||||||
|
<PackageIconUrl>http://threemammals.com/images/ocelot_logo.png</PackageIconUrl>
|
||||||
|
<RuntimeIdentifiers>win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64</RuntimeIdentifiers>
|
||||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
||||||
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
|
||||||
|
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||||
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
|
||||||
|
<Authors>Tom Pallister</Authors>
|
||||||
|
<CodeAnalysisRuleSet>..\..\codeanalysis.ruleset</CodeAnalysisRuleSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<DebugSymbols>True</DebugSymbols>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Ocelot\Ocelot.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Polly" Version="6.0.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
38
src/Ocelot.Provider.Polly/OcelotBuilderExtensions.cs
Normal file
38
src/Ocelot.Provider.Polly/OcelotBuilderExtensions.cs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
namespace Ocelot.Provider.Polly
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Configuration;
|
||||||
|
using DependencyInjection;
|
||||||
|
using Errors;
|
||||||
|
using global::Polly.CircuitBreaker;
|
||||||
|
using global::Polly.Timeout;
|
||||||
|
using Logging;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Requester;
|
||||||
|
|
||||||
|
public static class OcelotBuilderExtensions
|
||||||
|
{
|
||||||
|
public static IOcelotBuilder AddPolly(this IOcelotBuilder builder)
|
||||||
|
{
|
||||||
|
var errorMapping = new Dictionary<Type, Func<Exception, Error>>
|
||||||
|
{
|
||||||
|
{typeof(TaskCanceledException), e => new RequestTimedOutError(e)},
|
||||||
|
{typeof(TimeoutRejectedException), e => new RequestTimedOutError(e)},
|
||||||
|
{typeof(BrokenCircuitException), e => new RequestTimedOutError(e)}
|
||||||
|
};
|
||||||
|
|
||||||
|
builder.Services.AddSingleton(errorMapping);
|
||||||
|
|
||||||
|
DelegatingHandler QosDelegatingHandlerDelegate(DownstreamReRoute reRoute, IOcelotLoggerFactory logger)
|
||||||
|
{
|
||||||
|
return new PollyCircuitBreakingDelegatingHandler(new PollyQoSProvider(reRoute, logger), logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.Services.AddSingleton((QosDelegatingHandlerDelegate) QosDelegatingHandlerDelegate);
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ocelot.Logging;
|
||||||
|
using Polly;
|
||||||
|
using Polly.CircuitBreaker;
|
||||||
|
|
||||||
|
namespace Ocelot.Provider.Polly
|
||||||
|
{
|
||||||
|
public class PollyCircuitBreakingDelegatingHandler : DelegatingHandler
|
||||||
|
{
|
||||||
|
private readonly PollyQoSProvider _qoSProvider;
|
||||||
|
private readonly IOcelotLogger _logger;
|
||||||
|
|
||||||
|
public PollyCircuitBreakingDelegatingHandler(
|
||||||
|
PollyQoSProvider qoSProvider,
|
||||||
|
IOcelotLoggerFactory loggerFactory)
|
||||||
|
{
|
||||||
|
_qoSProvider = qoSProvider;
|
||||||
|
_logger = loggerFactory.CreateLogger<PollyCircuitBreakingDelegatingHandler>();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await Policy
|
||||||
|
.WrapAsync(_qoSProvider.CircuitBreaker.CircuitBreakerPolicy, _qoSProvider.CircuitBreaker.TimeoutPolicy)
|
||||||
|
.ExecuteAsync(() => base.SendAsync(request,cancellationToken));
|
||||||
|
}
|
||||||
|
catch (BrokenCircuitException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError($"Reached to allowed number of exceptions. Circuit is open",ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (HttpRequestException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError($"Error in CircuitBreakingDelegatingHandler.SendAync", ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
52
src/Ocelot.Provider.Polly/PollyQoSProvider.cs
Normal file
52
src/Ocelot.Provider.Polly/PollyQoSProvider.cs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
namespace Ocelot.Provider.Polly
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Net.Http;
|
||||||
|
using global::Polly;
|
||||||
|
using global::Polly.CircuitBreaker;
|
||||||
|
using global::Polly.Timeout;
|
||||||
|
using Ocelot.Configuration;
|
||||||
|
using Ocelot.Logging;
|
||||||
|
|
||||||
|
public class PollyQoSProvider
|
||||||
|
{
|
||||||
|
private readonly CircuitBreakerPolicy _circuitBreakerPolicy;
|
||||||
|
private readonly TimeoutPolicy _timeoutPolicy;
|
||||||
|
private readonly IOcelotLogger _logger;
|
||||||
|
|
||||||
|
public PollyQoSProvider(DownstreamReRoute reRoute, IOcelotLoggerFactory loggerFactory)
|
||||||
|
{
|
||||||
|
_logger = loggerFactory.CreateLogger<PollyQoSProvider>();
|
||||||
|
|
||||||
|
Enum.TryParse(reRoute.QosOptions.TimeoutStrategy, out TimeoutStrategy strategy);
|
||||||
|
|
||||||
|
_timeoutPolicy = Policy.TimeoutAsync(TimeSpan.FromMilliseconds(reRoute.QosOptions.TimeoutValue), strategy);
|
||||||
|
|
||||||
|
_circuitBreakerPolicy = Policy
|
||||||
|
.Handle<HttpRequestException>()
|
||||||
|
.Or<TimeoutRejectedException>()
|
||||||
|
.Or<TimeoutException>()
|
||||||
|
.CircuitBreakerAsync(
|
||||||
|
exceptionsAllowedBeforeBreaking: reRoute.QosOptions.ExceptionsAllowedBeforeBreaking,
|
||||||
|
durationOfBreak: TimeSpan.FromMilliseconds(reRoute.QosOptions.DurationOfBreak),
|
||||||
|
onBreak: (ex, breakDelay) =>
|
||||||
|
{
|
||||||
|
_logger.LogError(
|
||||||
|
".Breaker logging: Breaking the circuit for " + breakDelay.TotalMilliseconds + "ms!", ex);
|
||||||
|
},
|
||||||
|
onReset: () =>
|
||||||
|
{
|
||||||
|
_logger.LogDebug(".Breaker logging: Call ok! Closed the circuit again.");
|
||||||
|
},
|
||||||
|
onHalfOpen: () =>
|
||||||
|
{
|
||||||
|
_logger.LogDebug(".Breaker logging: Half-open; next call is a trial.");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
CircuitBreaker = new CircuitBreaker(_circuitBreakerPolicy, _timeoutPolicy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CircuitBreaker CircuitBreaker { get; }
|
||||||
|
}
|
||||||
|
}
|
18
src/Ocelot.Provider.Polly/Properties/AssemblyInfo.cs
Normal file
18
src/Ocelot.Provider.Polly/Properties/AssemblyInfo.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
// General Information about an assembly is controlled through the following
|
||||||
|
// set of attributes. Change these attribute values to modify the information
|
||||||
|
// associated with an assembly.
|
||||||
|
[assembly: AssemblyConfiguration("")]
|
||||||
|
[assembly: AssemblyCompany("")]
|
||||||
|
[assembly: AssemblyProduct("Ocelot")]
|
||||||
|
[assembly: AssemblyTrademark("")]
|
||||||
|
|
||||||
|
// Setting ComVisible to false makes the types in this assembly not visible
|
||||||
|
// to COM components. If you need to access a type in this assembly from
|
||||||
|
// COM, set the ComVisible attribute to true on that type.
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
|
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||||
|
[assembly: Guid("d6df4206-0dba-41d8-884d-c3e08290fdbb")]
|
13
src/Ocelot.Provider.Polly/RequestTimedOutError.cs
Normal file
13
src/Ocelot.Provider.Polly/RequestTimedOutError.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
namespace Ocelot.Provider.Polly
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using Errors;
|
||||||
|
|
||||||
|
public class RequestTimedOutError : Error
|
||||||
|
{
|
||||||
|
public RequestTimedOutError(Exception exception)
|
||||||
|
: base($"Timeout making http request, exception: {exception}", OcelotErrorCode.RequestTimedOutError)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
src/Ocelot.Provider.Rafty/BearerToken.cs
Normal file
16
src/Ocelot.Provider.Rafty/BearerToken.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
namespace Ocelot.Provider.Rafty
|
||||||
|
{
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
internal class BearerToken
|
||||||
|
{
|
||||||
|
[JsonProperty("access_token")]
|
||||||
|
public string AccessToken { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("expires_in")]
|
||||||
|
public int ExpiresIn { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("token_type")]
|
||||||
|
public string TokenType { get; set; }
|
||||||
|
}
|
||||||
|
}
|
14
src/Ocelot.Provider.Rafty/FakeCommand.cs
Normal file
14
src/Ocelot.Provider.Rafty/FakeCommand.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
namespace Ocelot.Provider.Rafty
|
||||||
|
{
|
||||||
|
using global::Rafty.FiniteStateMachine;
|
||||||
|
|
||||||
|
public class FakeCommand : ICommand
|
||||||
|
{
|
||||||
|
public FakeCommand(string value)
|
||||||
|
{
|
||||||
|
this.Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Value { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
7
src/Ocelot.Provider.Rafty/FilePeer.cs
Normal file
7
src/Ocelot.Provider.Rafty/FilePeer.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
namespace Ocelot.Provider.Rafty
|
||||||
|
{
|
||||||
|
public class FilePeer
|
||||||
|
{
|
||||||
|
public string HostAndPort { get; set; }
|
||||||
|
}
|
||||||
|
}
|
14
src/Ocelot.Provider.Rafty/FilePeers.cs
Normal file
14
src/Ocelot.Provider.Rafty/FilePeers.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
namespace Ocelot.Provider.Rafty
|
||||||
|
{
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
public class FilePeers
|
||||||
|
{
|
||||||
|
public FilePeers()
|
||||||
|
{
|
||||||
|
Peers = new List<FilePeer>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<FilePeer> Peers { get; set; }
|
||||||
|
}
|
||||||
|
}
|
45
src/Ocelot.Provider.Rafty/FilePeersProvider.cs
Normal file
45
src/Ocelot.Provider.Rafty/FilePeersProvider.cs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
namespace Ocelot.Provider.Rafty
|
||||||
|
{
|
||||||
|
using System.Net.Http;
|
||||||
|
using Configuration;
|
||||||
|
using Configuration.Repository;
|
||||||
|
using global::Rafty.Concensus.Peers;
|
||||||
|
using global::Rafty.Infrastructure;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Middleware;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Administration;
|
||||||
|
|
||||||
|
public class FilePeersProvider : IPeersProvider
|
||||||
|
{
|
||||||
|
private readonly IOptions<FilePeers> _options;
|
||||||
|
private readonly List<IPeer> _peers;
|
||||||
|
private IBaseUrlFinder _finder;
|
||||||
|
private IInternalConfigurationRepository _repo;
|
||||||
|
private IIdentityServerConfiguration _identityServerConfig;
|
||||||
|
|
||||||
|
public FilePeersProvider(IOptions<FilePeers> options, IBaseUrlFinder finder, IInternalConfigurationRepository repo, IIdentityServerConfiguration identityServerConfig)
|
||||||
|
{
|
||||||
|
_identityServerConfig = identityServerConfig;
|
||||||
|
_repo = repo;
|
||||||
|
_finder = finder;
|
||||||
|
_options = options;
|
||||||
|
_peers = new List<IPeer>();
|
||||||
|
|
||||||
|
var config = _repo.Get();
|
||||||
|
foreach (var item in _options.Value.Peers)
|
||||||
|
{
|
||||||
|
var httpClient = new HttpClient();
|
||||||
|
|
||||||
|
//todo what if this errors?
|
||||||
|
var httpPeer = new HttpPeer(item.HostAndPort, httpClient, _finder, config.Data, _identityServerConfig);
|
||||||
|
_peers.Add(httpPeer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<IPeer> Get()
|
||||||
|
{
|
||||||
|
return _peers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
130
src/Ocelot.Provider.Rafty/HttpPeer.cs
Normal file
130
src/Ocelot.Provider.Rafty/HttpPeer.cs
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
namespace Ocelot.Provider.Rafty
|
||||||
|
{
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Configuration;
|
||||||
|
using global::Rafty.Concensus.Messages;
|
||||||
|
using global::Rafty.Concensus.Peers;
|
||||||
|
using global::Rafty.FiniteStateMachine;
|
||||||
|
using global::Rafty.Infrastructure;
|
||||||
|
using Middleware;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Administration;
|
||||||
|
|
||||||
|
public class HttpPeer : IPeer
|
||||||
|
{
|
||||||
|
private readonly string _hostAndPort;
|
||||||
|
private readonly HttpClient _httpClient;
|
||||||
|
private readonly JsonSerializerSettings _jsonSerializerSettings;
|
||||||
|
private readonly string _baseSchemeUrlAndPort;
|
||||||
|
private BearerToken _token;
|
||||||
|
private readonly IInternalConfiguration _config;
|
||||||
|
private readonly IIdentityServerConfiguration _identityServerConfiguration;
|
||||||
|
|
||||||
|
public HttpPeer(string hostAndPort, HttpClient httpClient, IBaseUrlFinder finder, IInternalConfiguration config, IIdentityServerConfiguration identityServerConfiguration)
|
||||||
|
{
|
||||||
|
_identityServerConfiguration = identityServerConfiguration;
|
||||||
|
_config = config;
|
||||||
|
Id = hostAndPort;
|
||||||
|
_hostAndPort = hostAndPort;
|
||||||
|
_httpClient = httpClient;
|
||||||
|
_jsonSerializerSettings = new JsonSerializerSettings()
|
||||||
|
{
|
||||||
|
TypeNameHandling = TypeNameHandling.All
|
||||||
|
};
|
||||||
|
_baseSchemeUrlAndPort = finder.Find();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Id { get; }
|
||||||
|
|
||||||
|
public async Task<RequestVoteResponse> Request(RequestVote requestVote)
|
||||||
|
{
|
||||||
|
if (_token == null)
|
||||||
|
{
|
||||||
|
await SetToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
var json = JsonConvert.SerializeObject(requestVote, _jsonSerializerSettings);
|
||||||
|
var content = new StringContent(json);
|
||||||
|
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
|
||||||
|
var response = await _httpClient.PostAsync($"{_hostAndPort}/administration/raft/requestvote", content);
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
return JsonConvert.DeserializeObject<RequestVoteResponse>(await response.Content.ReadAsStringAsync(), _jsonSerializerSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RequestVoteResponse(false, requestVote.Term);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AppendEntriesResponse> Request(AppendEntries appendEntries)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_token == null)
|
||||||
|
{
|
||||||
|
await SetToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
var json = JsonConvert.SerializeObject(appendEntries, _jsonSerializerSettings);
|
||||||
|
var content = new StringContent(json);
|
||||||
|
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
|
||||||
|
var response = await _httpClient.PostAsync($"{_hostAndPort}/administration/raft/appendEntries", content);
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
return JsonConvert.DeserializeObject<AppendEntriesResponse>(await response.Content.ReadAsStringAsync(), _jsonSerializerSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AppendEntriesResponse(appendEntries.Term, false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine(ex);
|
||||||
|
return new AppendEntriesResponse(appendEntries.Term, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Response<T>> Request<T>(T command)
|
||||||
|
where T : ICommand
|
||||||
|
{
|
||||||
|
Console.WriteLine("SENDING REQUEST....");
|
||||||
|
if (_token == null)
|
||||||
|
{
|
||||||
|
await SetToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
var json = JsonConvert.SerializeObject(command, _jsonSerializerSettings);
|
||||||
|
var content = new StringContent(json);
|
||||||
|
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
|
||||||
|
var response = await _httpClient.PostAsync($"{_hostAndPort}/administration/raft/command", content);
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
Console.WriteLine("REQUEST OK....");
|
||||||
|
var okResponse = JsonConvert.DeserializeObject<OkResponse<ICommand>>(await response.Content.ReadAsStringAsync(), _jsonSerializerSettings);
|
||||||
|
return new OkResponse<T>((T)okResponse.Command);
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("REQUEST NOT OK....");
|
||||||
|
return new ErrorResponse<T>(await response.Content.ReadAsStringAsync(), command);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SetToken()
|
||||||
|
{
|
||||||
|
var tokenUrl = $"{_baseSchemeUrlAndPort}{_config.AdministrationPath}/connect/token";
|
||||||
|
var formData = new List<KeyValuePair<string, string>>
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>("client_id", _identityServerConfiguration.ApiName),
|
||||||
|
new KeyValuePair<string, string>("client_secret", _identityServerConfiguration.ApiSecret),
|
||||||
|
new KeyValuePair<string, string>("scope", _identityServerConfiguration.ApiName),
|
||||||
|
new KeyValuePair<string, string>("grant_type", "client_credentials")
|
||||||
|
};
|
||||||
|
var content = new FormUrlEncodedContent(formData);
|
||||||
|
var response = await _httpClient.PostAsync(tokenUrl, content);
|
||||||
|
var responseContent = await response.Content.ReadAsStringAsync();
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
_token = JsonConvert.DeserializeObject<BearerToken>(responseContent);
|
||||||
|
_httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(_token.TokenType, _token.AccessToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
39
src/Ocelot.Provider.Rafty/Ocelot.Provider.Rafty.csproj
Normal file
39
src/Ocelot.Provider.Rafty/Ocelot.Provider.Rafty.csproj
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
||||||
|
<NETStandardImplicitPackageVersion>2.0.0</NETStandardImplicitPackageVersion>
|
||||||
|
<NoPackageAnalysis>true</NoPackageAnalysis>
|
||||||
|
<Description>Provides Ocelot extensions to use Rafty</Description>
|
||||||
|
<AssemblyTitle>Ocelot.Provider.Rafty</AssemblyTitle>
|
||||||
|
<VersionPrefix>0.0.0-dev</VersionPrefix>
|
||||||
|
<AssemblyName>Ocelot.Provider.Rafty</AssemblyName>
|
||||||
|
<PackageId>Ocelot.Provider.Rafty</PackageId>
|
||||||
|
<PackageTags>API Gateway;.NET core</PackageTags>
|
||||||
|
<PackageProjectUrl>https://github.com/ThreeMammals/Ocelot.Provider.Rafty</PackageProjectUrl>
|
||||||
|
<PackageProjectUrl>https://github.com/ThreeMammals/Ocelot.Provider.Rafty</PackageProjectUrl>
|
||||||
|
<PackageIconUrl>http://threemammals.com/images/ocelot_logo.png</PackageIconUrl>
|
||||||
|
<RuntimeIdentifiers>win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64</RuntimeIdentifiers>
|
||||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
||||||
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
|
||||||
|
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||||
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
|
||||||
|
<Authors>Tom Pallister</Authors>
|
||||||
|
<CodeAnalysisRuleSet>..\..\codeanalysis.ruleset</CodeAnalysisRuleSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<DebugSymbols>True</DebugSymbols>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Ocelot\Ocelot.csproj" />
|
||||||
|
<ProjectReference Include="..\Ocelot.Administration\Ocelot.Administration.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Data.SQLite" Version="2.2.0"/>
|
||||||
|
<PackageReference Include="Rafty" Version="0.4.4"/>
|
||||||
|
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
@ -0,0 +1,28 @@
|
|||||||
|
namespace Ocelot.Provider.Rafty
|
||||||
|
{
|
||||||
|
using Configuration.Setter;
|
||||||
|
using DependencyInjection;
|
||||||
|
using global::Rafty.Concensus.Node;
|
||||||
|
using global::Rafty.FiniteStateMachine;
|
||||||
|
using global::Rafty.Infrastructure;
|
||||||
|
using global::Rafty.Log;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
|
|
||||||
|
public static class OcelotAdministrationBuilderExtensions
|
||||||
|
{
|
||||||
|
public static IOcelotAdministrationBuilder AddRafty(this IOcelotAdministrationBuilder builder)
|
||||||
|
{
|
||||||
|
var settings = new InMemorySettings(4000, 6000, 100, 10000);
|
||||||
|
builder.Services.RemoveAll<IFileConfigurationSetter>();
|
||||||
|
builder.Services.AddSingleton<IFileConfigurationSetter, RaftyFileConfigurationSetter>();
|
||||||
|
builder.Services.AddSingleton<ILog, SqlLiteLog>();
|
||||||
|
builder.Services.AddSingleton<IFiniteStateMachine, OcelotFiniteStateMachine>();
|
||||||
|
builder.Services.AddSingleton<ISettings>(settings);
|
||||||
|
builder.Services.AddSingleton<IPeersProvider, FilePeersProvider>();
|
||||||
|
builder.Services.AddSingleton<INode, Node>();
|
||||||
|
builder.Services.Configure<FilePeers>(builder.ConfigurationRoot);
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
src/Ocelot.Provider.Rafty/OcelotFiniteStateMachine.cs
Normal file
25
src/Ocelot.Provider.Rafty/OcelotFiniteStateMachine.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
namespace Ocelot.Provider.Rafty
|
||||||
|
{
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Configuration.Setter;
|
||||||
|
using global::Rafty.FiniteStateMachine;
|
||||||
|
using global::Rafty.Log;
|
||||||
|
|
||||||
|
public class OcelotFiniteStateMachine : IFiniteStateMachine
|
||||||
|
{
|
||||||
|
private readonly IFileConfigurationSetter _setter;
|
||||||
|
|
||||||
|
public OcelotFiniteStateMachine(IFileConfigurationSetter setter)
|
||||||
|
{
|
||||||
|
_setter = setter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Handle(LogEntry log)
|
||||||
|
{
|
||||||
|
//todo - handle an error
|
||||||
|
//hack it to just cast as at the moment we know this is the only command :P
|
||||||
|
var hack = (UpdateFileConfiguration)log.CommandData;
|
||||||
|
await _setter.Set(hack.Configuration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
src/Ocelot.Provider.Rafty/Properties/AssemblyInfo.cs
Normal file
18
src/Ocelot.Provider.Rafty/Properties/AssemblyInfo.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
// General Information about an assembly is controlled through the following
|
||||||
|
// set of attributes. Change these attribute values to modify the information
|
||||||
|
// associated with an assembly.
|
||||||
|
[assembly: AssemblyConfiguration("")]
|
||||||
|
[assembly: AssemblyCompany("")]
|
||||||
|
[assembly: AssemblyProduct("Ocelot")]
|
||||||
|
[assembly: AssemblyTrademark("")]
|
||||||
|
|
||||||
|
// Setting ComVisible to false makes the types in this assembly not visible
|
||||||
|
// to COM components. If you need to access a type in this assembly from
|
||||||
|
// COM, set the ComVisible attribute to true on that type.
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
|
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||||
|
[assembly: Guid("d6df4206-0dba-41d8-884d-c3e08290fdbb")]
|
96
src/Ocelot.Provider.Rafty/RaftController.cs
Normal file
96
src/Ocelot.Provider.Rafty/RaftController.cs
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
namespace Ocelot.Provider.Rafty
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using global::Rafty.Concensus.Messages;
|
||||||
|
using global::Rafty.Concensus.Node;
|
||||||
|
using global::Rafty.FiniteStateMachine;
|
||||||
|
using Logging;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Middleware;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
[Authorize]
|
||||||
|
[Route("raft")]
|
||||||
|
public class RaftController : Controller
|
||||||
|
{
|
||||||
|
private readonly INode _node;
|
||||||
|
private readonly IOcelotLogger _logger;
|
||||||
|
private readonly string _baseSchemeUrlAndPort;
|
||||||
|
private readonly JsonSerializerSettings _jsonSerialiserSettings;
|
||||||
|
|
||||||
|
public RaftController(INode node, IOcelotLoggerFactory loggerFactory, IBaseUrlFinder finder)
|
||||||
|
{
|
||||||
|
_jsonSerialiserSettings = new JsonSerializerSettings
|
||||||
|
{
|
||||||
|
TypeNameHandling = TypeNameHandling.All
|
||||||
|
};
|
||||||
|
_baseSchemeUrlAndPort = finder.Find();
|
||||||
|
_logger = loggerFactory.CreateLogger<RaftController>();
|
||||||
|
_node = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("appendentries")]
|
||||||
|
public async Task<IActionResult> AppendEntries()
|
||||||
|
{
|
||||||
|
using (var reader = new StreamReader(HttpContext.Request.Body))
|
||||||
|
{
|
||||||
|
var json = await reader.ReadToEndAsync();
|
||||||
|
|
||||||
|
var appendEntries = JsonConvert.DeserializeObject<AppendEntries>(json, _jsonSerialiserSettings);
|
||||||
|
|
||||||
|
_logger.LogDebug($"{_baseSchemeUrlAndPort}/appendentries called, my state is {_node.State.GetType().FullName}");
|
||||||
|
|
||||||
|
var appendEntriesResponse = await _node.Handle(appendEntries);
|
||||||
|
|
||||||
|
return new OkObjectResult(appendEntriesResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("requestvote")]
|
||||||
|
public async Task<IActionResult> RequestVote()
|
||||||
|
{
|
||||||
|
using (var reader = new StreamReader(HttpContext.Request.Body))
|
||||||
|
{
|
||||||
|
var json = await reader.ReadToEndAsync();
|
||||||
|
|
||||||
|
var requestVote = JsonConvert.DeserializeObject<RequestVote>(json, _jsonSerialiserSettings);
|
||||||
|
|
||||||
|
_logger.LogDebug($"{_baseSchemeUrlAndPort}/requestvote called, my state is {_node.State.GetType().FullName}");
|
||||||
|
|
||||||
|
var requestVoteResponse = await _node.Handle(requestVote);
|
||||||
|
|
||||||
|
return new OkObjectResult(requestVoteResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("command")]
|
||||||
|
public async Task<IActionResult> Command()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var reader = new StreamReader(HttpContext.Request.Body))
|
||||||
|
{
|
||||||
|
var json = await reader.ReadToEndAsync();
|
||||||
|
|
||||||
|
var command = JsonConvert.DeserializeObject<ICommand>(json, _jsonSerialiserSettings);
|
||||||
|
|
||||||
|
_logger.LogDebug($"{_baseSchemeUrlAndPort}/command called, my state is {_node.State.GetType().FullName}");
|
||||||
|
|
||||||
|
var commandResponse = await _node.Accept(command);
|
||||||
|
|
||||||
|
json = JsonConvert.SerializeObject(commandResponse, _jsonSerialiserSettings);
|
||||||
|
|
||||||
|
return StatusCode(200, json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError($"THERE WAS A PROBLEM ON NODE {_node.State.CurrentState.Id}", e);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
src/Ocelot.Provider.Rafty/RaftyFileConfigurationSetter.cs
Normal file
30
src/Ocelot.Provider.Rafty/RaftyFileConfigurationSetter.cs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
namespace Ocelot.Provider.Rafty
|
||||||
|
{
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Configuration.File;
|
||||||
|
using Configuration.Setter;
|
||||||
|
using global::Rafty.Concensus.Node;
|
||||||
|
using global::Rafty.Infrastructure;
|
||||||
|
|
||||||
|
public class RaftyFileConfigurationSetter : IFileConfigurationSetter
|
||||||
|
{
|
||||||
|
private readonly INode _node;
|
||||||
|
|
||||||
|
public RaftyFileConfigurationSetter(INode node)
|
||||||
|
{
|
||||||
|
_node = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Responses.Response> Set(FileConfiguration fileConfiguration)
|
||||||
|
{
|
||||||
|
var result = await _node.Accept(new UpdateFileConfiguration(fileConfiguration));
|
||||||
|
|
||||||
|
if (result.GetType() == typeof(ErrorResponse<UpdateFileConfiguration>))
|
||||||
|
{
|
||||||
|
return new Responses.ErrorResponse(new UnableToSaveAcceptCommand($"unable to save file configuration to state machine"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Responses.OkResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
namespace Ocelot.Provider.Rafty
|
||||||
|
{
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using global::Rafty.Concensus.Node;
|
||||||
|
using global::Rafty.Infrastructure;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Middleware;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
|
||||||
|
public static class RaftyMiddlewareConfigurationProvider
|
||||||
|
{
|
||||||
|
public static OcelotMiddlewareConfigurationDelegate Get = builder =>
|
||||||
|
{
|
||||||
|
if (UsingRafty(builder))
|
||||||
|
{
|
||||||
|
SetUpRafty(builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
private static bool UsingRafty(IApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
var node = builder.ApplicationServices.GetService<INode>();
|
||||||
|
if (node != null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetUpRafty(IApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
var applicationLifetime = builder.ApplicationServices.GetService<IApplicationLifetime>();
|
||||||
|
applicationLifetime.ApplicationStopping.Register(() => OnShutdown(builder));
|
||||||
|
var node = builder.ApplicationServices.GetService<INode>();
|
||||||
|
var nodeId = builder.ApplicationServices.GetService<NodeId>();
|
||||||
|
node.Start(nodeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnShutdown(IApplicationBuilder app)
|
||||||
|
{
|
||||||
|
var node = app.ApplicationServices.GetService<INode>();
|
||||||
|
node.Stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
334
src/Ocelot.Provider.Rafty/SqlLiteLog.cs
Normal file
334
src/Ocelot.Provider.Rafty/SqlLiteLog.cs
Normal file
@ -0,0 +1,334 @@
|
|||||||
|
namespace Ocelot.Provider.Rafty
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using global::Rafty.Infrastructure;
|
||||||
|
using global::Rafty.Log;
|
||||||
|
using Microsoft.Data.Sqlite;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
public class SqlLiteLog : ILog
|
||||||
|
{
|
||||||
|
private readonly string _path;
|
||||||
|
private readonly SemaphoreSlim _sempaphore = new SemaphoreSlim(1, 1);
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private readonly NodeId _nodeId;
|
||||||
|
|
||||||
|
public SqlLiteLog(NodeId nodeId, ILoggerFactory loggerFactory)
|
||||||
|
{
|
||||||
|
_logger = loggerFactory.CreateLogger<SqlLiteLog>();
|
||||||
|
_nodeId = nodeId;
|
||||||
|
_path = $"{nodeId.Id.Replace("/", "").Replace(":", "")}.db";
|
||||||
|
_sempaphore.Wait();
|
||||||
|
|
||||||
|
if (!File.Exists(_path))
|
||||||
|
{
|
||||||
|
var fs = File.Create(_path);
|
||||||
|
|
||||||
|
fs.Dispose();
|
||||||
|
|
||||||
|
using (var connection = new SqliteConnection($"Data Source={_path};"))
|
||||||
|
{
|
||||||
|
connection.Open();
|
||||||
|
|
||||||
|
const string sql = @"create table logs (
|
||||||
|
id integer primary key,
|
||||||
|
data text not null
|
||||||
|
)";
|
||||||
|
|
||||||
|
using (var command = new SqliteCommand(sql, connection))
|
||||||
|
{
|
||||||
|
var result = command.ExecuteNonQuery();
|
||||||
|
|
||||||
|
_logger.LogInformation(result == 0
|
||||||
|
? $"id: {_nodeId.Id} create database, result: {result}"
|
||||||
|
: $"id: {_nodeId.Id} did not create database., result: {result}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_sempaphore.Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> LastLogIndex()
|
||||||
|
{
|
||||||
|
_sempaphore.Wait();
|
||||||
|
var result = 1;
|
||||||
|
using (var connection = new SqliteConnection($"Data Source={_path};"))
|
||||||
|
{
|
||||||
|
connection.Open();
|
||||||
|
var sql = @"select id from logs order by id desc limit 1";
|
||||||
|
using (var command = new SqliteCommand(sql, connection))
|
||||||
|
{
|
||||||
|
var index = Convert.ToInt32(await command.ExecuteScalarAsync());
|
||||||
|
if (index > result)
|
||||||
|
{
|
||||||
|
result = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_sempaphore.Release();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<long> LastLogTerm()
|
||||||
|
{
|
||||||
|
_sempaphore.Wait();
|
||||||
|
long result = 0;
|
||||||
|
using (var connection = new SqliteConnection($"Data Source={_path};"))
|
||||||
|
{
|
||||||
|
connection.Open();
|
||||||
|
var sql = @"select data from logs order by id desc limit 1";
|
||||||
|
using (var command = new SqliteCommand(sql, connection))
|
||||||
|
{
|
||||||
|
var data = Convert.ToString(await command.ExecuteScalarAsync());
|
||||||
|
var jsonSerializerSettings = new JsonSerializerSettings()
|
||||||
|
{
|
||||||
|
TypeNameHandling = TypeNameHandling.All
|
||||||
|
};
|
||||||
|
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
|
||||||
|
if (log != null && log.Term > result)
|
||||||
|
{
|
||||||
|
result = log.Term;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_sempaphore.Release();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> Count()
|
||||||
|
{
|
||||||
|
_sempaphore.Wait();
|
||||||
|
var result = 0;
|
||||||
|
using (var connection = new SqliteConnection($"Data Source={_path};"))
|
||||||
|
{
|
||||||
|
connection.Open();
|
||||||
|
var sql = @"select count(id) from logs";
|
||||||
|
using (var command = new SqliteCommand(sql, connection))
|
||||||
|
{
|
||||||
|
var index = Convert.ToInt32(await command.ExecuteScalarAsync());
|
||||||
|
if (index > result)
|
||||||
|
{
|
||||||
|
result = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_sempaphore.Release();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> Apply(LogEntry log)
|
||||||
|
{
|
||||||
|
_sempaphore.Wait();
|
||||||
|
using (var connection = new SqliteConnection($"Data Source={_path};"))
|
||||||
|
{
|
||||||
|
connection.Open();
|
||||||
|
var jsonSerializerSettings = new JsonSerializerSettings()
|
||||||
|
{
|
||||||
|
TypeNameHandling = TypeNameHandling.All
|
||||||
|
};
|
||||||
|
var data = JsonConvert.SerializeObject(log, jsonSerializerSettings);
|
||||||
|
|
||||||
|
//todo - sql injection dont copy this..
|
||||||
|
var sql = $"insert into logs (data) values ('{data}')";
|
||||||
|
_logger.LogInformation($"id: {_nodeId.Id}, sql: {sql}");
|
||||||
|
using (var command = new SqliteCommand(sql, connection))
|
||||||
|
{
|
||||||
|
var result = await command.ExecuteNonQueryAsync();
|
||||||
|
_logger.LogInformation($"id: {_nodeId.Id}, insert log result: {result}");
|
||||||
|
}
|
||||||
|
|
||||||
|
sql = "select last_insert_rowid()";
|
||||||
|
using (var command = new SqliteCommand(sql, connection))
|
||||||
|
{
|
||||||
|
var result = await command.ExecuteScalarAsync();
|
||||||
|
_logger.LogInformation($"id: {_nodeId.Id}, about to release semaphore");
|
||||||
|
_sempaphore.Release();
|
||||||
|
_logger.LogInformation($"id: {_nodeId.Id}, saved log to sqlite");
|
||||||
|
return Convert.ToInt32(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteConflictsFromThisLog(int index, LogEntry logEntry)
|
||||||
|
{
|
||||||
|
_sempaphore.Wait();
|
||||||
|
using (var connection = new SqliteConnection($"Data Source={_path};"))
|
||||||
|
{
|
||||||
|
connection.Open();
|
||||||
|
|
||||||
|
//todo - sql injection dont copy this..
|
||||||
|
var sql = $"select data from logs where id = {index};";
|
||||||
|
_logger.LogInformation($"id: {_nodeId.Id} sql: {sql}");
|
||||||
|
using (var command = new SqliteCommand(sql, connection))
|
||||||
|
{
|
||||||
|
var data = Convert.ToString(await command.ExecuteScalarAsync());
|
||||||
|
var jsonSerializerSettings = new JsonSerializerSettings()
|
||||||
|
{
|
||||||
|
TypeNameHandling = TypeNameHandling.All
|
||||||
|
};
|
||||||
|
|
||||||
|
_logger.LogInformation($"id {_nodeId.Id} got log for index: {index}, data is {data} and new log term is {logEntry.Term}");
|
||||||
|
|
||||||
|
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
|
||||||
|
if (logEntry != null && log != null && logEntry.Term != log.Term)
|
||||||
|
{
|
||||||
|
//todo - sql injection dont copy this..
|
||||||
|
var deleteSql = $"delete from logs where id >= {index};";
|
||||||
|
_logger.LogInformation($"id: {_nodeId.Id} sql: {deleteSql}");
|
||||||
|
using (var deleteCommand = new SqliteCommand(deleteSql, connection))
|
||||||
|
{
|
||||||
|
var result = await deleteCommand.ExecuteNonQueryAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_sempaphore.Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> IsDuplicate(int index, LogEntry logEntry)
|
||||||
|
{
|
||||||
|
_sempaphore.Wait();
|
||||||
|
using (var connection = new SqliteConnection($"Data Source={_path};"))
|
||||||
|
{
|
||||||
|
connection.Open();
|
||||||
|
|
||||||
|
//todo - sql injection dont copy this..
|
||||||
|
var sql = $"select data from logs where id = {index};";
|
||||||
|
using (var command = new SqliteCommand(sql, connection))
|
||||||
|
{
|
||||||
|
var data = Convert.ToString(await command.ExecuteScalarAsync());
|
||||||
|
var jsonSerializerSettings = new JsonSerializerSettings()
|
||||||
|
{
|
||||||
|
TypeNameHandling = TypeNameHandling.All
|
||||||
|
};
|
||||||
|
|
||||||
|
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
|
||||||
|
|
||||||
|
if (logEntry != null && log != null && logEntry.Term == log.Term)
|
||||||
|
{
|
||||||
|
_sempaphore.Release();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_sempaphore.Release();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<LogEntry> Get(int index)
|
||||||
|
{
|
||||||
|
_sempaphore.Wait();
|
||||||
|
using (var connection = new SqliteConnection($"Data Source={_path};"))
|
||||||
|
{
|
||||||
|
connection.Open();
|
||||||
|
|
||||||
|
//todo - sql injection dont copy this..
|
||||||
|
var sql = $"select data from logs where id = {index}";
|
||||||
|
using (var command = new SqliteCommand(sql, connection))
|
||||||
|
{
|
||||||
|
var data = Convert.ToString(await command.ExecuteScalarAsync());
|
||||||
|
var jsonSerializerSettings = new JsonSerializerSettings()
|
||||||
|
{
|
||||||
|
TypeNameHandling = TypeNameHandling.All
|
||||||
|
};
|
||||||
|
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
|
||||||
|
_sempaphore.Release();
|
||||||
|
return log;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<(int index, LogEntry logEntry)>> GetFrom(int index)
|
||||||
|
{
|
||||||
|
_sempaphore.Wait();
|
||||||
|
var logsToReturn = new List<(int, LogEntry)>();
|
||||||
|
|
||||||
|
using (var connection = new SqliteConnection($"Data Source={_path};"))
|
||||||
|
{
|
||||||
|
connection.Open();
|
||||||
|
|
||||||
|
//todo - sql injection dont copy this..
|
||||||
|
var sql = $"select id, data from logs where id >= {index}";
|
||||||
|
using (var command = new SqliteCommand(sql, connection))
|
||||||
|
{
|
||||||
|
using (var reader = await command.ExecuteReaderAsync())
|
||||||
|
{
|
||||||
|
while (reader.Read())
|
||||||
|
{
|
||||||
|
var id = Convert.ToInt32(reader[0]);
|
||||||
|
var data = (string)reader[1];
|
||||||
|
var jsonSerializerSettings = new JsonSerializerSettings()
|
||||||
|
{
|
||||||
|
TypeNameHandling = TypeNameHandling.All
|
||||||
|
};
|
||||||
|
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
|
||||||
|
logsToReturn.Add((id, log));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_sempaphore.Release();
|
||||||
|
return logsToReturn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<long> GetTermAtIndex(int index)
|
||||||
|
{
|
||||||
|
_sempaphore.Wait();
|
||||||
|
long result = 0;
|
||||||
|
using (var connection = new SqliteConnection($"Data Source={_path};"))
|
||||||
|
{
|
||||||
|
connection.Open();
|
||||||
|
|
||||||
|
//todo - sql injection dont copy this..
|
||||||
|
var sql = $"select data from logs where id = {index}";
|
||||||
|
using (var command = new SqliteCommand(sql, connection))
|
||||||
|
{
|
||||||
|
var data = Convert.ToString(await command.ExecuteScalarAsync());
|
||||||
|
var jsonSerializerSettings = new JsonSerializerSettings()
|
||||||
|
{
|
||||||
|
TypeNameHandling = TypeNameHandling.All
|
||||||
|
};
|
||||||
|
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
|
||||||
|
if (log != null && log.Term > result)
|
||||||
|
{
|
||||||
|
result = log.Term;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_sempaphore.Release();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Remove(int indexOfCommand)
|
||||||
|
{
|
||||||
|
_sempaphore.Wait();
|
||||||
|
using (var connection = new SqliteConnection($"Data Source={_path};"))
|
||||||
|
{
|
||||||
|
connection.Open();
|
||||||
|
|
||||||
|
//todo - sql injection dont copy this..
|
||||||
|
var deleteSql = $"delete from logs where id >= {indexOfCommand};";
|
||||||
|
_logger.LogInformation($"id: {_nodeId.Id} Remove {deleteSql}");
|
||||||
|
using (var deleteCommand = new SqliteCommand(deleteSql, connection))
|
||||||
|
{
|
||||||
|
var result = await deleteCommand.ExecuteNonQueryAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_sempaphore.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
src/Ocelot.Provider.Rafty/UnableToSaveAcceptCommand.cs
Normal file
11
src/Ocelot.Provider.Rafty/UnableToSaveAcceptCommand.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
namespace Ocelot.Provider.Rafty
|
||||||
|
{
|
||||||
|
using Errors;
|
||||||
|
public class UnableToSaveAcceptCommand : Error
|
||||||
|
{
|
||||||
|
public UnableToSaveAcceptCommand(string message)
|
||||||
|
: base(message, OcelotErrorCode.UnknownError)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
src/Ocelot.Provider.Rafty/UpdateFileConfiguration.cs
Normal file
15
src/Ocelot.Provider.Rafty/UpdateFileConfiguration.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
namespace Ocelot.Provider.Rafty
|
||||||
|
{
|
||||||
|
using Configuration.File;
|
||||||
|
using global::Rafty.FiniteStateMachine;
|
||||||
|
|
||||||
|
public class UpdateFileConfiguration : ICommand
|
||||||
|
{
|
||||||
|
public UpdateFileConfiguration(FileConfiguration configuration)
|
||||||
|
{
|
||||||
|
Configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileConfiguration Configuration { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
103
src/Ocelot.Tracing.Butterfly/ButterflyTracer.cs
Normal file
103
src/Ocelot.Tracing.Butterfly/ButterflyTracer.cs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
namespace Ocelot.Tracing.Butterfly
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using global::Butterfly.Client.AspNetCore;
|
||||||
|
using global::Butterfly.Client.Tracing;
|
||||||
|
using global::Butterfly.OpenTracing;
|
||||||
|
using Infrastructure.Extensions;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
public class ButterflyTracer : DelegatingHandler, Logging.ITracer
|
||||||
|
{
|
||||||
|
private readonly IServiceTracer _tracer;
|
||||||
|
private const string PrefixSpanId = "ot-spanId";
|
||||||
|
|
||||||
|
public ButterflyTracer(IServiceProvider services)
|
||||||
|
{
|
||||||
|
_tracer = services.GetService<IServiceTracer>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Event(HttpContext httpContext, string @event)
|
||||||
|
{
|
||||||
|
// todo - if the user isnt using tracing the code gets here and will blow up on
|
||||||
|
// _tracer.Tracer.TryExtract..
|
||||||
|
if (_tracer == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var span = httpContext.GetSpan();
|
||||||
|
|
||||||
|
if (span == null)
|
||||||
|
{
|
||||||
|
var spanBuilder = new SpanBuilder($"server {httpContext.Request.Method} {httpContext.Request.Path}");
|
||||||
|
if (_tracer.Tracer.TryExtract(out var spanContext, httpContext.Request.Headers, (c, k) => c[k].GetValue(),
|
||||||
|
c => c.Select(x => new KeyValuePair<string, string>(x.Key, x.Value.GetValue())).GetEnumerator()))
|
||||||
|
{
|
||||||
|
spanBuilder.AsChildOf(spanContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
span = _tracer.Start(spanBuilder);
|
||||||
|
httpContext.SetSpan(span);
|
||||||
|
}
|
||||||
|
|
||||||
|
span?.Log(LogField.CreateNew().Event(@event));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<HttpResponseMessage> SendAsync(
|
||||||
|
HttpRequestMessage request,
|
||||||
|
CancellationToken cancellationToken,
|
||||||
|
Action<string> addTraceIdToRepo,
|
||||||
|
Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> baseSendAsync)
|
||||||
|
{
|
||||||
|
return _tracer.ChildTraceAsync($"httpclient {request.Method}", DateTimeOffset.UtcNow, span => TracingSendAsync(span, request, cancellationToken, addTraceIdToRepo, baseSendAsync));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual async Task<HttpResponseMessage> TracingSendAsync(
|
||||||
|
ISpan span,
|
||||||
|
HttpRequestMessage request,
|
||||||
|
CancellationToken cancellationToken,
|
||||||
|
Action<string> addTraceIdToRepo,
|
||||||
|
Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> baseSendAsync)
|
||||||
|
{
|
||||||
|
if (request.Headers.Contains(PrefixSpanId))
|
||||||
|
{
|
||||||
|
request.Headers.Remove(PrefixSpanId);
|
||||||
|
request.Headers.TryAddWithoutValidation(PrefixSpanId, span.SpanContext.SpanId);
|
||||||
|
}
|
||||||
|
|
||||||
|
addTraceIdToRepo(span.SpanContext.TraceId);
|
||||||
|
|
||||||
|
span.Tags.Client().Component("HttpClient")
|
||||||
|
.HttpMethod(request.Method.Method)
|
||||||
|
.HttpUrl(request.RequestUri.OriginalString)
|
||||||
|
.HttpHost(request.RequestUri.Host)
|
||||||
|
.HttpPath(request.RequestUri.PathAndQuery)
|
||||||
|
.PeerAddress(request.RequestUri.OriginalString)
|
||||||
|
.PeerHostName(request.RequestUri.Host)
|
||||||
|
.PeerPort(request.RequestUri.Port);
|
||||||
|
|
||||||
|
_tracer.Tracer.Inject(span.SpanContext, request.Headers, (c, k, v) =>
|
||||||
|
{
|
||||||
|
if (!c.Contains(k))
|
||||||
|
{
|
||||||
|
c.Add(k, v);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
span.Log(LogField.CreateNew().ClientSend());
|
||||||
|
|
||||||
|
var responseMessage = await baseSendAsync(request, cancellationToken);
|
||||||
|
|
||||||
|
span.Log(LogField.CreateNew().ClientReceive());
|
||||||
|
|
||||||
|
return responseMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
src/Ocelot.Tracing.Butterfly/Ocelot.Tracing.Butterfly.csproj
Normal file
37
src/Ocelot.Tracing.Butterfly/Ocelot.Tracing.Butterfly.csproj
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
||||||
|
<NETStandardImplicitPackageVersion>2.0.0</NETStandardImplicitPackageVersion>
|
||||||
|
<NoPackageAnalysis>true</NoPackageAnalysis>
|
||||||
|
<Description>This package provides methods to integrate Butterfly tracing with Ocelot.</Description>
|
||||||
|
<AssemblyTitle>Ocelot.Tracing.Butterfly</AssemblyTitle>
|
||||||
|
<VersionPrefix>0.0.0-dev</VersionPrefix>
|
||||||
|
<AssemblyName>Ocelot.Tracing.Butterfly</AssemblyName>
|
||||||
|
<PackageId>Ocelot.Tracing.Butterfly</PackageId>
|
||||||
|
<PackageTags>API Gateway;.NET core; Butterfly; ButterflyAPM</PackageTags>
|
||||||
|
<PackageProjectUrl>https://github.com/ThreeMammals/Ocelot</PackageProjectUrl>
|
||||||
|
<PackageProjectUrl>https://github.com/ThreeMammals/Ocelot</PackageProjectUrl>
|
||||||
|
<RuntimeIdentifiers>win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64</RuntimeIdentifiers>
|
||||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
||||||
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
|
||||||
|
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||||
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
|
||||||
|
<Authors>Tom Pallister</Authors>
|
||||||
|
<CodeAnalysisRuleSet>..\..\codeanalysis.ruleset</CodeAnalysisRuleSet>
|
||||||
|
<RootNamespace>Ocelot.Tracing.Butterfly</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<DebugSymbols>True</DebugSymbols>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Ocelot\Ocelot.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Butterfly.Client" Version="0.0.8" />
|
||||||
|
<PackageReference Include="Butterfly.Client.AspNetCore" Version="0.0.8" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
18
src/Ocelot.Tracing.Butterfly/OcelotBuilderExtensions.cs
Normal file
18
src/Ocelot.Tracing.Butterfly/OcelotBuilderExtensions.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
namespace Ocelot.Tracing.Butterfly
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using DependencyInjection;
|
||||||
|
using global::Butterfly.Client.AspNetCore;
|
||||||
|
using Logging;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
public static class OcelotBuilderExtensions
|
||||||
|
{
|
||||||
|
public static IOcelotBuilder AddButterfly(this IOcelotBuilder builder, Action<ButterflyOptions> settings)
|
||||||
|
{
|
||||||
|
builder.Services.AddSingleton<ITracer, ButterflyTracer>();
|
||||||
|
builder.Services.AddButterfly(settings);
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -32,7 +32,7 @@ namespace Ocelot.Authorisation
|
|||||||
if (!authorised)
|
if (!authorised)
|
||||||
{
|
{
|
||||||
return new ErrorResponse<bool>(new ClaimValueNotAuthorisedError(
|
return new ErrorResponse<bool>(new ClaimValueNotAuthorisedError(
|
||||||
$"claim value: {values.Data} is not the same as required value: {required.Value} for type: {required.Key}"));
|
$"claim value: {string.Join(", ", values.Data)} is not the same as required value: {required.Value} for type: {required.Key}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
using Microsoft.AspNetCore.Builder;
|
using Ocelot.Middleware.Pipeline;
|
||||||
using Ocelot.Middleware.Pipeline;
|
|
||||||
|
|
||||||
namespace Ocelot.Claims.Middleware
|
namespace Ocelot.Claims.Middleware
|
||||||
{
|
{
|
||||||
public static class ClaimsToClaimsMiddlewareExtensions
|
public static class ClaimsBuilderMiddlewareExtensions
|
||||||
{
|
{
|
||||||
public static IOcelotPipelineBuilder UseClaimsToClaimsMiddleware(this IOcelotPipelineBuilder builder)
|
public static IOcelotPipelineBuilder UseClaimsToClaimsMiddleware(this IOcelotPipelineBuilder builder)
|
||||||
{
|
{
|
||||||
|
@ -2,7 +2,6 @@ using Ocelot.Configuration.File;
|
|||||||
|
|
||||||
namespace Ocelot.Configuration.Creator
|
namespace Ocelot.Configuration.Creator
|
||||||
{
|
{
|
||||||
|
|
||||||
public class LoadBalancerOptionsCreator : ILoadBalancerOptionsCreator
|
public class LoadBalancerOptionsCreator : ILoadBalancerOptionsCreator
|
||||||
{
|
{
|
||||||
public LoadBalancerOptions Create(FileLoadBalancerOptions options)
|
public LoadBalancerOptions Create(FileLoadBalancerOptions options)
|
||||||
|
@ -94,8 +94,8 @@ namespace Ocelot.DependencyInjection
|
|||||||
Services.TryAddSingleton<IUrlPathToUrlTemplateMatcher, RegExUrlMatcher>();
|
Services.TryAddSingleton<IUrlPathToUrlTemplateMatcher, RegExUrlMatcher>();
|
||||||
Services.TryAddSingleton<IPlaceholderNameAndValueFinder, UrlPathPlaceholderNameAndValueFinder>();
|
Services.TryAddSingleton<IPlaceholderNameAndValueFinder, UrlPathPlaceholderNameAndValueFinder>();
|
||||||
Services.TryAddSingleton<IDownstreamPathPlaceholderReplacer, DownstreamTemplatePathPlaceholderReplacer>();
|
Services.TryAddSingleton<IDownstreamPathPlaceholderReplacer, DownstreamTemplatePathPlaceholderReplacer>();
|
||||||
Services.TryAddSingleton<IDownstreamRouteProvider, DownstreamRouteFinder>();
|
Services.AddSingleton<IDownstreamRouteProvider, DownstreamRouteFinder>();
|
||||||
Services.TryAddSingleton<IDownstreamRouteProvider, DownstreamRouteCreator>();
|
Services.AddSingleton<IDownstreamRouteProvider, DownstreamRouteCreator>();
|
||||||
Services.TryAddSingleton<IDownstreamRouteProviderFactory, DownstreamRouteProviderFactory>();
|
Services.TryAddSingleton<IDownstreamRouteProviderFactory, DownstreamRouteProviderFactory>();
|
||||||
Services.TryAddSingleton<IHttpRequester, HttpClientHttpRequester>();
|
Services.TryAddSingleton<IHttpRequester, HttpClientHttpRequester>();
|
||||||
Services.TryAddSingleton<IHttpResponder, HttpContextResponder>();
|
Services.TryAddSingleton<IHttpResponder, HttpContextResponder>();
|
||||||
|
@ -28,7 +28,7 @@ namespace Ocelot.DownstreamRouteFinder.Finder
|
|||||||
|
|
||||||
foreach (var reRoute in applicableReRoutes)
|
foreach (var reRoute in applicableReRoutes)
|
||||||
{
|
{
|
||||||
var urlMatch = _urlMatcher.Match(upstreamUrlPath, upstreamQueryString, reRoute.UpstreamTemplatePattern.Template, reRoute.UpstreamTemplatePattern.ContainsQueryString);
|
var urlMatch = _urlMatcher.Match(upstreamUrlPath, upstreamQueryString, reRoute.UpstreamTemplatePattern);
|
||||||
|
|
||||||
if (urlMatch.Data.Match)
|
if (urlMatch.Data.Match)
|
||||||
{
|
{
|
||||||
|
@ -5,7 +5,7 @@ namespace Ocelot.DownstreamRouteFinder.Finder
|
|||||||
public class UnableToFindDownstreamRouteError : Error
|
public class UnableToFindDownstreamRouteError : Error
|
||||||
{
|
{
|
||||||
public UnableToFindDownstreamRouteError(string path, string httpVerb)
|
public UnableToFindDownstreamRouteError(string path, string httpVerb)
|
||||||
: base($"Unable to find downstream route for path: {path}, verb: {httpVerb}", OcelotErrorCode.UnableToFindDownstreamRouteError)
|
: base($"Failed to match ReRoute configuration for upstream path: {path}, verb: {httpVerb}.", OcelotErrorCode.UnableToFindDownstreamRouteError)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
|
using Ocelot.Values;
|
||||||
|
|
||||||
namespace Ocelot.DownstreamRouteFinder.UrlMatcher
|
namespace Ocelot.DownstreamRouteFinder.UrlMatcher
|
||||||
{
|
{
|
||||||
public interface IUrlPathToUrlTemplateMatcher
|
public interface IUrlPathToUrlTemplateMatcher
|
||||||
{
|
{
|
||||||
Response<UrlMatch> Match(string upstreamUrlPath, string upstreamQueryString, string upstreamUrlPathTemplate, bool containsQueryString);
|
Response<UrlMatch> Match(string upstreamUrlPath, string upstreamQueryString, UpstreamPathTemplate pathTemplate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,21 @@
|
|||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
|
using Ocelot.Values;
|
||||||
|
|
||||||
namespace Ocelot.DownstreamRouteFinder.UrlMatcher
|
namespace Ocelot.DownstreamRouteFinder.UrlMatcher
|
||||||
{
|
{
|
||||||
public class RegExUrlMatcher : IUrlPathToUrlTemplateMatcher
|
public class RegExUrlMatcher : IUrlPathToUrlTemplateMatcher
|
||||||
{
|
{
|
||||||
public Response<UrlMatch> Match(string upstreamUrlPath, string upstreamQueryString, string upstreamUrlPathTemplate, bool containsQueryString)
|
public Response<UrlMatch> Match(string upstreamUrlPath, string upstreamQueryString, UpstreamPathTemplate pathTemplate)
|
||||||
{
|
{
|
||||||
var regex = new Regex(upstreamUrlPathTemplate);
|
if (!pathTemplate.ContainsQueryString)
|
||||||
|
|
||||||
if (!containsQueryString)
|
|
||||||
{
|
{
|
||||||
return regex.IsMatch(upstreamUrlPath)
|
return pathTemplate.Pattern.IsMatch(upstreamUrlPath)
|
||||||
? new OkResponse<UrlMatch>(new UrlMatch(true))
|
? new OkResponse<UrlMatch>(new UrlMatch(true))
|
||||||
: new OkResponse<UrlMatch>(new UrlMatch(false));
|
: new OkResponse<UrlMatch>(new UrlMatch(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
return regex.IsMatch($"{upstreamUrlPath}{upstreamQueryString}")
|
return pathTemplate.Pattern.IsMatch($"{upstreamUrlPath}{upstreamQueryString}")
|
||||||
? new OkResponse<UrlMatch>(new UrlMatch(true))
|
? new OkResponse<UrlMatch>(new UrlMatch(true))
|
||||||
: new OkResponse<UrlMatch>(new UrlMatch(false));
|
: new OkResponse<UrlMatch>(new UrlMatch(false));
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ namespace Ocelot.DownstreamUrlCreator.Middleware
|
|||||||
public async Task Invoke(DownstreamContext context)
|
public async Task Invoke(DownstreamContext context)
|
||||||
{
|
{
|
||||||
var response = _replacer
|
var response = _replacer
|
||||||
.Replace(context.DownstreamReRoute.DownstreamPathTemplate, context.TemplatePlaceholderNameAndValues);
|
.Replace(context.DownstreamReRoute.DownstreamPathTemplate.Value, context.TemplatePlaceholderNameAndValues);
|
||||||
|
|
||||||
if (response.IsError)
|
if (response.IsError)
|
||||||
{
|
{
|
||||||
@ -103,7 +103,7 @@ namespace Ocelot.DownstreamUrlCreator.Middleware
|
|||||||
return dsPath.Value.Substring(0, dsPath.Value.IndexOf("?", StringComparison.Ordinal));
|
return dsPath.Value.Substring(0, dsPath.Value.IndexOf("?", StringComparison.Ordinal));
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetQueryString(DownstreamPath dsPath)
|
private string GetQueryString(DownstreamPath dsPath)
|
||||||
{
|
{
|
||||||
return dsPath.Value.Substring(dsPath.Value.IndexOf("?", StringComparison.Ordinal));
|
return dsPath.Value.Substring(dsPath.Value.IndexOf("?", StringComparison.Ordinal));
|
||||||
}
|
}
|
||||||
@ -116,8 +116,9 @@ namespace Ocelot.DownstreamUrlCreator.Middleware
|
|||||||
private (string path, string query) CreateServiceFabricUri(DownstreamContext context, Response<DownstreamPath> dsPath)
|
private (string path, string query) CreateServiceFabricUri(DownstreamContext context, Response<DownstreamPath> dsPath)
|
||||||
{
|
{
|
||||||
var query = context.DownstreamRequest.Query;
|
var query = context.DownstreamRequest.Query;
|
||||||
var serviceFabricPath = $"/{context.DownstreamReRoute.ServiceName + dsPath.Data.Value}";
|
var serviceName = _replacer.Replace(context.DownstreamReRoute.ServiceName, context.TemplatePlaceholderNameAndValues);
|
||||||
return (serviceFabricPath, query);
|
var pathTemplate = $"/{serviceName.Data.Value}{dsPath.Data.Value}";
|
||||||
|
return (pathTemplate, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool ServiceFabricRequest(DownstreamContext context)
|
private static bool ServiceFabricRequest(DownstreamContext context)
|
||||||
|
@ -8,11 +8,12 @@ namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer
|
|||||||
{
|
{
|
||||||
public class DownstreamTemplatePathPlaceholderReplacer : IDownstreamPathPlaceholderReplacer
|
public class DownstreamTemplatePathPlaceholderReplacer : IDownstreamPathPlaceholderReplacer
|
||||||
{
|
{
|
||||||
public Response<DownstreamPath> Replace(DownstreamPathTemplate downstreamPathTemplate, List<PlaceholderNameAndValue> urlPathPlaceholderNameAndValues)
|
public Response<DownstreamPath> Replace(string downstreamPathTemplate,
|
||||||
|
List<PlaceholderNameAndValue> urlPathPlaceholderNameAndValues)
|
||||||
{
|
{
|
||||||
var downstreamPath = new StringBuilder();
|
var downstreamPath = new StringBuilder();
|
||||||
|
|
||||||
downstreamPath.Append(downstreamPathTemplate.Value);
|
downstreamPath.Append(downstreamPathTemplate);
|
||||||
|
|
||||||
foreach (var placeholderVariableAndValue in urlPathPlaceholderNameAndValues)
|
foreach (var placeholderVariableAndValue in urlPathPlaceholderNameAndValues)
|
||||||
{
|
{
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user