userver: /home/user/userver/libraries/easy/src/easy.cpp Source File
Loading...
Searching...
No Matches
easy.cpp
1#include <userver/easy.hpp>
2
3#include <fstream>
4#include <iostream>
5#include <unordered_map>
6
7#include <fmt/format.h>
8#include <fmt/ranges.h>
9#include <boost/algorithm/string/replace.hpp>
10#include <boost/program_options.hpp>
11
12#include <userver/clients/dns/component.hpp>
13#include <userver/clients/http/component.hpp>
14#include <userver/clients/http/middlewares/pipeline_component.hpp>
15#include <userver/components/component_context.hpp>
16#include <userver/components/minimal_server_component_list.hpp>
17#include <userver/components/run.hpp>
18#include <userver/server/handlers/http_handler_base.hpp>
19#include <userver/server/handlers/json_error_builder.hpp>
20#include <userver/storages/postgres/component.hpp>
21#include <userver/testsuite/testsuite_support.hpp>
22#include <userver/utils/daemon_run.hpp>
23
24USERVER_NAMESPACE_BEGIN
25
26namespace easy {
27
28namespace {
29
30constexpr std::string_view kConfigBase = R"~(# yaml
31components_manager:
32 task_processors: # Task processor is an executor for coroutine tasks
33 main-task-processor: # Make a task processor for CPU-bound coroutine tasks.
34 worker_threads: 4 # Process tasks in 4 threads.
35
36 fs-task-processor: # Make a separate task processor for filesystem bound tasks.
37 worker_threads: 1
38
39 default_task_processor: main-task-processor # Task processor in which components start.
40
41 components: # Configuring components that were registered via component_list)~";
42
43constexpr std::string_view kConfigServerTemplate = R"~(
44 server:
45 listener: # configuring the main listening socket...
46 port: {} # ...to listen on this port and...
47 task_processor: main-task-processor # ...process incoming requests on this task processor.
48)~";
49
50constexpr std::string_view kConfigLoggingTemplate = R"~(
51 logging:
52 fs-task-processor: fs-task-processor
53 loggers:
54 default:
55 file_path: '@stderr'
56 level: {}
57 overflow_behavior: discard # Drop logs if the system is too busy to write them down.
58)~";
59
60constexpr std::string_view kConfigHandlerTemplate{
61 "path: {0} # Registering handler by URL '{0}'.\n"
62 "method: {1}\n"
63 "task_processor: main-task-processor # Run it on CPU bound task processor\n"
64};
65
66struct SharedPyaload {
67 std::unordered_map<std::string, HttpBase::Callback> http_functions;
68 std::optional<http::ContentType> default_content_type;
69 std::string db_schema;
70};
71
72SharedPyaload globals{};
73
74} // anonymous namespace
75
76namespace impl {
77
78DependenciesBase::~DependenciesBase() = default;
79
80} // namespace impl
81
82class HttpBase::Handle final : public server::handlers::HttpHandlerBase {
83public:
84 using CustomHandlerException = server::handlers::CustomHandlerException;
85 using FormattedErrorData = server::handlers::FormattedErrorData;
86
87 Handle(const components::ComponentConfig& config, const components::ComponentContext& context)
88 : HttpHandlerBase(config, context),
89 deps_{context.FindComponent<impl::DependenciesBase>()},
90 callback_{globals.http_functions.at(config.Name())}
91 {
92 if (!callback_.content_type && globals.default_content_type) {
93 callback_.content_type = *globals.default_content_type;
94 }
95 }
96
97 std::string HandleRequestThrow(const server::http::HttpRequest& request, server::request::RequestContext&)
98 const override {
99 if (callback_.content_type) {
100 request.GetHttpResponse().SetContentType(*callback_.content_type);
101 }
102 return callback_.function(request, deps_);
103 }
104
105 FormattedErrorData GetFormattedExternalErrorBody(const CustomHandlerException& exc) const override {
106 if (callback_.content_type == http::content_type::kApplicationJson) {
107 return {
108 server::handlers::JsonErrorBuilder(exc).GetExternalBody(),
109 server::handlers::JsonErrorBuilder::GetContentType()
110 };
111 }
112
113 return HttpHandlerBase::GetFormattedExternalErrorBody(exc);
114 }
115
116private:
117 const impl::DependenciesBase& deps_;
118 HttpBase::Callback& callback_;
119};
120
121HttpBase::HttpBase(int argc, const char* const argv[])
122 : argc_{argc},
123 argv_{argv},
124 static_config_{kConfigBase},
126{}
127
128HttpBase::~HttpBase() {
129 static_config_.append(fmt::format(kConfigServerTemplate, port_));
130 static_config_.append(fmt::format(kConfigLoggingTemplate, ToString(level_)));
131
132 namespace po = boost::program_options;
133 po::variables_map vm;
134 auto desc = utils::BaseRunOptions();
135 std::string config_dump;
136 std::string schema_dump;
137
138 // clang-format off
139 desc.add_options()
140 ("dump-config", po::value(&config_dump)->implicit_value(""), "path to dump the server config")
141 ("dump-db-schema", po::value(&schema_dump)->implicit_value(""), "path to dump the DB schema")
142 ("config,c", po::value<std::string>(), "path to server config")
143 ;
144 // clang-format on
145
146 po::store(po::parse_command_line(argc_, argv_, desc), vm);
147 po::notify(vm);
148
149 if (vm.count("help")) {
150 std::cerr << desc << '\n';
151 return;
152 }
153
154 if (vm.count("dump-config")) {
155 if (config_dump.empty()) {
156 std::cout << static_config_ << std::endl;
157 } else {
158 std::ofstream(config_dump) << static_config_;
159 }
160 return;
161 }
162
163 if (vm.count("dump-db-schema")) {
164 if (schema_dump.empty()) {
165 std::cout << schema_dump << std::endl;
166 } else {
167 std::ofstream(schema_dump) << globals.db_schema;
168 }
169 return;
170 }
171
172 if (argc_ <= 1) {
173 components::Run(components::InMemoryConfig{static_config_}, component_list_);
174 } else {
175 const auto ret = utils::DaemonMain(vm, component_list_);
176 if (ret != 0) {
177 std::exit(ret); // NOLINT(concurrency-mt-unsafe)
178 }
179 }
180}
181
182void HttpBase::DefaultContentType(http::ContentType content_type) { globals.default_content_type = content_type; }
183
184void HttpBase::Route(std::string_view path, Callback&& func, std::initializer_list<server::http::HttpMethod> methods) {
185 auto component_name = fmt::format("{}-{}", path, fmt::join(methods, ","));
186
187 globals.http_functions.emplace(component_name, std::move(func));
188 component_list_.Append<Handle>(component_name);
189 AddComponentConfig(component_name, fmt::format(kConfigHandlerTemplate, path, fmt::join(methods, ",")));
190}
191
192void HttpBase::AddComponentConfig(std::string_view name, std::string_view config) {
193 static_config_ += fmt::format("\n {}:", name);
194 if (config.empty()) {
195 static_config_ += " {}\n";
196 } else {
197 if (config.back() == '\n') {
198 config = std::string_view{config.data(), config.size() - 1};
199 }
200 static_config_ += boost::algorithm::replace_all_copy("\n" + std::string{config}, "\n", "\n ");
201 static_config_ += '\n';
202 }
203}
204
205void HttpBase::DbSchema(std::string_view schema) { globals.db_schema = schema; }
206
207const std::string& HttpBase::GetDbSchema() noexcept { return globals.db_schema; }
208
209void HttpBase::Port(std::uint16_t port) { port_ = port; }
210
211void HttpBase::LogLevel(logging::Level level) { level_ = level; }
212
213PgDep::PgDep(const components::ComponentContext& context)
214 : pg_cluster_(context.FindComponent<components::Postgres>("postgres").GetCluster())
215{
216 const auto& db_schema = HttpBase::GetDbSchema();
217 if (!db_schema.empty()) {
218 pg_cluster_->Execute(storages::postgres::ClusterHostType::kMaster, db_schema);
219 }
220}
221
222void PgDep::RegisterOn(HttpBase& app) {
224 "postgres",
225 "dbconnection#env: POSTGRESQL\n"
226 "dbconnection#fallback: 'postgresql://testsuite@localhost:15433/postgres'\n"
227 "blocking_task_processor: fs-task-processor\n"
228 "dns_resolver: async\n"
229 );
230
231 app.TryAddComponent<components::TestsuiteSupport>(components::TestsuiteSupport::kName, "");
233 clients::dns::Component>(clients::dns::Component::kName, "fs-task-processor: fs-task-processor");
234}
235
236HttpDep::HttpDep(const components::ComponentContext& context)
237 : http_(context.FindComponent<components::HttpClient>().GetHttpClient())
238{}
239
240void HttpDep::RegisterOn(easy::HttpBase& app) {
241 app.TryAddComponent<components::HttpClientCore>(
242 components::HttpClientCore::kName,
243 "pool-statistics-disable: false\n"
244 "thread-name-prefix: http-client\n"
245 "threads: 2\n"
246 "fs-task-processor: fs-task-processor\n"
247 );
249 clients::http::MiddlewarePipelineComponent>(clients::http::MiddlewarePipelineComponent::kName, "");
251 components::HttpClient::kName,
252 fmt::format("core-component: {}\n", components::HttpClientCore::kName)
253 );
254}
255
256} // namespace easy
257
258USERVER_NAMESPACE_END