diff --git a/.circleci/config.yml b/.circleci/config.yml
index 09178e49c1..876d46b5c3 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -1,16 +1,17 @@
version: 2.1
orbs:
- queue: eddiewebb/queue@2.2.1
+ queue: eddiewebb/queue@3.1.4
jobs:
build:
docker:
- - image: ocelot2/circleci-build:8.21.0
+ - image: ocelot2/circleci-build:latest
+ resource_class: medium+
steps:
- checkout
- run: dotnet tool restore && dotnet cake
release:
docker:
- - image: ocelot2/circleci-build:8.21.0
+ - image: ocelot2/circleci-build:latest
steps:
- checkout
- run: dotnet tool restore && dotnet cake --target=Release
@@ -18,12 +19,12 @@ workflows:
version: 2
main:
jobs:
- - queue/block_workflow:
- time: '20'
- only-on-branch: main
+ # - queue/block_workflow:
+ # time: '20'
+ # only-on-branch: main
- release:
- requires:
- - queue/block_workflow
+ # requires:
+ # - queue/block_workflow
filters:
branches:
only: main
@@ -33,7 +34,7 @@ workflows:
filters:
branches:
only: develop
- pr:
+ PR:
jobs:
- build:
filters:
diff --git a/.editorconfig b/.editorconfig
index e4e769b528..e8766a5e04 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,15 +1,246 @@
+# Remove the line below if you want to inherit .editorconfig settings from higher directories
root = true
-[*]
-end_of_line = lf
-insert_final_newline = true
+# XML files
+[*.xml]
+indent_style = space
+indent_size = 2
+# C# files
[*.cs]
-end_of_line = lf
-indent_style = space
-indent_size = 4
-# XML files
-[*.xml]
+#### Core EditorConfig Options ####
+
+# Indentation and spacing
+indent_size = 4
indent_style = space
-indent_size = 2
+tab_width = 4
+
+# New line preferences
+end_of_line = crlf
+insert_final_newline = true
+
+#### .NET Coding Conventions ####
+
+# Organize usings
+dotnet_separate_import_directive_groups = false
+dotnet_sort_system_directives_first = false
+file_header_template = unset
+
+# this. and Me. preferences
+dotnet_style_qualification_for_event = false
+dotnet_style_qualification_for_field = false
+dotnet_style_qualification_for_method = false
+dotnet_style_qualification_for_property = false
+
+# Language keywords vs BCL types preferences
+dotnet_style_predefined_type_for_locals_parameters_members = true
+dotnet_style_predefined_type_for_member_access = true
+
+# Parentheses preferences
+dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity
+
+# Modifier preferences
+dotnet_style_require_accessibility_modifiers = for_non_interface_members
+
+# Expression-level preferences
+dotnet_style_coalesce_expression = true
+dotnet_style_collection_initializer = true
+dotnet_style_explicit_tuple_names = true
+dotnet_style_namespace_match_folder = true
+dotnet_style_null_propagation = true
+dotnet_style_object_initializer = true
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+dotnet_style_prefer_auto_properties = true
+dotnet_style_prefer_collection_expression = false:suggestion
+dotnet_style_prefer_compound_assignment = true
+dotnet_style_prefer_conditional_expression_over_assignment = true
+dotnet_style_prefer_conditional_expression_over_return = true
+dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed
+dotnet_style_prefer_inferred_anonymous_type_member_names = true
+dotnet_style_prefer_inferred_tuple_names = true
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true
+dotnet_style_prefer_simplified_boolean_expressions = true
+dotnet_style_prefer_simplified_interpolation = true
+
+# Field preferences
+dotnet_style_readonly_field = true
+
+# Parameter preferences
+dotnet_code_quality_unused_parameters = all
+
+# Suppression preferences
+dotnet_remove_unnecessary_suppression_exclusions = none
+
+# New line preferences
+dotnet_style_allow_multiple_blank_lines_experimental = true
+dotnet_style_allow_statement_immediately_after_block_experimental = true
+
+#### C# Coding Conventions ####
+
+# var preferences
+csharp_style_var_elsewhere = false
+csharp_style_var_for_built_in_types = false
+csharp_style_var_when_type_is_apparent = false
+
+# Expression-bodied members
+csharp_style_expression_bodied_accessors = true:silent
+csharp_style_expression_bodied_constructors = false:silent
+csharp_style_expression_bodied_indexers = true:silent
+csharp_style_expression_bodied_lambdas = true:silent
+csharp_style_expression_bodied_local_functions = false:silent
+csharp_style_expression_bodied_methods = false:silent
+csharp_style_expression_bodied_operators = false:silent
+csharp_style_expression_bodied_properties = true:silent
+
+# Pattern matching preferences
+csharp_style_pattern_matching_over_as_with_null_check = true
+csharp_style_pattern_matching_over_is_with_cast_check = true
+csharp_style_prefer_extended_property_pattern = true
+csharp_style_prefer_not_pattern = true
+csharp_style_prefer_pattern_matching = true
+csharp_style_prefer_switch_expression = true
+
+# Null-checking preferences
+csharp_style_conditional_delegate_call = true
+
+# Modifier preferences
+csharp_prefer_static_local_function = true
+csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async
+csharp_style_prefer_readonly_struct = true
+csharp_style_prefer_readonly_struct_member = true
+
+# Code-block preferences
+csharp_prefer_braces = true:silent
+csharp_prefer_simple_using_statement = true:suggestion
+csharp_style_namespace_declarations = block_scoped:silent
+csharp_style_prefer_method_group_conversion = true:silent
+csharp_style_prefer_primary_constructors = false:suggestion
+csharp_style_prefer_top_level_statements = true:silent
+
+# Expression-level preferences
+csharp_prefer_simple_default_expression = true
+csharp_style_deconstructed_variable_declaration = true
+csharp_style_implicit_object_creation_when_type_is_apparent = true
+csharp_style_inlined_variable_declaration = true
+csharp_style_prefer_index_operator = true
+csharp_style_prefer_local_over_anonymous_function = true
+csharp_style_prefer_null_check_over_type_check = true
+csharp_style_prefer_range_operator = true
+csharp_style_prefer_tuple_swap = true
+csharp_style_prefer_utf8_string_literals = true
+csharp_style_throw_expression = true
+csharp_style_unused_value_assignment_preference = discard_variable
+csharp_style_unused_value_expression_statement_preference = discard_variable
+
+# 'using' directive preferences
+csharp_using_directive_placement = outside_namespace:silent
+
+# New line preferences
+csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true
+csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true
+csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true
+csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true
+csharp_style_allow_embedded_statements_on_same_line_experimental = true
+
+#### C# Formatting Rules ####
+
+# New line preferences
+csharp_new_line_before_catch = true
+csharp_new_line_before_else = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_members_in_anonymous_types = true
+csharp_new_line_before_members_in_object_initializers = true
+csharp_new_line_before_open_brace = all
+csharp_new_line_between_query_expression_clauses = true
+
+# Indentation preferences
+csharp_indent_block_contents = true
+csharp_indent_braces = false
+csharp_indent_case_contents = true
+csharp_indent_case_contents_when_block = true
+csharp_indent_labels = one_less_than_current
+csharp_indent_switch_labels = true
+
+# Space preferences
+csharp_space_after_cast = false
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_after_comma = true
+csharp_space_after_dot = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_after_semicolon_in_for_statement = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_around_declaration_statements = false
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_before_comma = false
+csharp_space_before_dot = false
+csharp_space_before_open_square_brackets = false
+csharp_space_before_semicolon_in_for_statement = false
+csharp_space_between_empty_square_brackets = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_name_and_open_parenthesis = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_parentheses = false
+csharp_space_between_square_brackets = false
+
+# Wrapping preferences
+csharp_preserve_single_line_blocks = true
+csharp_preserve_single_line_statements = true
+
+#### Naming styles ####
+
+# Naming rules
+
+dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
+dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
+dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
+
+dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.types_should_be_pascal_case.symbols = types
+dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
+
+dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
+dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
+
+# Symbol specifications
+
+dotnet_naming_symbols.interface.applicable_kinds = interface
+dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.interface.required_modifiers =
+
+dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
+dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.types.required_modifiers =
+
+dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
+dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.non_field_members.required_modifiers =
+
+# Naming styles
+
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+
+dotnet_naming_style.begins_with_i.required_prefix = I
+dotnet_naming_style.begins_with_i.required_suffix =
+dotnet_naming_style.begins_with_i.word_separator =
+dotnet_naming_style.begins_with_i.capitalization = pascal_case
+
+[*.{cs,vb}]
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+tab_width = 4
+indent_size = 4
+end_of_line = crlf
+dotnet_style_coalesce_expression = true:suggestion
+insert_final_newline = true
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
diff --git a/.gitignore b/.gitignore
index 1c30928ce2..f01a1095e0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -417,5 +417,4 @@ test/Ocelot.AcceptanceTests/ocelot.json
# Read the Docs
# https://ocelot.readthedocs.io
_build/
-_static/
_templates/
diff --git a/Directory.Build.props b/Directory.Build.props
deleted file mode 100644
index 4fe1ec98fb..0000000000
--- a/Directory.Build.props
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
- latest
- git
- https://github.com/ThreeMammals/Ocelot
-
- true
-
- true
- snupkg
-
-
-
-
-
diff --git a/Ocelot.Release.sln b/Ocelot.Release.sln
new file mode 100644
index 0000000000..20a76ee837
--- /dev/null
+++ b/Ocelot.Release.sln
@@ -0,0 +1,231 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.9.34728.123
+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
+ .editorconfig = .editorconfig
+ .gitignore = .gitignore
+ .readthedocs.yaml = .readthedocs.yaml
+ build.cake = build.cake
+ build.ps1 = build.ps1
+ codeanalysis.ruleset = codeanalysis.ruleset
+ .circleci\config.yml = .circleci\config.yml
+ GitVersion.yml = GitVersion.yml
+ LICENSE.md = LICENSE.md
+ README.md = README.md
+ ReleaseNotes.md = ReleaseNotes.md
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{5B401523-36DA-4491-B73A-7590A26E420B}"
+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.Testing", "test\Ocelot.Testing\Ocelot.Testing.csproj", "{AE6BCCBD-0687-4C58-B30F-4ABBC6422087}"
+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.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.Kubernetes", "src\Ocelot.Provider.Kubernetes\Ocelot.Provider.Kubernetes.csproj", "{72C8E528-B4F5-45CE-8A06-CD3787364856}"
+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.Tracing.Butterfly", "src\Ocelot.Tracing.Butterfly\Ocelot.Tracing.Butterfly.csproj", "{6045E23D-669C-4F27-AF8E-8EEE6DB3557F}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Tracing.OpenTracing", "src\Ocelot.Tracing.OpenTracing\Ocelot.Tracing.OpenTracing.csproj", "{11C622AD-8C0A-4CF4-811B-3DBB76550797}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "samples", "{8FA0CBA0-0338-48EB-B37F-83CA5022237C}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Samples.AdministrationApi", "samples\Administration\Ocelot.Samples.AdministrationApi.csproj", "{A7F0CAFA-AECB-43CA-BE89-5F5B728E7C22}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Samples.Basic.ApiGateway", "samples\Basic\Ocelot.Samples.Basic.ApiGateway.csproj", "{F00C73F4-019D-490D-8194-CA1754D717FA}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Samples.Eureka.ApiGateway", "samples\Eureka\ApiGateway\Ocelot.Samples.Eureka.ApiGateway.csproj", "{FECB0C8B-5778-4441-B10E-0C815F5106D5}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Samples.Eureka.DownstreamService", "samples\Eureka\DownstreamService\Ocelot.Samples.Eureka.DownstreamService.csproj", "{28AD7065-8DB1-4711-83BF-9EA47D75F8F7}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Samples.GraphQL", "samples\GraphQL\Ocelot.Samples.GraphQL.csproj", "{869EE931-7E4A-40AA-ADDD-D20DF34C3BB3}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Samples.Kubernetes.ApiGateway", "samples\Kubernetes\ApiGateway\Ocelot.Samples.Kubernetes.ApiGateway.csproj", "{681B6E08-114D-4B9B-8F82-E370CA29B8EC}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Samples.Kubernetes.DownstreamService", "samples\Kubernetes\DownstreamService\Ocelot.Samples.Kubernetes.DownstreamService.csproj", "{161DD558-993D-491B-AD20-966127D71E49}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Samples.OpenTracing", "samples\OpenTracing\Ocelot.Samples.OpenTracing.csproj", "{DF9EFF21-58D3-428D-8A33-ACFA24E9B6E8}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Samples.ServiceDiscovery.ApiGateway", "samples\ServiceDiscovery\ApiGateway\Ocelot.Samples.ServiceDiscovery.ApiGateway.csproj", "{F25EA682-A763-431B-9D88-012A388D3618}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Samples.ServiceDiscovery.DownstreamService", "samples\ServiceDiscovery\DownstreamService\Ocelot.Samples.ServiceDiscovery.DownstreamService.csproj", "{DCBD0AB5-85DD-4F28-9166-0A23969E19EC}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Samples.ServiceFabric.ApiGateway", "samples\ServiceFabric\ApiGateway\Ocelot.Samples.ServiceFabric.ApiGateway.csproj", "{D991C694-01F0-4F04-8135-5C133DC8E029}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Samples.ServiceFabric.DownstreamService", "samples\ServiceFabric\DownstreamService\Ocelot.Samples.ServiceFabric.DownstreamService.csproj", "{AD09D124-7DD7-4C9E-9BCC-782B579B1786}"
+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
+ {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
+ {11C622AD-8C0A-4CF4-811B-3DBB76550797}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {11C622AD-8C0A-4CF4-811B-3DBB76550797}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {11C622AD-8C0A-4CF4-811B-3DBB76550797}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {11C622AD-8C0A-4CF4-811B-3DBB76550797}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AE6BCCBD-0687-4C58-B30F-4ABBC6422087}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AE6BCCBD-0687-4C58-B30F-4ABBC6422087}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AE6BCCBD-0687-4C58-B30F-4ABBC6422087}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AE6BCCBD-0687-4C58-B30F-4ABBC6422087}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A7F0CAFA-AECB-43CA-BE89-5F5B728E7C22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A7F0CAFA-AECB-43CA-BE89-5F5B728E7C22}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A7F0CAFA-AECB-43CA-BE89-5F5B728E7C22}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A7F0CAFA-AECB-43CA-BE89-5F5B728E7C22}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F00C73F4-019D-490D-8194-CA1754D717FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F00C73F4-019D-490D-8194-CA1754D717FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F00C73F4-019D-490D-8194-CA1754D717FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F00C73F4-019D-490D-8194-CA1754D717FA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FECB0C8B-5778-4441-B10E-0C815F5106D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FECB0C8B-5778-4441-B10E-0C815F5106D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FECB0C8B-5778-4441-B10E-0C815F5106D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FECB0C8B-5778-4441-B10E-0C815F5106D5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {28AD7065-8DB1-4711-83BF-9EA47D75F8F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {28AD7065-8DB1-4711-83BF-9EA47D75F8F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {28AD7065-8DB1-4711-83BF-9EA47D75F8F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {28AD7065-8DB1-4711-83BF-9EA47D75F8F7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {869EE931-7E4A-40AA-ADDD-D20DF34C3BB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {869EE931-7E4A-40AA-ADDD-D20DF34C3BB3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {869EE931-7E4A-40AA-ADDD-D20DF34C3BB3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {869EE931-7E4A-40AA-ADDD-D20DF34C3BB3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {681B6E08-114D-4B9B-8F82-E370CA29B8EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {681B6E08-114D-4B9B-8F82-E370CA29B8EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {681B6E08-114D-4B9B-8F82-E370CA29B8EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {681B6E08-114D-4B9B-8F82-E370CA29B8EC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {161DD558-993D-491B-AD20-966127D71E49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {161DD558-993D-491B-AD20-966127D71E49}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {161DD558-993D-491B-AD20-966127D71E49}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {161DD558-993D-491B-AD20-966127D71E49}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DF9EFF21-58D3-428D-8A33-ACFA24E9B6E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DF9EFF21-58D3-428D-8A33-ACFA24E9B6E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DF9EFF21-58D3-428D-8A33-ACFA24E9B6E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DF9EFF21-58D3-428D-8A33-ACFA24E9B6E8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F25EA682-A763-431B-9D88-012A388D3618}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F25EA682-A763-431B-9D88-012A388D3618}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F25EA682-A763-431B-9D88-012A388D3618}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F25EA682-A763-431B-9D88-012A388D3618}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DCBD0AB5-85DD-4F28-9166-0A23969E19EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DCBD0AB5-85DD-4F28-9166-0A23969E19EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DCBD0AB5-85DD-4F28-9166-0A23969E19EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DCBD0AB5-85DD-4F28-9166-0A23969E19EC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D991C694-01F0-4F04-8135-5C133DC8E029}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D991C694-01F0-4F04-8135-5C133DC8E029}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D991C694-01F0-4F04-8135-5C133DC8E029}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D991C694-01F0-4F04-8135-5C133DC8E029}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AD09D124-7DD7-4C9E-9BCC-782B579B1786}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AD09D124-7DD7-4C9E-9BCC-782B579B1786}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AD09D124-7DD7-4C9E-9BCC-782B579B1786}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AD09D124-7DD7-4C9E-9BCC-782B579B1786}.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}
+ {6045E23D-669C-4F27-AF8E-8EEE6DB3557F} = {5CFB79B7-C9DC-45A4-9A75-625D92471702}
+ {72C8E528-B4F5-45CE-8A06-CD3787364856} = {5CFB79B7-C9DC-45A4-9A75-625D92471702}
+ {11C622AD-8C0A-4CF4-811B-3DBB76550797} = {5CFB79B7-C9DC-45A4-9A75-625D92471702}
+ {AE6BCCBD-0687-4C58-B30F-4ABBC6422087} = {5B401523-36DA-4491-B73A-7590A26E420B}
+ {A7F0CAFA-AECB-43CA-BE89-5F5B728E7C22} = {8FA0CBA0-0338-48EB-B37F-83CA5022237C}
+ {F00C73F4-019D-490D-8194-CA1754D717FA} = {8FA0CBA0-0338-48EB-B37F-83CA5022237C}
+ {FECB0C8B-5778-4441-B10E-0C815F5106D5} = {8FA0CBA0-0338-48EB-B37F-83CA5022237C}
+ {28AD7065-8DB1-4711-83BF-9EA47D75F8F7} = {8FA0CBA0-0338-48EB-B37F-83CA5022237C}
+ {869EE931-7E4A-40AA-ADDD-D20DF34C3BB3} = {8FA0CBA0-0338-48EB-B37F-83CA5022237C}
+ {681B6E08-114D-4B9B-8F82-E370CA29B8EC} = {8FA0CBA0-0338-48EB-B37F-83CA5022237C}
+ {161DD558-993D-491B-AD20-966127D71E49} = {8FA0CBA0-0338-48EB-B37F-83CA5022237C}
+ {DF9EFF21-58D3-428D-8A33-ACFA24E9B6E8} = {8FA0CBA0-0338-48EB-B37F-83CA5022237C}
+ {F25EA682-A763-431B-9D88-012A388D3618} = {8FA0CBA0-0338-48EB-B37F-83CA5022237C}
+ {DCBD0AB5-85DD-4F28-9166-0A23969E19EC} = {8FA0CBA0-0338-48EB-B37F-83CA5022237C}
+ {D991C694-01F0-4F04-8135-5C133DC8E029} = {8FA0CBA0-0338-48EB-B37F-83CA5022237C}
+ {AD09D124-7DD7-4C9E-9BCC-782B579B1786} = {8FA0CBA0-0338-48EB-B37F-83CA5022237C}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {21476EFF-778A-4F97-8A56-D1AF1CEC0C48}
+ EndGlobalSection
+EndGlobal
diff --git a/Ocelot.sln b/Ocelot.sln
index e40f83cfb3..f09456c44e 100644
--- a/Ocelot.sln
+++ b/Ocelot.sln
@@ -1,7 +1,6 @@
-
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
-VisualStudioVersion = 17.8.34309.116
+VisualStudioVersion = 17.9.34728.123
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5CFB79B7-C9DC-45A4-9A75-625D92471702}"
EndProject
@@ -23,8 +22,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
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}"
@@ -35,6 +32,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Benchmarks", "test\O
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.Testing", "test\Ocelot.Testing\Ocelot.Testing.csproj", "{AE6BCCBD-0687-4C58-B30F-4ABBC6422087}"
+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.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}"
@@ -43,68 +44,20 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Provider.Consul", "s
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.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}") = "Ocelot.Samples.OcelotBasic.ApiGateway", "samples\OcelotBasic\Ocelot.Samples.OcelotBasic.ApiGateway.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}") = "Ocelot.Samples.OcelotKube.ApiGateway", "samples\OcelotKube\ApiGateway\Ocelot.Samples.OcelotKube.ApiGateway.csproj", "{8500055B-2C51-4CF1-A6EE-F05BB3E9BF16}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Samples.OcelotKube.DownstreamService", "samples\OcelotKube\DownstreamService\Ocelot.Samples.OcelotKube.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}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Provider.Polly", "src\Ocelot.Provider.Polly\Ocelot.Provider.Polly.csproj", "{1F6E5DCF-8A2E-4E24-A25D-064362DE8D0E}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "graphql", "graphql", "{C15CD120-5F8D-41DE-9B21-00E3EA77D6C1}"
+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.Tracing.OpenTracing", "src\Ocelot.Tracing.OpenTracing\Ocelot.Tracing.OpenTracing.csproj", "{11C622AD-8C0A-4CF4-811B-3DBB76550797}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "open-tracing", "open-tracing", "{731C6A8A-69ED-445C-A132-C638AA93F9C7}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OcelotOpenTracing", "samples\OcelotOpenTracing\OcelotOpenTracing.csproj", "{C9427E78-4281-4F59-A66E-17C0B66550E5}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "service-discovery", "service-discovery", "{25C30AAA-12DD-4BA5-A53F-9271E54EBAB7}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Samples.ServiceDiscovery.ApiGateway", "samples\OcelotServiceDiscovery\ApiGateway\Ocelot.Samples.ServiceDiscovery.ApiGateway.csproj", "{D37209EA-C13E-42AE-B851-A8604F1FCD0E}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Samples.ServiceDiscovery.DownstreamService", "samples\OcelotServiceDiscovery\DownstreamService\Ocelot.Samples.ServiceDiscovery.DownstreamService.csproj", "{E2AC741A-4120-4D59-B5E4-16382ED45E8D}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ocelot.Testing", "test\Ocelot.Testing\Ocelot.Testing.csproj", "{AE6BCCBD-0687-4C58-B30F-4ABBC6422087}"
-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
@@ -125,6 +78,14 @@ Global
{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
+ {AE6BCCBD-0687-4C58-B30F-4ABBC6422087}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AE6BCCBD-0687-4C58-B30F-4ABBC6422087}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AE6BCCBD-0687-4C58-B30F-4ABBC6422087}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AE6BCCBD-0687-4C58-B30F-4ABBC6422087}.Release|Any CPU.Build.0 = Release|Any CPU
+ {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
{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
@@ -141,6 +102,10 @@ Global
{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
+ {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
{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
@@ -149,106 +114,30 @@ Global
{6045E23D-669C-4F27-AF8E-8EEE6DB3557F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6045E23D-669C-4F27-AF8E-8EEE6DB3557F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6045E23D-669C-4F27-AF8E-8EEE6DB3557F}.Release|Any CPU.Build.0 = Release|Any CPU
- {72C8E528-B4F5-45CE-8A06-CD3787364856}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {72C8E528-B4F5-45CE-8A06-CD3787364856}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {72C8E528-B4F5-45CE-8A06-CD3787364856}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {72C8E528-B4F5-45CE-8A06-CD3787364856}.Release|Any CPU.Build.0 = Release|Any CPU
- {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
{11C622AD-8C0A-4CF4-811B-3DBB76550797}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{11C622AD-8C0A-4CF4-811B-3DBB76550797}.Debug|Any CPU.Build.0 = Debug|Any CPU
{11C622AD-8C0A-4CF4-811B-3DBB76550797}.Release|Any CPU.ActiveCfg = Release|Any CPU
{11C622AD-8C0A-4CF4-811B-3DBB76550797}.Release|Any CPU.Build.0 = Release|Any CPU
- {C9427E78-4281-4F59-A66E-17C0B66550E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {C9427E78-4281-4F59-A66E-17C0B66550E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {C9427E78-4281-4F59-A66E-17C0B66550E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {C9427E78-4281-4F59-A66E-17C0B66550E5}.Release|Any CPU.Build.0 = Release|Any CPU
- {D37209EA-C13E-42AE-B851-A8604F1FCD0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {D37209EA-C13E-42AE-B851-A8604F1FCD0E}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {D37209EA-C13E-42AE-B851-A8604F1FCD0E}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {D37209EA-C13E-42AE-B851-A8604F1FCD0E}.Release|Any CPU.Build.0 = Release|Any CPU
- {E2AC741A-4120-4D59-B5E4-16382ED45E8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {E2AC741A-4120-4D59-B5E4-16382ED45E8D}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {E2AC741A-4120-4D59-B5E4-16382ED45E8D}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {E2AC741A-4120-4D59-B5E4-16382ED45E8D}.Release|Any CPU.Build.0 = Release|Any CPU
- {AE6BCCBD-0687-4C58-B30F-4ABBC6422087}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {AE6BCCBD-0687-4C58-B30F-4ABBC6422087}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {AE6BCCBD-0687-4C58-B30F-4ABBC6422087}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {AE6BCCBD-0687-4C58-B30F-4ABBC6422087}.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}
+ {AE6BCCBD-0687-4C58-B30F-4ABBC6422087} = {5B401523-36DA-4491-B73A-7590A26E420B}
+ {D6DF4206-0DBA-41D8-884D-C3E08290FDBB} = {5CFB79B7-C9DC-45A4-9A75-625D92471702}
{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}
+ {72C8E528-B4F5-45CE-8A06-CD3787364856} = {5CFB79B7-C9DC-45A4-9A75-625D92471702}
{1F6E5DCF-8A2E-4E24-A25D-064362DE8D0E} = {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}
{11C622AD-8C0A-4CF4-811B-3DBB76550797} = {5CFB79B7-C9DC-45A4-9A75-625D92471702}
- {731C6A8A-69ED-445C-A132-C638AA93F9C7} = {8FA0CBA0-0338-48EB-B37F-83CA5022237C}
- {C9427E78-4281-4F59-A66E-17C0B66550E5} = {731C6A8A-69ED-445C-A132-C638AA93F9C7}
- {25C30AAA-12DD-4BA5-A53F-9271E54EBAB7} = {8FA0CBA0-0338-48EB-B37F-83CA5022237C}
- {D37209EA-C13E-42AE-B851-A8604F1FCD0E} = {25C30AAA-12DD-4BA5-A53F-9271E54EBAB7}
- {E2AC741A-4120-4D59-B5E4-16382ED45E8D} = {25C30AAA-12DD-4BA5-A53F-9271E54EBAB7}
- {AE6BCCBD-0687-4C58-B30F-4ABBC6422087} = {5B401523-36DA-4491-B73A-7590A26E420B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {21476EFF-778A-4F97-8A56-D1AF1CEC0C48}
diff --git a/ReleaseNotes.md b/ReleaseNotes.md
index 6698eb051f..48db3c3bfb 100644
--- a/ReleaseNotes.md
+++ b/ReleaseNotes.md
@@ -1,63 +1,31 @@
-## November-December 2023 (version {0}) aka [Sunny Koliada](https://www.google.com/search?q=winter+solstice) release
-> Codenamed as **[Sunny Koliada](https://www.bing.com/search?q=winter+solstice)**
+## Hotfix release (version {0}) for #2031 issue
+> Route path template placeholders and their validation rules
-### Focus On
+Special thanks to **[Guillaume Gnaegi](https://github.com/ggnaegi)** and [Fabrizio Mancin](https://github.com/Fabman08)!
-
- System performance. System core performance review, redesign of system core related to routing and content streaming
+### About
+The bug is related to the [Placeholders](https://ocelot.readthedocs.io/en/latest/features/routing.html#placeholders) feature in [Configuration](https://ocelot.readthedocs.io/en/latest/features/configuration.html) and [Routing](https://ocelot.readthedocs.io/en/latest/features/routing.html).
+The bug was introduced in version [23.2.0](https://github.com/ThreeMammals/Ocelot/releases/tag/23.2.0) as a part of PR #1927.
- - Modification of the `RequestMapper` with a brand new `StreamHttpContent` class, in `Ocelot.Request.Mapper` namespace. The request body is no longer copied when it is handled by the API gateway, avoiding Out-of-Memory issues in pods/containers. This significantly reduces the gateway's memory consumption, and allows you to transfer content larger than 2 GB in streaming scenarios.
- - Introduction of a new Message Invoker pool, in `Ocelot.Requester` namespace. We have replaced the [HttpClient](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient) class with [HttpMessageInvoker](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpmessageinvoker), which is the base class for `HttpClient`. The overall logic for managing the pool has been simplified, resulting in a reduction in the number of CPU cycles.
- - Full HTTP content buffering is deactivated, resulting in a 50% reduction in memory consumption and a performance improvement of around 10%. Content is no longer copied on the API gateway, avoiding Out-of-Memory issues.
- - **TODO** Include screenshots from Production...
-
+### Breaking Change
+The new [validation rules](https://github.com/ThreeMammals/Ocelot/blob/23.2.0/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs#L45-L50) of the `FileConfigurationFluentValidator` class do not allow the Ocelot app to start when implicit [placeholders](https://ocelot.readthedocs.io/en/latest/features/routing.html#placeholders) are defined in custom implementations, such as middlewares, delegating handlers, and replaced services in the dependency injection (DI) container.
+These new rules are capable of validating explicit [placeholders](https://ocelot.readthedocs.io/en/latest/features/routing.html#placeholders) only within the `UpstreamPathTemplate` and `DownstreamPathTemplate` properties. Unfortunately, they cannot oversee implicit placeholders in custom implementations, and they do not validate early during the Ocelot app startup process.
-
- Ocelot extra packages. Total 3 Ocelot packs were updated
-
- - [Ocelot.Cache.CacheManager](https://github.com/ThreeMammals/Ocelot/tree/main/src/Ocelot.Cache.CacheManager): Introduced default cache key generator with improved performance (the `DefaultCacheKeyGenerator` class). Old version of `CacheKeyGenerator` had significant performance issue when reading full content of HTTP request for caching key calculation of MD5 hash value. This hash value was excluded from the caching key.
- - [Ocelot.Provider.Kubernetes](https://github.com/ThreeMammals/Ocelot/tree/main/src/Ocelot.Provider.Kubernetes): Fixed long lasting breaking change being added in version [15.0.0](https://github.com/ThreeMammals/Ocelot/releases/tag/15.0.0), see commit https://github.com/ThreeMammals/Ocelot/commit/6e5471a714dddb0a3a40fbb97eac2810cee1c78d. The bug persisted for more than 3 years in versions **15.0.0-22.0.1**, being masked multiple times via class renaming! **Special Thanks to @ZisisTsatsas** who once again brought this issue to our attention, and our team finally realized that we had a breaking change and the provider was broken.
+Ensure that you avoid using version [23.2.0](https://github.com/ThreeMammals/Ocelot/releases/tag/23.2.0). If you are currently on that version, upgrade to version [{0}](https://github.com/ThreeMammals/Ocelot/releases/tag/{0}) by applying this hotfix patch.
- - [Ocelot.Provider.Polly](https://github.com/ThreeMammals/Ocelot/tree/main/src/Ocelot.Provider.Polly): A minor changes without feature delivery. We are preparing for a major update to the package in the next release.
-
+### Technical info
+With version [23.2.0](https://github.com/ThreeMammals/Ocelot/releases/tag/23.2.0), particularly if you have overridden certain service classes or implemented custom logic that manipulates placeholders, you may encounter Ocelot app crashes accompanied by the following errors in the log:
+```
+One or more errors occurred. (Unable to start Ocelot, errors are: XXX)
+```
+where `XXX` are the following validation error messages:
+- `UpstreamPathTemplate 'UUU' doesn't contain the same placeholders in DownstreamPathTemplate 'DDD'`
+- `DownstreamPathTemplate 'DDD' doesn't contain the same placeholders in UpstreamPathTemplate 'UUU'`
-
- Middlewares. Total 8 Ocelot middlewares were updated
-
- - `AuthenticationMiddleware`: Added new [Multiple Authentication Schemes](https://github.com/ThreeMammals/Ocelot/pull/1870) feature by @MayorSheFF
- - `OutputCacheMiddleware`, `RequestIdMiddleware`: Added new [Cache by Header Value](https://github.com/ThreeMammals/Ocelot/pull/1172) by @EngRajabi, and redesigned as [Default CacheKeyGenerator](https://github.com/ThreeMammals/Ocelot/pull/1849) feature by @raman-m
- - `DownstreamUrlCreatorMiddleware`: Fixed [bug](https://github.com/ThreeMammals/Ocelot/issues/748) for ending/omitting slash in path templates aka [Empty placeholders](https://github.com/ThreeMammals/Ocelot/pull/1911) feature by @AlyHKafoury
- - `ConfigurationMiddleware`, `HttpRequesterMiddleware`, `ResponderMiddleware`: System upgrade for [Custom HttpMessageInvoker pooling](https://github.com/ThreeMammals/Ocelot/pull/1824) feature by @ggnaegi
- - `DownstreamRequestInitialiserMiddleware`: System upgrade for [Performance of Request Mapper](https://github.com/ThreeMammals/Ocelot/pull/1724) feature by @ggnaegi
-
+**Finally**, the [validation rules](https://github.com/ThreeMammals/Ocelot/blob/23.2.0/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs#L45-L50) resulted from the incorrect assumption that placeholders are always explicit and can be validated early. Therefore, custom implementations and feature services in the dependency injection (DI) container, which rely on or manipulate placeholders, should validate the configuration JSON and appropriate options later, directly within their service implementations.
-
- Documentation for Authentication, Caching, Kubernetes and Routing
-
- - [Authentication](https://ocelot.readthedocs.io/en/latest/features/authentication.html)
- - [Caching](https://ocelot.readthedocs.io/en/latest/features/caching.html)
- - [Kubernetes](https://ocelot.readthedocs.io/en/latest/features/kubernetes.html)
- - [Routing](https://ocelot.readthedocs.io/en/latest/features/routing.html)
-
-
-
- Stabilization aka bug fixing
-
- - See [all bugs](https://github.com/ThreeMammals/Ocelot/issues?q=is%3Aissue+milestone%3ANov-December%2723+is%3Aclosed+label%3Abug) of the [Nov-December'23](https://github.com/ThreeMammals/Ocelot/milestone/2) milestone
-
-
-
- Testing
-
- - The `Ocelot.Benchmarks` testing project has been updated with new `PayloadBenchmarks` and `ResponseBenchmarks` by @ggnaegi
- - The `Ocelot.AcceptanceTests` testing project has been refactored by @raman-m using the new `AuthenticationSteps` class, and more refactoring will be done in future releases
-
-
-### Roadmap
-We would like to share our team's plans for the future regarding: development trends, ideas, community expectations, etc.
-- **Code Review and Performance Improvements**. Without a doubt, we care about code quality every day, following best development practices. And we review, test, refactor, and redesign features with overall performance in mind. In the next few releases (versions 23.x-24.0) we will take care of: generic providers, multiplexing middleware (Aggregation feature), memory management.
-- **Server-Sent Events protocol support**. There is a lot of community interest in this HTTP-based protocol.
-- **Long Polling for Consul provider**. [Consul](https://www.consul.io/) is our leading technology for service discovery. We are constantly improving the use cases for the `Ocelot.Provider.Consul` package and trying to improve the code inside the package.
-- **QoS feature refactoring**. [Polly](https://github.com/App-vNext/Polly/) was released with the new v.8.2+ after .NET 8. So we have to update `Ocelot.Provider.Polly` package taking into account new Polly behavior of redesigned features.
-- **Brainstorming** to redesign Rate Limiting, Websockets. More details in future release notes.
-- **Planning** of support for Swagger and gRPC proto. More details in future release notes.
+### Bug Artifacts
+- Released in version: [23.2.0](https://github.com/ThreeMammals/Ocelot/releases/tag/23.2.0)
+- Introduced in: PR #1927
+- Reported bug: #2031 by @ggnaegi and tested by @Fabman08
+- Hotfix PR: #2032 by @raman-m
diff --git a/build.cake b/build.cake
index 2b1c998283..a8f7cc362f 100644
--- a/build.cake
+++ b/build.cake
@@ -1,9 +1,9 @@
-#tool "dotnet:?package=GitVersion.Tool&version=5.8.1"
-#tool "dotnet:?package=coveralls.net&version=4.0.1"
-#addin nuget:?package=Newtonsoft.Json
-#addin nuget:?package=System.Text.Encodings.Web&version=4.7.1
-#tool "nuget:?package=ReportGenerator&version=5.2.0"
-#addin Cake.Coveralls&version=1.1.0
+#tool dotnet:?package=GitVersion.Tool&version=5.12.0 // 6.0.0-beta.7 supports .NET 8, 7, 6
+#tool dotnet:?package=coveralls.net&version=4.0.1
+#tool nuget:?package=ReportGenerator&version=5.2.4
+#addin nuget:?package=Newtonsoft.Json&version=13.0.3
+#addin nuget:?package=System.Text.Encodings.Web&version=8.0.0
+#addin nuget:?package=Cake.Coveralls&version=1.1.0
#r "Spectre.Console"
using Spectre.Console
@@ -13,10 +13,8 @@ using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
-// compile
-var compileConfig = Argument("configuration", "Release");
-
-var slnFile = "./Ocelot.sln";
+const string Release = "Release"; // task name, target, and Release config name
+var compileConfig = Argument("configuration", Release); // compile
// build artifacts
var artifactsDir = Directory("artifacts");
@@ -56,13 +54,15 @@ var nugetFeedStableSymbolsUploadUrl = "https://www.nuget.org/api/v2/package";
string committedVersion = "0.0.0-dev";
GitVersion versioning = null;
int releaseId = 0;
+bool IsTechnicalRelease = false;
string gitHubUsername = "TomPallister";
string gitHubPassword = Environment.GetEnvironmentVariable("OCELOT_GITHUB_API_KEY");
var target = Argument("target", "Default");
-
-Information("target is " + target);
-Information("Build configuration is " + compileConfig);
+var slnFile = (target == Release) ? $"./Ocelot.{Release}.sln" : "./Ocelot.sln";
+Information("\nTarget: " + target);
+Information("Build: " + compileConfig);
+Information("Solution: " + slnFile);
TaskTeardown(context => {
AnsiConsole.Markup($"[green]DONE[/] {context.Task.Name}\n");
@@ -82,7 +82,7 @@ Task("RunTests")
.IsDependentOn("RunAcceptanceTests")
.IsDependentOn("RunIntegrationTests");
-Task("Release")
+Task(Release)
.IsDependentOn("Build")
.IsDependentOn("CreateReleaseNotes")
.IsDependentOn("CreateArtifacts")
@@ -94,11 +94,18 @@ Task("Compile")
.IsDependentOn("Version")
.Does(() =>
{
+ Information("Build: " + compileConfig);
+ Information("Solution: " + slnFile);
var settings = new DotNetBuildSettings
{
Configuration = compileConfig,
};
-
+ if (target != Release)
+ {
+ settings.Framework = "net8.0"; // build using .NET 8 SDK only
+ }
+ Information($"Settings {nameof(DotNetBuildSettings.Framework)}: {settings.Framework}");
+ Information($"Settings {nameof(DotNetBuildSettings.Configuration)}: {settings.Configuration}");
DotNetBuild(slnFile, settings);
});
@@ -154,12 +161,18 @@ Task("CreateReleaseNotes")
var lastReleaseTags = GitHelper("describe --tags --abbrev=0 --exclude net*");
var lastRelease = lastReleaseTags.First(t => !t.StartsWith("net")); // skip 'net*-vX.Y.Z' tag and take 'major.minor.build'
- Information("Last release tag is " + lastRelease);
-
var releaseVersion = versioning.NuGetVersion;
+
// Read main header from Git file, substitute version in header, and add content further...
+ Information("{0} New release tag is " + releaseVersion);
+ Information("{1} Last release tag is " + lastRelease);
var releaseHeader = string.Format(System.IO.File.ReadAllText("./ReleaseNotes.md"), releaseVersion, lastRelease);
releaseNotes = new List { releaseHeader };
+ if (IsTechnicalRelease)
+ {
+ WriteReleaseNotes();
+ return;
+ }
var shortlogSummary = GitHelper($"shortlog --no-merges --numbered --summary {lastRelease}..HEAD")
.ToList();
@@ -298,11 +311,12 @@ Task("CreateReleaseNotes")
}
}
} // END of Top 3
- releaseNotes.Add("### Honoring :medal_sports: aka Top Contributors :clap:");
- releaseNotes.AddRange(topContributors);
- releaseNotes.Add("");
- releaseNotes.Add("### Starring :star: aka Release Influencers :bowtie:");
- releaseNotes.AddRange(starring);
+
+ // releaseNotes.Add("### Honoring :medal_sports: aka Top Contributors :clap:");
+ // releaseNotes.AddRange(topContributors);
+ // releaseNotes.Add("");
+ // releaseNotes.Add("### Starring :star: aka Release Influencers :bowtie:");
+ // releaseNotes.AddRange(starring);
releaseNotes.Add("");
releaseNotes.Add($"### Features in Release {releaseVersion}");
var commitsHistory = GitHelper($"log --no-merges --date=format:\"%A, %B %d at %H:%M\" --pretty=format:\"%h by **%aN** on %ad →%n%s\" {lastRelease}..HEAD");
@@ -336,15 +350,23 @@ Task("RunUnitTests")
{
Configuration = compileConfig,
ResultsDirectory = artifactsForUnitTestsDir,
- ArgumentCustomization = args => args
- // this create the code coverage report
- .Append("--collect:\"XPlat Code Coverage\"")
+ ArgumentCustomization = args => args
+ .Append("--no-restore")
+ .Append("--no-build")
+ .Append("--collect:\"XPlat Code Coverage\"") // this create the code coverage report
+ .Append("--verbosity:detailed")
+ .Append("--consoleLoggerParameters:ErrorsOnly")
};
-
+ if (target != Release)
+ {
+ testSettings.Framework = "net8.0"; // .NET 8 SDK only
+ }
EnsureDirectoryExists(artifactsForUnitTestsDir);
DotNetTest(unitTestAssemblies, testSettings);
- var coverageSummaryFile = GetSubDirectories(artifactsForUnitTestsDir).First().CombineWithFilePath(File("coverage.cobertura.xml"));
+ var coverageSummaryFile = GetSubDirectories(artifactsForUnitTestsDir)
+ .First()
+ .CombineWithFilePath(File("coverage.cobertura.xml"));
Information(coverageSummaryFile);
Information(artifactsForUnitTestsDir);
@@ -388,11 +410,15 @@ Task("RunAcceptanceTests")
var settings = new DotNetTestSettings
{
Configuration = compileConfig,
+ Framework = "net8.0", // .NET 8 SDK only
ArgumentCustomization = args => args
.Append("--no-restore")
.Append("--no-build")
};
-
+ if (target != Release)
+ {
+ settings.Framework = "net8.0"; // .NET 8 SDK only
+ }
EnsureDirectoryExists(artifactsForAcceptanceTestsDir);
DotNetTest(acceptanceTestAssemblies, settings);
});
@@ -404,11 +430,15 @@ Task("RunIntegrationTests")
var settings = new DotNetTestSettings
{
Configuration = compileConfig,
+ Framework = "net8.0", // .NET 8 SDK only
ArgumentCustomization = args => args
.Append("--no-restore")
.Append("--no-build")
};
-
+ if (target != Release)
+ {
+ settings.Framework = "net8.0"; // .NET 8 SDK only
+ }
EnsureDirectoryExists(artifactsForIntegrationTestsDir);
DotNetTest(integrationTestAssemblies, settings);
});
@@ -416,19 +446,22 @@ Task("RunIntegrationTests")
Task("CreateArtifacts")
.IsDependentOn("CreateReleaseNotes")
.IsDependentOn("Compile")
- .Does(() =>
+ .Does(() =>
{
WriteReleaseNotes();
System.IO.File.AppendAllLines(artifactsFile, new[] { "ReleaseNotes.md" });
- CopyFiles("./src/**/Release/Ocelot.*.nupkg", packagesDir);
- var projectFiles = GetFiles("./src/**/Release/Ocelot.*.nupkg");
- foreach(var projectFile in projectFiles)
+ if (!IsTechnicalRelease)
{
- System.IO.File.AppendAllLines(
- artifactsFile,
- new[] { projectFile.GetFilename().FullPath }
- );
+ CopyFiles("./src/**/Release/Ocelot.*.nupkg", packagesDir);
+ var projectFiles = GetFiles("./src/**/Release/Ocelot.*.nupkg");
+ foreach(var projectFile in projectFiles)
+ {
+ System.IO.File.AppendAllLines(
+ artifactsFile,
+ new[] { projectFile.GetFilename().FullPath }
+ );
+ }
}
var artifacts = System.IO.File.ReadAllLines(artifactsFile)
@@ -511,13 +544,20 @@ Task("PublishToNuget")
.IsDependentOn("DownloadGitHubReleaseArtifacts")
.Does(() =>
{
- Information("Skipping of publishing to NuGet...");
+ if (IsTechnicalRelease)
+ {
+ Information("Skipping of publishing to NuGet because of technical release...");
+ return;
+ }
+
if (IsRunningOnCircleCI())
{
PublishPackages(packagesDir, artifactsFile, nugetFeedStableKey, nugetFeedStableUploadUrl, nugetFeedStableSymbolsUploadUrl);
}
});
+Task("Void").Does(() => {});
+
RunTarget(target);
private void GenerateReport(Cake.Core.IO.FilePath coverageSummaryFile)
@@ -569,7 +609,7 @@ private void PersistVersion(string committedVersion, string newVersion)
/// Publishes code and symbols packages to nuget feed, based on contents of artifacts file
private void PublishPackages(ConvertableDirectoryPath packagesDir, ConvertableFilePath artifactsFile, string feedApiKey, string codeFeedUrl, string symbolFeedUrl)
{
- Information("PublishPackages");
+ Information("Publishing to NuGet...");
var artifacts = System.IO.File
.ReadAllLines(artifactsFile)
.Distinct();
@@ -582,17 +622,13 @@ private void PublishPackages(ConvertableDirectoryPath packagesDir, ConvertableFi
}
var codePackage = packagesDir + File(artifact);
+ Information("Pushing package " + codePackage + "...");
- Information("Pushing package " + codePackage);
-
- Information("Calling NuGetPush");
-
+ Information("Calling DotNetNuGetPush");
DotNetNuGetPush(
codePackage,
- new DotNetNuGetPushSettings {
- ApiKey = feedApiKey,
- Source = codeFeedUrl
- });
+ new DotNetNuGetPushSettings { ApiKey = feedApiKey, Source = codeFeedUrl }
+ );
}
}
diff --git a/docker/8.21.0/Dockerfile.base b/docker/8.21.0/Dockerfile.base
new file mode 100644
index 0000000000..49b877c101
--- /dev/null
+++ b/docker/8.21.0/Dockerfile.base
@@ -0,0 +1,16 @@
+FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine
+
+RUN apk add bash icu-libs krb5-libs libgcc libintl libssl1.1 libstdc++ zlib git openssh-client
+
+RUN curl -L --output ./dotnet-install.sh https://dot.net/v1/dotnet-install.sh
+
+RUN chmod u+x ./dotnet-install.sh
+
+# Install .NET 8 SDK (already included in the base image, but listed for consistency)
+RUN ./dotnet-install.sh -c 8.0 -i /usr/share/dotnet
+
+# Install .NET 7 SDK
+RUN ./dotnet-install.sh -c 7.0 -i /usr/share/dotnet
+
+# Install .NET 6 SDK
+RUN ./dotnet-install.sh -c 6.0 -i /usr/share/dotnet
diff --git a/docker/8.21.0/Dockerfile.build b/docker/8.21.0/Dockerfile.build
new file mode 100644
index 0000000000..51dff57ff4
--- /dev/null
+++ b/docker/8.21.0/Dockerfile.build
@@ -0,0 +1,17 @@
+# call from ocelot repo root with
+# docker build --platform linux/arm64 --build-arg OCELOT_COVERALLS_TOKEN=$OCELOT_COVERALLS_TOKEN -f ./docker/Dockerfile.build .
+# docker build --platform linux/amd64 --build-arg OCELOT_COVERALLS_TOKEN=$OCELOT_COVERALLS_TOKEN -f ./docker/Dockerfile.build .
+
+FROM ocelot2/circleci-build:8.21.0
+
+ARG OCELOT_COVERALLS_TOKEN
+
+ENV OCELOT_COVERALLS_TOKEN=$OCELOT_COVERALLS_TOKEN
+
+WORKDIR /build
+
+COPY ./. .
+
+RUN dotnet tool restore
+
+RUN dotnet cake
diff --git a/docker/8.21.0/Dockerfile.release b/docker/8.21.0/Dockerfile.release
new file mode 100644
index 0000000000..90e2d6ee1c
--- /dev/null
+++ b/docker/8.21.0/Dockerfile.release
@@ -0,0 +1,21 @@
+# call from ocelot repo root with
+# docker build --platform linux/arm64 --build-arg OCELOT_COVERALLS_TOKEN=$OCELOT_COVERALLS_TOKEN --build-arg OCELOT_GITHUB_API_KEY=$OCELOT_GITHUB_API_KEY --build-arg OCELOT_COVERALLS_TOKEN=$OCELOT_COVERALLS_TOKEN -f ./docker/Dockerfile.build .
+# docker build --platform linux/amd64 --build-arg OCELOT_COVERALLS_TOKEN=$OCELOT_COVERALLS_TOKEN --build-arg OCELOT_GITHUB_API_KEY=$OCELOT_GITHUB_API_KEY --build-arg OCELOT_COVERALLS_TOKEN=$OCELOT_COVERALLS_TOKEN -f ./docker/Dockerfile.build .
+
+FROM ocelot2/circleci-build:8.21.0
+
+ARG OCELOT_COVERALLS_TOKEN
+ARG OCELOT_NUTGET_API_KEY
+ARG OCELOT_GITHUB_API_KEY
+
+ENV OCELOT_COVERALLS_TOKEN=$OCELOT_COVERALLS_TOKEN
+ENV OCELOT_NUTGET_API_KEY=$OCELOT_NUTGET_API_KEY
+ENV OCELOT_GITHUB_API_KEY=$OCELOT_GITHUB_API_KEY
+
+WORKDIR /build
+
+COPY ./. .
+
+RUN dotnet tool restore
+
+RUN dotnet cake
diff --git a/docker/8.21.0/build.sh b/docker/8.21.0/build.sh
new file mode 100644
index 0000000000..15d1325add
--- /dev/null
+++ b/docker/8.21.0/build.sh
@@ -0,0 +1,11 @@
+# This script builds the Ocelot Docker file
+
+# {DotNetSdkVer}.{OcelotVer} -> {.NET8}.{21.0} -> 8.21.0
+version=8.21.0
+docker build --platform linux/amd64 -t ocelot2/circleci-build -f Dockerfile.base .
+
+echo $DOCKER_PASS | docker login -u $DOCKER_USER --password-stdin
+
+docker tag ocelot2/circleci-build ocelot2/circleci-build:$version
+docker push ocelot2/circleci-build:latest
+docker push ocelot2/circleci-build:$version
diff --git a/docker/Dockerfile.base b/docker/Dockerfile.base
index 49b877c101..d2387c9fca 100644
--- a/docker/Dockerfile.base
+++ b/docker/Dockerfile.base
@@ -1,6 +1,6 @@
FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine
-RUN apk add bash icu-libs krb5-libs libgcc libintl libssl1.1 libstdc++ zlib git openssh-client
+RUN apk add bash icu-libs krb5-libs libgcc libintl libssl3 libstdc++ zlib git openssh-client
RUN curl -L --output ./dotnet-install.sh https://dot.net/v1/dotnet-install.sh
@@ -14,3 +14,12 @@ RUN ./dotnet-install.sh -c 7.0 -i /usr/share/dotnet
# Install .NET 6 SDK
RUN ./dotnet-install.sh -c 6.0 -i /usr/share/dotnet
+
+# Generate and export the development certificate
+RUN dotnet dev-certs https -ep /certs/cert.pem -p '' && \
+ chmod 644 /certs/cert.pem
+
+ENV ASPNETCORE_URLS="https://+;http://+"
+ENV ASPNETCORE_HTTPS_PORT=443
+ENV ASPNETCORE_Kestrel__Certificates__Default__Password=""
+ENV ASPNETCORE_Kestrel__Certificates__Default__Path=/certs/cert.pem
diff --git a/docker/Dockerfile.build b/docker/Dockerfile.build
index 51dff57ff4..39d3107b99 100644
--- a/docker/Dockerfile.build
+++ b/docker/Dockerfile.build
@@ -2,7 +2,7 @@
# docker build --platform linux/arm64 --build-arg OCELOT_COVERALLS_TOKEN=$OCELOT_COVERALLS_TOKEN -f ./docker/Dockerfile.build .
# docker build --platform linux/amd64 --build-arg OCELOT_COVERALLS_TOKEN=$OCELOT_COVERALLS_TOKEN -f ./docker/Dockerfile.build .
-FROM ocelot2/circleci-build:8.21.0
+FROM ocelot2/circleci-build:latest
ARG OCELOT_COVERALLS_TOKEN
diff --git a/docker/Dockerfile.release b/docker/Dockerfile.release
index 90e2d6ee1c..89c1b3f799 100644
--- a/docker/Dockerfile.release
+++ b/docker/Dockerfile.release
@@ -2,7 +2,7 @@
# docker build --platform linux/arm64 --build-arg OCELOT_COVERALLS_TOKEN=$OCELOT_COVERALLS_TOKEN --build-arg OCELOT_GITHUB_API_KEY=$OCELOT_GITHUB_API_KEY --build-arg OCELOT_COVERALLS_TOKEN=$OCELOT_COVERALLS_TOKEN -f ./docker/Dockerfile.build .
# docker build --platform linux/amd64 --build-arg OCELOT_COVERALLS_TOKEN=$OCELOT_COVERALLS_TOKEN --build-arg OCELOT_GITHUB_API_KEY=$OCELOT_GITHUB_API_KEY --build-arg OCELOT_COVERALLS_TOKEN=$OCELOT_COVERALLS_TOKEN -f ./docker/Dockerfile.build .
-FROM ocelot2/circleci-build:8.21.0
+FROM ocelot2/circleci-build:latest
ARG OCELOT_COVERALLS_TOKEN
ARG OCELOT_NUTGET_API_KEY
diff --git a/docker/README.md b/docker/README.md
index dcecc224d3..cc1e409bdc 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -1,3 +1,14 @@
# docker build
This folder contains the `Dockerfile.*` and `build.sh` script to create the Ocelot build image & container.
+
+## Account
+- [Ocelot Gateway Profile | Docker Hub](https://hub.docker.com/u/ocelot2)
+
+## Repositories
+- [circleci-build](https://hub.docker.com/r/ocelot2/circleci-build)
+
+## ocelot2/circleci-build Tags
+- [latest](https://hub.docker.com/layers/ocelot2/circleci-build/latest/images/sha256-981d6f9e6e5ba54f6e044bca6fcf8b5197a8f3e6ce2b3cdfa9e6704ecd2ca969?context=explore) is version 8.23.2, uploaded on Apr 3, 2024.
+- [8.23.2](https://hub.docker.com/layers/ocelot2/circleci-build/8.23.2/images/sha256-981d6f9e6e5ba54f6e044bca6fcf8b5197a8f3e6ce2b3cdfa9e6704ecd2ca969?context=explore), uploaded on Apr 3, 2024. It supports development SSL certificates by the command `dotnet dev-certs https`.
+- [8.21.0](https://hub.docker.com/layers/ocelot2/circleci-build/8.21.0/images/sha256-edb46d37ab52d39a5b27dc63895e5944d4d491d1788744ed144ecb4303b94532?context=explore), uploaded on Nov 20, 2023. It contains .NET 8, 7 and 6 SDKs. It supports builds for `net6.0`, `net7.0` and `net8.0` frameworks.
diff --git a/docker/build.sh b/docker/build.sh
index 15d1325add..54f16bb966 100755
--- a/docker/build.sh
+++ b/docker/build.sh
@@ -1,7 +1,7 @@
# This script builds the Ocelot Docker file
-# {DotNetSdkVer}.{OcelotVer} -> {.NET8}.{21.0} -> 8.21.0
-version=8.21.0
+# {DotNetSdkVer}.{OcelotVer} -> {.NET8}.{23.2} -> 8.23.2
+version=8.23.2
docker build --platform linux/amd64 -t ocelot2/circleci-build -f Dockerfile.base .
echo $DOCKER_PASS | docker login -u $DOCKER_USER --password-stdin
diff --git a/docs/_static/overrides.css b/docs/_static/overrides.css
new file mode 100644
index 0000000000..a5f45454c8
--- /dev/null
+++ b/docs/_static/overrides.css
@@ -0,0 +1,6 @@
+blockquote {
+ font-size: 0.9em;
+}
+aside.footnote-list {
+ font-size: 0.9em;
+}
diff --git a/docs/conf.py b/docs/conf.py
index ceea7cca29..6d08568ced 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -8,8 +8,8 @@
project = 'Ocelot'
copyright = ' 2016-2024 ThreeMammals Ocelot team'
-author = 'Tom Pallister, Ocelot Core team at ThreeMammals'
-release = '23.0'
+author = 'Tom Pallister, Raman Maksimchuk and Ocelot Core team at ThreeMammals'
+release = '23.2'
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
@@ -27,3 +27,4 @@
# https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-html_static_path
html_static_path = ['_static']
+html_css_files = ['overrides.css']
diff --git a/docs/features/administration.rst b/docs/features/administration.rst
index cadecefdf9..61832d1ecf 100644
--- a/docs/features/administration.rst
+++ b/docs/features/administration.rst
@@ -146,4 +146,4 @@ The region is whatever you set against the **Region** field in the `FileCacheOpt
""""
-.. [#f1] The ``AddOcelot`` method adds default ASP.NET services to DI-container. You could call another more extended ``AddOcelotUsingBuilder`` method while configuring services to build and use custom builder via an ``IMvcCoreBuilder`` interface object. See more instructions in :doc:`../features/dependencyinjection`, "**The AddOcelotUsingBuilder method**" section.
+.. [#f1] :ref:`di-the-addocelot-method` adds default ASP.NET services to DI container. You could call another extended :ref:`di-addocelotusingbuilder-method` while configuring services to develop your own :ref:`di-custom-builder`. See more instructions in the ":ref:`di-addocelotusingbuilder-method`" section of :doc:`../features/dependencyinjection` feature.
diff --git a/docs/features/caching.rst b/docs/features/caching.rst
index f2bf302ed1..84803060f6 100644
--- a/docs/features/caching.rst
+++ b/docs/features/caching.rst
@@ -39,7 +39,8 @@ Finally, in order to use caching on a route in your Route configuration add this
"FileCacheOptions": {
"TtlSeconds": 15,
"Region": "europe-central",
- "Header": "Authorization"
+ "Header": "OC-Caching-Control",
+ "EnableContentHashing": false // my route has GET verb only, assigning 'true' for requests with body: POST, PUT etc.
}
In this example **TtlSeconds** is set to 15 which means the cache will expire after 15 seconds.
@@ -48,10 +49,38 @@ The **Region** represents a region of caching.
Additionally, if a header name is defined in the **Header** property, that header value is looked up by the key (header name) in the ``HttpRequest`` headers,
and if the header is found, its value will be included in caching key. This causes the cache to become invalid due to the header value changing.
+``EnableContentHashing`` option
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+In version `23.0`_, the new property **EnableContentHashing** has been introduced. Previously, the request body was utilized to compute the cache key.
+However, due to potential performance issues arising from request body hashing, it has been disabled by default.
+Clearly, this constitutes a breaking change and presents challenges for users who require cache key calculations that consider the request body (e.g., for the POST method).
+To address this issue, it is recommended to enable the option either at the route level or globally in the :ref:`cch-global-configuration` section:
+
+.. code-block:: json
+
+ "CacheOptions": {
+ // ...
+ "EnableContentHashing": true
+ }
+
+.. _cch-global-configuration:
+
+Global Configuration
+--------------------
+
+The positive update is that copying Route-level properties for each route is no longer necessary, as version `23.3`_ allows for setting their values in the ``GlobalConfiguration``.
+This convenience extends to **Header** and **Region** as well.
+However, an alternative is still being sought for **TtlSeconds**, which must be explicitly set for each route to enable caching.
+
+Notes
+-----
+
If you look at the example `here `_ you can see how the cache manager is setup and then passed into the Ocelot ``AddCacheManager`` configuration method.
You can use any settings supported by the **CacheManager** package and just pass them in.
-Anyway, Ocelot currently supports caching on the URL of the downstream service and setting a TTL in seconds to expire the cache. You can also clear the cache for a region by calling Ocelot's administration API.
+Anyway, Ocelot currently supports caching on the URL of the downstream service and setting a TTL in seconds to expire the cache.
+You can also clear the cache for a region by calling Ocelot's administration API.
Your Own Caching
----------------
@@ -68,3 +97,6 @@ If you want to add your own caching method, implement the following interfaces a
Please dig into the Ocelot source code to find more.
We would really appreciate it if anyone wants to implement `Redis `_, `Memcached `_ etc.
Please, open a new `Show and tell `_ thread in `Discussions `_ space of the repository.
+
+.. _23.0: https://github.com/ThreeMammals/Ocelot/releases/tag/23.0.0
+.. _23.3: https://github.com/ThreeMammals/Ocelot/releases/tag/23.3.0
diff --git a/docs/features/configuration.rst b/docs/features/configuration.rst
index 936a78a9d5..931e4f58d0 100644
--- a/docs/features/configuration.rst
+++ b/docs/features/configuration.rst
@@ -1,7 +1,7 @@
Configuration
=============
-An example configuration can be found here in `ocelot.json `_.
+An example configuration can be found here in `ocelot.json`_.
There are two sections to the configuration: an array of **Routes** and a **GlobalConfiguration**:
* The **Routes** are the objects that tell Ocelot how to treat an upstream request.
@@ -75,71 +75,141 @@ More information on how to use these options is below.
Multiple Environments
---------------------
-Like any other ASP.NET Core project Ocelot supports configuration file names such as **configuration.dev.json**, **configuration.test.json** etc. In order to implement this add the following
-to you:
+Like any other ASP.NET Core project Ocelot supports configuration file names such as ``appsettings.dev.json``, ``appsettings.test.json`` etc.
+In order to implement this add the following to you:
.. code-block:: csharp
- ConfigureAppConfiguration((hostingContext, config) =>
+ ConfigureAppConfiguration((context, config) =>
{
- config
- .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
+ var env = context.HostingEnvironment;
+ config.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", true, true)
- .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
- .AddJsonFile("ocelot.json")
- .AddJsonFile($"configuration.{hostingContext.HostingEnvironment.EnvironmentName}.json")
+ .AddJsonFile($"appsettings.{env.EnvironmentName}.json", true, true)
+ .AddJsonFile("ocelot.json") // primary config file
+ .AddJsonFile($"ocelot.{env.EnvironmentName}.json") // environment file
.AddEnvironmentVariables();
})
-Ocelot will now use the environment specific configuration and fall back to **ocelot.json** if there isn't one.
+Ocelot will now use the environment specific configuration and fall back to `ocelot.json`_ if there isn't one.
You also need to set the corresponding environment variable which is ``ASPNETCORE_ENVIRONMENT``.
More info on this can be found in the ASP.NET Core docs: `Use multiple environments in ASP.NET Core `_.
+.. _config-merging-files:
+
Merging Configuration Files
---------------------------
-This feature was requested in `issue 296 `_ and allows users to have multiple configuration files to make managing large configurations easier.
+This feature allows users to have multiple configuration files to make managing large configurations easier. [#f1]_
-Instead of adding the configuration directly e.g. ``AddJsonFile("ocelot.json")`` you can call ``AddOcelot()`` like below:
+Rather than directly adding the configuration e.g., using ``AddJsonFile("ocelot.json")``, you can achieve the same result by invoking ``AddOcelot()`` as shown below:
.. code-block:: csharp
- ConfigureAppConfiguration((hostingContext, config) =>
+ ConfigureAppConfiguration((context, config) =>
{
- config
- .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
+ var env = context.HostingEnvironment;
+ config.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", true, true)
- .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
- .AddOcelot(hostingContext.HostingEnvironment)
+ .AddJsonFile($"appsettings.{env.EnvironmentName}.json", true, true)
+ .AddOcelot(env) // happy path
.AddEnvironmentVariables();
})
-In this scenario Ocelot will look for any files that match the pattern ``(?i)ocelot.([a-zA-Z0-9]*).json`` and then merge these together.
-If you want to set the **GlobalConfiguration** property, you must have a file called **ocelot.global.json**.
+In this scenario Ocelot will look for any files that match the pattern ``^ocelot\.(.*?)\.json$`` and then merge these together.
+If you want to set the **GlobalConfiguration** property, you must have a file called ``ocelot.global.json``.
+
+The way Ocelot merges the files is basically load them, loop over them, add any **Routes**, add any **AggregateRoutes** and if the file is called ``ocelot.global.json`` add the **GlobalConfiguration** aswell as any **Routes** or **AggregateRoutes**.
+Ocelot will then save the merged configuration to a file called `ocelot.json`_ and this will be used as the source of truth while Ocelot is running.
-The way Ocelot merges the files is basically load them, loop over them, add any Routes, add any **AggregateRoutes** and if the file is called **ocelot.global.json** add the **GlobalConfiguration** aswell as any Routes or **AggregateRoutes**.
-Ocelot will then save the merged configuration to a file called **ocelot.json** and this will be used as the source of truth while Ocelot is running.
+ **Note 1**: Currently, validation occurs only during the final merging of configurations in Ocelot.
+ It's essential to be aware of this when troubleshooting issues.
+ We recommend thoroughly inspecting the contents of the ``ocelot.json`` file if you encounter any problems.
-At the moment there is no validation at this stage it only happens when Ocelot validates the final merged configuration.
-This is something to be aware of when you are investigating problems.
-We would advise always checking what is in **ocelot.json** file if you have any problems.
+ **Note 2**: The Merging feature is operational only during the application's startup.
+ Consequently, the merged configuration in ``ocelot.json`` remains static post-merging and startup.
+ It's important to be aware that the ``ConfigureAppConfiguration`` method is invoked solely during the startup of an ASP.NET web application.
+ Once the Ocelot application has started, you cannot call the ``AddOcelot`` method, nor can you employ the merging feature within ``AddOcelot``.
+ If you still require on-the-fly updating of the primary configuration file, ``ocelot.json``, please refer to the :ref:`config-react-to-changes` section.
+ Additionally, note that merging partial configuration files (such as ``ocelot.*.json``) on the fly using :doc:`../features/administration` API is not currently implemented.
+
+Keep files in a folder
+^^^^^^^^^^^^^^^^^^^^^^
You can also give Ocelot a specific path to look in for the configuration files like below:
.. code-block:: csharp
- ConfigureAppConfiguration((hostingContext, config) =>
+ ConfigureAppConfiguration((context, config) =>
{
- config
- .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
+ var env = context.HostingEnvironment;
+ config.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", true, true)
- .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
- .AddOcelot("/foo/bar", hostingContext.HostingEnvironment)
+ .AddJsonFile($"appsettings.{env.EnvironmentName}.json", true, true)
+ .AddOcelot("/my/folder", env) // happy path
.AddEnvironmentVariables();
})
-Ocelot needs the ``HostingEnvironment`` so it knows to exclude anything environment specific from the algorithm.
+Ocelot needs the ``HostingEnvironment`` so it knows to exclude anything environment specific from the merging algorithm.
+
+.. _config-merging-tomemory:
+
+Merging files to memory [#f2]_
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+By default, Ocelot writes the merged configuration to disk as `ocelot.json`_ (the primary configuration file) by adding the file to the ASP.NET configuration provider.
+
+If your web server lacks write permissions for the configuration folder, you can instruct Ocelot to use the merged configuration directly from memory.
+Here's how:
+
+.. code-block:: csharp
+
+ // It implicitly calls ASP.NET AddJsonStream extension method for IConfigurationBuilder
+ // config.AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(json)));
+ config.AddOcelot(context.HostingEnvironment, MergeOcelotJson.ToMemory);
+
+This feature proves exceptionally valuable in cloud environments like Azure, AWS, and GCP, especially when the app lacks sufficient write permissions to save files.
+Furthermore, within Docker container environments, permissions can be scarce, necessitating substantial DevOps efforts to enable file write operations.
+Therefore, save time by leveraging this feature! [#f2]_
+
+Reload JSON Config On Change
+----------------------------
+
+Ocelot supports reloading the JSON configuration file on change.
+For instance, the following will recreate Ocelot internal configuration when the `ocelot.json`_ file is updated manually:
+
+.. code-block:: csharp
+
+ config.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true); // ASP.NET framework version
+
+Important Note: Starting from version `23.2`_, most :ref:`di-configuration-addocelot` include optional ``bool?`` arguments, specifically ``optional`` and ``reloadOnChange``.
+Therefore, you have the flexibility to provide these arguments when invoking the internal ``AddJsonFile`` method during the final configuration step (see `AddOcelotJsonFile `_ implementation):
+
+.. code-block:: csharp
+
+ config.AddJsonFile(ConfigurationBuilderExtensions.PrimaryConfigFile, optional ?? false, reloadOnChange ?? false);
+
+As you can see, in versions prior to `23.2`_, the `AddOcelot extension methods `_ did not apply the ``reloadOnChange`` argument because it was set to ``false``.
+We recommend using the ``AddOcelot`` extension methods to control reloading, rather than relying on the framework's ``AddJsonFile`` method.
+For example:
+
+.. code-block:: csharp
+
+ ConfigureAppConfiguration((context, config) =>
+ {
+ config.AddJsonFile(ConfigurationBuilderExtensions.PrimaryConfigFile, optional: false, reloadOnChange: true); // old approach
+ var env = context.HostingEnvironment;
+ var mergeTo = MergeOcelotJson.ToFile; // ToMemory
+ var folder = "/My/folder";
+ FileConfiguration configuration = new(); // read from anywhere and initialize
+ config.AddOcelot(env, mergeTo, optional: false, reloadOnChange: true); // with environment and merging type
+ config.AddOcelot(folder, env, mergeTo, optional: false, reloadOnChange: true); // with folder, environment and merging type
+ config.AddOcelot(configuration, optional: false, reloadOnChange: true); // with configuration object created by your own
+ config.AddOcelot(configuration, env, mergeTo, optional: false, reloadOnChange: true); // with configuration object, environment and merging type
+ })
+
+Examining the code within the `ConfigurationBuilderExtensions class `_ would be helpful for gaining a better understanding of the signatures of the overloaded methods [#f2]_.
Store Configuration in Consul
-----------------------------
@@ -151,16 +221,15 @@ The first thing you need to do is install the `NuGet package `_ seconds TTL cache before making a new request to your local Consul agent.
+This feature has a `3 seconds `_ TTL cache before making a new request to your local Consul agent.
-Reload JSON Config On Change
-----------------------------
+.. _config-consul-key:
-Ocelot supports reloading the JSON configuration file on change. For instance, the following will recreate Ocelot internal configuration when the **ocelot.json** file is updated manually:
-
-.. code-block:: csharp
-
- config.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true);
-
-Configuration Key
------------------
+Consul Configuration Key [#f4]_
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If you are using Consul for configuration (or other providers in the future), you might want to key your configurations: so you can have multiple configurations.
-This feature was requested in `issue 346 `_.
+
In order to specify the key you need to set the **ConfigurationKey** property in the **ServiceDiscoveryProvider** options of the configuration JSON file e.g.
.. code-block:: json
@@ -258,7 +320,7 @@ For ``https`` scheme this fake validator was requested by `issue 309 `_.
As a team, we do not consider it as an ideal solution. From one side, the community wants to have an option to work with self-signed certificates.
-But from other side, currently source code scanners detect 2 serious security vulnerabilities because of this fake validator in `20.0 release `_.
+But from other side, currently source code scanners detect 2 serious security vulnerabilities because of this fake validator in `20.0`_ release.
The Ocelot team will rethink this unfortunate situation, and it is highly likely that this feature will at least be redesigned or removed completely.
For now, the SSL fake validator makes sense in local development environments when a route has ``https`` or ``wss`` schemes having self-signed certificate for those routes.
@@ -279,12 +341,14 @@ As a team, we highly recommend following these instructions when developing your
* **Production environments**. **Do not use self-signed certificates at all!**
System administrators or DevOps engineers must create real valid certificates being signed by hosting or cloud providers.
- **Switch off the feature for all routes!** Remove the **DangerousAcceptAnyServerCertificateValidator** property for all routes in production version of **ocelot.json** file!
+ **Switch off the feature for all routes!** Remove the **DangerousAcceptAnyServerCertificateValidator** property for all routes in production version of `ocelot.json`_ file!
+
+.. _config-react-to-changes:
React to Configuration Changes
------------------------------
-Resolve ``IOcelotConfigurationChangeTokenSource`` interface from the DI container if you wish to react to changes to the Ocelot configuration via the :doc:`../features/administration` API or **ocelot.json** being reloaded from the disk.
+Resolve ``IOcelotConfigurationChangeTokenSource`` interface from the DI container if you wish to react to changes to the Ocelot configuration via the :doc:`../features/administration` API or `ocelot.json`_ being reloaded from the disk.
You may either poll the change token's ``IChangeToken.HasChanged`` property, or register a callback with the ``RegisterChangeCallback`` method.
Polling the HasChanged property
@@ -342,6 +406,33 @@ DownstreamHttpVersion
Ocelot allows you to choose the HTTP version it will use to make the proxy request. It can be set as ``1.0``, ``1.1`` or ``2.0``.
+Dependency Injection
+--------------------
+
+*Dependency Injection* for this **Configuration** feature in Ocelot is designed to extend and/or control **the configuration** of the Ocelot kernel before the stage of building ASP.NET MVC pipeline services.
+The primary methods are :ref:`di-configuration-addocelot` within the `ConfigurationBuilderExtensions`_ class, which offers several overloaded versions with corresponding signatures.
+
+You can utilize these methods in the ``ConfigureAppConfiguration`` method (located in both **Program.cs** and **Startup.cs**) of your ASP.NET MVC gateway app (minimal web app) to configure the Ocelot pipeline and services.
+
+.. code-block:: csharp
+
+ namespace Microsoft.AspNetCore.Hosting;
+
+ public interface IWebHostBuilder
+ {
+ IWebHostBuilder ConfigureAppConfiguration(Action configureDelegate);
+ }
+
+You can find additional details in the dedicated :ref:`di-configuration-overview` section and in subsequent sections related to the :doc:`../features/dependencyinjection` chapter.
+
""""
-.. [#f1] The ``AddOcelot`` method adds default ASP.NET services to DI-container. You could call another more extended ``AddOcelotUsingBuilder`` method while configuring services to build and use custom builder via an ``IMvcCoreBuilder`` interface object. See more instructions in :doc:`../features/dependencyinjection`, "**The AddOcelotUsingBuilder method**" section.
+.. [#f1] ":ref:`config-merging-files`" feature was requested in `issue 296 `_, since then we extended it in `issue 1216 `_ (PR `1227 `_) as ":ref:`config-merging-tomemory`" subfeature which was released as a part of version `23.2`_.
+.. [#f2] ":ref:`config-merging-tomemory`" subfeature is based on the ``MergeOcelotJson`` enumeration type with values: ``ToFile`` and ``ToMemory``. The 1st one is implicit by default, and the second one is exactly what you need when merging to memory. See more details on implementations in the `ConfigurationBuilderExtensions`_ class.
+.. [#f3] :ref:`di-the-addocelot-method` adds default ASP.NET services to DI container. You could call another extended :ref:`di-addocelotusingbuilder-method` while configuring services to develop your own :ref:`di-custom-builder`. See more instructions in the ":ref:`di-addocelotusingbuilder-method`" section of :doc:`../features/dependencyinjection` feature.
+.. [#f4] ":ref:`config-consul-key`" feature was requested in `issue 346 `_ as a part of version `7.0.0 `_.
+
+.. _20.0: https://github.com/ThreeMammals/Ocelot/releases/tag/20.0.0
+.. _23.2: https://github.com/ThreeMammals/Ocelot/releases/tag/23.2.0
+.. _ocelot.json: https://github.com/ThreeMammals/Ocelot/blob/main/test/Ocelot.ManualTest/ocelot.json
+.. _ConfigurationBuilderExtensions: https://github.com/ThreeMammals/Ocelot/blob/develop/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs
diff --git a/docs/features/dependencyinjection.rst b/docs/features/dependencyinjection.rst
index 9dbd9b2be7..dbd3437f66 100644
--- a/docs/features/dependencyinjection.rst
+++ b/docs/features/dependencyinjection.rst
@@ -1,3 +1,8 @@
+.. _AddOcelot: #the-addocelot-method
+.. _AddOcelotUsingBuilder: #addocelotusingbuilder-method
+.. _AddDefaultAspNetServices: #adddefaultaspnetservices-method
+.. _OcelotBuilder: #ocelotbuilder-class
+
Dependency Injection
====================
@@ -7,50 +12,77 @@ Dependency Injection
Overview
--------
-Dependency Injection feature in Ocelot is designed to extend and/or control building of Ocelot core as ASP.NET MVC pipeline services.
-The main methods are `AddOcelot <#the-addocelot-method>`_ and `AddOcelotUsingBuilder <#the-addocelotusingbuilder-method>`_ of the ``ServiceCollectionExtensions`` class.
-Use them in **Program.cs** and **Startup.cs** of your ASP.NET MVC gateway app (minimal web app) to enable and build Ocelot pipeline.
+| Dependency Injection feature in Ocelot is designed to extend and/or control building of Ocelot core as ASP.NET MVC pipeline services.
+| The main methods of the `ServiceCollectionExtensions`_ class are:
+
+* `AddOcelot`_ adds required Ocelot services to DI and it adds default services using `AddDefaultAspNetServices`_ method.
+* `AddOcelotUsingBuilder`_ adds required Ocelot services to DI, and **it adds custom ASP.NET services** with configuration injected implicitly or explicitly.
+
+Use :ref:`di-service-extensions` in in the following ``ConfigureServices`` method (**Program.cs** and **Startup.cs**) of your ASP.NET MVC gateway app (minimal web app) to add/build Ocelot pipeline services:
+
+.. code-block:: csharp
+
+ namespace Microsoft.AspNetCore.Hosting;
+ public interface IWebHostBuilder
+ {
+ IWebHostBuilder ConfigureServices(Action configureServices);
+ }
+
+The fact is, the `OcelotBuilder`_ class is Ocelot's cornerstone logic.
+
+.. _di-service-extensions:
-And of course, the `OcelotBuilder <#the-ocelotbuilder-class>`_ class is the core of Ocelot.
+``IServiceCollection`` extensions
+---------------------------------
-IServiceCollection extensions
------------------------------
+ | **Namespace**: ``Ocelot.DependencyInjection``
+ | **Class**: `ServiceCollectionExtensions`_
- **Class**: `Ocelot.DependencyInjection.ServiceCollectionExtensions `_
+Based on the current implementations for the `OcelotBuilder`_ class, the `AddOcelot`_ method adds required ASP.NET services to DI container.
+You could call another more extended `AddOcelotUsingBuilder`_ method while configuring services to build and use custom builder via an ``IMvcCoreBuilder`` object.
-Based on the current implementations for the ``OcelotBuilder`` class, the ``AddOcelot`` method adds default ASP.NET services to DI container.
-You could call another more extended ``AddOcelotUsingBuilder`` method while configuring services to build and use custom builder via an ``IMvcCoreBuilder`` interface object.
+.. _di-the-addocelot-method:
-The AddOcelot method
-^^^^^^^^^^^^^^^^^^^^
+The ``AddOcelot`` method
+^^^^^^^^^^^^^^^^^^^^^^^^
**Signatures**:
-* ``IOcelotBuilder AddOcelot(this IServiceCollection services)``
-* ``IOcelotBuilder AddOcelot(this IServiceCollection services, IConfiguration configuration)``
+.. code-block:: csharp
+
+ IOcelotBuilder AddOcelot(this IServiceCollection services);
+ IOcelotBuilder AddOcelot(this IServiceCollection services, IConfiguration configuration);
-This ``IServiceCollection`` extension method adds default ASP.NET services and Ocelot application services with configuration injected implicitly or explicitly.
-Note! The method adds **default** ASP.NET services required for Ocelot core in the `AddDefaultAspNetServices <#the-adddefaultaspnetservices-method>`_ method which plays the role of default builder.
+These ``IServiceCollection`` extension methods add default ASP.NET services and Ocelot application services with configuration injected implicitly or explicitly.
-In this scenario, you do nothing except calling the ``AddOcelot`` method which has been mentioned in feature chapters, if additional startup settings are required.
-In this case you just reuse default settings to build Ocelot core. The alternative is ``AddOcelotUsingBuilder`` method, see the next section.
+**Note!** Both methods add required and **default** ASP.NET services for Ocelot pipeline in the `AddDefaultAspNetServices`_ method which is default builder.
-The AddOcelotUsingBuilder method
+In this scenario, you do nothing other than call the ``AddOcelot`` method, which is often mentioned in feature chapters, if additional startup settings are required.
+With this method, you simply reuse the default settings to build the Ocelot pipeline. The alternative is ``AddOcelotUsingBuilder`` method, see the next subsection.
+
+.. _di-addocelotusingbuilder-method:
+
+``AddOcelotUsingBuilder`` method
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
**Signatures**:
-* ``IOcelotBuilder AddOcelotUsingBuilder(this IServiceCollection services, Func customBuilder)``
-* ``IOcelotBuilder AddOcelotUsingBuilder(this IServiceCollection services, IConfiguration configuration, Func customBuilder)``
+.. code-block:: csharp
+
+ using CustomBuilderFunc = System.Func;
-This ``IServiceCollection`` extension method adds Ocelot application services, and it *adds custom ASP.NET services* with configuration injected implicitly or explicitly.
-Note! The method adds **custom** ASP.NET services required for Ocelot pipeline using custom builder (``customBuilder`` parameter).
-It is highly recommended to read docs of the `AddDefaultAspNetServices <#the-adddefaultaspnetservices-method>`_ method,
+ IOcelotBuilder AddOcelotUsingBuilder(this IServiceCollection services, CustomBuilderFunc customBuilder);
+ IOcelotBuilder AddOcelotUsingBuilder(this IServiceCollection services, IConfiguration configuration, CustomBuilderFunc customBuilder);
+
+These ``IServiceCollection`` extension methods add Ocelot application services, and they add **custom ASP.NET services** with configuration injected implicitly or explicitly.
+
+**Note!** The method adds **custom** ASP.NET services required for Ocelot pipeline using custom builder (aka ``customBuilder`` parameter).
+It is highly recommended to read docs of the `AddDefaultAspNetServices`_ method,
or even to review implementation to understand default ASP.NET services which are the minimal part of the gateway pipeline.
-In this custom scenario, you control everything during ASP.NET MVC pipeline building, and you provide custom settings to build Ocelot core.
+In this custom scenario, you control everything during ASP.NET MVC pipeline building, and you provide custom settings to build Ocelot pipeline.
-The OcelotBuilder class
+``OcelotBuilder`` class
-----------------------
**Source code**: `Ocelot.DependencyInjection.OcelotBuilder `_
@@ -58,7 +90,11 @@ The OcelotBuilder class
The ``OcelotBuilder`` class is the core of Ocelot which does the following:
- Contructs itself by single public constructor:
- ``public OcelotBuilder(IServiceCollection services, IConfiguration configurationRoot, Func customBuilder = null)``
+
+ .. code-block:: csharp
+
+ public OcelotBuilder(IServiceCollection services, IConfiguration configurationRoot, Func customBuilder = null);
+
- Initializes and stores public properties: **Services** (``IServiceCollection`` object), **Configuration** (``IConfiguration`` object) and **MvcCoreBuilder** (``IMvcCoreBuilder`` object)
- Adds **all application services** during construction phase over the ``Services`` property
- Adds ASP.NET services by builder using ``Func`` object in these 2 development scenarios:
@@ -73,15 +109,15 @@ The ``OcelotBuilder`` class is the core of Ocelot which does the following:
* ``AddDelegatingHandler`` method
* ``AddConfigPlaceholders`` method
-The AddDefaultAspNetServices method
+``AddDefaultAspNetServices`` method
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- **Class**: `Ocelot.DependencyInjection.OcelotBuilder `_
+ **Class**: `OcelotBuilder`_
Currently the method is protected and overriding is forbidden.
-The role of the method is to inject required services via both ``IServiceCollection`` and ``IMvcCoreBuilder`` interfaces objects for the minimal part of the gateway pipeline.
+The role of the method is to inject required services via both ``IServiceCollection`` and ``IMvcCoreBuilder`` interface objects for the minimal part of the gateway pipeline.
-Current implementation is the folowing:
+Current `implementation `_ is the folowing:
.. code-block:: csharp
@@ -100,9 +136,9 @@ Current implementation is the folowing:
}
The method cannot be overridden. It is not virtual, and there is no way to override current behavior by inheritance.
-And, the method is default builder of Ocelot pipeline while calling the `AddOcelot <#the-addocelot-method>`_ method.
+And, the method is default builder of Ocelot pipeline while calling the `AddOcelot`_ method.
As alternative, to "override" this default builder, you can design and reuse custom builder as a ``Func`` delegate object
-and pass it as parameter to the `AddOcelotUsingBuilder <#the-addocelotusingbuilder-method>`_ extension method.
+and pass it as parameter to the `AddOcelotUsingBuilder`_ extension method.
It gives you full control on design and buiding of Ocelot pipeline, but be careful while designing your custom Ocelot pipeline as customizable ASP.NET MVC pipeline.
Warning! Most of services from minimal part of the pipeline should be reused, but only a few of services could be removed.
@@ -116,16 +152,19 @@ Finally, as a default builder, the method above receives ``IMvcCoreBuilder`` obj
The next section shows you an example of designing custom Ocelot pipeline by custom builder.
+.. _di-custom-builder:
+
Custom Builder
--------------
+
**Goal**: Replace ``Newtonsoft.Json`` services with ``System.Text.Json`` services.
-The Problem
-^^^^^^^^^^^
+Problem
+^^^^^^^
-The default `AddOcelot <#the-addocelot-method>`_ method adds
+The main `AddOcelot`_ method adds
`Newtonsoft JSON `_ services
-by the ``AddNewtonsoftJson`` extension method in default builder (the `AddDefaultAspNetServices <#the-adddefaultaspnetservices-method>`_ method).
+by the ``AddNewtonsoftJson`` extension method in default builder (`AddDefaultAspNetServices`_ method).
The ``AddNewtonsoftJson`` method calling was introduced in old .NET and Ocelot releases which was necessary when Microsoft did not launch the ``System.Text.Json`` library,
but now it affects normal use, so we have an intention to solve the problem.
@@ -136,12 +175,14 @@ will help to configure JSON settings by the ``JsonSerializerOptions`` property f
Solution
^^^^^^^^
-We have the following methods in ``Ocelot.DependencyInjection.ServiceCollectionExtensions`` class:
+We have the following methods in `ServiceCollectionExtensions`_ class:
-- ``IOcelotBuilder AddOcelotUsingBuilder(this IServiceCollection services, Func customBuilder)``
-- ``IOcelotBuilder AddOcelotUsingBuilder(this IServiceCollection services, IConfiguration configuration, Func customBuilder)``
+.. code-block:: csharp
-These method with custom builder allows you to use your any desired JSON library for (de)serialization.
+ IOcelotBuilder AddOcelotUsingBuilder(this IServiceCollection services, Func customBuilder);
+ IOcelotBuilder AddOcelotUsingBuilder(this IServiceCollection services, IConfiguration configuration, Func customBuilder);
+
+These methods with custom builder allow you to use your any desired JSON library for (de)serialization.
But we are going to create custom ``MvcCoreBuilder`` with support of JSON services, such as ``System.Text.Json``.
To do that we need to call ``AddJsonOptions`` extension of the ``MvcCoreMvcCoreBuilderExtensions`` class
(NuGet package: `Microsoft.AspNetCore.Mvc.Core `_) in **Startup.cs**:
@@ -181,3 +222,95 @@ To do that we need to call ``AddJsonOptions`` extension of the ``MvcCoreMvcCoreB
The sample code provides settings to render JSON as indented text rather than compressed plain JSON text without spaces.
This is just one common use case, and you can add additional services to the builder.
+
+------------------------------------------------------------------
+
+.. _di-configuration-overview:
+
+Configuration Overview
+----------------------
+
+*Dependency Injection* for the :doc:`../features/configuration` feature in Ocelot is designed to extend and/or control **the configuration** of Ocelot kernel before the stage of building ASP.NET MVC pipeline services.
+
+To configure the Ocelot pipeline and services, use the :ref:`di-configuration-extensions` in the following ``ConfigureAppConfiguration`` method (located in *Program.cs* and *Startup.cs*) of your minimal web app:
+
+.. code-block:: csharp
+
+ namespace Microsoft.AspNetCore.Hosting;
+ public interface IWebHostBuilder
+ {
+ IWebHostBuilder ConfigureAppConfiguration(Action configureDelegate);
+ }
+
+.. _di-configuration-extensions:
+
+``IConfigurationBuilder`` extensions
+------------------------------------
+
+ | **Namespace**: ``Ocelot.DependencyInjection``
+ | **Class**: `ConfigurationBuilderExtensions`_
+
+The main methods are the :ref:`di-configuration-addocelot` within the `ConfigurationBuilderExtensions`_ class.
+This method has a list of overloaded versions with corresponding signatures.
+
+The purpose of this method is to prepare everything before actually configuring with native extensions. It involves the following steps:
+
+1. **Merging Partial JSON Files**: The ``GetMergedOcelotJson`` method merges partial JSON files.
+2. **Selecting Merge Type**: It allows you to choose a merge type to save the merged JSON configuration data either ``ToFile`` or ``ToMemory``.
+3. **Framework Extensions**: Finally, the method calls the following native ``IConfigurationBuilder`` framework extensions:
+
+ * The ``AddJsonFile`` method adds the primary configuration file (commonly known as `ocelot.json`_) after the merge stage. It writes the file back **to the file system** using the ``ToFile`` merge type option, which is implicitly the default.
+ * The ``AddJsonStream`` method adds the JSON data of the primary configuration file as a UTF-8 stream **into memory** after the merge stage. It uses the ``ToMemory`` merge type option.
+
+.. _di-configuration-addocelot:
+
+``AddOcelot`` methods
+^^^^^^^^^^^^^^^^^^^^^
+
+**Signatures** of the most common versions:
+
+.. code-block:: csharp
+
+ IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, IWebHostEnvironment env);
+ IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, string folder, IWebHostEnvironment env);
+
+**Note**: These versions use the implicit ``ToFile`` merge type to write `ocelot.json`_ back to disk. Finally, they call the ``AddJsonFile`` extension.
+
+**Signatures** of the versions to specify a ``MergeOcelotJson`` option:
+
+.. code-block:: csharp
+
+ IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, IWebHostEnvironment env, MergeOcelotJson mergeTo,
+ string primaryConfigFile = null, string globalConfigFile = null, string environmentConfigFile = null, bool? optional = null, bool? reloadOnChange = null);
+ IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, string folder, IWebHostEnvironment env, MergeOcelotJson mergeTo,
+ string primaryConfigFile = null, string globalConfigFile = null, string environmentConfigFile = null, bool? optional = null, bool? reloadOnChange = null);
+
+**Note**: These versions include optional arguments to specify the location of the three main files involved in the merge operation.
+In theory, these files can be located anywhere, but in practice, it is better to keep them in one folder.
+
+**Signatures** of the versions to indicate the ``FileConfiguration`` object of a self-created out-of-the-box configuration: [#f1]_
+
+.. code-block:: csharp
+
+ IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, FileConfiguration fileConfiguration,
+ string primaryConfigFile = null, bool? optional = null, bool? reloadOnChange = null);
+ IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, FileConfiguration fileConfiguration, IWebHostEnvironment env, MergeOcelotJson mergeTo,
+ string primaryConfigFile = null, string globalConfigFile = null, string environmentConfigFile = null, bool? optional = null, bool? reloadOnChange = null);
+
+| **Note 1**: These versions include optional arguments to specify the location of the three main files involved in the merge operation.
+| **Note 2**: Your ``FileConfiguration`` object can be serialized/deserialized from anywhere: local or remote storage, Consul KV storage, and even a database.
+ For more information about this super useful feature, please read PR `1569`_ [#f1]_.
+
+""""
+
+.. [#f1] The Dynamic :doc:`../features/configuration` feature was requested in issues `1228`_ and `1235`_. It was delivered by PR `1569`_ as part of version `20.0`_. Since then, we have extended it in PR `1227`_ and released it as part of version `23.2`_.
+
+.. _ServiceCollectionExtensions: https://github.com/ThreeMammals/Ocelot/blob/develop/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs#L7
+.. _ConfigurationBuilderExtensions: https://github.com/ThreeMammals/Ocelot/blob/develop/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs
+.. _ocelot.json: https://github.com/ThreeMammals/Ocelot/blob/main/test/Ocelot.ManualTest/ocelot.json
+.. _1227: https://github.com/ThreeMammals/Ocelot/pull/1227
+.. _1228: https://github.com/ThreeMammals/Ocelot/issues/1228
+.. _1235: https://github.com/ThreeMammals/Ocelot/issues/1235
+.. _1569: https://github.com/ThreeMammals/Ocelot/pull/1569
+.. _20.0: https://github.com/ThreeMammals/Ocelot/releases/tag/20.0.0
+.. _23.2: https://github.com/ThreeMammals/Ocelot/releases/tag/23.2.0
diff --git a/docs/features/kubernetes.rst b/docs/features/kubernetes.rst
index 44411e5892..8c6967c879 100644
--- a/docs/features/kubernetes.rst
+++ b/docs/features/kubernetes.rst
@@ -73,7 +73,11 @@ The example here shows a typical configuration:
}
Service deployment in **Namespace** ``Dev``, **ServiceDiscoveryProvider** type is ``Kube``, you also can set :ref:`k8s-pollkube-provider` type.
-Note: **Host**, **Port** and **Token** are no longer in use.
+
+ **Note 1**: ``Host``, ``Port`` and ``Token`` are no longer in use.
+
+ **Note 2**: The ``Kube`` provider searches for the service entry using ``ServiceName`` and then retrieves the first available port from the ``EndpointSubsetV1.Ports`` collection.
+ Therefore, if the port name is not specified, the default downstream scheme will be ``http``;
.. _k8s-pollkube-provider:
@@ -99,10 +103,10 @@ This really depends on how volatile your services are.
We doubt it will matter for most people and polling may give a tiny performance improvement over calling Kubernetes per request.
There is no way for Ocelot to work these out for you.
-Global vs Route levels
-^^^^^^^^^^^^^^^^^^^^^^
+Global vs Route Levels
+----------------------
-If your downstream service resides in a different namespace, you can override the global setting at the Route-level by specifying a **ServiceNamespace**:
+If your downstream service resides in a different namespace, you can override the global setting at the Route-level by specifying a ``ServiceNamespace``:
.. code-block:: json
@@ -113,7 +117,45 @@ If your downstream service resides in a different namespace, you can override th
}
]
+Downstream Scheme vs Port Names [#f3]_
+--------------------------------------
+
+Kubernetes configuration permits the definition of multiple ports with names for each address of an endpoint subset.
+When binding multiple ports, you assign a name to each subset port.
+To allow the ``Kube`` provider to recognize the desired port by its name, you need to specify the ``DownstreamScheme`` with the port's name;
+if not, the collection's first port entry will be chosen by default.
+
+For instance, consider a service on Kubernetes that exposes two ports: ``https`` for **443** and ``http`` for **80**, as follows:
+
+.. code-block:: text
+
+ Name: my-service
+ Namespace: default
+ Subsets:
+ Addresses: 10.1.161.59
+ Ports:
+ Name Port Protocol
+ ---- ---- --------
+ https 443 TCP
+ http 80 TCP
+
+**When** you need to use the ``http`` port while intentionally bypassing the default ``https`` port (first one),
+you must define ``DownstreamScheme`` to enable the provider to recognize the desired ``http`` port by comparing ``DownstreamScheme`` with the port name as follows:
+
+.. code-block:: json
+
+ "Routes": [
+ {
+ "ServiceName": "my-service",
+ "DownstreamScheme": "http", // port name -> http -> port is 80
+ }
+ ]
+
+**Note**: In the absence of a specified ``DownstreamScheme`` (which is the default behavior), the ``Kube`` provider will select **the first available port** from the ``EndpointSubsetV1.Ports`` collection.
+Consequently, if the port name is not designated, the default downstream scheme utilized will be ``http``.
+
""""
.. [#f1] `Wikipedia `_ | `K8s Website `_ | `K8s Documentation `_ | `K8s GitHub `_
.. [#f2] This feature was requested as part of `issue 345 `_ to add support for `Kubernetes `_ :doc:`../features/servicediscovery` provider.
+.. [#f3] *"Downstream Scheme vs Port Names"* feature was requested as part of `issue 1967 `_ and released in version `23.3 `_
diff --git a/docs/features/qualityofservice.rst b/docs/features/qualityofservice.rst
index 5efab9f1d9..799efdc086 100644
--- a/docs/features/qualityofservice.rst
+++ b/docs/features/qualityofservice.rst
@@ -1,55 +1,125 @@
-Quality of Service
-==================
-
- Label: `QoS `_
-
-Ocelot supports one QoS capability at the current time. You can set on a per Route basis if you want to use a circuit breaker when making requests to a downstream service.
-This uses an awesome .NET library called `Polly `_, check them out `in official repo `_.
-
-The first thing you need to do if you want to use the :doc:`../features/administration` API is bring in the relevant NuGet `package `_:
-
-.. code-block:: powershell
-
- Install-Package Ocelot.Provider.Polly
-
-Then in your ``ConfigureServices`` method to add Polly services we must call the ``AddPolly()`` extension of the ``OcelotBuilder`` being returned by ``AddOcelot()`` [#f1]_ like below:
-
-.. code-block:: csharp
-
- services.AddOcelot()
- .AddPolly();
-
-Then add the following section to a Route configuration:
-
-.. code-block:: json
-
- "QoSOptions": {
- "ExceptionsAllowedBeforeBreaking": 3,
- "DurationOfBreak": 1000,
- "TimeoutValue": 5000
- }
-
-- You must set a number greater than ``0`` against **ExceptionsAllowedBeforeBreaking** for this rule to be implemented. [#f2]_
-- **DurationOfBreak** means the circuit breaker will stay open for 1 second after it is tripped.
-- **TimeoutValue** means if a request takes more than 5 seconds, it will automatically be timed out.
-
-You can set the **TimeoutValue** in isolation of the **ExceptionsAllowedBeforeBreaking** and **DurationOfBreak** options:
-
-.. code-block:: json
-
- "QoSOptions": {
- "TimeoutValue": 5000
- }
-
-There is no point setting the other two in isolation as they affect each other!
-
-If you do not add a QoS section, QoS will not be used, however Ocelot will default to a **90** seconds timeout on all downstream requests.
-If someone needs this to be configurable, open an issue. [#f2]_
-
-""""
-
-.. [#f1] The ``AddOcelot`` method adds default ASP.NET services to DI-container. You could call another more extended ``AddOcelotUsingBuilder`` method while configuring services to build and use custom builder via an ``IMvcCoreBuilder`` interface object. See more instructions in :doc:`../features/dependencyinjection`, "**The AddOcelotUsingBuilder method**" section.
-.. [#f2] If something doesn't work or you get stuck, please review current `QoS issues `_ filtering by |QoS_label| label.
-
-.. |QoS_label| image:: https://img.shields.io/badge/-QoS-D3ADAF.svg
- :target: https://github.com/ThreeMammals/Ocelot/labels/QoS
+Quality of Service
+==================
+
+ Label: `QoS `_
+
+Ocelot supports one QoS capability at the current time. You can set on a per Route basis if you want to use a circuit breaker when making requests to a downstream service.
+This uses an awesome .NET library called `Polly`_, check them out `in official repository `_.
+
+The first thing you need to do if you want to use the :doc:`../features/administration` API is bring in the relevant NuGet `package `_:
+
+.. code-block:: powershell
+
+ Install-Package Ocelot.Provider.Polly
+
+Then in your ``ConfigureServices`` method to add `Polly`_ services we must call the ``AddPolly()`` extension of the ``OcelotBuilder`` being returned by ``AddOcelot()`` [#f1]_ like below:
+
+.. code-block:: csharp
+
+ services.AddOcelot()
+ .AddPolly();
+
+Then add the following section to a Route configuration:
+
+.. code-block:: json
+
+ "QoSOptions": {
+ "ExceptionsAllowedBeforeBreaking": 3,
+ "DurationOfBreak": 1000,
+ "TimeoutValue": 5000
+ }
+
+- You must set a number equal or greater than ``2`` against **ExceptionsAllowedBeforeBreaking** for this rule to be implemented. [#f2]_
+- **DurationOfBreak** means the circuit breaker will stay open for 1 second after it is tripped.
+- **TimeoutValue** means if a request takes more than 5 seconds, it will automatically be timed out.
+
+You can set the **TimeoutValue** in isolation of the **ExceptionsAllowedBeforeBreaking** and **DurationOfBreak** options:
+
+.. code-block:: json
+
+ "QoSOptions": {
+ "TimeoutValue": 5000
+ }
+
+There is no point setting the other two in isolation as they affect each other!
+
+Defaults
+--------
+
+If you do not add a QoS section, QoS will not be used, however Ocelot will default to a **90** seconds timeout on all downstream requests.
+If someone needs this to be configurable, open an issue. [#f2]_
+
+.. _qos-polly-v7-vs-v8:
+
+`Polly`_ v7 vs v8
+-----------------
+
+Important changes in version `23.2`_: [#f3]_
+
+ - With `Polly`_ version 8+, the ``ExceptionsAllowedBeforeBreaking`` value must be equal to or greater than **2**!
+ - The ``AddPolly`` method has been migrated from v7 policy wrappers to v8 resilience pipelines. Consequently, it now exhibits different behavior based on v8 pipelines.
+
+If you prefer not to modify your settings, you can continue using `Polly`_ v7 as follows:
+
+.. code-block:: csharp
+
+ services.AddOcelot()
+ .AddPollyV7();
+
+**Note**: Support for `Polly`_ v7 will be removed in a future version. We recommend avoiding this method (which is tagged as ``Obsolete``) unless absolutely necessary.
+
+.. _qos-extensibility:
+
+Extensibility [#f3]_
+--------------------
+
+If you want to use your ``ResiliencePipeline`` provider, you can use the following syntax:
+
+.. code-block:: csharp
+
+ services.AddOcelot()
+ .AddPolly();
+ // MyProvider should implement IPollyQoSResiliencePipelineProvider
+ // Note: you can use standard provider PollyQoSResiliencePipelineProvider
+
+If, in addition, you want to use your own ``DelegatingHandler``, you can use the following syntax:
+
+.. code-block:: csharp
+
+ services.AddOcelot()
+ .AddPolly(MyQosDelegatingHandlerDelegate);
+ // MyProvider should implement IPollyQoSResiliencePipelineProvider
+ // Note: you can use standard provider PollyQoSResiliencePipelineProvider
+ // MyQosDelegatingHandlerDelegate is a delegate use to get a DelegatingHandler
+
+And finally, if you want to define your own set of exceptions to map, you can use the following syntax:
+
+.. code-block:: csharp
+
+ services.AddOcelot()
+ .AddPolly(MyErrorMapping);
+ // MyProvider should implement IPollyQoSResiliencePipelineProvider
+ // Note: you can use standard provider PollyQoSResiliencePipelineProvider
+
+ // MyErrorMapping is a Dictionary>, eg:
+ private static readonly Dictionary> MyErrorMapping = new()
+ {
+ {typeof(TaskCanceledException), CreateError},
+ {typeof(TimeoutRejectedException), CreateError},
+ {typeof(BrokenCircuitException), CreateError},
+ {typeof(BrokenCircuitException), CreateError},
+ };
+ private static Error CreateError(Exception e) => new RequestTimedOutError(e);
+
+""""
+
+.. [#f1] :ref:`di-the-addocelot-method` adds default ASP.NET services to DI container. You could call another extended :ref:`di-addocelotusingbuilder-method` while configuring services to develop your own :ref:`di-custom-builder`. See more instructions in the ":ref:`di-addocelotusingbuilder-method`" section of :doc:`../features/dependencyinjection` feature.
+.. [#f2] If something doesn't work or you get stuck, please review current `QoS issues `_ filtering by |QoS_label| label.
+.. [#f3] We upgraded `Polly`_ version from v7.x to v8.x! The :ref:`qos-extensibility` feature was requested in issue `1875`_ and delivered by PR `1914`_ as a part of version `23.2`_.
+
+.. _Polly: https://www.thepollyproject.org
+.. _1875: https://github.com/ThreeMammals/Ocelot/issues/1875
+.. _1914: https://github.com/ThreeMammals/Ocelot/pull/1914
+.. _23.2: https://github.com/ThreeMammals/Ocelot/releases/tag/23.2.0
+.. |QoS_label| image:: https://img.shields.io/badge/-QoS-D3ADAF.svg
+ :target: https://github.com/ThreeMammals/Ocelot/labels/QoS
diff --git a/docs/features/ratelimiting.rst b/docs/features/ratelimiting.rst
index 9a69f4dedf..94db1db5df 100644
--- a/docs/features/ratelimiting.rst
+++ b/docs/features/ratelimiting.rst
@@ -1,35 +1,50 @@
Rate Limiting
=============
+`What's rate limiting? `_
+
+* `Rate limiting | Wikipedia `_
+* `Rate Limiting pattern | Azure Architecture Center | Microsoft Learn `_
+* `Rate Limiting | Ask Google `_
+
Ocelot Own Implementation
-------------------------
-Ocelot supports rate limiting of upstream requests so that your downstream services do not become overloaded.
+Ocelot provides *rate limiting* for upstream requests to prevent downstream services from becoming overwhelmed. [#f1]_
-The authors of this feature were inspired by `@catcherwong article `_ to finally write this documentation.
-This feature was added by `@geffzhang `_ on GitHub! Thanks very much!
+Rate Limit by Client's Header
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-To get rate limiting working for a Route you need to add the following JSON to it:
+To implement *rate limiting* for a Route, you need to incorporate the following JSON configuration:
.. code-block:: json
"RateLimitOptions": {
- "ClientWhitelist": [],
+ "ClientWhitelist": [], // array of strings
"EnableRateLimiting": true,
- "Period": "1s",
- "PeriodTimespan": 1,
+ "Period": "1s", // seconds, minutes, hours, days
+ "PeriodTimespan": 1, // only seconds
"Limit": 1
}
-* **ClientWhitelist** - This is an array that contains the whitelist of the client.
- It means that the client in this array will not be affected by the rate limiting.
-* **EnableRateLimiting** - This value specifies enable endpoint rate limiting.
-* **Period** - This value specifies the period that the limit applies to, such as ``1s``, ``5m``, ``1h``, ``1d`` and so on.
- If you make more requests in the period than the limit allows then you need to wait for **PeriodTimespan** to elapse before you make another request.
-* **PeriodTimespan** - This value specifies that we can retry after a certain number of seconds.
-* **Limit** - This value specifies the maximum number of requests that a client can make in a defined period.
+* **ClientWhitelist** - An array containing the whitelisted clients. Clients listed here will be exempt from rate limiting.
+ For more information on the **ClientIdHeader** option, refer to the :ref:`rl-global-configuration` section.
+* **EnableRateLimiting** - This setting enables rate limiting on endpoints.
+* **Period** - This parameter defines the duration for which the limit is applicable, such as ``1s`` (seconds), ``5m`` (minutes), ``1h`` (hours), and ``1d`` (days).
+ If you reach the exact **Limit** of requests, the excess occurs immediately, and the **PeriodTimespan** begins.
+ You must wait for the **PeriodTimespan** duration to pass before making another request.
+ Should you exceed the number of requests within the period more than the **Limit** permits, the **QuotaExceededMessage** will appear in the response, accompanied by the **HttpStatusCode**.
+* **PeriodTimespan** - This parameter indicates the time in **seconds** after which a retry is permissible.
+ During this interval, the **QuotaExceededMessage** will appear in the response, accompanied by an **HttpStatusCode**.
+ Clients are advised to consult the ``Retry-After`` header to determine the timing of subsequent requests.
+* **Limit** - This parameter defines the upper limit of requests a client is allowed to make within a specified **Period**.
+
+.. _rl-global-configuration:
+
+Global Configuration
+^^^^^^^^^^^^^^^^^^^^
-You can also set the following in the **GlobalConfiguration** part of **ocelot.json**:
+You can set the following in the ``GlobalConfiguration`` section of `ocelot.json`_:
.. code-block:: json
@@ -38,33 +53,48 @@ You can also set the following in the **GlobalConfiguration** part of **ocelot.j
"RateLimitOptions": {
"DisableRateLimitHeaders": false,
"QuotaExceededMessage": "Customize Tips!",
- "HttpStatusCode": 123,
- "ClientIdHeader": "Test"
+ "HttpStatusCode": 418, // I'm a teapot
+ "ClientIdHeader": "MyRateLimiting"
}
}
-* **DisableRateLimitHeaders** - This value specifies whether ``X-Rate-Limit`` and ``Retry-After`` headers are disabled.
-* **QuotaExceededMessage** - This value specifies the exceeded message.
-* **HttpStatusCode** - This value specifies the returned HTTP status code when rate limiting occurs.
-* **ClientIdHeader** - Allows you to specifiy the header that should be used to identify clients. By default it is ``ClientId``
+* **DisableRateLimitHeaders** - Determines if the ``X-Rate-Limit`` and ``Retry-After`` headers are disabled.
+* **QuotaExceededMessage** - Defines the message displayed when the quota is exceeded. It is optional and the default message is informative.
+* **HttpStatusCode** - Indicates the HTTP status code returned during *rate limiting*. The default value is **429** (`Too Many Requests`_).
+* **ClientIdHeader** - Specifies the header used to identify clients, with ``ClientId`` as the default.
Future and ASP.NET Core Implementation
--------------------------------------
-The Ocelot team considers to redesign *Rate Limiting* feature,
-because of `Announcing Rate Limiting for .NET `_ by Brennan Conroy on July 13th, 2022.
-There is no decision at the moment, and the old version of the feature is included as a part of release `20.0 `_ for .NET 7.
+The Ocelot team is contemplating a redesign of the *Rate Limiting* feature following the `Announcing Rate Limiting for .NET`_ by Brennan Conroy on July 13th, 2022.
+Currently, no decision has been made, and the previous version of the feature remains part of the `20.0`_ release for .NET 7. [#f2]_
-See more about new feature being added into ASP.NET Core 7.0 release:
+Discover the new features being introduced in the ASP.NET Core 7.0 release:
-* `RateLimiter Class `_, since ASP.NET Core **7.0**
-* `System.Threading.RateLimiting `_ NuGet package
-* `Rate limiting middleware in ASP.NET Core `_ article by Arvin Kahbazi, Maarten Balliauw, and Rick Anderson
+* The `RateLimiter Class `_, available since ASP.NET Core 7.0
+* The `System.Threading.RateLimiting `_ NuGet package
+* The `Rate limiting middleware in ASP.NET Core `_ article by Arvin Kahbazi, Maarten Balliauw, and Rick Anderson
-However, it makes sense to keep the old implementation as a Ocelot built-in native feature, but we are going to migrate to the new Rate Limiter from ``Microsoft.AspNetCore.RateLimiting`` namespace.
+While retaining the old implementation as an Ocelot built-in feature makes sense, we plan to transition to the new Rate Limiter from the ``Microsoft.AspNetCore.RateLimiting`` namespace.
+Please share your thoughts with us in the `Discussions `_ space of the repository. |octocat|
+
+""""
+
+.. [#f1] Historically, the *"Ocelot Own Rate Limiting"* feature is one of the oldest and first features of Ocelot. This feature was delivered in PR `37`_ by `@geffzhang`_ on GitHub. Many thanks! It was initially released in version `1.3.2`_. The authors were inspired by `@catcherwong article`_ to write this documentation.
+.. [#f2] Since PR `37`_ and version `1.3.2`_, the Ocelot team has reviewed and redesigned the feature to provide stable behavior. The fix for bug `1590`_ (PR `1592`_) was released as part of version `23.3`_.
+
+.. _Announcing Rate Limiting for .NET: https://devblogs.microsoft.com/dotnet/announcing-rate-limiting-for-dotnet/
+.. _ocelot.json: https://github.com/ThreeMammals/Ocelot/blob/main/test/Ocelot.ManualTest/ocelot.json
+.. _@geffzhang: https://github.com/ThreeMammals/Ocelot/commits?author=geffzhang
+.. _@catcherwong article: http://www.c-sharpcorner.com/article/building-api-gateway-using-ocelot-in-asp-net-core-rate-limiting-part-four/
+.. _Too Many Requests: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429
+.. _37: https://github.com/ThreeMammals/Ocelot/pull/37
+.. _1590: https://github.com/ThreeMammals/Ocelot/issues/1590
+.. _1592: https://github.com/ThreeMammals/Ocelot/pull/1592
+.. _1.3.2: https://github.com/ThreeMammals/Ocelot/releases/tag/1.3.2
+.. _20.0: https://github.com/ThreeMammals/Ocelot/releases/tag/20.0.0
+.. _23.3: https://github.com/ThreeMammals/Ocelot/releases/tag/23.3.0
.. |octocat| image:: https://github.githubassets.com/images/icons/emoji/octocat.png
:alt: octocat
:width: 23
-
-Please, share your opinion to us in the `Discussions `_ space of the repository. |octocat|
diff --git a/docs/features/requestaggregation.rst b/docs/features/requestaggregation.rst
index a07e7f9821..c9a6423885 100644
--- a/docs/features/requestaggregation.rst
+++ b/docs/features/requestaggregation.rst
@@ -1,24 +1,105 @@
-Request Aggregation
-===================
+Request Aggregation [#f1]_
+==========================
Ocelot allows you to specify Aggregate Routes that compose multiple normal Routes and map their responses into one object.
This is usually where you have a client that is making multiple requests to a server where it could just be one.
-This feature allows you to start implementing back-end for a front-end (BFF) type architecture with Ocelot.
-
-This feature was requested as part of `issue 79 `_ and further improvements were made as part of `issue 298 `_.
+This feature allows you to start implementing back-end for a front-end (BFF) type architecture with Ocelot. [#f1]_
In order to set this up you must do something like the following in your **ocelot.json**.
Here we have specified two normal Routes and each one has a **Key** property.
We then specify an Aggregate that composes the two Routes using their keys in the **RouteKeys** list and says then we have the **UpstreamPathTemplate** which works like a normal Route.
Obviously you cannot have duplicate **UpstreamPathTemplates** between **Routes** and **Aggregates**.
-You can use all of Ocelot's normal Route options apart from **RequestIdKey** (explained in `gotchas <#gotchas>`_ below).
+You can use all of Ocelot's normal Route options apart from **RequestIdKey** (explained in :ref:`agg-gotchas` below).
+
+Basic Expecting JSON from Downstream Services
+---------------------------------------------
+
+.. code-block:: json
+
+ {
+ "Routes": [
+ {
+ "UpstreamHttpMethod": [ "Get" ],
+ "UpstreamPathTemplate": "/laura",
+ "DownstreamPathTemplate": "/",
+ "DownstreamScheme": "http",
+ "DownstreamHostAndPorts": [
+ { "Host": "localhost", "Port": 51881 }
+ ],
+ "Key": "Laura"
+ },
+ {
+ "UpstreamHttpMethod": [ "Get" ],
+ "UpstreamPathTemplate": "/tom",
+ "DownstreamPathTemplate": "/",
+ "DownstreamScheme": "http",
+ "DownstreamHostAndPorts": [
+ { "Host": "localhost", "Port": 51882 }
+ ],
+ "Key": "Tom"
+ }
+ ],
+ "Aggregates": [
+ {
+ "UpstreamPathTemplate": "/",
+ "RouteKeys": [ "Tom", "Laura" ]
+ }
+ ]
+ }
+
+You can also set **UpstreamHost** and **RouteIsCaseSensitive** in the Aggregate configuration. These behave the same as any other Routes.
+
+If the Route ``/tom`` returned a body of ``{"Age": 19}`` and ``/laura`` returned ``{"Age": 25}``, the the response after aggregation would be as follows:
+
+.. code-block:: json
+
+ {"Tom":{"Age": 19},"Laura":{"Age": 25}}
+
+At the moment the aggregation is very simple. Ocelot just gets the response from your downstream service and sticks it into a JSON dictionary as above.
+With the Route key being the key of the dictionary and the value the response body from your downstream service.
+You can see that the object is just JSON without any pretty spaces etc.
+
+Note, all headers will be lost from the downstream services response.
+
+Ocelot will always return content type ``application/json`` with an aggregate request.
+
+If you downstream services return a `404 Not Found `_, the aggregate will just return nothing for that downstream service.
+It will not change the aggregate response into a ``404`` even if all the downstreams return a ``404``.
+
+Use Complex Aggregation
+-----------------------
+
+Imagine you'd like to use aggregated queries, but you don't know all the parameters of your queries. You first need to call an endpoint to obtain the necessary data, for example a user's id, and then return the user's details.
+
+Let's say we have an endpoint that returns a series of comments with references to various users or threads. The author of the comments is referenced by his Id, but you'd like to return all the details about the author.
+
+Here, you could use aggregation to get 1) all the comments, 2) attach the author details. In fact there are 2 endpoints that are called, but for the 2nd, you dynamically replace the user's Id in the route to obtain the details.
+
+In concrete terms:
-Advanced Register Your Own Aggregators
---------------------------------------
+1) ``/Comments`` contains the authorId property
+2) ``/users/{userId}`` with ``{userId}`` replaced by **authorId** to obtain the user's details.
+
+This functionality is still in its early stages, but it does allow you to search for data based on an initial request.
+
+To perform the mapping, you need to use **AggregateRouteConfig**:
+
+.. code-block:: csharp
+
+ new AggregateRouteConfig
+ {
+ RouteKey = "UserDetails",
+ JsonPath = "$[*].authorId",
+ Parameter = "userId"
+ };
+
+**RouteKey** is used as a reference for the route, **JsonPath** indicates where the parameter you are interested in is located in the first request response body and **Parameter** tells us that the value for ``authorId`` should be used for the request parameter ``userId``.
+
+Register Your Own Aggregators
+-----------------------------
Ocelot started with just the basic request aggregation and since then we have added a more advanced method that let's the user take in the responses from the
downstream services and then aggregate them into a response object.
-
The **ocelot.json** setup is pretty much the same as the basic aggregation approach apart from you need to add an **Aggregator** property like below:
.. code-block:: json
@@ -60,7 +141,7 @@ The **ocelot.json** setup is pretty much the same as the basic aggregation appro
Here we have added an aggregator called ``FakeDefinedAggregator``. Ocelot is going to look for this aggregator when it tries to aggregate this Route.
-In order to make the aggregator available we must add the ``FakeDefinedAggregator`` to the ``OcelotBuilder`` being returned by ``AddOcelot()`` [#f1]_ like below:
+In order to make the aggregator available we must add the ``FakeDefinedAggregator`` to the ``OcelotBuilder`` being returned by ``AddOcelot()`` [#f2]_ like below:
.. code-block:: csharp
@@ -98,74 +179,52 @@ In order to make an Aggregator you must implement this interface:
}
With this feature you can pretty much do whatever you want because the ``HttpContext`` objects contain the results of all the aggregate requests.
-Please note, if the ``HttpClient`` throws an exception when making a request to a Route in the aggregate then you will not get a ``HttpContext`` for it, but you would for any that succeed.
-If it does throw an exception, this will be logged.
-Basic Expecting JSON from Downstream Services
----------------------------------------------
+Please note, if the ``HttpClient`` throws an exception when making a request to a Route in the aggregate then you will not get a ``HttpContext`` for it, but you would for any that succeed. If it does throw an exception, this will be logged.
-.. code-block:: json
+Below is an example of an aggregator that you could implement for your solution:
+.. code-block:: csharp
+
+ public class FakeDefinedAggregator : IDefinedAggregator
{
- "Routes": [
- {
- "UpstreamHttpMethod": [ "Get" ],
- "UpstreamPathTemplate": "/laura",
- "DownstreamPathTemplate": "/",
- "DownstreamScheme": "http",
- "DownstreamHostAndPorts": [
- { "Host": "localhost", "Port": 51881 }
- ],
- "Key": "Laura"
- },
- {
- "UpstreamHttpMethod": [ "Get" ],
- "UpstreamPathTemplate": "/tom",
- "DownstreamPathTemplate": "/",
- "DownstreamScheme": "http",
- "DownstreamHostAndPorts": [
- { "Host": "localhost", "Port": 51882 }
- ],
- "Key": "Tom"
- }
- ],
- "Aggregates": [
+ public async Task Aggregate(List responseHttpContexts)
{
- "UpstreamPathTemplate": "/",
- "RouteKeys": [
- "Tom",
- "Laura"
- ]
+ // The aggregator gets a list of downstream responses as parameter.
+ // You can now implement your own logic to aggregate the responses (including bodies and headers) from the downstream services
+ var responses = responseHttpContexts.Select(x => x.Items.DownstreamResponse()).ToArray();
+
+ // In this example we are concatenating the results,
+ // but you could create a more complex construct, up to you.
+ var contentList = new List();
+ foreach (var response in responses)
+ {
+ var content = await response.Content.ReadAsStringAsync();
+ contentList.Add(content);
+ }
+
+ // The only constraint here: You must return a DownstreamResponse object.
+ return new DownstreamResponse(
+ new StringContent(JsonConvert.SerializeObject(contentList)),
+ HttpStatusCode.OK,
+ responses.SelectMany(x => x.Headers).ToList(),
+ "reason");
}
- ]
}
-You can also set **UpstreamHost** and **RouteIsCaseSensitive** in the Aggregate configuration. These behave the same as any other Routes.
-
-If the Route ``/tom`` returned a body of ``{"Age": 19}`` and ``/laura`` returned ``{"Age": 25}``, the the response after aggregation would be as follows:
-
-.. code-block:: json
-
- {"Tom":{"Age": 19},"Laura":{"Age": 25}}
-
-At the moment the aggregation is very simple. Ocelot just gets the response from your downstream service and sticks it into a JSON dictionary as above.
-With the Route key being the key of the dictionary and the value the response body from your downstream service.
-You can see that the object is just JSON without any pretty spaces etc.
-
-Note, all headers will be lost from the downstream services response.
-
-Ocelot will always return content type ``application/json`` with an aggregate request.
-
-If you downstream services return a `404 Not Found `_, the aggregate will just return nothing for that downstream service.
-It will not change the aggregate response into a ``404`` even if all the downstreams return a ``404``.
+.. _agg-gotchas:
Gotchas
-------
-You cannot use Routes with specific **RequestIdKeys** as this would be crazy complicated to track.
+* You cannot use Routes with specific **RequestIdKeys** as this would be crazy complicated to track.
+* Aggregation only supports the ``GET`` HTTP verb.
+* Aggregation allows for the forwarding of ``HttpRequest.Body`` to downstream services by duplicating the body data.
+ Form data and attached files should also be forwarded.
+ It is essential to always specify the ``Content-Length`` header in requests to upstream; otherwise, Ocelot will log warnings like *"Aggregation does not support body copy without Content-Length header!"*.
-Aggregation only supports the ``GET`` HTTP verb.
""""
-.. [#f1] The ``AddOcelot`` method adds default ASP.NET services to DI-container. You could call another more extended ``AddOcelotUsingBuilder`` method while configuring services to build and use custom builder via an ``IMvcCoreBuilder`` interface object. See more instructions in :doc:`../features/dependencyinjection`, "**The AddOcelotUsingBuilder method**" section.
+.. [#f1] This feature was requested as part of `issue 79 `_ and further improvements were made as part of `issue 298 `_. A significant refactoring and revision of the `Multiplexer `_ design was carried out on March 4, 2024 in version `23.1 `_, see PRs `1826 `_ and `1462 `_.
+.. [#f2] :ref:`di-the-addocelot-method` adds default ASP.NET services to DI container. You could call another extended :ref:`di-addocelotusingbuilder-method` while configuring services to develop your own :ref:`di-custom-builder`. See more instructions in the ":ref:`di-addocelotusingbuilder-method`" section of :doc:`../features/dependencyinjection` feature.
diff --git a/docs/features/servicefabric.rst b/docs/features/servicefabric.rst
index 6e3906bd0c..9e4206afb7 100644
--- a/docs/features/servicefabric.rst
+++ b/docs/features/servicefabric.rst
@@ -9,7 +9,7 @@ We also need to set up the **ServiceDiscoveryProvider** in **GlobalConfiguration
The example here shows a typical configuration.
It assumes *Service Fabric* is running on ``localhost`` and that the naming service is on port ``19081``.
-The example below is taken from the `samples/OcelotServiceFabric `_ folder so please check it if this doesn't make sense!
+The example below is taken from the `OcelotServiceFabric `_ sample, so please check it if this doesn't make sense!
.. code-block:: json
@@ -24,6 +24,7 @@ The example below is taken from the `samples/OcelotServiceFabric `_
+.. _Polly: https://github.com/App-vNext/Polly
+.. _@ebjornset: https://github.com/ebjornset
+.. _@RaynaldM: https://github.com/RaynaldM
+.. _@ArwynFr: https://github.com/ArwynFr
+.. _@AlyHKafoury: https://github.com/AlyHKafoury
+.. _@FelixBoers: https://github.com/FelixBoers
+.. _23.2: https://github.com/ThreeMammals/Ocelot/releases/tag/23.2.0
+
+Welcome to Ocelot `23.2`_
======================================================================================
Thanks for taking a look at the Ocelot documentation! Please use the left hand navigation to get around.
@@ -9,6 +17,72 @@ The main features are :doc:`../features/configuration` and :doc:`../features/rou
We **do** follow development process which is described in :doc:`../building/releaseprocess`.
+Release Notes
+-------------
+
+ | **Release Tag**: `23.2.0 `_
+ | **Release Codename**: `Lunar Eclipse `_
+
+What's new?
+^^^^^^^^^^^
+
+- :doc:`../features/configuration`: A brand new :ref:`config-merging-tomemory` by `@ebjornset`_ as a part of the :ref:`config-merging-files` feature.
+
+ The ``AddOcelot`` method merges the **ocelot.*.json** files into a single **ocelot.json** file as the primary configuration file, which is written back to disk and then added to the ``IConfigurationBuilder`` for the well-known ``IConfiguration``. You can now call another ``AddOcelot`` method that adds the merged JSON directly from memory to the ``IConfigurationBuilder``, using ``AddJsonStream`` instead.
+
+ See more details in :ref:`di-configuration-overview` of :doc:`../features/dependencyinjection`.
+
+- :doc:`../features/servicefabric`: Published old undocumented :ref:`sf-placeholders` feature of :doc:`../features/servicefabric` `service discovery provider `_.
+
+ This feature by `@FelixBoers`_ is available starting from version `13.0.0 `_.
+
+- :doc:`../features/qualityofservice`: A brand new `Polly`_ v8 pipelines :ref:`qos-extensibility` feature by `@RaynaldM`_
+
+Focus On
+^^^^^^^^
+
+Updates of the features
+"""""""""""""""""""""""
+
+ - :doc:`../features/configuration`: New :ref:`config-merging-tomemory` feature by `@ebjornset`_
+ - :doc:`../features/dependencyinjection`: Added new overloaded :ref:`di-configuration-addocelot` by `@ebjornset`_
+ - :doc:`../features/qualityofservice`: Support of new `Polly`_ v8 syntax and new :ref:`qos-extensibility` feature by `@RaynaldM`_
+
+Ocelot extra packages
+"""""""""""""""""""""
+
+ - `Ocelot.Provider.Polly `_: Support of new `Polly`_ v8 syntax.
+
+ | *Polly* `8.0+ `_ versions introduced the concept of `resilience pipelines `_.
+ | All `AddPolly extensions `_ have been automatically migrated from **v7** to **v8**.
+ | Please note that older **v7** extensions are marked with the ``[Obsolete]`` attribute and renamed using the ``V7`` suffix. And the old **v7** implementation has been moved to the `v7 namespace `_.
+ | See more details in :ref:`qos-polly-v7-vs-v8` section of :doc:`../features/qualityofservice` chapter.
+
+Stabilization aka bug fixing
+""""""""""""""""""""""""""""
+
+ - `683 `_ by PR `1927 `_. Thanks to `@AlyHKafoury`_!
+
+ | `New rules `_ have been added to Ocelot's configuration validation logic to find duplicate placeholders in path templates.
+ | See more in the `FileConfigurationFluentValidator `_ class.
+
+ - `1518 `_ hotfix by PR `1986 `_. Thanks to `@ArwynFr`_!
+
+ | Using the default ``IServiceCollection`` `DI extensions `_ to register Ocelot services resulted in the ``ServiceCollection`` provider being forced to be created by calling ``BuildServiceProvider()``.
+ | This resulted in problems with dependency injection libraries, or worse, causing the Ocelot app to crash!
+ | See more in the `ServiceCollectionExtensions `_ class.
+
+ - See `all bugs `_ of the `February'24 `_ milestone
+
+Updated Documentation
+"""""""""""""""""""""
+
+ - :doc:`../features/configuration`
+ - :doc:`../features/dependencyinjection`
+ - :doc:`../features/qualityofservice`
+ - :doc:`../features/servicefabric`
+
+
.. toctree::
:maxdepth: 2
:hidden:
diff --git a/docs/introduction/gettingstarted.rst b/docs/introduction/gettingstarted.rst
index 3332619926..2640b4e4be 100644
--- a/docs/introduction/gettingstarted.rst
+++ b/docs/introduction/gettingstarted.rst
@@ -71,7 +71,11 @@ Program
^^^^^^^
Then in your **Program.cs** you will want to have the following.
-The main things to note are ``AddOcelot()`` [#f1]_ (adds Ocelot default services), ``UseOcelot().Wait()`` (sets up all the Ocelot middleware).
+
+The main things to note are
+
+* ``AddOcelot()`` adds Ocelot required and default services [#f1]_
+* ``UseOcelot().Wait()`` sets up all the Ocelot middlewares.
.. code-block:: csharp
@@ -120,4 +124,4 @@ The main things to note are ``AddOcelot()`` [#f1]_ (adds Ocelot default services
""""
-.. [#f1] The ``AddOcelot`` method adds default ASP.NET services to DI-container. You could call another more extended ``AddOcelotUsingBuilder`` method while configuring services to build and use custom builder via an ``IMvcCoreBuilder`` interface object. See more instructions in :doc:`../features/dependencyinjection`, "**The AddOcelotUsingBuilder method**" section.
+.. [#f1] :ref:`di-the-addocelot-method` adds default ASP.NET services to DI container. You could call another extended :ref:`di-addocelotusingbuilder-method` while configuring services to develop your own :ref:`di-custom-builder`. See more instructions in the ":ref:`di-addocelotusingbuilder-method`" section of :doc:`../features/dependencyinjection` feature.
diff --git a/samples/AdministrationApi/Issue645.postman_collection.json b/samples/Administration/Issue645.postman_collection.json
similarity index 97%
rename from samples/AdministrationApi/Issue645.postman_collection.json
rename to samples/Administration/Issue645.postman_collection.json
index c9bac89f56..6fedd16b9c 100644
--- a/samples/AdministrationApi/Issue645.postman_collection.json
+++ b/samples/Administration/Issue645.postman_collection.json
@@ -1,150 +1,150 @@
-{
- "info": {
- "_postman_id": "6234b40a-e363-4c73-8577-1c9074abb951",
- "name": "Issue645",
- "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
- },
- "item": [
- {
- "name": "1. GET http://localhost: 55580/administration/.well-known/openid-configuration",
- "request": {
- "method": "GET",
- "header": [
- {
- "key": "Authorization",
- "value": "Bearer {{AccessToken}}"
- }
- ],
- "body": {},
- "url": {
- "raw": "http://localhost:5000/administration/.well-known/openid-configuration",
- "protocol": "http",
- "host": [
- "localhost"
- ],
- "port": "5000",
- "path": [
- "administration",
- ".well-known",
- "openid-configuration"
- ]
- }
- },
- "response": []
- },
- {
- "name": "3. GET http://localhost: 55580/administration/configuration",
- "request": {
- "method": "POST",
- "header": [
- {
- "key": "Authorization",
- "value": "Bearer {{AccessToken}}"
- },
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "body": {
- "mode": "raw",
- "raw": "{\r\n \"routes\": [\r\n {\r\n \"downstreamPathTemplate\": \"/{everything}\",\r\n \"upstreamPathTemplate\": \"/templates/{everything}\",\r\n \"upstreamHttpMethod\": [\r\n \"GET\"\r\n ],\r\n \"addHeadersToRequest\": {},\r\n \"upstreamHeaderTransform\": {},\r\n \"downstreamHeaderTransform\": {},\r\n \"addClaimsToRequest\": {},\r\n \"routeClaimsRequirement\": {},\r\n \"addQueriesToRequest\": {},\r\n \"requestIdKey\": null,\r\n \"fileCacheOptions\": {\r\n \"ttlSeconds\": 0,\r\n \"region\": null\r\n },\r\n \"routeIsCaseSensitive\": false,\r\n \"downstreamScheme\": \"http\",\r\n \"qoSOptions\": {\r\n \"exceptionsAllowedBeforeBreaking\": 0,\r\n \"durationOfBreak\": 0,\r\n \"timeoutValue\": 0\r\n },\r\n \"loadBalancerOptions\": {\r\n \"type\": null,\r\n \"key\": null,\r\n \"expiry\": 0\r\n },\r\n \"rateLimitOptions\": {\r\n \"clientWhitelist\": [],\r\n \"enableRateLimiting\": false,\r\n \"period\": null,\r\n \"periodTimespan\": 0,\r\n \"limit\": 0\r\n },\r\n \"authenticationOptions\": {\r\n \"authenticationProviderKey\": null,\r\n \"allowedScopes\": []\r\n },\r\n \"httpHandlerOptions\": {\r\n \"allowAutoRedirect\": false,\r\n \"useCookieContainer\": false,\r\n \"useTracing\": false,\r\n \"useProxy\": true\r\n },\r\n \"downstreamHostAndPorts\": [\r\n {\r\n \"host\": \"localhost\",\r\n \"port\": 50689\r\n }\r\n ],\r\n \"upstreamHost\": null,\r\n \"key\": null,\r\n \"delegatingHandlers\": [],\r\n \"priority\": 1,\r\n \"timeout\": 0,\r\n \"dangerousAcceptAnyServerCertificateValidator\": false\r\n }\r\n ],\r\n \"aggregates\": [],\r\n \"globalConfiguration\": {\r\n \"requestIdKey\": \"Request-Id\",\r\n \"rateLimitOptions\": {\r\n \"clientIdHeader\": \"ClientId\",\r\n \"quotaExceededMessage\": null,\r\n \"rateLimitCounterPrefix\": \"ocelot\",\r\n \"disableRateLimitHeaders\": false,\r\n \"httpStatusCode\": 429\r\n },\r\n \"qoSOptions\": {\r\n \"exceptionsAllowedBeforeBreaking\": 0,\r\n \"durationOfBreak\": 0,\r\n \"timeoutValue\": 0\r\n },\r\n \"baseUrl\": \"http://localhost:55580\",\r\n \"loadBalancerOptions\": {\r\n \"type\": null,\r\n \"key\": null,\r\n \"expiry\": 0\r\n },\r\n \"downstreamScheme\": null,\r\n \"httpHandlerOptions\": {\r\n \"allowAutoRedirect\": false,\r\n \"useCookieContainer\": false,\r\n \"useTracing\": false,\r\n \"useProxy\": true\r\n }\r\n }\r\n}"
- },
- "url": {
- "raw": "http://localhost:5000/administration/configuration",
- "protocol": "http",
- "host": [
- "localhost"
- ],
- "port": "5000",
- "path": [
- "administration",
- "configuration"
- ]
- }
- },
- "response": []
- },
- {
- "name": "2. POST http://localhost: 55580/administration/connect/token",
- "event": [
- {
- "listen": "test",
- "script": {
- "type": "text/javascript",
- "exec": [
- "var jsonData = JSON.parse(responseBody);",
- "postman.setGlobalVariable(\"AccessToken\", jsonData.access_token);",
- "postman.setGlobalVariable(\"RefreshToken\", jsonData.refresh_token);"
- ]
- }
- }
- ],
- "request": {
- "method": "POST",
- "header": [],
- "body": {
- "mode": "formdata",
- "formdata": [
- {
- "key": "client_id",
- "value": "admin",
- "type": "text"
- },
- {
- "key": "client_secret",
- "value": "secret",
- "type": "text"
- },
- {
- "key": "scope",
- "value": "admin",
- "type": "text"
- },
- {
- "key": "grant_type",
- "value": "client_credentials",
- "type": "text"
- }
- ]
- },
- "url": {
- "raw": "http://localhost:5000/administration/connect/token",
- "protocol": "http",
- "host": [
- "localhost"
- ],
- "port": "5000",
- "path": [
- "administration",
- "connect",
- "token"
- ]
- }
- },
- "response": []
- }
- ],
- "event": [
- {
- "listen": "prerequest",
- "script": {
- "id": "0f60e7b3-e4f1-4458-bbc4-fc4809e86b2d",
- "type": "text/javascript",
- "exec": [
- string.Empty
- ]
- }
- },
- {
- "listen": "test",
- "script": {
- "id": "1279a2cf-b771-4a86-9dfa-302b240fac62",
- "type": "text/javascript",
- "exec": [
- string.Empty
- ]
- }
- }
- ]
+{
+ "info": {
+ "_postman_id": "6234b40a-e363-4c73-8577-1c9074abb951",
+ "name": "Issue645",
+ "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
+ },
+ "item": [
+ {
+ "name": "1. GET http://localhost: 55580/administration/.well-known/openid-configuration",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "Authorization",
+ "value": "Bearer {{AccessToken}}"
+ }
+ ],
+ "body": {},
+ "url": {
+ "raw": "http://localhost:5000/administration/.well-known/openid-configuration",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "5000",
+ "path": [
+ "administration",
+ ".well-known",
+ "openid-configuration"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "3. GET http://localhost: 55580/administration/configuration",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Authorization",
+ "value": "Bearer {{AccessToken}}"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\r\n \"routes\": [\r\n {\r\n \"downstreamPathTemplate\": \"/{everything}\",\r\n \"upstreamPathTemplate\": \"/templates/{everything}\",\r\n \"upstreamHttpMethod\": [\r\n \"GET\"\r\n ],\r\n \"addHeadersToRequest\": {},\r\n \"upstreamHeaderTransform\": {},\r\n \"downstreamHeaderTransform\": {},\r\n \"addClaimsToRequest\": {},\r\n \"routeClaimsRequirement\": {},\r\n \"addQueriesToRequest\": {},\r\n \"requestIdKey\": null,\r\n \"fileCacheOptions\": {\r\n \"ttlSeconds\": 0,\r\n \"region\": null\r\n },\r\n \"routeIsCaseSensitive\": false,\r\n \"downstreamScheme\": \"http\",\r\n \"qoSOptions\": {\r\n \"exceptionsAllowedBeforeBreaking\": 0,\r\n \"durationOfBreak\": 0,\r\n \"timeoutValue\": 0\r\n },\r\n \"loadBalancerOptions\": {\r\n \"type\": null,\r\n \"key\": null,\r\n \"expiry\": 0\r\n },\r\n \"rateLimitOptions\": {\r\n \"clientWhitelist\": [],\r\n \"enableRateLimiting\": false,\r\n \"period\": null,\r\n \"periodTimespan\": 0,\r\n \"limit\": 0\r\n },\r\n \"authenticationOptions\": {\r\n \"authenticationProviderKey\": null,\r\n \"allowedScopes\": []\r\n },\r\n \"httpHandlerOptions\": {\r\n \"allowAutoRedirect\": false,\r\n \"useCookieContainer\": false,\r\n \"useTracing\": false,\r\n \"useProxy\": true\r\n },\r\n \"downstreamHostAndPorts\": [\r\n {\r\n \"host\": \"localhost\",\r\n \"port\": 50689\r\n }\r\n ],\r\n \"upstreamHost\": null,\r\n \"key\": null,\r\n \"delegatingHandlers\": [],\r\n \"priority\": 1,\r\n \"timeout\": 0,\r\n \"dangerousAcceptAnyServerCertificateValidator\": false\r\n }\r\n ],\r\n \"aggregates\": [],\r\n \"globalConfiguration\": {\r\n \"requestIdKey\": \"Request-Id\",\r\n \"rateLimitOptions\": {\r\n \"clientIdHeader\": \"ClientId\",\r\n \"quotaExceededMessage\": null,\r\n \"rateLimitCounterPrefix\": \"ocelot\",\r\n \"disableRateLimitHeaders\": false,\r\n \"httpStatusCode\": 429\r\n },\r\n \"qoSOptions\": {\r\n \"exceptionsAllowedBeforeBreaking\": 0,\r\n \"durationOfBreak\": 0,\r\n \"timeoutValue\": 0\r\n },\r\n \"baseUrl\": \"http://localhost:55580\",\r\n \"loadBalancerOptions\": {\r\n \"type\": null,\r\n \"key\": null,\r\n \"expiry\": 0\r\n },\r\n \"downstreamScheme\": null,\r\n \"httpHandlerOptions\": {\r\n \"allowAutoRedirect\": false,\r\n \"useCookieContainer\": false,\r\n \"useTracing\": false,\r\n \"useProxy\": true\r\n }\r\n }\r\n}"
+ },
+ "url": {
+ "raw": "http://localhost:5000/administration/configuration",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "5000",
+ "path": [
+ "administration",
+ "configuration"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "2. POST http://localhost: 55580/administration/connect/token",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "type": "text/javascript",
+ "exec": [
+ "var jsonData = JSON.parse(responseBody);",
+ "postman.setGlobalVariable(\"AccessToken\", jsonData.access_token);",
+ "postman.setGlobalVariable(\"RefreshToken\", jsonData.refresh_token);"
+ ]
+ }
+ }
+ ],
+ "request": {
+ "method": "POST",
+ "header": [],
+ "body": {
+ "mode": "formdata",
+ "formdata": [
+ {
+ "key": "client_id",
+ "value": "admin",
+ "type": "text"
+ },
+ {
+ "key": "client_secret",
+ "value": "secret",
+ "type": "text"
+ },
+ {
+ "key": "scope",
+ "value": "admin",
+ "type": "text"
+ },
+ {
+ "key": "grant_type",
+ "value": "client_credentials",
+ "type": "text"
+ }
+ ]
+ },
+ "url": {
+ "raw": "http://localhost:5000/administration/connect/token",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "5000",
+ "path": [
+ "administration",
+ "connect",
+ "token"
+ ]
+ }
+ },
+ "response": []
+ }
+ ],
+ "event": [
+ {
+ "listen": "prerequest",
+ "script": {
+ "id": "0f60e7b3-e4f1-4458-bbc4-fc4809e86b2d",
+ "type": "text/javascript",
+ "exec": [
+ string.Empty
+ ]
+ }
+ },
+ {
+ "listen": "test",
+ "script": {
+ "id": "1279a2cf-b771-4a86-9dfa-302b240fac62",
+ "type": "text/javascript",
+ "exec": [
+ string.Empty
+ ]
+ }
+ }
+ ]
}
\ No newline at end of file
diff --git a/samples/AdministrationApi/AdministrationApi.csproj b/samples/Administration/Ocelot.Samples.AdministrationApi.csproj
similarity index 100%
rename from samples/AdministrationApi/AdministrationApi.csproj
rename to samples/Administration/Ocelot.Samples.AdministrationApi.csproj
diff --git a/samples/AdministrationApi/Program.cs b/samples/Administration/Program.cs
similarity index 100%
rename from samples/AdministrationApi/Program.cs
rename to samples/Administration/Program.cs
diff --git a/samples/AdministrationApi/Properties/launchSettings.json b/samples/Administration/Properties/launchSettings.json
similarity index 100%
rename from samples/AdministrationApi/Properties/launchSettings.json
rename to samples/Administration/Properties/launchSettings.json
diff --git a/samples/AdministrationApi/README.md b/samples/Administration/README.md
similarity index 96%
rename from samples/AdministrationApi/README.md
rename to samples/Administration/README.md
index 42a01b2f4e..59d236aa75 100644
--- a/samples/AdministrationApi/README.md
+++ b/samples/Administration/README.md
@@ -1,94 +1,94 @@
-```json
-{
- "routes": [
- {
- "downstreamPathTemplate": "/{everything}",
- "upstreamPathTemplate": "/templates/{everything}",
- "upstreamHttpMethod": [
- "GET"
- ],
- "addHeadersToRequest": {},
- "upstreamHeaderTransform": {},
- "downstreamHeaderTransform": {},
- "addClaimsToRequest": {},
- "routeClaimsRequirement": {},
- "addQueriesToRequest": {},
- "requestIdKey": null,
- "fileCacheOptions": {
- "ttlSeconds": 0,
- "region": null
- },
- "routeIsCaseSensitive": false,
- "downstreamScheme": "http",
- "qoSOptions": {
- "exceptionsAllowedBeforeBreaking": 0,
- "durationOfBreak": 0,
- "timeoutValue": 0
- },
- "loadBalancerOptions": {
- "type": null,
- "key": null,
- "expiry": 0
- },
- "rateLimitOptions": {
- "clientWhitelist": [],
- "enableRateLimiting": false,
- "period": null,
- "periodTimespan": 0,
- "limit": 0
- },
- "authenticationOptions": {
- "authenticationProviderKey": null,
- "allowedScopes": []
- },
- "httpHandlerOptions": {
- "allowAutoRedirect": false,
- "useCookieContainer": false,
- "useTracing": false,
- "useProxy": true
- },
- "downstreamHostAndPorts": [
- {
- "host": "localhost",
- "port": 50689
- }
- ],
- "upstreamHost": null,
- "key": null,
- "delegatingHandlers": [],
- "priority": 1,
- "timeout": 0,
- "dangerousAcceptAnyServerCertificateValidator": false
- }
- ],
- "aggregates": [],
- "globalConfiguration": {
- "requestIdKey": "Request-Id",
- "rateLimitOptions": {
- "clientIdHeader": "ClientId",
- "quotaExceededMessage": null,
- "rateLimitCounterPrefix": "ocelot",
- "disableRateLimitHeaders": false,
- "httpStatusCode": 429
- },
- "qoSOptions": {
- "exceptionsAllowedBeforeBreaking": 0,
- "durationOfBreak": 0,
- "timeoutValue": 0
- },
- "baseUrl": "http://localhost:55580",
- "loadBalancerOptions": {
- "type": null,
- "key": null,
- "expiry": 0
- },
- "downstreamScheme": null,
- "httpHandlerOptions": {
- "allowAutoRedirect": false,
- "useCookieContainer": false,
- "useTracing": false,
- "useProxy": true
- }
- }
-}
-```
+```json
+{
+ "routes": [
+ {
+ "downstreamPathTemplate": "/{everything}",
+ "upstreamPathTemplate": "/templates/{everything}",
+ "upstreamHttpMethod": [
+ "GET"
+ ],
+ "addHeadersToRequest": {},
+ "upstreamHeaderTransform": {},
+ "downstreamHeaderTransform": {},
+ "addClaimsToRequest": {},
+ "routeClaimsRequirement": {},
+ "addQueriesToRequest": {},
+ "requestIdKey": null,
+ "fileCacheOptions": {
+ "ttlSeconds": 0,
+ "region": null
+ },
+ "routeIsCaseSensitive": false,
+ "downstreamScheme": "http",
+ "qoSOptions": {
+ "exceptionsAllowedBeforeBreaking": 0,
+ "durationOfBreak": 0,
+ "timeoutValue": 0
+ },
+ "loadBalancerOptions": {
+ "type": null,
+ "key": null,
+ "expiry": 0
+ },
+ "rateLimitOptions": {
+ "clientWhitelist": [],
+ "enableRateLimiting": false,
+ "period": null,
+ "periodTimespan": 0,
+ "limit": 0
+ },
+ "authenticationOptions": {
+ "authenticationProviderKey": null,
+ "allowedScopes": []
+ },
+ "httpHandlerOptions": {
+ "allowAutoRedirect": false,
+ "useCookieContainer": false,
+ "useTracing": false,
+ "useProxy": true
+ },
+ "downstreamHostAndPorts": [
+ {
+ "host": "localhost",
+ "port": 50689
+ }
+ ],
+ "upstreamHost": null,
+ "key": null,
+ "delegatingHandlers": [],
+ "priority": 1,
+ "timeout": 0,
+ "dangerousAcceptAnyServerCertificateValidator": false
+ }
+ ],
+ "aggregates": [],
+ "globalConfiguration": {
+ "requestIdKey": "Request-Id",
+ "rateLimitOptions": {
+ "clientIdHeader": "ClientId",
+ "quotaExceededMessage": null,
+ "rateLimitCounterPrefix": "ocelot",
+ "disableRateLimitHeaders": false,
+ "httpStatusCode": 429
+ },
+ "qoSOptions": {
+ "exceptionsAllowedBeforeBreaking": 0,
+ "durationOfBreak": 0,
+ "timeoutValue": 0
+ },
+ "baseUrl": "http://localhost:55580",
+ "loadBalancerOptions": {
+ "type": null,
+ "key": null,
+ "expiry": 0
+ },
+ "downstreamScheme": null,
+ "httpHandlerOptions": {
+ "allowAutoRedirect": false,
+ "useCookieContainer": false,
+ "useTracing": false,
+ "useProxy": true
+ }
+ }
+}
+```
diff --git a/samples/AdministrationApi/appsettings.json b/samples/Administration/appsettings.json
similarity index 100%
rename from samples/AdministrationApi/appsettings.json
rename to samples/Administration/appsettings.json
diff --git a/samples/AdministrationApi/ocelot.json b/samples/Administration/ocelot.json
similarity index 95%
rename from samples/AdministrationApi/ocelot.json
rename to samples/Administration/ocelot.json
index 0fa4143fff..02e7c55129 100644
--- a/samples/AdministrationApi/ocelot.json
+++ b/samples/Administration/ocelot.json
@@ -1,18 +1,18 @@
-{
- "Routes": [
- {
- "DownstreamPathTemplate": "/service/stats/collected",
- "DownstreamScheme": "http",
- "DownstreamHostAndPorts": [
- {
- "Host": "localhost",
- "Port": 5100
- }
- ],
- "UpstreamPathTemplate": "/api/stats/collected"
- }
- ],
- "GlobalConfiguration": {
- "BaseUrl": "http://localhost:5000"
- }
+{
+ "Routes": [
+ {
+ "DownstreamPathTemplate": "/service/stats/collected",
+ "DownstreamScheme": "http",
+ "DownstreamHostAndPorts": [
+ {
+ "Host": "localhost",
+ "Port": 5100
+ }
+ ],
+ "UpstreamPathTemplate": "/api/stats/collected"
+ }
+ ],
+ "GlobalConfiguration": {
+ "BaseUrl": "http://localhost:5000"
+ }
}
\ No newline at end of file
diff --git a/samples/AdministrationApi/tempkey.rsa b/samples/Administration/tempkey.rsa
similarity index 100%
rename from samples/AdministrationApi/tempkey.rsa
rename to samples/Administration/tempkey.rsa
diff --git a/samples/OcelotBasic/Ocelot.Samples.OcelotBasic.ApiGateway.csproj b/samples/Basic/Ocelot.Samples.Basic.ApiGateway.csproj
similarity index 100%
rename from samples/OcelotBasic/Ocelot.Samples.OcelotBasic.ApiGateway.csproj
rename to samples/Basic/Ocelot.Samples.Basic.ApiGateway.csproj
diff --git a/samples/OcelotBasic/Program.cs b/samples/Basic/Program.cs
similarity index 97%
rename from samples/OcelotBasic/Program.cs
rename to samples/Basic/Program.cs
index 34c555f199..3901407c20 100644
--- a/samples/OcelotBasic/Program.cs
+++ b/samples/Basic/Program.cs
@@ -1,29 +1,29 @@
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
-using System.IO;
+using System.IO;
-namespace Ocelot.Samples.OcelotBasic.ApiGateway;
-
-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();
- })
- .ConfigureLogging((hostingContext, logging) =>
+namespace Ocelot.Samples.OcelotBasic.ApiGateway;
+
+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();
+ })
+ .ConfigureLogging((hostingContext, logging) =>
{
if (hostingContext.HostingEnvironment.IsDevelopment())
{
@@ -31,10 +31,10 @@ public static void Main(string[] args)
logging.AddConsole();
}
//add your logging
- })
- .UseIISIntegration()
+ })
+ .UseIISIntegration()
.UseStartup()
- .Build()
- .Run();
- }
-}
+ .Build()
+ .Run();
+ }
+}
diff --git a/samples/OcelotBasic/Properties/launchSettings.json b/samples/Basic/Properties/launchSettings.json
similarity index 100%
rename from samples/OcelotBasic/Properties/launchSettings.json
rename to samples/Basic/Properties/launchSettings.json
diff --git a/samples/OcelotBasic/Startup.cs b/samples/Basic/Startup.cs
similarity index 100%
rename from samples/OcelotBasic/Startup.cs
rename to samples/Basic/Startup.cs
diff --git a/samples/OcelotBasic/appsettings.Development.json b/samples/Basic/appsettings.Development.json
similarity index 100%
rename from samples/OcelotBasic/appsettings.Development.json
rename to samples/Basic/appsettings.Development.json
diff --git a/samples/OcelotKube/ApiGateway/appsettings.json b/samples/Basic/appsettings.json
similarity index 100%
rename from samples/OcelotKube/ApiGateway/appsettings.json
rename to samples/Basic/appsettings.json
diff --git a/samples/OcelotBasic/ocelot.json b/samples/Basic/ocelot.json
similarity index 95%
rename from samples/OcelotBasic/ocelot.json
rename to samples/Basic/ocelot.json
index 2864550cd7..7cab024305 100644
--- a/samples/OcelotBasic/ocelot.json
+++ b/samples/Basic/ocelot.json
@@ -1,21 +1,21 @@
-{
- "Routes": [
- {
- "DownstreamPathTemplate": "/todos/{id}",
- "DownstreamScheme": "https",
- "DownstreamHostAndPorts": [
- {
- "Host": "jsonplaceholder.typicode.com",
- "Port": 443
- }
- ],
- "UpstreamPathTemplate": "/posts/{id}",
- "UpstreamHttpMethod": [
- "Get"
- ]
- }
- ],
- "GlobalConfiguration": {
- "BaseUrl": "https://localhost:5000"
- }
+{
+ "Routes": [
+ {
+ "DownstreamPathTemplate": "/todos/{id}",
+ "DownstreamScheme": "https",
+ "DownstreamHostAndPorts": [
+ {
+ "Host": "jsonplaceholder.typicode.com",
+ "Port": 443
+ }
+ ],
+ "UpstreamPathTemplate": "/posts/{id}",
+ "UpstreamHttpMethod": [
+ "Get"
+ ]
+ }
+ ],
+ "GlobalConfiguration": {
+ "BaseUrl": "https://localhost:5000"
+ }
}
diff --git a/samples/Docker/README.md b/samples/Docker/README.md
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/samples/OcelotEureka/ApiGateway/ApiGateway.csproj b/samples/Eureka/ApiGateway/Ocelot.Samples.Eureka.ApiGateway.csproj
similarity index 100%
rename from samples/OcelotEureka/ApiGateway/ApiGateway.csproj
rename to samples/Eureka/ApiGateway/Ocelot.Samples.Eureka.ApiGateway.csproj
diff --git a/samples/OcelotEureka/ApiGateway/Program.cs b/samples/Eureka/ApiGateway/Program.cs
similarity index 100%
rename from samples/OcelotEureka/ApiGateway/Program.cs
rename to samples/Eureka/ApiGateway/Program.cs
diff --git a/samples/OcelotEureka/ApiGateway/Properties/launchSettings.json b/samples/Eureka/ApiGateway/Properties/launchSettings.json
similarity index 100%
rename from samples/OcelotEureka/ApiGateway/Properties/launchSettings.json
rename to samples/Eureka/ApiGateway/Properties/launchSettings.json
diff --git a/samples/OcelotEureka/ApiGateway/appsettings.json b/samples/Eureka/ApiGateway/appsettings.json
similarity index 100%
rename from samples/OcelotEureka/ApiGateway/appsettings.json
rename to samples/Eureka/ApiGateway/appsettings.json
diff --git a/samples/OcelotEureka/ApiGateway/ocelot.json b/samples/Eureka/ApiGateway/ocelot.json
similarity index 96%
rename from samples/OcelotEureka/ApiGateway/ocelot.json
rename to samples/Eureka/ApiGateway/ocelot.json
index 5a69973de9..747cf23c80 100644
--- a/samples/OcelotEureka/ApiGateway/ocelot.json
+++ b/samples/Eureka/ApiGateway/ocelot.json
@@ -1,22 +1,22 @@
-{
- "Routes": [
- {
- "DownstreamPathTemplate": "/api/Category",
- "DownstreamScheme": "http",
- "UpstreamPathTemplate": "/Category",
- "ServiceName": "ncore-rat",
- "UpstreamHttpMethod": [ "Get" ],
- "QoSOptions": {
- "ExceptionsAllowedBeforeBreaking": 3,
- "DurationOfBreak": 10000,
- "TimeoutValue": 5000
- },
- "FileCacheOptions": { "TtlSeconds": 15 }
- }
- ],
- "GlobalConfiguration": {
- "RequestIdKey": "OcRequestId",
- "AdministrationPath": "/administration",
- "ServiceDiscoveryProvider": { "Type": "Eureka" }
- }
-}
+{
+ "Routes": [
+ {
+ "DownstreamPathTemplate": "/api/Category",
+ "DownstreamScheme": "http",
+ "UpstreamPathTemplate": "/Category",
+ "ServiceName": "ncore-rat",
+ "UpstreamHttpMethod": [ "Get" ],
+ "QoSOptions": {
+ "ExceptionsAllowedBeforeBreaking": 3,
+ "DurationOfBreak": 10000,
+ "TimeoutValue": 5000
+ },
+ "FileCacheOptions": { "TtlSeconds": 15 }
+ }
+ ],
+ "GlobalConfiguration": {
+ "RequestIdKey": "OcRequestId",
+ "AdministrationPath": "/administration",
+ "ServiceDiscoveryProvider": { "Type": "Eureka" }
+ }
+}
diff --git a/samples/OcelotEureka/DownstreamService/Controllers/CategoryController.cs b/samples/Eureka/DownstreamService/Controllers/CategoryController.cs
similarity index 100%
rename from samples/OcelotEureka/DownstreamService/Controllers/CategoryController.cs
rename to samples/Eureka/DownstreamService/Controllers/CategoryController.cs
diff --git a/samples/OcelotEureka/DownstreamService/DownstreamService.csproj b/samples/Eureka/DownstreamService/Ocelot.Samples.Eureka.DownstreamService.csproj
similarity index 100%
rename from samples/OcelotEureka/DownstreamService/DownstreamService.csproj
rename to samples/Eureka/DownstreamService/Ocelot.Samples.Eureka.DownstreamService.csproj
diff --git a/samples/OcelotEureka/DownstreamService/Program.cs b/samples/Eureka/DownstreamService/Program.cs
similarity index 100%
rename from samples/OcelotEureka/DownstreamService/Program.cs
rename to samples/Eureka/DownstreamService/Program.cs
diff --git a/samples/OcelotEureka/DownstreamService/Properties/launchSettings.json b/samples/Eureka/DownstreamService/Properties/launchSettings.json
similarity index 100%
rename from samples/OcelotEureka/DownstreamService/Properties/launchSettings.json
rename to samples/Eureka/DownstreamService/Properties/launchSettings.json
diff --git a/samples/OcelotEureka/DownstreamService/Startup.cs b/samples/Eureka/DownstreamService/Startup.cs
similarity index 100%
rename from samples/OcelotEureka/DownstreamService/Startup.cs
rename to samples/Eureka/DownstreamService/Startup.cs
diff --git a/samples/OcelotEureka/DownstreamService/appsettings.Development.json b/samples/Eureka/DownstreamService/appsettings.Development.json
similarity index 100%
rename from samples/OcelotEureka/DownstreamService/appsettings.Development.json
rename to samples/Eureka/DownstreamService/appsettings.Development.json
diff --git a/samples/OcelotEureka/DownstreamService/appsettings.json b/samples/Eureka/DownstreamService/appsettings.json
similarity index 100%
rename from samples/OcelotEureka/DownstreamService/appsettings.json
rename to samples/Eureka/DownstreamService/appsettings.json
diff --git a/samples/OcelotEureka/OcelotEureka.sln b/samples/Eureka/OcelotEureka.sln
similarity index 100%
rename from samples/OcelotEureka/OcelotEureka.sln
rename to samples/Eureka/OcelotEureka.sln
diff --git a/samples/OcelotEureka/README.md b/samples/Eureka/README.md
similarity index 100%
rename from samples/OcelotEureka/README.md
rename to samples/Eureka/README.md
diff --git a/samples/OcelotGraphQL/OcelotGraphQL.csproj b/samples/GraphQL/Ocelot.Samples.GraphQL.csproj
similarity index 100%
rename from samples/OcelotGraphQL/OcelotGraphQL.csproj
rename to samples/GraphQL/Ocelot.Samples.GraphQL.csproj
diff --git a/samples/OcelotGraphQL/OcelotGraphQL.sln b/samples/GraphQL/OcelotGraphQL.sln
similarity index 100%
rename from samples/OcelotGraphQL/OcelotGraphQL.sln
rename to samples/GraphQL/OcelotGraphQL.sln
diff --git a/samples/OcelotGraphQL/Program.cs b/samples/GraphQL/Program.cs
similarity index 96%
rename from samples/OcelotGraphQL/Program.cs
rename to samples/GraphQL/Program.cs
index e519875f1f..e2f19aaaf1 100644
--- a/samples/OcelotGraphQL/Program.cs
+++ b/samples/GraphQL/Program.cs
@@ -1,134 +1,134 @@
-using GraphQL;
-using GraphQL.Types;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-using Ocelot.DependencyInjection;
-using Ocelot.Middleware;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Net;
-using System.Net.Http;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace OcelotGraphQL
-{
- public class Hero
- {
- public int Id { get; set; }
- public string Name { get; set; }
- }
-
- public class Query
- {
- private readonly List _heroes = new()
- {
- new Hero { Id = 1, Name = "R2-D2" },
- new Hero { Id = 2, Name = "Batman" },
- new Hero { Id = 3, Name = "Wonder Woman" },
- new Hero { Id = 4, Name = "Tom Pallister" }
- };
-
- [GraphQLMetadata("hero")]
- public Hero GetHero(int id)
- {
- return _heroes.FirstOrDefault(x => x.Id == id);
- }
- }
-
- public class GraphQlDelegatingHandler : DelegatingHandler
- {
- //private readonly ISchema _schema;
- private readonly IDocumentExecuter _executer;
- private readonly IDocumentWriter _writer;
-
- public GraphQlDelegatingHandler(IDocumentExecuter executer, IDocumentWriter writer)
- {
- _executer = executer;
- _writer = writer;
- }
-
- protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
- {
- //try get query from body, could check http method :)
- var query = await request.Content.ReadAsStringAsync(cancellationToken);
-
- //if not body try query string, dont hack like this in real world..
- if (query.Length == 0)
- {
- var decoded = WebUtility.UrlDecode(request.RequestUri.Query);
- query = decoded.Replace("?query=", string.Empty);
- }
-
- var result = await _executer.ExecuteAsync(_ =>
- {
- _.Query = query;
- });
-
- var responseBody = await _writer.WriteToStringAsync(result);
-
- //maybe check for errors and headers etc in real world?
- var response = new HttpResponseMessage(HttpStatusCode.OK)
- {
- Content = new StringContent(responseBody)
- };
-
- //ocelot will treat this like any other http request...
- return response;
- }
- }
-
- public class Program
- {
- public static void Main()
- {
- var schema = Schema.For(@"
- type Hero {
- id: Int
- name: String
- }
-
- type Query {
- hero(id: Int): Hero
- }
- ", _ =>
- {
- _.Types.Include();
- });
-
- 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", false, false)
- .AddEnvironmentVariables();
- })
- .ConfigureServices(s =>
- {
- s.AddSingleton(schema);
- s.AddOcelot()
- .AddDelegatingHandler();
- })
- .ConfigureLogging((hostingContext, logging) =>
- {
- logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
- logging.AddConsole();
- })
- .UseIISIntegration()
- .Configure(app =>
- {
- app.UseOcelot().Wait();
- })
- .Build()
- .Run();
- }
- }
-}
+using GraphQL;
+using GraphQL.Types;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Ocelot.DependencyInjection;
+using Ocelot.Middleware;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace OcelotGraphQL
+{
+ public class Hero
+ {
+ public int Id { get; set; }
+ public string Name { get; set; }
+ }
+
+ public class Query
+ {
+ private readonly List _heroes = new()
+ {
+ new Hero { Id = 1, Name = "R2-D2" },
+ new Hero { Id = 2, Name = "Batman" },
+ new Hero { Id = 3, Name = "Wonder Woman" },
+ new Hero { Id = 4, Name = "Tom Pallister" }
+ };
+
+ [GraphQLMetadata("hero")]
+ public Hero GetHero(int id)
+ {
+ return _heroes.FirstOrDefault(x => x.Id == id);
+ }
+ }
+
+ public class GraphQlDelegatingHandler : DelegatingHandler
+ {
+ //private readonly ISchema _schema;
+ private readonly IDocumentExecuter _executer;
+ private readonly IDocumentWriter _writer;
+
+ public GraphQlDelegatingHandler(IDocumentExecuter executer, IDocumentWriter writer)
+ {
+ _executer = executer;
+ _writer = writer;
+ }
+
+ protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ //try get query from body, could check http method :)
+ var query = await request.Content.ReadAsStringAsync(cancellationToken);
+
+ //if not body try query string, dont hack like this in real world..
+ if (query.Length == 0)
+ {
+ var decoded = WebUtility.UrlDecode(request.RequestUri.Query);
+ query = decoded.Replace("?query=", string.Empty);
+ }
+
+ var result = await _executer.ExecuteAsync(_ =>
+ {
+ _.Query = query;
+ });
+
+ var responseBody = await _writer.WriteToStringAsync(result);
+
+ //maybe check for errors and headers etc in real world?
+ var response = new HttpResponseMessage(HttpStatusCode.OK)
+ {
+ Content = new StringContent(responseBody)
+ };
+
+ //ocelot will treat this like any other http request...
+ return response;
+ }
+ }
+
+ public class Program
+ {
+ public static void Main()
+ {
+ var schema = Schema.For(@"
+ type Hero {
+ id: Int
+ name: String
+ }
+
+ type Query {
+ hero(id: Int): Hero
+ }
+ ", _ =>
+ {
+ _.Types.Include();
+ });
+
+ 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", false, false)
+ .AddEnvironmentVariables();
+ })
+ .ConfigureServices(s =>
+ {
+ s.AddSingleton(schema);
+ s.AddOcelot()
+ .AddDelegatingHandler();
+ })
+ .ConfigureLogging((hostingContext, logging) =>
+ {
+ logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
+ logging.AddConsole();
+ })
+ .UseIISIntegration()
+ .Configure(app =>
+ {
+ app.UseOcelot().Wait();
+ })
+ .Build()
+ .Run();
+ }
+ }
+}
diff --git a/samples/OcelotGraphQL/Properties/launchSettings.json b/samples/GraphQL/Properties/launchSettings.json
similarity index 100%
rename from samples/OcelotGraphQL/Properties/launchSettings.json
rename to samples/GraphQL/Properties/launchSettings.json
diff --git a/samples/OcelotGraphQL/README.md b/samples/GraphQL/README.md
similarity index 96%
rename from samples/OcelotGraphQL/README.md
rename to samples/GraphQL/README.md
index 7f16ed9850..1a470c344f 100644
--- a/samples/OcelotGraphQL/README.md
+++ b/samples/GraphQL/README.md
@@ -1,71 +1,71 @@
-# Ocelot using GraphQL example
-
-Loads of people keep asking me if Ocelot will every support GraphQL, in my mind Ocelot and GraphQL are two different things that can work together.
-I would not try and implement GraphQL in Ocelot instead I would either have Ocelot in front of GraphQL to handle things like authorization / authentication or I would
-bring in the awesome [graphql-dotnet](https://github.com/graphql-dotnet/graphql-dotnet) library and use it in a [DelegatingHandler](http://ocelot.readthedocs.io/en/latest/features/delegatinghandlers.html). This way you could have Ocelot and GraphQL without the extra hop to GraphQL. This same is an example of how to do that.
-
-## Example
-
-If you run this project with
-
-$ dotnet run
-
-Use postman or something to make the following requests and you can see Ocelot and GraphQL in action together...
-
-GET http://localhost:5000/graphql?query={ hero(id: 4) { id name } }
-
-RESPONSE
-```json
- {
- "data": {
- "hero": {
- "id": 4,
- "name": "Tom Pallister"
- }
- }
- }
-```
-
-POST http://localhost:5000/graphql
-
-BODY
-```json
- { hero(id: 4) { id name } }
-```
-
-RESPONSE
-```json
- {
- "data": {
- "hero": {
- "id": 4,
- "name": "Tom Pallister"
- }
- }
- }
-```
-
-## Notes
-
-Please note this project never goes out to another service, it just gets the data for GraphQL in memory. You would need to add the details of your GraphQL server in ocelot.json e.g.
-
-```json
-{
- "Routes": [
- {
- "DownstreamPathTemplate": "/graphql",
- "DownstreamScheme": "http",
- "DownstreamHostAndPorts": [
- {
- "Host": "yourgraphqlhost.com",
- "Port": 80
- }
- ],
- "UpstreamPathTemplate": "/graphql",
- "DelegatingHandlers": [
- "GraphQlDelegatingHandler"
- ]
- }
- ]
- }
+# Ocelot using GraphQL example
+
+Loads of people keep asking me if Ocelot will every support GraphQL, in my mind Ocelot and GraphQL are two different things that can work together.
+I would not try and implement GraphQL in Ocelot instead I would either have Ocelot in front of GraphQL to handle things like authorization / authentication or I would
+bring in the awesome [graphql-dotnet](https://github.com/graphql-dotnet/graphql-dotnet) library and use it in a [DelegatingHandler](http://ocelot.readthedocs.io/en/latest/features/delegatinghandlers.html). This way you could have Ocelot and GraphQL without the extra hop to GraphQL. This same is an example of how to do that.
+
+## Example
+
+If you run this project with
+
+$ dotnet run
+
+Use postman or something to make the following requests and you can see Ocelot and GraphQL in action together...
+
+GET http://localhost:5000/graphql?query={ hero(id: 4) { id name } }
+
+RESPONSE
+```json
+ {
+ "data": {
+ "hero": {
+ "id": 4,
+ "name": "Tom Pallister"
+ }
+ }
+ }
+```
+
+POST http://localhost:5000/graphql
+
+BODY
+```json
+ { hero(id: 4) { id name } }
+```
+
+RESPONSE
+```json
+ {
+ "data": {
+ "hero": {
+ "id": 4,
+ "name": "Tom Pallister"
+ }
+ }
+ }
+```
+
+## Notes
+
+Please note this project never goes out to another service, it just gets the data for GraphQL in memory. You would need to add the details of your GraphQL server in ocelot.json e.g.
+
+```json
+{
+ "Routes": [
+ {
+ "DownstreamPathTemplate": "/graphql",
+ "DownstreamScheme": "http",
+ "DownstreamHostAndPorts": [
+ {
+ "Host": "yourgraphqlhost.com",
+ "Port": 80
+ }
+ ],
+ "UpstreamPathTemplate": "/graphql",
+ "DelegatingHandlers": [
+ "GraphQlDelegatingHandler"
+ ]
+ }
+ ]
+ }
```
\ No newline at end of file
diff --git a/samples/OcelotGraphQL/ocelot.json b/samples/GraphQL/ocelot.json
similarity index 96%
rename from samples/OcelotGraphQL/ocelot.json
rename to samples/GraphQL/ocelot.json
index c716bf2587..3529e0ba83 100644
--- a/samples/OcelotGraphQL/ocelot.json
+++ b/samples/GraphQL/ocelot.json
@@ -1,19 +1,19 @@
-{
- "Routes": [
- {
- "DownstreamPathTemplate": "/",
- "DownstreamScheme": "http",
- "DownstreamHostAndPorts": [
- {
- "Host": "jsonplaceholder.typicode.com",
- "Port": 80
- }
- ],
- "UpstreamPathTemplate": "/graphql",
- "DelegatingHandlers": [
- "GraphQlDelegatingHandler"
- ]
- }
- ]
- }
+{
+ "Routes": [
+ {
+ "DownstreamPathTemplate": "/",
+ "DownstreamScheme": "http",
+ "DownstreamHostAndPorts": [
+ {
+ "Host": "jsonplaceholder.typicode.com",
+ "Port": 80
+ }
+ ],
+ "UpstreamPathTemplate": "/graphql",
+ "DelegatingHandlers": [
+ "GraphQlDelegatingHandler"
+ ]
+ }
+ ]
+ }
\ No newline at end of file
diff --git a/samples/OcelotKube/.dockerignore b/samples/Kubernetes/.dockerignore
similarity index 100%
rename from samples/OcelotKube/.dockerignore
rename to samples/Kubernetes/.dockerignore
diff --git a/samples/OcelotKube/ApiGateway/Dockerfile b/samples/Kubernetes/ApiGateway/Dockerfile
similarity index 100%
rename from samples/OcelotKube/ApiGateway/Dockerfile
rename to samples/Kubernetes/ApiGateway/Dockerfile
diff --git a/samples/OcelotKube/ApiGateway/Ocelot.Samples.OcelotKube.ApiGateway.csproj b/samples/Kubernetes/ApiGateway/Ocelot.Samples.Kubernetes.ApiGateway.csproj
similarity index 80%
rename from samples/OcelotKube/ApiGateway/Ocelot.Samples.OcelotKube.ApiGateway.csproj
rename to samples/Kubernetes/ApiGateway/Ocelot.Samples.Kubernetes.ApiGateway.csproj
index f88cd602b1..e62d4e98b9 100644
--- a/samples/OcelotKube/ApiGateway/Ocelot.Samples.OcelotKube.ApiGateway.csproj
+++ b/samples/Kubernetes/ApiGateway/Ocelot.Samples.Kubernetes.ApiGateway.csproj
@@ -6,9 +6,6 @@
InProcess
Linux
-
-
-
diff --git a/samples/OcelotKube/ApiGateway/Program.cs b/samples/Kubernetes/ApiGateway/Program.cs
similarity index 100%
rename from samples/OcelotKube/ApiGateway/Program.cs
rename to samples/Kubernetes/ApiGateway/Program.cs
diff --git a/samples/OcelotKube/ApiGateway/Properties/launchSettings.json b/samples/Kubernetes/ApiGateway/Properties/launchSettings.json
similarity index 100%
rename from samples/OcelotKube/ApiGateway/Properties/launchSettings.json
rename to samples/Kubernetes/ApiGateway/Properties/launchSettings.json
diff --git a/samples/OcelotKube/ApiGateway/Startup.cs b/samples/Kubernetes/ApiGateway/Startup.cs
similarity index 100%
rename from samples/OcelotKube/ApiGateway/Startup.cs
rename to samples/Kubernetes/ApiGateway/Startup.cs
diff --git a/samples/OcelotKube/ApiGateway/appsettings.Development.json b/samples/Kubernetes/ApiGateway/appsettings.Development.json
similarity index 100%
rename from samples/OcelotKube/ApiGateway/appsettings.Development.json
rename to samples/Kubernetes/ApiGateway/appsettings.Development.json
diff --git a/samples/OcelotKube/DownstreamService/appsettings.json b/samples/Kubernetes/ApiGateway/appsettings.json
similarity index 100%
rename from samples/OcelotKube/DownstreamService/appsettings.json
rename to samples/Kubernetes/ApiGateway/appsettings.json
diff --git a/samples/OcelotKube/ApiGateway/ocelot.json b/samples/Kubernetes/ApiGateway/ocelot.json
similarity index 95%
rename from samples/OcelotKube/ApiGateway/ocelot.json
rename to samples/Kubernetes/ApiGateway/ocelot.json
index 6a28b9eec4..f4a5af0b86 100644
--- a/samples/OcelotKube/ApiGateway/ocelot.json
+++ b/samples/Kubernetes/ApiGateway/ocelot.json
@@ -1,20 +1,20 @@
-{
- "Routes": [
- {
- "DownstreamPathTemplate": "/api/values",
- "DownstreamScheme": "http",
- "UpstreamPathTemplate": "/values",
- "ServiceName": "downstreamservice",
- "UpstreamHttpMethod": [ "Get" ]
- }
- ],
- "GlobalConfiguration": {
- "ServiceDiscoveryProvider": {
- "Host": "192.168.0.13",
- "Port": 443,
- "Token": "txpc696iUhbVoudg164r93CxDTrKRVWG",
- "Namespace": "dev",
- "Type": "kube"
- }
- }
-}
+{
+ "Routes": [
+ {
+ "DownstreamPathTemplate": "/api/values",
+ "DownstreamScheme": "http",
+ "UpstreamPathTemplate": "/values",
+ "ServiceName": "downstreamservice",
+ "UpstreamHttpMethod": [ "Get" ]
+ }
+ ],
+ "GlobalConfiguration": {
+ "ServiceDiscoveryProvider": {
+ "Host": "192.168.0.13",
+ "Port": 443,
+ "Token": "txpc696iUhbVoudg164r93CxDTrKRVWG",
+ "Namespace": "dev",
+ "Type": "kube"
+ }
+ }
+}
diff --git a/samples/OcelotKube/Dockerfile b/samples/Kubernetes/Dockerfile
similarity index 100%
rename from samples/OcelotKube/Dockerfile
rename to samples/Kubernetes/Dockerfile
diff --git a/samples/OcelotKube/DownstreamService/Controllers/ValuesController.cs b/samples/Kubernetes/DownstreamService/Controllers/ValuesController.cs
similarity index 100%
rename from samples/OcelotKube/DownstreamService/Controllers/ValuesController.cs
rename to samples/Kubernetes/DownstreamService/Controllers/ValuesController.cs
diff --git a/samples/OcelotKube/DownstreamService/Controllers/WeatherForecastController.cs b/samples/Kubernetes/DownstreamService/Controllers/WeatherForecastController.cs
similarity index 100%
rename from samples/OcelotKube/DownstreamService/Controllers/WeatherForecastController.cs
rename to samples/Kubernetes/DownstreamService/Controllers/WeatherForecastController.cs
diff --git a/samples/OcelotKube/DownstreamService/Dockerfile b/samples/Kubernetes/DownstreamService/Dockerfile
similarity index 100%
rename from samples/OcelotKube/DownstreamService/Dockerfile
rename to samples/Kubernetes/DownstreamService/Dockerfile
diff --git a/samples/OcelotKube/DownstreamService/Models/WeatherForecast.cs b/samples/Kubernetes/DownstreamService/Models/WeatherForecast.cs
similarity index 100%
rename from samples/OcelotKube/DownstreamService/Models/WeatherForecast.cs
rename to samples/Kubernetes/DownstreamService/Models/WeatherForecast.cs
diff --git a/samples/OcelotKube/DownstreamService/Ocelot.Samples.OcelotKube.DownstreamService.csproj b/samples/Kubernetes/DownstreamService/Ocelot.Samples.Kubernetes.DownstreamService.csproj
similarity index 86%
rename from samples/OcelotKube/DownstreamService/Ocelot.Samples.OcelotKube.DownstreamService.csproj
rename to samples/Kubernetes/DownstreamService/Ocelot.Samples.Kubernetes.DownstreamService.csproj
index 3c1cfefb22..72b36b2fcb 100644
--- a/samples/OcelotKube/DownstreamService/Ocelot.Samples.OcelotKube.DownstreamService.csproj
+++ b/samples/Kubernetes/DownstreamService/Ocelot.Samples.Kubernetes.DownstreamService.csproj
@@ -1,6 +1,6 @@
- net7.0
+ net6.0;net7.0;net8.0
disable
disable
InProcess
diff --git a/samples/OcelotKube/DownstreamService/Program.cs b/samples/Kubernetes/DownstreamService/Program.cs
similarity index 100%
rename from samples/OcelotKube/DownstreamService/Program.cs
rename to samples/Kubernetes/DownstreamService/Program.cs
diff --git a/samples/OcelotKube/DownstreamService/Properties/launchSettings.json b/samples/Kubernetes/DownstreamService/Properties/launchSettings.json
similarity index 100%
rename from samples/OcelotKube/DownstreamService/Properties/launchSettings.json
rename to samples/Kubernetes/DownstreamService/Properties/launchSettings.json
diff --git a/samples/OcelotKube/DownstreamService/appsettings.Development.json b/samples/Kubernetes/DownstreamService/appsettings.Development.json
similarity index 100%
rename from samples/OcelotKube/DownstreamService/appsettings.Development.json
rename to samples/Kubernetes/DownstreamService/appsettings.Development.json
diff --git a/samples/OcelotBasic/appsettings.json b/samples/Kubernetes/DownstreamService/appsettings.json
similarity index 92%
rename from samples/OcelotBasic/appsettings.json
rename to samples/Kubernetes/DownstreamService/appsettings.json
index 7376aada1c..def9159a7d 100644
--- a/samples/OcelotBasic/appsettings.json
+++ b/samples/Kubernetes/DownstreamService/appsettings.json
@@ -1,8 +1,8 @@
-{
- "Logging": {
- "LogLevel": {
- "Default": "Warning"
- }
- },
- "AllowedHosts": "*"
-}
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/samples/OcelotKube/OcelotKube.sln b/samples/Kubernetes/OcelotKube.sln
similarity index 100%
rename from samples/OcelotKube/OcelotKube.sln
rename to samples/Kubernetes/OcelotKube.sln
diff --git a/samples/Ocelot.Samples.sln b/samples/Ocelot.Samples.sln
new file mode 100644
index 0000000000..ca208f4a69
--- /dev/null
+++ b/samples/Ocelot.Samples.sln
@@ -0,0 +1,91 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.9.34728.123
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Samples.AdministrationApi", "Administration\Ocelot.Samples.AdministrationApi.csproj", "{238467FE-19EE-4102-9AF7-51EB2C6F0354}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Samples.Basic.ApiGateway", "Basic\Ocelot.Samples.Basic.ApiGateway.csproj", "{A7D2C43A-E35C-4A89-AEE5-5C87052ECD89}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Samples.Eureka.ApiGateway", "Eureka\ApiGateway\Ocelot.Samples.Eureka.ApiGateway.csproj", "{EA0E146F-2C2B-4176-B6EC-F62A587F5077}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Samples.Eureka.DownstreamService", "Eureka\DownstreamService\Ocelot.Samples.Eureka.DownstreamService.csproj", "{B7317B64-2208-472D-90AC-F42B61956B79}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Samples.GraphQL", "GraphQL\Ocelot.Samples.GraphQL.csproj", "{6CCA3677-420A-4294-8D41-67CF3D818575}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Samples.Kubernetes.ApiGateway", "Kubernetes\ApiGateway\Ocelot.Samples.Kubernetes.ApiGateway.csproj", "{721C1737-70CB-4B11-A19B-C7AAC6856CC7}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Samples.Kubernetes.DownstreamService", "Kubernetes\DownstreamService\Ocelot.Samples.Kubernetes.DownstreamService.csproj", "{CE949A5D-9D25-46E3-B59A-DA63F7ED9A59}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Samples.OpenTracing", "OpenTracing\Ocelot.Samples.OpenTracing.csproj", "{707BD584-3CC0-4087-820C-049C3D68F6A3}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Samples.ServiceDiscovery.ApiGateway", "ServiceDiscovery\ApiGateway\Ocelot.Samples.ServiceDiscovery.ApiGateway.csproj", "{96B9F16E-C95D-425A-A419-40CB3C90CB77}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Samples.ServiceDiscovery.DownstreamService", "ServiceDiscovery\DownstreamService\Ocelot.Samples.ServiceDiscovery.DownstreamService.csproj", "{60E14B1A-C295-453B-910E-58E09F5A28AA}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Samples.ServiceFabric.ApiGateway", "ServiceFabric\ApiGateway\Ocelot.Samples.ServiceFabric.ApiGateway.csproj", "{115F7934-3326-492A-B131-64F0EAEBAD71}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Samples.ServiceFabric.DownstreamService", "ServiceFabric\DownstreamService\Ocelot.Samples.ServiceFabric.DownstreamService.csproj", "{6C777A20-F557-45CF-B87B-11E3C6B29A36}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {238467FE-19EE-4102-9AF7-51EB2C6F0354}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {238467FE-19EE-4102-9AF7-51EB2C6F0354}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {238467FE-19EE-4102-9AF7-51EB2C6F0354}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {238467FE-19EE-4102-9AF7-51EB2C6F0354}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A7D2C43A-E35C-4A89-AEE5-5C87052ECD89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A7D2C43A-E35C-4A89-AEE5-5C87052ECD89}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A7D2C43A-E35C-4A89-AEE5-5C87052ECD89}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A7D2C43A-E35C-4A89-AEE5-5C87052ECD89}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EA0E146F-2C2B-4176-B6EC-F62A587F5077}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EA0E146F-2C2B-4176-B6EC-F62A587F5077}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EA0E146F-2C2B-4176-B6EC-F62A587F5077}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EA0E146F-2C2B-4176-B6EC-F62A587F5077}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B7317B64-2208-472D-90AC-F42B61956B79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B7317B64-2208-472D-90AC-F42B61956B79}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B7317B64-2208-472D-90AC-F42B61956B79}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B7317B64-2208-472D-90AC-F42B61956B79}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6CCA3677-420A-4294-8D41-67CF3D818575}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6CCA3677-420A-4294-8D41-67CF3D818575}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6CCA3677-420A-4294-8D41-67CF3D818575}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6CCA3677-420A-4294-8D41-67CF3D818575}.Release|Any CPU.Build.0 = Release|Any CPU
+ {721C1737-70CB-4B11-A19B-C7AAC6856CC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {721C1737-70CB-4B11-A19B-C7AAC6856CC7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {721C1737-70CB-4B11-A19B-C7AAC6856CC7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {721C1737-70CB-4B11-A19B-C7AAC6856CC7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CE949A5D-9D25-46E3-B59A-DA63F7ED9A59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CE949A5D-9D25-46E3-B59A-DA63F7ED9A59}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CE949A5D-9D25-46E3-B59A-DA63F7ED9A59}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CE949A5D-9D25-46E3-B59A-DA63F7ED9A59}.Release|Any CPU.Build.0 = Release|Any CPU
+ {707BD584-3CC0-4087-820C-049C3D68F6A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {707BD584-3CC0-4087-820C-049C3D68F6A3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {707BD584-3CC0-4087-820C-049C3D68F6A3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {707BD584-3CC0-4087-820C-049C3D68F6A3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {96B9F16E-C95D-425A-A419-40CB3C90CB77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {96B9F16E-C95D-425A-A419-40CB3C90CB77}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {96B9F16E-C95D-425A-A419-40CB3C90CB77}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {96B9F16E-C95D-425A-A419-40CB3C90CB77}.Release|Any CPU.Build.0 = Release|Any CPU
+ {60E14B1A-C295-453B-910E-58E09F5A28AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {60E14B1A-C295-453B-910E-58E09F5A28AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {60E14B1A-C295-453B-910E-58E09F5A28AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {60E14B1A-C295-453B-910E-58E09F5A28AA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {115F7934-3326-492A-B131-64F0EAEBAD71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {115F7934-3326-492A-B131-64F0EAEBAD71}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {115F7934-3326-492A-B131-64F0EAEBAD71}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {115F7934-3326-492A-B131-64F0EAEBAD71}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6C777A20-F557-45CF-B87B-11E3C6B29A36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6C777A20-F557-45CF-B87B-11E3C6B29A36}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6C777A20-F557-45CF-B87B-11E3C6B29A36}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6C777A20-F557-45CF-B87B-11E3C6B29A36}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {2C1620D4-EB38-4C3E-9FC5-029FB6B2F426}
+ EndGlobalSection
+EndGlobal
diff --git a/samples/OcelotServiceDiscovery/DownstreamService/.dockerignore b/samples/OcelotServiceDiscovery/DownstreamService/.dockerignore
deleted file mode 100644
index e7b690f114..0000000000
--- a/samples/OcelotServiceDiscovery/DownstreamService/.dockerignore
+++ /dev/null
@@ -1,25 +0,0 @@
-**/.classpath
-**/.dockerignore
-**/.env
-**/.git
-**/.gitignore
-**/.project
-**/.settings
-**/.toolstarget
-**/.vs
-**/.vscode
-**/*.*proj.user
-**/*.dbmdl
-**/*.jfm
-**/azds.yaml
-**/bin
-**/charts
-**/docker-compose*
-**/Dockerfile*
-**/node_modules
-**/npm-debug.log
-**/obj
-**/secrets.dev.yaml
-**/values.dev.yaml
-LICENSE
-README.md
diff --git a/samples/OcelotServiceDiscovery/DownstreamService/Dockerfile b/samples/OcelotServiceDiscovery/DownstreamService/Dockerfile
deleted file mode 100644
index b7535cfcdd..0000000000
--- a/samples/OcelotServiceDiscovery/DownstreamService/Dockerfile
+++ /dev/null
@@ -1,22 +0,0 @@
-#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
-
-FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
-WORKDIR /app
-EXPOSE 80
-EXPOSE 443
-
-FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
-WORKDIR /src
-COPY ["Ocelot.Samples.ServiceDiscovery.DownstreamService.csproj", "."]
-RUN dotnet restore "./Ocelot.Samples.ServiceDiscovery.DownstreamService.csproj"
-COPY . .
-WORKDIR "/src/."
-RUN dotnet build "Ocelot.Samples.ServiceDiscovery.DownstreamService.csproj" -c Release -o /app/build
-
-FROM build AS publish
-RUN dotnet publish "Ocelot.Samples.ServiceDiscovery.DownstreamService.csproj" -c Release -o /app/publish /p:UseAppHost=false
-
-FROM base AS final
-WORKDIR /app
-COPY --from=publish /app/publish .
-ENTRYPOINT ["dotnet", "Ocelot.Samples.ServiceDiscovery.DownstreamService.dll"]
diff --git a/samples/OcelotServiceDiscovery/DownstreamService/Ocelot.Samples.ServiceDiscovery.DownstreamService.csproj b/samples/OcelotServiceDiscovery/DownstreamService/Ocelot.Samples.ServiceDiscovery.DownstreamService.csproj
deleted file mode 100644
index b7c731bead..0000000000
--- a/samples/OcelotServiceDiscovery/DownstreamService/Ocelot.Samples.ServiceDiscovery.DownstreamService.csproj
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
- net7.0
- disable
- enable
- Linux
- .
- d5492aa8-b50c-41ae-a044-9954846db9ac
-
-
-
-
-
-
-
-
-
diff --git a/samples/OcelotOpenTracing/OcelotOpenTracing.csproj b/samples/OpenTracing/Ocelot.Samples.OpenTracing.csproj
similarity index 100%
rename from samples/OcelotOpenTracing/OcelotOpenTracing.csproj
rename to samples/OpenTracing/Ocelot.Samples.OpenTracing.csproj
diff --git a/samples/OcelotOpenTracing/Program.cs b/samples/OpenTracing/Program.cs
similarity index 100%
rename from samples/OcelotOpenTracing/Program.cs
rename to samples/OpenTracing/Program.cs
diff --git a/samples/OcelotOpenTracing/appsettings.Development.json b/samples/OpenTracing/appsettings.Development.json
similarity index 100%
rename from samples/OcelotOpenTracing/appsettings.Development.json
rename to samples/OpenTracing/appsettings.Development.json
diff --git a/samples/OcelotOpenTracing/appsettings.json b/samples/OpenTracing/appsettings.json
similarity index 100%
rename from samples/OcelotOpenTracing/appsettings.json
rename to samples/OpenTracing/appsettings.json
diff --git a/samples/OcelotOpenTracing/ocelot.json b/samples/OpenTracing/ocelot.json
similarity index 100%
rename from samples/OcelotOpenTracing/ocelot.json
rename to samples/OpenTracing/ocelot.json
diff --git a/samples/OcelotServiceDiscovery/.dockerignore b/samples/ServiceDiscovery/.dockerignore
similarity index 100%
rename from samples/OcelotServiceDiscovery/.dockerignore
rename to samples/ServiceDiscovery/.dockerignore
diff --git a/samples/OcelotServiceDiscovery/ApiGateway/Ocelot.Samples.ServiceDiscovery.ApiGateway.csproj b/samples/ServiceDiscovery/ApiGateway/Ocelot.Samples.ServiceDiscovery.ApiGateway.csproj
similarity index 59%
rename from samples/OcelotServiceDiscovery/ApiGateway/Ocelot.Samples.ServiceDiscovery.ApiGateway.csproj
rename to samples/ServiceDiscovery/ApiGateway/Ocelot.Samples.ServiceDiscovery.ApiGateway.csproj
index aac1dd20ab..815c793e9f 100644
--- a/samples/OcelotServiceDiscovery/ApiGateway/Ocelot.Samples.ServiceDiscovery.ApiGateway.csproj
+++ b/samples/ServiceDiscovery/ApiGateway/Ocelot.Samples.ServiceDiscovery.ApiGateway.csproj
@@ -1,6 +1,8 @@
- net6.0;net7.0;net8.0
+ net8.0
+ disable
+ disable
diff --git a/samples/OcelotServiceDiscovery/ApiGateway/Program.cs b/samples/ServiceDiscovery/ApiGateway/Program.cs
similarity index 100%
rename from samples/OcelotServiceDiscovery/ApiGateway/Program.cs
rename to samples/ServiceDiscovery/ApiGateway/Program.cs
diff --git a/samples/OcelotServiceDiscovery/ApiGateway/Properties/launchSettings.json b/samples/ServiceDiscovery/ApiGateway/Properties/launchSettings.json
similarity index 100%
rename from samples/OcelotServiceDiscovery/ApiGateway/Properties/launchSettings.json
rename to samples/ServiceDiscovery/ApiGateway/Properties/launchSettings.json
diff --git a/samples/OcelotServiceDiscovery/ApiGateway/ServiceDiscovery/MyServiceDiscoveryProvider.cs b/samples/ServiceDiscovery/ApiGateway/ServiceDiscovery/MyServiceDiscoveryProvider.cs
similarity index 100%
rename from samples/OcelotServiceDiscovery/ApiGateway/ServiceDiscovery/MyServiceDiscoveryProvider.cs
rename to samples/ServiceDiscovery/ApiGateway/ServiceDiscovery/MyServiceDiscoveryProvider.cs
diff --git a/samples/OcelotServiceDiscovery/ApiGateway/ServiceDiscovery/MyServiceDiscoveryProviderFactory.cs b/samples/ServiceDiscovery/ApiGateway/ServiceDiscovery/MyServiceDiscoveryProviderFactory.cs
similarity index 100%
rename from samples/OcelotServiceDiscovery/ApiGateway/ServiceDiscovery/MyServiceDiscoveryProviderFactory.cs
rename to samples/ServiceDiscovery/ApiGateway/ServiceDiscovery/MyServiceDiscoveryProviderFactory.cs
diff --git a/samples/OcelotServiceDiscovery/ApiGateway/appsettings.json b/samples/ServiceDiscovery/ApiGateway/appsettings.json
similarity index 100%
rename from samples/OcelotServiceDiscovery/ApiGateway/appsettings.json
rename to samples/ServiceDiscovery/ApiGateway/appsettings.json
diff --git a/samples/OcelotServiceDiscovery/ApiGateway/ocelot.json b/samples/ServiceDiscovery/ApiGateway/ocelot.json
similarity index 100%
rename from samples/OcelotServiceDiscovery/ApiGateway/ocelot.json
rename to samples/ServiceDiscovery/ApiGateway/ocelot.json
diff --git a/samples/OcelotServiceDiscovery/DownstreamService/Controllers/CategoriesController.cs b/samples/ServiceDiscovery/DownstreamService/Controllers/CategoriesController.cs
similarity index 100%
rename from samples/OcelotServiceDiscovery/DownstreamService/Controllers/CategoriesController.cs
rename to samples/ServiceDiscovery/DownstreamService/Controllers/CategoriesController.cs
diff --git a/samples/OcelotServiceDiscovery/DownstreamService/Controllers/HealthController.cs b/samples/ServiceDiscovery/DownstreamService/Controllers/HealthController.cs
similarity index 100%
rename from samples/OcelotServiceDiscovery/DownstreamService/Controllers/HealthController.cs
rename to samples/ServiceDiscovery/DownstreamService/Controllers/HealthController.cs
diff --git a/samples/OcelotServiceDiscovery/DownstreamService/Controllers/WeatherForecastController.cs b/samples/ServiceDiscovery/DownstreamService/Controllers/WeatherForecastController.cs
similarity index 100%
rename from samples/OcelotServiceDiscovery/DownstreamService/Controllers/WeatherForecastController.cs
rename to samples/ServiceDiscovery/DownstreamService/Controllers/WeatherForecastController.cs
diff --git a/samples/OcelotServiceDiscovery/DownstreamService/Models/HealthResult.cs b/samples/ServiceDiscovery/DownstreamService/Models/HealthResult.cs
similarity index 100%
rename from samples/OcelotServiceDiscovery/DownstreamService/Models/HealthResult.cs
rename to samples/ServiceDiscovery/DownstreamService/Models/HealthResult.cs
diff --git a/samples/OcelotServiceDiscovery/DownstreamService/Models/MicroserviceResult.cs b/samples/ServiceDiscovery/DownstreamService/Models/MicroserviceResult.cs
similarity index 100%
rename from samples/OcelotServiceDiscovery/DownstreamService/Models/MicroserviceResult.cs
rename to samples/ServiceDiscovery/DownstreamService/Models/MicroserviceResult.cs
diff --git a/samples/OcelotServiceDiscovery/DownstreamService/Models/ReadyResult.cs b/samples/ServiceDiscovery/DownstreamService/Models/ReadyResult.cs
similarity index 100%
rename from samples/OcelotServiceDiscovery/DownstreamService/Models/ReadyResult.cs
rename to samples/ServiceDiscovery/DownstreamService/Models/ReadyResult.cs
diff --git a/samples/OcelotServiceDiscovery/DownstreamService/Models/WeatherForecast.cs b/samples/ServiceDiscovery/DownstreamService/Models/WeatherForecast.cs
similarity index 100%
rename from samples/OcelotServiceDiscovery/DownstreamService/Models/WeatherForecast.cs
rename to samples/ServiceDiscovery/DownstreamService/Models/WeatherForecast.cs
diff --git a/samples/ServiceDiscovery/DownstreamService/Ocelot.Samples.ServiceDiscovery.DownstreamService.csproj b/samples/ServiceDiscovery/DownstreamService/Ocelot.Samples.ServiceDiscovery.DownstreamService.csproj
new file mode 100644
index 0000000000..163a956bb0
--- /dev/null
+++ b/samples/ServiceDiscovery/DownstreamService/Ocelot.Samples.ServiceDiscovery.DownstreamService.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net8.0
+ disable
+ enable
+
+
+
+
+
+
+
diff --git a/samples/OcelotServiceDiscovery/DownstreamService/Program.cs b/samples/ServiceDiscovery/DownstreamService/Program.cs
similarity index 100%
rename from samples/OcelotServiceDiscovery/DownstreamService/Program.cs
rename to samples/ServiceDiscovery/DownstreamService/Program.cs
diff --git a/samples/OcelotServiceDiscovery/DownstreamService/Properties/launchSettings.json b/samples/ServiceDiscovery/DownstreamService/Properties/launchSettings.json
similarity index 100%
rename from samples/OcelotServiceDiscovery/DownstreamService/Properties/launchSettings.json
rename to samples/ServiceDiscovery/DownstreamService/Properties/launchSettings.json
diff --git a/samples/OcelotServiceDiscovery/DownstreamService/Startup.cs b/samples/ServiceDiscovery/DownstreamService/Startup.cs
similarity index 100%
rename from samples/OcelotServiceDiscovery/DownstreamService/Startup.cs
rename to samples/ServiceDiscovery/DownstreamService/Startup.cs
diff --git a/samples/OcelotServiceDiscovery/DownstreamService/appsettings.Development.json b/samples/ServiceDiscovery/DownstreamService/appsettings.Development.json
similarity index 100%
rename from samples/OcelotServiceDiscovery/DownstreamService/appsettings.Development.json
rename to samples/ServiceDiscovery/DownstreamService/appsettings.Development.json
diff --git a/samples/OcelotServiceDiscovery/DownstreamService/appsettings.json b/samples/ServiceDiscovery/DownstreamService/appsettings.json
similarity index 100%
rename from samples/OcelotServiceDiscovery/DownstreamService/appsettings.json
rename to samples/ServiceDiscovery/DownstreamService/appsettings.json
diff --git a/samples/OcelotServiceDiscovery/Ocelot.Samples.ServiceDiscovery.sln b/samples/ServiceDiscovery/Ocelot.Samples.ServiceDiscovery.sln
similarity index 100%
rename from samples/OcelotServiceDiscovery/Ocelot.Samples.ServiceDiscovery.sln
rename to samples/ServiceDiscovery/Ocelot.Samples.ServiceDiscovery.sln
diff --git a/samples/OcelotServiceDiscovery/README.md b/samples/ServiceDiscovery/README.md
similarity index 100%
rename from samples/OcelotServiceDiscovery/README.md
rename to samples/ServiceDiscovery/README.md
diff --git a/samples/OcelotServiceFabric/.gitignore b/samples/ServiceFabric/.gitignore
similarity index 100%
rename from samples/OcelotServiceFabric/.gitignore
rename to samples/ServiceFabric/.gitignore
diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/OcelotApplicationApiGateway.csproj b/samples/ServiceFabric/ApiGateway/Ocelot.Samples.ServiceFabric.ApiGateway.csproj
similarity index 92%
rename from samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/OcelotApplicationApiGateway.csproj
rename to samples/ServiceFabric/ApiGateway/Ocelot.Samples.ServiceFabric.ApiGateway.csproj
index c97238c53d..c9f886f75b 100644
--- a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/OcelotApplicationApiGateway.csproj
+++ b/samples/ServiceFabric/ApiGateway/Ocelot.Samples.ServiceFabric.ApiGateway.csproj
@@ -18,6 +18,6 @@
-
+
diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/OcelotApplicationApiGateway.cs b/samples/ServiceFabric/ApiGateway/OcelotApplicationApiGateway.cs
similarity index 100%
rename from samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/OcelotApplicationApiGateway.cs
rename to samples/ServiceFabric/ApiGateway/OcelotApplicationApiGateway.cs
diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/Program.cs b/samples/ServiceFabric/ApiGateway/Program.cs
similarity index 100%
rename from samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/Program.cs
rename to samples/ServiceFabric/ApiGateway/Program.cs
diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/Properties/launchSettings.json b/samples/ServiceFabric/ApiGateway/Properties/launchSettings.json
similarity index 100%
rename from samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/Properties/launchSettings.json
rename to samples/ServiceFabric/ApiGateway/Properties/launchSettings.json
diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/ServiceEventListener.cs b/samples/ServiceFabric/ApiGateway/ServiceEventListener.cs
similarity index 100%
rename from samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/ServiceEventListener.cs
rename to samples/ServiceFabric/ApiGateway/ServiceEventListener.cs
diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/ServiceEventSource.cs b/samples/ServiceFabric/ApiGateway/ServiceEventSource.cs
similarity index 100%
rename from samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/ServiceEventSource.cs
rename to samples/ServiceFabric/ApiGateway/ServiceEventSource.cs
diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/WebCommunicationListener.cs b/samples/ServiceFabric/ApiGateway/WebCommunicationListener.cs
similarity index 100%
rename from samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/WebCommunicationListener.cs
rename to samples/ServiceFabric/ApiGateway/WebCommunicationListener.cs
diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/appsettings.json b/samples/ServiceFabric/ApiGateway/appsettings.json
similarity index 100%
rename from samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/appsettings.json
rename to samples/ServiceFabric/ApiGateway/appsettings.json
diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/ocelot.json b/samples/ServiceFabric/ApiGateway/ocelot.json
similarity index 95%
rename from samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/ocelot.json
rename to samples/ServiceFabric/ApiGateway/ocelot.json
index b541e95c43..1b174cd62e 100644
--- a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/ocelot.json
+++ b/samples/ServiceFabric/ApiGateway/ocelot.json
@@ -1,21 +1,21 @@
-{
- "Routes": [
- {
- "DownstreamPathTemplate": "/api/values",
- "UpstreamPathTemplate": "/EquipmentInterfaces",
- "UpstreamHttpMethod": [
- "Get"
- ],
- "DownstreamScheme": "http",
- "ServiceName": "OcelotServiceApplication/OcelotApplicationService"
- }
- ],
- "GlobalConfiguration": {
- "RequestIdKey": "OcRequestId",
- "ServiceDiscoveryProvider": {
- "Host": "localhost",
- "Port": 19081,
- "Type": "ServiceFabric"
- }
- }
-}
+{
+ "Routes": [
+ {
+ "DownstreamPathTemplate": "/api/values",
+ "UpstreamPathTemplate": "/EquipmentInterfaces",
+ "UpstreamHttpMethod": [
+ "Get"
+ ],
+ "DownstreamScheme": "http",
+ "ServiceName": "OcelotServiceApplication/OcelotApplicationService"
+ }
+ ],
+ "GlobalConfiguration": {
+ "RequestIdKey": "OcRequestId",
+ "ServiceDiscoveryProvider": {
+ "Host": "localhost",
+ "Port": 19081,
+ "Type": "ServiceFabric"
+ }
+ }
+}
diff --git a/samples/OcelotServiceFabric/CONTRIBUTING.md b/samples/ServiceFabric/CONTRIBUTING.md
similarity index 100%
rename from samples/OcelotServiceFabric/CONTRIBUTING.md
rename to samples/ServiceFabric/CONTRIBUTING.md
diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationService/ApiGateway.cs b/samples/ServiceFabric/DownstreamService/ApiGateway.cs
similarity index 100%
rename from samples/OcelotServiceFabric/src/OcelotApplicationService/ApiGateway.cs
rename to samples/ServiceFabric/DownstreamService/ApiGateway.cs
diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationService/Controllers/ValuesController.cs b/samples/ServiceFabric/DownstreamService/Controllers/ValuesController.cs
similarity index 100%
rename from samples/OcelotServiceFabric/src/OcelotApplicationService/Controllers/ValuesController.cs
rename to samples/ServiceFabric/DownstreamService/Controllers/ValuesController.cs
diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationService/OcelotApplicationService.csproj b/samples/ServiceFabric/DownstreamService/Ocelot.Samples.ServiceFabric.DownstreamService.csproj
similarity index 100%
rename from samples/OcelotServiceFabric/src/OcelotApplicationService/OcelotApplicationService.csproj
rename to samples/ServiceFabric/DownstreamService/Ocelot.Samples.ServiceFabric.DownstreamService.csproj
diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationService/Program.cs b/samples/ServiceFabric/DownstreamService/Program.cs
similarity index 100%
rename from samples/OcelotServiceFabric/src/OcelotApplicationService/Program.cs
rename to samples/ServiceFabric/DownstreamService/Program.cs
diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationService/Properties/launchSettings.json b/samples/ServiceFabric/DownstreamService/Properties/launchSettings.json
similarity index 100%
rename from samples/OcelotServiceFabric/src/OcelotApplicationService/Properties/launchSettings.json
rename to samples/ServiceFabric/DownstreamService/Properties/launchSettings.json
diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationService/ServiceEventSource.cs b/samples/ServiceFabric/DownstreamService/ServiceEventSource.cs
similarity index 100%
rename from samples/OcelotServiceFabric/src/OcelotApplicationService/ServiceEventSource.cs
rename to samples/ServiceFabric/DownstreamService/ServiceEventSource.cs
diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationService/Startup.cs b/samples/ServiceFabric/DownstreamService/Startup.cs
similarity index 100%
rename from samples/OcelotServiceFabric/src/OcelotApplicationService/Startup.cs
rename to samples/ServiceFabric/DownstreamService/Startup.cs
diff --git a/samples/OcelotServiceFabric/LICENSE.md b/samples/ServiceFabric/LICENSE.md
similarity index 100%
rename from samples/OcelotServiceFabric/LICENSE.md
rename to samples/ServiceFabric/LICENSE.md
diff --git a/samples/OcelotServiceFabric/OcelotApplication/ApplicationManifest.xml b/samples/ServiceFabric/OcelotApplication/ApplicationManifest.xml
similarity index 100%
rename from samples/OcelotServiceFabric/OcelotApplication/ApplicationManifest.xml
rename to samples/ServiceFabric/OcelotApplication/ApplicationManifest.xml
diff --git a/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Code/entryPoint.cmd b/samples/ServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Code/entryPoint.cmd
similarity index 100%
rename from samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Code/entryPoint.cmd
rename to samples/ServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Code/entryPoint.cmd
diff --git a/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Code/entryPoint.sh b/samples/ServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Code/entryPoint.sh
similarity index 100%
rename from samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Code/entryPoint.sh
rename to samples/ServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Code/entryPoint.sh
diff --git a/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Config/Settings.xml b/samples/ServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Config/Settings.xml
similarity index 100%
rename from samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Config/Settings.xml
rename to samples/ServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Config/Settings.xml
diff --git a/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Config/_readme.txt b/samples/ServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Config/_readme.txt
similarity index 100%
rename from samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Config/_readme.txt
rename to samples/ServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Config/_readme.txt
diff --git a/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Data/_readme.txt b/samples/ServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Data/_readme.txt
similarity index 100%
rename from samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Data/_readme.txt
rename to samples/ServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Data/_readme.txt
diff --git a/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/ServiceManifest-Linux.xml b/samples/ServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/ServiceManifest-Linux.xml
similarity index 100%
rename from samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/ServiceManifest-Linux.xml
rename to samples/ServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/ServiceManifest-Linux.xml
diff --git a/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/ServiceManifest-Windows.xml b/samples/ServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/ServiceManifest-Windows.xml
similarity index 100%
rename from samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/ServiceManifest-Windows.xml
rename to samples/ServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/ServiceManifest-Windows.xml
diff --git a/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/ServiceManifest.xml b/samples/ServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/ServiceManifest.xml
similarity index 100%
rename from samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/ServiceManifest.xml
rename to samples/ServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/ServiceManifest.xml
diff --git a/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Code/entryPoint.cmd b/samples/ServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Code/entryPoint.cmd
similarity index 100%
rename from samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Code/entryPoint.cmd
rename to samples/ServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Code/entryPoint.cmd
diff --git a/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Code/entryPoint.sh b/samples/ServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Code/entryPoint.sh
similarity index 100%
rename from samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Code/entryPoint.sh
rename to samples/ServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Code/entryPoint.sh
diff --git a/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Config/Settings.xml b/samples/ServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Config/Settings.xml
similarity index 100%
rename from samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Config/Settings.xml
rename to samples/ServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Config/Settings.xml
diff --git a/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Config/_readme.txt b/samples/ServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Config/_readme.txt
similarity index 100%
rename from samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Config/_readme.txt
rename to samples/ServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Config/_readme.txt
diff --git a/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Data/_readme.txt b/samples/ServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Data/_readme.txt
similarity index 100%
rename from samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Data/_readme.txt
rename to samples/ServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Data/_readme.txt
diff --git a/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/ServiceManifest-Linux.xml b/samples/ServiceFabric/OcelotApplication/OcelotApplicationServicePkg/ServiceManifest-Linux.xml
similarity index 100%
rename from samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/ServiceManifest-Linux.xml
rename to samples/ServiceFabric/OcelotApplication/OcelotApplicationServicePkg/ServiceManifest-Linux.xml
diff --git a/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/ServiceManifest-Windows.xml b/samples/ServiceFabric/OcelotApplication/OcelotApplicationServicePkg/ServiceManifest-Windows.xml
similarity index 100%
rename from samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/ServiceManifest-Windows.xml
rename to samples/ServiceFabric/OcelotApplication/OcelotApplicationServicePkg/ServiceManifest-Windows.xml
diff --git a/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/ServiceManifest.xml b/samples/ServiceFabric/OcelotApplication/OcelotApplicationServicePkg/ServiceManifest.xml
similarity index 100%
rename from samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/ServiceManifest.xml
rename to samples/ServiceFabric/OcelotApplication/OcelotApplicationServicePkg/ServiceManifest.xml
diff --git a/samples/OcelotServiceFabric/README.md b/samples/ServiceFabric/README.md
similarity index 100%
rename from samples/OcelotServiceFabric/README.md
rename to samples/ServiceFabric/README.md
diff --git a/samples/OcelotServiceFabric/build.bat b/samples/ServiceFabric/build.bat
similarity index 100%
rename from samples/OcelotServiceFabric/build.bat
rename to samples/ServiceFabric/build.bat
diff --git a/samples/OcelotServiceFabric/build.sh b/samples/ServiceFabric/build.sh
similarity index 100%
rename from samples/OcelotServiceFabric/build.sh
rename to samples/ServiceFabric/build.sh
diff --git a/samples/OcelotServiceFabric/dotnet-include.sh b/samples/ServiceFabric/dotnet-include.sh
similarity index 100%
rename from samples/OcelotServiceFabric/dotnet-include.sh
rename to samples/ServiceFabric/dotnet-include.sh
diff --git a/samples/OcelotServiceFabric/install.ps1 b/samples/ServiceFabric/install.ps1
similarity index 100%
rename from samples/OcelotServiceFabric/install.ps1
rename to samples/ServiceFabric/install.ps1
diff --git a/samples/OcelotServiceFabric/install.sh b/samples/ServiceFabric/install.sh
similarity index 100%
rename from samples/OcelotServiceFabric/install.sh
rename to samples/ServiceFabric/install.sh
diff --git a/samples/OcelotServiceFabric/uninstall.ps1 b/samples/ServiceFabric/uninstall.ps1
similarity index 100%
rename from samples/OcelotServiceFabric/uninstall.ps1
rename to samples/ServiceFabric/uninstall.ps1
diff --git a/samples/OcelotServiceFabric/uninstall.sh b/samples/ServiceFabric/uninstall.sh
similarity index 100%
rename from samples/OcelotServiceFabric/uninstall.sh
rename to samples/ServiceFabric/uninstall.sh
diff --git a/src/Ocelot.Provider.Kubernetes/EndPointClientV1.cs b/src/Ocelot.Provider.Kubernetes/EndPointClientV1.cs
index 22f58e5387..83418957b2 100644
--- a/src/Ocelot.Provider.Kubernetes/EndPointClientV1.cs
+++ b/src/Ocelot.Provider.Kubernetes/EndPointClientV1.cs
@@ -1,6 +1,7 @@
using HTTPlease;
using KubeClient.Models;
using KubeClient.ResourceClients;
+using Ocelot.Provider.Kubernetes.Interfaces;
namespace Ocelot.Provider.Kubernetes
{
diff --git a/src/Ocelot.Provider.Kubernetes/IEndPointClient.cs b/src/Ocelot.Provider.Kubernetes/Interfaces/IEndPointClient.cs
similarity index 83%
rename from src/Ocelot.Provider.Kubernetes/IEndPointClient.cs
rename to src/Ocelot.Provider.Kubernetes/Interfaces/IEndPointClient.cs
index 6dfca972db..10f79f8aff 100644
--- a/src/Ocelot.Provider.Kubernetes/IEndPointClient.cs
+++ b/src/Ocelot.Provider.Kubernetes/Interfaces/IEndPointClient.cs
@@ -1,7 +1,7 @@
using KubeClient.Models;
using KubeClient.ResourceClients;
-namespace Ocelot.Provider.Kubernetes;
+namespace Ocelot.Provider.Kubernetes.Interfaces;
public interface IEndPointClient : IKubeResourceClient
{
diff --git a/src/Ocelot.Provider.Kubernetes/Interfaces/IKubeServiceBuilder.cs b/src/Ocelot.Provider.Kubernetes/Interfaces/IKubeServiceBuilder.cs
new file mode 100644
index 0000000000..d5c6bcc30e
--- /dev/null
+++ b/src/Ocelot.Provider.Kubernetes/Interfaces/IKubeServiceBuilder.cs
@@ -0,0 +1,9 @@
+using KubeClient.Models;
+using Ocelot.Values;
+
+namespace Ocelot.Provider.Kubernetes.Interfaces;
+
+public interface IKubeServiceBuilder
+{
+ IEnumerable BuildServices(KubeRegistryConfiguration configuration, EndpointsV1 endpoint);
+}
diff --git a/src/Ocelot.Provider.Kubernetes/Interfaces/IKubeServiceCreator.cs b/src/Ocelot.Provider.Kubernetes/Interfaces/IKubeServiceCreator.cs
new file mode 100644
index 0000000000..a6ace7b2dd
--- /dev/null
+++ b/src/Ocelot.Provider.Kubernetes/Interfaces/IKubeServiceCreator.cs
@@ -0,0 +1,10 @@
+using KubeClient.Models;
+using Ocelot.Values;
+
+namespace Ocelot.Provider.Kubernetes.Interfaces;
+
+public interface IKubeServiceCreator
+{
+ IEnumerable Create(KubeRegistryConfiguration configuration, EndpointsV1 endpoint, EndpointSubsetV1 subset);
+ IEnumerable CreateInstance(KubeRegistryConfiguration configuration, EndpointsV1 endpoint, EndpointSubsetV1 subset, EndpointAddressV1 address);
+}
diff --git a/src/Ocelot.Provider.Kubernetes/Kube.cs b/src/Ocelot.Provider.Kubernetes/Kube.cs
index 15b5cf6ccd..5350f43b9b 100644
--- a/src/Ocelot.Provider.Kubernetes/Kube.cs
+++ b/src/Ocelot.Provider.Kubernetes/Kube.cs
@@ -1,5 +1,6 @@
using KubeClient.Models;
using Ocelot.Logging;
+using Ocelot.Provider.Kubernetes.Interfaces;
using Ocelot.Values;
namespace Ocelot.Provider.Kubernetes;
@@ -9,47 +10,44 @@ namespace Ocelot.Provider.Kubernetes;
///
public class Kube : IServiceDiscoveryProvider
{
- private readonly KubeRegistryConfiguration _kubeRegistryConfiguration;
+ private readonly KubeRegistryConfiguration _configuration;
private readonly IOcelotLogger _logger;
private readonly IKubeApiClient _kubeApi;
-
- public Kube(KubeRegistryConfiguration kubeRegistryConfiguration, IOcelotLoggerFactory factory, IKubeApiClient kubeApi)
+ private readonly IKubeServiceBuilder _serviceBuilder;
+ private readonly List _services;
+
+ public Kube(
+ KubeRegistryConfiguration configuration,
+ IOcelotLoggerFactory factory,
+ IKubeApiClient kubeApi,
+ IKubeServiceBuilder serviceBuilder)
{
- _kubeRegistryConfiguration = kubeRegistryConfiguration;
+ _configuration = configuration;
_logger = factory.CreateLogger();
_kubeApi = kubeApi;
+ _serviceBuilder = serviceBuilder;
+ _services = new();
}
- public async Task> GetAsync()
+ public virtual async Task> GetAsync()
{
var endpoint = await _kubeApi
.ResourceClient(client => new EndPointClientV1(client))
- .GetAsync(_kubeRegistryConfiguration.KeyOfServiceInK8s, _kubeRegistryConfiguration.KubeNamespace);
+ .GetAsync(_configuration.KeyOfServiceInK8s, _configuration.KubeNamespace);
- var services = new List();
- if (endpoint != null && endpoint.Subsets.Any())
+ _services.Clear();
+ if (endpoint?.Subsets.Count != 0)
{
- services.AddRange(BuildServices(endpoint));
+ _services.AddRange(BuildServices(_configuration, endpoint));
}
else
{
- _logger.LogWarning(() => $"namespace:{_kubeRegistryConfiguration.KubeNamespace}service:{_kubeRegistryConfiguration.KeyOfServiceInK8s} Unable to use ,it is invalid. Address must contain host only e.g. localhost and port must be greater than 0");
+ _logger.LogWarning(() => $"K8s Namespace:{_configuration.KubeNamespace}, Service:{_configuration.KeyOfServiceInK8s}; Unable to use: it is invalid. Address must contain host only e.g. localhost and port must be greater than 0!");
}
- return services;
+ return _services;
}
- private static List BuildServices(EndpointsV1 endpoint)
- {
- var services = new List();
-
- foreach (var subset in endpoint.Subsets)
- {
- services.AddRange(subset.Addresses.Select(address => new Service(endpoint.Metadata.Name,
- new ServiceHostAndPort(address.Ip, subset.Ports.First().Port),
- endpoint.Metadata.Uid, string.Empty, Enumerable.Empty())));
- }
-
- return services;
- }
+ protected virtual IEnumerable BuildServices(KubeRegistryConfiguration configuration, EndpointsV1 endpoint)
+ => _serviceBuilder.BuildServices(configuration, endpoint);
}
diff --git a/src/Ocelot.Provider.Kubernetes/KubeRegistryConfiguration.cs b/src/Ocelot.Provider.Kubernetes/KubeRegistryConfiguration.cs
index 2a3d7e8159..b264e1b67b 100644
--- a/src/Ocelot.Provider.Kubernetes/KubeRegistryConfiguration.cs
+++ b/src/Ocelot.Provider.Kubernetes/KubeRegistryConfiguration.cs
@@ -1,8 +1,8 @@
-namespace Ocelot.Provider.Kubernetes
+namespace Ocelot.Provider.Kubernetes;
+
+public class KubeRegistryConfiguration
{
- public class KubeRegistryConfiguration
- {
- public string KubeNamespace { get; set; }
- public string KeyOfServiceInK8s { get; set; }
- }
+ public string KubeNamespace { get; set; }
+ public string KeyOfServiceInK8s { get; set; }
+ public string Scheme { get; set; }
}
diff --git a/src/Ocelot.Provider.Kubernetes/KubeServiceBuilder.cs b/src/Ocelot.Provider.Kubernetes/KubeServiceBuilder.cs
new file mode 100644
index 0000000000..589cfe5bae
--- /dev/null
+++ b/src/Ocelot.Provider.Kubernetes/KubeServiceBuilder.cs
@@ -0,0 +1,36 @@
+using KubeClient.Models;
+using Ocelot.Logging;
+using Ocelot.Provider.Kubernetes.Interfaces;
+using Ocelot.Values;
+
+namespace Ocelot.Provider.Kubernetes;
+
+public class KubeServiceBuilder : IKubeServiceBuilder
+{
+ private readonly IOcelotLogger _logger;
+ private readonly IKubeServiceCreator _serviceCreator;
+
+ public KubeServiceBuilder(IOcelotLoggerFactory factory, IKubeServiceCreator serviceCreator)
+ {
+ ArgumentNullException.ThrowIfNull(factory);
+ _logger = factory.CreateLogger();
+
+ ArgumentNullException.ThrowIfNull(serviceCreator);
+ _serviceCreator = serviceCreator;
+ }
+
+ public virtual IEnumerable BuildServices(KubeRegistryConfiguration configuration, EndpointsV1 endpoint)
+ {
+ ArgumentNullException.ThrowIfNull(configuration);
+ ArgumentNullException.ThrowIfNull(endpoint);
+
+ var services = endpoint.Subsets
+ .SelectMany(subset => _serviceCreator.Create(configuration, endpoint, subset))
+ .ToArray();
+
+ _logger.LogDebug(() => $"K8s '{Check(endpoint.Kind)}:{Check(endpoint.ApiVersion)}:{Check(endpoint.Metadata?.Name)}' endpoint: Total built {services.Length} services.");
+ return services;
+ }
+
+ private static string Check(string str) => string.IsNullOrEmpty(str) ? "?" : str;
+}
diff --git a/src/Ocelot.Provider.Kubernetes/KubeServiceCreator.cs b/src/Ocelot.Provider.Kubernetes/KubeServiceCreator.cs
new file mode 100644
index 0000000000..3d51159c35
--- /dev/null
+++ b/src/Ocelot.Provider.Kubernetes/KubeServiceCreator.cs
@@ -0,0 +1,59 @@
+using KubeClient.Models;
+using Ocelot.Logging;
+using Ocelot.Provider.Kubernetes.Interfaces;
+using Ocelot.Values;
+
+namespace Ocelot.Provider.Kubernetes;
+
+public class KubeServiceCreator : IKubeServiceCreator
+{
+ private readonly IOcelotLogger _logger;
+
+ public KubeServiceCreator(IOcelotLoggerFactory factory)
+ {
+ ArgumentNullException.ThrowIfNull(factory);
+ _logger = factory.CreateLogger();
+ }
+
+ public virtual IEnumerable Create(KubeRegistryConfiguration configuration, EndpointsV1 endpoint, EndpointSubsetV1 subset)
+ => (configuration == null || endpoint == null || subset == null)
+ ? Array.Empty()
+ : subset.Addresses
+ .SelectMany(address => CreateInstance(configuration, endpoint, subset, address))
+ .ToArray();
+
+ public virtual IEnumerable CreateInstance(KubeRegistryConfiguration configuration, EndpointsV1 endpoint, EndpointSubsetV1 subset, EndpointAddressV1 address)
+ {
+ var instance = new Service(
+ GetServiceName(configuration, endpoint, subset, address),
+ GetServiceHostAndPort(configuration, endpoint, subset, address),
+ GetServiceId(configuration, endpoint, subset, address),
+ GetServiceVersion(configuration, endpoint, subset, address),
+ GetServiceTags(configuration, endpoint, subset, address)
+ );
+ return new Service[] { instance };
+ }
+
+ protected virtual string GetServiceName(KubeRegistryConfiguration configuration, EndpointsV1 endpoint, EndpointSubsetV1 subset, EndpointAddressV1 address)
+ => endpoint.Metadata?.Name;
+
+ protected virtual ServiceHostAndPort GetServiceHostAndPort(KubeRegistryConfiguration configuration, EndpointsV1 endpoint, EndpointSubsetV1 subset, EndpointAddressV1 address)
+ {
+ var ports = subset.Ports;
+ bool portNameToScheme(EndpointPortV1 p) => string.Equals(p.Name, configuration.Scheme, StringComparison.InvariantCultureIgnoreCase);
+ var portV1 = string.IsNullOrEmpty(configuration.Scheme) || !ports.Any(portNameToScheme)
+ ? ports.FirstOrDefault()
+ : ports.FirstOrDefault(portNameToScheme);
+ portV1 ??= new();
+ portV1.Name ??= configuration.Scheme ?? string.Empty;
+ _logger.LogDebug(() => $"K8s service with key '{configuration.KeyOfServiceInK8s}' and address {address.Ip}; Detected port is {portV1.Name}:{portV1.Port}. Total {ports.Count} ports of [{string.Join(',', ports.Select(p => p.Name))}].");
+ return new ServiceHostAndPort(address.Ip, portV1.Port, portV1.Name);
+ }
+
+ protected virtual string GetServiceId(KubeRegistryConfiguration configuration, EndpointsV1 endpoint, EndpointSubsetV1 subset, EndpointAddressV1 address)
+ => endpoint.Metadata?.Uid;
+ protected virtual string GetServiceVersion(KubeRegistryConfiguration configuration, EndpointsV1 endpoint, EndpointSubsetV1 subset, EndpointAddressV1 address)
+ => endpoint.ApiVersion;
+ protected virtual IEnumerable GetServiceTags(KubeRegistryConfiguration configuration, EndpointsV1 endpoint, EndpointSubsetV1 subset, EndpointAddressV1 address)
+ => Enumerable.Empty();
+}
diff --git a/src/Ocelot.Provider.Kubernetes/KubernetesProviderFactory.cs b/src/Ocelot.Provider.Kubernetes/KubernetesProviderFactory.cs
index 4507c03e69..a3a1d48c05 100644
--- a/src/Ocelot.Provider.Kubernetes/KubernetesProviderFactory.cs
+++ b/src/Ocelot.Provider.Kubernetes/KubernetesProviderFactory.cs
@@ -1,6 +1,7 @@
using Microsoft.Extensions.DependencyInjection;
using Ocelot.Configuration;
-using Ocelot.Logging;
+using Ocelot.Logging;
+using Ocelot.Provider.Kubernetes.Interfaces;
namespace Ocelot.Provider.Kubernetes
{
@@ -17,14 +18,16 @@ private static IServiceDiscoveryProvider CreateProvider(IServiceProvider provide
{
var factory = provider.GetService();
var kubeClient = provider.GetService();
+ var serviceBuilder = provider.GetService();
var configuration = new KubeRegistryConfiguration
{
KeyOfServiceInK8s = route.ServiceName,
KubeNamespace = string.IsNullOrEmpty(route.ServiceNamespace) ? config.Namespace : route.ServiceNamespace,
+ Scheme = route.DownstreamScheme,
};
- var defaultK8sProvider = new Kube(configuration, factory, kubeClient);
+ var defaultK8sProvider = new Kube(configuration, factory, kubeClient, serviceBuilder);
return PollKube.Equals(config.Type, StringComparison.OrdinalIgnoreCase)
? new PollKube(config.PollingInterval, factory, defaultK8sProvider)
diff --git a/src/Ocelot.Provider.Kubernetes/OcelotBuilderExtensions.cs b/src/Ocelot.Provider.Kubernetes/OcelotBuilderExtensions.cs
index 0110bddbe3..fadedc356f 100644
--- a/src/Ocelot.Provider.Kubernetes/OcelotBuilderExtensions.cs
+++ b/src/Ocelot.Provider.Kubernetes/OcelotBuilderExtensions.cs
@@ -1,16 +1,18 @@
using Microsoft.Extensions.DependencyInjection;
using Ocelot.DependencyInjection;
+using Ocelot.Provider.Kubernetes.Interfaces;
-namespace Ocelot.Provider.Kubernetes
+namespace Ocelot.Provider.Kubernetes;
+
+public static class OcelotBuilderExtensions
{
- public static class OcelotBuilderExtensions
+ public static IOcelotBuilder AddKubernetes(this IOcelotBuilder builder, bool usePodServiceAccount = true)
{
- public static IOcelotBuilder AddKubernetes(this IOcelotBuilder builder, bool usePodServiceAccount = true)
- {
- builder.Services
- .AddSingleton(KubernetesProviderFactory.Get)
- .AddKubeClient(usePodServiceAccount);
- return builder;
- }
+ builder.Services
+ .AddKubeClient(usePodServiceAccount)
+ .AddSingleton(KubernetesProviderFactory.Get)
+ .AddSingleton()
+ .AddSingleton();
+ return builder;
}
}
diff --git a/src/Ocelot.Provider.Polly/Interfaces/IPollyQoSResiliencePipelineProvider.cs b/src/Ocelot.Provider.Polly/Interfaces/IPollyQoSResiliencePipelineProvider.cs
new file mode 100644
index 0000000000..599ca5c138
--- /dev/null
+++ b/src/Ocelot.Provider.Polly/Interfaces/IPollyQoSResiliencePipelineProvider.cs
@@ -0,0 +1,16 @@
+using Ocelot.Configuration;
+
+namespace Ocelot.Provider.Polly.Interfaces;
+
+/// Defines provider for Polly V8 pipelines.
+/// An HTTP result type, usually it is type.
+public interface IPollyQoSResiliencePipelineProvider
+ where TResult : IDisposable
+{
+ ///
+ /// Gets Polly v8 pipeline.
+ ///
+ /// The route to apply a pipeline for.
+ /// A object where T is .
+ ResiliencePipeline GetResiliencePipeline(DownstreamRoute route);
+}
diff --git a/src/Ocelot.Provider.Polly/Ocelot.Provider.Polly.csproj b/src/Ocelot.Provider.Polly/Ocelot.Provider.Polly.csproj
index a75db00e8b..176e4769ff 100644
--- a/src/Ocelot.Provider.Polly/Ocelot.Provider.Polly.csproj
+++ b/src/Ocelot.Provider.Polly/Ocelot.Provider.Polly.csproj
@@ -34,7 +34,7 @@
all
-
+
diff --git a/src/Ocelot.Provider.Polly/OcelotBuilderExtensions.cs b/src/Ocelot.Provider.Polly/OcelotBuilderExtensions.cs
index a126336830..bfa26cf501 100644
--- a/src/Ocelot.Provider.Polly/OcelotBuilderExtensions.cs
+++ b/src/Ocelot.Provider.Polly/OcelotBuilderExtensions.cs
@@ -6,39 +6,202 @@
using Ocelot.Errors.QoS;
using Ocelot.Logging;
using Ocelot.Provider.Polly.Interfaces;
+using Ocelot.Provider.Polly.v7;
using Ocelot.Requester;
using Polly.CircuitBreaker;
+using Polly.Registry;
using Polly.Timeout;
namespace Ocelot.Provider.Polly;
public static class OcelotBuilderExtensions
{
- public static IOcelotBuilder AddPolly(this IOcelotBuilder builder,
- QosDelegatingHandlerDelegate delegatingHandler,
- Dictionary> errorMapping)
- where T : class, IPollyQoSProvider
+ ///
+ /// Default mapping of Polly s to objects.
+ ///
+ public static readonly IDictionary> DefaultErrorMapping = new Dictionary>
+ {
+ {typeof(TaskCanceledException), CreateRequestTimedOutError},
+ {typeof(TimeoutRejectedException), CreateRequestTimedOutError},
+ {typeof(BrokenCircuitException), CreateRequestTimedOutError},
+ {typeof(BrokenCircuitException), CreateRequestTimedOutError},
+ };
+
+ private static Error CreateRequestTimedOutError(Exception e) => new RequestTimedOutError(e);
+
+ ///
+ /// Adds Polly QoS provider to Ocelot by custom delegate and with custom error mapping.
+ ///
+ /// QoS provider to use (by default use ).
+ /// Ocelot builder to extend.
+ /// Your customized delegating handler (to manage QoS behavior by yourself).
+ /// Your customized error mapping.
+ /// The reference to the same extended object.
+ public static IOcelotBuilder AddPolly(this IOcelotBuilder builder, QosDelegatingHandlerDelegate delegatingHandler, IDictionary> errorMapping)
+ where TProvider : class, IPollyQoSResiliencePipelineProvider
{
builder.Services
- .AddSingleton(errorMapping)
- .AddSingleton, T>()
+ .AddSingleton>()
+ .AddSingleton(errorMapping) // Dictionary injection used in HttpExceptionToErrorMapper
+ .AddSingleton, TProvider>()
.AddSingleton(delegatingHandler);
-
return builder;
}
+ ///
+ /// Adds Polly QoS provider to Ocelot with custom error mapping, but default is used.
+ ///
+ /// QoS provider to use (by default use ).
+ /// Ocelot builder to extend.
+ /// Your customized error mapping.
+ /// The reference to the same extended object.
+ public static IOcelotBuilder AddPolly(this IOcelotBuilder builder, IDictionary> errorMapping)
+ where TProvider : class, IPollyQoSResiliencePipelineProvider
+ => AddPolly(builder, GetDelegatingHandler, errorMapping);
+
+ ///
+ /// Adds Polly QoS provider to Ocelot with custom delegate, but default error mapping is used.
+ ///
+ /// QoS provider to use (by default use ).
+ /// Ocelot builder to extend.
+ /// Your customized delegating handler (to manage QoS behavior by yourself).
+ /// The reference to the same extended object.
+ public static IOcelotBuilder AddPolly(this IOcelotBuilder builder, QosDelegatingHandlerDelegate delegatingHandler)
+ where TProvider : class, IPollyQoSResiliencePipelineProvider
+ => AddPolly(builder, delegatingHandler, DefaultErrorMapping);
+
+ ///
+ /// Adds Polly QoS provider to Ocelot by defaults.
+ ///
+ ///
+ /// Defaults:
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// QoS provider to use (by default use ).
+ /// Ocelot builder to extend.
+ /// The reference to the same extended object.
+ public static IOcelotBuilder AddPolly(this IOcelotBuilder builder)
+ where TProvider : class, IPollyQoSResiliencePipelineProvider
+ => AddPolly(builder, GetDelegatingHandler, DefaultErrorMapping);
+
+ ///
+ /// Adds Polly QoS provider to Ocelot by defaults with default QoS provider.
+ ///
+ ///
+ /// Defaults:
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Ocelot builder to extend.
+ /// The reference to the same extended object.
public static IOcelotBuilder AddPolly(this IOcelotBuilder builder)
+ => AddPolly(builder, GetDelegatingHandler, DefaultErrorMapping);
+
+ ///
+ /// Creates default delegating handler based on the type.
+ ///
+ /// The downstream route to apply the handler for.
+ /// The context accessor of the route.
+ /// The factory of logger.
+ /// A object, but concrete type is the class.
+ private static DelegatingHandler GetDelegatingHandler(DownstreamRoute route, IHttpContextAccessor contextAccessor, IOcelotLoggerFactory loggerFactory)
+ => new PollyResiliencePipelineDelegatingHandler(route, contextAccessor, loggerFactory);
+
+ #region Obsolete extensions will be removed in future version
+
+ ///
+ /// Adds Polly QoS provider to Ocelot by custom delegate and with custom error mapping.
+ ///
+ /// QoS provider to use (by default use ).
+ /// Ocelot builder to extend.
+ /// Your customized delegating handler (to manage QoS behavior by yourself).
+ /// Your customized error mapping.
+ /// The reference to the same extended object.
+ [Obsolete("Use AddPolly instead, it will be remove in future version")]
+ public static IOcelotBuilder AddPollyV7(this IOcelotBuilder builder, QosDelegatingHandlerDelegate delegatingHandler, IDictionary> errorMapping)
+ where TProvider : class, IPollyQoSProvider
{
- var errorMapping = new Dictionary>
- {
- { typeof(TaskCanceledException), e => new RequestTimedOutError(e) },
- { typeof(TimeoutRejectedException), e => new RequestTimedOutError(e) },
- { typeof(BrokenCircuitException), e => new RequestTimedOutError(e) },
- { typeof(BrokenCircuitException), e => new RequestTimedOutError(e) },
- };
- return AddPolly(builder, GetDelegatingHandler, errorMapping);
+ builder.Services
+ .AddSingleton(errorMapping)
+ .AddSingleton, TProvider>()
+ .AddSingleton(delegatingHandler);
+ return builder;
}
- private static DelegatingHandler GetDelegatingHandler(DownstreamRoute route, IHttpContextAccessor contextAccessor, IOcelotLoggerFactory loggerFactory)
+ ///
+ /// Adds Polly QoS provider to Ocelot with custom error mapping, but default is used.
+ ///
+ /// QoS provider to use (by default use ).
+ /// Ocelot builder to extend.
+ /// Your customized error mapping.
+ /// The reference to the same extended object.
+ [Obsolete("Use AddPolly instead, it will be remove in future version")]
+ public static IOcelotBuilder AddPollyV7(this IOcelotBuilder builder, IDictionary> errorMapping)
+ where TProvider : class, IPollyQoSProvider
+ => AddPollyV7(builder, GetDelegatingHandlerV7, errorMapping);
+
+ ///
+ /// Adds Polly QoS provider to Ocelot with custom delegate, but default error mapping is used.
+ ///
+ /// QoS provider to use (by default use ).
+ /// Ocelot builder to extend.
+ /// Your customized delegating handler (to manage QoS behavior by yourself).
+ /// The reference to the same extended object.
+ [Obsolete("Use AddPolly instead, it will be remove in future version")]
+ public static IOcelotBuilder AddPollyV7(this IOcelotBuilder builder, QosDelegatingHandlerDelegate delegatingHandler)
+ where TProvider : class, IPollyQoSProvider
+ => AddPollyV7(builder, delegatingHandler, DefaultErrorMapping);
+
+ ///
+ /// Adds Polly QoS provider to Ocelot by defaults.
+ ///
+ ///
+ /// Defaults:
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// QoS provider to use (by default use ).
+ /// Ocelot builder to extend.
+ /// The reference to the same extended object.
+ [Obsolete("Use AddPolly instead, it will be remove in future version")]
+ public static IOcelotBuilder AddPollyV7(this IOcelotBuilder builder)
+ where TProvider : class, IPollyQoSProvider
+ => AddPollyV7(builder, GetDelegatingHandlerV7, DefaultErrorMapping);
+
+ ///
+ /// Adds Polly QoS provider to Ocelot by defaults with default QoS provider.
+ ///
+ ///
+ /// Defaults:
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Ocelot builder to extend.
+ /// The reference to the same extended object.
+ [Obsolete("Use AddPolly instead, it will be remove in future version")]
+ public static IOcelotBuilder AddPollyV7(this IOcelotBuilder builder)
+ => AddPollyV7(builder, GetDelegatingHandlerV7, DefaultErrorMapping);
+
+ ///
+ /// Creates default delegating handler based on the type.
+ ///
+ /// The downstream route to apply the handler for.
+ /// The context accessor of the route.
+ /// The factory of logger.
+ /// A object, but concrete type is the class.
+ private static DelegatingHandler GetDelegatingHandlerV7(DownstreamRoute route, IHttpContextAccessor contextAccessor, IOcelotLoggerFactory loggerFactory)
=> new PollyPoliciesDelegatingHandler(route, contextAccessor, loggerFactory);
+
+ #endregion
}
diff --git a/src/Ocelot.Provider.Polly/OcelotResiliencePipelineKey.cs b/src/Ocelot.Provider.Polly/OcelotResiliencePipelineKey.cs
new file mode 100644
index 0000000000..528df61eeb
--- /dev/null
+++ b/src/Ocelot.Provider.Polly/OcelotResiliencePipelineKey.cs
@@ -0,0 +1,12 @@
+using Polly.Registry;
+
+namespace Ocelot.Provider.Polly;
+
+///
+/// Object used to identify a resilience pipeline in .
+///
+///
+/// Object used to identify a resilience pipeline in
+///
+/// The key for the resilience pipeline.
+public record OcelotResiliencePipelineKey(string Key);
diff --git a/src/Ocelot.Provider.Polly/PollyQoSProviderBase.cs b/src/Ocelot.Provider.Polly/PollyQoSProviderBase.cs
new file mode 100644
index 0000000000..24760c8005
--- /dev/null
+++ b/src/Ocelot.Provider.Polly/PollyQoSProviderBase.cs
@@ -0,0 +1,25 @@
+using Ocelot.Configuration;
+using System.Net;
+
+namespace Ocelot.Provider.Polly;
+
+public abstract class PollyQoSProviderBase
+{
+ protected static readonly HashSet ServerErrorCodes = new()
+ {
+ HttpStatusCode.InternalServerError,
+ HttpStatusCode.NotImplemented,
+ HttpStatusCode.BadGateway,
+ HttpStatusCode.ServiceUnavailable,
+ HttpStatusCode.GatewayTimeout,
+ HttpStatusCode.HttpVersionNotSupported,
+ HttpStatusCode.VariantAlsoNegotiates,
+ HttpStatusCode.InsufficientStorage,
+ HttpStatusCode.LoopDetected,
+ };
+
+ protected static string GetRouteName(DownstreamRoute route)
+ => string.IsNullOrWhiteSpace(route.ServiceName)
+ ? route.UpstreamPathTemplate?.Template ?? route.DownstreamPathTemplate?.Value ?? string.Empty
+ : route.ServiceName;
+}
diff --git a/src/Ocelot.Provider.Polly/PollyQoSResiliencePipelineProvider.cs b/src/Ocelot.Provider.Polly/PollyQoSResiliencePipelineProvider.cs
new file mode 100644
index 0000000000..be2df16b3e
--- /dev/null
+++ b/src/Ocelot.Provider.Polly/PollyQoSResiliencePipelineProvider.cs
@@ -0,0 +1,84 @@
+using Ocelot.Configuration;
+using Ocelot.Logging;
+using Ocelot.Provider.Polly.Interfaces;
+using Polly.CircuitBreaker;
+using Polly.Registry;
+using Polly.Timeout;
+
+namespace Ocelot.Provider.Polly;
+
+///
+/// Default provider for Polly V8 pipelines.
+///
+public class PollyQoSResiliencePipelineProvider : PollyQoSProviderBase, IPollyQoSResiliencePipelineProvider
+{
+ private readonly ResiliencePipelineRegistry _resiliencePipelineRegistry;
+ private readonly IOcelotLogger _logger;
+
+ public PollyQoSResiliencePipelineProvider(IOcelotLoggerFactory loggerFactory,
+ ResiliencePipelineRegistry resiliencePipelineRegistry)
+ {
+ _resiliencePipelineRegistry = resiliencePipelineRegistry;
+ _logger = loggerFactory.CreateLogger();
+ }
+
+ ///
+ /// Gets Polly V8 resilience pipeline (applies QoS feature) for the route.
+ ///
+ /// The downstream route to apply the pipeline for.
+ /// A object where T is .
+ public ResiliencePipeline GetResiliencePipeline(DownstreamRoute route)
+ {
+ var options = route.QosOptions;
+
+ // Check if we need pipeline at all before calling GetOrAddPipeline
+ if (options is null ||
+ (options.ExceptionsAllowedBeforeBreaking == 0 && options.TimeoutValue is int.MaxValue))
+ {
+ return null; // shortcut > no qos
+ }
+
+ var currentRouteName = GetRouteName(route);
+ return _resiliencePipelineRegistry.GetOrAddPipeline(
+ key: new OcelotResiliencePipelineKey(currentRouteName),
+ configure: (builder) => PollyResiliencePipelineWrapperFactory(builder, route));
+ }
+
+ private void PollyResiliencePipelineWrapperFactory(ResiliencePipelineBuilder builder, DownstreamRoute route)
+ {
+ var options = route.QosOptions;
+
+ // Add TimeoutStrategy if TimeoutValue is not int.MaxValue and greater than 0
+ if (options.TimeoutValue != int.MaxValue && options.TimeoutValue > 0)
+ {
+ builder.AddTimeout(TimeSpan.FromMilliseconds(options.TimeoutValue));
+ }
+
+ // Add CircuitBreakerStrategy only if ExceptionsAllowedBeforeBreaking is greater than 0
+ if (options.ExceptionsAllowedBeforeBreaking <= 0)
+ {
+ return; // shortcut > no qos (no timeout, no ExceptionsAllowedBeforeBreaking)
+ }
+
+ var info = $"Circuit Breaker for Route: {GetRouteName(route)}: ";
+
+ var circuitBreakerStrategyOptions = new CircuitBreakerStrategyOptions
+ {
+ FailureRatio = 0.8,
+ SamplingDuration = TimeSpan.FromSeconds(10),
+ MinimumThroughput = options.ExceptionsAllowedBeforeBreaking,
+ BreakDuration = TimeSpan.FromMilliseconds(options.DurationOfBreak),
+ ShouldHandle = new PredicateBuilder()
+ .HandleResult(message => ServerErrorCodes.Contains(message.StatusCode))
+ .Handle()
+ .Handle(),
+ OnOpened = args =>
+ {
+ _logger.LogError(info + $"Breaking for {args.BreakDuration.TotalMilliseconds} ms", args.Outcome.Exception);
+ return ValueTask.CompletedTask;
+ },
+ };
+
+ builder.AddCircuitBreaker(circuitBreakerStrategyOptions);
+ }
+}
diff --git a/src/Ocelot.Provider.Polly/PollyResiliencePipelineDelegatingHandler.cs b/src/Ocelot.Provider.Polly/PollyResiliencePipelineDelegatingHandler.cs
new file mode 100644
index 0000000000..4d8b0d10c9
--- /dev/null
+++ b/src/Ocelot.Provider.Polly/PollyResiliencePipelineDelegatingHandler.cs
@@ -0,0 +1,57 @@
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Ocelot.Configuration;
+using Ocelot.Logging;
+using Ocelot.Provider.Polly.Interfaces;
+using Polly.CircuitBreaker;
+using System.Diagnostics;
+
+namespace Ocelot.Provider.Polly;
+
+public class PollyResiliencePipelineDelegatingHandler : DelegatingHandler
+{
+ private readonly DownstreamRoute _route;
+ private readonly IHttpContextAccessor _contextAccessor;
+ private readonly IOcelotLogger _logger;
+
+ public PollyResiliencePipelineDelegatingHandler(
+ DownstreamRoute route,
+ IHttpContextAccessor contextAccessor,
+ IOcelotLoggerFactory loggerFactory)
+ {
+ _route = route;
+ _contextAccessor = contextAccessor;
+ _logger = loggerFactory.CreateLogger();
+ }
+
+ private IPollyQoSResiliencePipelineProvider GetQoSProvider()
+ {
+ Debug.Assert(_contextAccessor.HttpContext != null, "_contextAccessor.HttpContext != null");
+
+ // TODO: Move IPollyQoSResiliencePipelineProvider object injection to DI container by a DI helper
+ return _contextAccessor.HttpContext.RequestServices.GetService>();
+ }
+
+ ///
+ /// Sends an HTTP request to the inner handler to send to the server as an asynchronous operation.
+ ///
+ /// Downstream request.
+ /// Token to cancel the task.
+ /// A object of a result.
+ /// Exception thrown when a circuit is broken.
+ /// Exception thrown by and classes.
+ protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ var qoSProvider = this.GetQoSProvider();
+ var pipeline = qoSProvider.GetResiliencePipeline(_route);
+
+ if (pipeline == null)
+ {
+ _logger.LogDebug(() => $"No {nameof(pipeline)} was detected by QoS provider for the route with downstream URL '{request.RequestUri}'.");
+ return await base.SendAsync(request, cancellationToken); // shortcut > no qos
+ }
+
+ _logger.LogInformation(() => $"The {pipeline.GetType().Name} {nameof(pipeline)} has detected by QoS provider for the route with downstream URL '{request.RequestUri}'. Going to execute request...");
+ return await pipeline.ExecuteAsync(async (token) => await base.SendAsync(request, token), cancellationToken);
+ }
+}
diff --git a/src/Ocelot.Provider.Polly/Interfaces/IPollyQoSProvider.cs b/src/Ocelot.Provider.Polly/v7/IPollyQoSProvider.cs
similarity index 54%
rename from src/Ocelot.Provider.Polly/Interfaces/IPollyQoSProvider.cs
rename to src/Ocelot.Provider.Polly/v7/IPollyQoSProvider.cs
index 2145975315..1537439b92 100644
--- a/src/Ocelot.Provider.Polly/Interfaces/IPollyQoSProvider.cs
+++ b/src/Ocelot.Provider.Polly/v7/IPollyQoSProvider.cs
@@ -1,7 +1,8 @@
using Ocelot.Configuration;
-namespace Ocelot.Provider.Polly.Interfaces;
+namespace Ocelot.Provider.Polly.v7;
+[Obsolete("It is obsolete because now, we use IPollyQoSResiliencePipelineProvider with new v8 resilience strategies")]
public interface IPollyQoSProvider
where TResult : class
{
diff --git a/src/Ocelot.Provider.Polly/v7/OcelotBuilderExtensions.cs b/src/Ocelot.Provider.Polly/v7/OcelotBuilderExtensions.cs
new file mode 100644
index 0000000000..6098bbe018
--- /dev/null
+++ b/src/Ocelot.Provider.Polly/v7/OcelotBuilderExtensions.cs
@@ -0,0 +1,120 @@
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Ocelot.Configuration;
+using Ocelot.DependencyInjection;
+using Ocelot.Errors;
+using Ocelot.Errors.QoS;
+using Ocelot.Logging;
+using Ocelot.Requester;
+using Polly.CircuitBreaker;
+using Polly.Timeout;
+
+namespace Ocelot.Provider.Polly.v7;
+
+public static class OcelotBuilderExtensions
+{
+ ///
+ /// Default mapping of Polly s to objects.
+ ///
+ public static readonly IDictionary> DefaultErrorMapping = new Dictionary>
+ {
+ {typeof(TaskCanceledException), CreateRequestTimedOutError},
+ {typeof(TimeoutRejectedException), CreateRequestTimedOutError},
+ {typeof(BrokenCircuitException), CreateRequestTimedOutError},
+ {typeof(BrokenCircuitException), CreateRequestTimedOutError},
+ };
+
+ private static Error CreateRequestTimedOutError(Exception e) => new RequestTimedOutError(e);
+
+ #region Obsolete extensions will be removed in future version
+
+ ///
+ /// Adds Polly QoS provider to Ocelot by custom delegate and with custom error mapping.
+ ///
+ /// QoS provider to use (by default use ).
+ /// Ocelot builder to extend.
+ /// Your customized delegating handler (to manage QoS behavior by yourself).
+ /// Your customized error mapping.
+ /// The reference to the same extended object.
+ [Obsolete("We advise you to use Ocelot.Provider.Polly.AddPolly rather than this one. Polly v7 support will be removed in a future version")]
+ public static IOcelotBuilder AddPollyV7(this IOcelotBuilder builder, QosDelegatingHandlerDelegate delegatingHandler, IDictionary> errorMapping)
+ where TProvider : class, IPollyQoSProvider
+ {
+ builder.Services
+ .AddSingleton(errorMapping)
+ .AddSingleton, TProvider>()
+ .AddSingleton(delegatingHandler);
+ return builder;
+ }
+
+ ///
+ /// Adds Polly QoS provider to Ocelot with custom error mapping, but default is used.
+ ///
+ /// QoS provider to use (by default use ).
+ /// Ocelot builder to extend.
+ /// Your customized error mapping.
+ /// The reference to the same extended object.
+ [Obsolete("We advise you to use Ocelot.Provider.Polly.AddPolly rather than this one. Polly v7 support will be removed in a future version")]
+ public static IOcelotBuilder AddPollyV7(this IOcelotBuilder builder, IDictionary> errorMapping)
+ where TProvider : class, IPollyQoSProvider
+ => AddPollyV7(builder, GetDelegatingHandlerV7, errorMapping);
+
+ ///
+ /// Adds Polly QoS provider to Ocelot with custom delegate, but default error mapping is used.
+ ///
+ /// QoS provider to use (by default use ).
+ /// Ocelot builder to extend.
+ /// Your customized delegating handler (to manage QoS behavior by yourself).
+ /// The reference to the same extended object.
+ [Obsolete("We advise you to use Ocelot.Provider.Polly.AddPolly rather than this one. Polly v7 support will be removed in a future version")]
+ public static IOcelotBuilder AddPollyV7(this IOcelotBuilder builder, QosDelegatingHandlerDelegate delegatingHandler)
+ where TProvider : class, IPollyQoSProvider
+ => AddPollyV7(builder, delegatingHandler, DefaultErrorMapping);
+
+ ///
+ /// Adds Polly QoS provider to Ocelot by defaults.
+ ///
+ ///
+ /// Defaults:
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// QoS provider to use (by default use ).
+ /// Ocelot builder to extend.
+ /// The reference to the same extended object.
+ [Obsolete("Use AddPolly instead, it will be remove in future version")]
+ public static IOcelotBuilder AddPollyV7(this IOcelotBuilder builder)
+ where TProvider : class, IPollyQoSProvider
+ => AddPollyV7(builder, GetDelegatingHandlerV7, DefaultErrorMapping);
+
+ ///
+ /// Adds Polly QoS provider to Ocelot by defaults with default QoS provider.
+ ///
+ ///
+ /// Defaults:
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Ocelot builder to extend.
+ /// The reference to the same extended object.
+ [Obsolete("We advise you to use Ocelot.Provider.Polly.AddPolly rather than this one. Polly v7 support will be removed in a future version")]
+ public static IOcelotBuilder AddPollyV7(this IOcelotBuilder builder)
+ => AddPollyV7(builder, GetDelegatingHandlerV7, DefaultErrorMapping);
+
+ ///
+ /// Creates default delegating handler based on the type.
+ ///
+ /// The downstream route to apply the handler for.
+ /// The context accessor of the route.
+ /// The factory of logger.
+ /// A object, but concrete type is the class.
+ private static DelegatingHandler GetDelegatingHandlerV7(DownstreamRoute route, IHttpContextAccessor contextAccessor, IOcelotLoggerFactory loggerFactory)
+ => new PollyPoliciesDelegatingHandler(route, contextAccessor, loggerFactory);
+
+ #endregion
+}
diff --git a/src/Ocelot.Provider.Polly/PollyPoliciesDelegatingHandler.cs b/src/Ocelot.Provider.Polly/v7/PollyPoliciesDelegatingHandler.cs
similarity index 86%
rename from src/Ocelot.Provider.Polly/PollyPoliciesDelegatingHandler.cs
rename to src/Ocelot.Provider.Polly/v7/PollyPoliciesDelegatingHandler.cs
index 03be864951..162e123371 100644
--- a/src/Ocelot.Provider.Polly/PollyPoliciesDelegatingHandler.cs
+++ b/src/Ocelot.Provider.Polly/v7/PollyPoliciesDelegatingHandler.cs
@@ -1,13 +1,15 @@
+using System.Diagnostics;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Ocelot.Configuration;
using Ocelot.Logging;
-using Ocelot.Provider.Polly.Interfaces;
using Polly.CircuitBreaker;
-using System.Diagnostics;
-namespace Ocelot.Provider.Polly;
+namespace Ocelot.Provider.Polly.v7;
+/// Delegates sending to downstream.
+/// Outdated V7 design! Use the class.
+[Obsolete("Due to new v8 policy definition in Polly 8 (use PollyResiliencePipelineDelegatingHandler)")]
public class PollyPoliciesDelegatingHandler : DelegatingHandler
{
private readonly DownstreamRoute _route;
diff --git a/src/Ocelot.Provider.Polly/PollyPolicyWrapper.cs b/src/Ocelot.Provider.Polly/v7/PollyPolicyWrapper.cs
similarity index 82%
rename from src/Ocelot.Provider.Polly/PollyPolicyWrapper.cs
rename to src/Ocelot.Provider.Polly/v7/PollyPolicyWrapper.cs
index 0b3058cbb1..821743433c 100644
--- a/src/Ocelot.Provider.Polly/PollyPolicyWrapper.cs
+++ b/src/Ocelot.Provider.Polly/v7/PollyPolicyWrapper.cs
@@ -1,4 +1,4 @@
-namespace Ocelot.Provider.Polly;
+namespace Ocelot.Provider.Polly.v7;
public class PollyPolicyWrapper
where TResult : class
@@ -12,8 +12,8 @@ public PollyPolicyWrapper(params IAsyncPolicy[] policies)
{
var allPolicies = policies.Where(p => p != null).ToArray();
- AsyncPollyPolicy = allPolicies.Length > 1 ?
- Policy.WrapAsync(allPolicies) :
+ AsyncPollyPolicy = allPolicies.Length > 1 ?
+ Policy.WrapAsync(allPolicies) :
allPolicies[0];
}
diff --git a/src/Ocelot.Provider.Polly/PollyQoSProvider.cs b/src/Ocelot.Provider.Polly/v7/PollyQoSProvider.cs
similarity index 71%
rename from src/Ocelot.Provider.Polly/PollyQoSProvider.cs
rename to src/Ocelot.Provider.Polly/v7/PollyQoSProvider.cs
index 7a9465a22e..51c727e802 100644
--- a/src/Ocelot.Provider.Polly/PollyQoSProvider.cs
+++ b/src/Ocelot.Provider.Polly/v7/PollyQoSProvider.cs
@@ -1,44 +1,29 @@
-using Ocelot.Configuration;
+using Ocelot.Configuration;
using Ocelot.Logging;
-using Ocelot.Provider.Polly.Interfaces;
using Polly.CircuitBreaker;
using Polly.Timeout;
-using System.Net;
-namespace Ocelot.Provider.Polly;
+namespace Ocelot.Provider.Polly.v7;
-public class PollyQoSProvider : IPollyQoSProvider
+/// Legacy QoS provider based on Polly v7.
+/// Use the as a new QoS provider based on Polly v8.
+[Obsolete("Due to new v8 policy definition in Polly 8 (use PollyQoSResiliencePipelineProvider)")]
+public class PollyQoSProvider : PollyQoSProviderBase, IPollyQoSProvider
{
private readonly Dictionary> _policyWrappers = new();
+
private readonly object _lockObject = new();
private readonly IOcelotLogger _logger;
- //todo: this should be configurable and available as global config parameter in ocelot.json
+ // TODO: This should be configurable and available as global config parameter in ocelot.json
public const int DefaultRequestTimeoutSeconds = 90;
- private readonly HashSet _serverErrorCodes = new()
- {
- HttpStatusCode.InternalServerError,
- HttpStatusCode.NotImplemented,
- HttpStatusCode.BadGateway,
- HttpStatusCode.ServiceUnavailable,
- HttpStatusCode.GatewayTimeout,
- HttpStatusCode.HttpVersionNotSupported,
- HttpStatusCode.VariantAlsoNegotiates,
- HttpStatusCode.InsufficientStorage,
- HttpStatusCode.LoopDetected,
- };
-
public PollyQoSProvider(IOcelotLoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger();
}
- private static string GetRouteName(DownstreamRoute route)
- => string.IsNullOrWhiteSpace(route.ServiceName)
- ? route.UpstreamPathTemplate?.Template ?? route.DownstreamPathTemplate?.Value ?? string.Empty
- : route.ServiceName;
-
+ [Obsolete("Due to new v8 policy definition in Polly 8 (use GetResiliencePipeline in PollyQoSResiliencePipelineProvider)")]
public PollyPolicyWrapper GetPollyPolicyWrapper(DownstreamRoute route)
{
lock (_lockObject)
@@ -61,7 +46,7 @@ private PollyPolicyWrapper PollyPolicyWrapperFactory(Downst
var info = $"Route: {GetRouteName(route)}; Breaker logging in {nameof(PollyQoSProvider)}: ";
exceptionsAllowedBeforeBreakingPolicy = Policy
- .HandleResult(r => _serverErrorCodes.Contains(r.StatusCode))
+ .HandleResult(r => ServerErrorCodes.Contains(r.StatusCode))
.Or()
.Or()
.CircuitBreakerAsync(route.QosOptions.ExceptionsAllowedBeforeBreaking,
diff --git a/src/Ocelot/Cache/AspMemoryCache.cs b/src/Ocelot/Cache/DefaultMemoryCache.cs
similarity index 94%
rename from src/Ocelot/Cache/AspMemoryCache.cs
rename to src/Ocelot/Cache/DefaultMemoryCache.cs
index 2067b38139..f53b6ceafd 100644
--- a/src/Ocelot/Cache/AspMemoryCache.cs
+++ b/src/Ocelot/Cache/DefaultMemoryCache.cs
@@ -2,12 +2,12 @@
namespace Ocelot.Cache
{
- public class AspMemoryCache : IOcelotCache
+ public class DefaultMemoryCache : IOcelotCache
{
private readonly IMemoryCache _memoryCache;
private readonly Dictionary> _regions;
- public AspMemoryCache(IMemoryCache memoryCache)
+ public DefaultMemoryCache(IMemoryCache memoryCache)
{
_memoryCache = memoryCache;
_regions = new Dictionary>();
diff --git a/src/Ocelot/Cache/IRegionCreator.cs b/src/Ocelot/Cache/IRegionCreator.cs
deleted file mode 100644
index da1b042dab..0000000000
--- a/src/Ocelot/Cache/IRegionCreator.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using Ocelot.Configuration.File;
-
-namespace Ocelot.Cache
-{
- public interface IRegionCreator
- {
- string Create(FileRoute route);
- }
-}
diff --git a/src/Ocelot/Cache/RegionCreator.cs b/src/Ocelot/Cache/RegionCreator.cs
deleted file mode 100644
index c2dd0ccac6..0000000000
--- a/src/Ocelot/Cache/RegionCreator.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using Ocelot.Configuration.File;
-
-namespace Ocelot.Cache
-{
- public class RegionCreator : IRegionCreator
- {
- public string Create(FileRoute route)
- {
- if (!string.IsNullOrEmpty(route?.FileCacheOptions?.Region))
- {
- return route?.FileCacheOptions?.Region;
- }
-
- var methods = string.Join(string.Empty, route.UpstreamHttpMethod.Select(m => m));
-
- var region = $"{methods}{route.UpstreamPathTemplate.Replace("/", string.Empty)}";
-
- return region;
- }
- }
-}
diff --git a/src/Ocelot/Configuration/AuthenticationOptions.cs b/src/Ocelot/Configuration/AuthenticationOptions.cs
index af5cf7273e..978a940a8a 100644
--- a/src/Ocelot/Configuration/AuthenticationOptions.cs
+++ b/src/Ocelot/Configuration/AuthenticationOptions.cs
@@ -8,22 +8,21 @@ public AuthenticationOptions(List allowedScopes, string authenticationPr
{
AllowedScopes = allowedScopes;
AuthenticationProviderKey = authenticationProviderKey;
- AuthenticationProviderKeys = [];
+ AuthenticationProviderKeys = Array.Empty();
}
public AuthenticationOptions(FileAuthenticationOptions from)
{
- AllowedScopes = from.AllowedScopes ?? [];
+ AllowedScopes = from.AllowedScopes ?? new();
AuthenticationProviderKey = from.AuthenticationProviderKey ?? string.Empty;
- AuthenticationProviderKeys = from.AuthenticationProviderKeys ?? [];
+ AuthenticationProviderKeys = from.AuthenticationProviderKeys ?? Array.Empty();
}
- public AuthenticationOptions(List allowedScopes, string authenticationProviderKey,
- string[] authenticationProviderKeys)
+ public AuthenticationOptions(List allowedScopes, string authenticationProviderKey, string[] authenticationProviderKeys)
{
- AllowedScopes = allowedScopes ?? [];
+ AllowedScopes = allowedScopes ?? new();
AuthenticationProviderKey = authenticationProviderKey ?? string.Empty;
- AuthenticationProviderKeys = authenticationProviderKeys ?? [];
+ AuthenticationProviderKeys = authenticationProviderKeys ?? Array.Empty();
}
public List AllowedScopes { get; }
diff --git a/src/Ocelot/Configuration/Builder/AuthenticationOptionsBuilder.cs b/src/Ocelot/Configuration/Builder/AuthenticationOptionsBuilder.cs
index e911908c7d..0b85ee8095 100644
--- a/src/Ocelot/Configuration/Builder/AuthenticationOptionsBuilder.cs
+++ b/src/Ocelot/Configuration/Builder/AuthenticationOptionsBuilder.cs
@@ -4,7 +4,7 @@ public class AuthenticationOptionsBuilder
{
private List _allowedScopes = new();
private string _authenticationProviderKey;
- private string[] _authenticationProviderKeys =[];
+ private string[] _authenticationProviderKeys = Array.Empty();
public AuthenticationOptionsBuilder WithAllowedScopes(List allowedScopes)
{
diff --git a/src/Ocelot/Configuration/Builder/DownstreamRouteBuilder.cs b/src/Ocelot/Configuration/Builder/DownstreamRouteBuilder.cs
index 9dc93008ee..281e930332 100644
--- a/src/Ocelot/Configuration/Builder/DownstreamRouteBuilder.cs
+++ b/src/Ocelot/Configuration/Builder/DownstreamRouteBuilder.cs
@@ -19,7 +19,7 @@ public class DownstreamRouteBuilder
private List _claimToDownstreamPath;
private string _requestIdHeaderKey;
private bool _isCached;
- private CacheOptions _fileCacheOptions;
+ private CacheOptions _cacheOptions;
private string _downstreamScheme;
private LoadBalancerOptions _loadBalancerOptions;
private QoSOptions _qosOptions;
@@ -87,7 +87,7 @@ public DownstreamRouteBuilder WithUpstreamPathTemplate(UpstreamPathTemplate inpu
public DownstreamRouteBuilder WithUpstreamHttpMethod(List input)
{
- _upstreamHttpMethod = (input.Count == 0) ? new List() : input.Select(x => new HttpMethod(x.Trim())).ToList();
+ _upstreamHttpMethod = input.Count == 0 ? new List() : input.Select(x => new HttpMethod(x.Trim())).ToList();
return this;
}
@@ -147,7 +147,7 @@ public DownstreamRouteBuilder WithIsCached(bool input)
public DownstreamRouteBuilder WithCacheOptions(CacheOptions input)
{
- _fileCacheOptions = input;
+ _cacheOptions = input;
return this;
}
@@ -276,7 +276,7 @@ public DownstreamRoute Build()
_downstreamScheme,
_requestIdHeaderKey,
_isCached,
- _fileCacheOptions,
+ _cacheOptions,
_loadBalancerOptions,
_rateLimitOptions,
_routeClaimRequirement,
diff --git a/src/Ocelot/Configuration/CacheOptions.cs b/src/Ocelot/Configuration/CacheOptions.cs
index 352b501d87..dc3c191162 100644
--- a/src/Ocelot/Configuration/CacheOptions.cs
+++ b/src/Ocelot/Configuration/CacheOptions.cs
@@ -1,39 +1,42 @@
using Ocelot.Request.Middleware;
-namespace Ocelot.Configuration
-{
- public class CacheOptions
- {
- internal CacheOptions() { }
+namespace Ocelot.Configuration;
- public CacheOptions(int ttlSeconds, string region, string header)
- {
- TtlSeconds = ttlSeconds;
- Region = region;
- Header = header;
- }
-
- public CacheOptions(int ttlSeconds, string region, string header, bool enableContentHashing)
- {
- TtlSeconds = ttlSeconds;
- Region = region;
- Header = header;
- EnableContentHashing = enableContentHashing;
- }
+public class CacheOptions
+{
+ internal CacheOptions() { }
- public int TtlSeconds { get; }
- public string Region { get; }
- public string Header { get; }
-
- ///
- /// Enables MD5 hash calculation of the of the object.
- ///
- ///
- /// Default value is . No hashing by default.
- ///
- ///
- /// if hashing is enabled, otherwise it is .
- ///
- public bool EnableContentHashing { get; }
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// Internal defaults:
+ ///
+ /// - The default value for is , but it is set to null for route-level configuration to allow global configuration usage.
+ /// - The default value for is 0.
+ ///
+ ///
+ /// Time-to-live seconds. If not speciefied, zero value is used by default.
+ /// The region of caching.
+ /// The header name to control cached value.
+ /// The switcher for content hashing. If not speciefied, false value is used by default.
+ public CacheOptions(int? ttlSeconds, string region, string header, bool? enableContentHashing)
+ {
+ TtlSeconds = ttlSeconds ?? 0;
+ Region = region;
+ Header = header;
+ EnableContentHashing = enableContentHashing ?? false;
}
-}
+
+ /// Time-to-live seconds.
+ /// Default value is 0. No caching by default.
+ /// An value of seconds.
+ public int TtlSeconds { get; }
+ public string Region { get; }
+ public string Header { get; }
+
+ /// Enables MD5 hash calculation of the of the object.
+ /// Default value is . No hashing by default.
+ /// if hashing is enabled, otherwise it is .
+ public bool EnableContentHashing { get; }
+}
diff --git a/src/Ocelot/Configuration/Creator/AggregatesCreator.cs b/src/Ocelot/Configuration/Creator/AggregatesCreator.cs
index 6743edfc71..9a5ae29069 100644
--- a/src/Ocelot/Configuration/Creator/AggregatesCreator.cs
+++ b/src/Ocelot/Configuration/Creator/AggregatesCreator.cs
@@ -24,16 +24,15 @@ private Route SetUpAggregateRoute(IEnumerable routes, FileAggregateRoute
{
var applicableRoutes = new List();
var allRoutes = routes.SelectMany(x => x.DownstreamRoute);
-
- foreach (var routeKey in aggregateRoute.RouteKeys)
- {
- var selec = allRoutes.FirstOrDefault(q => q.Key == routeKey);
- if (selec == null)
- {
- return null;
- }
-
- applicableRoutes.Add(selec);
+ var downstreamRoutes = aggregateRoute.RouteKeys.Select(routeKey => allRoutes.FirstOrDefault(q => q.Key == routeKey));
+ foreach (var downstreamRoute in downstreamRoutes)
+ {
+ if (downstreamRoute == null)
+ {
+ return null;
+ }
+
+ applicableRoutes.Add(downstreamRoute);
}
var upstreamTemplatePattern = _creator.Create(aggregateRoute);
diff --git a/src/Ocelot/Configuration/Creator/CacheOptionsCreator.cs b/src/Ocelot/Configuration/Creator/CacheOptionsCreator.cs
new file mode 100644
index 0000000000..6d1c8f2b49
--- /dev/null
+++ b/src/Ocelot/Configuration/Creator/CacheOptionsCreator.cs
@@ -0,0 +1,27 @@
+using Ocelot.Configuration.File;
+
+namespace Ocelot.Configuration.Creator;
+
+public class CacheOptionsCreator : ICacheOptionsCreator
+{
+ public CacheOptions Create(FileCacheOptions options, FileGlobalConfiguration global, string upstreamPathTemplate, IList upstreamHttpMethods)
+ {
+ var region = GetRegion(options.Region ?? global?.CacheOptions.Region, upstreamPathTemplate, upstreamHttpMethods);
+ var header = options.Header ?? global?.CacheOptions.Header;
+ var ttlSeconds = options.TtlSeconds ?? global?.CacheOptions.TtlSeconds;
+ var enableContentHashing = options.EnableContentHashing ?? global?.CacheOptions.EnableContentHashing;
+
+ return new CacheOptions(ttlSeconds, region, header, enableContentHashing);
+ }
+
+ protected virtual string GetRegion(string region, string upstreamPathTemplate, IList upstreamHttpMethod)
+ {
+ if (!string.IsNullOrEmpty(region))
+ {
+ return region;
+ }
+
+ var methods = string.Join(string.Empty, upstreamHttpMethod);
+ return $"{methods}{upstreamPathTemplate.Replace("/", string.Empty)}";
+ }
+}
diff --git a/src/Ocelot/Configuration/Creator/ICacheOptionsCreator.cs b/src/Ocelot/Configuration/Creator/ICacheOptionsCreator.cs
new file mode 100644
index 0000000000..a76a1b20e9
--- /dev/null
+++ b/src/Ocelot/Configuration/Creator/ICacheOptionsCreator.cs
@@ -0,0 +1,19 @@
+using Ocelot.Configuration.File;
+
+namespace Ocelot.Configuration.Creator;
+
+///
+/// This interface is used to create cache options.
+///
+public interface ICacheOptionsCreator
+{
+ ///
+ /// Creates cache options based on the file cache options, upstream path template and upstream HTTP methods.
+ /// Upstream path template and upstream HTTP methods are used to get the region name.
+ /// The file cache options.
+ /// The global configuration.
+ /// The upstream path template as string.
+ /// The upstream http methods as a list of strings.
+ /// The generated cache options.
+ CacheOptions Create(FileCacheOptions options, FileGlobalConfiguration global, string upstreamPathTemplate, IList upstreamHttpMethods);
+}
diff --git a/src/Ocelot/Configuration/Creator/RouteKeyCreator.cs b/src/Ocelot/Configuration/Creator/RouteKeyCreator.cs
index 93ff6f0aa6..8bc5debc94 100644
--- a/src/Ocelot/Configuration/Creator/RouteKeyCreator.cs
+++ b/src/Ocelot/Configuration/Creator/RouteKeyCreator.cs
@@ -1,17 +1,86 @@
using Ocelot.Configuration.File;
-using Ocelot.LoadBalancer.LoadBalancers;
+using Ocelot.LoadBalancer.LoadBalancers;
-namespace Ocelot.Configuration.Creator
-{
- public class RouteKeyCreator : IRouteKeyCreator
+namespace Ocelot.Configuration.Creator;
+
+public class RouteKeyCreator : IRouteKeyCreator
+{
+ ///
+ /// Creates the unique key based on the route properties for load balancing etc.
+ ///
+ ///
+ /// Key template:
+ ///
+ /// - UpstreamHttpMethod|UpstreamPathTemplate|UpstreamHost|DownstreamHostAndPorts|ServiceNamespace|ServiceName|LoadBalancerType|LoadBalancerKey
+ ///
+ ///
+ /// The route object.
+ /// A object containing the key.
+ public string Create(FileRoute fileRoute)
{
- public string Create(FileRoute fileRoute) => IsStickySession(fileRoute)
- ? $"{nameof(CookieStickySessions)}:{fileRoute.LoadBalancerOptions.Key}"
- : $"{fileRoute.UpstreamPathTemplate}|{string.Join(',', fileRoute.UpstreamHttpMethod)}|{string.Join(',', fileRoute.DownstreamHostAndPorts.Select(x => $"{x.Host}:{x.Port}"))}";
+ var isStickySession = fileRoute.LoadBalancerOptions is
+ {
+ Type: nameof(CookieStickySessions),
+ Key.Length: > 0
+ };
+
+ if (isStickySession)
+ {
+ return $"{nameof(CookieStickySessions)}:{fileRoute.LoadBalancerOptions.Key}";
+ }
+
+ var upstreamHttpMethods = Csv(fileRoute.UpstreamHttpMethod);
+ var downstreamHostAndPorts = Csv(fileRoute.DownstreamHostAndPorts.Select(downstream => $"{downstream.Host}:{downstream.Port}"));
+
+ var keyBuilder = new StringBuilder()
+
+ // UpstreamHttpMethod and UpstreamPathTemplate are required
+ .AppendNext(upstreamHttpMethods)
+ .AppendNext(fileRoute.UpstreamPathTemplate)
+
+ // Other properties are optional, replace undefined values with defaults to aid debugging
+ .AppendNext(Coalesce(fileRoute.UpstreamHost, "no-host"))
+
+ .AppendNext(Coalesce(downstreamHostAndPorts, "no-host-and-port"))
+ .AppendNext(Coalesce(fileRoute.ServiceNamespace, "no-svc-ns"))
+ .AppendNext(Coalesce(fileRoute.ServiceName, "no-svc-name"))
+ .AppendNext(Coalesce(fileRoute.LoadBalancerOptions.Type, "no-lb-type"))
+ .AppendNext(Coalesce(fileRoute.LoadBalancerOptions.Key, "no-lb-key"));
- private static bool IsStickySession(FileRoute fileRoute) =>
- !string.IsNullOrEmpty(fileRoute.LoadBalancerOptions.Type)
- && !string.IsNullOrEmpty(fileRoute.LoadBalancerOptions.Key)
- && fileRoute.LoadBalancerOptions.Type == nameof(CookieStickySessions);
- }
+ return keyBuilder.ToString();
+ }
+
+ ///
+ /// Helper function to convert multiple strings into a comma-separated string.
+ ///
+ /// The collection of strings to join by comma separator.
+ /// A in the comma-separated format.
+ private static string Csv(IEnumerable values) => string.Join(',', values);
+
+ ///
+ /// Helper function to return the first non-null-or-whitespace string.
+ ///
+ /// The 1st string to check.
+ /// The 2nd string to check.
+ /// A which is not empty.
+ private static string Coalesce(string first, string second) => string.IsNullOrWhiteSpace(first) ? second : first;
+}
+
+internal static class RouteKeyCreatorHelpers
+{
+ ///
+ /// Helper function to append a string to the key builder, separated by a pipe.
+ ///
+ /// The builder of the key.
+ /// The next word to add.
+ /// The reference to the builder.
+ public static StringBuilder AppendNext(this StringBuilder builder, string next)
+ {
+ if (builder.Length > 0)
+ {
+ builder.Append('|');
+ }
+
+ return builder.Append(next);
+ }
}
diff --git a/src/Ocelot/Configuration/Creator/RouteOptionsCreator.cs b/src/Ocelot/Configuration/Creator/RouteOptionsCreator.cs
index 8e0911e560..8b6f0e3eac 100644
--- a/src/Ocelot/Configuration/Creator/RouteOptionsCreator.cs
+++ b/src/Ocelot/Configuration/Creator/RouteOptionsCreator.cs
@@ -17,6 +17,8 @@ public RouteOptions Create(FileRoute fileRoute)
&& (!string.IsNullOrEmpty(authOpts.AuthenticationProviderKey)
|| authOpts.AuthenticationProviderKeys?.Any(k => !string.IsNullOrWhiteSpace(k)) == true);
var isAuthorized = fileRoute.RouteClaimsRequirement?.Any() == true;
+
+ // TODO: This sounds more like a hack, it might be better to refactor this at some point.
var isCached = fileRoute.FileCacheOptions.TtlSeconds > 0;
var enableRateLimiting = fileRoute.RateLimitOptions?.EnableRateLimiting == true;
var useServiceDiscovery = !string.IsNullOrEmpty(fileRoute.ServiceName);
diff --git a/src/Ocelot/Configuration/Creator/RoutesCreator.cs b/src/Ocelot/Configuration/Creator/RoutesCreator.cs
index 8c1f1de639..19bce7b17e 100644
--- a/src/Ocelot/Configuration/Creator/RoutesCreator.cs
+++ b/src/Ocelot/Configuration/Creator/RoutesCreator.cs
@@ -1,5 +1,4 @@
-using Ocelot.Cache;
-using Ocelot.Configuration.Builder;
+using Ocelot.Configuration.Builder;
using Ocelot.Configuration.File;
namespace Ocelot.Configuration.Creator
@@ -14,7 +13,7 @@ public class RoutesCreator : IRoutesCreator
private readonly IQoSOptionsCreator _qosOptionsCreator;
private readonly IRouteOptionsCreator _fileRouteOptionsCreator;
private readonly IRateLimitOptionsCreator _rateLimitOptionsCreator;
- private readonly IRegionCreator _regionCreator;
+ private readonly ICacheOptionsCreator _cacheOptionsCreator;
private readonly IHttpHandlerOptionsCreator _httpHandlerOptionsCreator;
private readonly IHeaderFindAndReplaceCreator _headerFAndRCreator;
private readonly IDownstreamAddressesCreator _downstreamAddressesCreator;
@@ -30,21 +29,20 @@ public RoutesCreator(
IQoSOptionsCreator qosOptionsCreator,
IRouteOptionsCreator fileRouteOptionsCreator,
IRateLimitOptionsCreator rateLimitOptionsCreator,
- IRegionCreator regionCreator,
+ ICacheOptionsCreator cacheOptionsCreator,
IHttpHandlerOptionsCreator httpHandlerOptionsCreator,
IHeaderFindAndReplaceCreator headerFAndRCreator,
IDownstreamAddressesCreator downstreamAddressesCreator,
ILoadBalancerOptionsCreator loadBalancerOptionsCreator,
IRouteKeyCreator routeKeyCreator,
ISecurityOptionsCreator securityOptionsCreator,
- IVersionCreator versionCreator
- )
+ IVersionCreator versionCreator)
{
_routeKeyCreator = routeKeyCreator;
_loadBalancerOptionsCreator = loadBalancerOptionsCreator;
_downstreamAddressesCreator = downstreamAddressesCreator;
_headerFAndRCreator = headerFAndRCreator;
- _regionCreator = regionCreator;
+ _cacheOptionsCreator = cacheOptionsCreator;
_rateLimitOptionsCreator = rateLimitOptionsCreator;
_requestIdKeyCreator = requestIdKeyCreator;
_upstreamTemplatePatternCreator = upstreamTemplatePatternCreator;
@@ -93,8 +91,6 @@ private DownstreamRoute SetUpDownstreamRoute(FileRoute fileRoute, FileGlobalConf
var rateLimitOption = _rateLimitOptionsCreator.Create(fileRoute.RateLimitOptions, globalConfiguration);
- var region = _regionCreator.Create(fileRoute);
-
var httpHandlerOptions = _httpHandlerOptionsCreator.Create(fileRoute.HttpHandlerOptions);
var hAndRs = _headerFAndRCreator.Create(fileRoute);
@@ -107,6 +103,8 @@ private DownstreamRoute SetUpDownstreamRoute(FileRoute fileRoute, FileGlobalConf
var downstreamHttpVersion = _versionCreator.Create(fileRoute.DownstreamHttpVersion);
+ var cacheOptions = _cacheOptionsCreator.Create(fileRoute.FileCacheOptions, globalConfiguration, fileRoute.UpstreamPathTemplate, fileRoute.UpstreamHttpMethod);
+
var route = new DownstreamRouteBuilder()
.WithKey(fileRoute.Key)
.WithDownstreamPathTemplate(fileRoute.DownstreamPathTemplate)
@@ -122,7 +120,7 @@ private DownstreamRoute SetUpDownstreamRoute(FileRoute fileRoute, FileGlobalConf
.WithClaimsToDownstreamPath(claimsToDownstreamPath)
.WithRequestIdKey(requestIdKey)
.WithIsCached(fileRouteOptions.IsCached)
- .WithCacheOptions(new CacheOptions(fileRoute.FileCacheOptions.TtlSeconds, region, fileRoute.FileCacheOptions.Header))
+ .WithCacheOptions(cacheOptions)
.WithDownstreamScheme(fileRoute.DownstreamScheme)
.WithLoadBalancerOptions(lbOptions)
.WithDownstreamAddresses(downstreamAddresses)
diff --git a/src/Ocelot/Configuration/File/FileAuthenticationOptions.cs b/src/Ocelot/Configuration/File/FileAuthenticationOptions.cs
index 24d9b787de..0dd93a9c61 100644
--- a/src/Ocelot/Configuration/File/FileAuthenticationOptions.cs
+++ b/src/Ocelot/Configuration/File/FileAuthenticationOptions.cs
@@ -4,13 +4,13 @@ public sealed class FileAuthenticationOptions
{
public FileAuthenticationOptions()
{
- AllowedScopes = [];
- AuthenticationProviderKeys = [];
+ AllowedScopes = new();
+ AuthenticationProviderKeys = Array.Empty();
}
public FileAuthenticationOptions(FileAuthenticationOptions from)
{
- AllowedScopes = [..from.AllowedScopes];
+ AllowedScopes = new(from.AllowedScopes);
AuthenticationProviderKey = from.AuthenticationProviderKey;
AuthenticationProviderKeys = from.AuthenticationProviderKeys;
}
diff --git a/src/Ocelot/Configuration/File/FileCacheOptions.cs b/src/Ocelot/Configuration/File/FileCacheOptions.cs
index a1b1deed5b..42b793390e 100644
--- a/src/Ocelot/Configuration/File/FileCacheOptions.cs
+++ b/src/Ocelot/Configuration/File/FileCacheOptions.cs
@@ -1,21 +1,26 @@
-namespace Ocelot.Configuration.File
-{
- public class FileCacheOptions
- {
- public FileCacheOptions()
- {
- Region = string.Empty;
- TtlSeconds = 0;
- }
+namespace Ocelot.Configuration.File;
- public FileCacheOptions(FileCacheOptions from)
- {
- Region = from.Region;
- TtlSeconds = from.TtlSeconds;
- }
+public class FileCacheOptions
+{
+ public FileCacheOptions() { }
- public int TtlSeconds { get; set; }
- public string Region { get; set; }
- public string Header { get; set; }
- }
+ public FileCacheOptions(FileCacheOptions from)
+ {
+ Region = from.Region;
+ TtlSeconds = from.TtlSeconds;
+ Header = from.Header;
+ EnableContentHashing = from.EnableContentHashing;
+ }
+
+ /// Using where T is to have as default value and allowing global configuration usage.
+ /// If then use global configuration with 0 by default.
+ /// The time to live seconds, with 0 by default.
+ public int? TtlSeconds { get; set; }
+ public string Region { get; set; }
+ public string Header { get; set; }
+
+ /// Using where T is to have as default value and allowing global configuration usage.
+ /// If then use global configuration with by default.
+ /// if content hashing is enabled; otherwise, .
+ public bool? EnableContentHashing { get; set; }
}
diff --git a/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs b/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs
index 6692ba046b..04497cca18 100644
--- a/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs
+++ b/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs
@@ -9,6 +9,7 @@ public FileGlobalConfiguration()
LoadBalancerOptions = new FileLoadBalancerOptions();
QoSOptions = new FileQoSOptions();
HttpHandlerOptions = new FileHttpHandlerOptions();
+ CacheOptions = new FileCacheOptions();
}
public string RequestIdKey { get; set; }
@@ -28,5 +29,7 @@ public FileGlobalConfiguration()
public FileHttpHandlerOptions HttpHandlerOptions { get; set; }
public string DownstreamHttpVersion { get; set; }
+
+ public FileCacheOptions CacheOptions { get; set; }
}
}
diff --git a/src/Ocelot/Configuration/File/FileRoute.cs b/src/Ocelot/Configuration/File/FileRoute.cs
index 5823113ad9..ea076e0223 100644
--- a/src/Ocelot/Configuration/File/FileRoute.cs
+++ b/src/Ocelot/Configuration/File/FileRoute.cs
@@ -41,8 +41,8 @@ public FileRoute(FileRoute from)
public string DownstreamHttpMethod { get; set; }
public string DownstreamHttpVersion { get; set; }
public string DownstreamPathTemplate { get; set; }
- public string DownstreamScheme { get; set; }
- public FileCacheOptions FileCacheOptions { get; set; }
+ public string DownstreamScheme { get; set; }
+ public FileCacheOptions FileCacheOptions { get; set; }
public FileHttpHandlerOptions HttpHandlerOptions { get; set; }
public string Key { get; set; }
public FileLoadBalancerOptions LoadBalancerOptions { get; set; }
diff --git a/src/Ocelot/Configuration/QoSOptions.cs b/src/Ocelot/Configuration/QoSOptions.cs
index 915f07e2a0..e1897bc580 100644
--- a/src/Ocelot/Configuration/QoSOptions.cs
+++ b/src/Ocelot/Configuration/QoSOptions.cs
@@ -32,10 +32,41 @@ public QoSOptions(
TimeoutValue = timeoutValue;
}
+ ///
+ /// How long the circuit should stay open before resetting in milliseconds.
+ ///
+ ///
+ /// If using Polly version 8 or above, this value must be 500 (0.5 sec) or greater.
+ ///
+ ///
+ /// An value (milliseconds).
+ ///
public int DurationOfBreak { get; }
+
+ ///
+ /// How many times a circuit can fail before being set to open.
+ ///
+ ///
+ /// If using Polly version 8 or above, this value must be 2 or greater.
+ ///
+ ///
+ /// An value (no of exceptions).
+ ///
public int ExceptionsAllowedBeforeBreaking { get; }
+
public string Key { get; }
+
+ ///
+ /// Value for TimeoutStrategy in milliseconds.
+ ///
+ ///
+ /// If using Polly version 8 or above, this value must be 1000 (1 sec) or greater.
+ ///
+ ///
+ /// An value (milliseconds).
+ ///
public int TimeoutValue { get; }
+
public bool UseQos => ExceptionsAllowedBeforeBreaking > 0 || TimeoutValue > 0;
}
}
diff --git a/src/Ocelot/Configuration/Repository/DiskFileConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/DiskFileConfigurationRepository.cs
index 18fe36ee51..988292b94e 100644
--- a/src/Ocelot/Configuration/Repository/DiskFileConfigurationRepository.cs
+++ b/src/Ocelot/Configuration/Repository/DiskFileConfigurationRepository.cs
@@ -2,37 +2,53 @@
using Newtonsoft.Json;
using Ocelot.Configuration.ChangeTracking;
using Ocelot.Configuration.File;
+using Ocelot.DependencyInjection;
using Ocelot.Responses;
+using FileSys = System.IO.File;
namespace Ocelot.Configuration.Repository
{
public class DiskFileConfigurationRepository : IFileConfigurationRepository
{
+ private readonly IWebHostEnvironment _hostingEnvironment;
private readonly IOcelotConfigurationChangeTokenSource _changeTokenSource;
- private readonly string _environmentFilePath;
- private readonly string _ocelotFilePath;
- private static readonly object _lock = new();
-
- ///
- /// Main prefix for Ocelot configuration JSON-files.
- ///
- public const string ConfigurationFileName = "ocelot";
+ private FileInfo _ocelotFile;
+ private FileInfo _environmentFile;
+ private readonly object _lock = new();
public DiskFileConfigurationRepository(IWebHostEnvironment hosting, IOcelotConfigurationChangeTokenSource changeTokenSource)
{
+ _hostingEnvironment = hostingEnvironment;
_changeTokenSource = changeTokenSource;
_environmentFilePath = $"{AppContext.BaseDirectory}{ConfigurationFileName}{(string.IsNullOrEmpty(hosting.EnvironmentName) ? string.Empty : ".")}{hosting.EnvironmentName}.json";
+ Initialize(AppContext.BaseDirectory);
+ }
+
+ public DiskFileConfigurationRepository(IWebHostEnvironment hostingEnvironment, IOcelotConfigurationChangeTokenSource changeTokenSource, string folder)
+ {
+ _hostingEnvironment = hostingEnvironment;
+ _changeTokenSource = changeTokenSource;
+ Initialize(folder);
+ }
- _ocelotFilePath = $"{AppContext.BaseDirectory}{ConfigurationFileName}.json";
+ private void Initialize(string folder)
+ {
+ folder ??= AppContext.BaseDirectory;
+ _ocelotFile = new FileInfo(Path.Combine(folder, ConfigurationBuilderExtensions.PrimaryConfigFile));
+ var envFile = !string.IsNullOrEmpty(_hostingEnvironment.EnvironmentName)
+ ? string.Format(ConfigurationBuilderExtensions.EnvironmentConfigFile, _hostingEnvironment.EnvironmentName)
+ : ConfigurationBuilderExtensions.PrimaryConfigFile;
+ _environmentFile = new FileInfo(Path.Combine(folder, envFile));
}
+ public Task> Get()
public Task GetAsync()
{
string jsonConfiguration;
lock (_lock)
{
- jsonConfiguration = System.IO.File.ReadAllText(_environmentFilePath);
+ jsonConfiguration = FileSys.ReadAllText(_environmentFile.FullName);
}
var fileConfiguration = JsonConvert.DeserializeObject(jsonConfiguration);
@@ -45,19 +61,19 @@ public Task SetAsync(FileConfiguration fileConfiguration)
lock (_lock)
{
- if (System.IO.File.Exists(_environmentFilePath))
+ if (_environmentFile.Exists)
{
- System.IO.File.Delete(_environmentFilePath);
+ _environmentFile.Delete();
}
- System.IO.File.WriteAllText(_environmentFilePath, jsonConfiguration);
+ FileSys.WriteAllText(_environmentFile.FullName, jsonConfiguration);
- if (System.IO.File.Exists(_ocelotFilePath))
+ if (_ocelotFile.Exists)
{
- System.IO.File.Delete(_ocelotFilePath);
+ _ocelotFile.Delete();
}
- System.IO.File.WriteAllText(_ocelotFilePath, jsonConfiguration);
+ FileSys.WriteAllText(_ocelotFile.FullName, jsonConfiguration);
}
_changeTokenSource.Activate();
diff --git a/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs b/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs
index 064a4e0c9a..7e74251e23 100644
--- a/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs
+++ b/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs
@@ -7,10 +7,13 @@
namespace Ocelot.Configuration.Validator
{
- public class FileConfigurationFluentValidator : AbstractValidator, IConfigurationValidator
+ ///
+ /// Validation of a objects.
+ ///
+ public partial class FileConfigurationFluentValidator : AbstractValidator, IConfigurationValidator
{
private const string Servicefabric = "servicefabric";
- private readonly List _serviceDiscoveryFinderDelegates;
+ private readonly List _serviceDiscoveryFinderDelegates;
public FileConfigurationFluentValidator(IServiceProvider provider, RouteFluentValidator routeFluentValidator, FileGlobalConfigurationFluentValidator fileGlobalConfigurationFluentValidator)
{
@@ -34,7 +37,10 @@ public FileConfigurationFluentValidator(IServiceProvider provider, RouteFluentVa
RuleForEach(configuration => configuration.Routes)
.Must((_, route) => IsPlaceholderNotDuplicatedIn(route.UpstreamPathTemplate))
- .WithMessage((_, route) => $"{nameof(route)} {route.UpstreamPathTemplate} has duplicated placeholder");
+ .WithMessage((_, route) => $"{nameof(route.UpstreamPathTemplate)} '{route.UpstreamPathTemplate}' has duplicated placeholder");
+ RuleForEach(configuration => configuration.Routes)
+ .Must((_, route) => IsPlaceholderNotDuplicatedIn(route.DownstreamPathTemplate))
+ .WithMessage((_, route) => $"{nameof(route.DownstreamPathTemplate)} '{route.DownstreamPathTemplate}' has duplicated placeholder");
RuleFor(configuration => configuration.GlobalConfiguration.ServiceDiscoveryProvider)
.Must(HaveServiceDiscoveryProviderRegistered)
@@ -93,15 +99,22 @@ private static bool AllRoutesForAggregateExist(FileAggregateRoute fileAggregateR
return routesForAggregate.Count() == fileAggregateRoute.RouteKeys.Count;
}
-
- private static bool IsPlaceholderNotDuplicatedIn(string upstreamPathTemplate)
+
+#if NET7_0_OR_GREATER
+ [GeneratedRegex(@"\{\w+\}", RegexOptions.IgnoreCase | RegexOptions.Singleline, "en-US")]
+ private static partial Regex PlaceholderRegex();
+#else
+ private static readonly Regex PlaceholderRegexVar = new(@"\{\w+\}", RegexOptions.IgnoreCase | RegexOptions.Singleline, TimeSpan.FromMilliseconds(1000));
+ private static Regex PlaceholderRegex() => PlaceholderRegexVar;
+#endif
+
+ private static bool IsPlaceholderNotDuplicatedIn(string pathTemplate)
{
- var regExPlaceholder = new Regex("{[^}]+}");
- var matches = regExPlaceholder.Matches(upstreamPathTemplate);
- var upstreamPathPlaceholders = matches.Select(m => m.Value);
- return upstreamPathPlaceholders.Count() == upstreamPathPlaceholders.Distinct().Count();
- }
-
+ var placeholders = PlaceholderRegex().Matches(pathTemplate)
+ .Select(m => m.Value).ToList();
+ return placeholders.Count == placeholders.Distinct().Count();
+ }
+
private static bool DoesNotContainRoutesWithSpecificRequestIdKeys(FileAggregateRoute fileAggregateRoute,
IEnumerable routes)
{
@@ -131,7 +144,7 @@ private static bool IsNotDuplicateIn(FileRoute route,
var duplicateSpecificVerbs = matchingRoutes.SelectMany(x => x.UpstreamHttpMethod).GroupBy(x => x.ToLower()).SelectMany(x => x.Skip(1)).Any();
- if (duplicateAllowAllVerbs || duplicateSpecificVerbs || (allowAllVerbs && specificVerbs))
+ if (duplicateAllowAllVerbs || duplicateSpecificVerbs || allowAllVerbs && specificVerbs)
{
return false;
}
@@ -155,6 +168,6 @@ private static bool IsNotDuplicateIn(FileAggregateRoute route, IEnumerable r.UpstreamPathTemplate == route.UpstreamPathTemplate & r.UpstreamHost == route.UpstreamHost);
return matchingRoutes.Count() <= 1;
- }
+ }
}
}
diff --git a/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs b/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs
index 3af7b58c7f..909b57d572 100644
--- a/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs
+++ b/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs
@@ -14,16 +14,14 @@ public static partial class ConfigurationBuilderExtensions
{
public const string PrimaryConfigFile = "ocelot.json";
public const string GlobalConfigFile = "ocelot.global.json";
+ public const string EnvironmentConfigFile = "ocelot.{0}.json";
#if NET7_0_OR_GREATER
[GeneratedRegex(@"^ocelot\.(.*?)\.json$", RegexOptions.IgnoreCase | RegexOptions.Singleline, "en-US")]
private static partial Regex SubConfigRegex();
#else
private static readonly Regex SubConfigRegexVar = new(@"^ocelot\.(.*?)\.json$", RegexOptions.IgnoreCase | RegexOptions.Singleline, TimeSpan.FromMilliseconds(1000));
- private static Regex SubConfigRegex()
- {
- return SubConfigRegexVar;
- }
+ private static Regex SubConfigRegex() => SubConfigRegexVar;
#endif
[Obsolete("Please set BaseUrl in ocelot.json GlobalConfiguration.BaseUrl")]
@@ -37,9 +35,7 @@ public static IConfigurationBuilder AddOcelotBaseUrl(this IConfigurationBuilder
},
};
- builder.Add(memorySource);
-
- return builder;
+ return builder.Add(memorySource);
}
///
@@ -49,9 +45,7 @@ public static IConfigurationBuilder AddOcelotBaseUrl(this IConfigurationBuilder
/// Web hosting environment object.
/// An object.
public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, IWebHostEnvironment env)
- {
- return builder.AddOcelot(".", env);
- }
+ => builder.AddOcelot(".", env);
///
/// Adds Ocelot configuration by environment, reading the required files from the specified folder.
@@ -61,36 +55,94 @@ public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder
/// Web hosting environment object.
/// An object.
public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, string folder, IWebHostEnvironment env)
+ => builder.AddOcelot(folder, env, MergeOcelotJson.ToFile);
+
+ ///
+ /// Adds Ocelot configuration by environment and merge option, reading the required files from the current default folder.
+ ///
+ /// Use optional arguments for injections and overridings.
+ /// Configuration builder to extend.
+ /// Web hosting environment object.
+ /// Option to merge files to.
+ /// Primary config file.
+ /// Global config file.
+ /// Environment config file.
+ /// The 2nd argument of the AddJsonFile.
+ /// The 3rd argument of the AddJsonFile.
+ /// An object.
+ public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, IWebHostEnvironment env, MergeOcelotJson mergeTo,
+ string primaryConfigFile = null, string globalConfigFile = null, string environmentConfigFile = null, bool? optional = null, bool? reloadOnChange = null) // optional injections
+ => builder.AddOcelot(".", env, mergeTo, primaryConfigFile, globalConfigFile, environmentConfigFile, optional, reloadOnChange);
+
+ ///
+ /// Adds Ocelot configuration by environment and merge option, reading the required files from the specified folder.
+ ///
+ /// Use optional arguments for injections and overridings.
+ /// Configuration builder to extend.
+ /// Folder to read files from.
+ /// Web hosting environment object.
+ /// Option to merge files to.
+ /// Primary config file.
+ /// Global config file.
+ /// Environment config file.
+ /// The 2nd argument of the AddJsonFile.
+ /// The 3rd argument of the AddJsonFile.
+ /// An object.
+ public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, string folder, IWebHostEnvironment env, MergeOcelotJson mergeTo,
+ string primaryConfigFile = null, string globalConfigFile = null, string environmentConfigFile = null, bool? optional = null, bool? reloadOnChange = null) // optional injections
{
- var excludeConfigName = env?.EnvironmentName != null ? $"ocelot.{env.EnvironmentName}.json" : string.Empty;
+ var json = GetMergedOcelotJson(folder, env, null, primaryConfigFile, globalConfigFile, environmentConfigFile);
+ return ApplyMergeOcelotJsonOption(builder, mergeTo, json, primaryConfigFile, optional, reloadOnChange);
+ }
- var reg = SubConfigRegex();
+ private static IConfigurationBuilder ApplyMergeOcelotJsonOption(IConfigurationBuilder builder, MergeOcelotJson mergeTo, string json,
+ string primaryConfigFile, bool? optional, bool? reloadOnChange)
+ {
+ return mergeTo == MergeOcelotJson.ToMemory ?
+ builder.AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(json))) :
+ AddOcelotJsonFile(builder, json, primaryConfigFile, optional, reloadOnChange);
+ }
+ private static string GetMergedOcelotJson(string folder, IWebHostEnvironment env,
+ FileConfiguration fileConfiguration = null, string primaryFile = null, string globalFile = null, string environmentFile = null)
+ {
+ var envName = string.IsNullOrEmpty(env?.EnvironmentName) ? "Development" : env.EnvironmentName;
+ environmentFile ??= string.Format(EnvironmentConfigFile, envName);
+ var reg = SubConfigRegex();
+ var environmentFileInfo = new FileInfo(environmentFile);
var files = new DirectoryInfo(folder)
.EnumerateFiles()
- .Where(fi => reg.IsMatch(fi.Name) && fi.Name != excludeConfigName)
+ .Where(fi => reg.IsMatch(fi.Name) &&
+ !fi.Name.Equals(environmentFileInfo.Name, StringComparison.OrdinalIgnoreCase) &&
+ !fi.FullName.Equals(environmentFileInfo.FullName, StringComparison.OrdinalIgnoreCase))
.ToArray();
- dynamic fileConfiguration = new JObject();
- fileConfiguration.GlobalConfiguration = new JObject();
- fileConfiguration.Aggregates = new JArray();
- fileConfiguration.Routes = new JArray();
-
+ dynamic fileConfiguration = new JObject();
+ fileConfiguration.GlobalConfiguration = new JObject();
+ fileConfiguration.Aggregates = new JArray();
+ fileConfiguration.Routes = new JArray();
+ primaryFile ??= PrimaryConfigFile;
+ globalFile ??= GlobalConfigFile;
+ var primaryFileInfo = new FileInfo(primaryFile);
+ var globalFileInfo = new FileInfo(globalFile);
foreach (var file in files)
{
- if (files.Length > 1 && file.Name.Equals(PrimaryConfigFile, StringComparison.OrdinalIgnoreCase))
+ if (files.Length > 1 &&
+ file.Name.Equals(primaryFileInfo.Name, StringComparison.OrdinalIgnoreCase) &&
+ file.FullName.Equals(primaryFileInfo.FullName, StringComparison.OrdinalIgnoreCase))
{
continue;
}
var lines = File.ReadAllText(file.FullName);
- dynamic config = JToken.Parse(lines);
- var isGlobal = file.Name.Equals(GlobalConfigFile, StringComparison.OrdinalIgnoreCase);
-
- MergeConfig(fileConfiguration, config, isGlobal);
+ dynamic config = JToken.Parse(lines);
+ var isGlobal = file.Name.Equals(globalFileInfo.Name, StringComparison.OrdinalIgnoreCase) &&
+ file.FullName.Equals(globalFileInfo.FullName, StringComparison.OrdinalIgnoreCase);
+
+ MergeConfig(fileConfiguration, config, isGlobal);
}
- return AddOcelot(builder, (JObject)fileConfiguration);
+ return (JObject)fileConfiguration.ToString();
}
public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, JObject fileConfiguration)
@@ -100,17 +152,59 @@ public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder
/// Adds Ocelot configuration by ready configuration object and writes JSON to the primary configuration file.
/// Finally, adds JSON file as configuration provider.
///
+ /// Use optional arguments for injections and overridings.
/// Configuration builder to extend.
/// File configuration to add as JSON provider.
+ /// Primary config file.
+ /// The 2nd argument of the AddJsonFile.
+ /// The 3rd argument of the AddJsonFile.
/// An object.
- public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, FileConfiguration fileConfiguration)
+ public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, FileConfiguration fileConfiguration,
+ string primaryConfigFile = null, bool? optional = null, bool? reloadOnChange = null) // optional injections
=> SerializeToFile(builder, fileConfiguration);
private static IConfigurationBuilder SerializeToFile(IConfigurationBuilder builder, object fileConfiguration)
{
- var json = JsonConvert.SerializeObject(fileConfiguration);
- File.WriteAllText(PrimaryConfigFile, json);
- return builder.AddJsonFile(PrimaryConfigFile, false, false);
+ var json = JsonConvert.SerializeObject(fileConfiguration, Formatting.Indented);
+ return AddOcelotJsonFile(builder, json, primaryConfigFile, optional, reloadOnChange);
+ }
+ ///
+ /// Adds Ocelot configuration by ready configuration object, environment and merge option, reading the required files from the current default folder.
+ ///
+ /// Configuration builder to extend.
+ /// File configuration to add as JSON provider.
+ /// Web hosting environment object.
+ /// Option to merge files to.
+ /// Primary config file.
+ /// Global config file.
+ /// Environment config file.
+ /// The 2nd argument of the AddJsonFile.
+ /// The 3rd argument of the AddJsonFile.
+ /// An object.
+ public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, FileConfiguration fileConfiguration, IWebHostEnvironment env, MergeOcelotJson mergeTo,
+ string primaryConfigFile = null, string globalConfigFile = null, string environmentConfigFile = null, bool? optional = null, bool? reloadOnChange = null) // optional injections
+ {
+ var json = GetMergedOcelotJson(".", env, fileConfiguration, primaryConfigFile, globalConfigFile, environmentConfigFile);
+ return ApplyMergeOcelotJsonOption(builder, mergeTo, json, primaryConfigFile, optional, reloadOnChange);
+ }
+ ///
+ /// Adds Ocelot primary configuration file (aka ocelot.json).
+ /// Writes JSON to the file.
+ /// Adds the file as a JSON configuration provider via the extension.
+ ///
+ /// Use optional arguments for injections and overridings.
+ /// The builder to extend.
+ /// JSON data of the Ocelot configuration.
+ /// Primary config file.
+ /// The 2nd argument of the AddJsonFile.
+ /// The 3rd argument of the AddJsonFile.
+ /// An object.
+ private static IConfigurationBuilder AddOcelotJsonFile(IConfigurationBuilder builder, string json,
+ string primaryFile = null, bool? optional = null, bool? reloadOnChange = null) // optional injections
+ {
+ var primary = primaryFile ?? PrimaryConfigFile;
+ File.WriteAllText(primary, json);
+ return builder?.AddJsonFile(primary, optional ?? false, reloadOnChange ?? false);
}
private static void MergeConfig(JToken destConfig, JToken srcConfig, bool isGlobal)
diff --git a/src/Ocelot/DependencyInjection/Features.cs b/src/Ocelot/DependencyInjection/Features.cs
new file mode 100644
index 0000000000..8910145afd
--- /dev/null
+++ b/src/Ocelot/DependencyInjection/Features.cs
@@ -0,0 +1,36 @@
+using Microsoft.Extensions.DependencyInjection;
+using Ocelot.Cache;
+using Ocelot.Configuration.Creator;
+using Ocelot.Configuration.File;
+using Ocelot.RateLimiting;
+
+namespace Ocelot.DependencyInjection;
+
+public static class Features
+{
+ ///
+ /// Ocelot feature: Rate Limiting.
+ ///
+ ///
+ /// Read The Docs: Rate Limiting.
+ ///
+ /// The services collection to add the feature to.
+ /// The same object.
+ public static IServiceCollection AddRateLimiting(this IServiceCollection services) => services
+ .AddSingleton()
+ .AddSingleton();
+
+ ///
+ /// Ocelot feature: Request Caching.
+ ///
+ ///
+ /// Read The Docs: Caching.
+ ///
+ /// The services collection to add the feature to.
+ /// The same object.
+ public static IServiceCollection AddOcelotCache(this IServiceCollection services) => services
+ .AddSingleton, DefaultMemoryCache>()
+ .AddSingleton