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