From 54a49332536fb88860f798b1f10c096bd0d24390 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 | 10 +- ...gestModule.cc => ChttpCzarIngestModule.cc} | 75 ++++----- ...IngestModule.h => ChttpCzarIngestModule.h} | 33 ++-- ...QueryModule.cc => ChttpCzarQueryModule.cc} | 46 ++--- ...arQueryModule.h => ChttpCzarQueryModule.h} | 31 ++-- src/czar/ChttpCzarSvc.cc | 157 ++++++++++++++++++ src/czar/ChttpCzarSvc.h | 84 ++++++++++ src/czar/ChttpModule.cc | 67 ++++++++ src/czar/ChttpModule.h | 69 ++++++++ src/czar/HttpCzarSvc.cc | 157 ------------------ src/czar/HttpCzarSvc.h | 151 ----------------- src/czar/qserv-czar-http.cc | 22 +-- 13 files changed, 492 insertions(+), 418 deletions(-) rename src/czar/{HttpCzarIngestModule.cc => ChttpCzarIngestModule.cc} (86%) rename src/czar/{HttpCzarIngestModule.h => ChttpCzarIngestModule.h} (90%) rename src/czar/{HttpCzarQueryModule.cc => ChttpCzarQueryModule.cc} (89%) rename src/czar/{HttpCzarQueryModule.h => ChttpCzarQueryModule.h} (75%) create mode 100644 src/czar/ChttpCzarSvc.cc create mode 100644 src/czar/ChttpCzarSvc.h create mode 100644 src/czar/ChttpModule.cc create mode 100644 src/czar/ChttpModule.h delete mode 100644 src/czar/HttpCzarSvc.cc delete mode 100644 src/czar/HttpCzarSvc.h 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..865da92df 100644 --- a/src/czar/CMakeLists.txt +++ b/src/czar/CMakeLists.txt @@ -1,10 +1,11 @@ add_library(czar OBJECT) target_sources(czar PRIVATE + ChttpCzarIngestModule.cc + ChttpCzarQueryModule.cc + ChttpCzarSvc.cc + ChttpModule.cc Czar.cc - HttpCzarIngestModule.cc - HttpCzarSvc.cc - HttpCzarQueryModule.cc HttpModule.cc HttpMonitorModule.cc HttpSvc.cc @@ -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/HttpCzarIngestModule.cc b/src/czar/ChttpCzarIngestModule.cc similarity index 86% rename from src/czar/HttpCzarIngestModule.cc rename to src/czar/ChttpCzarIngestModule.cc index c8ccdef7f..cd24cc7c9 100644 --- a/src/czar/HttpCzarIngestModule.cc +++ b/src/czar/ChttpCzarIngestModule.cc @@ -20,7 +20,7 @@ */ // Class header -#include "czar/HttpCzarIngestModule.h" +#include "czar/ChttpCzarIngestModule.h" // System headers #include @@ -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; @@ -105,27 +104,25 @@ 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) { - HttpCzarIngestModule module(io_service, context, req, resp); +void ChttpCzarIngestModule::process(asio::io_service& io_service, string const& context, + httplib::Request const& req, httplib::Response& resp, + string const& subModuleName, http::AuthType const authType) { + ChttpCzarIngestModule 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(), +ChttpCzarIngestModule::ChttpCzarIngestModule(asio::io_service& io_service, string const& context, + 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), _registryBaseUrl("http://" + cconfig::CzarConfig::instance()->replicationRegistryHost() + ":" + to_string(cconfig::CzarConfig::instance()->replicationRegistryPort())) {} -string HttpCzarIngestModule::context() const { return _context; } +string ChttpCzarIngestModule::context() const { return _context; } -json HttpCzarIngestModule::executeImpl(string const& subModuleName) { +json ChttpCzarIngestModule::executeImpl(string const& subModuleName) { string const func = string(__func__) + "[sub-module='" + subModuleName + "']"; debug(func); if (subModuleName == "INGEST-DATA") @@ -137,7 +134,7 @@ json HttpCzarIngestModule::executeImpl(string const& subModuleName) { throw invalid_argument(context() + func + " unsupported sub-module"); } -json HttpCzarIngestModule::_ingestData() { +json ChttpCzarIngestModule::_ingestData() { debug(__func__); checkApiVersion(__func__, 35); @@ -262,7 +259,7 @@ json HttpCzarIngestModule::_ingestData() { return json(); } -json HttpCzarIngestModule::_deleteDatabase() { +json ChttpCzarIngestModule::_deleteDatabase() { debug(__func__); checkApiVersion(__func__, 34); @@ -277,7 +274,7 @@ json HttpCzarIngestModule::_deleteDatabase() { return json(); } -json HttpCzarIngestModule::_deleteTable() { +json ChttpCzarIngestModule::_deleteTable() { debug(__func__); checkApiVersion(__func__, 34); @@ -295,7 +292,7 @@ json HttpCzarIngestModule::_deleteTable() { return json(); } -vector HttpCzarIngestModule::_getWorkerIds() { +vector ChttpCzarIngestModule::_getWorkerIds() { vector workerIds; auto const workersJson = _requestController(http::Method::GET, "/replication/config"); for (auto const& worker : workersJson.at("config").at("workers")) { @@ -311,7 +308,7 @@ vector HttpCzarIngestModule::_getWorkerIds() { return workerIds; } -void HttpCzarIngestModule::_unpublishOrCreateDatabase(const string& databaseName) { +void ChttpCzarIngestModule::_unpublishOrCreateDatabase(const string& databaseName) { json const config = _requestController(http::Method::GET, "/replication/config").at("config"); for (const auto& database : config.at("databases")) { if (boost::iequals(database.at("database").get(), databaseName)) { @@ -324,7 +321,7 @@ void HttpCzarIngestModule::_unpublishOrCreateDatabase(const string& databaseName _createDirectorTable(databaseName); } -void HttpCzarIngestModule::_createDatabase(string const& databaseName) { +void ChttpCzarIngestModule::_createDatabase(string const& databaseName) { json data = json::object({{"database", databaseName}, {"num_stripes", ::defaultNumStripes}, {"num_sub_stripes", ::defaultNumSubStripes}, @@ -332,29 +329,29 @@ void HttpCzarIngestModule::_createDatabase(string const& databaseName) { _requestController(http::Method::POST, "/ingest/database", data); } -void HttpCzarIngestModule::_deleteDatabase(string const& databaseName) { +void ChttpCzarIngestModule::_deleteDatabase(string const& databaseName) { json data = json::object(); _requestController(http::Method::DELETE, "/ingest/database/" + databaseName, data); } -void HttpCzarIngestModule::_unpublishDatabase(string const& databaseName) { +void ChttpCzarIngestModule::_unpublishDatabase(string const& databaseName) { json data = json::object({{"publish", 0}}); _requestController(http::Method::PUT, "/replication/config/database/" + databaseName, data); } -void HttpCzarIngestModule::_publishDatabase(string const& databaseName) { +void ChttpCzarIngestModule::_publishDatabase(string const& databaseName) { json data = json::object(); _requestController(http::Method::PUT, "/ingest/database/" + databaseName, data); } -void HttpCzarIngestModule::_createTable(string const& databaseName, string const& tableName, - json const& schema) { +void ChttpCzarIngestModule::_createTable(string const& databaseName, string const& tableName, + json const& schema) { json data = json::object( {{"database", databaseName}, {"table", tableName}, {"is_partitioned", 0}, {"schema", schema}}); _requestController(http::Method::POST, "/ingest/table/", data); } -void HttpCzarIngestModule::_createDirectorTable(string const& databaseName) { +void ChttpCzarIngestModule::_createDirectorTable(string const& databaseName) { json const schema = json::array({{{"name", "objectId"}, {"type", "BIGINT"}}, {{"name", "ra"}, {"type", "DOUBLE"}}, {{"name", "dec"}, {"type", "DOUBLE"}}, @@ -377,30 +374,30 @@ void HttpCzarIngestModule::_createDirectorTable(string const& databaseName) { _allocateChunk(databaseName, ::defaultChunkId); } -void HttpCzarIngestModule::_deleteTable(string const& databaseName, string const& tableName) { +void ChttpCzarIngestModule::_deleteTable(string const& databaseName, string const& tableName) { json data = json::object(); _requestController(http::Method::DELETE, "/ingest/table/" + databaseName + "/" + tableName, data); } -uint32_t HttpCzarIngestModule::_startTransaction(string const& databaseName) { +uint32_t ChttpCzarIngestModule::_startTransaction(string const& databaseName) { json data = json::object({{"database", databaseName}}); auto const response = _requestController(http::Method::POST, "/ingest/trans", data); return response.at("databases").at(databaseName).at("transactions")[0].at("id").get(); } -void HttpCzarIngestModule::_abortOrCommitTransaction(uint32_t id, bool abort) { +void ChttpCzarIngestModule::_abortOrCommitTransaction(uint32_t id, bool abort) { json data = json::object(); auto const service = "/ingest/trans/" + to_string(id) + "?abort=" + (abort ? "1" : "0"); _requestController(http::Method::PUT, service, data); } -json HttpCzarIngestModule::_allocateChunk(string const& databaseName, unsigned int chunkId) { +json ChttpCzarIngestModule::_allocateChunk(string const& databaseName, unsigned int chunkId) { json data = json::object({{"database", databaseName}, {"chunk", chunkId}}); return _requestController(http::Method::POST, "/ingest/chunk", data); } -void HttpCzarIngestModule::_createIndexes(string const& func, string const& databaseName, - string const& tableName, json const& indexes) { +void ChttpCzarIngestModule::_createIndexes(string const& func, string const& databaseName, + string const& tableName, json const& indexes) { for (auto const& indexDef : indexes) { if (!indexDef.is_object()) throw http::Error(func, "index definition is not a JSON object"); try { @@ -415,8 +412,8 @@ void HttpCzarIngestModule::_createIndexes(string const& func, string const& data } } -void HttpCzarIngestModule::_countRows(string const& func, string const& databaseName, - string const& tableName) { +void ChttpCzarIngestModule::_countRows(string const& func, string const& databaseName, + string const& tableName) { json data = json::object({{"database", databaseName}, {"table", tableName}, {"row_counters_state_update_policy", "ENABLED"}, @@ -428,7 +425,7 @@ void HttpCzarIngestModule::_countRows(string const& func, string const& database } } -string HttpCzarIngestModule::_controller() { +string ChttpCzarIngestModule::_controller() { if (_controllerBaseUrl.empty()) { auto const response = _requestRegistry(http::Method::GET, "/services"); for (auto const& [id, controller] : response.at("services").at("controllers").items()) { @@ -443,7 +440,7 @@ string HttpCzarIngestModule::_controller() { return _controllerBaseUrl; } -string HttpCzarIngestModule::_worker(string const& workerId) { +string ChttpCzarIngestModule::_worker(string const& workerId) { if (_workerBaseUrls.empty()) { auto const response = _requestRegistry(http::Method::GET, "/services"); for (auto const& [id, worker] : response.at("services").at("workers").items()) { @@ -458,7 +455,7 @@ string HttpCzarIngestModule::_worker(string const& workerId) { return _workerBaseUrls.at(workerId); } -json HttpCzarIngestModule::_request(http::Method method, string const& url, json& data) { +json ChttpCzarIngestModule::_request(http::Method method, string const& url, json& data) { json const errorExt = json::object( {{"method", http::method2string(method)}, {"url", url}, {"timeout_sec", _timeoutSec}}); auto const request = _asyncRequest(method, url, data); @@ -480,8 +477,8 @@ json HttpCzarIngestModule::_request(http::Method method, string const& url, json return response; } -shared_ptr HttpCzarIngestModule::_asyncRequest(http::Method method, string const& url, - json& data) { +shared_ptr ChttpCzarIngestModule::_asyncRequest(http::Method method, string const& url, + json& data) { shared_ptr request; if (method == http::Method::GET) { string const url_ = url + "?version=" + to_string(http::MetaModule::version) + @@ -496,7 +493,7 @@ shared_ptr HttpCzarIngestModule::_asyncRequest(http::Method meth return request; } -shared_ptr HttpCzarIngestModule::_asyncPostRequest(string const& url, string const& data) { +shared_ptr ChttpCzarIngestModule::_asyncPostRequest(string const& url, string const& data) { unordered_map const headers({{"Content-Type", "application/json"}}); auto const request = http::AsyncReq::create(_io_service, nullptr, http::Method::POST, url, data, headers); request->setExpirationIval(_timeoutSec); diff --git a/src/czar/HttpCzarIngestModule.h b/src/czar/ChttpCzarIngestModule.h similarity index 90% rename from src/czar/HttpCzarIngestModule.h rename to src/czar/ChttpCzarIngestModule.h index 4ce9ea237..81a9af3d0 100644 --- a/src/czar/HttpCzarIngestModule.h +++ b/src/czar/ChttpCzarIngestModule.h @@ -18,8 +18,8 @@ * the GNU General Public License along with this program. If not, * see . */ -#ifndef LSST_QSERV_CZAR_HTTPCZARINGESTMODULE_H -#define LSST_QSERV_CZAR_HTTPCZARINGESTMODULE_H +#ifndef LSST_QSERV_CZAR_CHTTPCZARINGESTMODULE_H +#define LSST_QSERV_CZAR_CHTTPCZARINGESTMODULE_H // System headers #include @@ -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,19 +41,19 @@ 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 { /** - * Class HttpCzarIngestModule implements a handler for processing requests for ingesting + * Class ChttpCzarIngestModule implements a handler for processing requests for ingesting * user-generated data prodicts via the HTTP-based frontend. */ -class HttpCzarIngestModule : public http::QhttpModule { +class ChttpCzarIngestModule : public http::ChttpModule { public: /** * @note supported values for parameter 'subModuleName' are: @@ -64,24 +64,23 @@ 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; - HttpCzarIngestModule(HttpCzarIngestModule const&) = delete; - HttpCzarIngestModule& operator=(HttpCzarIngestModule const&) = delete; + ChttpCzarIngestModule() = delete; + ChttpCzarIngestModule(ChttpCzarIngestModule const&) = delete; + ChttpCzarIngestModule& operator=(ChttpCzarIngestModule const&) = delete; - virtual ~HttpCzarIngestModule() = default; + virtual ~ChttpCzarIngestModule() = default; protected: virtual std::string context() const final; virtual nlohmann::json executeImpl(std::string const& subModuleName) final; private: - HttpCzarIngestModule(boost::asio::io_service& io_service, std::string const& context, - std::shared_ptr const& req, - std::shared_ptr const& resp); + ChttpCzarIngestModule(boost::asio::io_service& io_service, std::string const& context, + httplib::Request const& req, httplib::Response& resp); nlohmann::json _ingestData(); nlohmann::json _deleteDatabase(); @@ -233,4 +232,4 @@ class HttpCzarIngestModule : public http::QhttpModule { } // namespace lsst::qserv::czar -#endif // LSST_QSERV_CZAR_HTTPCZARINGESTMODULE_H +#endif // LSST_QSERV_CZAR_CHTTPCZARINGESTMODULE_H diff --git a/src/czar/HttpCzarQueryModule.cc b/src/czar/ChttpCzarQueryModule.cc similarity index 89% rename from src/czar/HttpCzarQueryModule.cc rename to src/czar/ChttpCzarQueryModule.cc index 5d45e77d0..30ebd258f 100644 --- a/src/czar/HttpCzarQueryModule.cc +++ b/src/czar/ChttpCzarQueryModule.cc @@ -20,7 +20,7 @@ */ // Class header -#include "czar/HttpCzarQueryModule.h" +#include "czar/ChttpCzarQueryModule.h" // System headers #include @@ -52,18 +52,18 @@ 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) { - HttpCzarQueryModule module(context, req, resp); +void ChttpCzarQueryModule::process(string const& context, httplib::Request const& req, + httplib::Response& resp, string const& subModuleName, + http::AuthType const authType) { + ChttpCzarQueryModule 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) {} +ChttpCzarQueryModule::ChttpCzarQueryModule(string const& context, httplib::Request const& req, + httplib::Response& resp) + : ChttpModule(context, req, resp) {} -json HttpCzarQueryModule::executeImpl(string const& subModuleName) { +json ChttpCzarQueryModule::executeImpl(string const& subModuleName) { string const func = string(__func__) + "[sub-module='" + subModuleName + "']"; debug(func); if (subModuleName == "SUBMIT") @@ -79,7 +79,7 @@ json HttpCzarQueryModule::executeImpl(string const& subModuleName) { throw invalid_argument(context() + func + " unsupported sub-module"); } -json HttpCzarQueryModule::_submit() { +json ChttpCzarQueryModule::_submit() { debug(__func__); checkApiVersion(__func__, 35); @@ -91,14 +91,14 @@ json HttpCzarQueryModule::_submit() { return _waitAndExtractResult(submitResult, binaryEncoding); } -json HttpCzarQueryModule::_submitAsync() { +json ChttpCzarQueryModule::_submitAsync() { debug(__func__); checkApiVersion(__func__, 32); SubmitResult const submitResult = _getRequestParamsAndSubmit(__func__, true); return json::object({{"queryId", submitResult.queryId}}); } -SubmitResult HttpCzarQueryModule::_getRequestParamsAndSubmit(string const& func, bool async) { +SubmitResult ChttpCzarQueryModule::_getRequestParamsAndSubmit(string const& func, bool async) { string const userQuery = body().required("query"); string const defaultDatabase = body().optional("database", string()); debug(__func__, "query=" + userQuery); @@ -113,7 +113,7 @@ SubmitResult HttpCzarQueryModule::_getRequestParamsAndSubmit(string const& func, return submitResult; } -json HttpCzarQueryModule::_cancel() { +json ChttpCzarQueryModule::_cancel() { debug(__func__); checkApiVersion(__func__, 30); QueryId const queryId = _getQueryId(); @@ -122,7 +122,7 @@ json HttpCzarQueryModule::_cancel() { return json::object(); } -json HttpCzarQueryModule::_status() { +json ChttpCzarQueryModule::_status() { debug(__func__); checkApiVersion(__func__, 30); SubmitResult const submitResult = _getQueryInfo(); @@ -136,7 +136,7 @@ json HttpCzarQueryModule::_status() { return json::object({{"status", statusJson}}); } -json HttpCzarQueryModule::_result() { +json ChttpCzarQueryModule::_result() { debug(__func__); checkApiVersion(__func__, 35); string const binaryEncodingStr = query().optionalString("binary_encoding", "hex"); @@ -145,7 +145,7 @@ json HttpCzarQueryModule::_result() { return _waitAndExtractResult(_getQueryInfo(), binaryEncoding); } -QueryId HttpCzarQueryModule::_getQueryId() const { +QueryId ChttpCzarQueryModule::_getQueryId() const { // The input is going to sanitized by turning the string into a number of // the corresponding type to ensure it's formally valid. string const queryIdStr = params().at("qid"); @@ -153,7 +153,7 @@ QueryId HttpCzarQueryModule::_getQueryId() const { return stoull(queryIdStr); } -SubmitResult HttpCzarQueryModule::_getQueryInfo() const { +SubmitResult ChttpCzarQueryModule::_getQueryInfo() const { QueryId const queryId = _getQueryId(); SubmitResult submitResult; try { @@ -170,8 +170,8 @@ SubmitResult HttpCzarQueryModule::_getQueryInfo() const { return submitResult; } -json HttpCzarQueryModule::_waitAndExtractResult(SubmitResult const& submitResult, - http::BinaryEncodingMode binaryEncoding) const { +json ChttpCzarQueryModule::_waitAndExtractResult(SubmitResult const& submitResult, + http::BinaryEncodingMode binaryEncoding) const { // Block the current thread before the query will finish or fail. string const messageSelectQuery = "SELECT chunkId, code, message, severity+0, timeStamp FROM " + submitResult.messageTable; @@ -247,7 +247,7 @@ json HttpCzarQueryModule::_waitAndExtractResult(SubmitResult const& submitResult return json::object({{"schema", schemaJson}, {"rows", rowsJson}}); } -void HttpCzarQueryModule::_dropTable(string const& tableName) const { +void ChttpCzarQueryModule::_dropTable(string const& tableName) const { if (tableName.empty()) return; string const query = "DROP TABLE " + tableName; debug(__func__, query); @@ -259,7 +259,7 @@ void HttpCzarQueryModule::_dropTable(string const& tableName) const { } } -json HttpCzarQueryModule::_schemaToJson(sql::Schema const& schema) const { +json ChttpCzarQueryModule::_schemaToJson(sql::Schema const& schema) const { json schemaJson = json::array(); for (auto const& colDef : schema.columns) { json columnJson = json::object(); @@ -280,8 +280,8 @@ json HttpCzarQueryModule::_schemaToJson(sql::Schema const& schema) const { return schemaJson; } -json HttpCzarQueryModule::_rowsToJson(sql::SqlResults& results, json const& schemaJson, - http::BinaryEncodingMode binaryEncoding) const { +json ChttpCzarQueryModule::_rowsToJson(sql::SqlResults& results, json const& schemaJson, + http::BinaryEncodingMode binaryEncoding) const { // Extract the column binary attributes into the vector. Checkimg column type // status in the vector should work significantly faster comparing with JSON. size_t const numColumns = schemaJson.size(); diff --git a/src/czar/HttpCzarQueryModule.h b/src/czar/ChttpCzarQueryModule.h similarity index 75% rename from src/czar/HttpCzarQueryModule.h rename to src/czar/ChttpCzarQueryModule.h index 0f267ba07..94d6557fb 100644 --- a/src/czar/HttpCzarQueryModule.h +++ b/src/czar/ChttpCzarQueryModule.h @@ -18,8 +18,8 @@ * the GNU General Public License along with this program. If not, * see . */ -#ifndef LSST_QSERV_CZAR_HTTPCZARQUERYMODULE_H -#define LSST_QSERV_CZAR_HTTPCZARQUERYMODULE_H +#ifndef LSST_QSERV_CZAR_CHTTPCZARQUERYMODULE_H +#define LSST_QSERV_CZAR_CHTTPCZARQUERYMODULE_H // System headers #include @@ -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; @@ -53,10 +53,10 @@ struct Schema; namespace lsst::qserv::czar { /** - * Class HttpCzarQueryModule implements a handler for processing user + * Class ChttpCzarQueryModule implements a handler for processing user * queries submitted to Czar via the HTTP-based frontend. */ -class HttpCzarQueryModule : public czar::HttpModule { +class ChttpCzarQueryModule : public czar::ChttpModule { public: /** * @note supported values for parameter 'subModuleName' are: @@ -68,22 +68,21 @@ 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; - HttpCzarQueryModule(HttpCzarQueryModule const&) = delete; - HttpCzarQueryModule& operator=(HttpCzarQueryModule const&) = delete; + ChttpCzarQueryModule() = delete; + ChttpCzarQueryModule(ChttpCzarQueryModule const&) = delete; + ChttpCzarQueryModule& operator=(ChttpCzarQueryModule const&) = delete; - ~HttpCzarQueryModule() final = default; + ~ChttpCzarQueryModule() final = default; protected: 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); + ChttpCzarQueryModule(std::string const& context, httplib::Request const& req, httplib::Response& resp); nlohmann::json _submit(); nlohmann::json _submitAsync(); @@ -104,4 +103,4 @@ class HttpCzarQueryModule : public czar::HttpModule { } // namespace lsst::qserv::czar -#endif // LSST_QSERV_CZAR_HTTPCZARQUERYMODULE_H +#endif // LSST_QSERV_CZAR_CHTTPCZARQUERYMODULE_H diff --git a/src/czar/ChttpCzarSvc.cc b/src/czar/ChttpCzarSvc.cc new file mode 100644 index 000000000..892e4b162 --- /dev/null +++ b/src/czar/ChttpCzarSvc.cc @@ -0,0 +1,157 @@ +/* + * 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/ChttpCzarSvc.h" + +// 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/ChttpCzarIngestModule.h" +#include "czar/ChttpCzarQueryModule.h" +#include "http/ChttpMetaModule.h" + +// LSST headers +#include "lsst/log/Log.h" + +using namespace nlohmann; +using namespace std; + +namespace { + +LOG_LOGGER _log = LOG_GET("lsst.qserv.czar.ChttpCzarSvc"); + +string const serviceName = "CZAR-FRONTEND "; + +} // namespace + +namespace lsst::qserv::czar { + +shared_ptr ChttpCzarSvc::create(int port, unsigned int numThreads, string const& sslCertFile, + string const& sslPrivateKeyFile) { + return shared_ptr(new ChttpCzarSvc(port, numThreads, sslCertFile, sslPrivateKeyFile)); +} + +ChttpCzarSvc::ChttpCzarSvc(int port, unsigned int numThreads, string const& sslCertFile, + string const& sslPrivateKeyFile) + : _port(port), + _numThreads(numThreads), + _sslCertFile(sslCertFile), + _sslPrivateKeyFile(sslPrivateKeyFile) { + _createAndConfigure(); +} + +void ChttpCzarSvc::startAndWait() { + string const context = "czar::ChttpCzarSvc::" + string(__func__) + " "; + + // 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(); + + // 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 < _numBoostAsioThreads; ++i) { + _threads.push_back(make_unique([self = shared_from_this()]() { self->_io_service.run(); })); + } + if (!_svr->listen_after_bind()) throw runtime_error(context + "Failed to start the server"); +} + +void ChttpCzarSvc::_createAndConfigure() { + string const context = "czar::ChttpCzarSvc::" + string(__func__) + " "; + if (_sslCertFile.empty()) { + throw invalid_argument(context + "SSL certificate file is not valid"); + } + if (_sslPrivateKeyFile.empty()) { + throw invalid_argument(context + "SSL private key file is not valid"); + } + _svr = make_unique(_sslCertFile.data(), _sslPrivateKeyFile.data()); + if (!_svr->is_valid()) { + throw runtime_error(context + "Failed to create the server."); + } + _svr->new_task_queue = [&] { return new httplib::ThreadPool(_numThreads, _maxQueuedRequests); }; + if (_port == 0) { + _port = _svr->bind_to_any_port(_bindAddr, _port); + if (_port < 0) { + throw runtime_error(context + "Failed to bind the server to any port."); + } + } else { + if (!_svr->bind_to_port(_bindAddr, _port)) { + throw runtime_error(context + "Failed to bind the server to the port: " + to_string(_port)); + } + } + LOGS(_log, LOG_LVL_INFO, context + "started on port " + to_string(_port)); +} + +void ChttpCzarSvc::_registerHandlers() { + string const context = "czar::ChttpCzarSvc::" + string(__func__) + " "; + if (_svr == nullptr) { + throw logic_error(context + "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) { + ChttpCzarQueryModule::process(::serviceName, req, resp, "SUBMIT"); + }); + _svr->Post("/query-async", [self](httplib::Request const& req, httplib::Response& resp) { + ChttpCzarQueryModule::process(::serviceName, req, resp, "SUBMIT-ASYNC"); + }); + _svr->Delete("/query-async/:qid", [self](httplib::Request const& req, httplib::Response& resp) { + ChttpCzarQueryModule::process(::serviceName, req, resp, "CANCEL"); + }); + _svr->Get("/query-async/status/:qid", [self](httplib::Request const& req, httplib::Response& resp) { + ChttpCzarQueryModule::process(::serviceName, req, resp, "STATUS"); + }); + _svr->Get("/query-async/result/:qid", [self](httplib::Request const& req, httplib::Response& resp) { + ChttpCzarQueryModule::process(::serviceName, req, resp, "RESULT"); + }); + _svr->Post("/ingest/data", [self](httplib::Request const& req, httplib::Response& resp) { + ChttpCzarIngestModule::process(self->_io_service, ::serviceName, req, resp, "INGEST-DATA"); + }); + _svr->Delete("/ingest/database/:database", [self](httplib::Request const& req, httplib::Response& resp) { + ChttpCzarIngestModule::process(self->_io_service, ::serviceName, req, resp, "DELETE-DATABASE"); + }); + _svr->Delete( + "/ingest/table/:database/:table", [self](httplib::Request const& req, httplib::Response& resp) { + ChttpCzarIngestModule::process(self->_io_service, ::serviceName, req, resp, "DELETE-TABLE"); + }); +} + +} // namespace lsst::qserv::czar diff --git a/src/czar/ChttpCzarSvc.h b/src/czar/ChttpCzarSvc.h new file mode 100644 index 000000000..d52156a9b --- /dev/null +++ b/src/czar/ChttpCzarSvc.h @@ -0,0 +1,84 @@ +/* + * 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_CHTTPCZARSVC_H +#define LSST_QSERV_CZAR_CHTTPCZARSVC_H + +// System headers +#include +#include +#include +#include + +// Third party headers +#include "boost/asio.hpp" + +// Forward declarations +namespace httplib { +class SSLServer; +} // namespace httplib + +namespace lsst::qserv::wcontrol { +class Foreman; +} // namespace lsst::qserv::wcontrol + +// This header declarations +namespace lsst::qserv::czar { + +/** + * Class ChttpCzarSvc is the HTTP server for processing user requests. + */ +class ChttpCzarSvc : public std::enable_shared_from_this { +public: + 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: + ChttpCzarSvc(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; + std::vector> _threads; +}; + +} // namespace lsst::qserv::czar + +#endif // LSST_QSERV_CZAR_CHTTPCZARSVC_H 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/HttpCzarSvc.cc b/src/czar/HttpCzarSvc.cc deleted file mode 100644 index ca4326904..000000000 --- a/src/czar/HttpCzarSvc.cc +++ /dev/null @@ -1,157 +0,0 @@ -/* - * 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/HttpCzarSvc.h" - -// System headers -#include - -// Qserv headers -#include "cconfig/CzarConfig.h" -#include "czar/HttpCzarIngestModule.h" -#include "czar/HttpCzarQueryModule.h" -#include "http/MetaModule.h" -#include "qhttp/Server.h" - -// LSST headers -#include "lsst/log/Log.h" - -using namespace nlohmann; -using namespace std; - -namespace { - -LOG_LOGGER _log = LOG_GET("lsst.qserv.czar.HttpCzarSvc"); - -string const serviceName = "CZAR-FRONTEND "; - -} // namespace - -namespace lsst::qserv::czar { - -shared_ptr HttpCzarSvc::create(uint16_t port, unsigned int numThreads) { - return shared_ptr(new HttpCzarSvc(port, numThreads)); -} - -HttpCzarSvc::HttpCzarSvc(uint16_t port, unsigned int numThreads) : _port(port), _numThreads(numThreads) {} - -uint16_t HttpCzarSvc::start() { - 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(); - - // 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(); - - // 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(); })); - } - auto const actualPort = _httpServerPtr->getPort(); - LOGS(_log, LOG_LVL_INFO, context + "started on port " + to_string(actualPort)); - return actualPort; -} - -void HttpCzarSvc::stop() { - 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(); - - LOGS(_log, LOG_LVL_INFO, context + "stopped"); -} - -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(); - } - LOGS(_log, LOG_LVL_INFO, context + "unlocked"); -} - -} // namespace lsst::qserv::czar diff --git a/src/czar/HttpCzarSvc.h b/src/czar/HttpCzarSvc.h deleted file mode 100644 index b85e8313e..000000000 --- a/src/czar/HttpCzarSvc.h +++ /dev/null @@ -1,151 +0,0 @@ -/* - * 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_HTTPCZARSVC_H -#define LSST_QSERV_CZAR_HTTPCZARSVC_H - -// System headers -#include -#include -#include -#include - -// Third party headers -#include "boost/asio.hpp" - -namespace lsst::qserv::qhttp { -class Server; -} // namespace lsst::qserv::qhttp - -namespace lsst::qserv::wcontrol { -class Foreman; -} // namespace lsst::qserv::wcontrol - -// This header declarations -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(); - -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. - boost::asio::io_service _io_service; - - /// The thread pool for running ASIO services. - std::vector> _threads; -}; - -} // namespace lsst::qserv::czar - -#endif // LSST_QSERV_CZAR_HTTPCZARSVC_H diff --git a/src/czar/qserv-czar-http.cc b/src/czar/qserv-czar-http.cc index 65950647e..48d46b037 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 @@ -31,16 +31,18 @@ // Qserv headers #include "czar/Czar.h" -#include "czar/HttpCzarSvc.h" -#include "global/stringUtil.h" +#include "czar/ChttpCzarSvc.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::ChttpCzarSvc::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 +}