From 26505950443df50138d5fe75f6689150201885f7 Mon Sep 17 00:00:00 2001 From: Igor Gaponenko Date: Wed, 3 Jul 2024 16:36:26 -0700 Subject: [PATCH] Implemented HTTPS-based Czar front-end Eliminated classes of the QHTTP-based version of the Czar frontend --- .../python/lsst/qserv/admin/cli/entrypoint.py | 8 +- src/czar/CMakeLists.txt | 8 +- src/czar/ChttpModule.cc | 67 +++++++ src/czar/ChttpModule.h | 69 +++++++ src/czar/HttpCzarIngestModule.cc | 11 +- src/czar/HttpCzarIngestModule.h | 15 +- src/czar/HttpCzarQueryModule.cc | 11 +- src/czar/HttpCzarQueryModule.h | 15 +- src/czar/HttpCzarSvc.cc | 177 +++++++++--------- src/czar/HttpCzarSvc.h | 127 +++---------- src/czar/HttpMonitorModule.cc | 2 +- src/czar/HttpMonitorModule.h | 4 +- src/czar/{HttpModule.cc => QhttpModule.cc} | 10 +- src/czar/{HttpModule.h => QhttpModule.h} | 22 +-- src/czar/qserv-czar-http.cc | 20 +- 15 files changed, 319 insertions(+), 247 deletions(-) create mode 100644 src/czar/ChttpModule.cc create mode 100644 src/czar/ChttpModule.h rename src/czar/{HttpModule.cc => QhttpModule.cc} (88%) rename src/czar/{HttpModule.h => QhttpModule.h} (74%) diff --git a/src/admin/python/lsst/qserv/admin/cli/entrypoint.py b/src/admin/python/lsst/qserv/admin/cli/entrypoint.py index e992ebfa8..e679ee47c 100644 --- a/src/admin/python/lsst/qserv/admin/cli/entrypoint.py +++ b/src/admin/python/lsst/qserv/admin/cli/entrypoint.py @@ -137,7 +137,13 @@ class CommandInfo: "--lua-cpath=/usr/local/lua/qserv/lib/czarProxy.so --defaults-file={{proxy_cfg_path}}", )), ("czar-http", CommandInfo( - "qserv-czar-http http {{czar_cfg_path}} {{http_frontend_port}} {{http_frontend_threads}} ", + "qserv-czar-http " + "http " + "{{czar_cfg_path}} " + "{{http_frontend_port}} " + "{{http_frontend_threads}} " + "{{http_ssl_cert_file}} " + "{{http_ssl_private_key_file}}", )), ("cmsd-manager", CommandInfo( "cmsd -c {{cmsd_manager_cfg_path}} -n manager -I v4", diff --git a/src/czar/CMakeLists.txt b/src/czar/CMakeLists.txt index 613fdc243..4015913c7 100644 --- a/src/czar/CMakeLists.txt +++ b/src/czar/CMakeLists.txt @@ -1,14 +1,15 @@ add_library(czar OBJECT) target_sources(czar PRIVATE + ChttpModule.cc Czar.cc HttpCzarIngestModule.cc - HttpCzarSvc.cc HttpCzarQueryModule.cc - HttpModule.cc + HttpCzarSvc.cc HttpMonitorModule.cc HttpSvc.cc MessageTable.cc + QhttpModule.cc ) target_include_directories(czar PRIVATE @@ -23,6 +24,7 @@ target_link_libraries(czar PUBLIC util log XrdSsiLib + cpp-httplib ) function(CZAR_UTILS) @@ -51,4 +53,4 @@ endfunction() czar_utils( qserv-czar-http -) \ No newline at end of file +) diff --git a/src/czar/ChttpModule.cc b/src/czar/ChttpModule.cc new file mode 100644 index 000000000..f080b0219 --- /dev/null +++ b/src/czar/ChttpModule.cc @@ -0,0 +1,67 @@ +/* + * LSST Data Management System + * + * This product includes software developed by the + * LSST Project (http://www.lsst.org/). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the LSST License Statement and + * the GNU General Public License along with this program. If not, + * see . + */ + +// Class header +#include "czar/ChttpModule.h" + +// System headers +#include + +// Qserv headers +#include "cconfig/CzarConfig.h" +#include "http/Exceptions.h" +#include "http/RequestBodyJSON.h" +#include "http/RequestQuery.h" + +using namespace std; + +namespace lsst::qserv::czar { + +ChttpModule::ChttpModule(string const& context, httplib::Request const& req, httplib::Response& resp) + : http::ChttpModule(cconfig::CzarConfig::instance()->replicationAuthKey(), + cconfig::CzarConfig::instance()->replicationAdminAuthKey(), req, resp), + _context(context) {} + +string ChttpModule::context() const { return _context; } + +void ChttpModule::enforceCzarName(string const& func) const { + string const czarNameAttrName = "czar"; + string czarName; + if (method() == "GET") { + if (!query().has(czarNameAttrName)) { + throw http::Error(func, "No Czar identifier was provided in the request query."); + } + czarName = query().requiredString(czarNameAttrName); + } else { + if (!body().has(czarNameAttrName)) { + throw http::Error(func, "No Czar identifier was provided in the request body."); + } + czarName = body().required(czarNameAttrName); + } + string const expectedCzarName = cconfig::CzarConfig::instance()->name(); + if (expectedCzarName != czarName) { + string const msg = "Requested Czar identifier '" + czarName + "' does not match the one '" + + expectedCzarName + "' of the current Czar."; + throw http::Error(func, msg); + } +} + +} // namespace lsst::qserv::czar diff --git a/src/czar/ChttpModule.h b/src/czar/ChttpModule.h new file mode 100644 index 000000000..efb91a15b --- /dev/null +++ b/src/czar/ChttpModule.h @@ -0,0 +1,69 @@ +/* + * LSST Data Management System + * + * This product includes software developed by the + * LSST Project (http://www.lsst.org/). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the LSST License Statement and + * the GNU General Public License along with this program. If not, + * see . + */ +#ifndef LSST_QSERV_CZAR_CHTTPMODULE_H +#define LSST_QSERV_CZAR_CHTTPMODULE_H + +// System headers +#include + +// Qserv headers +#include "http/ChttpModule.h" + +// Forward declarations +namespace httplib { +class Request; +class Response; +} // namespace httplib + +// This header declarations +namespace lsst::qserv::czar { + +/** + * Class ChttpModule is an intermediate base class of the Qserv Czar modules. + */ +class ChttpModule : public http::ChttpModule { +public: + ChttpModule() = delete; + ChttpModule(ChttpModule const&) = delete; + ChttpModule& operator=(ChttpModule const&) = delete; + + virtual ~ChttpModule() = default; + +protected: + ChttpModule(std::string const& context, httplib::Request const& req, httplib::Response& resp); + + virtual std::string context() const final; + + /** + * Check if Czar identifier is present in a request and if so then the identifier + * is the same as the one of the current Czar. Throw an exception in case of mismatch. + * @param func The name of the calling context (it's used for error reporting). + * @throws std::invalid_argument If the dentifiers didn't match. + */ + void enforceCzarName(std::string const& func) const; + +private: + std::string const _context; +}; + +} // namespace lsst::qserv::czar + +#endif // LSST_QSERV_CZAR_CHTTPMODULE_H diff --git a/src/czar/HttpCzarIngestModule.cc b/src/czar/HttpCzarIngestModule.cc index c8ccdef7f..a0443d6a2 100644 --- a/src/czar/HttpCzarIngestModule.cc +++ b/src/czar/HttpCzarIngestModule.cc @@ -38,7 +38,6 @@ #include "http/Exceptions.h" #include "http/MetaModule.h" #include "http/RequestBodyJSON.h" -#include "qhttp/Request.h" #include "qhttp/Status.h" using namespace std; @@ -106,17 +105,15 @@ void setProtocolFields(json& data) { namespace lsst::qserv::czar { void HttpCzarIngestModule::process(asio::io_service& io_service, string const& context, - shared_ptr const& req, - shared_ptr const& resp, string const& subModuleName, - http::AuthType const authType) { + httplib::Request const& req, httplib::Response& resp, + string const& subModuleName, http::AuthType const authType) { HttpCzarIngestModule module(io_service, context, req, resp); module.execute(subModuleName, authType); } HttpCzarIngestModule::HttpCzarIngestModule(asio::io_service& io_service, string const& context, - shared_ptr const& req, - shared_ptr const& resp) - : http::QhttpModule(cconfig::CzarConfig::instance()->replicationAuthKey(), + httplib::Request const& req, httplib::Response& resp) + : http::ChttpModule(cconfig::CzarConfig::instance()->replicationAuthKey(), cconfig::CzarConfig::instance()->replicationAdminAuthKey(), req, resp), _io_service(io_service), _context(context), diff --git a/src/czar/HttpCzarIngestModule.h b/src/czar/HttpCzarIngestModule.h index 4ce9ea237..25b53bdf3 100644 --- a/src/czar/HttpCzarIngestModule.h +++ b/src/czar/HttpCzarIngestModule.h @@ -32,8 +32,8 @@ #include "nlohmann/json.hpp" // Qserv headers +#include "http/ChttpModule.h" #include "http/Method.h" -#include "http/QhttpModule.h" // Forward declarations @@ -41,10 +41,10 @@ namespace lsst::qserv::http { class AsyncReq; } // namespace lsst::qserv::http -namespace lsst::qserv::qhttp { +namespace httplib { class Request; class Response; -} // namespace lsst::qserv::qhttp +} // namespace httplib // This header declarations namespace lsst::qserv::czar { @@ -53,7 +53,7 @@ namespace lsst::qserv::czar { * Class HttpCzarIngestModule implements a handler for processing requests for ingesting * user-generated data prodicts via the HTTP-based frontend. */ -class HttpCzarIngestModule : public http::QhttpModule { +class HttpCzarIngestModule : public http::ChttpModule { public: /** * @note supported values for parameter 'subModuleName' are: @@ -64,8 +64,8 @@ class HttpCzarIngestModule : public http::QhttpModule { * @throws std::invalid_argument for unknown values of parameter 'subModuleName' */ static void process(boost::asio::io_service& io_service, std::string const& context, - std::shared_ptr const& req, - std::shared_ptr const& resp, std::string const& subModuleName, + httplib::Request const& req, httplib::Response& resp, + std::string const& subModuleName, http::AuthType const authType = http::AuthType::NONE); HttpCzarIngestModule() = delete; @@ -80,8 +80,7 @@ class HttpCzarIngestModule : public http::QhttpModule { private: HttpCzarIngestModule(boost::asio::io_service& io_service, std::string const& context, - std::shared_ptr const& req, - std::shared_ptr const& resp); + httplib::Request const& req, httplib::Response& resp); nlohmann::json _ingestData(); nlohmann::json _deleteDatabase(); diff --git a/src/czar/HttpCzarQueryModule.cc b/src/czar/HttpCzarQueryModule.cc index 5d45e77d0..ec10497f4 100644 --- a/src/czar/HttpCzarQueryModule.cc +++ b/src/czar/HttpCzarQueryModule.cc @@ -52,16 +52,15 @@ vector const binTypes = {"BIT", "BINARY", "VARBINARY", "TINYBLOB", "BLOB namespace lsst::qserv::czar { -void HttpCzarQueryModule::process(string const& context, shared_ptr const& req, - shared_ptr const& resp, string const& subModuleName, - http::AuthType const authType) { +void HttpCzarQueryModule::process(string const& context, httplib::Request const& req, httplib::Response& resp, + string const& subModuleName, http::AuthType const authType) { HttpCzarQueryModule module(context, req, resp); module.execute(subModuleName, authType); } -HttpCzarQueryModule::HttpCzarQueryModule(string const& context, shared_ptr const& req, - shared_ptr const& resp) - : HttpModule(context, req, resp) {} +HttpCzarQueryModule::HttpCzarQueryModule(string const& context, httplib::Request const& req, + httplib::Response& resp) + : ChttpModule(context, req, resp) {} json HttpCzarQueryModule::executeImpl(string const& subModuleName) { string const func = string(__func__) + "[sub-module='" + subModuleName + "']"; diff --git a/src/czar/HttpCzarQueryModule.h b/src/czar/HttpCzarQueryModule.h index 0f267ba07..eb8ee22a9 100644 --- a/src/czar/HttpCzarQueryModule.h +++ b/src/czar/HttpCzarQueryModule.h @@ -29,7 +29,7 @@ #include "nlohmann/json.hpp" // Qserv headers -#include "czar/HttpModule.h" +#include "czar/ChttpModule.h" #include "global/intTypes.h" #include "http/BinaryEncoding.h" @@ -39,10 +39,10 @@ namespace lsst::qserv::czar { struct SubmitResult; } // namespace lsst::qserv::czar -namespace lsst::qserv::qhttp { +namespace httplib { class Request; class Response; -} // namespace lsst::qserv::qhttp +} // namespace httplib namespace lsst::qserv::sql { class SqlResults; @@ -56,7 +56,7 @@ namespace lsst::qserv::czar { * Class HttpCzarQueryModule implements a handler for processing user * queries submitted to Czar via the HTTP-based frontend. */ -class HttpCzarQueryModule : public czar::HttpModule { +class HttpCzarQueryModule : public czar::ChttpModule { public: /** * @note supported values for parameter 'subModuleName' are: @@ -68,8 +68,8 @@ class HttpCzarQueryModule : public czar::HttpModule { * * @throws std::invalid_argument for unknown values of parameter 'subModuleName' */ - static void process(std::string const& context, std::shared_ptr const& req, - std::shared_ptr const& resp, std::string const& subModuleName, + static void process(std::string const& context, httplib::Request const& req, httplib::Response& resp, + std::string const& subModuleName, http::AuthType const authType = http::AuthType::NONE); HttpCzarQueryModule() = delete; @@ -82,8 +82,7 @@ class HttpCzarQueryModule : public czar::HttpModule { virtual nlohmann::json executeImpl(std::string const& subModuleName) final; private: - HttpCzarQueryModule(std::string const& context, std::shared_ptr const& req, - std::shared_ptr const& resp); + HttpCzarQueryModule(std::string const& context, httplib::Request const& req, httplib::Response& resp); nlohmann::json _submit(); nlohmann::json _submitAsync(); diff --git a/src/czar/HttpCzarSvc.cc b/src/czar/HttpCzarSvc.cc index ca4326904..026855a54 100644 --- a/src/czar/HttpCzarSvc.cc +++ b/src/czar/HttpCzarSvc.cc @@ -25,12 +25,17 @@ // System headers #include +// Third-party headers +#ifndef CPPHTTPLIB_OPENSSL_SUPPORT +#define CPPHTTPLIB_OPENSSL_SUPPORT 1 +#endif +#include + // Qserv headers #include "cconfig/CzarConfig.h" #include "czar/HttpCzarIngestModule.h" #include "czar/HttpCzarQueryModule.h" -#include "http/MetaModule.h" -#include "qhttp/Server.h" +#include "http/ChttpMetaModule.h" // LSST headers #include "lsst/log/Log.h" @@ -44,114 +49,108 @@ LOG_LOGGER _log = LOG_GET("lsst.qserv.czar.HttpCzarSvc"); string const serviceName = "CZAR-FRONTEND "; +template +void throwIf(bool condition, string const& message) { + if (condition) throw T(message); +} + } // namespace namespace lsst::qserv::czar { -shared_ptr HttpCzarSvc::create(uint16_t port, unsigned int numThreads) { - return shared_ptr(new HttpCzarSvc(port, numThreads)); +shared_ptr HttpCzarSvc::create(int port, unsigned int numThreads, string const& sslCertFile, + string const& sslPrivateKeyFile) { + return shared_ptr(new HttpCzarSvc(port, numThreads, sslCertFile, sslPrivateKeyFile)); } -HttpCzarSvc::HttpCzarSvc(uint16_t port, unsigned int numThreads) : _port(port), _numThreads(numThreads) {} +HttpCzarSvc::HttpCzarSvc(int port, unsigned int numThreads, string const& sslCertFile, + string const& sslPrivateKeyFile) + : _port(port), + _numThreads(numThreads), + _sslCertFile(sslCertFile), + _sslPrivateKeyFile(sslPrivateKeyFile) { + _createAndConfigure(); +} -uint16_t HttpCzarSvc::start() { +void HttpCzarSvc::startAndWait() { string const context = "czar::HttpCzarSvc::" + string(__func__) + " "; - if (_httpServerPtr != nullptr) { - throw logic_error(context + "the service is already running."); - } - _httpServerPtr = qhttp::Server::create(_io_service, _port); - auto const self = shared_from_this(); + // IMPORTANT: Request handlers can't be registered in the constructor + // because of the shared_from_this() call. This is because the shared + // pointer is not yet initialized at the time of the constructor call. + _registerHandlers(); - // Make sure the handlers are registered and the server is started before - // launching any BOOST ASIO threads. This will prevent threads from finishing - // due to a lack of work to be done. - _httpServerPtr->addHandlers( - {{"GET", "/meta/version", - [self](shared_ptr const& req, shared_ptr const& resp) { - json const info = json::object( - {{"kind", "qserv-czar-query-frontend"}, - {"id", cconfig::CzarConfig::instance()->id()}, - {"instance_id", cconfig::CzarConfig::instance()->replicationInstanceId()}}); - http::MetaModule::process(::serviceName, info, req, resp, "VERSION"); - }}}); - _httpServerPtr->addHandlers( - {{"POST", "/query", - [self](shared_ptr const& req, shared_ptr const& resp) { - HttpCzarQueryModule::process(::serviceName, req, resp, "SUBMIT"); - }}}); - _httpServerPtr->addHandlers( - {{"POST", "/query-async", - [self](shared_ptr const& req, shared_ptr const& resp) { - HttpCzarQueryModule::process(::serviceName, req, resp, "SUBMIT-ASYNC"); - }}}); - _httpServerPtr->addHandlers( - {{"DELETE", "/query-async/:qid", - [self](shared_ptr const& req, shared_ptr const& resp) { - HttpCzarQueryModule::process(::serviceName, req, resp, "CANCEL"); - }}}); - _httpServerPtr->addHandlers( - {{"GET", "/query-async/status/:qid", - [self](shared_ptr const& req, shared_ptr const& resp) { - HttpCzarQueryModule::process(::serviceName, req, resp, "STATUS"); - }}}); - _httpServerPtr->addHandlers( - {{"GET", "/query-async/result/:qid", - [self](shared_ptr const& req, shared_ptr const& resp) { - HttpCzarQueryModule::process(::serviceName, req, resp, "RESULT"); - }}}); - _httpServerPtr->addHandlers( - {{"POST", "/ingest/data", - [self](shared_ptr const& req, shared_ptr const& resp) { - HttpCzarIngestModule::process(self->_io_service, ::serviceName, req, resp, "INGEST-DATA"); - }}}); - _httpServerPtr->addHandlers( - {{"DELETE", "/ingest/database/:database", - [self](shared_ptr const& req, shared_ptr const& resp) { - HttpCzarIngestModule::process(self->_io_service, ::serviceName, req, resp, - "DELETE-DATABASE"); - }}}); - _httpServerPtr->addHandlers( - {{"DELETE", "/ingest/table/:database/:table", - [self](shared_ptr const& req, shared_ptr const& resp) { - HttpCzarIngestModule::process(self->_io_service, ::serviceName, req, resp, "DELETE-TABLE"); - }}}); - _httpServerPtr->start(); + // This will prevent the I/O service from exiting the .run() + // method event when it will run out of any requests to process. + // Unless the service will be explicitly stopped. + _work.reset(new boost::asio::io_service::work(_io_service)); // Initialize the I/O context and start the service threads. At this point // the server will be ready to service incoming requests. - for (unsigned int i = 0; i < _numThreads; ++i) { - _threads.push_back(make_unique([self]() { self->_io_service.run(); })); + for (unsigned int i = 0; i < _numBoostAsioThreads; ++i) { + _threads.push_back(make_unique([self = shared_from_this()]() { self->_io_service.run(); })); } - auto const actualPort = _httpServerPtr->getPort(); - LOGS(_log, LOG_LVL_INFO, context + "started on port " + to_string(actualPort)); - return actualPort; + bool const started = _svr->listen_after_bind(); + ::throwIf(!started, context + "Failed to start the server"); } -void HttpCzarSvc::stop() { +void HttpCzarSvc::_createAndConfigure() { string const context = "czar::HttpCzarSvc::" + string(__func__) + " "; - if (_httpServerPtr == nullptr) { - throw logic_error(context + "the service is not running."); - } - // Stopping the server and resetting the I/O context will abort the ongoing - // requests and unblock the service threads. - _httpServerPtr->stop(); - _httpServerPtr = nullptr; - _io_service.reset(); + ::throwIf(_sslCertFile.empty(), context + "SSL certificate file is not valid"); + ::throwIf(_sslPrivateKeyFile.empty(), context + "SSL private key file is not valid"); - LOGS(_log, LOG_LVL_INFO, context + "stopped"); -} + _svr = make_unique(_sslCertFile.data(), _sslPrivateKeyFile.data()); + ::throwIf(!_svr->is_valid(), context + "Failed to create the server"); -void HttpCzarSvc::wait() { - string const context = "czar::HttpCzarSvc::" + string(__func__) + " "; - if (_httpServerPtr == nullptr) { - throw logic_error(context + "the service is not running."); - } - for (auto&& t : _threads) { - t->join(); + _svr->new_task_queue = [&] { return new httplib::ThreadPool(_numThreads, _maxQueuedRequests); }; + if (_port == 0) { + _port = _svr->bind_to_any_port(_bindAddr, _port); + ::throwIf(_port < 0, context + "Failed to bind the server to any port"); + } else { + bool const bound = _svr->bind_to_port(_bindAddr, _port); + ::throwIf(!bound, + context + "Failed to bind the server to the port: " + to_string(_port)); } - LOGS(_log, LOG_LVL_INFO, context + "unlocked"); + LOGS(_log, LOG_LVL_INFO, context + "started on port " + to_string(_port)); +} + +void HttpCzarSvc::_registerHandlers() { + ::throwIf(_svr == nullptr, + "czar::HttpCzarSvc::" + string(__func__) + " the server is not initialized"); + auto const self = shared_from_this(); + _svr->Get("/meta/version", [self](httplib::Request const& req, httplib::Response& resp) { + json const info = + json::object({{"kind", "qserv-czar-query-frontend"}, + {"id", cconfig::CzarConfig::instance()->id()}, + {"instance_id", cconfig::CzarConfig::instance()->replicationInstanceId()}}); + http::ChttpMetaModule::process(::serviceName, info, req, resp, "VERSION"); + }); + _svr->Post("/query", [self](httplib::Request const& req, httplib::Response& resp) { + HttpCzarQueryModule::process(::serviceName, req, resp, "SUBMIT"); + }); + _svr->Post("/query-async", [self](httplib::Request const& req, httplib::Response& resp) { + HttpCzarQueryModule::process(::serviceName, req, resp, "SUBMIT-ASYNC"); + }); + _svr->Delete("/query-async/:qid", [self](httplib::Request const& req, httplib::Response& resp) { + HttpCzarQueryModule::process(::serviceName, req, resp, "CANCEL"); + }); + _svr->Get("/query-async/status/:qid", [self](httplib::Request const& req, httplib::Response& resp) { + HttpCzarQueryModule::process(::serviceName, req, resp, "STATUS"); + }); + _svr->Get("/query-async/result/:qid", [self](httplib::Request const& req, httplib::Response& resp) { + HttpCzarQueryModule::process(::serviceName, req, resp, "RESULT"); + }); + _svr->Post("/ingest/data", [self](httplib::Request const& req, httplib::Response& resp) { + HttpCzarIngestModule::process(self->_io_service, ::serviceName, req, resp, "INGEST-DATA"); + }); + _svr->Delete("/ingest/database/:database", [self](httplib::Request const& req, httplib::Response& resp) { + HttpCzarIngestModule::process(self->_io_service, ::serviceName, req, resp, "DELETE-DATABASE"); + }); + _svr->Delete( + "/ingest/table/:database/:table", [self](httplib::Request const& req, httplib::Response& resp) { + HttpCzarIngestModule::process(self->_io_service, ::serviceName, req, resp, "DELETE-TABLE"); + }); } } // namespace lsst::qserv::czar diff --git a/src/czar/HttpCzarSvc.h b/src/czar/HttpCzarSvc.h index b85e8313e..6106d458e 100644 --- a/src/czar/HttpCzarSvc.h +++ b/src/czar/HttpCzarSvc.h @@ -30,9 +30,10 @@ // Third party headers #include "boost/asio.hpp" -namespace lsst::qserv::qhttp { -class Server; -} // namespace lsst::qserv::qhttp +// Forward declarations +namespace httplib { +class SSLServer; +} // namespace httplib namespace lsst::qserv::wcontrol { class Foreman; @@ -43,106 +44,38 @@ namespace lsst::qserv::czar { /** * Class HttpCzarSvc is the HTTP server for processing user requests. - * - * The server creates and manages its own collection of BOOST ASIO service threads. - * The number of threads is specified via the corresponding parameter of the class's - * constructor. - * - * Typical usage of the class: - * @code - * // Create the server. Note, it won't run yet until explicitly started. - * uint16_t const port = 0; // The port will be dynamically allocated at start - * unsigned int const numThreads = 2; // The number of BOOST ASIO threads - * auto const svc = czar::HttpCzarSvc::create(port, numThreads); - * - * // Start the server and get the actual port number. - * uint16_t const actualPort = svc->start(); - * std::cout << "HTTP server is running on port " << actualPort << std::endl; - * - * // Stop the server to release resources. - * svc->stop(); - * svc.reset(); - * @code - * Alternatively, one may wait before the service will finish. In this case - * the server would need to be stopped from some other thread. For example: - * @code - * auto const svc = ... - * svc->start(); - * std::thread([svc]() { - * std::this_thread::sleep_for(60s); - * svc->stop(); - * }); - * svc->wait(); - * @code - * @note The class implementation is NOT thread safe. A correct ordering of - * calls to the methods 'start -> {stop | wait}' is required. */ class HttpCzarSvc : public std::enable_shared_from_this { public: - /** - * The factory will not initialize ASIO context and threads, or start - * the server. This has to be done by calling method HttpCzarSvc::start() - * - * @param port The number of a port to bind to. - * @param numThreads The number of BOOST ASIO threads. - * @return The shared pointer to the running server. - */ - static std::shared_ptr create(uint16_t port, unsigned int numThreads); - - HttpCzarSvc() = delete; - HttpCzarSvc(HttpCzarSvc const&) = delete; - HttpCzarSvc& operator=(HttpCzarSvc const&) = delete; - - ~HttpCzarSvc() = default; - - /** - * Initialize ASIO context and threads, and start the server. - * - * @note Once the server is started it has to be explicitly stopped - * using the counterpart method stop() to allow releasing allocated - * resources and letting the destructor to be executed. Note that - * service threads started by the curent method and the HTTP server - * incerement the reference counter on the shared pointer that is - * returned by the class's factory method. - * - * @return The actual port number on which the server is run. - * @throws std::logic_error If the server is already running. - */ - uint16_t start(); - - /** - * Stop the server and release the relevant resources. - * @throws std::logic_error If the server is not running. - */ - void stop(); - - /** - * Block the calling thread waiting before the server threads will finish. - * @throws std::logic_error If the server is not running. - */ - void wait(); + static std::shared_ptr create(int port, unsigned int numThreads, + std::string const& sslCertFile, + std::string const& sslPrivateKeyFile); + int port() const { return _port; } + void startAndWait(); private: - /** - * The constructor will not initialize ASIO context and threads, or start - * the server. This has to be done by calling method HttpCzarSvc::start() - * @param port The number of a port to bind to. - * @param numThreads The number of BOOST ASIO threads. - */ - HttpCzarSvc(uint16_t port, unsigned int numThreads); - - // Input parameters - - uint16_t const _port; ///< The input port number (could be 0 to allow autoallocation). - unsigned int const _numThreads; ///< The number of the BOOST ASIO service threads. - - /// Worker management requests are processed by this server. - std::shared_ptr _httpServerPtr; - - /// The BOOST ASIO I/O services. + HttpCzarSvc(int port, unsigned int numThreads, std::string const& sslCertFile, + std::string const& sslPrivateKeyFile); + void _createAndConfigure(); + void _registerHandlers(); + + int _port; + unsigned int const _numThreads; + std::string const _sslCertFile; + std::string const _sslPrivateKeyFile; + std::size_t const _maxQueuedRequests = 0; // 0 means unlimited + std::string const _bindAddr = "0.0.0.0"; + std::unique_ptr _svr; + + // The BOOST ASIO I/O services and a thread pool for async communication with + // the Replication Controller and workers. + // TODO: Consider a configuration option for setting the desired number + // of threads in the pool. + + unsigned int const _numBoostAsioThreads = 2; + + std::unique_ptr _work; boost::asio::io_service _io_service; - - /// The thread pool for running ASIO services. std::vector> _threads; }; diff --git a/src/czar/HttpMonitorModule.cc b/src/czar/HttpMonitorModule.cc index f8c58e503..491e2402c 100644 --- a/src/czar/HttpMonitorModule.cc +++ b/src/czar/HttpMonitorModule.cc @@ -48,7 +48,7 @@ void HttpMonitorModule::process(string const& context, shared_ptr const& req, shared_ptr const& resp) - : HttpModule(context, req, resp) {} + : QhttpModule(context, req, resp) {} json HttpMonitorModule::executeImpl(string const& subModuleName) { string const func = string(__func__) + "[sub-module='" + subModuleName + "']"; diff --git a/src/czar/HttpMonitorModule.h b/src/czar/HttpMonitorModule.h index 82183e853..0982472ab 100644 --- a/src/czar/HttpMonitorModule.h +++ b/src/czar/HttpMonitorModule.h @@ -29,7 +29,7 @@ #include "nlohmann/json.hpp" // Qserv headers -#include "czar/HttpModule.h" +#include "czar/QhttpModule.h" // Forward declarations namespace lsst::qserv::qhttp { @@ -44,7 +44,7 @@ namespace lsst::qserv::czar { * Class HttpMonitorModule implements a handler for reporting various run-time * monitoring metrics and statistics collected at the Qserv worker. */ -class HttpMonitorModule : public czar::HttpModule { +class HttpMonitorModule : public QhttpModule { public: /** * @note supported values for parameter 'subModuleName' are: diff --git a/src/czar/HttpModule.cc b/src/czar/QhttpModule.cc similarity index 88% rename from src/czar/HttpModule.cc rename to src/czar/QhttpModule.cc index 680431010..390b63ab3 100644 --- a/src/czar/HttpModule.cc +++ b/src/czar/QhttpModule.cc @@ -20,7 +20,7 @@ */ // Class header -#include "czar/HttpModule.h" +#include "czar/QhttpModule.h" // System headers #include @@ -36,15 +36,15 @@ using namespace std; namespace lsst::qserv::czar { -HttpModule::HttpModule(string const& context, shared_ptr const& req, - shared_ptr const& resp) +QhttpModule::QhttpModule(string const& context, shared_ptr const& req, + shared_ptr const& resp) : http::QhttpModule(cconfig::CzarConfig::instance()->replicationAuthKey(), cconfig::CzarConfig::instance()->replicationAdminAuthKey(), req, resp), _context(context) {} -string HttpModule::context() const { return _context; } +string QhttpModule::context() const { return _context; } -void HttpModule::enforceCzarName(string const& func) const { +void QhttpModule::enforceCzarName(string const& func) const { string const czarNameAttrName = "czar"; string czarName; if (req()->method == "GET") { diff --git a/src/czar/HttpModule.h b/src/czar/QhttpModule.h similarity index 74% rename from src/czar/HttpModule.h rename to src/czar/QhttpModule.h index f64b44ce7..dc7eb8b75 100644 --- a/src/czar/HttpModule.h +++ b/src/czar/QhttpModule.h @@ -18,8 +18,8 @@ * the GNU General Public License along with this program. If not, * see . */ -#ifndef LSST_QSERV_CZAR_HTTPMODULE_H -#define LSST_QSERV_CZAR_HTTPMODULE_H +#ifndef LSST_QSERV_CZAR_QHTTPMODULE_H +#define LSST_QSERV_CZAR_QHTTPMODULE_H // System headers #include @@ -38,19 +38,19 @@ class Response; namespace lsst::qserv::czar { /** - * Class HttpModule is an intermediate base class of the Qserv Czar modules. + * Class QhttpModule is an intermediate base class of the Qserv Czar modules. */ -class HttpModule : public http::QhttpModule { +class QhttpModule : public http::QhttpModule { public: - HttpModule() = delete; - HttpModule(HttpModule const&) = delete; - HttpModule& operator=(HttpModule const&) = delete; + QhttpModule() = delete; + QhttpModule(QhttpModule const&) = delete; + QhttpModule& operator=(QhttpModule const&) = delete; - virtual ~HttpModule() = default; + virtual ~QhttpModule() = default; protected: - HttpModule(std::string const& context, std::shared_ptr const& req, - std::shared_ptr const& resp); + QhttpModule(std::string const& context, std::shared_ptr const& req, + std::shared_ptr const& resp); virtual std::string context() const final; @@ -68,4 +68,4 @@ class HttpModule : public http::QhttpModule { } // namespace lsst::qserv::czar -#endif // LSST_QSERV_CZAR_HTTPMODULE_H +#endif // LSST_QSERV_CZAR_QHTTPMODULE_H diff --git a/src/czar/qserv-czar-http.cc b/src/czar/qserv-czar-http.cc index 65950647e..e97467c24 100644 --- a/src/czar/qserv-czar-http.cc +++ b/src/czar/qserv-czar-http.cc @@ -20,7 +20,7 @@ */ /** - * The HTTP-based frontend for Czar. + * The CPP-HTTPLIB-based frontend for Czar. */ // System headers @@ -32,15 +32,17 @@ // Qserv headers #include "czar/Czar.h" #include "czar/HttpCzarSvc.h" -#include "global/stringUtil.h" +#include "global/stringUtil.h" // for qserv::stoui using namespace std; namespace czar = lsst::qserv::czar; namespace qserv = lsst::qserv; namespace { -string const usage = "Usage: "; -} + +string const usage = "Usage: "; + +} // namespace int main(int argc, char* argv[]) { // Parse command-line parameters to get: @@ -73,13 +75,13 @@ int main(int argc, char* argv[]) { } try { auto const czar = czar::Czar::createCzar(configFilePath, czarName); - auto const svc = czar::HttpCzarSvc::create(port, numThreads); - port = svc->start(); - cout << __func__ << ": HTTP-based query processing service of Czar started on port " << port << endl; - svc->wait(); + auto const svc = czar::HttpCzarSvc::create(port, numThreads, sslCertFile, sslPrivateKeyFile); + cout << __func__ << ": HTTP-based query processing service of Czar bound to port: " << svc->port() + << endl; + svc->startAndWait(); } catch (exception const& ex) { cerr << __func__ << ": the application failed, exception: " << ex.what() << endl; return 1; } return 0; -} \ No newline at end of file +}