diff --git a/Directory.Build.props b/Directory.Build.props
index 2def6a21..4fe1ec98 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,16 +1,15 @@
-
-
- latest
-
- git
- https://github.com/ThreeMammals/Ocelot
-
- true
-
- true
- snupkg
-
-
-
-
-
+
+
+ latest
+ git
+ https://github.com/ThreeMammals/Ocelot
+
+ true
+
+ true
+ snupkg
+
+
+
+
+
diff --git a/Ocelot.sln b/Ocelot.sln
index 088d7be0..e943d369 100644
--- a/Ocelot.sln
+++ b/Ocelot.sln
@@ -1,146 +1,229 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.27130.2036
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5CFB79B7-C9DC-45A4-9A75-625D92471702}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3FA7C349-DBE8-4904-A2CE-015B8869CE6C}"
- ProjectSection(SolutionItems) = preProject
- .dockerignore = .dockerignore
- .gitignore = .gitignore
- build-and-release-unstable.ps1 = build-and-release-unstable.ps1
- build-and-run-tests.ps1 = build-and-run-tests.ps1
- build.cake = build.cake
- build.ps1 = build.ps1
- codeanalysis.ruleset = codeanalysis.ruleset
- docker-compose.yaml = docker-compose.yaml
- Dockerfile = Dockerfile
- GitVersion.yml = GitVersion.yml
- global.json = global.json
- LICENSE.md = LICENSE.md
- README.md = README.md
- release.ps1 = release.ps1
- ReleaseNotes.md = ReleaseNotes.md
- run-acceptance-tests.ps1 = run-acceptance-tests.ps1
- run-benchmarks.ps1 = run-benchmarks.ps1
- run-unit-tests.ps1 = run-unit-tests.ps1
- version.ps1 = version.ps1
- EndProjectSection
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{5B401523-36DA-4491-B73A-7590A26E420B}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot", "src\Ocelot\Ocelot.csproj", "{D6DF4206-0DBA-41D8-884D-C3E08290FDBB}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.UnitTests", "test\Ocelot.UnitTests\Ocelot.UnitTests.csproj", "{54E84F1A-E525-4443-96EC-039CBD50C263}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.AcceptanceTests", "test\Ocelot.AcceptanceTests\Ocelot.AcceptanceTests.csproj", "{F8C224FE-36BE-45F5-9B0E-666D8F4A9B52}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.ManualTest", "test\Ocelot.ManualTest\Ocelot.ManualTest.csproj", "{02BBF4C5-517E-4157-8D21-4B8B9E118B7A}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Benchmarks", "test\Ocelot.Benchmarks\Ocelot.Benchmarks.csproj", "{106B49E6-95F6-4A7B-B81C-96BFA74AF035}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.IntegrationTests", "test\Ocelot.IntegrationTests\Ocelot.IntegrationTests.csproj", "{D4575572-99CA-4530-8737-C296EDA326F8}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Administration", "src\Ocelot.Administration\Ocelot.Administration.csproj", "{F69CEF43-27D2-4940-A47A-FCA879E371BC}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Cache.CacheManager", "src\Ocelot.Cache.CacheManager\Ocelot.Cache.CacheManager.csproj", "{EB9F438F-062E-499F-B6EA-4412BEF6D74C}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Provider.Consul", "src\Ocelot.Provider.Consul\Ocelot.Provider.Consul.csproj", "{02F5AE4D-9C36-4E58-B7C6-012CBBDEFDE0}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Provider.Eureka", "src\Ocelot.Provider.Eureka\Ocelot.Provider.Eureka.csproj", "{9BBD3586-145C-4FA0-91C5-9ED58287D753}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Provider.Polly", "src\Ocelot.Provider.Polly\Ocelot.Provider.Polly.csproj", "{1F6E5DCF-8A2E-4E24-A25D-064362DE8D0E}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Provider.Rafty", "src\Ocelot.Provider.Rafty\Ocelot.Provider.Rafty.csproj", "{AC153C67-EF18-47E6-A230-F0D3CF5F0A98}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Tracing.Butterfly", "src\Ocelot.Tracing.Butterfly\Ocelot.Tracing.Butterfly.csproj", "{6045E23D-669C-4F27-AF8E-8EEE6DB3557F}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ocelot.Provider.Kubernetes", "src\Ocelot.Provider.Kubernetes\Ocelot.Provider.Kubernetes.csproj", "{72C8E528-B4F5-45CE-8A06-CD3787364856}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {D6DF4206-0DBA-41D8-884D-C3E08290FDBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {D6DF4206-0DBA-41D8-884D-C3E08290FDBB}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {D6DF4206-0DBA-41D8-884D-C3E08290FDBB}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {D6DF4206-0DBA-41D8-884D-C3E08290FDBB}.Release|Any CPU.Build.0 = Release|Any CPU
- {54E84F1A-E525-4443-96EC-039CBD50C263}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {54E84F1A-E525-4443-96EC-039CBD50C263}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {54E84F1A-E525-4443-96EC-039CBD50C263}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {54E84F1A-E525-4443-96EC-039CBD50C263}.Release|Any CPU.Build.0 = Release|Any CPU
- {F8C224FE-36BE-45F5-9B0E-666D8F4A9B52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {F8C224FE-36BE-45F5-9B0E-666D8F4A9B52}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {F8C224FE-36BE-45F5-9B0E-666D8F4A9B52}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {F8C224FE-36BE-45F5-9B0E-666D8F4A9B52}.Release|Any CPU.Build.0 = Release|Any CPU
- {02BBF4C5-517E-4157-8D21-4B8B9E118B7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {02BBF4C5-517E-4157-8D21-4B8B9E118B7A}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {02BBF4C5-517E-4157-8D21-4B8B9E118B7A}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {02BBF4C5-517E-4157-8D21-4B8B9E118B7A}.Release|Any CPU.Build.0 = Release|Any CPU
- {106B49E6-95F6-4A7B-B81C-96BFA74AF035}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {106B49E6-95F6-4A7B-B81C-96BFA74AF035}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {106B49E6-95F6-4A7B-B81C-96BFA74AF035}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {106B49E6-95F6-4A7B-B81C-96BFA74AF035}.Release|Any CPU.Build.0 = Release|Any CPU
- {D4575572-99CA-4530-8737-C296EDA326F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {D4575572-99CA-4530-8737-C296EDA326F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {D4575572-99CA-4530-8737-C296EDA326F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {D4575572-99CA-4530-8737-C296EDA326F8}.Release|Any CPU.Build.0 = Release|Any CPU
- {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
- {72C8E528-B4F5-45CE-8A06-CD3787364856}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {72C8E528-B4F5-45CE-8A06-CD3787364856}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {72C8E528-B4F5-45CE-8A06-CD3787364856}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {72C8E528-B4F5-45CE-8A06-CD3787364856}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- 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}
- {72C8E528-B4F5-45CE-8A06-CD3787364856} = {5CFB79B7-C9DC-45A4-9A75-625D92471702}
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {21476EFF-778A-4F97-8A56-D1AF1CEC0C48}
- EndGlobalSection
-EndGlobal
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29613.14
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5CFB79B7-C9DC-45A4-9A75-625D92471702}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3FA7C349-DBE8-4904-A2CE-015B8869CE6C}"
+ ProjectSection(SolutionItems) = preProject
+ .dockerignore = .dockerignore
+ .gitignore = .gitignore
+ build-and-release-unstable.ps1 = build-and-release-unstable.ps1
+ build-and-run-tests.ps1 = build-and-run-tests.ps1
+ build.cake = build.cake
+ build.ps1 = build.ps1
+ codeanalysis.ruleset = codeanalysis.ruleset
+ docker-compose.yaml = docker-compose.yaml
+ Dockerfile = Dockerfile
+ GitVersion.yml = GitVersion.yml
+ global.json = global.json
+ LICENSE.md = LICENSE.md
+ README.md = README.md
+ release.ps1 = release.ps1
+ ReleaseNotes.md = ReleaseNotes.md
+ run-acceptance-tests.ps1 = run-acceptance-tests.ps1
+ run-benchmarks.ps1 = run-benchmarks.ps1
+ run-unit-tests.ps1 = run-unit-tests.ps1
+ version.ps1 = version.ps1
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{5B401523-36DA-4491-B73A-7590A26E420B}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot", "src\Ocelot\Ocelot.csproj", "{D6DF4206-0DBA-41D8-884D-C3E08290FDBB}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.UnitTests", "test\Ocelot.UnitTests\Ocelot.UnitTests.csproj", "{54E84F1A-E525-4443-96EC-039CBD50C263}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.AcceptanceTests", "test\Ocelot.AcceptanceTests\Ocelot.AcceptanceTests.csproj", "{F8C224FE-36BE-45F5-9B0E-666D8F4A9B52}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.ManualTest", "test\Ocelot.ManualTest\Ocelot.ManualTest.csproj", "{02BBF4C5-517E-4157-8D21-4B8B9E118B7A}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Benchmarks", "test\Ocelot.Benchmarks\Ocelot.Benchmarks.csproj", "{106B49E6-95F6-4A7B-B81C-96BFA74AF035}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.IntegrationTests", "test\Ocelot.IntegrationTests\Ocelot.IntegrationTests.csproj", "{D4575572-99CA-4530-8737-C296EDA326F8}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Administration", "src\Ocelot.Administration\Ocelot.Administration.csproj", "{F69CEF43-27D2-4940-A47A-FCA879E371BC}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Cache.CacheManager", "src\Ocelot.Cache.CacheManager\Ocelot.Cache.CacheManager.csproj", "{EB9F438F-062E-499F-B6EA-4412BEF6D74C}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Provider.Consul", "src\Ocelot.Provider.Consul\Ocelot.Provider.Consul.csproj", "{02F5AE4D-9C36-4E58-B7C6-012CBBDEFDE0}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Provider.Eureka", "src\Ocelot.Provider.Eureka\Ocelot.Provider.Eureka.csproj", "{9BBD3586-145C-4FA0-91C5-9ED58287D753}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Provider.Polly", "src\Ocelot.Provider.Polly\Ocelot.Provider.Polly.csproj", "{1F6E5DCF-8A2E-4E24-A25D-064362DE8D0E}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Provider.Rafty", "src\Ocelot.Provider.Rafty\Ocelot.Provider.Rafty.csproj", "{AC153C67-EF18-47E6-A230-F0D3CF5F0A98}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Tracing.Butterfly", "src\Ocelot.Tracing.Butterfly\Ocelot.Tracing.Butterfly.csproj", "{6045E23D-669C-4F27-AF8E-8EEE6DB3557F}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Provider.Kubernetes", "src\Ocelot.Provider.Kubernetes\Ocelot.Provider.Kubernetes.csproj", "{72C8E528-B4F5-45CE-8A06-CD3787364856}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{8FA0CBA0-0338-48EB-B37F-83CA5022237C}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OcelotBasic", "samples\OcelotBasic\OcelotBasic.csproj", "{ED0B3A09-112B-4BA4-82D6-11569BC7A99B}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AdministrationApi", "samples\AdministrationApi\AdministrationApi.csproj", "{B180F8AE-2F8F-44F9-9E5D-FE65B84B742E}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OcelotGraphQL", "samples\OcelotGraphQL\OcelotGraphQL.csproj", "{F43429C3-EC49-464F-9423-9118A36E8FE3}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "eureka", "eureka", "{F1CF6F06-5A34-4A6A-8C19-003A78AB0DCF}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApiGateway", "samples\OcelotEureka\ApiGateway\ApiGateway.csproj", "{48B3DD3C-7F4D-40C1-A104-3BF9EF4ACE29}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DownstreamService", "samples\OcelotEureka\DownstreamService\DownstreamService.csproj", "{32ADF9B3-CBFA-4607-8A8E-1532D90A7197}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "k8s", "k8s", "{4B706988-4817-43A8-ABE1-32A67998C2C8}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApiGateway", "samples\OcelotKube\ApiGateway\ApiGateway.csproj", "{8500055B-2C51-4CF1-A6EE-F05BB3E9BF16}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DownstreamService", "samples\OcelotKube\DownstreamService\DownstreamService.csproj", "{7B319B8C-8155-4779-BD93-5ABD05CA2AB6}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "service-fabric", "service-fabric", "{B412628F-C325-47E1-A8D9-873DE04C8AF5}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OcelotApplicationApiGateway", "samples\OcelotServiceFabric\src\OcelotApplicationApiGateway\OcelotApplicationApiGateway.csproj", "{8E6DAE6E-E9B1-433A-80C3-1E2640FBA590}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OcelotApplicationService", "samples\OcelotServiceFabric\src\OcelotApplicationService\OcelotApplicationService.csproj", "{33BE6D88-F188-4E60-83AC-3C4B94D24675}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "administration", "administration", "{1F1F324D-6EA4-4E63-A6A7-C6053F412F1A}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "basic", "basic", "{ED066001-BAF7-4117-9884-DF591A56347D}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "graphql", "graphql", "{C15CD120-5F8D-41DE-9B21-00E3EA77D6C1}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {D6DF4206-0DBA-41D8-884D-C3E08290FDBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D6DF4206-0DBA-41D8-884D-C3E08290FDBB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D6DF4206-0DBA-41D8-884D-C3E08290FDBB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D6DF4206-0DBA-41D8-884D-C3E08290FDBB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {54E84F1A-E525-4443-96EC-039CBD50C263}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {54E84F1A-E525-4443-96EC-039CBD50C263}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {54E84F1A-E525-4443-96EC-039CBD50C263}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {54E84F1A-E525-4443-96EC-039CBD50C263}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F8C224FE-36BE-45F5-9B0E-666D8F4A9B52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F8C224FE-36BE-45F5-9B0E-666D8F4A9B52}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F8C224FE-36BE-45F5-9B0E-666D8F4A9B52}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F8C224FE-36BE-45F5-9B0E-666D8F4A9B52}.Release|Any CPU.Build.0 = Release|Any CPU
+ {02BBF4C5-517E-4157-8D21-4B8B9E118B7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {02BBF4C5-517E-4157-8D21-4B8B9E118B7A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {02BBF4C5-517E-4157-8D21-4B8B9E118B7A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {02BBF4C5-517E-4157-8D21-4B8B9E118B7A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {106B49E6-95F6-4A7B-B81C-96BFA74AF035}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {106B49E6-95F6-4A7B-B81C-96BFA74AF035}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {106B49E6-95F6-4A7B-B81C-96BFA74AF035}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {106B49E6-95F6-4A7B-B81C-96BFA74AF035}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D4575572-99CA-4530-8737-C296EDA326F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D4575572-99CA-4530-8737-C296EDA326F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D4575572-99CA-4530-8737-C296EDA326F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D4575572-99CA-4530-8737-C296EDA326F8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {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
+ {72C8E528-B4F5-45CE-8A06-CD3787364856}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {72C8E528-B4F5-45CE-8A06-CD3787364856}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {72C8E528-B4F5-45CE-8A06-CD3787364856}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {72C8E528-B4F5-45CE-8A06-CD3787364856}.Release|Any CPU.Build.0 = Release|Any CPU
+ {ED0B3A09-112B-4BA4-82D6-11569BC7A99B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {ED0B3A09-112B-4BA4-82D6-11569BC7A99B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {ED0B3A09-112B-4BA4-82D6-11569BC7A99B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {ED0B3A09-112B-4BA4-82D6-11569BC7A99B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B180F8AE-2F8F-44F9-9E5D-FE65B84B742E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B180F8AE-2F8F-44F9-9E5D-FE65B84B742E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B180F8AE-2F8F-44F9-9E5D-FE65B84B742E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B180F8AE-2F8F-44F9-9E5D-FE65B84B742E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F43429C3-EC49-464F-9423-9118A36E8FE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F43429C3-EC49-464F-9423-9118A36E8FE3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F43429C3-EC49-464F-9423-9118A36E8FE3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F43429C3-EC49-464F-9423-9118A36E8FE3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {48B3DD3C-7F4D-40C1-A104-3BF9EF4ACE29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {48B3DD3C-7F4D-40C1-A104-3BF9EF4ACE29}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {48B3DD3C-7F4D-40C1-A104-3BF9EF4ACE29}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {48B3DD3C-7F4D-40C1-A104-3BF9EF4ACE29}.Release|Any CPU.Build.0 = Release|Any CPU
+ {32ADF9B3-CBFA-4607-8A8E-1532D90A7197}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {32ADF9B3-CBFA-4607-8A8E-1532D90A7197}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {32ADF9B3-CBFA-4607-8A8E-1532D90A7197}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {32ADF9B3-CBFA-4607-8A8E-1532D90A7197}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8500055B-2C51-4CF1-A6EE-F05BB3E9BF16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8500055B-2C51-4CF1-A6EE-F05BB3E9BF16}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8500055B-2C51-4CF1-A6EE-F05BB3E9BF16}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8500055B-2C51-4CF1-A6EE-F05BB3E9BF16}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7B319B8C-8155-4779-BD93-5ABD05CA2AB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7B319B8C-8155-4779-BD93-5ABD05CA2AB6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7B319B8C-8155-4779-BD93-5ABD05CA2AB6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7B319B8C-8155-4779-BD93-5ABD05CA2AB6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8E6DAE6E-E9B1-433A-80C3-1E2640FBA590}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8E6DAE6E-E9B1-433A-80C3-1E2640FBA590}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8E6DAE6E-E9B1-433A-80C3-1E2640FBA590}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8E6DAE6E-E9B1-433A-80C3-1E2640FBA590}.Release|Any CPU.Build.0 = Release|Any CPU
+ {33BE6D88-F188-4E60-83AC-3C4B94D24675}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {33BE6D88-F188-4E60-83AC-3C4B94D24675}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {33BE6D88-F188-4E60-83AC-3C4B94D24675}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {33BE6D88-F188-4E60-83AC-3C4B94D24675}.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}
+ {72C8E528-B4F5-45CE-8A06-CD3787364856} = {5CFB79B7-C9DC-45A4-9A75-625D92471702}
+ {ED0B3A09-112B-4BA4-82D6-11569BC7A99B} = {ED066001-BAF7-4117-9884-DF591A56347D}
+ {B180F8AE-2F8F-44F9-9E5D-FE65B84B742E} = {1F1F324D-6EA4-4E63-A6A7-C6053F412F1A}
+ {F43429C3-EC49-464F-9423-9118A36E8FE3} = {C15CD120-5F8D-41DE-9B21-00E3EA77D6C1}
+ {F1CF6F06-5A34-4A6A-8C19-003A78AB0DCF} = {8FA0CBA0-0338-48EB-B37F-83CA5022237C}
+ {48B3DD3C-7F4D-40C1-A104-3BF9EF4ACE29} = {F1CF6F06-5A34-4A6A-8C19-003A78AB0DCF}
+ {32ADF9B3-CBFA-4607-8A8E-1532D90A7197} = {F1CF6F06-5A34-4A6A-8C19-003A78AB0DCF}
+ {4B706988-4817-43A8-ABE1-32A67998C2C8} = {8FA0CBA0-0338-48EB-B37F-83CA5022237C}
+ {8500055B-2C51-4CF1-A6EE-F05BB3E9BF16} = {4B706988-4817-43A8-ABE1-32A67998C2C8}
+ {7B319B8C-8155-4779-BD93-5ABD05CA2AB6} = {4B706988-4817-43A8-ABE1-32A67998C2C8}
+ {B412628F-C325-47E1-A8D9-873DE04C8AF5} = {8FA0CBA0-0338-48EB-B37F-83CA5022237C}
+ {8E6DAE6E-E9B1-433A-80C3-1E2640FBA590} = {B412628F-C325-47E1-A8D9-873DE04C8AF5}
+ {33BE6D88-F188-4E60-83AC-3C4B94D24675} = {B412628F-C325-47E1-A8D9-873DE04C8AF5}
+ {1F1F324D-6EA4-4E63-A6A7-C6053F412F1A} = {8FA0CBA0-0338-48EB-B37F-83CA5022237C}
+ {ED066001-BAF7-4117-9884-DF591A56347D} = {8FA0CBA0-0338-48EB-B37F-83CA5022237C}
+ {C15CD120-5F8D-41DE-9B21-00E3EA77D6C1} = {8FA0CBA0-0338-48EB-B37F-83CA5022237C}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {21476EFF-778A-4F97-8A56-D1AF1CEC0C48}
+ EndGlobalSection
+EndGlobal
diff --git a/README.md b/README.md
index 4bdb92ad..06a47e6b 100644
--- a/README.md
+++ b/README.md
@@ -54,7 +54,7 @@ A quick list of Ocelot's capabilities for more information see the [documentatio
## How to install
-Ocelot is designed to work with ASP.NET Core only and it targets `netstandard2.0`. This means it can be used anywhere `.NET Standard 2.0` is supported, including `.NET Core 2.1` and `.NET Framework 4.7.2` and up. [This](https://docs.microsoft.com/en-us/dotnet/standard/net-standard) documentation may prove helpful when working out if Ocelot would be suitable for you.
+Ocelot is designed to work with ASP.NET Core only and it targets `netstandard2.0`. This means it can be used anywhere `.NET Standard 2.0` is supported, including `.NET Core 3.1` and `.NET Framework 4.8` and up. [This](https://docs.microsoft.com/en-us/dotnet/standard/net-standard) documentation may prove helpful when working out if Ocelot would be suitable for you.
Install Ocelot and it's dependencies using NuGet.
diff --git a/docs/introduction/gettingstarted.rst b/docs/introduction/gettingstarted.rst
index cf2239fc..e2178c98 100644
--- a/docs/introduction/gettingstarted.rst
+++ b/docs/introduction/gettingstarted.rst
@@ -4,7 +4,7 @@ Getting Started
Ocelot is designed to work with .NET Core only and is currently
built to netstandard2.0. `This `_ documentation may prove helpful when working out if Ocelot would be suitable for you.
-.NET Core 2.1
+.NET Core 3.1
^^^^^^^^^^^^^
**Install NuGet package**
@@ -29,6 +29,30 @@ The following is a very basic ocelot.json. It won't do anything but should get O
}
}
+If you want some example that actually does something use the following:
+
+.. code-block:: json
+
+ {
+ "ReRoutes": [
+ {
+ "DownstreamPathTemplate": "/todos/{id}",
+ "DownstreamScheme": "https",
+ "DownstreamHostAndPorts": [
+ {
+ "Host": "jsonplaceholder.typicode.com",
+ "Port": 443
+ }
+ ],
+ "UpstreamPathTemplate": "/todos/{id}",
+ "UpstreamHttpMethod": [ "Get" ]
+ }
+ ],
+ "GlobalConfiguration": {
+ "BaseUrl": "https://localhost:5000"
+ }
+ }
+
The most important thing to note here is BaseUrl. Ocelot needs to know the URL it is running under
in order to do Header find & replace and for certain administration configurations. When setting this URL it should be the external URL that clients will see Ocelot running on e.g. If you are running containers Ocelot might run on the url http://123.12.1.1:6543 but has something like nginx in front of it responding on https://api.mybusiness.com. In this case the Ocelot base url should be https://api.mybusiness.com.
@@ -42,14 +66,20 @@ AddOcelot() (adds ocelot services), UseOcelot().Wait() (sets up all the Ocelot m
.. code-block:: csharp
+ using System.IO;
+ using Microsoft.AspNetCore.Hosting;
+ using Microsoft.Extensions.Configuration;
+ using Microsoft.Extensions.Hosting;
using Ocelot.DependencyInjection;
using Ocelot.Middleware;
-
- public class Program
+
+ namespace OcelotBasic
{
- public static void Main(string[] args)
+ public class Program
{
- new WebHostBuilder()
+ public static void Main(string[] args)
+ {
+ new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration((hostingContext, config) =>
@@ -74,9 +104,11 @@ AddOcelot() (adds ocelot services), UseOcelot().Wait() (sets up all the Ocelot m
app.UseOcelot().Wait();
})
.Build()
- .Run();
+ .Run();
+ }
}
}
+
**Note:** When using ASP.NET Core 2.2 and you want to use In-Process hosting, replace **.UseIISIntegration()** with **.UseIIS()**, otherwise you'll get startup errors.
diff --git a/samples/AdministrationApi/AdministrationApi.csproj b/samples/AdministrationApi/AdministrationApi.csproj
index bb8cf07f..3fcf5dc8 100644
--- a/samples/AdministrationApi/AdministrationApi.csproj
+++ b/samples/AdministrationApi/AdministrationApi.csproj
@@ -1,13 +1,13 @@
-
-
- netcoreapp2.1
-
-
-
-
-
-
-
-
-
+
+
+ netcoreapp3.1
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/OcelotBasic/OcelotBasic.csproj b/samples/OcelotBasic/OcelotBasic.csproj
new file mode 100644
index 00000000..5e290206
--- /dev/null
+++ b/samples/OcelotBasic/OcelotBasic.csproj
@@ -0,0 +1,16 @@
+
+
+
+ netcoreapp3.1
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/OcelotBasic/Program.cs b/samples/OcelotBasic/Program.cs
new file mode 100644
index 00000000..c0d8bed6
--- /dev/null
+++ b/samples/OcelotBasic/Program.cs
@@ -0,0 +1,42 @@
+using System.IO;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Hosting;
+using Ocelot.DependencyInjection;
+using Ocelot.Middleware;
+
+namespace OcelotBasic
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ new WebHostBuilder()
+ .UseKestrel()
+ .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();
+ })
+ .ConfigureLogging((hostingContext, logging) =>
+ {
+ //add your logging
+ })
+ .UseIISIntegration()
+ .Configure(app =>
+ {
+ app.UseOcelot().Wait();
+ })
+ .Build()
+ .Run();
+ }
+ }
+}
diff --git a/samples/OcelotBasic/appsettings.Development.json b/samples/OcelotBasic/appsettings.Development.json
new file mode 100644
index 00000000..dba68eb1
--- /dev/null
+++ b/samples/OcelotBasic/appsettings.Development.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ }
+}
diff --git a/samples/OcelotBasic/appsettings.json b/samples/OcelotBasic/appsettings.json
new file mode 100644
index 00000000..81ff8777
--- /dev/null
+++ b/samples/OcelotBasic/appsettings.json
@@ -0,0 +1,10 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/samples/OcelotBasic/ocelot.json b/samples/OcelotBasic/ocelot.json
new file mode 100644
index 00000000..7a1759ff
--- /dev/null
+++ b/samples/OcelotBasic/ocelot.json
@@ -0,0 +1,21 @@
+{
+ "ReRoutes": [
+ {
+ "DownstreamPathTemplate": "/todos/{id}",
+ "DownstreamScheme": "https",
+ "DownstreamHostAndPorts": [
+ {
+ "Host": "jsonplaceholder.typicode.com",
+ "Port": 443
+ }
+ ],
+ "UpstreamPathTemplate": "/posts/{id}",
+ "UpstreamHttpMethod": [
+ "Get"
+ ]
+ }
+ ],
+ "GlobalConfiguration": {
+ "BaseUrl": "https://localhost:5000"
+ }
+}
\ No newline at end of file
diff --git a/samples/OcelotEureka/ApiGateway/ApiGateway.csproj b/samples/OcelotEureka/ApiGateway/ApiGateway.csproj
index a1fe22dd..a257c980 100644
--- a/samples/OcelotEureka/ApiGateway/ApiGateway.csproj
+++ b/samples/OcelotEureka/ApiGateway/ApiGateway.csproj
@@ -1,24 +1,23 @@
-
-
-
- netcoreapp2.2
-
-
-
-
-
-
-
-
- PreserveNewest
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ netcoreapp3.1
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/OcelotEureka/DownstreamService/DownstreamService.csproj b/samples/OcelotEureka/DownstreamService/DownstreamService.csproj
index 3d5689a5..209fa6d2 100644
--- a/samples/OcelotEureka/DownstreamService/DownstreamService.csproj
+++ b/samples/OcelotEureka/DownstreamService/DownstreamService.csproj
@@ -1,20 +1,19 @@
-
-
-
- netcoreapp2.2
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ netcoreapp3.1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/OcelotGraphQL/OcelotGraphQL.csproj b/samples/OcelotGraphQL/OcelotGraphQL.csproj
index 6577b719..408d8543 100644
--- a/samples/OcelotGraphQL/OcelotGraphQL.csproj
+++ b/samples/OcelotGraphQL/OcelotGraphQL.csproj
@@ -1,18 +1,17 @@
-
-
- netcoreapp2.0
-
-
-
- PreserveNewest
-
-
-
-
-
-
-
-
-
-
+
+
+ netcoreapp3.1
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/OcelotKube/ApiGateway/ApiGateway.csproj b/samples/OcelotKube/ApiGateway/ApiGateway.csproj
index 81b47303..05f462ab 100644
--- a/samples/OcelotKube/ApiGateway/ApiGateway.csproj
+++ b/samples/OcelotKube/ApiGateway/ApiGateway.csproj
@@ -1,17 +1,17 @@
-
-
-
- netcoreapp2.2
- InProcess
- Linux
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ netcoreapp3.1
+ InProcess
+ Linux
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/OcelotKube/DownstreamService/DownstreamService.csproj b/samples/OcelotKube/DownstreamService/DownstreamService.csproj
index 88fdbfb7..d430c524 100644
--- a/samples/OcelotKube/DownstreamService/DownstreamService.csproj
+++ b/samples/OcelotKube/DownstreamService/DownstreamService.csproj
@@ -1,15 +1,15 @@
-
-
-
- netcoreapp2.2
- InProcess
- Linux
-
-
-
-
-
-
-
-
-
+
+
+
+ netcoreapp3.1
+ InProcess
+ Linux
+
+
+
+
+
+
+
+
+
diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/OcelotApplicationApiGateway.csproj b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/OcelotApplicationApiGateway.csproj
index 08324cbe..186b43f3 100644
--- a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/OcelotApplicationApiGateway.csproj
+++ b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/OcelotApplicationApiGateway.csproj
@@ -1,22 +1,22 @@
-
-
- Stateless Web Service for Stateful OcelotApplicationApiGateway App
-
- netcoreapp2.0
- OcelotApplicationApiGateway
- Exe
- OcelotApplicationApiGateway
-
-
-
- PreserveNewest
-
-
-
-
-
-
-
-
-
+
+
+ Stateless Web Service for Stateful OcelotApplicationApiGateway App
+
+ netcoreapp3.1
+ OcelotApplicationApiGateway
+ Exe
+ OcelotApplicationApiGateway
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationService/OcelotApplicationService.csproj b/samples/OcelotServiceFabric/src/OcelotApplicationService/OcelotApplicationService.csproj
index 34991440..ea45d737 100644
--- a/samples/OcelotServiceFabric/src/OcelotApplicationService/OcelotApplicationService.csproj
+++ b/samples/OcelotServiceFabric/src/OcelotApplicationService/OcelotApplicationService.csproj
@@ -1,21 +1,21 @@
-
-
- Stateless Service Application
-
- Exe
- netcoreapp2.0
- OcelotApplicationService
- OcelotApplicationService
- $(PackageTargetFallback)
-
-
-
-
-
-
-
-
-
-
-
+
+
+ Stateless Service Application
+
+ Exe
+ netcoreapp3.1
+ OcelotApplicationService
+ OcelotApplicationService
+ $(PackageTargetFallback)
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Ocelot.Administration/Ocelot.Administration.csproj b/src/Ocelot.Administration/Ocelot.Administration.csproj
index abf0f33f..c1be0599 100644
--- a/src/Ocelot.Administration/Ocelot.Administration.csproj
+++ b/src/Ocelot.Administration/Ocelot.Administration.csproj
@@ -1,39 +1,39 @@
-
-
- netcoreapp3.0
- true
- Provides Ocelot extensions to use the administration API and IdentityService dependencies that come with it
- Ocelot.Administration
- 0.0.0-dev
- Ocelot.Administration
- Ocelot.Administration
- API Gateway;.NET core
- https://github.com/ThreeMammals/Ocelot.Administration
- https://github.com/ThreeMammals/Ocelot.Administration
- http://threemammals.com/images/ocelot_logo.png
- win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64
- false
- false
- True
- false
- Tom Pallister
- ..\..\codeanalysis.ruleset
-
-
- full
- True
-
-
-
-
-
-
- all
-
-
-
-
-
-
-
-
+
+
+ netcoreapp3.1
+ true
+ Provides Ocelot extensions to use the administration API and IdentityService dependencies that come with it
+ Ocelot.Administration
+ 0.0.0-dev
+ Ocelot.Administration
+ Ocelot.Administration
+ API Gateway;.NET core
+ https://github.com/ThreeMammals/Ocelot.Administration
+ https://github.com/ThreeMammals/Ocelot.Administration
+ http://threemammals.com/images/ocelot_logo.png
+ win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64
+ false
+ false
+ True
+ false
+ Tom Pallister
+ ..\..\codeanalysis.ruleset
+
+
+ full
+ True
+
+
+
+
+
+
+ all
+
+
+
+
+
+
+
+
diff --git a/src/Ocelot.Cache.CacheManager/Ocelot.Cache.CacheManager.csproj b/src/Ocelot.Cache.CacheManager/Ocelot.Cache.CacheManager.csproj
index dab96133..4c575fe5 100644
--- a/src/Ocelot.Cache.CacheManager/Ocelot.Cache.CacheManager.csproj
+++ b/src/Ocelot.Cache.CacheManager/Ocelot.Cache.CacheManager.csproj
@@ -1,39 +1,39 @@
-
-
- netcoreapp3.0
- true
- Provides Ocelot extensions to use CacheManager.Net
- Ocelot.Cache.CacheManager
- 0.0.0-dev
- Ocelot.Cache.CacheManager
- Ocelot.Cache.CacheManager
- API Gateway;.NET core
- https://github.com/ThreeMammals/Ocelot.Cache.CacheManager
- https://github.com/ThreeMammals/Ocelot.Cache.CacheManager
- win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64
- false
- false
- True
- false
- Tom Pallister
- ..\..\codeanalysis.ruleset
-
-
- full
- True
-
-
-
-
-
-
- all
-
-
-
-
-
-
-
-
-
+
+
+ netcoreapp3.1
+ true
+ Provides Ocelot extensions to use CacheManager.Net
+ Ocelot.Cache.CacheManager
+ 0.0.0-dev
+ Ocelot.Cache.CacheManager
+ Ocelot.Cache.CacheManager
+ API Gateway;.NET core
+ https://github.com/ThreeMammals/Ocelot.Cache.CacheManager
+ https://github.com/ThreeMammals/Ocelot.Cache.CacheManager
+ win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64
+ false
+ false
+ True
+ false
+ Tom Pallister
+ ..\..\codeanalysis.ruleset
+
+
+ full
+ True
+
+
+
+
+
+
+ all
+
+
+
+
+
+
+
+
+
diff --git a/src/Ocelot.Provider.Consul/Ocelot.Provider.Consul.csproj b/src/Ocelot.Provider.Consul/Ocelot.Provider.Consul.csproj
index 61df395b..ece85c9b 100644
--- a/src/Ocelot.Provider.Consul/Ocelot.Provider.Consul.csproj
+++ b/src/Ocelot.Provider.Consul/Ocelot.Provider.Consul.csproj
@@ -1,38 +1,38 @@
-
-
- netcoreapp3.0
- true
- Provides Ocelot extensions to use Consul
- Ocelot.Provider.Consul
- 0.0.0-dev
- Ocelot.Provider.Consul
- Ocelot.Provider.Consul
- API Gateway;.NET core
- https://github.com/ThreeMammals/Ocelot.Provider.Consul
- https://github.com/ThreeMammals/Ocelot.Provider.Consul
- http://threemammals.com/images/ocelot_logo.png
- win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64
- false
- false
- True
- false
- Tom Pallister
- ..\..\codeanalysis.ruleset
-
-
- full
- True
-
-
-
-
-
-
-
- all
-
-
-
-
-
-
+
+
+ netcoreapp3.1
+ true
+ Provides Ocelot extensions to use Consul
+ Ocelot.Provider.Consul
+ 0.0.0-dev
+ Ocelot.Provider.Consul
+ Ocelot.Provider.Consul
+ API Gateway;.NET core
+ https://github.com/ThreeMammals/Ocelot.Provider.Consul
+ https://github.com/ThreeMammals/Ocelot.Provider.Consul
+ http://threemammals.com/images/ocelot_logo.png
+ win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64
+ false
+ false
+ True
+ false
+ Tom Pallister
+ ..\..\codeanalysis.ruleset
+
+
+ full
+ True
+
+
+
+
+
+
+
+ all
+
+
+
+
+
+
diff --git a/src/Ocelot.Provider.Eureka/Ocelot.Provider.Eureka.csproj b/src/Ocelot.Provider.Eureka/Ocelot.Provider.Eureka.csproj
index 2254cc2a..587071e0 100644
--- a/src/Ocelot.Provider.Eureka/Ocelot.Provider.Eureka.csproj
+++ b/src/Ocelot.Provider.Eureka/Ocelot.Provider.Eureka.csproj
@@ -1,38 +1,38 @@
-
-
- netcoreapp3.0
- true
- Provides Ocelot extensions to use Eureka
- Ocelot.Provider.Eureka
- 0.0.0-dev
- Ocelot.Provider.Eureka
- Ocelot.Provider.Eureka
- API Gateway;.NET core
- https://github.com/ThreeMammals/Ocelot.Provider.Eureka
- https://github.com/ThreeMammals/Ocelot.Provider.Eureka
- http://threemammals.com/images/ocelot_logo.png
- win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64
- false
- false
- True
- false
- Tom Pallister
- ..\..\codeanalysis.ruleset
-
-
- full
- True
-
-
-
-
-
-
-
- all
-
-
-
-
-
-
+
+
+ netcoreapp3.1
+ true
+ Provides Ocelot extensions to use Eureka
+ Ocelot.Provider.Eureka
+ 0.0.0-dev
+ Ocelot.Provider.Eureka
+ Ocelot.Provider.Eureka
+ API Gateway;.NET core
+ https://github.com/ThreeMammals/Ocelot.Provider.Eureka
+ https://github.com/ThreeMammals/Ocelot.Provider.Eureka
+ http://threemammals.com/images/ocelot_logo.png
+ win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64
+ false
+ false
+ True
+ false
+ Tom Pallister
+ ..\..\codeanalysis.ruleset
+
+
+ full
+ True
+
+
+
+
+
+
+
+ all
+
+
+
+
+
+
diff --git a/src/Ocelot.Provider.Kubernetes/Ocelot.Provider.Kubernetes.csproj b/src/Ocelot.Provider.Kubernetes/Ocelot.Provider.Kubernetes.csproj
index 6dba88d1..980cd909 100644
--- a/src/Ocelot.Provider.Kubernetes/Ocelot.Provider.Kubernetes.csproj
+++ b/src/Ocelot.Provider.Kubernetes/Ocelot.Provider.Kubernetes.csproj
@@ -1,43 +1,43 @@
-
-
-
- netcoreapp3.0
- true
- Ocelot
- Provides Ocelot extensions to use kubernetes
- https://github.com/ThreeMammals/Ocelot
- http://threemammals.com/images/ocelot_logo.png
-
- Ocelot.Provider.Kubernetes
- Ocelot.Provider.Kubernetes
- API Gateway;.NET core
- win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64
- false
- false
- true
- false
- 0.0.0-dev
- geffzhang
-
- ..\..\codeanalysis.ruleset
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ netcoreapp3.1
+ true
+ Ocelot
+ Provides Ocelot extensions to use kubernetes
+ https://github.com/ThreeMammals/Ocelot
+ http://threemammals.com/images/ocelot_logo.png
+
+ Ocelot.Provider.Kubernetes
+ Ocelot.Provider.Kubernetes
+ API Gateway;.NET core
+ win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64
+ false
+ false
+ true
+ false
+ 0.0.0-dev
+ geffzhang
+
+ ..\..\codeanalysis.ruleset
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Ocelot.Provider.Polly/Ocelot.Provider.Polly.csproj b/src/Ocelot.Provider.Polly/Ocelot.Provider.Polly.csproj
index 36143432..447db1da 100644
--- a/src/Ocelot.Provider.Polly/Ocelot.Provider.Polly.csproj
+++ b/src/Ocelot.Provider.Polly/Ocelot.Provider.Polly.csproj
@@ -1,38 +1,38 @@
-
-
- netcoreapp3.0
- true
- Provides Ocelot extensions to use Polly.NET
- Ocelot.Provider.Polly
- 0.0.0-dev
- Ocelot.Provider.Polly
- Ocelot.Provider.Polly
- API Gateway;.NET core
- https://github.com/ThreeMammals/Ocelot.Provider.Polly
- https://github.com/ThreeMammals/Ocelot.Provider.Polly
- http://threemammals.com/images/ocelot_logo.png
- win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64
- false
- false
- True
- false
- Tom Pallister
- ..\..\codeanalysis.ruleset
-
-
- full
- True
-
-
-
-
-
-
- all
-
-
-
-
-
-
-
+
+
+ netcoreapp3.1
+ true
+ Provides Ocelot extensions to use Polly.NET
+ Ocelot.Provider.Polly
+ 0.0.0-dev
+ Ocelot.Provider.Polly
+ Ocelot.Provider.Polly
+ API Gateway;.NET core
+ https://github.com/ThreeMammals/Ocelot.Provider.Polly
+ https://github.com/ThreeMammals/Ocelot.Provider.Polly
+ http://threemammals.com/images/ocelot_logo.png
+ win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64
+ false
+ false
+ True
+ false
+ Tom Pallister
+ ..\..\codeanalysis.ruleset
+
+
+ full
+ True
+
+
+
+
+
+
+ all
+
+
+
+
+
+
+
diff --git a/src/Ocelot.Provider.Rafty/Ocelot.Provider.Rafty.csproj b/src/Ocelot.Provider.Rafty/Ocelot.Provider.Rafty.csproj
index e6699562..8cb2ab42 100644
--- a/src/Ocelot.Provider.Rafty/Ocelot.Provider.Rafty.csproj
+++ b/src/Ocelot.Provider.Rafty/Ocelot.Provider.Rafty.csproj
@@ -1,40 +1,40 @@
-
-
- netcoreapp3.0
- true
- Provides Ocelot extensions to use Rafty
- Ocelot.Provider.Rafty
- 0.0.0-dev
- Ocelot.Provider.Rafty
- Ocelot.Provider.Rafty
- API Gateway;.NET core
- https://github.com/ThreeMammals/Ocelot.Provider.Rafty
- https://github.com/ThreeMammals/Ocelot.Provider.Rafty
- http://threemammals.com/images/ocelot_logo.png
- win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64
- false
- false
- True
- false
- Tom Pallister
- ..\..\codeanalysis.ruleset
-
-
- full
- True
-
-
-
-
-
-
-
-
-
- all
-
-
-
-
-
-
+
+
+ netcoreapp3.1
+ true
+ Provides Ocelot extensions to use Rafty
+ Ocelot.Provider.Rafty
+ 0.0.0-dev
+ Ocelot.Provider.Rafty
+ Ocelot.Provider.Rafty
+ API Gateway;.NET core
+ https://github.com/ThreeMammals/Ocelot.Provider.Rafty
+ https://github.com/ThreeMammals/Ocelot.Provider.Rafty
+ http://threemammals.com/images/ocelot_logo.png
+ win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64
+ false
+ false
+ True
+ false
+ Tom Pallister
+ ..\..\codeanalysis.ruleset
+
+
+ full
+ True
+
+
+
+
+
+
+
+
+
+ all
+
+
+
+
+
+
diff --git a/src/Ocelot.Tracing.Butterfly/Ocelot.Tracing.Butterfly.csproj b/src/Ocelot.Tracing.Butterfly/Ocelot.Tracing.Butterfly.csproj
index 6f2e8141..f2401e02 100644
--- a/src/Ocelot.Tracing.Butterfly/Ocelot.Tracing.Butterfly.csproj
+++ b/src/Ocelot.Tracing.Butterfly/Ocelot.Tracing.Butterfly.csproj
@@ -1,38 +1,38 @@
-
-
-
- netcoreapp3.0
- true
- This package provides methods to integrate Butterfly tracing with Ocelot.
- Ocelot.Tracing.Butterfly
- 0.0.0-dev
- Ocelot.Tracing.Butterfly
- Ocelot.Tracing.Butterfly
- API Gateway;.NET core; Butterfly; ButterflyAPM
- https://github.com/ThreeMammals/Ocelot
- https://github.com/ThreeMammals/Ocelot
- win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64
- false
- false
- True
- false
- Tom Pallister
- ..\..\codeanalysis.ruleset
- Ocelot.Tracing.Butterfly
-
-
- full
- True
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ netcoreapp3.1
+ true
+ This package provides methods to integrate Butterfly tracing with Ocelot.
+ Ocelot.Tracing.Butterfly
+ 0.0.0-dev
+ Ocelot.Tracing.Butterfly
+ Ocelot.Tracing.Butterfly
+ API Gateway;.NET core; Butterfly; ButterflyAPM
+ https://github.com/ThreeMammals/Ocelot
+ https://github.com/ThreeMammals/Ocelot
+ win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64
+ false
+ false
+ True
+ false
+ Tom Pallister
+ ..\..\codeanalysis.ruleset
+ Ocelot.Tracing.Butterfly
+
+
+ full
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Ocelot/Ocelot.csproj b/src/Ocelot/Ocelot.csproj
index fb0d541a..7c290d0a 100644
--- a/src/Ocelot/Ocelot.csproj
+++ b/src/Ocelot/Ocelot.csproj
@@ -1,42 +1,42 @@
-
-
- netcoreapp3.0
- true
- Ocelot is an API Gateway. The project is aimed at people using .NET running a micro services / service orientated architecture that need a unified point of entry into their system. In particular I want easy integration with IdentityServer reference and bearer tokens. reference tokens. Ocelot is a bunch of middlewares in a specific order. Ocelot manipulates the HttpRequest object into a state specified by its configuration until it reaches a request builder middleware where it creates a HttpRequestMessage object which is used to make a request to a downstream service. The middleware that makes the request is the last thing in the Ocelot pipeline. It does not call the next middleware. The response from the downstream service is stored in a per request scoped repository and retrived as the requests goes back up the Ocelot pipeline. There is a piece of middleware that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client. That is basically it with a bunch of other features.
- Ocelot
- 0.0.0-dev
- Ocelot
- Ocelot
- API Gateway;.NET core
- https://github.com/ThreeMammals/Ocelot
- https://github.com/ThreeMammals/Ocelot
- http://threemammals.com/images/ocelot_logo.png
- win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64
- false
- false
- True
- false
- Tom Pallister
- ..\..\codeanalysis.ruleset
-
-
- full
- True
-
-
-
-
-
-
- NU1701
-
-
-
- all
-
-
-
-
-
-
-
+
+
+ netcoreapp3.1
+ true
+ Ocelot is an API Gateway. The project is aimed at people using .NET running a micro services / service orientated architecture that need a unified point of entry into their system. In particular I want easy integration with IdentityServer reference and bearer tokens. reference tokens. Ocelot is a bunch of middlewares in a specific order. Ocelot manipulates the HttpRequest object into a state specified by its configuration until it reaches a request builder middleware where it creates a HttpRequestMessage object which is used to make a request to a downstream service. The middleware that makes the request is the last thing in the Ocelot pipeline. It does not call the next middleware. The response from the downstream service is stored in a per request scoped repository and retrived as the requests goes back up the Ocelot pipeline. There is a piece of middleware that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client. That is basically it with a bunch of other features.
+ Ocelot
+ 0.0.0-dev
+ Ocelot
+ Ocelot
+ API Gateway;.NET core
+ https://github.com/ThreeMammals/Ocelot
+ https://github.com/ThreeMammals/Ocelot
+ http://threemammals.com/images/ocelot_logo.png
+ win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64
+ false
+ false
+ True
+ false
+ Tom Pallister
+ ..\..\codeanalysis.ruleset
+
+
+ full
+ True
+
+
+
+
+
+
+ NU1701
+
+
+
+ all
+
+
+
+
+
+
+
diff --git a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj
index a41029d4..4782eeb5 100644
--- a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj
+++ b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj
@@ -1,74 +1,74 @@
-
-
- 0.0.0-dev
- netcoreapp3.0
- Ocelot.AcceptanceTests
- Exe
- Ocelot.AcceptanceTests
- true
- osx.10.11-x64;osx.10.12-x64;win7-x64;win10-x64
- false
- false
- false
- ..\..\codeanalysis.ruleset
-
-
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- all
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ 0.0.0-dev
+ netcoreapp3.1
+ Ocelot.AcceptanceTests
+ Exe
+ Ocelot.AcceptanceTests
+ true
+ osx.10.11-x64;osx.10.12-x64;win7-x64;win10-x64
+ false
+ false
+ false
+ ..\..\codeanalysis.ruleset
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ all
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/Ocelot.AcceptanceTests/RoutingTests.cs b/test/Ocelot.AcceptanceTests/RoutingTests.cs
index cce30751..15eaf8d9 100644
--- a/test/Ocelot.AcceptanceTests/RoutingTests.cs
+++ b/test/Ocelot.AcceptanceTests/RoutingTests.cs
@@ -1,1057 +1,1057 @@
-namespace Ocelot.AcceptanceTests
-{
- using Microsoft.AspNetCore.Http;
- using Ocelot.Configuration.File;
- using Shouldly;
- using System;
- using System.Collections.Generic;
- using System.Net;
- using TestStack.BDDfy;
- using Xunit;
-
- public class RoutingTests : IDisposable
- {
- private readonly Steps _steps;
- private string _downstreamPath;
- private readonly ServiceHandler _serviceHandler;
-
- public RoutingTests()
- {
- _serviceHandler = new ServiceHandler();
- _steps = new Steps();
- }
-
- [Fact]
- public void should_not_match_forward_slash_in_pattern_before_next_forward_slash()
- {
- var port = 31879;
- var configuration = new FileConfiguration
- {
- ReRoutes = new List
- {
- new FileReRoute
- {
- DownstreamPathTemplate = "/api/v{apiVersion}/cards",
- DownstreamScheme = "http",
- UpstreamPathTemplate = "/api/v{apiVersion}/cards",
- UpstreamHttpMethod = new List { "Get" },
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = port,
- }
- },
- Priority = 1
- }
- }
- };
-
- this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}/", "/api/v1/aaaaaaaaa/cards", 200, "Hello from Laura"))
- .And(x => _steps.GivenThereIsAConfiguration(configuration))
- .And(x => _steps.GivenOcelotIsRunning())
- .When(x => _steps.WhenIGetUrlOnTheApiGateway("/api/v1/aaaaaaaaa/cards"))
- .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound))
- .BDDfy();
- }
-
- [Fact]
- public void should_return_response_404_when_no_configuration_at_all()
- {
- this.Given(x => _steps.GivenThereIsAConfiguration(new FileConfiguration()))
- .And(x => _steps.GivenOcelotIsRunning())
- .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
- .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound))
- .BDDfy();
- }
-
- [Fact]
- public void should_return_response_200_with_forward_slash_and_placeholder_only()
- {
- var configuration = new FileConfiguration
- {
- ReRoutes = new List
- {
- new FileReRoute
- {
- DownstreamPathTemplate = "/{url}",
- DownstreamScheme = "http",
- UpstreamPathTemplate = "/{url}",
- UpstreamHttpMethod = new List { "Get" },
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 57873,
- }
- }
- }
- }
- };
-
- this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:57873/", "/", 200, "Hello from Laura"))
- .And(x => _steps.GivenThereIsAConfiguration(configuration))
- .And(x => _steps.GivenOcelotIsRunning())
- .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
- .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
- .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
- .BDDfy();
- }
-
- [Fact]
- public void should_return_response_200_favouring_forward_slash_with_path_route()
- {
- var configuration = new FileConfiguration
- {
- ReRoutes = new List
- {
- new FileReRoute
- {
- DownstreamPathTemplate = "/{url}",
- DownstreamScheme = "http",
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 51880,
- }
- },
- UpstreamPathTemplate = "/{url}",
- UpstreamHttpMethod = new List { "Get" },
- },
- new FileReRoute
- {
- DownstreamPathTemplate = "/",
- DownstreamScheme = "http",
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 50810,
- }
- },
- UpstreamPathTemplate = "/",
- UpstreamHttpMethod = new List { "Get" },
- }
- }
- };
-
- this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51880/", "/test", 200, "Hello from Laura"))
- .And(x => _steps.GivenThereIsAConfiguration(configuration))
- .And(x => _steps.GivenOcelotIsRunning())
- .When(x => _steps.WhenIGetUrlOnTheApiGateway("/test"))
- .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
- .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
- .BDDfy();
- }
-
- [Fact]
- public void should_return_response_200_favouring_forward_slash()
- {
- var configuration = new FileConfiguration
- {
- ReRoutes = new List
- {
- new FileReRoute
- {
- DownstreamPathTemplate = "/{url}",
- DownstreamScheme = "http",
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 51880,
- }
- },
- UpstreamPathTemplate = "/{url}",
- UpstreamHttpMethod = new List { "Get" },
- },
- new FileReRoute
- {
- DownstreamPathTemplate = "/",
- DownstreamScheme = "http",
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 50810,
- }
- },
- UpstreamPathTemplate = "/",
- UpstreamHttpMethod = new List { "Get" },
- }
- }
- };
-
- this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:50810/", "/", 200, "Hello from Laura"))
- .And(x => _steps.GivenThereIsAConfiguration(configuration))
- .And(x => _steps.GivenOcelotIsRunning())
- .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
- .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
- .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
- .BDDfy();
- }
-
- [Fact]
- public void should_return_response_200_favouring_forward_slash_route_because_it_is_first()
- {
- var configuration = new FileConfiguration
- {
- ReRoutes = new List
- {
- new FileReRoute
- {
- DownstreamPathTemplate = "/",
- DownstreamScheme = "http",
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 51880,
- }
- },
- UpstreamPathTemplate = "/",
- UpstreamHttpMethod = new List { "Get" },
- },
- new FileReRoute
- {
- DownstreamPathTemplate = "/{url}",
- DownstreamScheme = "http",
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 51879,
- }
- },
- UpstreamPathTemplate = "/{url}",
- UpstreamHttpMethod = new List { "Get" },
- }
- }
- };
-
- this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51880/", "/", 200, "Hello from Laura"))
- .And(x => _steps.GivenThereIsAConfiguration(configuration))
- .And(x => _steps.GivenOcelotIsRunning())
- .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
- .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
- .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
- .BDDfy();
- }
-
- [Fact]
- public void should_return_response_200_with_nothing_and_placeholder_only()
- {
- var configuration = new FileConfiguration
- {
- ReRoutes = new List
- {
- new FileReRoute
- {
- DownstreamPathTemplate = "/{url}",
- DownstreamScheme = "http",
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 51005,
- }
- },
- UpstreamPathTemplate = "/{url}",
- UpstreamHttpMethod = new List { "Get" },
- }
- }
- };
-
- this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51005", "/", 200, "Hello from Laura"))
- .And(x => _steps.GivenThereIsAConfiguration(configuration))
- .And(x => _steps.GivenOcelotIsRunning())
- .When(x => _steps.WhenIGetUrlOnTheApiGateway(""))
- .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
- .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
- .BDDfy();
- }
-
- [Fact]
- public void should_return_response_200_with_simple_url()
- {
- var configuration = new FileConfiguration
- {
- ReRoutes = new List
- {
- new FileReRoute
- {
- DownstreamPathTemplate = "/",
- DownstreamScheme = "http",
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 58589,
- }
- },
- UpstreamPathTemplate = "/",
- UpstreamHttpMethod = new List { "Get" },
- }
- }
- };
-
- this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:58589", "/", 200, "Hello from Laura"))
- .And(x => _steps.GivenThereIsAConfiguration(configuration))
- .And(x => _steps.GivenOcelotIsRunning())
- .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
- .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
- .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
- .BDDfy();
- }
-
- [Fact]
- public void bug()
- {
- var configuration = new FileConfiguration
- {
- ReRoutes = new List
- {
- new FileReRoute
- {
- DownstreamPathTemplate = "/api/v1/vacancy",
- DownstreamScheme = "http",
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 51874,
- }
- },
- UpstreamPathTemplate = "/vacancy/",
- UpstreamHttpMethod = new List { "Options", "Put", "Get", "Post", "Delete" },
- LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" }
- },
- new FileReRoute
- {
- DownstreamPathTemplate = "/api/v1/vacancy/{vacancyId}",
- DownstreamScheme = "http",
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 51874,
- }
- },
- UpstreamPathTemplate = "/vacancy/{vacancyId}",
- UpstreamHttpMethod = new List { "Options", "Put", "Get", "Post", "Delete" },
- LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" }
- }
- }
- };
-
- this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51874", "/api/v1/vacancy/1", 200, "Hello from Laura"))
- .And(x => _steps.GivenThereIsAConfiguration(configuration))
- .And(x => _steps.GivenOcelotIsRunning())
- .When(x => _steps.WhenIGetUrlOnTheApiGateway("/vacancy/1"))
- .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
- .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
- .BDDfy();
- }
-
- [Fact]
- public void should_return_response_200_when_path_missing_forward_slash_as_first_char()
- {
- var configuration = new FileConfiguration
- {
- ReRoutes = new List
- {
- new FileReRoute
- {
- DownstreamPathTemplate = "/api/products",
- DownstreamScheme = "http",
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 51206,
- }
- },
- UpstreamPathTemplate = "/",
- UpstreamHttpMethod = new List { "Get" },
- }
- }
- };
-
- this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51206", "/api/products", 200, "Hello from Laura"))
- .And(x => _steps.GivenThereIsAConfiguration(configuration))
- .And(x => _steps.GivenOcelotIsRunning())
- .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
- .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
- .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
- .BDDfy();
- }
-
- [Fact]
- public void should_return_response_200_when_host_has_trailing_slash()
- {
- var configuration = new FileConfiguration
- {
- ReRoutes = new List
- {
- new FileReRoute
- {
- DownstreamPathTemplate = "/api/products",
- DownstreamScheme = "http",
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 51990,
- }
- },
- UpstreamPathTemplate = "/",
- UpstreamHttpMethod = new List { "Get" },
- }
- }
- };
-
- this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51990", "/api/products", 200, "Hello from Laura"))
- .And(x => _steps.GivenThereIsAConfiguration(configuration))
- .And(x => _steps.GivenOcelotIsRunning())
- .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
- .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
- .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
- .BDDfy();
- }
-
- [Fact]
- public void should_return_ok_when_upstream_url_ends_with_forward_slash_but_template_does_not()
- {
- var configuration = new FileConfiguration
- {
- ReRoutes = new List
- {
- new FileReRoute
- {
- DownstreamPathTemplate = "/products",
- DownstreamScheme = "http",
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 58804,
- }
- },
- UpstreamPathTemplate = "/products/",
- UpstreamHttpMethod = new List { "Get" },
- }
- }
- };
-
- this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:58804", "/products", 200, "Hello from Laura"))
- .And(x => _steps.GivenThereIsAConfiguration(configuration))
- .And(x => _steps.GivenOcelotIsRunning())
- .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products"))
- .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
- .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
- .BDDfy();
- }
-
- [Fact]
- public void should_return_not_found_when_upstream_url_ends_with_forward_slash_but_template_does_not()
- {
- var configuration = new FileConfiguration
- {
- ReRoutes = new List
- {
- new FileReRoute
- {
- DownstreamPathTemplate = "/products",
- DownstreamScheme = "http",
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 54015,
- }
- },
- UpstreamPathTemplate = "/products",
- UpstreamHttpMethod = new List { "Get" },
- }
- }
- };
-
- this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:54015", "/products", 200, "Hello from Laura"))
- .And(x => _steps.GivenThereIsAConfiguration(configuration))
- .And(x => _steps.GivenOcelotIsRunning())
- .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products/"))
- .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound))
- .BDDfy();
- }
-
- [Fact]
- public void should_return_not_found()
- {
- var configuration = new FileConfiguration
- {
- ReRoutes = new List
- {
- new FileReRoute
- {
- DownstreamPathTemplate = "/products",
- DownstreamScheme = "http",
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 54072,
- }
- },
- UpstreamPathTemplate = "/products/{productId}",
- UpstreamHttpMethod = new List { "Get" }
- }
- }
- };
-
- this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:54072", "/products", 200, "Hello from Laura"))
- .And(x => _steps.GivenThereIsAConfiguration(configuration))
- .And(x => _steps.GivenOcelotIsRunning())
- .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products/"))
- .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound))
- .BDDfy();
- }
-
- [Fact]
- public void should_return_response_200_with_complex_url()
- {
- var configuration = new FileConfiguration
- {
- ReRoutes = new List
- {
- new FileReRoute
- {
- DownstreamPathTemplate = "/api/products/{productId}",
- DownstreamScheme = "http",
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 55961,
- }
- },
- UpstreamPathTemplate = "/products/{productId}",
- UpstreamHttpMethod = new List { "Get" },
- }
- }
- };
-
- this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:55961", "/api/products/1", 200, "Some Product"))
- .And(x => _steps.GivenThereIsAConfiguration(configuration))
- .And(x => _steps.GivenOcelotIsRunning())
- .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products/1"))
- .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
- .And(x => _steps.ThenTheResponseBodyShouldBe("Some Product"))
- .BDDfy();
- }
-
- [Fact]
- public void should_return_response_200_with_complex_url_that_starts_with_placeholder()
- {
- var configuration = new FileConfiguration
- {
- ReRoutes = new List
- {
- new FileReRoute
- {
- DownstreamPathTemplate = "/api/{variantId}/products/{productId}",
- DownstreamScheme = "http",
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 51116,
- }
- },
- UpstreamPathTemplate = "/{variantId}/products/{productId}",
- UpstreamHttpMethod = new List { "Get" },
- }
- }
- };
-
- this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51116", "/api/23/products/1", 200, "Some Product"))
- .And(x => _steps.GivenThereIsAConfiguration(configuration))
- .And(x => _steps.GivenOcelotIsRunning())
- .When(x => _steps.WhenIGetUrlOnTheApiGateway("23/products/1"))
- .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
- .And(x => _steps.ThenTheResponseBodyShouldBe("Some Product"))
- .BDDfy();
- }
-
- [Fact]
- public void should_not_add_trailing_slash_to_downstream_url()
- {
- var configuration = new FileConfiguration
- {
- ReRoutes = new List
- {
- new FileReRoute
- {
- DownstreamPathTemplate = "/api/products/{productId}",
- DownstreamScheme = "http",
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 51809,
- }
- },
- UpstreamPathTemplate = "/products/{productId}",
- UpstreamHttpMethod = new List { "Get" },
- }
- }
- };
-
- this.Given(x => GivenThereIsAServiceRunningOn("http://localhost:51809", "/api/products/1", 200, "Some Product"))
- .And(x => _steps.GivenThereIsAConfiguration(configuration))
- .And(x => _steps.GivenOcelotIsRunning())
- .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products/1"))
- .Then(x => ThenTheDownstreamUrlPathShouldBe("/api/products/1"))
- .BDDfy();
- }
-
- [Fact]
- public void should_return_response_201_with_simple_url()
- {
- var configuration = new FileConfiguration
- {
- ReRoutes = new List
- {
- new FileReRoute
- {
- DownstreamPathTemplate = "/",
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 56615,
- }
- },
- DownstreamScheme = "http",
- UpstreamPathTemplate = "/",
- UpstreamHttpMethod = new List { "Post" },
- }
- }
- };
-
- this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:56615", "/", 201, string.Empty))
- .And(x => _steps.GivenThereIsAConfiguration(configuration))
- .And(x => _steps.GivenOcelotIsRunning())
- .And(x => _steps.GivenThePostHasContent("postContent"))
- .When(x => _steps.WhenIPostUrlOnTheApiGateway("/"))
- .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Created))
- .BDDfy();
- }
-
- [Fact]
- public void should_return_response_201_with_complex_query_string()
- {
- var configuration = new FileConfiguration
- {
- ReRoutes = new List
- {
- new FileReRoute
- {
- DownstreamPathTemplate = "/newThing",
- UpstreamPathTemplate = "/newThing",
- DownstreamScheme = "http",
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 57771,
- }
- },
- UpstreamHttpMethod = new List { "Get" },
- }
- }
- };
-
- this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:57771", "/newThing", 200, "Hello from Laura"))
- .And(x => _steps.GivenThereIsAConfiguration(configuration))
- .And(x => _steps.GivenOcelotIsRunning())
- .When(x => _steps.WhenIGetUrlOnTheApiGateway("/newThing?DeviceType=IphoneApp&Browser=moonpigIphone&BrowserString=-&CountryCode=123&DeviceName=iPhone 5 (GSM+CDMA)&OperatingSystem=iPhone OS 7.1.2&BrowserVersion=3708AdHoc&ipAddress=-"))
- .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
- .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
- .BDDfy();
- }
-
- [Fact]
- public void should_return_response_200_with_placeholder_for_final_url_path()
- {
- var configuration = new FileConfiguration
- {
- ReRoutes = new List
- {
- new FileReRoute
- {
- DownstreamPathTemplate = "/api/{urlPath}",
- DownstreamScheme = "http",
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 55609,
- }
- },
- UpstreamPathTemplate = "/myApp1Name/api/{urlPath}",
- UpstreamHttpMethod = new List { "Get" },
- }
- }
- };
-
- this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:55609", "/api/products/1", 200, "Some Product"))
- .And(x => _steps.GivenThereIsAConfiguration(configuration))
- .And(x => _steps.GivenOcelotIsRunning())
- .When(x => _steps.WhenIGetUrlOnTheApiGateway("/myApp1Name/api/products/1"))
- .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
- .And(x => _steps.ThenTheResponseBodyShouldBe("Some Product"))
- .BDDfy();
- }
-
- [Fact]
- public void should_return_response_201_with_simple_url_and_multiple_upstream_http_method()
- {
- var configuration = new FileConfiguration
- {
- ReRoutes = new List
- {
- new FileReRoute
- {
- DownstreamPathTemplate = "/",
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 59911,
- }
- },
- DownstreamScheme = "http",
- UpstreamPathTemplate = "/",
- UpstreamHttpMethod = new List { "Get", "Post" },
- }
- }
- };
-
- this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:59911", "", 201, string.Empty))
- .And(x => _steps.GivenThereIsAConfiguration(configuration))
- .And(x => _steps.GivenOcelotIsRunning())
- .And(x => _steps.GivenThePostHasContent("postContent"))
- .When(x => _steps.WhenIPostUrlOnTheApiGateway("/"))
- .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Created))
- .BDDfy();
- }
-
- [Fact]
- public void should_return_response_200_with_simple_url_and_any_upstream_http_method()
- {
- var configuration = new FileConfiguration
- {
- ReRoutes = new List
- {
- new FileReRoute
- {
- DownstreamPathTemplate = "/",
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 50187,
- }
- },
- DownstreamScheme = "http",
- UpstreamPathTemplate = "/",
- UpstreamHttpMethod = new List(),
- }
- }
- };
-
- this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:50187", "/", 200, "Hello from Laura"))
- .And(x => _steps.GivenThereIsAConfiguration(configuration))
- .And(x => _steps.GivenOcelotIsRunning())
- .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
- .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
- .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
- .BDDfy();
- }
-
- [Fact]
- public void should_return_404_when_calling_upstream_route_with_no_matching_downstream_re_route_github_issue_134()
- {
- var configuration = new FileConfiguration
- {
- ReRoutes = new List
- {
- new FileReRoute
- {
- DownstreamPathTemplate = "/api/v1/vacancy",
- DownstreamScheme = "http",
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 54079,
- }
- },
- UpstreamPathTemplate = "/vacancy/",
- UpstreamHttpMethod = new List { "Options", "Put", "Get", "Post", "Delete" },
- LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" }
- },
- new FileReRoute
- {
- DownstreamPathTemplate = "/api/v1/vacancy/{vacancyId}",
- DownstreamScheme = "http",
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 54079,
- }
- },
- UpstreamPathTemplate = "/vacancy/{vacancyId}",
- UpstreamHttpMethod = new List { "Options", "Put", "Get", "Post", "Delete" },
- LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" }
- }
- }
- };
-
- this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:54079", "/api/v1/vacancy/1", 200, "Hello from Laura"))
- .And(x => _steps.GivenThereIsAConfiguration(configuration))
- .And(x => _steps.GivenOcelotIsRunning())
- .When(x => _steps.WhenIGetUrlOnTheApiGateway("api/vacancy/1"))
- .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound))
- .BDDfy();
- }
-
- [Fact]
- public void should_not_set_trailing_slash_on_url_template()
- {
- var configuration = new FileConfiguration
- {
- ReRoutes = new List
- {
- new FileReRoute
- {
- DownstreamPathTemplate = "/api/{url}",
- DownstreamScheme = "http",
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 51899,
- }
- },
- UpstreamPathTemplate = "/platform/{url}",
- UpstreamHttpMethod = new List { "Get" },
- }
- }
- };
-
- this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51899", "/api/swagger/lib/backbone-min.js", 200, "Hello from Laura"))
- .And(x => _steps.GivenThereIsAConfiguration(configuration))
- .And(x => _steps.GivenOcelotIsRunning())
- .When(x => _steps.WhenIGetUrlOnTheApiGateway("/platform/swagger/lib/backbone-min.js"))
- .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
- .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
- .And(x => ThenTheDownstreamUrlPathShouldBe("/api/swagger/lib/backbone-min.js"))
- .BDDfy();
- }
-
- [Fact]
- public void should_use_priority()
- {
- var configuration = new FileConfiguration
- {
- ReRoutes = new List
- {
- new FileReRoute
- {
- DownstreamPathTemplate = "/goods/{url}",
- DownstreamScheme = "http",
- UpstreamPathTemplate = "/goods/{url}",
- UpstreamHttpMethod = new List { "Get" },
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 53879,
- }
- },
- Priority = 0
- },
- new FileReRoute
- {
- DownstreamPathTemplate = "/goods/delete",
- DownstreamScheme = "http",
- UpstreamPathTemplate = "/goods/delete",
- UpstreamHttpMethod = new List { "Get" },
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 52879,
- }
- },
- }
- }
- };
-
- this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:52879/", "/goods/delete", 200, "Hello from Laura"))
- .And(x => _steps.GivenThereIsAConfiguration(configuration))
- .And(x => _steps.GivenOcelotIsRunning())
- .When(x => _steps.WhenIGetUrlOnTheApiGateway("/goods/delete"))
- .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
- .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
- .BDDfy();
- }
-
- [Fact]
- public void should_match_multiple_paths_with_catch_all()
- {
- var port = 61999;
- var configuration = new FileConfiguration
- {
- ReRoutes = new List
- {
- new FileReRoute
- {
- DownstreamPathTemplate = "/{everything}",
- DownstreamScheme = "http",
- UpstreamPathTemplate = "/{everything}",
- UpstreamHttpMethod = new List { "Get" },
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = port,
- }
- },
- }
- }
- };
-
- this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}/", "/test/toot", 200, "Hello from Laura"))
- .And(x => _steps.GivenThereIsAConfiguration(configuration))
- .And(x => _steps.GivenOcelotIsRunning())
- .When(x => _steps.WhenIGetUrlOnTheApiGateway("/test/toot"))
- .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
- .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
- .BDDfy();
- }
-
- [Fact]
- public void should_fix_issue_271()
- {
- var configuration = new FileConfiguration
- {
- ReRoutes = new List
- {
- new FileReRoute
- {
- DownstreamPathTemplate = "/api/v1/{everything}",
- DownstreamScheme = "http",
- UpstreamPathTemplate = "/api/v1/{everything}",
- UpstreamHttpMethod = new List { "Get", "Put", "Post" },
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 54879,
- }
- },
- },
- new FileReRoute
- {
- DownstreamPathTemplate = "/connect/token",
- DownstreamScheme = "http",
- UpstreamPathTemplate = "/connect/token",
- UpstreamHttpMethod = new List { "Post" },
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 5001,
- }
- },
- }
- }
- };
-
- this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:54879/", "/api/v1/modules/Test", 200, "Hello from Laura"))
- .And(x => _steps.GivenThereIsAConfiguration(configuration))
- .And(x => _steps.GivenOcelotIsRunning())
- .When(x => _steps.WhenIGetUrlOnTheApiGateway("/api/v1/modules/Test"))
- .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
- .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
- .BDDfy();
- }
-
- private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody)
- {
- _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context =>
- {
- _downstreamPath = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value;
-
- if (_downstreamPath != basePath)
- {
- context.Response.StatusCode = statusCode;
- await context.Response.WriteAsync("downstream path didnt match base path");
- }
- else
- {
- context.Response.StatusCode = statusCode;
- await context.Response.WriteAsync(responseBody);
- }
- });
- }
-
- internal void ThenTheDownstreamUrlPathShouldBe(string expectedDownstreamPath)
- {
- _downstreamPath.ShouldBe(expectedDownstreamPath);
- }
-
- public void Dispose()
- {
- _serviceHandler.Dispose();
- _steps.Dispose();
- }
- }
-}
+namespace Ocelot.AcceptanceTests
+{
+ using Microsoft.AspNetCore.Http;
+ using Ocelot.Configuration.File;
+ using Shouldly;
+ using System;
+ using System.Collections.Generic;
+ using System.Net;
+ using TestStack.BDDfy;
+ using Xunit;
+
+ public class RoutingTests : IDisposable
+ {
+ private readonly Steps _steps;
+ private string _downstreamPath;
+ private readonly ServiceHandler _serviceHandler;
+
+ public RoutingTests()
+ {
+ _serviceHandler = new ServiceHandler();
+ _steps = new Steps();
+ }
+
+ [Fact]
+ public void should_not_match_forward_slash_in_pattern_before_next_forward_slash()
+ {
+ var port = 31879;
+ var configuration = new FileConfiguration
+ {
+ ReRoutes = new List
+ {
+ new FileReRoute
+ {
+ DownstreamPathTemplate = "/api/v{apiVersion}/cards",
+ DownstreamScheme = "http",
+ UpstreamPathTemplate = "/api/v{apiVersion}/cards",
+ UpstreamHttpMethod = new List { "Get" },
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = port,
+ }
+ },
+ Priority = 1
+ }
+ }
+ };
+
+ this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}/", "/api/v1/aaaaaaaaa/cards", 200, "Hello from Laura"))
+ .And(x => _steps.GivenThereIsAConfiguration(configuration))
+ .And(x => _steps.GivenOcelotIsRunning())
+ .When(x => _steps.WhenIGetUrlOnTheApiGateway("/api/v1/aaaaaaaaa/cards"))
+ .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound))
+ .BDDfy();
+ }
+
+ [Fact]
+ public void should_return_response_404_when_no_configuration_at_all()
+ {
+ this.Given(x => _steps.GivenThereIsAConfiguration(new FileConfiguration()))
+ .And(x => _steps.GivenOcelotIsRunning())
+ .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
+ .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound))
+ .BDDfy();
+ }
+
+ [Fact]
+ public void should_return_response_200_with_forward_slash_and_placeholder_only()
+ {
+ var configuration = new FileConfiguration
+ {
+ ReRoutes = new List
+ {
+ new FileReRoute
+ {
+ DownstreamPathTemplate = "/{url}",
+ DownstreamScheme = "http",
+ UpstreamPathTemplate = "/{url}",
+ UpstreamHttpMethod = new List { "Get" },
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 57873,
+ }
+ }
+ }
+ }
+ };
+
+ this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:57873/", "/", 200, "Hello from Laura"))
+ .And(x => _steps.GivenThereIsAConfiguration(configuration))
+ .And(x => _steps.GivenOcelotIsRunning())
+ .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
+ .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
+ .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
+ .BDDfy();
+ }
+
+ [Fact]
+ public void should_return_response_200_favouring_forward_slash_with_path_route()
+ {
+ var configuration = new FileConfiguration
+ {
+ ReRoutes = new List
+ {
+ new FileReRoute
+ {
+ DownstreamPathTemplate = "/{url}",
+ DownstreamScheme = "http",
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 51880,
+ }
+ },
+ UpstreamPathTemplate = "/{url}",
+ UpstreamHttpMethod = new List { "Get" },
+ },
+ new FileReRoute
+ {
+ DownstreamPathTemplate = "/",
+ DownstreamScheme = "http",
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 50810,
+ }
+ },
+ UpstreamPathTemplate = "/",
+ UpstreamHttpMethod = new List { "Get" },
+ }
+ }
+ };
+
+ this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51880/", "/test", 200, "Hello from Laura"))
+ .And(x => _steps.GivenThereIsAConfiguration(configuration))
+ .And(x => _steps.GivenOcelotIsRunning())
+ .When(x => _steps.WhenIGetUrlOnTheApiGateway("/test"))
+ .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
+ .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
+ .BDDfy();
+ }
+
+ [Fact]
+ public void should_return_response_200_favouring_forward_slash()
+ {
+ var configuration = new FileConfiguration
+ {
+ ReRoutes = new List
+ {
+ new FileReRoute
+ {
+ DownstreamPathTemplate = "/{url}",
+ DownstreamScheme = "http",
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 51880,
+ }
+ },
+ UpstreamPathTemplate = "/{url}",
+ UpstreamHttpMethod = new List { "Get" },
+ },
+ new FileReRoute
+ {
+ DownstreamPathTemplate = "/",
+ DownstreamScheme = "http",
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 50810,
+ }
+ },
+ UpstreamPathTemplate = "/",
+ UpstreamHttpMethod = new List { "Get" },
+ }
+ }
+ };
+
+ this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:50810/", "/", 200, "Hello from Laura"))
+ .And(x => _steps.GivenThereIsAConfiguration(configuration))
+ .And(x => _steps.GivenOcelotIsRunning())
+ .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
+ .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
+ .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
+ .BDDfy();
+ }
+
+ [Fact]
+ public void should_return_response_200_favouring_forward_slash_route_because_it_is_first()
+ {
+ var configuration = new FileConfiguration
+ {
+ ReRoutes = new List
+ {
+ new FileReRoute
+ {
+ DownstreamPathTemplate = "/",
+ DownstreamScheme = "http",
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 51880,
+ }
+ },
+ UpstreamPathTemplate = "/",
+ UpstreamHttpMethod = new List { "Get" },
+ },
+ new FileReRoute
+ {
+ DownstreamPathTemplate = "/{url}",
+ DownstreamScheme = "http",
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 51879,
+ }
+ },
+ UpstreamPathTemplate = "/{url}",
+ UpstreamHttpMethod = new List { "Get" },
+ }
+ }
+ };
+
+ this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51880/", "/", 200, "Hello from Laura"))
+ .And(x => _steps.GivenThereIsAConfiguration(configuration))
+ .And(x => _steps.GivenOcelotIsRunning())
+ .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
+ .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
+ .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
+ .BDDfy();
+ }
+
+ [Fact]
+ public void should_return_response_200_with_nothing_and_placeholder_only()
+ {
+ var configuration = new FileConfiguration
+ {
+ ReRoutes = new List
+ {
+ new FileReRoute
+ {
+ DownstreamPathTemplate = "/{url}",
+ DownstreamScheme = "http",
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 51005,
+ }
+ },
+ UpstreamPathTemplate = "/{url}",
+ UpstreamHttpMethod = new List { "Get" },
+ }
+ }
+ };
+
+ this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51005", "/", 200, "Hello from Laura"))
+ .And(x => _steps.GivenThereIsAConfiguration(configuration))
+ .And(x => _steps.GivenOcelotIsRunning())
+ .When(x => _steps.WhenIGetUrlOnTheApiGateway(""))
+ .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
+ .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
+ .BDDfy();
+ }
+
+ [Fact]
+ public void should_return_response_200_with_simple_url()
+ {
+ var configuration = new FileConfiguration
+ {
+ ReRoutes = new List
+ {
+ new FileReRoute
+ {
+ DownstreamPathTemplate = "/",
+ DownstreamScheme = "http",
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 58589,
+ }
+ },
+ UpstreamPathTemplate = "/",
+ UpstreamHttpMethod = new List { "Get" },
+ }
+ }
+ };
+
+ this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:58589", "/", 200, "Hello from Laura"))
+ .And(x => _steps.GivenThereIsAConfiguration(configuration))
+ .And(x => _steps.GivenOcelotIsRunning())
+ .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
+ .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
+ .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
+ .BDDfy();
+ }
+
+ [Fact]
+ public void bug()
+ {
+ var configuration = new FileConfiguration
+ {
+ ReRoutes = new List
+ {
+ new FileReRoute
+ {
+ DownstreamPathTemplate = "/api/v1/vacancy",
+ DownstreamScheme = "http",
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 51874,
+ }
+ },
+ UpstreamPathTemplate = "/vacancy/",
+ UpstreamHttpMethod = new List { "Options", "Put", "Get", "Post", "Delete" },
+ LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" }
+ },
+ new FileReRoute
+ {
+ DownstreamPathTemplate = "/api/v1/vacancy/{vacancyId}",
+ DownstreamScheme = "http",
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 51874,
+ }
+ },
+ UpstreamPathTemplate = "/vacancy/{vacancyId}",
+ UpstreamHttpMethod = new List { "Options", "Put", "Get", "Post", "Delete" },
+ LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" }
+ }
+ }
+ };
+
+ this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51874", "/api/v1/vacancy/1", 200, "Hello from Laura"))
+ .And(x => _steps.GivenThereIsAConfiguration(configuration))
+ .And(x => _steps.GivenOcelotIsRunning())
+ .When(x => _steps.WhenIGetUrlOnTheApiGateway("/vacancy/1"))
+ .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
+ .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
+ .BDDfy();
+ }
+
+ [Fact]
+ public void should_return_response_200_when_path_missing_forward_slash_as_first_char()
+ {
+ var configuration = new FileConfiguration
+ {
+ ReRoutes = new List
+ {
+ new FileReRoute
+ {
+ DownstreamPathTemplate = "/api/products",
+ DownstreamScheme = "http",
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 51206,
+ }
+ },
+ UpstreamPathTemplate = "/",
+ UpstreamHttpMethod = new List { "Get" },
+ }
+ }
+ };
+
+ this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51206", "/api/products", 200, "Hello from Laura"))
+ .And(x => _steps.GivenThereIsAConfiguration(configuration))
+ .And(x => _steps.GivenOcelotIsRunning())
+ .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
+ .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
+ .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
+ .BDDfy();
+ }
+
+ [Fact]
+ public void should_return_response_200_when_host_has_trailing_slash()
+ {
+ var configuration = new FileConfiguration
+ {
+ ReRoutes = new List
+ {
+ new FileReRoute
+ {
+ DownstreamPathTemplate = "/api/products",
+ DownstreamScheme = "http",
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 51990,
+ }
+ },
+ UpstreamPathTemplate = "/",
+ UpstreamHttpMethod = new List { "Get" },
+ }
+ }
+ };
+
+ this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51990", "/api/products", 200, "Hello from Laura"))
+ .And(x => _steps.GivenThereIsAConfiguration(configuration))
+ .And(x => _steps.GivenOcelotIsRunning())
+ .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
+ .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
+ .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
+ .BDDfy();
+ }
+
+ [Fact]
+ public void should_return_ok_when_upstream_url_ends_with_forward_slash_but_template_does_not()
+ {
+ var configuration = new FileConfiguration
+ {
+ ReRoutes = new List
+ {
+ new FileReRoute
+ {
+ DownstreamPathTemplate = "/products",
+ DownstreamScheme = "http",
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 58804,
+ }
+ },
+ UpstreamPathTemplate = "/products/",
+ UpstreamHttpMethod = new List { "Get" },
+ }
+ }
+ };
+
+ this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:58804", "/products", 200, "Hello from Laura"))
+ .And(x => _steps.GivenThereIsAConfiguration(configuration))
+ .And(x => _steps.GivenOcelotIsRunning())
+ .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products"))
+ .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
+ .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
+ .BDDfy();
+ }
+
+ [Fact]
+ public void should_return_not_found_when_upstream_url_ends_with_forward_slash_but_template_does_not()
+ {
+ var configuration = new FileConfiguration
+ {
+ ReRoutes = new List
+ {
+ new FileReRoute
+ {
+ DownstreamPathTemplate = "/products",
+ DownstreamScheme = "http",
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 54015,
+ }
+ },
+ UpstreamPathTemplate = "/products",
+ UpstreamHttpMethod = new List { "Get" },
+ }
+ }
+ };
+
+ this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:54015", "/products", 200, "Hello from Laura"))
+ .And(x => _steps.GivenThereIsAConfiguration(configuration))
+ .And(x => _steps.GivenOcelotIsRunning())
+ .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products/"))
+ .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound))
+ .BDDfy();
+ }
+
+ [Fact]
+ public void should_return_not_found()
+ {
+ var configuration = new FileConfiguration
+ {
+ ReRoutes = new List
+ {
+ new FileReRoute
+ {
+ DownstreamPathTemplate = "/products",
+ DownstreamScheme = "http",
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 54072,
+ }
+ },
+ UpstreamPathTemplate = "/products/{productId}",
+ UpstreamHttpMethod = new List { "Get" }
+ }
+ }
+ };
+
+ this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:54072", "/products", 200, "Hello from Laura"))
+ .And(x => _steps.GivenThereIsAConfiguration(configuration))
+ .And(x => _steps.GivenOcelotIsRunning())
+ .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products/"))
+ .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound))
+ .BDDfy();
+ }
+
+ [Fact]
+ public void should_return_response_200_with_complex_url()
+ {
+ var configuration = new FileConfiguration
+ {
+ ReRoutes = new List
+ {
+ new FileReRoute
+ {
+ DownstreamPathTemplate = "/api/products/{productId}",
+ DownstreamScheme = "http",
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 55961,
+ }
+ },
+ UpstreamPathTemplate = "/products/{productId}",
+ UpstreamHttpMethod = new List { "Get" },
+ }
+ }
+ };
+
+ this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:55961", "/api/products/1", 200, "Some Product"))
+ .And(x => _steps.GivenThereIsAConfiguration(configuration))
+ .And(x => _steps.GivenOcelotIsRunning())
+ .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products/1"))
+ .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
+ .And(x => _steps.ThenTheResponseBodyShouldBe("Some Product"))
+ .BDDfy();
+ }
+
+ [Fact]
+ public void should_return_response_200_with_complex_url_that_starts_with_placeholder()
+ {
+ var configuration = new FileConfiguration
+ {
+ ReRoutes = new List
+ {
+ new FileReRoute
+ {
+ DownstreamPathTemplate = "/api/{variantId}/products/{productId}",
+ DownstreamScheme = "http",
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 51116,
+ }
+ },
+ UpstreamPathTemplate = "/{variantId}/products/{productId}",
+ UpstreamHttpMethod = new List { "Get" },
+ }
+ }
+ };
+
+ this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51116", "/api/23/products/1", 200, "Some Product"))
+ .And(x => _steps.GivenThereIsAConfiguration(configuration))
+ .And(x => _steps.GivenOcelotIsRunning())
+ .When(x => _steps.WhenIGetUrlOnTheApiGateway("23/products/1"))
+ .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
+ .And(x => _steps.ThenTheResponseBodyShouldBe("Some Product"))
+ .BDDfy();
+ }
+
+ [Fact]
+ public void should_not_add_trailing_slash_to_downstream_url()
+ {
+ var configuration = new FileConfiguration
+ {
+ ReRoutes = new List
+ {
+ new FileReRoute
+ {
+ DownstreamPathTemplate = "/api/products/{productId}",
+ DownstreamScheme = "http",
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 51809,
+ }
+ },
+ UpstreamPathTemplate = "/products/{productId}",
+ UpstreamHttpMethod = new List { "Get" },
+ }
+ }
+ };
+
+ this.Given(x => GivenThereIsAServiceRunningOn("http://localhost:51809", "/api/products/1", 200, "Some Product"))
+ .And(x => _steps.GivenThereIsAConfiguration(configuration))
+ .And(x => _steps.GivenOcelotIsRunning())
+ .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products/1"))
+ .Then(x => ThenTheDownstreamUrlPathShouldBe("/api/products/1"))
+ .BDDfy();
+ }
+
+ [Fact]
+ public void should_return_response_201_with_simple_url()
+ {
+ var configuration = new FileConfiguration
+ {
+ ReRoutes = new List
+ {
+ new FileReRoute
+ {
+ DownstreamPathTemplate = "/",
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 56615,
+ }
+ },
+ DownstreamScheme = "http",
+ UpstreamPathTemplate = "/",
+ UpstreamHttpMethod = new List { "Post" },
+ }
+ }
+ };
+
+ this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:56615", "/", 201, string.Empty))
+ .And(x => _steps.GivenThereIsAConfiguration(configuration))
+ .And(x => _steps.GivenOcelotIsRunning())
+ .And(x => _steps.GivenThePostHasContent("postContent"))
+ .When(x => _steps.WhenIPostUrlOnTheApiGateway("/"))
+ .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Created))
+ .BDDfy();
+ }
+
+ [Fact]
+ public void should_return_response_201_with_complex_query_string()
+ {
+ var configuration = new FileConfiguration
+ {
+ ReRoutes = new List
+ {
+ new FileReRoute
+ {
+ DownstreamPathTemplate = "/newThing",
+ UpstreamPathTemplate = "/newThing",
+ DownstreamScheme = "http",
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 57771,
+ }
+ },
+ UpstreamHttpMethod = new List { "Get" },
+ }
+ }
+ };
+
+ this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:57771", "/newThing", 200, "Hello from Laura"))
+ .And(x => _steps.GivenThereIsAConfiguration(configuration))
+ .And(x => _steps.GivenOcelotIsRunning())
+ .When(x => _steps.WhenIGetUrlOnTheApiGateway("/newThing?DeviceType=IphoneApp&Browser=moonpigIphone&BrowserString=-&CountryCode=123&DeviceName=iPhone 5 (GSM+CDMA)&OperatingSystem=iPhone OS 7.1.2&BrowserVersion=3708AdHoc&ipAddress=-"))
+ .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
+ .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
+ .BDDfy();
+ }
+
+ [Fact]
+ public void should_return_response_200_with_placeholder_for_final_url_path()
+ {
+ var configuration = new FileConfiguration
+ {
+ ReRoutes = new List
+ {
+ new FileReRoute
+ {
+ DownstreamPathTemplate = "/api/{urlPath}",
+ DownstreamScheme = "http",
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 55609,
+ }
+ },
+ UpstreamPathTemplate = "/myApp1Name/api/{urlPath}",
+ UpstreamHttpMethod = new List { "Get" },
+ }
+ }
+ };
+
+ this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:55609", "/api/products/1", 200, "Some Product"))
+ .And(x => _steps.GivenThereIsAConfiguration(configuration))
+ .And(x => _steps.GivenOcelotIsRunning())
+ .When(x => _steps.WhenIGetUrlOnTheApiGateway("/myApp1Name/api/products/1"))
+ .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
+ .And(x => _steps.ThenTheResponseBodyShouldBe("Some Product"))
+ .BDDfy();
+ }
+
+ [Fact]
+ public void should_return_response_201_with_simple_url_and_multiple_upstream_http_method()
+ {
+ var configuration = new FileConfiguration
+ {
+ ReRoutes = new List
+ {
+ new FileReRoute
+ {
+ DownstreamPathTemplate = "/",
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 59911,
+ }
+ },
+ DownstreamScheme = "http",
+ UpstreamPathTemplate = "/",
+ UpstreamHttpMethod = new List { "Get", "Post" },
+ }
+ }
+ };
+
+ this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:59911", "", 201, string.Empty))
+ .And(x => _steps.GivenThereIsAConfiguration(configuration))
+ .And(x => _steps.GivenOcelotIsRunning())
+ .And(x => _steps.GivenThePostHasContent("postContent"))
+ .When(x => _steps.WhenIPostUrlOnTheApiGateway("/"))
+ .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Created))
+ .BDDfy();
+ }
+
+ [Fact]
+ public void should_return_response_200_with_simple_url_and_any_upstream_http_method()
+ {
+ var configuration = new FileConfiguration
+ {
+ ReRoutes = new List
+ {
+ new FileReRoute
+ {
+ DownstreamPathTemplate = "/",
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 59187,
+ }
+ },
+ DownstreamScheme = "http",
+ UpstreamPathTemplate = "/",
+ UpstreamHttpMethod = new List(),
+ }
+ }
+ };
+
+ this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:59187", "/", 200, "Hello from Laura"))
+ .And(x => _steps.GivenThereIsAConfiguration(configuration))
+ .And(x => _steps.GivenOcelotIsRunning())
+ .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
+ .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
+ .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
+ .BDDfy();
+ }
+
+ [Fact]
+ public void should_return_404_when_calling_upstream_route_with_no_matching_downstream_re_route_github_issue_134()
+ {
+ var configuration = new FileConfiguration
+ {
+ ReRoutes = new List
+ {
+ new FileReRoute
+ {
+ DownstreamPathTemplate = "/api/v1/vacancy",
+ DownstreamScheme = "http",
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 54079,
+ }
+ },
+ UpstreamPathTemplate = "/vacancy/",
+ UpstreamHttpMethod = new List { "Options", "Put", "Get", "Post", "Delete" },
+ LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" }
+ },
+ new FileReRoute
+ {
+ DownstreamPathTemplate = "/api/v1/vacancy/{vacancyId}",
+ DownstreamScheme = "http",
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 54079,
+ }
+ },
+ UpstreamPathTemplate = "/vacancy/{vacancyId}",
+ UpstreamHttpMethod = new List { "Options", "Put", "Get", "Post", "Delete" },
+ LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" }
+ }
+ }
+ };
+
+ this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:54079", "/api/v1/vacancy/1", 200, "Hello from Laura"))
+ .And(x => _steps.GivenThereIsAConfiguration(configuration))
+ .And(x => _steps.GivenOcelotIsRunning())
+ .When(x => _steps.WhenIGetUrlOnTheApiGateway("api/vacancy/1"))
+ .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound))
+ .BDDfy();
+ }
+
+ [Fact]
+ public void should_not_set_trailing_slash_on_url_template()
+ {
+ var configuration = new FileConfiguration
+ {
+ ReRoutes = new List
+ {
+ new FileReRoute
+ {
+ DownstreamPathTemplate = "/api/{url}",
+ DownstreamScheme = "http",
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 51899,
+ }
+ },
+ UpstreamPathTemplate = "/platform/{url}",
+ UpstreamHttpMethod = new List { "Get" },
+ }
+ }
+ };
+
+ this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51899", "/api/swagger/lib/backbone-min.js", 200, "Hello from Laura"))
+ .And(x => _steps.GivenThereIsAConfiguration(configuration))
+ .And(x => _steps.GivenOcelotIsRunning())
+ .When(x => _steps.WhenIGetUrlOnTheApiGateway("/platform/swagger/lib/backbone-min.js"))
+ .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
+ .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
+ .And(x => ThenTheDownstreamUrlPathShouldBe("/api/swagger/lib/backbone-min.js"))
+ .BDDfy();
+ }
+
+ [Fact]
+ public void should_use_priority()
+ {
+ var configuration = new FileConfiguration
+ {
+ ReRoutes = new List
+ {
+ new FileReRoute
+ {
+ DownstreamPathTemplate = "/goods/{url}",
+ DownstreamScheme = "http",
+ UpstreamPathTemplate = "/goods/{url}",
+ UpstreamHttpMethod = new List { "Get" },
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 53879,
+ }
+ },
+ Priority = 0
+ },
+ new FileReRoute
+ {
+ DownstreamPathTemplate = "/goods/delete",
+ DownstreamScheme = "http",
+ UpstreamPathTemplate = "/goods/delete",
+ UpstreamHttpMethod = new List { "Get" },
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 52879,
+ }
+ },
+ }
+ }
+ };
+
+ this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:52879/", "/goods/delete", 200, "Hello from Laura"))
+ .And(x => _steps.GivenThereIsAConfiguration(configuration))
+ .And(x => _steps.GivenOcelotIsRunning())
+ .When(x => _steps.WhenIGetUrlOnTheApiGateway("/goods/delete"))
+ .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
+ .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
+ .BDDfy();
+ }
+
+ [Fact]
+ public void should_match_multiple_paths_with_catch_all()
+ {
+ var port = 61999;
+ var configuration = new FileConfiguration
+ {
+ ReRoutes = new List
+ {
+ new FileReRoute
+ {
+ DownstreamPathTemplate = "/{everything}",
+ DownstreamScheme = "http",
+ UpstreamPathTemplate = "/{everything}",
+ UpstreamHttpMethod = new List { "Get" },
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = port,
+ }
+ },
+ }
+ }
+ };
+
+ this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}/", "/test/toot", 200, "Hello from Laura"))
+ .And(x => _steps.GivenThereIsAConfiguration(configuration))
+ .And(x => _steps.GivenOcelotIsRunning())
+ .When(x => _steps.WhenIGetUrlOnTheApiGateway("/test/toot"))
+ .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
+ .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
+ .BDDfy();
+ }
+
+ [Fact]
+ public void should_fix_issue_271()
+ {
+ var configuration = new FileConfiguration
+ {
+ ReRoutes = new List
+ {
+ new FileReRoute
+ {
+ DownstreamPathTemplate = "/api/v1/{everything}",
+ DownstreamScheme = "http",
+ UpstreamPathTemplate = "/api/v1/{everything}",
+ UpstreamHttpMethod = new List { "Get", "Put", "Post" },
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 54879,
+ }
+ },
+ },
+ new FileReRoute
+ {
+ DownstreamPathTemplate = "/connect/token",
+ DownstreamScheme = "http",
+ UpstreamPathTemplate = "/connect/token",
+ UpstreamHttpMethod = new List { "Post" },
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 5001,
+ }
+ },
+ }
+ }
+ };
+
+ this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:54879/", "/api/v1/modules/Test", 200, "Hello from Laura"))
+ .And(x => _steps.GivenThereIsAConfiguration(configuration))
+ .And(x => _steps.GivenOcelotIsRunning())
+ .When(x => _steps.WhenIGetUrlOnTheApiGateway("/api/v1/modules/Test"))
+ .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
+ .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
+ .BDDfy();
+ }
+
+ private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody)
+ {
+ _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context =>
+ {
+ _downstreamPath = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value;
+
+ if (_downstreamPath != basePath)
+ {
+ context.Response.StatusCode = statusCode;
+ await context.Response.WriteAsync("downstream path didnt match base path");
+ }
+ else
+ {
+ context.Response.StatusCode = statusCode;
+ await context.Response.WriteAsync(responseBody);
+ }
+ });
+ }
+
+ internal void ThenTheDownstreamUrlPathShouldBe(string expectedDownstreamPath)
+ {
+ _downstreamPath.ShouldBe(expectedDownstreamPath);
+ }
+
+ public void Dispose()
+ {
+ _serviceHandler.Dispose();
+ _steps.Dispose();
+ }
+ }
+}
diff --git a/test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj b/test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj
index 48df8a92..0ebd49e7 100644
--- a/test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj
+++ b/test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj
@@ -2,7 +2,7 @@
0.0.0-dev
- netcoreapp3.0
+ netcoreapp3.1
Ocelot.Benchmarks
Exe
Ocelot.Benchmarks
@@ -25,6 +25,6 @@
-
+
\ No newline at end of file
diff --git a/test/Ocelot.IntegrationTests/AdministrationTests.cs b/test/Ocelot.IntegrationTests/AdministrationTests.cs
index b874f787..fe18965d 100644
--- a/test/Ocelot.IntegrationTests/AdministrationTests.cs
+++ b/test/Ocelot.IntegrationTests/AdministrationTests.cs
@@ -1,864 +1,864 @@
-using IdentityServer4.AccessTokenValidation;
-using IdentityServer4.Models;
-using IdentityServer4.Test;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.AspNetCore.Http;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Hosting;
-using Newtonsoft.Json;
-using Ocelot.Administration;
-using Ocelot.Cache;
-using Ocelot.Configuration.File;
-using Ocelot.DependencyInjection;
-using Ocelot.Middleware;
-using Shouldly;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Net;
-using System.Net.Http;
-using System.Net.Http.Headers;
-using TestStack.BDDfy;
-using Xunit;
-
-namespace Ocelot.IntegrationTests
-{
- public class AdministrationTests : IDisposable
- {
- private HttpClient _httpClient;
- private readonly HttpClient _httpClientTwo;
- private HttpResponseMessage _response;
- private IHost _builder;
- private IHostBuilder _webHostBuilder;
- private string _ocelotBaseUrl;
- private BearerToken _token;
- private IHostBuilder _webHostBuilderTwo;
- private IHost _builderTwo;
- private IHost _identityServerBuilder;
- private IHost _fooServiceBuilder;
- private IHost _barServiceBuilder;
-
- public AdministrationTests()
- {
- _httpClient = new HttpClient();
- _httpClientTwo = new HttpClient();
- _ocelotBaseUrl = "http://localhost:5000";
- _httpClient.BaseAddress = new Uri(_ocelotBaseUrl);
- }
-
- [Fact]
- public void should_return_response_401_with_call_re_routes_controller()
- {
- var configuration = new FileConfiguration();
-
- this.Given(x => GivenThereIsAConfiguration(configuration))
- .And(x => GivenOcelotIsRunning())
- .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration"))
- .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized))
- .BDDfy();
- }
-
- [Fact]
- public void should_return_response_200_with_call_re_routes_controller()
- {
- var configuration = new FileConfiguration();
-
- this.Given(x => GivenThereIsAConfiguration(configuration))
- .And(x => GivenOcelotIsRunning())
- .And(x => GivenIHaveAnOcelotToken("/administration"))
- .And(x => GivenIHaveAddedATokenToMyRequest())
- .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration"))
- .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
- .BDDfy();
- }
-
- [Fact]
- public void should_return_response_200_with_call_re_routes_controller_using_base_url_added_in_file_config()
- {
- _httpClient = new HttpClient();
- _ocelotBaseUrl = "http://localhost:5011";
- _httpClient.BaseAddress = new Uri(_ocelotBaseUrl);
-
- var configuration = new FileConfiguration
- {
- GlobalConfiguration = new FileGlobalConfiguration
- {
- BaseUrl = _ocelotBaseUrl
- }
- };
-
- this.Given(x => GivenThereIsAConfiguration(configuration))
- .And(x => GivenOcelotIsRunningWithNoWebHostBuilder(_ocelotBaseUrl))
- .And(x => GivenIHaveAnOcelotToken("/administration"))
- .And(x => GivenIHaveAddedATokenToMyRequest())
- .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration"))
- .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
- .BDDfy();
- }
-
- [Fact]
- public void should_be_able_to_use_token_from_ocelot_a_on_ocelot_b()
- {
- var configuration = new FileConfiguration();
-
- this.Given(x => GivenThereIsAConfiguration(configuration))
- .And(x => GivenIdentityServerSigningEnvironmentalVariablesAreSet())
- .And(x => GivenOcelotIsRunning())
- .And(x => GivenIHaveAnOcelotToken("/administration"))
- .And(x => GivenAnotherOcelotIsRunning("http://localhost:5007"))
- .When(x => WhenIGetUrlOnTheSecondOcelot("/administration/configuration"))
- .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
- .BDDfy();
- }
-
- [Fact]
- public void should_return_file_configuration()
- {
- var configuration = new FileConfiguration
- {
- GlobalConfiguration = new FileGlobalConfiguration
- {
- RequestIdKey = "RequestId",
- ServiceDiscoveryProvider = new FileServiceDiscoveryProvider
- {
- Host = "127.0.0.1",
- }
- },
- ReRoutes = new List()
- {
- new FileReRoute()
- {
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 80,
- }
- },
- DownstreamScheme = "https",
- DownstreamPathTemplate = "/",
- UpstreamHttpMethod = new List { "get" },
- UpstreamPathTemplate = "/",
- FileCacheOptions = new FileCacheOptions
- {
- TtlSeconds = 10,
- Region = "Geoff"
- }
- },
- new FileReRoute()
- {
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 80,
- }
- },
- DownstreamScheme = "https",
- DownstreamPathTemplate = "/",
- UpstreamHttpMethod = new List { "get" },
- UpstreamPathTemplate = "/test",
- FileCacheOptions = new FileCacheOptions
- {
- TtlSeconds = 10,
- Region = "Dave"
- }
- }
- }
- };
-
- this.Given(x => GivenThereIsAConfiguration(configuration))
- .And(x => GivenOcelotIsRunning())
- .And(x => GivenIHaveAnOcelotToken("/administration"))
- .And(x => GivenIHaveAddedATokenToMyRequest())
- .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration"))
- .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
- .And(x => ThenTheResponseShouldBe(configuration))
- .BDDfy();
- }
-
- [Fact]
- public void should_get_file_configuration_edit_and_post_updated_version()
- {
- var initialConfiguration = new FileConfiguration
- {
- GlobalConfiguration = new FileGlobalConfiguration
- {
- },
- ReRoutes = new List()
- {
- new FileReRoute()
- {
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 80,
- }
- },
- DownstreamScheme = "https",
- DownstreamPathTemplate = "/",
- UpstreamHttpMethod = new List { "get" },
- UpstreamPathTemplate = "/"
- },
- new FileReRoute()
- {
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 80,
- }
- },
- DownstreamScheme = "https",
- DownstreamPathTemplate = "/",
- UpstreamHttpMethod = new List { "get" },
- UpstreamPathTemplate = "/test"
- }
- },
- };
-
- var updatedConfiguration = new FileConfiguration
- {
- GlobalConfiguration = new FileGlobalConfiguration
- {
- },
- ReRoutes = new List()
- {
- new FileReRoute()
- {
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 80,
- }
- },
- DownstreamScheme = "http",
- DownstreamPathTemplate = "/geoffrey",
- UpstreamHttpMethod = new List { "get" },
- UpstreamPathTemplate = "/"
- },
- new FileReRoute()
- {
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "123.123.123",
- Port = 443,
- }
- },
- DownstreamScheme = "https",
- DownstreamPathTemplate = "/blooper/{productId}",
- UpstreamHttpMethod = new List { "post" },
- UpstreamPathTemplate = "/test"
- }
- }
- };
-
- this.Given(x => GivenThereIsAConfiguration(initialConfiguration))
- .And(x => GivenOcelotIsRunning())
- .And(x => GivenIHaveAnOcelotToken("/administration"))
- .And(x => GivenIHaveAddedATokenToMyRequest())
- .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration"))
- .When(x => WhenIPostOnTheApiGateway("/administration/configuration", updatedConfiguration))
- .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
- .And(x => ThenTheResponseShouldBe(updatedConfiguration))
- .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration"))
- .And(x => ThenTheResponseShouldBe(updatedConfiguration))
- .And(_ => ThenTheConfigurationIsSavedCorrectly(updatedConfiguration))
- .BDDfy();
- }
-
- private void ThenTheConfigurationIsSavedCorrectly(FileConfiguration expected)
- {
- var ocelotJsonPath = $"{AppContext.BaseDirectory}ocelot.json";
- var resultText = File.ReadAllText(ocelotJsonPath);
- var expectedText = JsonConvert.SerializeObject(expected, Formatting.Indented);
- resultText.ShouldBe(expectedText);
-
- var environmentSpecificPath = $"{AppContext.BaseDirectory}/ocelot.Production.json";
- resultText = File.ReadAllText(environmentSpecificPath);
- expectedText = JsonConvert.SerializeObject(expected, Formatting.Indented);
- resultText.ShouldBe(expectedText);
- }
-
- [Fact]
- public void should_get_file_configuration_edit_and_post_updated_version_redirecting_reroute()
- {
- var fooPort = 47689;
- var barPort = 47690;
-
- var initialConfiguration = new FileConfiguration
- {
- ReRoutes = new List()
- {
- new FileReRoute()
- {
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = fooPort,
- }
- },
- DownstreamScheme = "http",
- DownstreamPathTemplate = "/foo",
- UpstreamHttpMethod = new List { "get" },
- UpstreamPathTemplate = "/foo"
- }
- }
- };
-
- var updatedConfiguration = new FileConfiguration
- {
- GlobalConfiguration = new FileGlobalConfiguration
- {
- },
- ReRoutes = new List()
- {
- new FileReRoute()
- {
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = barPort,
- }
- },
- DownstreamScheme = "http",
- DownstreamPathTemplate = "/bar",
- UpstreamHttpMethod = new List { "get" },
- UpstreamPathTemplate = "/foo"
- }
- }
- };
-
- this.Given(x => GivenThereIsAConfiguration(initialConfiguration))
- .And(x => GivenThereIsAFooServiceRunningOn($"http://localhost:{fooPort}"))
- .And(x => GivenThereIsABarServiceRunningOn($"http://localhost:{barPort}"))
- .And(x => GivenOcelotIsRunning())
- .And(x => WhenIGetUrlOnTheApiGateway("/foo"))
- .Then(x => ThenTheResponseBodyShouldBe("foo"))
- .And(x => GivenIHaveAnOcelotToken("/administration"))
- .And(x => GivenIHaveAddedATokenToMyRequest())
- .When(x => WhenIPostOnTheApiGateway("/administration/configuration", updatedConfiguration))
- .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
- .And(x => ThenTheResponseShouldBe(updatedConfiguration))
- .And(x => WhenIGetUrlOnTheApiGateway("/foo"))
- .Then(x => ThenTheResponseBodyShouldBe("bar"))
- .When(x => WhenIPostOnTheApiGateway("/administration/configuration", initialConfiguration))
- .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
- .And(x => ThenTheResponseShouldBe(initialConfiguration))
- .And(x => WhenIGetUrlOnTheApiGateway("/foo"))
- .Then(x => ThenTheResponseBodyShouldBe("foo"))
- .BDDfy();
- }
-
- [Fact]
- public void should_clear_region()
- {
- var initialConfiguration = new FileConfiguration
- {
- GlobalConfiguration = new FileGlobalConfiguration
- {
- },
- ReRoutes = new List()
- {
- new FileReRoute()
- {
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 80,
- }
- },
- DownstreamScheme = "https",
- DownstreamPathTemplate = "/",
- UpstreamHttpMethod = new List { "get" },
- UpstreamPathTemplate = "/",
- FileCacheOptions = new FileCacheOptions
- {
- TtlSeconds = 10
- }
- },
- new FileReRoute()
- {
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 80,
- }
- },
- DownstreamScheme = "https",
- DownstreamPathTemplate = "/",
- UpstreamHttpMethod = new List { "get" },
- UpstreamPathTemplate = "/test",
- FileCacheOptions = new FileCacheOptions
- {
- TtlSeconds = 10
- }
- }
- }
- };
-
- var regionToClear = "gettest";
-
- this.Given(x => GivenThereIsAConfiguration(initialConfiguration))
- .And(x => GivenOcelotIsRunning())
- .And(x => GivenIHaveAnOcelotToken("/administration"))
- .And(x => GivenIHaveAddedATokenToMyRequest())
- .When(x => WhenIDeleteOnTheApiGateway($"/administration/outputcache/{regionToClear}"))
- .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.NoContent))
- .BDDfy();
- }
-
- [Fact]
- public void should_return_response_200_with_call_re_routes_controller_when_using_own_identity_server_to_secure_admin_area()
- {
- var configuration = new FileConfiguration();
-
- var identityServerRootUrl = "http://localhost:5123";
-
- Action options = o =>
- {
- o.Authority = identityServerRootUrl;
- o.ApiName = "api";
- o.RequireHttpsMetadata = false;
- o.SupportedTokens = SupportedTokens.Both;
- o.ApiSecret = "secret";
- };
-
- this.Given(x => GivenThereIsAConfiguration(configuration))
- .And(x => GivenThereIsAnIdentityServerOn(identityServerRootUrl, "api"))
- .And(x => GivenOcelotIsRunningWithIdentityServerSettings(options))
- .And(x => GivenIHaveAToken(identityServerRootUrl))
- .And(x => GivenIHaveAddedATokenToMyRequest())
- .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration"))
- .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
- .BDDfy();
- }
-
- private void GivenIHaveAToken(string url)
- {
- var formData = new List>
- {
- new KeyValuePair("client_id", "api"),
- new KeyValuePair("client_secret", "secret"),
- new KeyValuePair("scope", "api"),
- new KeyValuePair("username", "test"),
- new KeyValuePair("password", "test"),
- new KeyValuePair("grant_type", "password")
- };
- var content = new FormUrlEncodedContent(formData);
-
- using (var httpClient = new HttpClient())
- {
- var response = httpClient.PostAsync($"{url}/connect/token", content).Result;
- var responseContent = response.Content.ReadAsStringAsync().Result;
- response.EnsureSuccessStatusCode();
- _token = JsonConvert.DeserializeObject(responseContent);
- }
- }
-
- private void GivenThereIsAnIdentityServerOn(string url, string apiName)
- {
- _identityServerBuilder = Host.CreateDefaultBuilder()
- .ConfigureWebHost(webBuilder =>
- {
- webBuilder.UseUrls(url)
- .UseKestrel()
- .UseContentRoot(Directory.GetCurrentDirectory())
- .ConfigureServices(services =>
- {
- services.AddLogging();
- services.AddIdentityServer()
- .AddDeveloperSigningCredential()
- .AddInMemoryApiResources(new List
- {
- new ApiResource
- {
- Name = apiName,
- Description = apiName,
- Enabled = true,
- DisplayName = apiName,
- Scopes = new List()
- {
- new Scope(apiName),
- },
- },
- })
- .AddInMemoryClients(new List
- {
- new Client
- {
- ClientId = apiName,
- AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
- ClientSecrets = new List { new Secret("secret".Sha256()) },
- AllowedScopes = new List { apiName },
- AccessTokenType = AccessTokenType.Jwt,
- Enabled = true
- },
- })
- .AddTestUsers(new List
- {
- new TestUser
- {
- Username = "test",
- Password = "test",
- SubjectId = "1231231"
- },
- });
- })
- .Configure(app =>
- {
- app.UseIdentityServer();
- }
- );
- }).Build();
-
- _identityServerBuilder.Start();
-
- using (var httpClient = new HttpClient())
- {
- var response = httpClient.GetAsync($"{url}/.well-known/openid-configuration").Result;
- response.EnsureSuccessStatusCode();
- }
- }
-
- private void GivenAnotherOcelotIsRunning(string baseUrl)
- {
- _httpClientTwo.BaseAddress = new Uri(baseUrl);
-
- _webHostBuilderTwo = Host.CreateDefaultBuilder()
- .ConfigureWebHost(webBuilder =>
- {
- webBuilder.UseUrls(baseUrl)
- .UseKestrel()
- .UseContentRoot(Directory.GetCurrentDirectory())
- .ConfigureAppConfiguration((hostingContext, config) =>
- {
- config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath);
- var env = hostingContext.HostingEnvironment;
- config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
- .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false);
- config.AddJsonFile("ocelot.json", false, false);
- config.AddEnvironmentVariables();
- })
- .ConfigureServices(x =>
- {
- x.AddMvc(option => option.EnableEndpointRouting = false);
- x.AddOcelot()
- .AddAdministration("/administration", "secret");
- })
- .Configure(app =>
- {
- app.UseOcelot().Wait();
- });
- });
-
- _builderTwo = _webHostBuilderTwo.Build();
-
- _builderTwo.Start();
- }
-
- private void GivenIdentityServerSigningEnvironmentalVariablesAreSet()
- {
- Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE", "idsrv3test.pfx");
- Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD", "idsrv3test");
- }
-
- private void WhenIGetUrlOnTheSecondOcelot(string url)
- {
- _httpClientTwo.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken);
- _response = _httpClientTwo.GetAsync(url).Result;
- }
-
- private void WhenIPostOnTheApiGateway(string url, FileConfiguration updatedConfiguration)
- {
- var json = JsonConvert.SerializeObject(updatedConfiguration);
- var content = new StringContent(json);
- content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
- _response = _httpClient.PostAsync(url, content).Result;
- }
-
- private void ThenTheResponseShouldBe(List expected)
- {
- var content = _response.Content.ReadAsStringAsync().Result;
- var result = JsonConvert.DeserializeObject(content);
- result.Value.ShouldBe(expected);
- }
-
- private void ThenTheResponseBodyShouldBe(string expected)
- {
- var content = _response.Content.ReadAsStringAsync().Result;
- content.ShouldBe(expected);
- }
-
- private void ThenTheResponseShouldBe(FileConfiguration expecteds)
- {
- var response = JsonConvert.DeserializeObject(_response.Content.ReadAsStringAsync().Result);
-
- response.GlobalConfiguration.RequestIdKey.ShouldBe(expecteds.GlobalConfiguration.RequestIdKey);
- response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Host);
- response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Port);
-
- for (var i = 0; i < response.ReRoutes.Count; i++)
- {
- for (var j = 0; j < response.ReRoutes[i].DownstreamHostAndPorts.Count; j++)
- {
- var result = response.ReRoutes[i].DownstreamHostAndPorts[j];
- var expected = expecteds.ReRoutes[i].DownstreamHostAndPorts[j];
- result.Host.ShouldBe(expected.Host);
- result.Port.ShouldBe(expected.Port);
- }
-
- response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expecteds.ReRoutes[i].DownstreamPathTemplate);
- response.ReRoutes[i].DownstreamScheme.ShouldBe(expecteds.ReRoutes[i].DownstreamScheme);
- response.ReRoutes[i].UpstreamPathTemplate.ShouldBe(expecteds.ReRoutes[i].UpstreamPathTemplate);
- response.ReRoutes[i].UpstreamHttpMethod.ShouldBe(expecteds.ReRoutes[i].UpstreamHttpMethod);
- }
- }
-
- private void GivenIHaveAddedATokenToMyRequest()
- {
- _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken);
- }
-
- private void GivenIHaveAnOcelotToken(string adminPath)
- {
- var tokenUrl = $"{adminPath}/connect/token";
- var formData = new List>
- {
- new KeyValuePair("client_id", "admin"),
- new KeyValuePair("client_secret", "secret"),
- new KeyValuePair("scope", "admin"),
- new KeyValuePair("grant_type", "client_credentials")
- };
- var content = new FormUrlEncodedContent(formData);
-
- var response = _httpClient.PostAsync(tokenUrl, content).Result;
- var responseContent = response.Content.ReadAsStringAsync().Result;
- response.EnsureSuccessStatusCode();
- _token = JsonConvert.DeserializeObject(responseContent);
- var configPath = $"{adminPath}/.well-known/openid-configuration";
- response = _httpClient.GetAsync(configPath).Result;
- response.EnsureSuccessStatusCode();
- }
-
- private void GivenOcelotIsRunningWithIdentityServerSettings(Action configOptions)
- {
- _webHostBuilder = Host.CreateDefaultBuilder()
- .ConfigureWebHost(webBuilder =>
- {
- webBuilder.UseUrls(_ocelotBaseUrl)
- .UseKestrel()
- .UseContentRoot(Directory.GetCurrentDirectory())
- .ConfigureAppConfiguration((hostingContext, config) =>
- {
- config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath);
- var env = hostingContext.HostingEnvironment;
- config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
- .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false);
- config.AddJsonFile("ocelot.json", false, false);
- config.AddEnvironmentVariables();
- })
- .ConfigureServices(x =>
- {
- x.AddMvc(option => option.EnableEndpointRouting = false);
- x.AddSingleton(_webHostBuilder);
- x.AddOcelot()
- .AddAdministration("/administration", configOptions);
- })
- .Configure(app =>
- {
- app.UseOcelot().Wait();
- });
- });
-
- _builder = _webHostBuilder.Build();
-
- _builder.Start();
- }
-
- private void GivenOcelotIsRunning()
- {
- _webHostBuilder = Host.CreateDefaultBuilder()
- .ConfigureWebHost(webBuilder =>
- {
- webBuilder.UseUrls(_ocelotBaseUrl)
- .UseKestrel()
- .UseContentRoot(Directory.GetCurrentDirectory())
- .ConfigureAppConfiguration((hostingContext, config) =>
- {
- config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath);
- var env = hostingContext.HostingEnvironment;
- config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
- .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false);
- config.AddJsonFile("ocelot.json", false, false);
- config.AddEnvironmentVariables();
- })
- .ConfigureServices(x =>
- {
- x.AddMvc(s => s.EnableEndpointRouting = false);
- x.AddOcelot()
- .AddAdministration("/administration", "secret");
- })
- .Configure(app =>
- {
- app.UseOcelot().Wait();
- });
- });
-
- _builder = _webHostBuilder.Build();
-
- _builder.Start();
- }
-
- private void GivenOcelotIsRunningWithNoWebHostBuilder(string baseUrl)
- {
- _webHostBuilder = Host.CreateDefaultBuilder()
- .ConfigureWebHost(webBuilder =>
- {
- webBuilder.UseUrls(_ocelotBaseUrl)
- .UseKestrel()
- .UseContentRoot(Directory.GetCurrentDirectory())
- .ConfigureAppConfiguration((hostingContext, config) =>
- {
- config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath);
- var env = hostingContext.HostingEnvironment;
- config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
- .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false);
- config.AddJsonFile("ocelot.json", false, false);
- config.AddEnvironmentVariables();
- })
- .ConfigureServices(x =>
- {
- x.AddMvc(option => option.EnableEndpointRouting = false);
- x.AddSingleton(_webHostBuilder);
- x.AddOcelot()
- .AddAdministration("/administration", "secret");
- })
- .Configure(app =>
- {
- app.UseOcelot().Wait();
- });
- });
-
- _builder = _webHostBuilder.Build();
-
- _builder.Start();
- }
-
- private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration)
- {
- var configurationPath = $"{Directory.GetCurrentDirectory()}/ocelot.json";
-
- var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration);
-
- if (File.Exists(configurationPath))
- {
- File.Delete(configurationPath);
- }
-
- File.WriteAllText(configurationPath, jsonConfiguration);
-
- var text = File.ReadAllText(configurationPath);
-
- configurationPath = $"{AppContext.BaseDirectory}/ocelot.json";
-
- if (File.Exists(configurationPath))
- {
- File.Delete(configurationPath);
- }
-
- File.WriteAllText(configurationPath, jsonConfiguration);
-
- text = File.ReadAllText(configurationPath);
- }
-
- private void WhenIGetUrlOnTheApiGateway(string url)
- {
- _response = _httpClient.GetAsync(url).Result;
- }
-
- private void WhenIDeleteOnTheApiGateway(string url)
- {
- _response = _httpClient.DeleteAsync(url).Result;
- }
-
- private void ThenTheStatusCodeShouldBe(HttpStatusCode expectedHttpStatusCode)
- {
- _response.StatusCode.ShouldBe(expectedHttpStatusCode);
- }
-
- public void Dispose()
- {
- Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE", "");
- Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD", "");
- _builder?.Dispose();
- _httpClient?.Dispose();
- _identityServerBuilder?.Dispose();
- }
-
- private void GivenThereIsAFooServiceRunningOn(string baseUrl)
- {
- _fooServiceBuilder = Host.CreateDefaultBuilder()
- .ConfigureWebHost(webBuilder =>
- {
- webBuilder.UseUrls(baseUrl)
- .UseKestrel()
- .UseContentRoot(Directory.GetCurrentDirectory())
- .UseIISIntegration()
- .Configure(app =>
- {
- app.UsePathBase("/foo");
- app.Run(async context =>
- {
- context.Response.StatusCode = 200;
- await context.Response.WriteAsync("foo");
- });
- });
- }).Build();
-
- _fooServiceBuilder.Start();
- }
-
- private void GivenThereIsABarServiceRunningOn(string baseUrl)
- {
- _barServiceBuilder = Host.CreateDefaultBuilder()
- .ConfigureWebHost(webBuilder =>
- {
- webBuilder.UseUrls(baseUrl)
- .UseKestrel()
- .UseContentRoot(Directory.GetCurrentDirectory())
- .UseIISIntegration()
- .Configure(app =>
- {
- app.UsePathBase("/bar");
- app.Run(async context =>
- {
- context.Response.StatusCode = 200;
- await context.Response.WriteAsync("bar");
- });
- });
- }).Build();
-
- _barServiceBuilder.Start();
- }
- }
-}
+using IdentityServer4.AccessTokenValidation;
+using IdentityServer4.Models;
+using IdentityServer4.Test;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Newtonsoft.Json;
+using Ocelot.Administration;
+using Ocelot.Cache;
+using Ocelot.Configuration.File;
+using Ocelot.DependencyInjection;
+using Ocelot.Middleware;
+using Shouldly;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using TestStack.BDDfy;
+using Xunit;
+
+namespace Ocelot.IntegrationTests
+{
+ public class AdministrationTests : IDisposable
+ {
+ private HttpClient _httpClient;
+ private readonly HttpClient _httpClientTwo;
+ private HttpResponseMessage _response;
+ private IHost _builder;
+ private IHostBuilder _webHostBuilder;
+ private string _ocelotBaseUrl;
+ private BearerToken _token;
+ private IHostBuilder _webHostBuilderTwo;
+ private IHost _builderTwo;
+ private IHost _identityServerBuilder;
+ private IHost _fooServiceBuilder;
+ private IHost _barServiceBuilder;
+
+ public AdministrationTests()
+ {
+ _httpClient = new HttpClient();
+ _httpClientTwo = new HttpClient();
+ _ocelotBaseUrl = "http://localhost:5000";
+ _httpClient.BaseAddress = new Uri(_ocelotBaseUrl);
+ }
+
+ [Fact]
+ public void should_return_response_401_with_call_re_routes_controller()
+ {
+ var configuration = new FileConfiguration();
+
+ this.Given(x => GivenThereIsAConfiguration(configuration))
+ .And(x => GivenOcelotIsRunning())
+ .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration"))
+ .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized))
+ .BDDfy();
+ }
+
+ [Fact]
+ public void should_return_response_200_with_call_re_routes_controller()
+ {
+ var configuration = new FileConfiguration();
+
+ this.Given(x => GivenThereIsAConfiguration(configuration))
+ .And(x => GivenOcelotIsRunning())
+ .And(x => GivenIHaveAnOcelotToken("/administration"))
+ .And(x => GivenIHaveAddedATokenToMyRequest())
+ .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration"))
+ .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
+ .BDDfy();
+ }
+
+ [Fact]
+ public void should_return_response_200_with_call_re_routes_controller_using_base_url_added_in_file_config()
+ {
+ _httpClient = new HttpClient();
+ _ocelotBaseUrl = "http://localhost:5011";
+ _httpClient.BaseAddress = new Uri(_ocelotBaseUrl);
+
+ var configuration = new FileConfiguration
+ {
+ GlobalConfiguration = new FileGlobalConfiguration
+ {
+ BaseUrl = _ocelotBaseUrl
+ }
+ };
+
+ this.Given(x => GivenThereIsAConfiguration(configuration))
+ .And(x => GivenOcelotIsRunningWithNoWebHostBuilder(_ocelotBaseUrl))
+ .And(x => GivenIHaveAnOcelotToken("/administration"))
+ .And(x => GivenIHaveAddedATokenToMyRequest())
+ .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration"))
+ .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
+ .BDDfy();
+ }
+
+ [Fact]
+ public void should_be_able_to_use_token_from_ocelot_a_on_ocelot_b()
+ {
+ var configuration = new FileConfiguration();
+
+ this.Given(x => GivenThereIsAConfiguration(configuration))
+ .And(x => GivenIdentityServerSigningEnvironmentalVariablesAreSet())
+ .And(x => GivenOcelotIsRunning())
+ .And(x => GivenIHaveAnOcelotToken("/administration"))
+ .And(x => GivenAnotherOcelotIsRunning("http://localhost:5017"))
+ .When(x => WhenIGetUrlOnTheSecondOcelot("/administration/configuration"))
+ .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
+ .BDDfy();
+ }
+
+ [Fact]
+ public void should_return_file_configuration()
+ {
+ var configuration = new FileConfiguration
+ {
+ GlobalConfiguration = new FileGlobalConfiguration
+ {
+ RequestIdKey = "RequestId",
+ ServiceDiscoveryProvider = new FileServiceDiscoveryProvider
+ {
+ Host = "127.0.0.1",
+ }
+ },
+ ReRoutes = new List()
+ {
+ new FileReRoute()
+ {
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 80,
+ }
+ },
+ DownstreamScheme = "https",
+ DownstreamPathTemplate = "/",
+ UpstreamHttpMethod = new List { "get" },
+ UpstreamPathTemplate = "/",
+ FileCacheOptions = new FileCacheOptions
+ {
+ TtlSeconds = 10,
+ Region = "Geoff"
+ }
+ },
+ new FileReRoute()
+ {
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 80,
+ }
+ },
+ DownstreamScheme = "https",
+ DownstreamPathTemplate = "/",
+ UpstreamHttpMethod = new List { "get" },
+ UpstreamPathTemplate = "/test",
+ FileCacheOptions = new FileCacheOptions
+ {
+ TtlSeconds = 10,
+ Region = "Dave"
+ }
+ }
+ }
+ };
+
+ this.Given(x => GivenThereIsAConfiguration(configuration))
+ .And(x => GivenOcelotIsRunning())
+ .And(x => GivenIHaveAnOcelotToken("/administration"))
+ .And(x => GivenIHaveAddedATokenToMyRequest())
+ .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration"))
+ .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
+ .And(x => ThenTheResponseShouldBe(configuration))
+ .BDDfy();
+ }
+
+ [Fact]
+ public void should_get_file_configuration_edit_and_post_updated_version()
+ {
+ var initialConfiguration = new FileConfiguration
+ {
+ GlobalConfiguration = new FileGlobalConfiguration
+ {
+ },
+ ReRoutes = new List()
+ {
+ new FileReRoute()
+ {
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 80,
+ }
+ },
+ DownstreamScheme = "https",
+ DownstreamPathTemplate = "/",
+ UpstreamHttpMethod = new List { "get" },
+ UpstreamPathTemplate = "/"
+ },
+ new FileReRoute()
+ {
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 80,
+ }
+ },
+ DownstreamScheme = "https",
+ DownstreamPathTemplate = "/",
+ UpstreamHttpMethod = new List { "get" },
+ UpstreamPathTemplate = "/test"
+ }
+ },
+ };
+
+ var updatedConfiguration = new FileConfiguration
+ {
+ GlobalConfiguration = new FileGlobalConfiguration
+ {
+ },
+ ReRoutes = new List()
+ {
+ new FileReRoute()
+ {
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 80,
+ }
+ },
+ DownstreamScheme = "http",
+ DownstreamPathTemplate = "/geoffrey",
+ UpstreamHttpMethod = new List { "get" },
+ UpstreamPathTemplate = "/"
+ },
+ new FileReRoute()
+ {
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "123.123.123",
+ Port = 443,
+ }
+ },
+ DownstreamScheme = "https",
+ DownstreamPathTemplate = "/blooper/{productId}",
+ UpstreamHttpMethod = new List { "post" },
+ UpstreamPathTemplate = "/test"
+ }
+ }
+ };
+
+ this.Given(x => GivenThereIsAConfiguration(initialConfiguration))
+ .And(x => GivenOcelotIsRunning())
+ .And(x => GivenIHaveAnOcelotToken("/administration"))
+ .And(x => GivenIHaveAddedATokenToMyRequest())
+ .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration"))
+ .When(x => WhenIPostOnTheApiGateway("/administration/configuration", updatedConfiguration))
+ .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
+ .And(x => ThenTheResponseShouldBe(updatedConfiguration))
+ .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration"))
+ .And(x => ThenTheResponseShouldBe(updatedConfiguration))
+ .And(_ => ThenTheConfigurationIsSavedCorrectly(updatedConfiguration))
+ .BDDfy();
+ }
+
+ private void ThenTheConfigurationIsSavedCorrectly(FileConfiguration expected)
+ {
+ var ocelotJsonPath = $"{AppContext.BaseDirectory}ocelot.json";
+ var resultText = File.ReadAllText(ocelotJsonPath);
+ var expectedText = JsonConvert.SerializeObject(expected, Formatting.Indented);
+ resultText.ShouldBe(expectedText);
+
+ var environmentSpecificPath = $"{AppContext.BaseDirectory}/ocelot.Production.json";
+ resultText = File.ReadAllText(environmentSpecificPath);
+ expectedText = JsonConvert.SerializeObject(expected, Formatting.Indented);
+ resultText.ShouldBe(expectedText);
+ }
+
+ [Fact]
+ public void should_get_file_configuration_edit_and_post_updated_version_redirecting_reroute()
+ {
+ var fooPort = 47689;
+ var barPort = 47690;
+
+ var initialConfiguration = new FileConfiguration
+ {
+ ReRoutes = new List()
+ {
+ new FileReRoute()
+ {
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = fooPort,
+ }
+ },
+ DownstreamScheme = "http",
+ DownstreamPathTemplate = "/foo",
+ UpstreamHttpMethod = new List { "get" },
+ UpstreamPathTemplate = "/foo"
+ }
+ }
+ };
+
+ var updatedConfiguration = new FileConfiguration
+ {
+ GlobalConfiguration = new FileGlobalConfiguration
+ {
+ },
+ ReRoutes = new List()
+ {
+ new FileReRoute()
+ {
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = barPort,
+ }
+ },
+ DownstreamScheme = "http",
+ DownstreamPathTemplate = "/bar",
+ UpstreamHttpMethod = new List { "get" },
+ UpstreamPathTemplate = "/foo"
+ }
+ }
+ };
+
+ this.Given(x => GivenThereIsAConfiguration(initialConfiguration))
+ .And(x => GivenThereIsAFooServiceRunningOn($"http://localhost:{fooPort}"))
+ .And(x => GivenThereIsABarServiceRunningOn($"http://localhost:{barPort}"))
+ .And(x => GivenOcelotIsRunning())
+ .And(x => WhenIGetUrlOnTheApiGateway("/foo"))
+ .Then(x => ThenTheResponseBodyShouldBe("foo"))
+ .And(x => GivenIHaveAnOcelotToken("/administration"))
+ .And(x => GivenIHaveAddedATokenToMyRequest())
+ .When(x => WhenIPostOnTheApiGateway("/administration/configuration", updatedConfiguration))
+ .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
+ .And(x => ThenTheResponseShouldBe(updatedConfiguration))
+ .And(x => WhenIGetUrlOnTheApiGateway("/foo"))
+ .Then(x => ThenTheResponseBodyShouldBe("bar"))
+ .When(x => WhenIPostOnTheApiGateway("/administration/configuration", initialConfiguration))
+ .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
+ .And(x => ThenTheResponseShouldBe(initialConfiguration))
+ .And(x => WhenIGetUrlOnTheApiGateway("/foo"))
+ .Then(x => ThenTheResponseBodyShouldBe("foo"))
+ .BDDfy();
+ }
+
+ [Fact]
+ public void should_clear_region()
+ {
+ var initialConfiguration = new FileConfiguration
+ {
+ GlobalConfiguration = new FileGlobalConfiguration
+ {
+ },
+ ReRoutes = new List()
+ {
+ new FileReRoute()
+ {
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 80,
+ }
+ },
+ DownstreamScheme = "https",
+ DownstreamPathTemplate = "/",
+ UpstreamHttpMethod = new List { "get" },
+ UpstreamPathTemplate = "/",
+ FileCacheOptions = new FileCacheOptions
+ {
+ TtlSeconds = 10
+ }
+ },
+ new FileReRoute()
+ {
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 80,
+ }
+ },
+ DownstreamScheme = "https",
+ DownstreamPathTemplate = "/",
+ UpstreamHttpMethod = new List { "get" },
+ UpstreamPathTemplate = "/test",
+ FileCacheOptions = new FileCacheOptions
+ {
+ TtlSeconds = 10
+ }
+ }
+ }
+ };
+
+ var regionToClear = "gettest";
+
+ this.Given(x => GivenThereIsAConfiguration(initialConfiguration))
+ .And(x => GivenOcelotIsRunning())
+ .And(x => GivenIHaveAnOcelotToken("/administration"))
+ .And(x => GivenIHaveAddedATokenToMyRequest())
+ .When(x => WhenIDeleteOnTheApiGateway($"/administration/outputcache/{regionToClear}"))
+ .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.NoContent))
+ .BDDfy();
+ }
+
+ [Fact]
+ public void should_return_response_200_with_call_re_routes_controller_when_using_own_identity_server_to_secure_admin_area()
+ {
+ var configuration = new FileConfiguration();
+
+ var identityServerRootUrl = "http://localhost:5123";
+
+ Action options = o =>
+ {
+ o.Authority = identityServerRootUrl;
+ o.ApiName = "api";
+ o.RequireHttpsMetadata = false;
+ o.SupportedTokens = SupportedTokens.Both;
+ o.ApiSecret = "secret";
+ };
+
+ this.Given(x => GivenThereIsAConfiguration(configuration))
+ .And(x => GivenThereIsAnIdentityServerOn(identityServerRootUrl, "api"))
+ .And(x => GivenOcelotIsRunningWithIdentityServerSettings(options))
+ .And(x => GivenIHaveAToken(identityServerRootUrl))
+ .And(x => GivenIHaveAddedATokenToMyRequest())
+ .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration"))
+ .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
+ .BDDfy();
+ }
+
+ private void GivenIHaveAToken(string url)
+ {
+ var formData = new List>
+ {
+ new KeyValuePair("client_id", "api"),
+ new KeyValuePair("client_secret", "secret"),
+ new KeyValuePair("scope", "api"),
+ new KeyValuePair("username", "test"),
+ new KeyValuePair("password", "test"),
+ new KeyValuePair("grant_type", "password")
+ };
+ var content = new FormUrlEncodedContent(formData);
+
+ using (var httpClient = new HttpClient())
+ {
+ var response = httpClient.PostAsync($"{url}/connect/token", content).Result;
+ var responseContent = response.Content.ReadAsStringAsync().Result;
+ response.EnsureSuccessStatusCode();
+ _token = JsonConvert.DeserializeObject(responseContent);
+ }
+ }
+
+ private void GivenThereIsAnIdentityServerOn(string url, string apiName)
+ {
+ _identityServerBuilder = Host.CreateDefaultBuilder()
+ .ConfigureWebHost(webBuilder =>
+ {
+ webBuilder.UseUrls(url)
+ .UseKestrel()
+ .UseContentRoot(Directory.GetCurrentDirectory())
+ .ConfigureServices(services =>
+ {
+ services.AddLogging();
+ services.AddIdentityServer()
+ .AddDeveloperSigningCredential()
+ .AddInMemoryApiResources(new List
+ {
+ new ApiResource
+ {
+ Name = apiName,
+ Description = apiName,
+ Enabled = true,
+ DisplayName = apiName,
+ Scopes = new List()
+ {
+ new Scope(apiName),
+ },
+ },
+ })
+ .AddInMemoryClients(new List
+ {
+ new Client
+ {
+ ClientId = apiName,
+ AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
+ ClientSecrets = new List { new Secret("secret".Sha256()) },
+ AllowedScopes = new List { apiName },
+ AccessTokenType = AccessTokenType.Jwt,
+ Enabled = true
+ },
+ })
+ .AddTestUsers(new List
+ {
+ new TestUser
+ {
+ Username = "test",
+ Password = "test",
+ SubjectId = "1231231"
+ },
+ });
+ })
+ .Configure(app =>
+ {
+ app.UseIdentityServer();
+ }
+ );
+ }).Build();
+
+ _identityServerBuilder.Start();
+
+ using (var httpClient = new HttpClient())
+ {
+ var response = httpClient.GetAsync($"{url}/.well-known/openid-configuration").Result;
+ response.EnsureSuccessStatusCode();
+ }
+ }
+
+ private void GivenAnotherOcelotIsRunning(string baseUrl)
+ {
+ _httpClientTwo.BaseAddress = new Uri(baseUrl);
+
+ _webHostBuilderTwo = Host.CreateDefaultBuilder()
+ .ConfigureWebHost(webBuilder =>
+ {
+ webBuilder.UseUrls(baseUrl)
+ .UseKestrel()
+ .UseContentRoot(Directory.GetCurrentDirectory())
+ .ConfigureAppConfiguration((hostingContext, config) =>
+ {
+ config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath);
+ var env = hostingContext.HostingEnvironment;
+ config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
+ .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false);
+ config.AddJsonFile("ocelot.json", false, false);
+ config.AddEnvironmentVariables();
+ })
+ .ConfigureServices(x =>
+ {
+ x.AddMvc(option => option.EnableEndpointRouting = false);
+ x.AddOcelot()
+ .AddAdministration("/administration", "secret");
+ })
+ .Configure(app =>
+ {
+ app.UseOcelot().Wait();
+ });
+ });
+
+ _builderTwo = _webHostBuilderTwo.Build();
+
+ _builderTwo.Start();
+ }
+
+ private void GivenIdentityServerSigningEnvironmentalVariablesAreSet()
+ {
+ Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE", "idsrv3test.pfx");
+ Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD", "idsrv3test");
+ }
+
+ private void WhenIGetUrlOnTheSecondOcelot(string url)
+ {
+ _httpClientTwo.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken);
+ _response = _httpClientTwo.GetAsync(url).Result;
+ }
+
+ private void WhenIPostOnTheApiGateway(string url, FileConfiguration updatedConfiguration)
+ {
+ var json = JsonConvert.SerializeObject(updatedConfiguration);
+ var content = new StringContent(json);
+ content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
+ _response = _httpClient.PostAsync(url, content).Result;
+ }
+
+ private void ThenTheResponseShouldBe(List expected)
+ {
+ var content = _response.Content.ReadAsStringAsync().Result;
+ var result = JsonConvert.DeserializeObject(content);
+ result.Value.ShouldBe(expected);
+ }
+
+ private void ThenTheResponseBodyShouldBe(string expected)
+ {
+ var content = _response.Content.ReadAsStringAsync().Result;
+ content.ShouldBe(expected);
+ }
+
+ private void ThenTheResponseShouldBe(FileConfiguration expecteds)
+ {
+ var response = JsonConvert.DeserializeObject(_response.Content.ReadAsStringAsync().Result);
+
+ response.GlobalConfiguration.RequestIdKey.ShouldBe(expecteds.GlobalConfiguration.RequestIdKey);
+ response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Host);
+ response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Port);
+
+ for (var i = 0; i < response.ReRoutes.Count; i++)
+ {
+ for (var j = 0; j < response.ReRoutes[i].DownstreamHostAndPorts.Count; j++)
+ {
+ var result = response.ReRoutes[i].DownstreamHostAndPorts[j];
+ var expected = expecteds.ReRoutes[i].DownstreamHostAndPorts[j];
+ result.Host.ShouldBe(expected.Host);
+ result.Port.ShouldBe(expected.Port);
+ }
+
+ response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expecteds.ReRoutes[i].DownstreamPathTemplate);
+ response.ReRoutes[i].DownstreamScheme.ShouldBe(expecteds.ReRoutes[i].DownstreamScheme);
+ response.ReRoutes[i].UpstreamPathTemplate.ShouldBe(expecteds.ReRoutes[i].UpstreamPathTemplate);
+ response.ReRoutes[i].UpstreamHttpMethod.ShouldBe(expecteds.ReRoutes[i].UpstreamHttpMethod);
+ }
+ }
+
+ private void GivenIHaveAddedATokenToMyRequest()
+ {
+ _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken);
+ }
+
+ private void GivenIHaveAnOcelotToken(string adminPath)
+ {
+ var tokenUrl = $"{adminPath}/connect/token";
+ var formData = new List>
+ {
+ new KeyValuePair("client_id", "admin"),
+ new KeyValuePair("client_secret", "secret"),
+ new KeyValuePair("scope", "admin"),
+ new KeyValuePair("grant_type", "client_credentials")
+ };
+ var content = new FormUrlEncodedContent(formData);
+
+ var response = _httpClient.PostAsync(tokenUrl, content).Result;
+ var responseContent = response.Content.ReadAsStringAsync().Result;
+ response.EnsureSuccessStatusCode();
+ _token = JsonConvert.DeserializeObject(responseContent);
+ var configPath = $"{adminPath}/.well-known/openid-configuration";
+ response = _httpClient.GetAsync(configPath).Result;
+ response.EnsureSuccessStatusCode();
+ }
+
+ private void GivenOcelotIsRunningWithIdentityServerSettings(Action configOptions)
+ {
+ _webHostBuilder = Host.CreateDefaultBuilder()
+ .ConfigureWebHost(webBuilder =>
+ {
+ webBuilder.UseUrls(_ocelotBaseUrl)
+ .UseKestrel()
+ .UseContentRoot(Directory.GetCurrentDirectory())
+ .ConfigureAppConfiguration((hostingContext, config) =>
+ {
+ config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath);
+ var env = hostingContext.HostingEnvironment;
+ config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
+ .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false);
+ config.AddJsonFile("ocelot.json", false, false);
+ config.AddEnvironmentVariables();
+ })
+ .ConfigureServices(x =>
+ {
+ x.AddMvc(option => option.EnableEndpointRouting = false);
+ x.AddSingleton(_webHostBuilder);
+ x.AddOcelot()
+ .AddAdministration("/administration", configOptions);
+ })
+ .Configure(app =>
+ {
+ app.UseOcelot().Wait();
+ });
+ });
+
+ _builder = _webHostBuilder.Build();
+
+ _builder.Start();
+ }
+
+ private void GivenOcelotIsRunning()
+ {
+ _webHostBuilder = Host.CreateDefaultBuilder()
+ .ConfigureWebHost(webBuilder =>
+ {
+ webBuilder.UseUrls(_ocelotBaseUrl)
+ .UseKestrel()
+ .UseContentRoot(Directory.GetCurrentDirectory())
+ .ConfigureAppConfiguration((hostingContext, config) =>
+ {
+ config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath);
+ var env = hostingContext.HostingEnvironment;
+ config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
+ .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false);
+ config.AddJsonFile("ocelot.json", false, false);
+ config.AddEnvironmentVariables();
+ })
+ .ConfigureServices(x =>
+ {
+ x.AddMvc(s => s.EnableEndpointRouting = false);
+ x.AddOcelot()
+ .AddAdministration("/administration", "secret");
+ })
+ .Configure(app =>
+ {
+ app.UseOcelot().Wait();
+ });
+ });
+
+ _builder = _webHostBuilder.Build();
+
+ _builder.Start();
+ }
+
+ private void GivenOcelotIsRunningWithNoWebHostBuilder(string baseUrl)
+ {
+ _webHostBuilder = Host.CreateDefaultBuilder()
+ .ConfigureWebHost(webBuilder =>
+ {
+ webBuilder.UseUrls(_ocelotBaseUrl)
+ .UseKestrel()
+ .UseContentRoot(Directory.GetCurrentDirectory())
+ .ConfigureAppConfiguration((hostingContext, config) =>
+ {
+ config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath);
+ var env = hostingContext.HostingEnvironment;
+ config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
+ .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false);
+ config.AddJsonFile("ocelot.json", false, false);
+ config.AddEnvironmentVariables();
+ })
+ .ConfigureServices(x =>
+ {
+ x.AddMvc(option => option.EnableEndpointRouting = false);
+ x.AddSingleton(_webHostBuilder);
+ x.AddOcelot()
+ .AddAdministration("/administration", "secret");
+ })
+ .Configure(app =>
+ {
+ app.UseOcelot().Wait();
+ });
+ });
+
+ _builder = _webHostBuilder.Build();
+
+ _builder.Start();
+ }
+
+ private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration)
+ {
+ var configurationPath = $"{Directory.GetCurrentDirectory()}/ocelot.json";
+
+ var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration);
+
+ if (File.Exists(configurationPath))
+ {
+ File.Delete(configurationPath);
+ }
+
+ File.WriteAllText(configurationPath, jsonConfiguration);
+
+ var text = File.ReadAllText(configurationPath);
+
+ configurationPath = $"{AppContext.BaseDirectory}/ocelot.json";
+
+ if (File.Exists(configurationPath))
+ {
+ File.Delete(configurationPath);
+ }
+
+ File.WriteAllText(configurationPath, jsonConfiguration);
+
+ text = File.ReadAllText(configurationPath);
+ }
+
+ private void WhenIGetUrlOnTheApiGateway(string url)
+ {
+ _response = _httpClient.GetAsync(url).Result;
+ }
+
+ private void WhenIDeleteOnTheApiGateway(string url)
+ {
+ _response = _httpClient.DeleteAsync(url).Result;
+ }
+
+ private void ThenTheStatusCodeShouldBe(HttpStatusCode expectedHttpStatusCode)
+ {
+ _response.StatusCode.ShouldBe(expectedHttpStatusCode);
+ }
+
+ public void Dispose()
+ {
+ Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE", "");
+ Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD", "");
+ _builder?.Dispose();
+ _httpClient?.Dispose();
+ _identityServerBuilder?.Dispose();
+ }
+
+ private void GivenThereIsAFooServiceRunningOn(string baseUrl)
+ {
+ _fooServiceBuilder = Host.CreateDefaultBuilder()
+ .ConfigureWebHost(webBuilder =>
+ {
+ webBuilder.UseUrls(baseUrl)
+ .UseKestrel()
+ .UseContentRoot(Directory.GetCurrentDirectory())
+ .UseIISIntegration()
+ .Configure(app =>
+ {
+ app.UsePathBase("/foo");
+ app.Run(async context =>
+ {
+ context.Response.StatusCode = 200;
+ await context.Response.WriteAsync("foo");
+ });
+ });
+ }).Build();
+
+ _fooServiceBuilder.Start();
+ }
+
+ private void GivenThereIsABarServiceRunningOn(string baseUrl)
+ {
+ _barServiceBuilder = Host.CreateDefaultBuilder()
+ .ConfigureWebHost(webBuilder =>
+ {
+ webBuilder.UseUrls(baseUrl)
+ .UseKestrel()
+ .UseContentRoot(Directory.GetCurrentDirectory())
+ .UseIISIntegration()
+ .Configure(app =>
+ {
+ app.UsePathBase("/bar");
+ app.Run(async context =>
+ {
+ context.Response.StatusCode = 200;
+ await context.Response.WriteAsync("bar");
+ });
+ });
+ }).Build();
+
+ _barServiceBuilder.Start();
+ }
+ }
+}
diff --git a/test/Ocelot.IntegrationTests/HeaderTests.cs b/test/Ocelot.IntegrationTests/HeaderTests.cs
index cf2222c2..a57644f8 100644
--- a/test/Ocelot.IntegrationTests/HeaderTests.cs
+++ b/test/Ocelot.IntegrationTests/HeaderTests.cs
@@ -1,199 +1,199 @@
-using Xunit;
-
-[assembly: CollectionBehavior(DisableTestParallelization = true)]
-
-namespace Ocelot.IntegrationTests
-{
- using Microsoft.AspNetCore.Builder;
- using Microsoft.AspNetCore.Hosting;
- using Microsoft.AspNetCore.Http;
- using Microsoft.Extensions.Configuration;
- using Newtonsoft.Json;
- using Ocelot.Configuration.File;
- using Ocelot.DependencyInjection;
- using Ocelot.Middleware;
- using Shouldly;
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
- using System.Net;
- using System.Net.Http;
- using System.Threading.Tasks;
- using TestStack.BDDfy;
-
- public class HeaderTests : IDisposable
- {
- private readonly HttpClient _httpClient;
- private IWebHost _builder;
- private IWebHostBuilder _webHostBuilder;
- private readonly string _ocelotBaseUrl;
- private IWebHost _downstreamBuilder;
- private HttpResponseMessage _response;
-
- public HeaderTests()
- {
- _httpClient = new HttpClient();
- _ocelotBaseUrl = "http://localhost:5005";
- _httpClient.BaseAddress = new Uri(_ocelotBaseUrl);
- }
-
- [Fact]
- public void should_pass_remote_ip_address_if_as_x_forwarded_for_header()
- {
- var configuration = new FileConfiguration
- {
- ReRoutes = new List
- {
- new FileReRoute
- {
- DownstreamPathTemplate = "/",
- DownstreamScheme = "http",
- DownstreamHostAndPorts = new List
- {
- new FileHostAndPort
- {
- Host = "localhost",
- Port = 6773,
- }
- },
- UpstreamPathTemplate = "/",
- UpstreamHttpMethod = new List { "Get" },
- UpstreamHeaderTransform = new Dictionary
- {
- {"X-Forwarded-For", "{RemoteIpAddress}"}
- },
- HttpHandlerOptions = new FileHttpHandlerOptions
- {
- AllowAutoRedirect = false
- }
- }
- }
- };
-
- this.Given(x => GivenThereIsAServiceRunningOn("http://localhost:6773", 200, "X-Forwarded-For"))
- .And(x => GivenThereIsAConfiguration(configuration))
- .And(x => GivenOcelotIsRunning())
- .When(x => WhenIGetUrlOnTheApiGateway("/"))
- .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
- .And(x => ThenXForwardedForIsSet())
- .BDDfy();
- }
-
- private void GivenThereIsAServiceRunningOn(string url, int statusCode, string headerKey)
- {
- _downstreamBuilder = new WebHostBuilder()
- .UseUrls(url)
- .UseKestrel()
- .UseContentRoot(Directory.GetCurrentDirectory())
- .UseIISIntegration()
- .UseUrls(url)
- .Configure(app =>
- {
- app.Run(async context =>
- {
- if (context.Request.Headers.TryGetValue(headerKey, out var values))
- {
- var result = values.First();
- context.Response.StatusCode = statusCode;
- await context.Response.WriteAsync(result);
- }
- });
- })
- .Build();
-
- _downstreamBuilder.Start();
- }
-
- private void GivenOcelotIsRunning()
- {
- _webHostBuilder = new WebHostBuilder()
- .UseUrls(_ocelotBaseUrl)
- .UseKestrel()
- .UseContentRoot(Directory.GetCurrentDirectory())
- .ConfigureAppConfiguration((hostingContext, config) =>
- {
- config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath);
- var env = hostingContext.HostingEnvironment;
- config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
- .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false);
- config.AddJsonFile("ocelot.json", false, false);
- config.AddEnvironmentVariables();
- })
- .ConfigureServices(x =>
- {
- x.AddOcelot();
- })
- .Configure(app =>
- {
- app.UseOcelot().Wait();
- });
-
- _builder = _webHostBuilder.Build();
-
- _builder.Start();
- }
-
- private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration)
- {
- var configurationPath = $"{Directory.GetCurrentDirectory()}/ocelot.json";
-
- var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration);
-
- if (File.Exists(configurationPath))
- {
- File.Delete(configurationPath);
- }
-
- File.WriteAllText(configurationPath, jsonConfiguration);
-
- var text = File.ReadAllText(configurationPath);
-
- configurationPath = $"{AppContext.BaseDirectory}/ocelot.json";
-
- if (File.Exists(configurationPath))
- {
- File.Delete(configurationPath);
- }
-
- File.WriteAllText(configurationPath, jsonConfiguration);
-
- text = File.ReadAllText(configurationPath);
- }
-
- public async Task WhenIGetUrlOnTheApiGateway(string url)
- {
- var request = new HttpRequestMessage(HttpMethod.Get, url);
- _response = await _httpClient.SendAsync(request);
- }
-
- private void ThenTheStatusCodeShouldBe(HttpStatusCode code)
- {
- _response.StatusCode.ShouldBe(code);
- }
-
- private void ThenXForwardedForIsSet()
- {
- var windowsOrMac = "::1";
- var linux = "127.0.0.1";
-
- var header = _response.Content.ReadAsStringAsync().Result;
-
- bool passed = false;
-
- if (header == windowsOrMac || header == linux)
- {
- passed = true;
- }
-
- passed.ShouldBeTrue();
- }
-
- public void Dispose()
- {
- _builder?.Dispose();
- _httpClient?.Dispose();
- _downstreamBuilder?.Dispose();
- }
- }
-}
+using Xunit;
+
+[assembly: CollectionBehavior(DisableTestParallelization = true)]
+
+namespace Ocelot.IntegrationTests
+{
+ using Microsoft.AspNetCore.Builder;
+ using Microsoft.AspNetCore.Hosting;
+ using Microsoft.AspNetCore.Http;
+ using Microsoft.Extensions.Configuration;
+ using Newtonsoft.Json;
+ using Ocelot.Configuration.File;
+ using Ocelot.DependencyInjection;
+ using Ocelot.Middleware;
+ using Shouldly;
+ using System;
+ using System.Collections.Generic;
+ using System.IO;
+ using System.Linq;
+ using System.Net;
+ using System.Net.Http;
+ using System.Threading.Tasks;
+ using TestStack.BDDfy;
+
+ public class HeaderTests : IDisposable
+ {
+ private readonly HttpClient _httpClient;
+ private IWebHost _builder;
+ private IWebHostBuilder _webHostBuilder;
+ private readonly string _ocelotBaseUrl;
+ private IWebHost _downstreamBuilder;
+ private HttpResponseMessage _response;
+
+ public HeaderTests()
+ {
+ _httpClient = new HttpClient();
+ _ocelotBaseUrl = "http://localhost:5010";
+ _httpClient.BaseAddress = new Uri(_ocelotBaseUrl);
+ }
+
+ [Fact]
+ public void should_pass_remote_ip_address_if_as_x_forwarded_for_header()
+ {
+ var configuration = new FileConfiguration
+ {
+ ReRoutes = new List
+ {
+ new FileReRoute
+ {
+ DownstreamPathTemplate = "/",
+ DownstreamScheme = "http",
+ DownstreamHostAndPorts = new List
+ {
+ new FileHostAndPort
+ {
+ Host = "localhost",
+ Port = 6773,
+ }
+ },
+ UpstreamPathTemplate = "/",
+ UpstreamHttpMethod = new List { "Get" },
+ UpstreamHeaderTransform = new Dictionary
+ {
+ {"X-Forwarded-For", "{RemoteIpAddress}"}
+ },
+ HttpHandlerOptions = new FileHttpHandlerOptions
+ {
+ AllowAutoRedirect = false
+ }
+ }
+ }
+ };
+
+ this.Given(x => GivenThereIsAServiceRunningOn("http://localhost:6773", 200, "X-Forwarded-For"))
+ .And(x => GivenThereIsAConfiguration(configuration))
+ .And(x => GivenOcelotIsRunning())
+ .When(x => WhenIGetUrlOnTheApiGateway("/"))
+ .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
+ .And(x => ThenXForwardedForIsSet())
+ .BDDfy();
+ }
+
+ private void GivenThereIsAServiceRunningOn(string url, int statusCode, string headerKey)
+ {
+ _downstreamBuilder = new WebHostBuilder()
+ .UseUrls(url)
+ .UseKestrel()
+ .UseContentRoot(Directory.GetCurrentDirectory())
+ .UseIISIntegration()
+ .UseUrls(url)
+ .Configure(app =>
+ {
+ app.Run(async context =>
+ {
+ if (context.Request.Headers.TryGetValue(headerKey, out var values))
+ {
+ var result = values.First();
+ context.Response.StatusCode = statusCode;
+ await context.Response.WriteAsync(result);
+ }
+ });
+ })
+ .Build();
+
+ _downstreamBuilder.Start();
+ }
+
+ private void GivenOcelotIsRunning()
+ {
+ _webHostBuilder = new WebHostBuilder()
+ .UseUrls(_ocelotBaseUrl)
+ .UseKestrel()
+ .UseContentRoot(Directory.GetCurrentDirectory())
+ .ConfigureAppConfiguration((hostingContext, config) =>
+ {
+ config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath);
+ var env = hostingContext.HostingEnvironment;
+ config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
+ .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false);
+ config.AddJsonFile("ocelot.json", false, false);
+ config.AddEnvironmentVariables();
+ })
+ .ConfigureServices(x =>
+ {
+ x.AddOcelot();
+ })
+ .Configure(app =>
+ {
+ app.UseOcelot().Wait();
+ });
+
+ _builder = _webHostBuilder.Build();
+
+ _builder.Start();
+ }
+
+ private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration)
+ {
+ var configurationPath = $"{Directory.GetCurrentDirectory()}/ocelot.json";
+
+ var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration);
+
+ if (File.Exists(configurationPath))
+ {
+ File.Delete(configurationPath);
+ }
+
+ File.WriteAllText(configurationPath, jsonConfiguration);
+
+ var text = File.ReadAllText(configurationPath);
+
+ configurationPath = $"{AppContext.BaseDirectory}/ocelot.json";
+
+ if (File.Exists(configurationPath))
+ {
+ File.Delete(configurationPath);
+ }
+
+ File.WriteAllText(configurationPath, jsonConfiguration);
+
+ text = File.ReadAllText(configurationPath);
+ }
+
+ public async Task WhenIGetUrlOnTheApiGateway(string url)
+ {
+ var request = new HttpRequestMessage(HttpMethod.Get, url);
+ _response = await _httpClient.SendAsync(request);
+ }
+
+ private void ThenTheStatusCodeShouldBe(HttpStatusCode code)
+ {
+ _response.StatusCode.ShouldBe(code);
+ }
+
+ private void ThenXForwardedForIsSet()
+ {
+ var windowsOrMac = "::1";
+ var linux = "127.0.0.1";
+
+ var header = _response.Content.ReadAsStringAsync().Result;
+
+ bool passed = false;
+
+ if (header == windowsOrMac || header == linux)
+ {
+ passed = true;
+ }
+
+ passed.ShouldBeTrue();
+ }
+
+ public void Dispose()
+ {
+ _builder?.Dispose();
+ _httpClient?.Dispose();
+ _downstreamBuilder?.Dispose();
+ }
+ }
+}
diff --git a/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj b/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj
index f38ede57..96286ca4 100644
--- a/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj
+++ b/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj
@@ -1,61 +1,61 @@
-
-
- 0.0.0-dev
- netcoreapp3.0
- Ocelot.IntegrationTests
- Exe
- Ocelot.IntegrationTests
- true
- win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64
- false
- false
- false
- ..\..\codeanalysis.ruleset
-
-
-
- PreserveNewest
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- all
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ 0.0.0-dev
+ netcoreapp3.1
+ Ocelot.IntegrationTests
+ Exe
+ Ocelot.IntegrationTests
+ true
+ win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64
+ false
+ false
+ false
+ ..\..\codeanalysis.ruleset
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ all
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj b/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj
index ccfdce53..2d31ecc3 100644
--- a/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj
+++ b/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj
@@ -1,45 +1,45 @@
-
-
- 0.0.0-dev
- netcoreapp3.0
- true
- Ocelot.ManualTest
- Exe
- Ocelot.ManualTest
- osx.10.11-x64;osx.10.12-x64;win7-x64;win10-x64
- ..\..\codeanalysis.ruleset
-
-
-
- PreserveNewest
-
-
-
-
- PreserveNewest
-
-
-
-
- PreserveNewest
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- all
-
-
-
-
-
+
+
+ 0.0.0-dev
+ netcoreapp3.1
+ true
+ Ocelot.ManualTest
+ Exe
+ Ocelot.ManualTest
+ osx.10.11-x64;osx.10.12-x64;win7-x64;win10-x64
+ ..\..\codeanalysis.ruleset
+
+
+
+ PreserveNewest
+
+
+
+
+ PreserveNewest
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ all
+
+
+
+
+
\ No newline at end of file
diff --git a/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj b/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj
index d067d869..b999c7be 100644
--- a/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj
+++ b/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj
@@ -1,97 +1,97 @@
-
-
-
- 0.0.0-dev
- netcoreapp3.0
- Ocelot.UnitTests
- Ocelot.UnitTests
- Exe
- true
- osx.10.11-x64;osx.10.12-x64;win7-x64;win10-x64
- false
- false
- false
- ..\..\codeanalysis.ruleset
-
-
-
- full
- True
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
-
-
-
-
-
-
- all
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers
-
-
+
+
+
+ 0.0.0-dev
+ netcoreapp3.1
+ Ocelot.UnitTests
+ Ocelot.UnitTests
+ Exe
+ true
+ osx.10.11-x64;osx.10.12-x64;win7-x64;win10-x64
+ false
+ false
+ false
+ ..\..\codeanalysis.ruleset
+
+
+
+ full
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+ all
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
+
\ No newline at end of file