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