2018-08-07 17:57:44 +00:00
# include "ODBCBridge.h"
2018-08-08 16:15:29 +00:00
# include "HandlerFactory.h"
2018-08-07 17:57:44 +00:00
# include <string>
# include <errno.h>
# include <IO/ReadHelpers.h>
# include <boost/program_options.hpp>
2020-01-10 16:33:26 +00:00
2020-05-08 14:11:19 +00:00
# if USE_ODBC
// It doesn't make much sense to build this bridge without ODBC, but we still do this.
# include <Poco / Data / ODBC / Connector.h>
2020-01-10 16:33:26 +00:00
# endif
2018-08-07 17:57:44 +00:00
# include <Poco/Net/HTTPServer.h>
# include <Poco/Net/NetException.h>
# include <Poco/String.h>
# include <Poco/Util/HelpFormatter.h>
# include <Common/Exception.h>
# include <Common/StringUtils/StringUtils.h>
# include <Common/config.h>
2020-10-29 03:39:43 +00:00
# include <Formats/registerFormats.h>
2018-08-07 17:57:44 +00:00
# include <common/logger_useful.h>
2018-08-10 11:42:12 +00:00
# include <ext/scope_guard.h>
# include <ext/range.h>
2019-09-06 17:48:27 +00:00
# include <Common/SensitiveDataMasker.h>
2018-08-07 17:57:44 +00:00
2020-10-29 03:39:43 +00:00
2018-08-07 17:57:44 +00:00
namespace DB
{
namespace ErrorCodes
{
extern const int ARGUMENT_OUT_OF_BOUND ;
}
namespace
{
Poco : : Net : : SocketAddress makeSocketAddress ( const std : : string & host , UInt16 port , Poco : : Logger * log )
{
Poco : : Net : : SocketAddress socket_address ;
try
{
socket_address = Poco : : Net : : SocketAddress ( host , port ) ;
}
catch ( const Poco : : Net : : DNSException & e )
{
const auto code = e . code ( ) ;
if ( code = = EAI_FAMILY
# if defined(EAI_ADDRFAMILY)
| | code = = EAI_ADDRFAMILY
# endif
)
{
2020-05-23 22:24:01 +00:00
LOG_ERROR ( log , " Cannot resolve listen_host ({}), error {}: {}. If it is an IPv6 address and your host has disabled IPv6, then consider to specify IPv4 address to listen in <listen_host> element of configuration file. Example: <listen_host>0.0.0.0</listen_host> " , host , e . code ( ) , e . message ( ) ) ;
2018-08-07 17:57:44 +00:00
}
throw ;
}
return socket_address ;
}
Poco : : Net : : SocketAddress socketBindListen ( Poco : : Net : : ServerSocket & socket , const std : : string & host , UInt16 port , Poco : : Logger * log )
{
auto address = makeSocketAddress ( host , port , log ) ;
# if POCO_VERSION < 0x01080000
socket . bind ( address , /* reuseAddress = */ true ) ;
# else
socket . bind ( address , /* reuseAddress = */ true , /* reusePort = */ false ) ;
# endif
socket . listen ( /* backlog = */ 64 ) ;
return address ;
2018-08-26 00:51:37 +00:00
}
2018-08-07 17:57:44 +00:00
}
void ODBCBridge : : handleHelp ( const std : : string & , const std : : string & )
{
2020-03-23 02:12:31 +00:00
Poco : : Util : : HelpFormatter help_formatter ( options ( ) ) ;
help_formatter . setCommand ( commandName ( ) ) ;
help_formatter . setHeader ( " HTTP-proxy for odbc requests " ) ;
help_formatter . setUsage ( " --http-port <port> " ) ;
help_formatter . format ( std : : cerr ) ;
2018-08-07 17:57:44 +00:00
stopOptionsProcessing ( ) ;
}
void ODBCBridge : : defineOptions ( Poco : : Util : : OptionSet & options )
{
options . addOption ( Poco : : Util : : Option ( " http-port " , " " , " port to listen " ) . argument ( " http-port " , true ) . binding ( " http-port " ) ) ;
options . addOption (
2018-08-10 11:42:12 +00:00
Poco : : Util : : Option ( " listen-host " , " " , " hostname to listen, default localhost " ) . argument ( " listen-host " ) . binding ( " listen-host " ) ) ;
2018-08-07 17:57:44 +00:00
options . addOption (
2020-01-11 09:50:41 +00:00
Poco : : Util : : Option ( " http-timeout " , " " , " http timeout for socket, default 1800 " ) . argument ( " http-timeout " ) . binding ( " http-timeout " ) ) ;
2018-08-10 11:42:12 +00:00
2018-08-07 17:57:44 +00:00
options . addOption ( Poco : : Util : : Option ( " max-server-connections " , " " , " max connections to server, default 1024 " )
. argument ( " max-server-connections " )
. binding ( " max-server-connections " ) ) ;
options . addOption ( Poco : : Util : : Option ( " keep-alive-timeout " , " " , " keepalive timeout, default 10 " )
. argument ( " keep-alive-timeout " )
. binding ( " keep-alive-timeout " ) ) ;
2018-08-08 16:15:29 +00:00
options . addOption ( Poco : : Util : : Option ( " log-level " , " " , " sets log level, default info " ) . argument ( " log-level " ) . binding ( " logger.level " ) ) ;
options . addOption (
Poco : : Util : : Option ( " log-path " , " " , " log path for all logs, default console " ) . argument ( " log-path " ) . binding ( " logger.log " ) ) ;
options . addOption ( Poco : : Util : : Option ( " err-log-path " , " " , " err log path for all logs, default no " )
. argument ( " err-log-path " )
. binding ( " logger.errorlog " ) ) ;
2018-08-07 17:57:44 +00:00
using Me = std : : decay_t < decltype ( * this ) > ;
options . addOption ( Poco : : Util : : Option ( " help " , " " , " produce this help message " )
. binding ( " help " )
. callback ( Poco : : Util : : OptionCallback < Me > ( this , & Me : : handleHelp ) ) ) ;
2020-03-18 02:02:24 +00:00
ServerApplication : : defineOptions ( options ) ; // NOLINT Don't need complex BaseDaemon's .xml config
2018-08-07 17:57:44 +00:00
}
void ODBCBridge : : initialize ( Application & self )
{
2018-08-12 12:23:22 +00:00
BaseDaemon : : closeFDs ( ) ;
2018-08-07 17:57:44 +00:00
is_help = config ( ) . has ( " help " ) ;
2018-08-08 16:15:29 +00:00
2018-08-07 17:57:44 +00:00
if ( is_help )
return ;
2018-08-08 16:15:29 +00:00
config ( ) . setString ( " logger " , " ODBCBridge " ) ;
2019-12-29 13:08:33 +00:00
buildLoggers ( config ( ) , logger ( ) , self . commandName ( ) ) ;
2019-06-20 07:17:21 +00:00
2019-09-26 12:08:24 +00:00
BaseDaemon : : logRevision ( ) ;
2018-08-07 17:57:44 +00:00
log = & logger ( ) ;
2018-08-10 11:42:12 +00:00
hostname = config ( ) . getString ( " listen-host " , " localhost " ) ;
2018-08-07 17:57:44 +00:00
port = config ( ) . getUInt ( " http-port " ) ;
2018-08-09 12:57:34 +00:00
if ( port > 0xFFFF )
2018-08-07 17:57:44 +00:00
throw Exception ( " Out of range 'http-port': " + std : : to_string ( port ) , ErrorCodes : : ARGUMENT_OUT_OF_BOUND ) ;
http_timeout = config ( ) . getUInt ( " http-timeout " , DEFAULT_HTTP_READ_BUFFER_TIMEOUT ) ;
max_server_connections = config ( ) . getUInt ( " max-server-connections " , 1024 ) ;
keep_alive_timeout = config ( ) . getUInt ( " keep-alive-timeout " , 10 ) ;
initializeTerminationAndSignalProcessing ( ) ;
2020-05-08 14:11:19 +00:00
# if USE_ODBC
2019-09-26 12:08:24 +00:00
// It doesn't make much sense to build this bridge without ODBC, but we
// still do this.
2019-09-23 14:15:23 +00:00
Poco : : Data : : ODBC : : Connector : : registerConnector ( ) ;
2019-09-26 12:08:24 +00:00
# endif
2019-09-23 14:15:23 +00:00
2020-03-18 02:02:24 +00:00
ServerApplication : : initialize ( self ) ; // NOLINT
2018-08-07 17:57:44 +00:00
}
void ODBCBridge : : uninitialize ( )
{
BaseDaemon : : uninitialize ( ) ;
}
int ODBCBridge : : main ( const std : : vector < std : : string > & /*args*/ )
{
if ( is_help )
2018-08-08 16:29:09 +00:00
return Application : : EXIT_OK ;
2018-08-07 17:57:44 +00:00
2020-10-29 03:39:43 +00:00
registerFormats ( ) ;
2020-05-23 22:24:01 +00:00
LOG_INFO ( log , " Starting up " ) ;
2018-08-07 17:57:44 +00:00
Poco : : Net : : ServerSocket socket ;
auto address = socketBindListen ( socket , hostname , port , log ) ;
socket . setReceiveTimeout ( http_timeout ) ;
socket . setSendTimeout ( http_timeout ) ;
Poco : : ThreadPool server_pool ( 3 , max_server_connections ) ;
Poco : : Net : : HTTPServerParams : : Ptr http_params = new Poco : : Net : : HTTPServerParams ;
http_params - > setTimeout ( http_timeout ) ;
http_params - > setKeepAliveTimeout ( keep_alive_timeout ) ;
2020-04-17 09:47:40 +00:00
auto shared_context = Context : : createShared ( ) ;
Context context ( Context : : createGlobal ( shared_context . get ( ) ) ) ;
context . makeGlobalContext ( ) ;
2018-08-07 17:57:44 +00:00
2019-06-20 07:17:21 +00:00
if ( config ( ) . has ( " query_masking_rules " ) )
{
2019-09-06 17:48:27 +00:00
SensitiveDataMasker : : setInstance ( std : : make_unique < SensitiveDataMasker > ( config ( ) , " query_masking_rules " ) ) ;
2019-06-20 07:17:21 +00:00
}
2018-08-07 17:57:44 +00:00
auto server = Poco : : Net : : HTTPServer (
2018-08-08 16:15:29 +00:00
new HandlerFactory ( " ODBCRequestHandlerFactory-factory " , keep_alive_timeout , context ) , server_pool , socket , http_params ) ;
2018-08-07 17:57:44 +00:00
server . start ( ) ;
2020-05-23 22:24:01 +00:00
LOG_INFO ( log , " Listening http://{} " , address . toString ( ) ) ;
2018-08-07 17:57:44 +00:00
2018-08-10 11:42:12 +00:00
SCOPE_EXIT ( {
2020-05-23 22:24:01 +00:00
LOG_DEBUG ( log , " Received termination signal. " ) ;
LOG_DEBUG ( log , " Waiting for current connections to close. " ) ;
2018-08-10 11:42:12 +00:00
server . stop ( ) ;
for ( size_t count : ext : : range ( 1 , 6 ) )
{
if ( server . currentConnections ( ) = = 0 )
break ;
2020-05-23 22:24:01 +00:00
LOG_DEBUG ( log , " Waiting for {} connections, try {} " , server . currentConnections ( ) , count ) ;
2018-08-10 11:42:12 +00:00
std : : this_thread : : sleep_for ( std : : chrono : : milliseconds ( 1000 ) ) ;
}
} ) ;
2018-08-07 17:57:44 +00:00
2018-08-10 11:42:12 +00:00
waitForTerminationRequest ( ) ;
2018-08-07 17:57:44 +00:00
return Application : : EXIT_OK ;
}
}
2019-12-15 06:34:43 +00:00
# pragma GCC diagnostic ignored "-Wmissing-declarations"
2018-08-07 17:57:44 +00:00
int mainEntryClickHouseODBCBridge ( int argc , char * * argv )
{
DB : : ODBCBridge app ;
try
{
return app . run ( argc , argv ) ;
}
catch ( . . . )
{
std : : cerr < < DB : : getCurrentExceptionMessage ( true ) < < " \n " ;
auto code = DB : : getCurrentExceptionCode ( ) ;
return code ? code : 1 ;
}
}