1#include "ODBCBridge.h"
2#include "HandlerFactory.h"
3
4#include <string>
5#include <errno.h>
6#include <IO/ReadHelpers.h>
7#include <boost/program_options.hpp>
8#include <Poco/Net/HTTPServer.h>
9#include <Poco/Net/NetException.h>
10#include <Poco/String.h>
11#include <Poco/Util/HelpFormatter.h>
12#include <Common/Exception.h>
13#include <Common/StringUtils/StringUtils.h>
14#include <Common/config.h>
15#include <common/logger_useful.h>
16#include <ext/scope_guard.h>
17#include <ext/range.h>
18#include <Common/SensitiveDataMasker.h>
19
20namespace DB
21{
22namespace ErrorCodes
23{
24 extern const int ARGUMENT_OUT_OF_BOUND;
25}
26
27namespace
28{
29 Poco::Net::SocketAddress makeSocketAddress(const std::string & host, UInt16 port, Poco::Logger * log)
30 {
31 Poco::Net::SocketAddress socket_address;
32 try
33 {
34 socket_address = Poco::Net::SocketAddress(host, port);
35 }
36 catch (const Poco::Net::DNSException & e)
37 {
38 const auto code = e.code();
39 if (code == EAI_FAMILY
40#if defined(EAI_ADDRFAMILY)
41 || code == EAI_ADDRFAMILY
42#endif
43 )
44 {
45 LOG_ERROR(log,
46 "Cannot resolve listen_host (" << host << "), error " << e.code() << ": " << e.message()
47 << ". "
48 "If it is an IPv6 address and your host has disabled IPv6, then consider to "
49 "specify IPv4 address to listen in <listen_host> element of configuration "
50 "file. Example: <listen_host>0.0.0.0</listen_host>");
51 }
52
53 throw;
54 }
55 return socket_address;
56 }
57
58 Poco::Net::SocketAddress socketBindListen(Poco::Net::ServerSocket & socket, const std::string & host, UInt16 port, Poco::Logger * log)
59 {
60 auto address = makeSocketAddress(host, port, log);
61#if POCO_VERSION < 0x01080000
62 socket.bind(address, /* reuseAddress = */ true);
63#else
64 socket.bind(address, /* reuseAddress = */ true, /* reusePort = */ false);
65#endif
66
67 socket.listen(/* backlog = */ 64);
68
69 return address;
70 }
71}
72
73void ODBCBridge::handleHelp(const std::string &, const std::string &)
74{
75 Poco::Util::HelpFormatter helpFormatter(options());
76 helpFormatter.setCommand(commandName());
77 helpFormatter.setHeader("HTTP-proxy for odbc requests");
78 helpFormatter.setUsage("--http-port <port>");
79 helpFormatter.format(std::cerr);
80
81 stopOptionsProcessing();
82}
83
84
85void ODBCBridge::defineOptions(Poco::Util::OptionSet & options)
86{
87 options.addOption(Poco::Util::Option("http-port", "", "port to listen").argument("http-port", true).binding("http-port"));
88 options.addOption(
89 Poco::Util::Option("listen-host", "", "hostname to listen, default localhost").argument("listen-host").binding("listen-host"));
90 options.addOption(
91 Poco::Util::Option("http-timeout", "", "http timout for socket, default 1800").argument("http-timeout").binding("http-timeout"));
92
93 options.addOption(Poco::Util::Option("max-server-connections", "", "max connections to server, default 1024")
94 .argument("max-server-connections")
95 .binding("max-server-connections"));
96 options.addOption(Poco::Util::Option("keep-alive-timeout", "", "keepalive timeout, default 10")
97 .argument("keep-alive-timeout")
98 .binding("keep-alive-timeout"));
99
100 options.addOption(Poco::Util::Option("log-level", "", "sets log level, default info").argument("log-level").binding("logger.level"));
101
102 options.addOption(
103 Poco::Util::Option("log-path", "", "log path for all logs, default console").argument("log-path").binding("logger.log"));
104
105 options.addOption(Poco::Util::Option("err-log-path", "", "err log path for all logs, default no")
106 .argument("err-log-path")
107 .binding("logger.errorlog"));
108
109 using Me = std::decay_t<decltype(*this)>;
110 options.addOption(Poco::Util::Option("help", "", "produce this help message")
111 .binding("help")
112 .callback(Poco::Util::OptionCallback<Me>(this, &Me::handleHelp)));
113
114 ServerApplication::defineOptions(options); /// Don't need complex BaseDaemon's .xml config
115}
116
117void ODBCBridge::initialize(Application & self)
118{
119 BaseDaemon::closeFDs();
120 is_help = config().has("help");
121
122 if (is_help)
123 return;
124
125 config().setString("logger", "ODBCBridge");
126
127 buildLoggers(config(), logger(), self.commandName());
128
129 log = &logger();
130 hostname = config().getString("listen-host", "localhost");
131 port = config().getUInt("http-port");
132 if (port > 0xFFFF)
133 throw Exception("Out of range 'http-port': " + std::to_string(port), ErrorCodes::ARGUMENT_OUT_OF_BOUND);
134
135 http_timeout = config().getUInt("http-timeout", DEFAULT_HTTP_READ_BUFFER_TIMEOUT);
136 max_server_connections = config().getUInt("max-server-connections", 1024);
137 keep_alive_timeout = config().getUInt("keep-alive-timeout", 10);
138
139 initializeTerminationAndSignalProcessing();
140
141 ServerApplication::initialize(self);
142}
143
144void ODBCBridge::uninitialize()
145{
146 BaseDaemon::uninitialize();
147}
148
149int ODBCBridge::main(const std::vector<std::string> & /*args*/)
150{
151 if (is_help)
152 return Application::EXIT_OK;
153
154 LOG_INFO(log, "Starting up");
155 Poco::Net::ServerSocket socket;
156 auto address = socketBindListen(socket, hostname, port, log);
157 socket.setReceiveTimeout(http_timeout);
158 socket.setSendTimeout(http_timeout);
159 Poco::ThreadPool server_pool(3, max_server_connections);
160 Poco::Net::HTTPServerParams::Ptr http_params = new Poco::Net::HTTPServerParams;
161 http_params->setTimeout(http_timeout);
162 http_params->setKeepAliveTimeout(keep_alive_timeout);
163
164 context = std::make_shared<Context>(Context::createGlobal());
165 context->makeGlobalContext();
166
167 if (config().has("query_masking_rules"))
168 {
169 SensitiveDataMasker::setInstance(std::make_unique<SensitiveDataMasker>(config(), "query_masking_rules"));
170 }
171
172 auto server = Poco::Net::HTTPServer(
173 new HandlerFactory("ODBCRequestHandlerFactory-factory", keep_alive_timeout, context), server_pool, socket, http_params);
174 server.start();
175
176 LOG_INFO(log, "Listening http://" + address.toString());
177
178 SCOPE_EXIT({
179 LOG_DEBUG(log, "Received termination signal.");
180 LOG_DEBUG(log, "Waiting for current connections to close.");
181 server.stop();
182 for (size_t count : ext::range(1, 6))
183 {
184 if (server.currentConnections() == 0)
185 break;
186 LOG_DEBUG(log, "Waiting for " << server.currentConnections() << " connections, try " << count);
187 std::this_thread::sleep_for(std::chrono::milliseconds(1000));
188 }
189 });
190
191 waitForTerminationRequest();
192 return Application::EXIT_OK;
193}
194}
195
196#pragma GCC diagnostic ignored "-Wmissing-declarations"
197int mainEntryClickHouseODBCBridge(int argc, char ** argv)
198{
199 DB::ODBCBridge app;
200 try
201 {
202 return app.run(argc, argv);
203 }
204 catch (...)
205 {
206 std::cerr << DB::getCurrentExceptionMessage(true) << "\n";
207 auto code = DB::getCurrentExceptionCode();
208 return code ? code : 1;
209 }
210}
211