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