userver: userver/middlewares/runner.hpp Source File
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages Concepts
runner.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/middlewares/runner.hpp
4/// @brief @copybrief middlewares::RunnerComponentBase
5
6#include <vector>
7
8#include <userver/components/component_base.hpp>
9#include <userver/components/component_config.hpp>
10#include <userver/components/component_context.hpp>
11#include <userver/utils/assert.hpp>
12#include <userver/utils/impl/internal_tag.hpp>
13#include <userver/yaml_config/merge_schemas.hpp>
14
15#include <userver/middlewares/impl/middleware_pipeline_config.hpp>
16#include <userver/middlewares/impl/simple_middleware_pipeline.hpp>
17#include <userver/middlewares/pipeline.hpp>
18
19USERVER_NAMESPACE_BEGIN
20
21namespace middlewares {
22
23namespace impl {
24
25// Options for a middleware can be from two places:
26// 1. The global config of a middleware.
27// 2. Overriding in RunnerComponentBase component.
28// We must validate (2) and merge values from (1) and (2).
29yaml_config::YamlConfig ValidateAndMergeMiddlewareConfigs(
30 const formats::yaml::Value& global,
31 const yaml_config::YamlConfig& local,
32 yaml_config::Schema schema
33);
34
35/// Make the default builder for `middlewares::groups::User` group.
36MiddlewareDependencyBuilder MakeDefaultUserDependency();
37
38class WithMiddlewareDependencyComponentBase : public components::ComponentBase {
39public:
40 WithMiddlewareDependencyComponentBase(
41 const components::ComponentConfig& config,
42 const components::ComponentContext& context
43
44 )
45 : components::ComponentBase(config, context) {}
46
47 virtual const middlewares::impl::MiddlewareDependency& GetMiddlewareDependency(utils::impl::InternalTag) const = 0;
48};
49
50void LogConfiguration(std::string_view component_name, const std::vector<std::string>& names);
51
52void LogValidateError(std::string_view middleware_name, const std::exception& e);
53
54} // namespace impl
55
56/// @ingroup userver_base_classes
57///
58/// @brief Base class for middleware factory component.
59template <typename MiddlewareBaseType, typename HandlerInfo>
60class MiddlewareFactoryComponentBase : public impl::WithMiddlewareDependencyComponentBase {
61public:
62 using MiddlewareBase = MiddlewareBaseType;
63
64 MiddlewareFactoryComponentBase(
65 const components::ComponentConfig& config,
66 const components::ComponentContext& context,
67 MiddlewareDependencyBuilder&& builder = impl::MakeDefaultUserDependency()
68 )
69 : impl::WithMiddlewareDependencyComponentBase(config, context),
70 global_config_(config.As<formats::yaml::Value>()),
71 dependency_(std::move(builder).ExtractDependency(/*middleware_name=*/config.Name())) {}
72
73 /// @brief Returns a middleware according to the component's settings.
74 ///
75 /// @param info is a handler info for the middleware.
76 /// @param middleware_config config for the middleware.
77 ///
78 /// @warning Don't store `info` by reference. `info` object will be dropped after the `CreateMiddleware` call.
79 virtual std::shared_ptr<const MiddlewareBase>
80 CreateMiddleware(const HandlerInfo& info, const yaml_config::YamlConfig& middleware_config) const = 0;
81
82 /// @brief This method should return the schema of a middleware configuration.
83 /// Always write `return GetStaticConfigSchema();` in this method.
84 virtual yaml_config::Schema GetMiddlewareConfigSchema() const { return GetStaticConfigSchema(); }
85
86 static yaml_config::Schema GetStaticConfigSchema() {
88type: object
89description: base class for grpc-server middleware
90additionalProperties: false
91properties:
92 enabled:
93 type: string
94 description: the flag to enable/disable middleware in the pipeline
95 defaultDescription: true
96)");
97 }
98
99 /// @cond
100 /// Only for internal use.
101 const middlewares::impl::MiddlewareDependency& GetMiddlewareDependency(utils::impl::InternalTag) const override {
102 return dependency_;
103 }
104
105 const formats::yaml::Value& GetGlobalConfig(utils::impl::InternalTag) const { return global_config_; }
106 /// @endcond
107
108private:
109 const formats::yaml::Value global_config_;
110 middlewares::impl::MiddlewareDependency dependency_;
111};
112
113/// @brief Base class for a component that runs middlewares.
114///
115/// There are a local and global configs of middlewares.
116/// Global config of middleware is a classic config in `components_manager.components`.
117/// You can override the global config for the specific service/client by the local config in the config of this
118/// component: see the 'middlewares' option.
119///
120/// `RunnerComponentBase` creates middleware instances using `MiddlewareFactoryComponentBase`.
121/// The Ordered list of middlewares `RunnerComponentBase` takes from Pipeline component.
122/// So, 'Pipeline' is responsible for the order of middlewares. `RunnerComponentBase` is responsible for creating
123/// middlewares and overriding configs.
124template <typename MiddlewareBase, typename HandlerInfo>
125class RunnerComponentBase : public components::ComponentBase,
126 public impl::PipelineCreatorInterface<MiddlewareBase, HandlerInfo> {
127public:
128 static yaml_config::Schema GetStaticConfigSchema() {
130type: object
131description: base class for all the gRPC service components
132additionalProperties: false
133properties:
134 disable-user-pipeline-middlewares:
135 type: boolean
136 description: flag to disable groups::User middlewares from pipeline
137 defaultDescription: false
138 disable-all-pipeline-middlewares:
139 type: boolean
140 description: flag to disable all middlewares from pipline
141 defaultDescription: false
142 middlewares:
143 type: object
144 description: overloads of configs of middlewares per service
145 additionalProperties:
146 type: object
147 description: a middleware config
148 additionalProperties: true
149 properties:
150 enabled:
151 type: boolean
152 description: enable middleware in the list
153 properties: {}
154)");
155 }
156
157protected:
158 using MiddlewareFactory = MiddlewareFactoryComponentBase<MiddlewareBase, HandlerInfo>;
159
160 RunnerComponentBase(
161 const components::ComponentConfig& config,
162 const components::ComponentContext& context,
163 std::string_view pipeline_name
164 );
165
166 /// @cond
167 // Only for internal use.
168 std::vector<std::shared_ptr<const MiddlewareBase>> CreateMiddlewares(const HandlerInfo& info) const override;
169 /// @endcond
170
171private:
172 struct MiddlewareInfo final {
173 const MiddlewareFactory* factory{nullptr};
174 yaml_config::YamlConfig local_config{};
175 };
176
177 std::vector<MiddlewareInfo> middleware_infos_{};
178};
179
180template <typename MiddlewareBase, typename HandlerInfo>
181RunnerComponentBase<MiddlewareBase, HandlerInfo>::RunnerComponentBase(
182 const components::ComponentConfig& config,
183 const components::ComponentContext& context,
184 std::string_view pipeline_name
185)
186 : components::ComponentBase(config, context) {
187 const auto& middlewares = config["middlewares"];
188 const auto& pipeline = context.FindComponent<impl::AnyMiddlewarePipelineComponent>(pipeline_name).GetPipeline();
189 const auto names = pipeline.GetPerServiceMiddlewares(config.As<impl::MiddlewareRunnerConfig>());
190 impl::LogConfiguration(config.Name(), names);
191 for (const auto& mid : names) {
192 const auto* factory = context.FindComponentOptional<MiddlewareFactory>(mid);
193 UINVARIANT(factory != nullptr, fmt::format("The middleware '{}' must exist", mid));
194 middleware_infos_.push_back(MiddlewareInfo{factory, middlewares[mid]});
195 }
196}
197
198/// @cond
199template <typename MiddlewareBase, typename HandlerInfo>
200std::vector<std::shared_ptr<const MiddlewareBase>> RunnerComponentBase<MiddlewareBase, HandlerInfo>::CreateMiddlewares(
201 const HandlerInfo& info
202) const {
203 std::vector<std::shared_ptr<const MiddlewareBase>> middlewares{};
204 middlewares.reserve(middleware_infos_.size());
205 for (const auto& [factory, local_config] : middleware_infos_) {
206 try {
207 auto config = impl::ValidateAndMergeMiddlewareConfigs(
208 factory->GetGlobalConfig(utils::impl::InternalTag{}), local_config, factory->GetMiddlewareConfigSchema()
209 );
210 middlewares.push_back(factory->CreateMiddleware(info, config));
211 } catch (const std::exception& e) {
212 impl::LogValidateError(factory->GetMiddlewareDependency(utils::impl::InternalTag{}).middleware_name, e);
213 throw;
214 }
215 }
216 return middlewares;
217}
218/// @endcond
219
220namespace impl {
221
222/// @brief A short-cut for defining a middleware-factory.
223///
224/// `MiddlewareBase` type is a interface of middleware, so `Middleware` type is a implementation of this interface.
225/// Your middleware will be created with a default constructor and will be in `middlewares::groups::User` group.
226template <typename MiddlewareBase, typename Middleware, typename HandlerInfo>
227class SimpleMiddlewareFactoryComponent final : public MiddlewareFactoryComponentBase<MiddlewareBase, HandlerInfo> {
228public:
229 static constexpr std::string_view kName = Middleware::kName;
230
231 SimpleMiddlewareFactoryComponent(
232 const components::ComponentConfig& config,
233 const components::ComponentContext& context
234 )
235 : MiddlewareFactoryComponentBase<MiddlewareBase, HandlerInfo>(
236 config,
237 context,
238 middlewares::MiddlewareDependencyBuilder{Middleware::kDependency}
239 ) {}
240
241private:
242 std::shared_ptr<const MiddlewareBase> CreateMiddleware(const HandlerInfo&, const yaml_config::YamlConfig&)
243 const override {
244 return std::make_shared<Middleware>();
245 }
246};
247
248} // namespace impl
249
250} // namespace middlewares
251
252template <typename MiddlewareBase, typename Middleware, typename HandlerInfo>
253inline constexpr bool components::kHasValidate<
254 middlewares::impl::SimpleMiddlewareFactoryComponent<MiddlewareBase, Middleware, HandlerInfo>> = true;
255
256USERVER_NAMESPACE_END