From 5da918f722bd506bc00ef6be819469f8ce5bae4e Mon Sep 17 00:00:00 2001 From: arudenko02 Date: Sat, 25 Jan 2025 15:25:05 +0300 Subject: [PATCH] feat grpc: a new system to configure middlewares A new system to configure middlewares in `.cpp` code. Add a `MiddlewaresPipeline` that build a DAG graph and sort middlewares to ensure the same order for the same set of middlewares commit_hash:2222e8b43378cdd5546dd48d9588f2edfd754c80 --- .mapping.json | 14 + grpc/functional_tests/basic_chaos/main.cpp | 16 +- .../basic_chaos/static_config.yaml | 14 +- .../basic_server/grpc_service.cpp | 6 +- .../basic_server/static_config.yaml | 7 +- grpc/functional_tests/metrics/src/main.cpp | 10 +- .../metrics/static_config.yaml | 13 +- .../middleware_server/src/main.cpp | 7 +- .../middleware_server/static_config.yaml | 21 +- .../functional_tests/health/service.cpp | 4 +- .../health/static_config.yaml | 5 +- .../userver/ugrpc/server/component_list.hpp | 44 ++ .../impl/middleware_pipeline_config.hpp | 46 ++ .../server/middlewares/baggage/component.hpp | 9 +- .../userver/ugrpc/server/middlewares/base.hpp | 17 +- .../congestion_control/component.hpp | 9 +- .../deadline_propagation/component.hpp | 9 +- .../middlewares/field_mask/component.hpp | 7 + .../ugrpc/server/middlewares/groups.hpp | 66 +++ .../headers_propagator/component.hpp | 7 + .../server/middlewares/log/component.hpp | 7 + .../ugrpc/server/middlewares/pipeline.hpp | 192 +++++++++ .../ugrpc/server/service_component_base.hpp | 4 +- grpc/src/ugrpc/server/component_list.cpp | 47 +++ .../impl/middleware_pipeline_config.cpp | 49 +++ .../ugrpc/server/impl/middlewares_graph.cpp | 173 ++++++++ .../ugrpc/server/impl/middlewares_graph.hpp | 48 +++ grpc/src/ugrpc/server/impl/parse_config.cpp | 25 +- .../ugrpc/server/impl/service_defaults.hpp | 1 - grpc/src/ugrpc/server/impl/topology_sort.cpp | 62 +++ grpc/src/ugrpc/server/impl/topology_sort.hpp | 20 + .../server/middlewares/baggage/component.cpp | 13 +- grpc/src/ugrpc/server/middlewares/base.cpp | 12 + .../congestion_control/component.cpp | 19 +- .../deadline_propagation/component.cpp | 23 +- .../middlewares/field_mask/component.cpp | 4 +- .../headers_propagator/component.cpp | 3 +- .../server/middlewares/log/component.cpp | 9 +- .../src/ugrpc/server/middlewares/pipeline.cpp | 128 ++++++ grpc/src/ugrpc/server/server_component.cpp | 6 - .../ugrpc/server/service_component_base.cpp | 36 +- grpc/tests/middleware_pipeline_test.cpp | 396 ++++++++++++++++++ grpc/tests/topology_test.cpp | 74 ++++ .../golden_path/grpc_reflection_service.cpp | 10 +- .../golden_path/static_config.yaml | 5 +- rabbitmq/include/userver/rabbitmq.hpp | 2 +- samples/grpc-generic-proxy/main.cpp | 12 +- samples/grpc-generic-proxy/static_config.yaml | 16 +- samples/grpc_middleware_service/src/main.cpp | 3 +- .../src/middlewares/server/component.cpp | 10 +- .../static_config.yaml | 15 +- samples/grpc_service/main.cpp | 2 + samples/grpc_service/static_config.yaml | 2 +- scripts/docs/en/index.md | 1 + scripts/docs/en/userver/grpc.md | 37 +- .../en/userver/grpc_server_middlewares.md | 108 +++++ 56 files changed, 1707 insertions(+), 198 deletions(-) create mode 100644 grpc/include/userver/ugrpc/server/component_list.hpp create mode 100644 grpc/include/userver/ugrpc/server/impl/middleware_pipeline_config.hpp create mode 100644 grpc/include/userver/ugrpc/server/middlewares/groups.hpp create mode 100644 grpc/include/userver/ugrpc/server/middlewares/pipeline.hpp create mode 100644 grpc/src/ugrpc/server/component_list.cpp create mode 100644 grpc/src/ugrpc/server/impl/middleware_pipeline_config.cpp create mode 100644 grpc/src/ugrpc/server/impl/middlewares_graph.cpp create mode 100644 grpc/src/ugrpc/server/impl/middlewares_graph.hpp create mode 100644 grpc/src/ugrpc/server/impl/topology_sort.cpp create mode 100644 grpc/src/ugrpc/server/impl/topology_sort.hpp create mode 100644 grpc/src/ugrpc/server/middlewares/pipeline.cpp create mode 100644 grpc/tests/middleware_pipeline_test.cpp create mode 100644 grpc/tests/topology_test.cpp create mode 100644 scripts/docs/en/userver/grpc_server_middlewares.md diff --git a/.mapping.json b/.mapping.json index 2ba44e5bd8c9..79090b3f6731 100644 --- a/.mapping.json +++ b/.mapping.json @@ -1954,6 +1954,7 @@ "grpc/include/userver/ugrpc/protobuf_visit.hpp":"taxi/uservices/userver/grpc/include/userver/ugrpc/protobuf_visit.hpp", "grpc/include/userver/ugrpc/server/call.hpp":"taxi/uservices/userver/grpc/include/userver/ugrpc/server/call.hpp", "grpc/include/userver/ugrpc/server/call_context.hpp":"taxi/uservices/userver/grpc/include/userver/ugrpc/server/call_context.hpp", + "grpc/include/userver/ugrpc/server/component_list.hpp":"taxi/uservices/userver/grpc/include/userver/ugrpc/server/component_list.hpp", "grpc/include/userver/ugrpc/server/exceptions.hpp":"taxi/uservices/userver/grpc/include/userver/ugrpc/server/exceptions.hpp", "grpc/include/userver/ugrpc/server/generic_service_base.hpp":"taxi/uservices/userver/grpc/include/userver/ugrpc/server/generic_service_base.hpp", "grpc/include/userver/ugrpc/server/impl/async_method_invocation.hpp":"taxi/uservices/userver/grpc/include/userver/ugrpc/server/impl/async_method_invocation.hpp", @@ -1967,6 +1968,7 @@ "grpc/include/userver/ugrpc/server/impl/completion_queue_pool.hpp":"taxi/uservices/userver/grpc/include/userver/ugrpc/server/impl/completion_queue_pool.hpp", "grpc/include/userver/ugrpc/server/impl/error_code.hpp":"taxi/uservices/userver/grpc/include/userver/ugrpc/server/impl/error_code.hpp", "grpc/include/userver/ugrpc/server/impl/exceptions.hpp":"taxi/uservices/userver/grpc/include/userver/ugrpc/server/impl/exceptions.hpp", + "grpc/include/userver/ugrpc/server/impl/middleware_pipeline_config.hpp":"taxi/uservices/userver/grpc/include/userver/ugrpc/server/impl/middleware_pipeline_config.hpp", "grpc/include/userver/ugrpc/server/impl/service_worker.hpp":"taxi/uservices/userver/grpc/include/userver/ugrpc/server/impl/service_worker.hpp", "grpc/include/userver/ugrpc/server/impl/service_worker_impl.hpp":"taxi/uservices/userver/grpc/include/userver/ugrpc/server/impl/service_worker_impl.hpp", "grpc/include/userver/ugrpc/server/metadata_utils.hpp":"taxi/uservices/userver/grpc/include/userver/ugrpc/server/metadata_utils.hpp", @@ -1976,8 +1978,10 @@ "grpc/include/userver/ugrpc/server/middlewares/deadline_propagation/component.hpp":"taxi/uservices/userver/grpc/include/userver/ugrpc/server/middlewares/deadline_propagation/component.hpp", "grpc/include/userver/ugrpc/server/middlewares/field_mask/component.hpp":"taxi/uservices/userver/grpc/include/userver/ugrpc/server/middlewares/field_mask/component.hpp", "grpc/include/userver/ugrpc/server/middlewares/fwd.hpp":"taxi/uservices/userver/grpc/include/userver/ugrpc/server/middlewares/fwd.hpp", + "grpc/include/userver/ugrpc/server/middlewares/groups.hpp":"taxi/uservices/userver/grpc/include/userver/ugrpc/server/middlewares/groups.hpp", "grpc/include/userver/ugrpc/server/middlewares/headers_propagator/component.hpp":"taxi/uservices/userver/grpc/include/userver/ugrpc/server/middlewares/headers_propagator/component.hpp", "grpc/include/userver/ugrpc/server/middlewares/log/component.hpp":"taxi/uservices/userver/grpc/include/userver/ugrpc/server/middlewares/log/component.hpp", + "grpc/include/userver/ugrpc/server/middlewares/pipeline.hpp":"taxi/uservices/userver/grpc/include/userver/ugrpc/server/middlewares/pipeline.hpp", "grpc/include/userver/ugrpc/server/result.hpp":"taxi/uservices/userver/grpc/include/userver/ugrpc/server/result.hpp", "grpc/include/userver/ugrpc/server/rpc.hpp":"taxi/uservices/userver/grpc/include/userver/ugrpc/server/rpc.hpp", "grpc/include/userver/ugrpc/server/server.hpp":"taxi/uservices/userver/grpc/include/userver/ugrpc/server/server.hpp", @@ -2059,6 +2063,7 @@ "grpc/src/ugrpc/protobuf_visit.cpp":"taxi/uservices/userver/grpc/src/ugrpc/protobuf_visit.cpp", "grpc/src/ugrpc/server/call.cpp":"taxi/uservices/userver/grpc/src/ugrpc/server/call.cpp", "grpc/src/ugrpc/server/call_context.cpp":"taxi/uservices/userver/grpc/src/ugrpc/server/call_context.cpp", + "grpc/src/ugrpc/server/component_list.cpp":"taxi/uservices/userver/grpc/src/ugrpc/server/component_list.cpp", "grpc/src/ugrpc/server/exceptions.cpp":"taxi/uservices/userver/grpc/src/ugrpc/server/exceptions.cpp", "grpc/src/ugrpc/server/generic_service_base.cpp":"taxi/uservices/userver/grpc/src/ugrpc/server/generic_service_base.cpp", "grpc/src/ugrpc/server/impl/async_method_invocation.cpp":"taxi/uservices/userver/grpc/src/ugrpc/server/impl/async_method_invocation.cpp", @@ -2069,6 +2074,9 @@ "grpc/src/ugrpc/server/impl/format_log_message.hpp":"taxi/uservices/userver/grpc/src/ugrpc/server/impl/format_log_message.hpp", "grpc/src/ugrpc/server/impl/generic_service_worker.cpp":"taxi/uservices/userver/grpc/src/ugrpc/server/impl/generic_service_worker.cpp", "grpc/src/ugrpc/server/impl/generic_service_worker.hpp":"taxi/uservices/userver/grpc/src/ugrpc/server/impl/generic_service_worker.hpp", + "grpc/src/ugrpc/server/impl/middleware_pipeline_config.cpp":"taxi/uservices/userver/grpc/src/ugrpc/server/impl/middleware_pipeline_config.cpp", + "grpc/src/ugrpc/server/impl/middlewares_graph.cpp":"taxi/uservices/userver/grpc/src/ugrpc/server/impl/middlewares_graph.cpp", + "grpc/src/ugrpc/server/impl/middlewares_graph.hpp":"taxi/uservices/userver/grpc/src/ugrpc/server/impl/middlewares_graph.hpp", "grpc/src/ugrpc/server/impl/parse_config.cpp":"taxi/uservices/userver/grpc/src/ugrpc/server/impl/parse_config.cpp", "grpc/src/ugrpc/server/impl/parse_config.hpp":"taxi/uservices/userver/grpc/src/ugrpc/server/impl/parse_config.hpp", "grpc/src/ugrpc/server/impl/server_configs.cpp":"taxi/uservices/userver/grpc/src/ugrpc/server/impl/server_configs.cpp", @@ -2076,6 +2084,8 @@ "grpc/src/ugrpc/server/impl/service_defaults.hpp":"taxi/uservices/userver/grpc/src/ugrpc/server/impl/service_defaults.hpp", "grpc/src/ugrpc/server/impl/service_worker.cpp":"taxi/uservices/userver/grpc/src/ugrpc/server/impl/service_worker.cpp", "grpc/src/ugrpc/server/impl/service_worker_impl.cpp":"taxi/uservices/userver/grpc/src/ugrpc/server/impl/service_worker_impl.cpp", + "grpc/src/ugrpc/server/impl/topology_sort.cpp":"taxi/uservices/userver/grpc/src/ugrpc/server/impl/topology_sort.cpp", + "grpc/src/ugrpc/server/impl/topology_sort.hpp":"taxi/uservices/userver/grpc/src/ugrpc/server/impl/topology_sort.hpp", "grpc/src/ugrpc/server/middlewares/baggage/component.cpp":"taxi/uservices/userver/grpc/src/ugrpc/server/middlewares/baggage/component.cpp", "grpc/src/ugrpc/server/middlewares/baggage/middleware.cpp":"taxi/uservices/userver/grpc/src/ugrpc/server/middlewares/baggage/middleware.cpp", "grpc/src/ugrpc/server/middlewares/baggage/middleware.hpp":"taxi/uservices/userver/grpc/src/ugrpc/server/middlewares/baggage/middleware.hpp", @@ -2095,6 +2105,7 @@ "grpc/src/ugrpc/server/middlewares/log/component.cpp":"taxi/uservices/userver/grpc/src/ugrpc/server/middlewares/log/component.cpp", "grpc/src/ugrpc/server/middlewares/log/middleware.cpp":"taxi/uservices/userver/grpc/src/ugrpc/server/middlewares/log/middleware.cpp", "grpc/src/ugrpc/server/middlewares/log/middleware.hpp":"taxi/uservices/userver/grpc/src/ugrpc/server/middlewares/log/middleware.hpp", + "grpc/src/ugrpc/server/middlewares/pipeline.cpp":"taxi/uservices/userver/grpc/src/ugrpc/server/middlewares/pipeline.cpp", "grpc/src/ugrpc/server/server.cpp":"taxi/uservices/userver/grpc/src/ugrpc/server/server.cpp", "grpc/src/ugrpc/server/server_component.cpp":"taxi/uservices/userver/grpc/src/ugrpc/server/server_component.cpp", "grpc/src/ugrpc/server/service_base.cpp":"taxi/uservices/userver/grpc/src/ugrpc/server/service_base.cpp", @@ -2120,6 +2131,7 @@ "grpc/tests/generic_client_test.cpp":"taxi/uservices/userver/grpc/tests/generic_client_test.cpp", "grpc/tests/generic_server_test.cpp":"taxi/uservices/userver/grpc/tests/generic_server_test.cpp", "grpc/tests/logging_test.cpp":"taxi/uservices/userver/grpc/tests/logging_test.cpp", + "grpc/tests/middleware_pipeline_test.cpp":"taxi/uservices/userver/grpc/tests/middleware_pipeline_test.cpp", "grpc/tests/middlewares_test.cpp":"taxi/uservices/userver/grpc/tests/middlewares_test.cpp", "grpc/tests/protobuf_collector_test.cpp":"taxi/uservices/userver/grpc/tests/protobuf_collector_test.cpp", "grpc/tests/protobuf_visit_test.cpp":"taxi/uservices/userver/grpc/tests/protobuf_visit_test.cpp", @@ -2133,6 +2145,7 @@ "grpc/tests/tests/service_multichannel.hpp":"taxi/uservices/userver/grpc/tests/tests/service_multichannel.hpp", "grpc/tests/tests/timed_out_service.hpp":"taxi/uservices/userver/grpc/tests/tests/timed_out_service.hpp", "grpc/tests/tests/unit_test_client_qos.hpp":"taxi/uservices/userver/grpc/tests/tests/unit_test_client_qos.hpp", + "grpc/tests/topology_test.cpp":"taxi/uservices/userver/grpc/tests/topology_test.cpp", "grpc/tests/tracing_test.cpp":"taxi/uservices/userver/grpc/tests/tracing_test.cpp", "grpc/tests/unimplemented_test.cpp":"taxi/uservices/userver/grpc/tests/unimplemented_test.cpp", "grpc/tests/wait_any_test.cpp":"taxi/uservices/userver/grpc/tests/wait_any_test.cpp", @@ -3502,6 +3515,7 @@ "scripts/docs/en/userver/framework_comparison.md":"taxi/uservices/userver/scripts/docs/en/userver/framework_comparison.md", "scripts/docs/en/userver/functional_testing.md":"taxi/uservices/userver/scripts/docs/en/userver/functional_testing.md", "scripts/docs/en/userver/grpc.md":"taxi/uservices/userver/scripts/docs/en/userver/grpc.md", + "scripts/docs/en/userver/grpc_server_middlewares.md":"taxi/uservices/userver/scripts/docs/en/userver/grpc_server_middlewares.md", "scripts/docs/en/userver/http_server.md":"taxi/uservices/userver/scripts/docs/en/userver/http_server.md", "scripts/docs/en/userver/http_server_middlewares.md":"taxi/uservices/userver/scripts/docs/en/userver/http_server_middlewares.md", "scripts/docs/en/userver/intro.md":"taxi/uservices/userver/scripts/docs/en/userver/intro.md", diff --git a/grpc/functional_tests/basic_chaos/main.cpp b/grpc/functional_tests/basic_chaos/main.cpp index 0c7439001b8c..9b97c11114b4 100644 --- a/grpc/functional_tests/basic_chaos/main.cpp +++ b/grpc/functional_tests/basic_chaos/main.cpp @@ -3,19 +3,13 @@ #include #include #include -#include #include #include #include #include #include #include -#include -#include -#include -#include -#include -#include +#include #include #include "handler.hpp" @@ -24,12 +18,7 @@ int main(int argc, char* argv[]) { const auto component_list = components::MinimalServerComponentList() .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() + .AppendComponentList(ugrpc::server::DefaultComponentList()) .Append() .Append() .Append() @@ -38,7 +27,6 @@ int main(int argc, char* argv[]) { .Append() .Append() .Append() - .Append() .Append() .Append() .Append("greeter-client-component") diff --git a/grpc/functional_tests/basic_chaos/static_config.yaml b/grpc/functional_tests/basic_chaos/static_config.yaml index 6d12087941aa..91b7cf13b38e 100644 --- a/grpc/functional_tests/basic_chaos/static_config.yaml +++ b/grpc/functional_tests/basic_chaos/static_config.yaml @@ -15,10 +15,6 @@ components_manager: port: 8091 service-defaults: task-processor: main-task-processor - middlewares: - - grpc-server-logging - - grpc-server-baggage - - grpc-server-congestion-control # /// [Sample grpc server logging middleware component config] grpc-server-logging: log-level: info @@ -45,6 +41,16 @@ components_manager: grpc-server-field-mask: metadata-field-name: field-mask # /// [Sample grpc server field-mask middleware component config] + + grpc-server-middlewares-pipeline: + middlewares: + grpc-server-headers-propagator: + enabled: false + grpc-server-field-mask: + enabled: false + grpc-server-deadline-propagation: + enabled: false + greeter-service: greeting-prefix: Hello diff --git a/grpc/functional_tests/basic_server/grpc_service.cpp b/grpc/functional_tests/basic_server/grpc_service.cpp index 44f987160ed1..902a616b93ef 100644 --- a/grpc/functional_tests/basic_server/grpc_service.cpp +++ b/grpc/functional_tests/basic_server/grpc_service.cpp @@ -16,8 +16,7 @@ #include #include -#include -#include +#include #include #include @@ -79,13 +78,12 @@ yaml_config::Schema GreeterServiceComponent::GetStaticConfigSchema() { int main(int argc, char* argv[]) { const auto component_list = components::MinimalServerComponentList() .Append() + .AppendComponentList(ugrpc::server::DefaultComponentList()) .Append() .Append() - .Append() .Append() .Append() .Append() - .Append() .Append() .Append(); return utils::DaemonMain(argc, argv, component_list); diff --git a/grpc/functional_tests/basic_server/static_config.yaml b/grpc/functional_tests/basic_server/static_config.yaml index d4a9e238b745..6097636e7468 100644 --- a/grpc/functional_tests/basic_server/static_config.yaml +++ b/grpc/functional_tests/basic_server/static_config.yaml @@ -34,8 +34,11 @@ components_manager: greeter-service: echo-url: 'mockserver/v1/translations' # Some other microservice listens on this URL task-processor: main-task-processor + disable-all-pipeline-middlewares: true middlewares: - - grpc-server-headers-propagator + grpc-server-headers-propagator: + + grpc-server-middlewares-pipeline: http-client: fs-task-processor: main-task-processor @@ -53,6 +56,8 @@ components_manager: http-client-plugin-headers-propagator: {} + congestion-control: + server: listener: port: 8092 diff --git a/grpc/functional_tests/metrics/src/main.cpp b/grpc/functional_tests/metrics/src/main.cpp index f9725601aac3..311a1aade975 100644 --- a/grpc/functional_tests/metrics/src/main.cpp +++ b/grpc/functional_tests/metrics/src/main.cpp @@ -11,10 +11,7 @@ #include #include #include -#include -#include -#include -#include +#include #include "greeter_client.hpp" #include "greeter_service.hpp" @@ -22,11 +19,8 @@ int main(int argc, const char* const argv[]) { const auto component_list = components::MinimalServerComponentList() .Append() + .AppendComponentList(ugrpc::server::DefaultComponentList()) .Append() - .Append() - .Append() - .Append() - .Append() .Append() .Append() .Append() diff --git a/grpc/functional_tests/metrics/static_config.yaml b/grpc/functional_tests/metrics/static_config.yaml index 042084c0f192..d246dab1e76c 100644 --- a/grpc/functional_tests/metrics/static_config.yaml +++ b/grpc/functional_tests/metrics/static_config.yaml @@ -15,21 +15,26 @@ components_manager: method: GET task_processor: main-task-processor + # gRPC server and service grpc-server: port: 8091 + congestion-control: + grpc-server-logging: msg-size-log-limit: 10 - grpc-server-deadline-propagation: - grpc-server-baggage: + grpc-server-middlewares-pipeline: greeter-service: task-processor: main-task-processor + disable-all-pipeline-middlewares: true middlewares: - - grpc-server-logging - - grpc-server-baggage + grpc-server-logging: + enabled: true + grpc-server-baggage: + enabled: true # gRPC client grpc-client-common: diff --git a/grpc/functional_tests/middleware_server/src/main.cpp b/grpc/functional_tests/middleware_server/src/main.cpp index 1e1ec03b8455..309de664f966 100644 --- a/grpc/functional_tests/middleware_server/src/main.cpp +++ b/grpc/functional_tests/middleware_server/src/main.cpp @@ -1,4 +1,3 @@ - #include #include @@ -6,8 +5,7 @@ #include #include #include -#include -#include +#include #include #include "my_middleware.hpp" @@ -16,10 +14,9 @@ int main(int argc, const char* const argv[]) { const auto component_list = components::MinimalServerComponentList() + .AppendComponentList(ugrpc::server::DefaultComponentList()) .Append() .Append() - .Append() - .Append() .Append() .Append() .Append(); diff --git a/grpc/functional_tests/middleware_server/static_config.yaml b/grpc/functional_tests/middleware_server/static_config.yaml index a97056e97253..7d3369cfd841 100644 --- a/grpc/functional_tests/middleware_server/static_config.yaml +++ b/grpc/functional_tests/middleware_server/static_config.yaml @@ -15,6 +15,7 @@ components_manager: method: GET task_processor: main-task-processor + # gRPC server and service grpc-server: port: 8091 @@ -22,12 +23,26 @@ components_manager: grpc-server-logging: msg-size-log-limit: 10 + congestion-control: + + grpc-server-middlewares-pipeline: + middlewares: + my-middleware-server: + enabled: true + my-second-middleware-server: + enabled: true + greeter-service: task-processor: main-task-processor + disable-all-pipeline-middlewares: true middlewares: - - grpc-server-logging - - my-middleware-server - - my-second-middleware-server + grpc-server-logging: + enabled: true + my-middleware-server: + enabled: true + my-second-middleware-server: + enabled: true + # http server server: diff --git a/grpc/handlers/functional_tests/health/service.cpp b/grpc/handlers/functional_tests/health/service.cpp index e73a2c955369..f339c9d2dffe 100644 --- a/grpc/handlers/functional_tests/health/service.cpp +++ b/grpc/handlers/functional_tests/health/service.cpp @@ -14,13 +14,13 @@ #include #include +#include #include -#include #include int main(int argc, char* argv[]) { const auto component_list = components::MinimalServerComponentList() - .Append() + .AppendComponentList(ugrpc::server::DefaultComponentList()) .Append(); return utils::DaemonMain(argc, argv, component_list); } diff --git a/grpc/handlers/functional_tests/health/static_config.yaml b/grpc/handlers/functional_tests/health/static_config.yaml index 5676f48c803c..a0be2e85ff25 100644 --- a/grpc/handlers/functional_tests/health/static_config.yaml +++ b/grpc/handlers/functional_tests/health/static_config.yaml @@ -10,6 +10,10 @@ components_manager: level: debug overflow_behavior: discard + congestion-control: + + grpc-server-middlewares-pipeline: + grpc-server: port: 8091 @@ -20,7 +24,6 @@ components_manager: grpc-health: task-processor: main-task-processor - middlewares: [] default_task_processor: main-task-processor diff --git a/grpc/include/userver/ugrpc/server/component_list.hpp b/grpc/include/userver/ugrpc/server/component_list.hpp new file mode 100644 index 000000000000..fad32a05af44 --- /dev/null +++ b/grpc/include/userver/ugrpc/server/component_list.hpp @@ -0,0 +1,44 @@ +#pragma once + +/// @file userver/ugrpc/server/component_list.hpp +/// @brief Two common component lists for grpc-server (default and minimal) + +#include + +USERVER_NAMESPACE_BEGIN + +namespace ugrpc::server { + +/// @ingroup userver_components +/// +/// @brief Returns a list of components to do a default grpc server configuration +/// +/// The list contains: +/// * congestion_control::Component +/// * ugrpc::server::ServerComponent +/// * ugrpc::server::MiddlewarePipelineComponent +/// * ugrpc::server::middlewares::baggage::Component +/// * ugrpc::server::middlewares::congestion_control::Component +/// * ugrpc::server::middlewares::deadline_propagation::Component +/// * ugrpc::server::middlewares::field_mask::Component +/// * ugrpc::server::middlewares::headers_propagator::Component +/// * ugrpc::server::middlewares::log::Component +components::ComponentList DefaultComponentList(); + +/// @ingroup userver_components +/// +/// @brief Returns a list of components to do a minimal grpc server configuration +/// +/// The list contains: +/// * congestion_control::Component +/// * ugrpc::server::ServerComponent +/// * ugrpc::server::MiddlewarePipelineComponent +/// * ugrpc::server::middlewares::baggage::Component +/// * ugrpc::server::middlewares::congestion_control::Component +/// * ugrpc::server::middlewares::deadline_propagation::Component +/// * ugrpc::server::middlewares::log::Component +components::ComponentList MinimalComponentList(); + +} // namespace ugrpc::server + +USERVER_NAMESPACE_END diff --git a/grpc/include/userver/ugrpc/server/impl/middleware_pipeline_config.hpp b/grpc/include/userver/ugrpc/server/impl/middleware_pipeline_config.hpp new file mode 100644 index 000000000000..14210ad7adef --- /dev/null +++ b/grpc/include/userver/ugrpc/server/impl/middleware_pipeline_config.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include + +#include + +USERVER_NAMESPACE_BEGIN + +namespace ugrpc::server::impl { + +struct MiddlewareConfig final { + bool enabled{true}; +}; + +MiddlewareConfig Parse(const yaml_config::YamlConfig& value, formats::parse::To); + +struct MiddlewarePipelineConfig final { + std::unordered_map middlewares{}; +}; + +MiddlewarePipelineConfig Parse(const yaml_config::YamlConfig& value, formats::parse::To); + +const std::unordered_map& UserverMiddlewares(); + +struct MiddlewareServiceConfig final { + std::unordered_map service_middlewares{}; + bool disable_user_pipeline_middlewares{false}; + bool disable_all_pipeline_middlewares{false}; +}; + +MiddlewareServiceConfig Parse(const yaml_config::YamlConfig& value, formats::parse::To); + +struct MiddlewareEnabled final { + std::string name{}; + bool enabled{true}; +}; + +bool operator==(const MiddlewareEnabled& l, const MiddlewareEnabled& r); + +using MiddlewareOrderedList = std::vector; + +} // namespace ugrpc::server::impl + +USERVER_NAMESPACE_END diff --git a/grpc/include/userver/ugrpc/server/middlewares/baggage/component.hpp b/grpc/include/userver/ugrpc/server/middlewares/baggage/component.hpp index 3d03fb6bb5a4..fc4f2395a429 100644 --- a/grpc/include/userver/ugrpc/server/middlewares/baggage/component.hpp +++ b/grpc/include/userver/ugrpc/server/middlewares/baggage/component.hpp @@ -34,10 +34,15 @@ class Component final : public MiddlewareComponentBase { Component(const components::ComponentConfig& config, const components::ComponentContext& context); std::shared_ptr GetMiddleware() override; - - static yaml_config::Schema GetStaticConfigSchema(); }; } // namespace ugrpc::server::middlewares::baggage +template <> +inline constexpr bool components::kHasValidate = true; + +template <> +inline constexpr auto components::kConfigFileMode = + ConfigFileMode::kNotRequired; + USERVER_NAMESPACE_END diff --git a/grpc/include/userver/ugrpc/server/middlewares/base.hpp b/grpc/include/userver/ugrpc/server/middlewares/base.hpp index de71a764150a..f159833a4b5d 100644 --- a/grpc/include/userver/ugrpc/server/middlewares/base.hpp +++ b/grpc/include/userver/ugrpc/server/middlewares/base.hpp @@ -13,6 +13,7 @@ #include #include +#include USERVER_NAMESPACE_BEGIN @@ -83,11 +84,23 @@ class MiddlewareBase { /// /// @brief Base class for middleware component class MiddlewareComponentBase : public components::ComponentBase { - using components::ComponentBase::ComponentBase; - public: + MiddlewareComponentBase( + const components::ComponentConfig&, + const components::ComponentContext&, + MiddlewareDependencyBuilder&& builder = MiddlewareDependencyBuilder().InGroup() + ); + /// @brief Returns a middleware according to the component's settings virtual std::shared_ptr GetMiddleware() = 0; + + /// @cond + /// Only for internal use. + const impl::MiddlewareDependency& GetMiddlewareDependency() const; + /// @endcond + +private: + impl::MiddlewareDependency dependency_; }; } // namespace ugrpc::server diff --git a/grpc/include/userver/ugrpc/server/middlewares/congestion_control/component.hpp b/grpc/include/userver/ugrpc/server/middlewares/congestion_control/component.hpp index 1f4f116143d4..ec3709b7f365 100644 --- a/grpc/include/userver/ugrpc/server/middlewares/congestion_control/component.hpp +++ b/grpc/include/userver/ugrpc/server/middlewares/congestion_control/component.hpp @@ -38,12 +38,17 @@ class Component final : public MiddlewareComponentBase { std::shared_ptr GetMiddleware() override; - static yaml_config::Schema GetStaticConfigSchema(); - private: std::shared_ptr middleware_; }; } // namespace ugrpc::server::middlewares::congestion_control +template <> +inline constexpr bool components::kHasValidate = true; + +template <> +inline constexpr auto components::kConfigFileMode = + ConfigFileMode::kNotRequired; + USERVER_NAMESPACE_END diff --git a/grpc/include/userver/ugrpc/server/middlewares/deadline_propagation/component.hpp b/grpc/include/userver/ugrpc/server/middlewares/deadline_propagation/component.hpp index e0fb3a030439..db51c40af4ae 100644 --- a/grpc/include/userver/ugrpc/server/middlewares/deadline_propagation/component.hpp +++ b/grpc/include/userver/ugrpc/server/middlewares/deadline_propagation/component.hpp @@ -36,10 +36,15 @@ class Component final : public MiddlewareComponentBase { Component(const components::ComponentConfig& config, const components::ComponentContext& context); std::shared_ptr GetMiddleware() override; - - static yaml_config::Schema GetStaticConfigSchema(); }; } // namespace ugrpc::server::middlewares::deadline_propagation +template <> +inline constexpr bool components::kHasValidate = true; + +template <> +inline constexpr auto components::kConfigFileMode = + ConfigFileMode::kNotRequired; + USERVER_NAMESPACE_END diff --git a/grpc/include/userver/ugrpc/server/middlewares/field_mask/component.hpp b/grpc/include/userver/ugrpc/server/middlewares/field_mask/component.hpp index 2b298ce36930..95b747455384 100644 --- a/grpc/include/userver/ugrpc/server/middlewares/field_mask/component.hpp +++ b/grpc/include/userver/ugrpc/server/middlewares/field_mask/component.hpp @@ -66,4 +66,11 @@ class Component final : public MiddlewareComponentBase { } // namespace ugrpc::server::middlewares::field_mask +template <> +inline constexpr bool components::kHasValidate = true; + +template <> +inline constexpr auto components::kConfigFileMode = + ConfigFileMode::kNotRequired; + USERVER_NAMESPACE_END diff --git a/grpc/include/userver/ugrpc/server/middlewares/groups.hpp b/grpc/include/userver/ugrpc/server/middlewares/groups.hpp new file mode 100644 index 000000000000..a1bb7eb828c6 --- /dev/null +++ b/grpc/include/userver/ugrpc/server/middlewares/groups.hpp @@ -0,0 +1,66 @@ +#pragma once + +/// @file userver/ugrpc/server/middlewares/groups.hpp +/// @brief +/// There are groups of middlewares to build a pipeline. +/// @see @ref scripts/docs/en/userver/grpc_server_middlewares.md + +#include + +USERVER_NAMESPACE_BEGIN + +/// Server middlewares groups for middlewares pipeline. +/// @see @ref scripts/docs/en/userver/grpc_server_middlewares.md +namespace ugrpc::server::groups { + +/// @brief The first group in the pipeline. +struct PreCore final { + static constexpr std::string_view kName = "pre-core"; + static inline const auto dependency = MiddlewareDependencyBuilder(); +}; + +/// @brief The Group to work wih logging. Is located after PreCore. +/// +/// @details There are: +/// ugrpc::server::middlewares::log::Component. +struct Logging final { + static constexpr std::string_view kName = "logging"; + static inline const auto dependency = MiddlewareDependencyBuilder().After(); +}; + +/// @brief The Group for authentication middlewares. Is located after `Logging`. +struct Auth final { + static constexpr std::string_view kName = "auth"; + static inline const auto dependency = MiddlewareDependencyBuilder().After(); +}; + +/// @brief The core group of middlewares. Is located after `Auth`. +/// +/// @details There are: +/// * ugrpc::server::middlewares::congestion_control::Component +/// * ugrpc::server::middlewares::deadline_propagation::Component +struct Core final { + static constexpr std::string_view kName = "core"; + static inline const auto dependency = MiddlewareDependencyBuilder().After(); +}; + +/// @brief The group is located after `Core`. +struct PostCore final { + static constexpr std::string_view kName = "post-core"; + static inline const auto dependency = MiddlewareDependencyBuilder().After(); +}; + +/// @brief The group for user middlewares - the last group in pipeline. It group used by default. +/// +/// @details There are: +/// * ugrpc::server::middlewares::baggage::Component +/// * ugrpc::server::middlewares::field_mask::Component +/// * ugrpc::server::middlewares::headers_propagator::Component +struct User final { + static constexpr std::string_view kName = "user"; + static inline const auto dependency = MiddlewareDependencyBuilder().After(); +}; + +} // namespace ugrpc::server::groups + +USERVER_NAMESPACE_END diff --git a/grpc/include/userver/ugrpc/server/middlewares/headers_propagator/component.hpp b/grpc/include/userver/ugrpc/server/middlewares/headers_propagator/component.hpp index de6fdefd3e7b..81f5b8584161 100644 --- a/grpc/include/userver/ugrpc/server/middlewares/headers_propagator/component.hpp +++ b/grpc/include/userver/ugrpc/server/middlewares/headers_propagator/component.hpp @@ -43,4 +43,11 @@ class Component final : public MiddlewareComponentBase { } // namespace ugrpc::server::middlewares::headers_propagator +template <> +inline constexpr bool components::kHasValidate = true; + +template <> +inline constexpr auto components::kConfigFileMode = + ConfigFileMode::kNotRequired; + USERVER_NAMESPACE_END diff --git a/grpc/include/userver/ugrpc/server/middlewares/log/component.hpp b/grpc/include/userver/ugrpc/server/middlewares/log/component.hpp index 47bd70403e14..f59d08f55fb9 100644 --- a/grpc/include/userver/ugrpc/server/middlewares/log/component.hpp +++ b/grpc/include/userver/ugrpc/server/middlewares/log/component.hpp @@ -62,4 +62,11 @@ class Component final : public MiddlewareComponentBase { } // namespace ugrpc::server::middlewares::log +template <> +inline constexpr bool components::kHasValidate = true; + +template <> +inline constexpr auto components::kConfigFileMode = + ConfigFileMode::kNotRequired; + USERVER_NAMESPACE_END diff --git a/grpc/include/userver/ugrpc/server/middlewares/pipeline.hpp b/grpc/include/userver/ugrpc/server/middlewares/pipeline.hpp new file mode 100644 index 000000000000..e15b79582619 --- /dev/null +++ b/grpc/include/userver/ugrpc/server/middlewares/pipeline.hpp @@ -0,0 +1,192 @@ +#pragma once + +/// @file userver/ugrpc/server/pipeline.hpp +/// @brief Lists all available middlewares and builds their order of execution. + +#include + +#include +#include +#include +#include + +#include +#include +#include + +USERVER_NAMESPACE_BEGIN + +namespace ugrpc::server { + +/// @brief The dependency type between middlewares. +/// +/// Iff dependency type from 'X' to 'Y' is `kStrong` and 'Y' is disabled, userver will failure when start, +/// otherwise (in `kWeak`) we ignore this dependency. +enum class DependencyType { + kStrong = 0, + kWeak = 1, +}; + +namespace impl { + +struct Connect final { + std::string node_name{}; + DependencyType type{DependencyType::kStrong}; +}; + +struct MiddlewareDependency final { + std::string middleware_name{}; + std::vector befores{}; + std::vector afters{}; + bool enabled{true}; + std::optional group{}; +}; + +using Dependencies = std::unordered_map; + +class MiddlewarePipeline final { +public: + explicit MiddlewarePipeline(Dependencies&& deps); + + std::vector GetPerServiceMiddlewares(const MiddlewareServiceConfig& config) const; + + const MiddlewareOrderedList& GetOrderedList() const { return pipeline_; } + +private: + Dependencies deps_{}; + MiddlewareOrderedList pipeline_{}; +}; + +template +std::string BeginOfGroup() { + return std::string{Group::kName} + "#begin"; +} + +template +std::string EndOfGroup() { + return std::string{Group::kName} + "#end"; +} + +} // namespace impl + +/// @brief Component to create middlewares pipeline. +/// +/// Your must register your grpc-server middleware in this component. +/// Use `MiddlewareDependencyBuilder` to set a dependency of your middleware from others. +/// +/// ## Static options: +/// Name | Description | Default value +/// ---- | ----------- | ------------- +/// middlewares | middlewares names to use | `{}` +class MiddlewarePipelineComponent final : public components::ComponentBase { +public: + /// @ingroup userver_component_names + /// @brief The default name of ugrpc::server::MiddlewarePipelineComponent + static constexpr std::string_view kName = "grpc-server-middlewares-pipeline"; + + MiddlewarePipelineComponent(const components::ComponentConfig& config, const components::ComponentContext& context); + + static yaml_config::Schema GetStaticConfigSchema(); + + /// @cond + /// Only for internal use. + const impl::MiddlewarePipeline& GetPipeline() const; + /// @endcond + +private: + impl::MiddlewarePipeline pipeline_; +}; + +/// @brief class for building a middleware dependency. +/// +/// If you don't care about the order in relative to others, ignore this and your middleware +/// will be in the `kUser` group. +/// Otherwise, pass a instance of this class to `MiddlewareComponentBase` in the constructor of your +/// middleware component. +class MiddlewareDependencyBuilder final { +public: + /// @brief Builder for middleware dependencey + /// @param priority is middleware priority + explicit MiddlewareDependencyBuilder() = default; + + MiddlewareDependencyBuilder(const MiddlewareDependencyBuilder&) = default; + + MiddlewareDependencyBuilder(MiddlewareDependencyBuilder&&) noexcept = default; + + /// @brief Add dependency for your middleware. Your middleware will be before 'MiddlewareBefore' in the pipeline + /// @param type is connect type between middlewares + template + MiddlewareDependencyBuilder Before(DependencyType type = DependencyType::kStrong) &&; + + /// @brief Add dependency for your middleware. Your middleware will be before 'before' in the pipeline + /// @param type is connect type between middlewares + /// @param before is the middleware component name + MiddlewareDependencyBuilder Before(std::string_view before, DependencyType type = DependencyType::kStrong) &&; + + /// @brief Add dependency for your middleware. Your middleware will be after 'MiddlewareAfter' in the pipeline + /// @param type is connect type between middlewares + template + MiddlewareDependencyBuilder After(DependencyType type = DependencyType::kStrong) &&; + + /// @brief Add dependency for your middleware. Your middleware will be after 'after' in the pipeline + /// @param type is connect type between middlewares + /// @param after is the middleware component name + MiddlewareDependencyBuilder After(std::string_view after, DependencyType type = DependencyType::kStrong) &&; + + /// @brief Add dependency for your middleware. Your middleware will be in the 'Group' group + /// @param type is type of Group + template + MiddlewareDependencyBuilder InGroup() &&; + + /// @cond + /// Only for internal use. + impl::MiddlewareDependency Extract(std::string_view middleware_name) &&; + /// @endcond + +private: + impl::MiddlewareDependency dep_{}; +}; + +namespace impl { + +template +using IsGroup = decltype(T::dependency); + +template +constexpr bool kIsGroup = std::is_same_v, const MiddlewareDependencyBuilder>; + +} // namespace impl + +template +MiddlewareDependencyBuilder MiddlewareDependencyBuilder::Before(DependencyType type) && { + if constexpr (impl::kIsGroup) { + return std::move(*this).Before(impl::BeginOfGroup(), type); + } + return std::move(*this).Before(MiddlewareBefore::kName, type); +} + +template +MiddlewareDependencyBuilder MiddlewareDependencyBuilder::After(DependencyType type) && { + if constexpr (impl::kIsGroup) { + return std::move(*this).After(impl::EndOfGroup(), type); + } + return std::move(*this).After(MiddlewareAfter::kName, type); +} + +template +MiddlewareDependencyBuilder MiddlewareDependencyBuilder::InGroup() && { + dep_.group = Group::kName; + dep_.afters.push_back(impl::Connect{impl::BeginOfGroup(), DependencyType::kStrong}); + return std::move(*this).Before(impl::EndOfGroup(), DependencyType::kWeak); +} + +} // namespace ugrpc::server + +template <> +inline constexpr bool components::kHasValidate = true; + +template <> +inline constexpr auto components::kConfigFileMode = + ConfigFileMode::kNotRequired; + +USERVER_NAMESPACE_END diff --git a/grpc/include/userver/ugrpc/server/service_component_base.hpp b/grpc/include/userver/ugrpc/server/service_component_base.hpp index 758df678b491..0df547e1e239 100644 --- a/grpc/include/userver/ugrpc/server/service_component_base.hpp +++ b/grpc/include/userver/ugrpc/server/service_component_base.hpp @@ -28,7 +28,9 @@ class GenericServiceBase; /// Name | Description | Default value /// ---- | ----------- | ------------- /// task-processor | the task processor to use for responses | taken from grpc-server.service-defaults -/// middlewares | middleware component names to use for each RPC call, can be empty array ([]) | taken from grpc-server.service-defaults +/// disable-user-pipeline-middlewares | flag to disable `groups::User` middlewares from pipeline | false +/// disable-all-pipeline-middlewares | flag to disable all middlewares from pipline | false +/// middlewares | middlewares names to use | `{}` (use server defaults) // clang-format on diff --git a/grpc/src/ugrpc/server/component_list.cpp b/grpc/src/ugrpc/server/component_list.cpp new file mode 100644 index 000000000000..6352fd3222a4 --- /dev/null +++ b/grpc/src/ugrpc/server/component_list.cpp @@ -0,0 +1,47 @@ +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +USERVER_NAMESPACE_BEGIN + +namespace ugrpc::server { + +components::ComponentList DefaultComponentList() { + return components::ComponentList() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append(); + ; +} + +components::ComponentList MinimalComponentList() { + return components::ComponentList() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append(); +} + +} // namespace ugrpc::server + +USERVER_NAMESPACE_END diff --git a/grpc/src/ugrpc/server/impl/middleware_pipeline_config.cpp b/grpc/src/ugrpc/server/impl/middleware_pipeline_config.cpp new file mode 100644 index 000000000000..576644d185e4 --- /dev/null +++ b/grpc/src/ugrpc/server/impl/middleware_pipeline_config.cpp @@ -0,0 +1,49 @@ +#include + +#include + +USERVER_NAMESPACE_BEGIN + +namespace ugrpc::server::impl { + +MiddlewarePipelineConfig Parse(const yaml_config::YamlConfig& value, formats::parse::To) { + MiddlewarePipelineConfig config; + config.middlewares = value["middlewares"].As>({}); + return config; +} + +const std::unordered_map& UserverMiddlewares() { + static std::unordered_map core_pipeline{ + {"grpc-server-logging", {}}, + {"grpc-server-baggage", {}}, + {"grpc-server-congestion-control", {}}, + {"grpc-server-deadline-propagation", {}}, + {"grpc-server-field-mask", {}}, + {"grpc-server-headers-propagator", {}}, + }; + return core_pipeline; +} + +MiddlewareConfig Parse(const yaml_config::YamlConfig& value, formats::parse::To) { + MiddlewareConfig config{}; + config.enabled = value["enabled"].As(config.enabled); + return config; +} + +MiddlewareServiceConfig Parse(const yaml_config::YamlConfig& value, formats::parse::To) { + MiddlewareServiceConfig conf{}; + conf.disable_user_pipeline_middlewares = + value["disable-user-pipeline-middlewares"].As(conf.disable_user_pipeline_middlewares); + conf.disable_all_pipeline_middlewares = + value["disable-all-pipeline-middlewares"].As(conf.disable_all_pipeline_middlewares); + conf.service_middlewares = value["middlewares"].As>({}); + return conf; +} + +bool operator==(const MiddlewareEnabled& l, const MiddlewareEnabled& r) { + return l.name == r.name && l.enabled == r.enabled; +} + +} // namespace ugrpc::server::impl + +USERVER_NAMESPACE_END diff --git a/grpc/src/ugrpc/server/impl/middlewares_graph.cpp b/grpc/src/ugrpc/server/impl/middlewares_graph.cpp new file mode 100644 index 000000000000..b002f07ea1bf --- /dev/null +++ b/grpc/src/ugrpc/server/impl/middlewares_graph.cpp @@ -0,0 +1,173 @@ +#include + +#include + +#include + +#include +#include + +USERVER_NAMESPACE_BEGIN + +namespace ugrpc::server::impl { + +namespace { + +bool IsUserverGroup(const std::string& group) { + return utils::text::EndsWith(group, "#end") || utils::text::EndsWith(group, "#begin"); +} + +void ValidateConnects( + const MiddlewareDependency& dep, + const std::vector& connects, + const Dependencies& dependencies +) { + for (const auto& con : connects) { + if (IsUserverGroup(con.node_name)) { + continue; + } + + const auto it = dependencies.find(con.node_name); + if (it != dependencies.end()) { + if (dep.group.has_value() && it->second.group.has_value()) { + UINVARIANT( + *dep.group == *it->second.group, + fmt::format( + "Middleware '{}' in group '{}'. Middleware '{}' in group '{}'. But they must be in the same " + "group.", + dep.middleware_name, + *dep.group, + con.node_name, + *it->second.group + + ) + ); + } + } else { + UINVARIANT( + con.type != DependencyType::kStrong, + fmt::format( + "Middleware `{}` does not exist and there is a kStrong dependency from {}.", + con.node_name, + dep.middleware_name + ) + ); + } + } +} + +void ValidateDependencies(const Dependencies& dependencies) { + for (const auto& [name, dep] : dependencies) { + UASSERT(name == dep.middleware_name); + ValidateConnects(dep, dep.afters, dependencies); + ValidateConnects(dep, dep.befores, dependencies); + } +} + +void AddEdges(Graph& graph, const MiddlewareDependency& dep) { + for (const auto& before : dep.befores) { + graph.AddEdge({before.node_name, dep.middleware_name, before.type}); + } + for (const auto& after : dep.afters) { + graph.AddEdge({dep.middleware_name, after.node_name, after.type}); + } + graph.AddNode(dep.middleware_name, dep.enabled); +} + +template +void AddEdgesForGroup(Graph& graph, const MiddlewareDependency& dep) { + const auto begin = impl::BeginOfGroup(); + const auto end = impl::EndOfGroup(); + graph.AddNode(begin, true); + graph.AddNode(end, true); + graph.AddEdge({end, begin, DependencyType::kStrong}); + + UASSERT(dep.afters.size() <= 1 && dep.befores.empty()); + if (dep.afters.size() == 1) { + const auto after_end = dep.afters.front().node_name; + graph.AddEdge({begin, after_end, DependencyType::kStrong}); + } +} + +template +void AddEdgesGroup(Graph& graph) { + const MiddlewareDependency dep = MiddlewareDependencyBuilder{Group::dependency}.Extract(Group::kName); + AddEdgesForGroup(graph, dep); +} + +} // namespace + +void Graph::AddEdge(Edge edge) { + const auto edge_id = edges_.size(); + edges_.push_back(edge); + edges_lists_[edge.to].push_back(edge_id); +} + +std::vector Graph::TopologySort() && { return BuildTopologySortOfMiddlewares(Reverse()); } + +const Graph::Edge& Graph::GetEdge(EdgeId edge_id) const { + UASSERT(edge_id < edges_.size()); + return edges_[edge_id]; +} + +std::unordered_map> Graph::Reverse() { + std::unordered_map> res{}; + for (const auto& [name, _] : nodes_) { + res.emplace(name, std::vector{}); + } + for (const auto& [node, edges] : edges_lists_) { + for (const auto& edge_id : edges) { + const auto& edge = GetEdge(edge_id); + const auto it = nodes_.find(edge.to); + UINVARIANT(it != nodes_.end(), fmt::format("Middleware '{}' does not exist.", edge.to)); + if (!it->second) { // The middleware is disabled + UINVARIANT( + edge.type != DependencyType::kStrong, + fmt::format( + "There is a strong connect from '{}' to '{}'. But the last is missing.", edge.from, edge.to + ) + ); + // We can handle the weak connection => not panic + } + res[edge.from].push_back(edge.to); + } + } + return res; +} + +MiddlewareOrderedList BuildPipeline(Dependencies&& dependencies) { + ValidateDependencies(dependencies); + + Graph graph{}; + + AddEdgesGroup(graph); + AddEdgesGroup(graph); + AddEdgesGroup(graph); + AddEdgesGroup(graph); + AddEdgesGroup(graph); + AddEdgesGroup(graph); + + for (const auto& [name, dep] : dependencies) { + AddEdges(graph, dep); + } + + const auto nodes = std::move(graph).TopologySort(); + LOG_INFO() << fmt::format("The global middlewares configuration: [{}].", fmt::join(nodes, ", ")); + + MiddlewareOrderedList list{}; + list.reserve(nodes.size()); + for (auto& mid : nodes) { + if (IsUserverGroup(mid)) { + continue; + } + const auto it = dependencies.find(mid); + UINVARIANT(it != dependencies.end(), fmt::format("Middleware {} does not exist.", mid)); + list.push_back({mid, it->second.enabled}); + } + + return list; +} + +} // namespace ugrpc::server::impl + +USERVER_NAMESPACE_END diff --git a/grpc/src/ugrpc/server/impl/middlewares_graph.hpp b/grpc/src/ugrpc/server/impl/middlewares_graph.hpp new file mode 100644 index 000000000000..9c549dcb32a8 --- /dev/null +++ b/grpc/src/ugrpc/server/impl/middlewares_graph.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include + +#include + +USERVER_NAMESPACE_BEGIN + +namespace ugrpc::server::impl { + +class Graph final { +public: + using MiddlewareName = std::string; + using Node = MiddlewareName; + + struct Edge final { + Node from; + Node to; + DependencyType type; + }; + + Graph() = default; + + void AddEdge(Edge edge); + + void AddNode(Node node, bool enabled) { nodes_.emplace(node, enabled); } + + std::vector TopologySort() &&; + +private: + std::unordered_map> Reverse(); + + using EdgeId = std::size_t; + + const Edge& GetEdge(EdgeId edge_id) const; + + std::vector edges_{}; + std::unordered_map> edges_lists_{}; + std::unordered_map nodes_{}; +}; + +MiddlewareOrderedList BuildPipeline(Dependencies&& dependencies); + +} // namespace ugrpc::server::impl + +USERVER_NAMESPACE_END diff --git a/grpc/src/ugrpc/server/impl/parse_config.cpp b/grpc/src/ugrpc/server/impl/parse_config.cpp index 6b54b3f710bc..1340afcc56ad 100644 --- a/grpc/src/ugrpc/server/impl/parse_config.cpp +++ b/grpc/src/ugrpc/server/impl/parse_config.cpp @@ -19,7 +19,6 @@ namespace ugrpc::server::impl { namespace { constexpr std::string_view kTaskProcessorKey = "task-processor"; -constexpr std::string_view kMiddlewaresKey = "middlewares"; template auto ParseOptional( @@ -35,7 +34,7 @@ auto ParseOptional( } template -Field MergeField( +Field GetFieldOrDefault( const yaml_config::YamlConfig& service_field, const boost::optional& server_default, const components::ComponentContext& context, @@ -53,27 +52,12 @@ ParseTaskProcessor(const yaml_config::YamlConfig& field, const components::Compo return context.GetTaskProcessor(field.As()); } -std::vector -ParseMiddlewares(const yaml_config::YamlConfig& field, const components::ComponentContext& /*context*/) { - return field.As>(); -} - -Middlewares FindMiddlewares(const std::vector& names, const components::ComponentContext& context) { - return utils::AsContainer( - names | boost::adaptors::transformed([&](const std::string& name) { - return context.FindComponent(name).GetMiddleware(); - }) - ); -} - } // namespace ServiceDefaults ParseServiceDefaults(const yaml_config::YamlConfig& value, const components::ComponentContext& context) { return ServiceDefaults{ /*task_processor=*/ParseOptional(value[kTaskProcessorKey], context, ParseTaskProcessor), - /*middleware_names=*/ - ParseOptional(value[kMiddlewaresKey], context, ParseMiddlewares), }; } @@ -83,11 +67,10 @@ server::ServiceConfig ParseServiceConfig( const ServiceDefaults& defaults ) { return server::ServiceConfig{ - /*task_processor=*/MergeField(value[kTaskProcessorKey], defaults.task_processor, context, ParseTaskProcessor), - /*middlewares=*/ - FindMiddlewares( - MergeField(value[kMiddlewaresKey], defaults.middleware_names, context, ParseMiddlewares), context + /*task_processor=*/GetFieldOrDefault( + value[kTaskProcessorKey], defaults.task_processor, context, ParseTaskProcessor ), + /*middlewares=*/{}, }; } diff --git a/grpc/src/ugrpc/server/impl/service_defaults.hpp b/grpc/src/ugrpc/server/impl/service_defaults.hpp index a193db1f342d..fb407ac59ed1 100644 --- a/grpc/src/ugrpc/server/impl/service_defaults.hpp +++ b/grpc/src/ugrpc/server/impl/service_defaults.hpp @@ -14,7 +14,6 @@ namespace ugrpc::server::impl { struct ServiceDefaults final { // using boost::optional to easily generalize to references boost::optional task_processor; - boost::optional> middleware_names; }; } // namespace ugrpc::server::impl diff --git a/grpc/src/ugrpc/server/impl/topology_sort.cpp b/grpc/src/ugrpc/server/impl/topology_sort.cpp new file mode 100644 index 000000000000..9c20290de299 --- /dev/null +++ b/grpc/src/ugrpc/server/impl/topology_sort.cpp @@ -0,0 +1,62 @@ +#include + +#include +#include + +#include +#include + +USERVER_NAMESPACE_BEGIN + +namespace ugrpc::server::impl { + +std::vector BuildTopologySortOfMiddlewares( + std::unordered_map>&& graph +) { + std::vector topology_order{}; + topology_order.reserve(graph.size()); + std::unordered_set without_connections{}; + while (!graph.empty()) { + without_connections.clear(); + for (const auto& [node_name, connections] : graph) { + if (connections.empty()) { + without_connections.insert(node_name); + } + } + if (without_connections.empty()) { + throw std::runtime_error{fmt::format( + "There are not nodes without connections => There is a cycle in the graph. Processed: {}. The cycle in " + "the graph: {}", + topology_order, + graph | boost::adaptors::map_keys + )}; + } + for (const auto& node_name : without_connections) { + graph.erase(node_name); + } + for (auto& [node, list] : graph) { + list.erase( + std::remove_if( + list.begin(), + list.end(), + [&without_connections](const auto& name) { return without_connections.count(name) != 0; } + ), + list.end() + ); + } + + const auto prev_size = topology_order.size(); + topology_order.insert( + topology_order.end(), + std::make_move_iterator(without_connections.begin()), + std::make_move_iterator(without_connections.end()) + ); + // sort only the last part of nodes + std::sort(topology_order.begin() + prev_size, topology_order.end()); + } + return topology_order; +} + +} // namespace ugrpc::server::impl + +USERVER_NAMESPACE_END diff --git a/grpc/src/ugrpc/server/impl/topology_sort.hpp b/grpc/src/ugrpc/server/impl/topology_sort.hpp new file mode 100644 index 000000000000..7e0af62a7e8c --- /dev/null +++ b/grpc/src/ugrpc/server/impl/topology_sort.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include +#include +#include + +USERVER_NAMESPACE_BEGIN + +namespace ugrpc::server::impl { + +/// Sort the DAG from independent nodes (middlewares) to dependent. +/// If there is a group of independent middlewares, the func sorts this group by the lexicographic order, +/// so we guarantee a deterministic order for each call +std::vector BuildTopologySortOfMiddlewares( + std::unordered_map>&& graph +); + +} // namespace ugrpc::server::impl + +USERVER_NAMESPACE_END diff --git a/grpc/src/ugrpc/server/middlewares/baggage/component.cpp b/grpc/src/ugrpc/server/middlewares/baggage/component.cpp index 58479b85846a..08a3967664e8 100644 --- a/grpc/src/ugrpc/server/middlewares/baggage/component.cpp +++ b/grpc/src/ugrpc/server/middlewares/baggage/component.cpp @@ -1,9 +1,11 @@ #include -#include #include #include +#include +#include + USERVER_NAMESPACE_BEGIN namespace ugrpc::server::middlewares::baggage { @@ -13,15 +15,6 @@ Component::Component(const components::ComponentConfig& config, const components std::shared_ptr Component::GetMiddleware() { return std::make_shared(); } -yaml_config::Schema Component::GetStaticConfigSchema() { - return yaml_config::MergeSchemas(R"( -type: object -description: gRPC service baggage middleware component -additionalProperties: false -properties: {} -)"); -} - } // namespace ugrpc::server::middlewares::baggage USERVER_NAMESPACE_END diff --git a/grpc/src/ugrpc/server/middlewares/base.cpp b/grpc/src/ugrpc/server/middlewares/base.cpp index 8bbb85f72cfe..8496b2904f89 100644 --- a/grpc/src/ugrpc/server/middlewares/base.cpp +++ b/grpc/src/ugrpc/server/middlewares/base.cpp @@ -1,5 +1,8 @@ #include +#include +#include + #include USERVER_NAMESPACE_BEGIN @@ -58,6 +61,15 @@ void MiddlewareBase::CallRequestHook(const MiddlewareCallContext&, google::proto void MiddlewareBase::CallResponseHook(const MiddlewareCallContext&, google::protobuf::Message&) {} +MiddlewareComponentBase::MiddlewareComponentBase( + const components::ComponentConfig& config, + const components::ComponentContext& context, + MiddlewareDependencyBuilder&& dependency +) + : components::ComponentBase(config, context), dependency_(std::move(dependency).Extract(config.Name())) {} + +const impl::MiddlewareDependency& MiddlewareComponentBase::GetMiddlewareDependency() const { return dependency_; } + } // namespace ugrpc::server USERVER_NAMESPACE_END diff --git a/grpc/src/ugrpc/server/middlewares/congestion_control/component.cpp b/grpc/src/ugrpc/server/middlewares/congestion_control/component.cpp index dbfda6b15442..53caa7db7934 100644 --- a/grpc/src/ugrpc/server/middlewares/congestion_control/component.cpp +++ b/grpc/src/ugrpc/server/middlewares/congestion_control/component.cpp @@ -1,18 +1,22 @@ #include -#include #include #include #include -#include #include +#include +#include +#include +#include + USERVER_NAMESPACE_BEGIN namespace ugrpc::server::middlewares::congestion_control { Component::Component(const components::ComponentConfig& config, const components::ComponentContext& context) - : MiddlewareComponentBase(config, context), middleware_(std::make_shared()) { + : MiddlewareComponentBase(config, context, MiddlewareDependencyBuilder().InGroup()), + middleware_(std::make_shared()) { auto& cc_component = context.FindComponent(); auto& server_limiter = cc_component.GetServerLimiter(); @@ -25,15 +29,6 @@ Component::Component(const components::ComponentConfig& config, const components std::shared_ptr Component::GetMiddleware() { return middleware_; } -yaml_config::Schema Component::GetStaticConfigSchema() { - return yaml_config::MergeSchemas(R"( -type: object -description: gRPC service congestion control middleware component -additionalProperties: false -properties: {} -)"); -} - } // namespace ugrpc::server::middlewares::congestion_control USERVER_NAMESPACE_END diff --git a/grpc/src/ugrpc/server/middlewares/deadline_propagation/component.cpp b/grpc/src/ugrpc/server/middlewares/deadline_propagation/component.cpp index 48e42822bb11..4fcb6c0c9669 100644 --- a/grpc/src/ugrpc/server/middlewares/deadline_propagation/component.cpp +++ b/grpc/src/ugrpc/server/middlewares/deadline_propagation/component.cpp @@ -1,27 +1,28 @@ #include -#include #include #include +#include +#include +#include +#include + USERVER_NAMESPACE_BEGIN namespace ugrpc::server::middlewares::deadline_propagation { Component::Component(const components::ComponentConfig& config, const components::ComponentContext& context) - : MiddlewareComponentBase(config, context) {} + : MiddlewareComponentBase( + config, + context, + MiddlewareDependencyBuilder().InGroup().After( + DependencyType::kWeak + ) + ) {} std::shared_ptr Component::GetMiddleware() { return std::make_shared(); } -yaml_config::Schema Component::GetStaticConfigSchema() { - return yaml_config::MergeSchemas(R"( -type: object -description: gRPC service deadline propagation middleware component -additionalProperties: false -properties: {} -)"); -} - } // namespace ugrpc::server::middlewares::deadline_propagation USERVER_NAMESPACE_END diff --git a/grpc/src/ugrpc/server/middlewares/field_mask/component.cpp b/grpc/src/ugrpc/server/middlewares/field_mask/component.cpp index be1d5bdddae7..22748f1d5d24 100644 --- a/grpc/src/ugrpc/server/middlewares/field_mask/component.cpp +++ b/grpc/src/ugrpc/server/middlewares/field_mask/component.cpp @@ -1,9 +1,11 @@ #include -#include #include #include +#include +#include + USERVER_NAMESPACE_BEGIN namespace ugrpc::server::middlewares::field_mask { diff --git a/grpc/src/ugrpc/server/middlewares/headers_propagator/component.cpp b/grpc/src/ugrpc/server/middlewares/headers_propagator/component.cpp index beb61248aa47..0fea79fc9dde 100644 --- a/grpc/src/ugrpc/server/middlewares/headers_propagator/component.cpp +++ b/grpc/src/ugrpc/server/middlewares/headers_propagator/component.cpp @@ -1,9 +1,10 @@ #include -#include #include #include +#include + USERVER_NAMESPACE_BEGIN namespace ugrpc::server::middlewares::headers_propagator { diff --git a/grpc/src/ugrpc/server/middlewares/log/component.cpp b/grpc/src/ugrpc/server/middlewares/log/component.cpp index a4024248fa46..57d275dcf700 100644 --- a/grpc/src/ugrpc/server/middlewares/log/component.cpp +++ b/grpc/src/ugrpc/server/middlewares/log/component.cpp @@ -1,10 +1,14 @@ #include -#include #include #include #include +#include +#include +#include +#include + USERVER_NAMESPACE_BEGIN namespace ugrpc::server::middlewares::log { @@ -19,7 +23,8 @@ Settings Parse(const yaml_config::YamlConfig& config, formats::parse::To()) {} + : MiddlewareComponentBase(config, context, MiddlewareDependencyBuilder().InGroup()), + settings_(config.As()) {} Component::~Component() = default; diff --git a/grpc/src/ugrpc/server/middlewares/pipeline.cpp b/grpc/src/ugrpc/server/middlewares/pipeline.cpp new file mode 100644 index 000000000000..6efc09061d6c --- /dev/null +++ b/grpc/src/ugrpc/server/middlewares/pipeline.cpp @@ -0,0 +1,128 @@ +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include + +USERVER_NAMESPACE_BEGIN + +namespace ugrpc::server { + +namespace { + +impl::Dependencies +MakeDependencies(const components::ComponentContext& context, impl::MiddlewarePipelineConfig&& pipeline_config) { + auto userver_deps = impl::UserverMiddlewares(); + pipeline_config.middlewares.merge(userver_deps); + impl::Dependencies dependencies{}; + dependencies.reserve(pipeline_config.middlewares.size()); + for (const auto& [mname, conf] : pipeline_config.middlewares) { + const auto* middleware = context.FindComponentOptional(mname); + if (middleware) { + auto dep = middleware->GetMiddlewareDependency(); + dep.enabled = conf.enabled; + dependencies.emplace(mname, std::move(dep)); + } else { + UINVARIANT( + impl::UserverMiddlewares().count(mname) != 0, + fmt::format("The User middleware '{}' is not registered in the component system", mname) + ); + } + } + return dependencies; +} + +} // namespace + +namespace impl { + +MiddlewarePipeline::MiddlewarePipeline(Dependencies&& deps) : deps_(deps), pipeline_(BuildPipeline(std::move(deps))) {} + +std::vector MiddlewarePipeline::GetPerServiceMiddlewares(const impl::MiddlewareServiceConfig& config +) const { + std::vector res{}; + const auto& per_service_middlewares = config.service_middlewares; + for (const auto& [name, enabled] : pipeline_) { + if (const auto it = per_service_middlewares.find(name); it != per_service_middlewares.end()) { + // Per-service enabled is high priority + if (it->second.enabled) { + res.push_back(name); + } + continue; + } + if (!enabled || config.disable_all_pipeline_middlewares) { + continue; + } + if (config.disable_user_pipeline_middlewares) { + const auto it = deps_.find(name); + UASSERT_MSG(it != deps_.end(), fmt::format("Middleware `{}` does not exist", name)); + if (it->second.group == "user") { + continue; + } + } + res.push_back(name); + } + return res; +} + +} // namespace impl + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +MiddlewarePipelineComponent::MiddlewarePipelineComponent( + const components::ComponentConfig& config, + const components::ComponentContext& context +) + : components::ComponentBase(config, context), + pipeline_(MakeDependencies(context, config.As())) {} + +const impl::MiddlewarePipeline& MiddlewarePipelineComponent::GetPipeline() const { return pipeline_; } + +yaml_config::Schema MiddlewarePipelineComponent::GetStaticConfigSchema() { + return yaml_config::MergeSchemas(R"( +type: object +description: base class for all the gRPC service components +additionalProperties: false +properties: + middlewares: + type: object + description: middlewares names to use + additionalProperties: + type: object + description: a middleware config + additionalProperties: false + properties: + enabled: + type: boolean + description: enable middleware in the list + properties: {} +)"); +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////// + +MiddlewareDependencyBuilder MiddlewareDependencyBuilder::Before(std::string_view before, DependencyType type) && { + dep_.befores.push_back(impl::Connect{std::string{before}, type}); + return MiddlewareDependencyBuilder(std::move(*this)); +} + +MiddlewareDependencyBuilder MiddlewareDependencyBuilder::After(std::string_view after, DependencyType type) && { + dep_.afters.push_back(impl::Connect{std::string{after}, type}); + return MiddlewareDependencyBuilder(std::move(*this)); +} + +impl::MiddlewareDependency MiddlewareDependencyBuilder::Extract(std::string_view middleware_name) && { + dep_.middleware_name = std::string{middleware_name}; + return std::move(dep_); +} + +} // namespace ugrpc::server + +USERVER_NAMESPACE_END diff --git a/grpc/src/ugrpc/server/server_component.cpp b/grpc/src/ugrpc/server/server_component.cpp index 0bc3d9e8cb0d..e838a91fea6f 100644 --- a/grpc/src/ugrpc/server/server_component.cpp +++ b/grpc/src/ugrpc/server/server_component.cpp @@ -104,12 +104,6 @@ additionalProperties: false task-processor: type: string description: the task processor to use for responses - middlewares: - type: array - description: middlewares names to use - items: - type: string - description: middleware component name )"); } diff --git a/grpc/src/ugrpc/server/service_component_base.cpp b/grpc/src/ugrpc/server/service_component_base.cpp index 3dbe9f763dd4..59e09a417436 100644 --- a/grpc/src/ugrpc/server/service_component_base.cpp +++ b/grpc/src/ugrpc/server/service_component_base.cpp @@ -7,7 +7,9 @@ #include #include +#include #include +#include USERVER_NAMESPACE_BEGIN @@ -19,10 +21,17 @@ ServiceComponentBase::ServiceComponentBase( ) : ComponentBase(config, context), server_(context.FindComponent()), - config_(server_.ParseServiceConfig(config, context)) {} + config_(server_.ParseServiceConfig(config, context)) { + const auto conf = config.As(); + for (const auto& mid : + context.FindComponent().GetPipeline().GetPerServiceMiddlewares(conf)) { + config_.middlewares.push_back(context.FindComponent(mid).GetMiddleware()); + } +} void ServiceComponentBase::RegisterService(ServiceBase& service) { UINVARIANT(!registered_.exchange(true), "Register must only be called once"); + server_.GetServer().AddService(service, std::move(config_)); } @@ -41,13 +50,26 @@ additionalProperties: false type: string description: the task processor to use for responses defaultDescription: uses grpc-server.service-defaults.task-processor + disable-user-pipeline-middlewares: + type: boolean + description: flag to disable groups::User middlewares from pipeline + defaultDescription: false + disable-all-pipeline-middlewares: + type: boolean + description: flag to disable all middlewares from pipline + defaultDescription: false middlewares: - type: array - description: middlewares names to use - defaultDescription: uses grpc-server.service-defaults.middlewares - items: - type: string - description: middleware component name + type: object + description: overloads of configs of middlewares per service + additionalProperties: + type: object + description: a middleware config + additionalProperties: false + properties: + enabled: + type: boolean + description: enable middleware in the list + properties: {} )"); } diff --git a/grpc/tests/middleware_pipeline_test.cpp b/grpc/tests/middleware_pipeline_test.cpp new file mode 100644 index 000000000000..34ab58dbc23e --- /dev/null +++ b/grpc/tests/middleware_pipeline_test.cpp @@ -0,0 +1,396 @@ +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +USERVER_NAMESPACE_BEGIN + +namespace { + +using Builder = ugrpc::server::MiddlewareDependencyBuilder; +using OrderedList = ugrpc::server::impl::MiddlewareOrderedList; + +using Log = ugrpc::server::middlewares::log::Component; +using Baggage = ugrpc::server::middlewares::baggage::Component; +using Deadline = ugrpc::server::middlewares::deadline_propagation::Component; +using Congestion = ugrpc::server::middlewares::congestion_control::Component; +using HeadersPropagator = ugrpc::server::middlewares::headers_propagator::Component; +using FieldMask = ugrpc::server::middlewares::field_mask::Component; + +constexpr auto kStrongConnect = ugrpc::server::DependencyType::kStrong; +constexpr auto kWeakConnect = ugrpc::server::DependencyType::kWeak; + +const auto kEmptyConfig = ugrpc::server::impl::MiddlewareServiceConfig{ + {}, + /* disable_user_pipeline_middlewares=*/false, + /* disable_all_pipeline_middlewares=*/false, +}; + +template +ugrpc::server::impl::MiddlewareEnabled Mid(bool enabed = true) { + return {std::string{Middleware::kName}, enabed}; +} + +template +std::pair Dependency( + ugrpc::server::DependencyType con_type = ugrpc::server::DependencyType::kWeak +) { + return { + std::string{Before::kName}, Builder().InGroup().template After(con_type).Extract(Before::kName)}; +} + +ugrpc::server::impl::Dependencies kDefaultDependencies{ + {std::string{Log::kName}, Builder().InGroup().Extract(Log::kName)}, + {std::string{Congestion::kName}, Builder().InGroup().Extract(Congestion::kName)}, + {std::string{Deadline::kName}, + Builder().InGroup().After(kWeakConnect).Extract(Deadline::kName)}, + {std::string{Baggage::kName}, Builder().InGroup().Extract(Baggage::kName)}, + {std::string{FieldMask::kName}, Builder().InGroup().Extract(FieldMask::kName)}, + {std::string{HeadersPropagator::kName}, + Builder().InGroup().Extract(HeadersPropagator::kName)}, +}; + +struct A1 final { + static constexpr std::string_view kName = "a1"; +}; + +struct A2 final { + static constexpr std::string_view kName = "a2"; +}; + +struct C1 final { + static constexpr std::string_view kName = "c1"; +}; + +struct C2 final { + static constexpr std::string_view kName = "c2"; +}; + +struct C3 final { + static constexpr std::string_view kName = "c3"; +}; + +struct U1 final { + static constexpr std::string_view kName = "u1"; +}; + +struct U2 final { + static constexpr std::string_view kName = "u2"; +}; + +} // namespace + +TEST(MiddlewarePipeline, Empty) { + auto dependencies = kDefaultDependencies; + dependencies.clear(); + + const ugrpc::server::impl::MiddlewarePipeline pipeline{std::move(dependencies)}; + const auto& list = pipeline.GetOrderedList(); + + ASSERT_TRUE(list.empty()); +} + +TEST(MiddlewarePipeline, SimpleList) { + auto dependencies = kDefaultDependencies; + + const ugrpc::server::impl::MiddlewarePipeline pipeline{std::move(dependencies)}; + const auto& list = pipeline.GetOrderedList(); + + const OrderedList expected{ + Mid(), + Mid(), + Mid(), + Mid(), + Mid(), + Mid(), + }; + + ASSERT_EQ(expected, list); +} + +TEST(MiddlewarePipeline, DisableWeakConnection) { + auto dependencies = kDefaultDependencies; + const bool disabled = false; + dependencies["grpc-server-logging"].enabled = disabled; + dependencies["grpc-server-field-mask"].enabled = disabled; + + const ugrpc::server::impl::MiddlewarePipeline pipeline{std::move(dependencies)}; + const auto& list = pipeline.GetOrderedList(); + + const OrderedList expected{ + Mid(disabled), + Mid(), + Mid(), + Mid(), + Mid(disabled), + Mid(), + }; + ASSERT_EQ(expected, list); +} + +TEST(MiddlewarePipeline, DisableStrongConnection) { + auto dependencies = kDefaultDependencies; + + // disable 'grpc-server-congestion-control' + dependencies["grpc-server-congestion-control"].enabled = false; + // Set a Strong connect from 'grpc-server-congestion-control' to 'grpc-server-congestion-control' + dependencies.erase(std::string{Deadline::kName}); + dependencies.insert(Dependency(kStrongConnect)); + + EXPECT_UINVARIANT_FAILURE(ugrpc::server::impl::BuildPipeline(std::move(dependencies))); +} + +TEST(MiddlewarePipeline, DependencyToOtherGroup) { + auto dependencies = kDefaultDependencies; + // Dependency from User to Core is not allowed + dependencies.insert(Dependency(kWeakConnect)); + + EXPECT_UINVARIANT_FAILURE(ugrpc::server::impl::BuildPipeline(std::move(dependencies))); +} + +TEST(MiddlewarePipeline, LexicographicOrder) { + auto dependencies = kDefaultDependencies; + dependencies.insert(Dependency()); + dependencies.insert(Dependency()); + dependencies.insert(Dependency()); + + const ugrpc::server::impl::MiddlewarePipeline pipeline{std::move(dependencies)}; + const auto& list = pipeline.GetOrderedList(); + + const OrderedList expected{ + Mid(), + Mid(), + Mid(), + Mid(), + Mid(), + Mid(), + Mid(), + Mid(), + Mid(), + }; + ASSERT_EQ(expected, list); +}; + +TEST(MiddlewarePipeline, MultiDependency) { + auto dependencies = kDefaultDependencies; + dependencies.insert( + {std::string{A2::kName}, + Builder().InGroup().After().Before().Extract(A2::kName)} + ); + + const ugrpc::server::impl::MiddlewarePipeline pipeline{std::move(dependencies)}; + const auto& list = pipeline.GetOrderedList(); + + const OrderedList expected{ + Mid(), + Mid(), + Mid(), + Mid(), + Mid(), + Mid(), + Mid(), + }; + ASSERT_EQ(expected, list); +}; + +TEST(MiddlewarePipeline, BetweenGroups) { + auto dependencies = kDefaultDependencies; + dependencies.insert( + {std::string{A1::kName}, + Builder().After().Before().Extract(A1::kName)} + ); + + const ugrpc::server::impl::MiddlewarePipeline pipeline{std::move(dependencies)}; + const auto& list = pipeline.GetOrderedList(); + + const OrderedList expected{ + Mid(), + Mid(), + Mid(), + Mid(), + Mid(), + Mid(), + Mid(), + }; + ASSERT_EQ(expected, list); +}; + +TEST(MiddlewarePipeline, DisablePerService) { + auto dependencies = kDefaultDependencies; + + dependencies.emplace(std::string{U1::kName}, Builder().InGroup().Extract(U1::kName)); + + const ugrpc::server::impl::MiddlewarePipeline pipeline{std::move(dependencies)}; + const auto list = pipeline.GetPerServiceMiddlewares(ugrpc::server::impl::MiddlewareServiceConfig{ + { + {std::string{Deadline::kName}, {false}}, + {std::string{U1::kName}, {false}}, + }, + /* disable_user_pipeline_middlewares=*/false, + /* disable_all_pipeline_middlewares=*/false, + }); + + const std::vector expected{ + std::string{Log::kName}, + std::string{Congestion::kName}, + // Deadline id disabled + std::string{Baggage::kName}, + std::string{FieldMask::kName}, + std::string{HeadersPropagator::kName}, + // U1 is disabled + }; + ASSERT_EQ(expected, list); +}; + +TEST(MiddlewarePipeline, DisableUserGroup) { + auto dependencies = kDefaultDependencies; + + const ugrpc::server::impl::MiddlewarePipeline pipeline{std::move(dependencies)}; + const auto list = pipeline.GetPerServiceMiddlewares(ugrpc::server::impl::MiddlewareServiceConfig{ + { + {std::string{Baggage::kName}, {true}}, + }, + /* disable_user_pipeline_middlewares=*/true, + /* disable_all_pipeline_middlewares=*/false, + }); + + const std::vector expected{ + std::string{Log::kName}, + std::string{Congestion::kName}, + std::string{Deadline::kName}, + std::string{Baggage::kName}, // force enable + // Baggage, FieldMask and HeadersPropagator are disabled + }; + ASSERT_EQ(expected, list); +}; + +TEST(MiddlewarePipeline, DisableAllPipelineMiddlewares) { + auto dependencies = kDefaultDependencies; + + dependencies.emplace( + std::string{A1::kName}, Builder().InGroup().After().Extract(A1::kName) + ); + dependencies.emplace(std::string{A2::kName}, Builder().InGroup().Extract(A2::kName)); + const ugrpc::server::impl::MiddlewarePipeline pipeline{std::move(dependencies)}; + const auto list = pipeline.GetPerServiceMiddlewares(ugrpc::server::impl::MiddlewareServiceConfig{ + { + {std::string{A1::kName}, {true}}, + {std::string{A2::kName}, {true}}, + {std::string{Deadline::kName}, {true}}, + }, + /* disable_user_pipeline_middlewares=*/false, + /* disable_all_pipeline_middlewares=*/true, + }); + + // Disable the global pipeline, but local force enabled, so there are middlewares from MiddlewareServiceConfig + const std::vector expected{ + std::string{A2::kName}, + std::string{A1::kName}, + std::string{Deadline::kName}, + }; + ASSERT_EQ(expected, list); +}; + +TEST(MiddlewarePipeline, DisableAll) { + auto dependencies = kDefaultDependencies; + + const ugrpc::server::impl::MiddlewarePipeline pipeline{std::move(dependencies)}; + const auto list = pipeline.GetPerServiceMiddlewares(ugrpc::server::impl::MiddlewareServiceConfig{ + {}, + /* disable_user_pipeline_middlewares=*/false, + /* disable_all_pipeline_middlewares=*/true, + }); + ASSERT_TRUE(list.empty()); +}; + +TEST(MiddlewarePipeline, GlobalDisableAndPerServiceEnable) { + auto dependencies = kDefaultDependencies; + dependencies["grpc-server-logging"].enabled = false; + dependencies["grpc-server-headers-propagator"].enabled = false; + dependencies["grpc-server-baggage"].enabled = false; + dependencies["grpc-server-field-mask"].enabled = false; + + const ugrpc::server::impl::MiddlewarePipeline pipeline{std::move(dependencies)}; + const auto list = pipeline.GetPerServiceMiddlewares(ugrpc::server::impl::MiddlewareServiceConfig{ + { + {std::string{Log::kName}, {true}}, + {std::string{Baggage::kName}, {true}}, + }, + /* disable_user_pipeline_middlewares=*/false, + /* disable_all_pipeline_middlewares=*/false, + }); + + const std::vector expected{ + std::string{Log::kName}, // force enabled + std::string{Congestion::kName}, + std::string{Deadline::kName}, + std::string{Baggage::kName}, // force enabled + // FieldMask is disabled + // HeadersPropagator is disabled + }; + ASSERT_EQ(expected, list); +}; + +TEST(MiddlewarePipeline, DurabilityOrder) { + auto dependencies = kDefaultDependencies; + + dependencies.emplace( + std::string{U1::kName}, Builder().InGroup().Before().Extract(U1::kName) + ); + dependencies.emplace( + std::string{U2::kName}, Builder().InGroup().Before().Extract(U2::kName) + ); + const ugrpc::server::impl::MiddlewarePipeline pipeline{std::move(dependencies)}; + const auto list = pipeline.GetPerServiceMiddlewares(kEmptyConfig); + + const std::vector expected{ + std::string{Log::kName}, + std::string{Congestion::kName}, + std::string{Deadline::kName}, + std::string{FieldMask::kName}, + std::string{HeadersPropagator::kName}, + std::string{U2::kName}, + std::string{U1::kName}, + std::string{Baggage::kName}, + }; + ASSERT_EQ(expected, list); + + ///////////////////////////////////////// + auto dependencies2 = kDefaultDependencies; + dependencies2.emplace( + std::string{U1::kName}, + Builder().InGroup().Before(kWeakConnect).Extract(U1::kName) + ); + dependencies2.emplace( + std::string{U2::kName}, + Builder().InGroup().Before(kWeakConnect).Extract(U2::kName) + ); + // Disable 'u1' and now 'u2' is not Before but the order is durability, because `enabled` does not affect + // connects + dependencies2["u1"].enabled = false; + const ugrpc::server::impl::MiddlewarePipeline pipeline2{std::move(dependencies2)}; + const auto list2 = pipeline2.GetPerServiceMiddlewares(kEmptyConfig); + + std::vector expected2{ + std::string{Log::kName}, + std::string{Congestion::kName}, + std::string{Deadline::kName}, + std::string{FieldMask::kName}, + std::string{HeadersPropagator::kName}, + std::string{U2::kName}, // There is a phantom disabled node 'u1', so U2 is still before Baggage + std::string{Baggage::kName}, + }; + ASSERT_EQ(expected2, list2); +}; + +USERVER_NAMESPACE_END diff --git a/grpc/tests/topology_test.cpp b/grpc/tests/topology_test.cpp new file mode 100644 index 000000000000..0616e12a49dd --- /dev/null +++ b/grpc/tests/topology_test.cpp @@ -0,0 +1,74 @@ +#include + +#include + +#include + +USERVER_NAMESPACE_BEGIN + +TEST(TopologySort, Basic) { + std::unordered_map> graph{ + {"grpc-server-baggage", {"grpc-server-logging"}}, + {"grpc-server-congestion-control", {"grpc-server-logging"}}, + {"grpc-server-deadline-propagation", {"grpc-server-logging"}}, + {"grpc-server-field-mask", {"grpc-server-logging"}}, + {"grpc-server-headers-propagator", {"grpc-server-logging"}}, + {"grpc-server-logging", {}}, + }; + auto sort = ugrpc::server::impl::BuildTopologySortOfMiddlewares(std::move(graph)); + std::vector expected{ + "grpc-server-logging", + "grpc-server-baggage", + "grpc-server-congestion-control", + "grpc-server-deadline-propagation", + "grpc-server-field-mask", + "grpc-server-headers-propagator"}; + ASSERT_EQ(sort, expected); +} + +TEST(TopologySort, Throw) { + /* + <- B + / ^ + A <- | + \ | + <- D + C (independent) + + */ + std::unordered_map> graph{ + {"B", {"A"}}, + {"D", {"A", "B"}}, + }; + UASSERT_THROW(ugrpc::server::impl::BuildTopologySortOfMiddlewares(std::move(graph)), std::runtime_error); + graph["A"] = {}; + graph["C"] = {}; + auto sort = ugrpc::server::impl::BuildTopologySortOfMiddlewares(std::move(graph)); + std::vector expected{"A", "C", "B", "D"}; + ASSERT_EQ(sort, expected); +} + +TEST(TopologySort, TwoSubPath) { + /* + <- B + / + A <- + \ + <- C + + E <- D + + */ + std::unordered_map> graph{ + {"A", {}}, + {"E", {}}, + {"B", {"A"}}, + {"C", {"A"}}, + {"D", {"E"}}, + }; + auto sort = ugrpc::server::impl::BuildTopologySortOfMiddlewares(std::move(graph)); + std::vector expected{"A", "E", "B", "C", "D"}; + ASSERT_EQ(sort, expected); +} + +USERVER_NAMESPACE_END diff --git a/libraries/grpc-reflection/functional_tests/golden_path/grpc_reflection_service.cpp b/libraries/grpc-reflection/functional_tests/golden_path/grpc_reflection_service.cpp index 9c17a796cfba..ea292be37baf 100644 --- a/libraries/grpc-reflection/functional_tests/golden_path/grpc_reflection_service.cpp +++ b/libraries/grpc-reflection/functional_tests/golden_path/grpc_reflection_service.cpp @@ -8,16 +8,10 @@ #include #include #include +#include #include -#include #include -#include -#include -#include - -#include - #include int main(int argc, char* argv[]) { @@ -27,7 +21,7 @@ int main(int argc, char* argv[]) { .Append() .Append() .Append() - .Append() + .AppendComponentList(ugrpc::server::DefaultComponentList()) .Append(); return utils::DaemonMain(argc, argv, component_list); } diff --git a/libraries/grpc-reflection/functional_tests/golden_path/static_config.yaml b/libraries/grpc-reflection/functional_tests/golden_path/static_config.yaml index 75c7635ac7fb..5051807baefb 100644 --- a/libraries/grpc-reflection/functional_tests/golden_path/static_config.yaml +++ b/libraries/grpc-reflection/functional_tests/golden_path/static_config.yaml @@ -11,7 +11,11 @@ components_manager: components: grpc-reflection-service: + disable-all-pipeline-middlewares: true grpc-health: + disable-all-pipeline-middlewares: true + grpc-server-middlewares-pipeline: + congestion-control: testsuite-support: @@ -22,7 +26,6 @@ components_manager: completion-queue-count: 3 service-defaults: task-processor: main-task-processor - middlewares: [] diff --git a/rabbitmq/include/userver/rabbitmq.hpp b/rabbitmq/include/userver/rabbitmq.hpp index e93c3ffd22dd..db55f97016df 100644 --- a/rabbitmq/include/userver/rabbitmq.hpp +++ b/rabbitmq/include/userver/rabbitmq.hpp @@ -41,7 +41,7 @@ /// ---------- /// /// @htmlonly
@endhtmlonly -/// ⇦ @ref scripts/docs/en/userver/grpc.md | +/// ⇦ @ref scripts/docs/en/userver/grpc_server_middlewares.md | /// @ref scripts/docs/en/userver/dynamic_config.md ⇨ /// @htmlonly
@endhtmlonly diff --git a/samples/grpc-generic-proxy/main.cpp b/samples/grpc-generic-proxy/main.cpp index b744c99065b1..052dcaff5058 100644 --- a/samples/grpc-generic-proxy/main.cpp +++ b/samples/grpc-generic-proxy/main.cpp @@ -15,10 +15,7 @@ #include #include #include -#include -#include -#include -#include +#include #include #include @@ -29,7 +26,6 @@ int main(int argc, char* argv[]) { components::MinimalServerComponentList() // Base userver components .Append() - .Append() // HTTP client and server are (sadly) needed for testsuite support .Append() .Append() @@ -41,11 +37,7 @@ int main(int argc, char* argv[]) { .Append() .Append>("generic-client") // gRPC server setup - .Append() - .Append() - .Append() - .Append() - .Append() + .AppendComponentList(ugrpc::server::DefaultComponentList()) .Append(); return utils::DaemonMain(argc, argv, component_list); diff --git a/samples/grpc-generic-proxy/static_config.yaml b/samples/grpc-generic-proxy/static_config.yaml index a35f83338535..b74085757003 100644 --- a/samples/grpc-generic-proxy/static_config.yaml +++ b/samples/grpc-generic-proxy/static_config.yaml @@ -56,16 +56,14 @@ components_manager: port: $grpc-server-port service-defaults: task-processor: main-task-processor - middlewares: - - grpc-server-logging - - grpc-server-deadline-propagation - - grpc-server-congestion-control - - grpc-server-field-mask - grpc-server-logging: - grpc-server-deadline-propagation: - grpc-server-congestion-control: - grpc-server-field-mask: + grpc-server-middlewares-pipeline: + middlewares: + grpc-server-baggage: + enabled: false + grpc-server-headers-propagator: + enabled: false + proxy-service: diff --git a/samples/grpc_middleware_service/src/main.cpp b/samples/grpc_middleware_service/src/main.cpp index a8e7591f091f..85c20b3a72aa 100644 --- a/samples/grpc_middleware_service/src/main.cpp +++ b/samples/grpc_middleware_service/src/main.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -14,10 +15,10 @@ /// [gRPC middleware sample - components registration] int main(int argc, char* argv[]) { const auto component_list = components::MinimalServerComponentList() + .AppendComponentList(ugrpc::server::DefaultComponentList()) .Append() .Append() .Append() - .Append() .Append() .Append() .Append() diff --git a/samples/grpc_middleware_service/src/middlewares/server/component.cpp b/samples/grpc_middleware_service/src/middlewares/server/component.cpp index 6b2e15caaed1..5da7288957d4 100644 --- a/samples/grpc_middleware_service/src/middlewares/server/component.cpp +++ b/samples/grpc_middleware_service/src/middlewares/server/component.cpp @@ -4,12 +4,20 @@ #include #include +/// [gRPC middleware sample - middleware registration] +#include namespace sample::grpc::auth::server { Component::Component(const components::ComponentConfig& config, const components::ComponentContext& context) - : MiddlewareComponentBase(config, context), middleware_(std::make_shared()) {} + : MiddlewareComponentBase( + config, + context, + ugrpc::server::MiddlewareDependencyBuilder().InGroup() + ), + middleware_(std::make_shared()) {} std::shared_ptr Component::GetMiddleware() { return middleware_; } } // namespace sample::grpc::auth::server +/// [gRPC middleware sample - middleware registration] diff --git a/samples/grpc_middleware_service/static_config.yaml b/samples/grpc_middleware_service/static_config.yaml index 33963de4c708..1220895e463e 100644 --- a/samples/grpc_middleware_service/static_config.yaml +++ b/samples/grpc_middleware_service/static_config.yaml @@ -9,8 +9,6 @@ components_manager: level: debug overflow_behavior: discard - grpc-auth-server: - grpc-auth-client: # /// [gRPC middleware sample - static config client middleware] @@ -33,6 +31,17 @@ components_manager: # which is kind of pointless, but works for an example endpoint: '[::1]:8091' + congestion-control: + +# /// [gRPC middleware sample - static config middleware-pipeline] + grpc-auth-server: + + grpc-server-middlewares-pipeline: + middlewares: + grpc-auth-server: + enabled: true +# /// [gRPC middleware sample - static config middleware-pipeline] + # Common configuration for gRPC server grpc-server: # The single listening port for incoming RPCs @@ -45,8 +54,6 @@ components_manager: greeter-service: task-processor: main-task-processor greeting-prefix: Hello - middlewares: - - grpc-auth-server # /// [gRPC middleware sample - static config server middleware] # In this example, the tests still communicate with the microservice diff --git a/samples/grpc_service/main.cpp b/samples/grpc_service/main.cpp index d6e71b39c00e..afa816a7e705 100644 --- a/samples/grpc_service/main.cpp +++ b/samples/grpc_service/main.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -23,6 +24,7 @@ int main(int argc, char* argv[]) { .Append() // All gRPC services are registered in this component. .Append() + .Append() // Custom components: .Append() .Append() diff --git a/samples/grpc_service/static_config.yaml b/samples/grpc_service/static_config.yaml index 6545f42ff6df..c5311de85c8e 100644 --- a/samples/grpc_service/static_config.yaml +++ b/samples/grpc_service/static_config.yaml @@ -38,6 +38,7 @@ components_manager: factory-component: grpc-client-factory # /// [static config client] + grpc-server-middlewares-pipeline: # /// [static config server] # yaml @@ -52,7 +53,6 @@ components_manager: greeter-service: task-processor: main-task-processor greeting-prefix: Hello - middlewares: [] # /// [static config server] # In this example, the tests still communicate with the microservice diff --git a/scripts/docs/en/index.md b/scripts/docs/en/index.md index 4c2bf7cd80d8..2549808629be 100644 --- a/scripts/docs/en/index.md +++ b/scripts/docs/en/index.md @@ -88,6 +88,7 @@ are available at the ## Protocols * @ref scripts/docs/en/userver/grpc.md + * @ref scripts/docs/en/userver/grpc_server_middlewares.md * HTTP: * @ref clients::http::Client "Client" * @ref scripts/docs/en/userver/http_server.md diff --git a/scripts/docs/en/userver/grpc.md b/scripts/docs/en/userver/grpc.md index 5a9b0e4f8c72..06c20cc7b7cd 100644 --- a/scripts/docs/en/userver/grpc.md +++ b/scripts/docs/en/userver/grpc.md @@ -167,40 +167,7 @@ By default, gRPC server uses `grpc::InsecureServerCredentials`. To pass a custom ### Server middlewares -The gRPC server can be extended by middlewares. -Middleware is called on each incoming (for service) or outgoing (for client) RPC request. -Different middlewares handle the call in the defined order. -A middleware may decide to reject the call or call the next middleware in the stack. -Middlewares may implement almost any enhancement to the gRPC server including authorization -and authentication, ratelimiting, logging, tracing, audit, etc. - -Middlewares to use are indicated in static config in section `middlewares` of `ugrpc::server::ServiceComponentBase` descendant component. -Default middleware list for handlers can be specified in `grpc-server.service-defaults.middlewares` config section. - -Example configuration: -``` -components_manager: - components: - some-service-client: - middlewares: - - grpc-client-logging - - grpc-client-deadline-propagation - - grpc-client-baggage - - grpc-server: - service-defaults: - middlewares: - - grpc-server-logging - - grpc-server-deadline-propagation - - grpc-server-congestion-control - - grpc-server-baggage - - some-service: - middlewares: - # Completely overwrite the default list - - grpc-server-logging - -``` +@ref scripts/docs/en/userver/grpc_server_middlewares.md. Use ugrpc::server::MiddlewareBase and ugrpc::client::MiddlewareBase to implement new middlewares. @@ -394,5 +361,5 @@ These are the metrics provided for each gRPC method: ---------- @htmlonly
@endhtmlonly -⇦ @ref scripts/docs/en/userver/profile_context_switches.md | @ref rabbitmq_driver ⇨ +⇦ @ref scripts/docs/en/userver/profile_context_switches.md | @ref scripts/docs/en/userver/grpc_server_middlewares.md ⇨ @htmlonly
@endhtmlonly diff --git a/scripts/docs/en/userver/grpc_server_middlewares.md b/scripts/docs/en/userver/grpc_server_middlewares.md new file mode 100644 index 000000000000..ee5751f32b57 --- /dev/null +++ b/scripts/docs/en/userver/grpc_server_middlewares.md @@ -0,0 +1,108 @@ +# gRPC server middlewares + +## Default middlewares + +The gRPC server can be extended by middlewares. +Middleware is called on each incoming (for service) or outgoing (for client) RPC request. +Different middlewares handle the call in the defined order. +A middleware may decide to reject the call or call the next middleware in the stack. +Middlewares may implement almost any enhancement to the gRPC server including authorization +and authentication, ratelimiting, logging, tracing, audit, etc. + +There is an `ugrpc::server::MiddlewarePipeline` component for configuring the middlewares pipeline. +There are default middlewares: + - grpc-server-logging + - grpc-server-deadline-propagation + - grpc-server-congestion-control + - grpc-server-baggage + - grpc-server-field-mask + - grpc-server-headers-propagator + +All of these middlewares are enabled by default. However, you must register components of these middlewares in the component list. +You should use `ugrpc::server::DefaultComponentList` or `ugrpc::server::MinimalComponentList`. + +`ugrpc::server::MiddlewarePipeline` is a global configuration of server middlewares. So, you can enabled/disable middlewares with the option `enabled` in the global (`grpc-server-middlewares-pipeline`) or middleware config. + +Example configuration: +```yaml +components_manager: + components: + grpc-server: + + grpc-server-middlewares-pipeline: + middlewares: + grpc-server-field-mask: + enabled: false + + some-service: + middlewares: + # force enable in this service. Or it can be disabled for special service + grpc-server-field-mask: + enabled: true + +``` + +## User middlewares + +If you implement your server middleware, you must register it in config `grpc-server-middlewares-pipeline`. + +Example: + +@snippet samples/grpc_middleware_service/static_config.yaml gRPC middleware sample - static config middleware-pipeline + +Your middleware will be called after all userver middlewares. Your middlewares will be lexicographic ordered. + +## Middlewares order + +It possible to order middlewares in the pipeline. Use `ugrpc::server::MiddlewareDependencyBuilder` and (optional) enum `ugrpc::server::DependencyType`. + +```cpp +#include + +MiddlewareComponent::MiddlewareComponent(const components::ComponentConfig& config, const components::ComponentContext& context) + : MiddlewareComponentBase( + config, + context, + ugrpc::server::MiddlewareDependencyBuilder().After() + ) {} + +``` +Then the middleware of the component `MiddlewareComponent` will be after `path_to_my_middleware::Component` in the pipeline. + +@warn Middlewares that ordered with `ugrpc::server::MiddlewareDependencyBuilder::Before` and `ugrpc::server::MiddlewareDependencyBuilder::After` must be in the same group. + +If then someone disable middleware `path_to_my_middleware::Component`, userver does not start, because `MiddlewareComponent` requested this middleware. So, this connection is strong (`ugrpc::server::DependencyType::kStrong`). You can set `ugrpc::server::DependencyType::kWeak` to ignore disabling of `path_to_my_middleware::Component`. + +```cpp +#include + +MiddlewareComponent::MiddlewareComponent(const components::ComponentConfig& config, const components::ComponentContext& context) + : MiddlewareComponentBase( + config, + context, + ugrpc::server::MiddlewareDependencyBuilder() + .After(ugrpc::server::DependencyType::kWeak) + ) {} +``` + +@warn The middlewares pipeline can't has a cycles. Userver does not start. So, be careful when order middlewares. + +## Middlewares groups + +The middlewares pipeline consists of groups (ordered from begin to end): + - `ugrpc::server::groups::PreCore` + - `ugrpc::server::groups::Logging` + - `ugrpc::server::groups::Auth` + - `ugrpc::server::groups::Core` + - `ugrpc::server::groups::PostCore` + - `ugrpc::server::groups::User` + +All user middlewares will be in `ugrpc::server::groups::User` group by default. But you can register your middleware in any group such as: + +@snippet samples/grpc_middleware_service/src/middlewares/server/component.cpp gRPC middleware sample - middleware registration + +You can don't pass `ugrpc::server::MiddlewareDependencyBuilder` in `ugrpc::server::MiddlewareComponentBase` and middleware will be in the group `User` by default. + +@htmlonly
@endhtmlonly +⇦ @ref scripts/docs/en/userver/grpc.md | @ref rabbitmq_driver ⇨ +@htmlonly
@endhtmlonly