Merge pull request #53 from ThreeMammals/develop

merge newest
This commit is contained in:
geffzhang 2019-01-13 15:26:03 +08:00 committed by GitHub
commit 08c2ac1b05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
162 changed files with 10315 additions and 1005 deletions

2
.dockerignore Normal file
View File

@ -0,0 +1,2 @@
*/*/bin
*/*/obj

3
.gitignore vendored
View File

@ -252,3 +252,6 @@ _templates/
# JetBrains Rider # JetBrains Rider
.idea/ .idea/
# Test Results
*.trx

View File

@ -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
@ -28,4 +28,4 @@ cache:
- .packages - .packages
- tools/Addins - tools/Addins
- tools/gitreleasemanager - tools/gitreleasemanager
- tools/GitVersion.CommandLine - tools/GitVersion.CommandLine

39
Dockerfile Normal file
View 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"]

View File

@ -1,88 +1,139 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15 # Visual Studio 15
VisualStudioVersion = 15.0.27130.2036 VisualStudioVersion = 15.0.27130.2036
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5CFB79B7-C9DC-45A4-9A75-625D92471702}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5CFB79B7-C9DC-45A4-9A75-625D92471702}"
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
.gitignore = .gitignore .dockerignore = .dockerignore
build-and-release-unstable.ps1 = build-and-release-unstable.ps1 .gitignore = .gitignore
build-and-run-tests.ps1 = build-and-run-tests.ps1 build-and-release-unstable.ps1 = build-and-release-unstable.ps1
build.cake = build.cake build-and-run-tests.ps1 = build-and-run-tests.ps1
build.ps1 = build.ps1 build.cake = build.cake
codeanalysis.ruleset = codeanalysis.ruleset build.ps1 = build.ps1
GitVersion.yml = GitVersion.yml codeanalysis.ruleset = codeanalysis.ruleset
global.json = global.json docker-compose.yaml = docker-compose.yaml
LICENSE.md = LICENSE.md Dockerfile = Dockerfile
ocelot.postman_collection.json = ocelot.postman_collection.json GitVersion.yml = GitVersion.yml
README.md = README.md global.json = global.json
release.ps1 = release.ps1 LICENSE.md = LICENSE.md
ReleaseNotes.md = ReleaseNotes.md README.md = README.md
run-acceptance-tests.ps1 = run-acceptance-tests.ps1 release.ps1 = release.ps1
run-benchmarks.ps1 = run-benchmarks.ps1 ReleaseNotes.md = ReleaseNotes.md
run-unit-tests.ps1 = run-unit-tests.ps1 run-acceptance-tests.ps1 = run-acceptance-tests.ps1
version.ps1 = version.ps1 run-benchmarks.ps1 = run-benchmarks.ps1
EndProjectSection run-unit-tests.ps1 = run-unit-tests.ps1
EndProject version.ps1 = version.ps1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{5B401523-36DA-4491-B73A-7590A26E420B}" EndProjectSection
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot", "src\Ocelot\Ocelot.csproj", "{D6DF4206-0DBA-41D8-884D-C3E08290FDBB}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{5B401523-36DA-4491-B73A-7590A26E420B}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.UnitTests", "test\Ocelot.UnitTests\Ocelot.UnitTests.csproj", "{54E84F1A-E525-4443-96EC-039CBD50C263}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot", "src\Ocelot\Ocelot.csproj", "{D6DF4206-0DBA-41D8-884D-C3E08290FDBB}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.AcceptanceTests", "test\Ocelot.AcceptanceTests\Ocelot.AcceptanceTests.csproj", "{F8C224FE-36BE-45F5-9B0E-666D8F4A9B52}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.UnitTests", "test\Ocelot.UnitTests\Ocelot.UnitTests.csproj", "{54E84F1A-E525-4443-96EC-039CBD50C263}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.ManualTest", "test\Ocelot.ManualTest\Ocelot.ManualTest.csproj", "{02BBF4C5-517E-4157-8D21-4B8B9E118B7A}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.AcceptanceTests", "test\Ocelot.AcceptanceTests\Ocelot.AcceptanceTests.csproj", "{F8C224FE-36BE-45F5-9B0E-666D8F4A9B52}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Benchmarks", "test\Ocelot.Benchmarks\Ocelot.Benchmarks.csproj", "{106B49E6-95F6-4A7B-B81C-96BFA74AF035}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.ManualTest", "test\Ocelot.ManualTest\Ocelot.ManualTest.csproj", "{02BBF4C5-517E-4157-8D21-4B8B9E118B7A}"
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.Benchmarks", "test\Ocelot.Benchmarks\Ocelot.Benchmarks.csproj", "{106B49E6-95F6-4A7B-B81C-96BFA74AF035}"
EndProject EndProject
Global Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.IntegrationTests", "test\Ocelot.IntegrationTests\Ocelot.IntegrationTests.csproj", "{D4575572-99CA-4530-8737-C296EDA326F8}"
GlobalSection(SolutionConfigurationPlatforms) = preSolution EndProject
Debug|Any CPU = Debug|Any CPU Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Administration", "src\Ocelot.Administration\Ocelot.Administration.csproj", "{F69CEF43-27D2-4940-A47A-FCA879E371BC}"
Release|Any CPU = Release|Any CPU EndProject
EndGlobalSection Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Cache.CacheManager", "src\Ocelot.Cache.CacheManager\Ocelot.Cache.CacheManager.csproj", "{EB9F438F-062E-499F-B6EA-4412BEF6D74C}"
GlobalSection(ProjectConfigurationPlatforms) = postSolution EndProject
{D6DF4206-0DBA-41D8-884D-C3E08290FDBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Provider.Consul", "src\Ocelot.Provider.Consul\Ocelot.Provider.Consul.csproj", "{02F5AE4D-9C36-4E58-B7C6-012CBBDEFDE0}"
{D6DF4206-0DBA-41D8-884D-C3E08290FDBB}.Debug|Any CPU.Build.0 = Debug|Any CPU EndProject
{D6DF4206-0DBA-41D8-884D-C3E08290FDBB}.Release|Any CPU.ActiveCfg = Release|Any CPU Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Provider.Eureka", "src\Ocelot.Provider.Eureka\Ocelot.Provider.Eureka.csproj", "{9BBD3586-145C-4FA0-91C5-9ED58287D753}"
{D6DF4206-0DBA-41D8-884D-C3E08290FDBB}.Release|Any CPU.Build.0 = Release|Any CPU EndProject
{54E84F1A-E525-4443-96EC-039CBD50C263}.Debug|Any CPU.ActiveCfg = Debug|Any CPU Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Provider.Polly", "src\Ocelot.Provider.Polly\Ocelot.Provider.Polly.csproj", "{1F6E5DCF-8A2E-4E24-A25D-064362DE8D0E}"
{54E84F1A-E525-4443-96EC-039CBD50C263}.Debug|Any CPU.Build.0 = Debug|Any CPU EndProject
{54E84F1A-E525-4443-96EC-039CBD50C263}.Release|Any CPU.ActiveCfg = Release|Any CPU Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Provider.Rafty", "src\Ocelot.Provider.Rafty\Ocelot.Provider.Rafty.csproj", "{AC153C67-EF18-47E6-A230-F0D3CF5F0A98}"
{54E84F1A-E525-4443-96EC-039CBD50C263}.Release|Any CPU.Build.0 = Release|Any CPU EndProject
{F8C224FE-36BE-45F5-9B0E-666D8F4A9B52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Tracing.Butterfly", "src\Ocelot.Tracing.Butterfly\Ocelot.Tracing.Butterfly.csproj", "{6045E23D-669C-4F27-AF8E-8EEE6DB3557F}"
{F8C224FE-36BE-45F5-9B0E-666D8F4A9B52}.Debug|Any CPU.Build.0 = Debug|Any CPU EndProject
{F8C224FE-36BE-45F5-9B0E-666D8F4A9B52}.Release|Any CPU.ActiveCfg = Release|Any CPU Global
{F8C224FE-36BE-45F5-9B0E-666D8F4A9B52}.Release|Any CPU.Build.0 = Release|Any CPU GlobalSection(SolutionConfigurationPlatforms) = preSolution
{02BBF4C5-517E-4157-8D21-4B8B9E118B7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
{02BBF4C5-517E-4157-8D21-4B8B9E118B7A}.Debug|Any CPU.Build.0 = Debug|Any CPU Release|Any CPU = Release|Any CPU
{02BBF4C5-517E-4157-8D21-4B8B9E118B7A}.Release|Any CPU.ActiveCfg = Release|Any CPU EndGlobalSection
{02BBF4C5-517E-4157-8D21-4B8B9E118B7A}.Release|Any CPU.Build.0 = Release|Any CPU GlobalSection(ProjectConfigurationPlatforms) = postSolution
{106B49E6-95F6-4A7B-B81C-96BFA74AF035}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D6DF4206-0DBA-41D8-884D-C3E08290FDBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{106B49E6-95F6-4A7B-B81C-96BFA74AF035}.Debug|Any CPU.Build.0 = Debug|Any CPU {D6DF4206-0DBA-41D8-884D-C3E08290FDBB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{106B49E6-95F6-4A7B-B81C-96BFA74AF035}.Release|Any CPU.ActiveCfg = Release|Any CPU {D6DF4206-0DBA-41D8-884D-C3E08290FDBB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{106B49E6-95F6-4A7B-B81C-96BFA74AF035}.Release|Any CPU.Build.0 = Release|Any CPU {D6DF4206-0DBA-41D8-884D-C3E08290FDBB}.Release|Any CPU.Build.0 = Release|Any CPU
{D4575572-99CA-4530-8737-C296EDA326F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {54E84F1A-E525-4443-96EC-039CBD50C263}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D4575572-99CA-4530-8737-C296EDA326F8}.Debug|Any CPU.Build.0 = Debug|Any CPU {54E84F1A-E525-4443-96EC-039CBD50C263}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D4575572-99CA-4530-8737-C296EDA326F8}.Release|Any CPU.ActiveCfg = Release|Any CPU {54E84F1A-E525-4443-96EC-039CBD50C263}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D4575572-99CA-4530-8737-C296EDA326F8}.Release|Any CPU.Build.0 = Release|Any CPU {54E84F1A-E525-4443-96EC-039CBD50C263}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection {F8C224FE-36BE-45F5-9B0E-666D8F4A9B52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
GlobalSection(SolutionProperties) = preSolution {F8C224FE-36BE-45F5-9B0E-666D8F4A9B52}.Debug|Any CPU.Build.0 = Debug|Any CPU
HideSolutionNode = FALSE {F8C224FE-36BE-45F5-9B0E-666D8F4A9B52}.Release|Any CPU.ActiveCfg = Release|Any CPU
EndGlobalSection {F8C224FE-36BE-45F5-9B0E-666D8F4A9B52}.Release|Any CPU.Build.0 = Release|Any CPU
GlobalSection(NestedProjects) = preSolution {02BBF4C5-517E-4157-8D21-4B8B9E118B7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D6DF4206-0DBA-41D8-884D-C3E08290FDBB} = {5CFB79B7-C9DC-45A4-9A75-625D92471702} {02BBF4C5-517E-4157-8D21-4B8B9E118B7A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{54E84F1A-E525-4443-96EC-039CBD50C263} = {5B401523-36DA-4491-B73A-7590A26E420B} {02BBF4C5-517E-4157-8D21-4B8B9E118B7A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F8C224FE-36BE-45F5-9B0E-666D8F4A9B52} = {5B401523-36DA-4491-B73A-7590A26E420B} {02BBF4C5-517E-4157-8D21-4B8B9E118B7A}.Release|Any CPU.Build.0 = Release|Any CPU
{02BBF4C5-517E-4157-8D21-4B8B9E118B7A} = {5B401523-36DA-4491-B73A-7590A26E420B} {106B49E6-95F6-4A7B-B81C-96BFA74AF035}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{106B49E6-95F6-4A7B-B81C-96BFA74AF035} = {5B401523-36DA-4491-B73A-7590A26E420B} {106B49E6-95F6-4A7B-B81C-96BFA74AF035}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D4575572-99CA-4530-8737-C296EDA326F8} = {5B401523-36DA-4491-B73A-7590A26E420B} {106B49E6-95F6-4A7B-B81C-96BFA74AF035}.Release|Any CPU.ActiveCfg = Release|Any CPU
EndGlobalSection {106B49E6-95F6-4A7B-B81C-96BFA74AF035}.Release|Any CPU.Build.0 = Release|Any CPU
GlobalSection(ExtensibilityGlobals) = postSolution {D4575572-99CA-4530-8737-C296EDA326F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
SolutionGuid = {21476EFF-778A-4F97-8A56-D1AF1CEC0C48} {D4575572-99CA-4530-8737-C296EDA326F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
EndGlobalSection {D4575572-99CA-4530-8737-C296EDA326F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
EndGlobal {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
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{D6DF4206-0DBA-41D8-884D-C3E08290FDBB} = {5CFB79B7-C9DC-45A4-9A75-625D92471702}
{54E84F1A-E525-4443-96EC-039CBD50C263} = {5B401523-36DA-4491-B73A-7590A26E420B}
{F8C224FE-36BE-45F5-9B0E-666D8F4A9B52} = {5B401523-36DA-4491-B73A-7590A26E420B}
{02BBF4C5-517E-4157-8D21-4B8B9E118B7A} = {5B401523-36DA-4491-B73A-7590A26E420B}
{106B49E6-95F6-4A7B-B81C-96BFA74AF035} = {5B401523-36DA-4491-B73A-7590A26E420B}
{D4575572-99CA-4530-8737-C296EDA326F8} = {5B401523-36DA-4491-B73A-7590A26E420B}
{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
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {21476EFF-778A-4F97-8A56-D1AF1CEC0C48}
EndGlobalSection
EndGlobal

View File

@ -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
[![](https://codescene.io/projects/697/status.svg) Get more details at **codescene.io**.](https://codescene.io/projects/697/jobs/latest-successful/results) [![](https://codescene.io/projects/697/status.svg) Get more details at **codescene.io**.](https://codescene.io/projects/697/jobs/latest-successful/results)

View File

@ -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"]);
Information("Pushing package " + codePackage);
Information("Calling NuGetPush"); foreach(var artifact in artifacts)
{
var codePackage = packagesDir + File(artifact);
NuGetPush( Information("Pushing package " + codePackage);
codePackage,
new NuGetPushSettings { Information("Calling NuGetPush");
ApiKey = feedApiKey,
Source = codeFeedUrl NuGetPush(
}); codePackage,
new NuGetPushSettings {
ApiKey = feedApiKey,
Source = codeFeedUrl
});
}
} }
/// gets the resource from the specified url /// gets the resource from the specified url

477
build.ps1
View File

@ -1,235 +1,242 @@
########################################################################## ##########################################################################
# This is the Cake bootstrapper script for PowerShell. # This is the Cake bootstrapper script for PowerShell.
# This file was downloaded from https://github.com/cake-build/resources # This file was downloaded from https://github.com/cake-build/resources
# Feel free to change this file to fit your needs. # Feel free to change this file to fit your needs.
########################################################################## ##########################################################################
<# <#
.SYNOPSIS .SYNOPSIS
This is a Powershell script to bootstrap a Cake build. This is a Powershell script to bootstrap a Cake build.
.DESCRIPTION .DESCRIPTION
This Powershell script will download NuGet if missing, restore NuGet tools (including Cake) This Powershell script will download NuGet if missing, restore NuGet tools (including Cake)
and execute your Cake build script with the parameters you provide. and execute your Cake build script with the parameters you provide.
.PARAMETER Script .PARAMETER Script
The build script to execute. The build script to execute.
.PARAMETER Target .PARAMETER Target
The build script target to run. The build script target to run.
.PARAMETER Configuration .PARAMETER Configuration
The build configuration to use. The build configuration to use.
.PARAMETER Verbosity .PARAMETER Verbosity
Specifies the amount of information to be displayed. Specifies the amount of information to be displayed.
.PARAMETER ShowDescription .PARAMETER ShowDescription
Shows description about tasks. Shows description about tasks.
.PARAMETER DryRun .PARAMETER DryRun
Performs a dry run. Performs a dry run.
.PARAMETER Experimental .PARAMETER SkipToolPackageRestore
Uses the nightly builds of the Roslyn script engine. Skips restoring of packages.
.PARAMETER Mono .PARAMETER ScriptArgs
Uses the Mono Compiler rather than the Roslyn script engine. Remaining arguments are added here.
.PARAMETER SkipToolPackageRestore
Skips restoring of packages. .LINK
.PARAMETER ScriptArgs https://cakebuild.net
Remaining arguments are added here.
#>
.LINK
https://cakebuild.net [CmdletBinding()]
Param(
#> [string]$Script = "build.cake",
[string]$Target,
[CmdletBinding()] [string]$Configuration,
Param( [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")]
[string]$Script = "build.cake", [string]$Verbosity,
[string]$Target, [switch]$ShowDescription,
[string]$Configuration, [Alias("WhatIf", "Noop")]
[ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] [switch]$DryRun,
[string]$Verbosity, [switch]$SkipToolPackageRestore,
[switch]$ShowDescription, [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)]
[Alias("WhatIf", "Noop")] [string[]]$ScriptArgs
[switch]$DryRun, )
[switch]$Experimental,
[switch]$Mono, # Attempt to set highest encryption available for SecurityProtocol.
[switch]$SkipToolPackageRestore, # PowerShell will not set this by default (until maybe .NET 4.6.x). This
[Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] # will typically produce a message for PowerShell v2 (just an info
[string[]]$ScriptArgs # message though)
) try {
# Set TLS 1.2 (3072), then TLS 1.1 (768), then TLS 1.0 (192), finally SSL 3.0 (48)
[Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null # Use integers because the enumeration values for TLS 1.2 and TLS 1.1 won't
function MD5HashFile([string] $filePath) # exist in .NET 4.0, even though they are addressable if .NET 4.5+ is
{ # installed (.NET 4.5 is an in-place upgrade).
if ([string]::IsNullOrEmpty($filePath) -or !(Test-Path $filePath -PathType Leaf)) [System.Net.ServicePointManager]::SecurityProtocol = 3072 -bor 768 -bor 192 -bor 48
{ } catch {
return $null 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'
} }
[System.IO.Stream] $file = $null; [Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null
[System.Security.Cryptography.MD5] $md5 = $null; function MD5HashFile([string] $filePath)
try {
{ if ([string]::IsNullOrEmpty($filePath) -or !(Test-Path $filePath -PathType Leaf))
$md5 = [System.Security.Cryptography.MD5]::Create() {
$file = [System.IO.File]::OpenRead($filePath) return $null
return [System.BitConverter]::ToString($md5.ComputeHash($file)) }
}
finally [System.IO.Stream] $file = $null;
{ [System.Security.Cryptography.MD5] $md5 = $null;
if ($file -ne $null) try
{ {
$file.Dispose() $md5 = [System.Security.Cryptography.MD5]::Create()
} $file = [System.IO.File]::OpenRead($filePath)
} return [System.BitConverter]::ToString($md5.ComputeHash($file))
} }
finally
function GetProxyEnabledWebClient {
{ if ($file -ne $null)
$wc = New-Object System.Net.WebClient {
$proxy = [System.Net.WebRequest]::GetSystemWebProxy() $file.Dispose()
$proxy.Credentials = [System.Net.CredentialCache]::DefaultCredentials }
$wc.Proxy = $proxy }
return $wc }
}
function GetProxyEnabledWebClient
Write-Host "Preparing to run build script..." {
$wc = New-Object System.Net.WebClient
if(!$PSScriptRoot){ $proxy = [System.Net.WebRequest]::GetSystemWebProxy()
$PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent $proxy.Credentials = [System.Net.CredentialCache]::DefaultCredentials
} $wc.Proxy = $proxy
return $wc
$TOOLS_DIR = Join-Path $PSScriptRoot "tools" }
$ADDINS_DIR = Join-Path $TOOLS_DIR "Addins"
$MODULES_DIR = Join-Path $TOOLS_DIR "Modules" Write-Host "Preparing to run build script..."
$NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe"
$CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe" if(!$PSScriptRoot){
$NUGET_URL = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
$PACKAGES_CONFIG = Join-Path $TOOLS_DIR "packages.config" }
$PACKAGES_CONFIG_MD5 = Join-Path $TOOLS_DIR "packages.config.md5sum"
$ADDINS_PACKAGES_CONFIG = Join-Path $ADDINS_DIR "packages.config" $TOOLS_DIR = Join-Path $PSScriptRoot "tools"
$MODULES_PACKAGES_CONFIG = Join-Path $MODULES_DIR "packages.config" $ADDINS_DIR = Join-Path $TOOLS_DIR "Addins"
$MODULES_DIR = Join-Path $TOOLS_DIR "Modules"
# Make sure tools folder exists $NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe"
if ((Test-Path $PSScriptRoot) -and !(Test-Path $TOOLS_DIR)) { $CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe"
Write-Verbose -Message "Creating tools directory..." $NUGET_URL = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe"
New-Item -Path $TOOLS_DIR -Type directory | out-null $PACKAGES_CONFIG = Join-Path $TOOLS_DIR "packages.config"
} $PACKAGES_CONFIG_MD5 = Join-Path $TOOLS_DIR "packages.config.md5sum"
$ADDINS_PACKAGES_CONFIG = Join-Path $ADDINS_DIR "packages.config"
# Make sure that packages.config exist. $MODULES_PACKAGES_CONFIG = Join-Path $MODULES_DIR "packages.config"
if (!(Test-Path $PACKAGES_CONFIG)) {
Write-Verbose -Message "Downloading packages.config..." # Make sure tools folder exists
try { if ((Test-Path $PSScriptRoot) -and !(Test-Path $TOOLS_DIR)) {
$wc = GetProxyEnabledWebClient Write-Verbose -Message "Creating tools directory..."
$wc.DownloadFile("https://cakebuild.net/download/bootstrapper/packages", $PACKAGES_CONFIG) } catch { New-Item -Path $TOOLS_DIR -Type directory | out-null
Throw "Could not download packages.config." }
}
} # Make sure that packages.config exist.
if (!(Test-Path $PACKAGES_CONFIG)) {
# Try find NuGet.exe in path if not exists Write-Verbose -Message "Downloading packages.config..."
if (!(Test-Path $NUGET_EXE)) { try {
Write-Verbose -Message "Trying to find nuget.exe in PATH..." $wc = GetProxyEnabledWebClient
$existingPaths = $Env:Path -Split ';' | Where-Object { (![string]::IsNullOrEmpty($_)) -and (Test-Path $_ -PathType Container) } $wc.DownloadFile("https://cakebuild.net/download/bootstrapper/packages", $PACKAGES_CONFIG)
$NUGET_EXE_IN_PATH = Get-ChildItem -Path $existingPaths -Filter "nuget.exe" | Select -First 1 } catch {
if ($NUGET_EXE_IN_PATH -ne $null -and (Test-Path $NUGET_EXE_IN_PATH.FullName)) { Throw "Could not download packages.config."
Write-Verbose -Message "Found in PATH at $($NUGET_EXE_IN_PATH.FullName)." }
$NUGET_EXE = $NUGET_EXE_IN_PATH.FullName }
}
} # Try find NuGet.exe in path if not exists
if (!(Test-Path $NUGET_EXE)) {
# Try download NuGet.exe if not exists Write-Verbose -Message "Trying to find nuget.exe in PATH..."
if (!(Test-Path $NUGET_EXE)) { $existingPaths = $Env:Path -Split ';' | Where-Object { (![string]::IsNullOrEmpty($_)) -and (Test-Path $_ -PathType Container) }
Write-Verbose -Message "Downloading NuGet.exe..." $NUGET_EXE_IN_PATH = Get-ChildItem -Path $existingPaths -Filter "nuget.exe" | Select -First 1
try { if ($NUGET_EXE_IN_PATH -ne $null -and (Test-Path $NUGET_EXE_IN_PATH.FullName)) {
$wc = GetProxyEnabledWebClient Write-Verbose -Message "Found in PATH at $($NUGET_EXE_IN_PATH.FullName)."
$wc.DownloadFile($NUGET_URL, $NUGET_EXE) $NUGET_EXE = $NUGET_EXE_IN_PATH.FullName
} catch { }
Throw "Could not download NuGet.exe." }
}
} # Try download NuGet.exe if not exists
if (!(Test-Path $NUGET_EXE)) {
# Save nuget.exe path to environment to be available to child processed Write-Verbose -Message "Downloading NuGet.exe..."
$ENV:NUGET_EXE = $NUGET_EXE try {
$wc = GetProxyEnabledWebClient
# Restore tools from NuGet? $wc.DownloadFile($NUGET_URL, $NUGET_EXE)
if(-Not $SkipToolPackageRestore.IsPresent) { } catch {
Push-Location Throw "Could not download NuGet.exe."
Set-Location $TOOLS_DIR }
}
# Check for changes in packages.config and remove installed tools if true.
[string] $md5Hash = MD5HashFile($PACKAGES_CONFIG) # Save nuget.exe path to environment to be available to child processed
if((!(Test-Path $PACKAGES_CONFIG_MD5)) -Or $ENV:NUGET_EXE = $NUGET_EXE
($md5Hash -ne (Get-Content $PACKAGES_CONFIG_MD5 ))) {
Write-Verbose -Message "Missing or changed package.config hash..." # Restore tools from NuGet?
Get-ChildItem -Exclude packages.config,nuget.exe,Cake.Bakery | if(-Not $SkipToolPackageRestore.IsPresent) {
Remove-Item -Recurse Push-Location
} Set-Location $TOOLS_DIR
Write-Verbose -Message "Restoring tools from NuGet..." # Check for changes in packages.config and remove installed tools if true.
$NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$TOOLS_DIR`"" [string] $md5Hash = MD5HashFile($PACKAGES_CONFIG)
if((!(Test-Path $PACKAGES_CONFIG_MD5)) -Or
if ($LASTEXITCODE -ne 0) { ($md5Hash -ne (Get-Content $PACKAGES_CONFIG_MD5 ))) {
Throw "An error occurred while restoring NuGet tools." Write-Verbose -Message "Missing or changed package.config hash..."
} Get-ChildItem -Exclude packages.config,nuget.exe,Cake.Bakery |
else Remove-Item -Recurse
{ }
$md5Hash | Out-File $PACKAGES_CONFIG_MD5 -Encoding "ASCII"
} Write-Verbose -Message "Restoring tools from NuGet..."
Write-Verbose -Message ($NuGetOutput | out-string) $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$TOOLS_DIR`""
Pop-Location if ($LASTEXITCODE -ne 0) {
} Throw "An error occurred while restoring NuGet tools."
}
# Restore addins from NuGet else
if (Test-Path $ADDINS_PACKAGES_CONFIG) { {
Push-Location $md5Hash | Out-File $PACKAGES_CONFIG_MD5 -Encoding "ASCII"
Set-Location $ADDINS_DIR }
Write-Verbose -Message ($NuGetOutput | out-string)
Write-Verbose -Message "Restoring addins from NuGet..."
$NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$ADDINS_DIR`"" Pop-Location
}
if ($LASTEXITCODE -ne 0) {
Throw "An error occurred while restoring NuGet addins." # Restore addins from NuGet
} if (Test-Path $ADDINS_PACKAGES_CONFIG) {
Push-Location
Write-Verbose -Message ($NuGetOutput | out-string) Set-Location $ADDINS_DIR
Pop-Location Write-Verbose -Message "Restoring addins from NuGet..."
} $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$ADDINS_DIR`""
# Restore modules from NuGet if ($LASTEXITCODE -ne 0) {
if (Test-Path $MODULES_PACKAGES_CONFIG) { Throw "An error occurred while restoring NuGet addins."
Push-Location }
Set-Location $MODULES_DIR
Write-Verbose -Message ($NuGetOutput | out-string)
Write-Verbose -Message "Restoring modules from NuGet..."
$NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$MODULES_DIR`"" Pop-Location
}
if ($LASTEXITCODE -ne 0) {
Throw "An error occurred while restoring NuGet modules." # Restore modules from NuGet
} if (Test-Path $MODULES_PACKAGES_CONFIG) {
Push-Location
Write-Verbose -Message ($NuGetOutput | out-string) Set-Location $MODULES_DIR
Pop-Location Write-Verbose -Message "Restoring modules from NuGet..."
} $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$MODULES_DIR`""
# Make sure that Cake has been installed. if ($LASTEXITCODE -ne 0) {
if (!(Test-Path $CAKE_EXE)) { Throw "An error occurred while restoring NuGet modules."
Throw "Could not find Cake.exe at $CAKE_EXE" }
}
Write-Verbose -Message ($NuGetOutput | out-string)
Pop-Location
# Build Cake arguments }
$cakeArguments = @("$Script");
if ($Target) { $cakeArguments += "-target=$Target" } # Make sure that Cake has been installed.
if ($Configuration) { $cakeArguments += "-configuration=$Configuration" } if (!(Test-Path $CAKE_EXE)) {
if ($Verbosity) { $cakeArguments += "-verbosity=$Verbosity" } Throw "Could not find Cake.exe at $CAKE_EXE"
if ($ShowDescription) { $cakeArguments += "-showdescription" } }
if ($DryRun) { $cakeArguments += "-dryrun" }
if ($Experimental) { $cakeArguments += "-experimental" }
if ($Mono) { $cakeArguments += "-mono" }
$cakeArguments += $ScriptArgs # Build Cake arguments
$cakeArguments = @("$Script");
# Start Cake if ($Target) { $cakeArguments += "-target=$Target" }
Write-Host "Running build script..." if ($Configuration) { $cakeArguments += "-configuration=$Configuration" }
&$CAKE_EXE $cakeArguments if ($Verbosity) { $cakeArguments += "-verbosity=$Verbosity" }
exit $LASTEXITCODE if ($ShowDescription) { $cakeArguments += "-showdescription" }
if ($DryRun) { $cakeArguments += "-dryrun" }
$cakeArguments += $ScriptArgs
# Start Cake
Write-Host "Running build script..."
&$CAKE_EXE $cakeArguments
exit $LASTEXITCODE

View File

@ -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
View 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" ]

View File

@ -1,23 +1,23 @@
Release process Release process
=============== ===============
This section defines the release process for the maintainers of the project. This section defines the release process for the maintainers of the project.
* Merge pull requests to the `release` branch. * Merge pull requests to the `release` branch.
* Every commit pushed to the Origin repo will kick off the `ocelot-build <https://ci.appveyor.com/project/TomPallister/ocelot-fcfpb>`_ project in AppVeyor. This performs the same tasks as the command line build, and in addition pushes the packages to the unstable nuget feed. * Every commit pushed to the Origin repo will kick off the `ocelot-build <https://ci.appveyor.com/project/TomPallister/ocelot-fcfpb>`_ project in AppVeyor. This performs the same tasks as the command line build, and in addition pushes the packages to the unstable nuget feed.
* When you're ready for a release, create a release branch. You'll probably want to update the committed `./ReleaseNotes.md` based on the contents of the equivalent file in the `./artifacts` directory. * When you're ready for a release, create a release branch. You'll probably want to update the committed `./ReleaseNotes.md` based on the contents of the equivalent file in the `./artifacts` directory.
* When the `release` branch has built successfully in Appveyor, select the build and then Deploy to the `GitHub Release` environment. This will create a new release in GitHub. * 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.
* The `ocelot-release <https://ci.appveyor.com/project/TomPallister/ocelot-ayj4w>`_ project will detect the newly created tag and kick off the release process. This will download the artifacts from GitHub, and publish the packages to the stable nuget feed. * The `ocelot-release <https://ci.appveyor.com/project/TomPallister/ocelot-ayj4w>`_ project will detect the newly created tag and kick off the release process. This will download the artifacts from GitHub, and publish the packages to the stable nuget feed.
* When you have a final stable release build, merge the `release` branch into `master` and `develop`. Deploy the master branch to github and following the full release process as described above. Don't forget to uncheck the "This is a pre-release" checkbox in GitHub before publishing. * When you have a final stable release build, merge the `release` branch into `master` and `develop`. Deploy the master branch to github and following the full release process as described above. Don't forget to uncheck the "This is a pre-release" checkbox in GitHub before publishing.
* Note - because the release builds are initiated by tagging a commit, if for some reason a release build fails in AppVeyor you'll need to delete the tag from the repo and republish the release in GitHub. * Note - because the release builds are initiated by tagging a commit, if for some reason a release build fails in AppVeyor you'll need to delete the tag from the repo and republish the release in GitHub.

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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
^^^^^ ^^^^^

View File

@ -1,150 +1,150 @@
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
^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^
This feature was requestes in `GitHub #313 <https://github.com/ThreeMammals/Ocelot/issues/313>`_. This feature was requestes in `GitHub #313 <https://github.com/ThreeMammals/Ocelot/issues/313>`_.
If you want to add a header to your upstream request please add the following to a ReRoute in your ocelot.json: If you want to add a header to your upstream request please add the following to a ReRoute in your ocelot.json:
.. code-block:: json .. code-block:: json
"UpstreamHeaderTransform": { "UpstreamHeaderTransform": {
"Uncle": "Bob" "Uncle": "Bob"
} }
In the example above a header with the key Uncle and value Bob would be send to to the upstream service. In the example above a header with the key Uncle and value Bob would be send to to the upstream service.
Placeholders are supported too (see below). 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..
.. code-block:: json .. code-block:: json
"DownstreamHeaderTransform": { "DownstreamHeaderTransform": {
"Uncle": "Bob" "Uncle": "Bob"
}, },
In the example above a header with the key Uncle and value Bob would be returned by Ocelot when requesting the specific ReRoute. In the example above a header with the key Uncle and value Bob would be returned by Ocelot when requesting the specific ReRoute.
If you want to return the Butterfly APM trace id then do something like the following.. If you want to return the Butterfly APM trace id then do something like the following..
.. code-block:: json .. code-block:: json
"DownstreamHeaderTransform": { "DownstreamHeaderTransform": {
"AnyKey": "{TraceId}" "AnyKey": "{TraceId}"
}, },
Find and Replace Find and Replace
^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^
In order to transform a header first we specify the header key and then the type of transform we want e.g. In order to transform a header first we specify the header key and then the type of transform we want e.g.
.. code-block:: json .. code-block:: json
"Test": "http://www.bbc.co.uk/, http://ocelot.com/" "Test": "http://www.bbc.co.uk/, http://ocelot.com/"
The key is "Test" and the value is "http://www.bbc.co.uk/, http://ocelot.com/". The value is saying replace http://www.bbc.co.uk/ with http://ocelot.com/. The syntax is {find}, {replace}. Hopefully pretty simple. There are examples below that explain more. The key is "Test" and the value is "http://www.bbc.co.uk/, http://ocelot.com/". The value is saying replace http://www.bbc.co.uk/ with http://ocelot.com/. The syntax is {find}, {replace}. Hopefully pretty simple. There are examples below that explain more.
Pre Downstream Request Pre Downstream Request
^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^
Add the following to a ReRoute in ocelot.json in order to replace http://www.bbc.co.uk/ with http://ocelot.com/. This header will be changed before the request downstream and will be sent to the downstream server. Add the following to a ReRoute in ocelot.json in order to replace http://www.bbc.co.uk/ with http://ocelot.com/. This header will be changed before the request downstream and will be sent to the downstream server.
.. code-block:: json .. code-block:: json
"UpstreamHeaderTransform": { "UpstreamHeaderTransform": {
"Test": "http://www.bbc.co.uk/, http://ocelot.com/" "Test": "http://www.bbc.co.uk/, http://ocelot.com/"
}, },
Post Downstream Request Post Downstream Request
^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^
Add the following to a ReRoute in ocelot.json in order to replace http://www.bbc.co.uk/ with http://ocelot.com/. This transformation will take place after Ocelot has received the response from the downstream service. Add the following to a ReRoute in ocelot.json in order to replace http://www.bbc.co.uk/ with http://ocelot.com/. This transformation will take place after Ocelot has received the response from the downstream service.
.. code-block:: json .. code-block:: json
"DownstreamHeaderTransform": { "DownstreamHeaderTransform": {
"Test": "http://www.bbc.co.uk/, http://ocelot.com/" "Test": "http://www.bbc.co.uk/, http://ocelot.com/"
}, },
Placeholders Placeholders
^^^^^^^^^^^^ ^^^^^^^^^^^^
Ocelot allows placeholders that can be used in header transformation. Ocelot allows placeholders that can be used in header transformation.
{RemoteIpAddress} - This will find the clients IP address using _httpContextAccessor.HttpContext.Connection.RemoteIpAddress.ToString() so you will get back some IP. {RemoteIpAddress} - This will find the clients IP address using _httpContextAccessor.HttpContext.Connection.RemoteIpAddress.ToString() so you will get back some IP.
{BaseUrl} - This will use Ocelot's base url e.g. http://localhost:5000 as its value. {BaseUrl} - This will use Ocelot's base url e.g. http://localhost:5000 as its value.
{DownstreamBaseUrl} - This will use the downstream services base url e.g. http://localhost:5000 as its value. This only works for DownstreamHeaderTransform at the moment. {DownstreamBaseUrl} - This will use the downstream services base url e.g. http://localhost:5000 as its value. This only works for DownstreamHeaderTransform at the moment.
{TraceId} - This will use the Butterfly APM Trace Id. This only works for DownstreamHeaderTransform at the moment. {TraceId} - This will use the Butterfly APM Trace Id. This only works for DownstreamHeaderTransform at the moment.
Handling 302 Redirects Handling 302 Redirects
^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^
Ocelot will by default automatically follow redirects however if you want to return the location header to the client you might want to change the location to be Ocelot not the downstream service. Ocelot allows this with the following configuration. Ocelot will by default automatically follow redirects however if you want to return the location header to the client you might want to change the location to be Ocelot not the downstream service. Ocelot allows this with the following configuration.
.. code-block:: json .. code-block:: json
"DownstreamHeaderTransform": { "DownstreamHeaderTransform": {
"Location": "http://www.bbc.co.uk/, http://ocelot.com/" "Location": "http://www.bbc.co.uk/, http://ocelot.com/"
}, },
"HttpHandlerOptions": { "HttpHandlerOptions": {
"AllowAutoRedirect": false, "AllowAutoRedirect": false,
}, },
or you could use the BaseUrl placeholder. or you could use the BaseUrl placeholder.
.. code-block:: json .. code-block:: json
"DownstreamHeaderTransform": { "DownstreamHeaderTransform": {
"Location": "http://localhost:6773, {BaseUrl}" "Location": "http://localhost:6773, {BaseUrl}"
}, },
"HttpHandlerOptions": { "HttpHandlerOptions": {
"AllowAutoRedirect": false, "AllowAutoRedirect": false,
}, },
finally if you are using a load balancer with Ocelot you will get multiple downstream base urls so the above would not work. In this case you can do the following. finally if you are using a load balancer with Ocelot you will get multiple downstream base urls so the above would not work. In this case you can do the following.
.. code-block:: json .. code-block:: json
"DownstreamHeaderTransform": { "DownstreamHeaderTransform": {
"Location": "{DownstreamBaseUrl}, {BaseUrl}" "Location": "{DownstreamBaseUrl}, {BaseUrl}"
}, },
"HttpHandlerOptions": { "HttpHandlerOptions": {
"AllowAutoRedirect": false, "AllowAutoRedirect": false,
}, },
X-Forwarded-For X-Forwarded-For
^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^
An example of using {RemoteIpAddress} placeholder... An example of using {RemoteIpAddress} placeholder...
.. code-block:: json .. code-block:: json
"UpstreamHeaderTransform": { "UpstreamHeaderTransform": {
"X-Forwarded-For": "{RemoteIpAddress}" "X-Forwarded-For": "{RemoteIpAddress}"
} }
Future Future
^^^^^^ ^^^^^^
Ideally this feature would be able to support the fact that a header can have multiple values. At the moment it just assumes one. Ideally this feature would be able to support the fact that a header can have multiple values. At the moment it just assumes one.
It would also be nice if it could multi find and replace e.g. It would also be nice if it could multi find and replace e.g.
.. code-block:: json .. code-block:: json
"DownstreamHeaderTransform": { "DownstreamHeaderTransform": {
"Location": "[{one,one},{two,two}" "Location": "[{one,one},{two,two}"
}, },
"HttpHandlerOptions": { "HttpHandlerOptions": {
"AllowAutoRedirect": false, "AllowAutoRedirect": false,
}, },
If anyone wants to have a go at this please help yourself!! If anyone wants to have a go at this please help yourself!!

View File

@ -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.

View File

@ -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..
@ -45,4 +45,4 @@ You can set the TimeoutValue in isoldation of the ExceptionsAllowedBeforeBreakin
There is no point setting the other two in isolation as they affect each other :) There is no point setting the other two in isolation as they affect each other :)
If you do not add a QoS section QoS will not be used however Ocelot will default to a 90 second timeout If you do not add a QoS section QoS will not be used however Ocelot will default to a 90 second timeout
on all downstream requests. If someone needs this to be configurable open an issue. on all downstream requests. If someone needs this to be configurable open an issue.

View File

@ -1,49 +1,49 @@
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).
To get Raft support you must first install the Ocelot Rafty package. To get Raft support you must first install the Ocelot Rafty package.
``Install-Package Ocelot.Provider.Rafty`` ``Install-Package Ocelot.Provider.Rafty``
Then you must make the following changes to your Startup.cs / Program.cs. Then you must make the following changes to your Startup.cs / Program.cs.
.. code-block:: csharp .. code-block:: csharp
public virtual void ConfigureServices(IServiceCollection services) public virtual void ConfigureServices(IServiceCollection services)
{ {
services services
.AddOcelot() .AddOcelot()
.AddAdministration("/administration", "secret") .AddAdministration("/administration", "secret")
.AddRafty(); .AddRafty();
} }
In addition to this you must add a file called peers.json to your main project and it will look as follows In addition to this you must add a file called peers.json to your main project and it will look as follows
.. code-block:: json .. code-block:: json
{ {
"Peers": [{ "Peers": [{
"HostAndPort": "http://localhost:5000" "HostAndPort": "http://localhost:5000"
}, },
{ {
"HostAndPort": "http://localhost:5002" "HostAndPort": "http://localhost:5002"
}, },
{ {
"HostAndPort": "http://localhost:5003" "HostAndPort": "http://localhost:5003"
}, },
{ {
"HostAndPort": "http://localhost:5004" "HostAndPort": "http://localhost:5004"
}, },
{ {
"HostAndPort": "http://localhost:5001" "HostAndPort": "http://localhost:5001"
} }
] ]
} }
Each instance of Ocelot must have it's address in the array so that they can communicate using Rafty. Each instance of Ocelot must have it's address in the array so that they can communicate using Rafty.
Once you have made these configuration changes you must deploy and start each instance of Ocelot using the addresses in the peers.json file. The servers should then start communicating with each other! You can test if everything is working by posting a configuration update and checking it has replicated to all servers by getting their configuration. Once you have made these configuration changes you must deploy and start each instance of Ocelot using the addresses in the peers.json file. The servers should then start communicating with each other! You can test if everything is working by posting a configuration update and checking it has replicated to all servers by getting their configuration.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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
@ -40,4 +40,4 @@ package doesnt reload swagger.json if it changes during runtime. Ocelot's config
information would not match. Unless I rolled my own Swagger implementation. information would not match. Unless I rolled my own Swagger implementation.
If the user wants something to easily test against the Ocelot API then I suggest using Postman as a simple way to do this. It might If the user wants something to easily test against the Ocelot API then I suggest using Postman as a simple way to do this. It might
even be possible to write something that maps ocelot.json to the postman json spec. However I don't intend to do this. even be possible to write something that maps ocelot.json to the postman json spec. However I don't intend to do this.

View File

@ -1,6 +1,6 @@
{ {
"projects": [ "src", "test" ], "projects": [ "src", "test" ],
"sdk": { "sdk": {
"version": "2.1.301" "version": "2.1.500"
} }
} }

View 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>

View 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": [
""
]
}
}
]
}

View 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();
}
}
}

View 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"
}
}
}
}

View 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
}
}
}

View File

@ -0,0 +1,10 @@
{
"Logging": {
"IncludeScopes": true,
"LogLevel": {
"Default": "Trace",
"System": "Trace",
"Microsoft": "Trace"
}
}
}

View 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"
}
}

View 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="}}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View 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; }
}
}

View 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; }
}
}

View File

@ -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
);
}
}
}

View File

@ -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;
};
}
}

View 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>

View 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 }
}
};
}
}
}

View 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")]

View 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 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>

View 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;
}
}
}

View 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);
}
}
}

View 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")]

View 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);
}
}
}

View 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;
}
});
}
}
}

View File

@ -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}"));
}
}
}

View File

@ -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;
}
}
}

View 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;
};
}
}

View 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; }
}
}

View File

@ -0,0 +1,9 @@
namespace Ocelot.Provider.Consul
{
using global::Consul;
public interface IConsulClientFactory
{
IConsulClient Get(ConsulRegistryConfiguration config);
}
}

View 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>

View 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;
}
}
}

View File

@ -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();
}
}
}

View 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")]

View File

@ -0,0 +1,12 @@
namespace Ocelot.Provider.Consul
{
using Errors;
public class UnableToSetConfigInConsulError : Error
{
public UnableToSetConfigInConsulError(string s)
: base(s, OcelotErrorCode.UnknownError)
{
}
}
}

View 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);
}
}
}

View File

@ -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";
}
}
}

View 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;
};
}
}

View 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>

View 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;
}
}
}

View 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")]

View 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; }
}
}

View 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>

View 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;
}
}
}

View File

@ -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;
}
}
}
}

View 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; }
}
}

View 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")]

View 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)
{
}
}
}

View 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; }
}
}

View 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; }
}
}

View File

@ -0,0 +1,7 @@
namespace Ocelot.Provider.Rafty
{
public class FilePeer
{
public string HostAndPort { get; set; }
}
}

View 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; }
}
}

View 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;
}
}
}

View 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);
}
}
}

View 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>

View File

@ -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;
}
}
}

View 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);
}
}
}

View 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")]

View 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;
}
}
}
}

View 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();
}
}
}

View File

@ -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();
}
}
}

View 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();
}
}
}

View File

@ -0,0 +1,11 @@
namespace Ocelot.Provider.Rafty
{
using Errors;
public class UnableToSaveAcceptCommand : Error
{
public UnableToSaveAcceptCommand(string message)
: base(message, OcelotErrorCode.UnknownError)
{
}
}
}

View 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; }
}
}

View 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;
}
}
}

View 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>

View 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;
}
}
}

View File

@ -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

View File

@ -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)
{ {

View File

@ -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)

View File

@ -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>();

View File

@ -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)
{ {

View File

@ -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)
{ {
} }
} }

View File

@ -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);
}
} }

View File

@ -1,24 +1,23 @@
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 pathTemplate.Pattern.IsMatch(upstreamUrlPath)
return regex.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 pathTemplate.Pattern.IsMatch($"{upstreamUrlPath}{upstreamQueryString}")
return regex.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)); }
} }
} }
}

View File

@ -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)

View File

@ -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