diff --git a/admin/local/docker/compose/docker-compose.yml b/admin/local/docker/compose/docker-compose.yml
index 1af25e667..a8c13e90d 100644
--- a/admin/local/docker/compose/docker-compose.yml
+++ b/admin/local/docker/compose/docker-compose.yml
@@ -399,6 +399,8 @@ services:
--xrootd-manager czar_xrootd
--http-frontend-port 4048
--http-frontend-threads 4
+ --http-ssl-cert-file /config-etc/ssl/czar-cert.pem
+ --http-ssl-private-key-file /config-etc/ssl/czar-key.pem
--log-cfg-file=/config-etc/log/log-czar-proxy.cnf
--repl-instance-id qserv_proj
--repl-auth-key replauthkey
diff --git a/admin/tools/docker/base/Dockerfile b/admin/tools/docker/base/Dockerfile
index c9ea08095..71bc86d55 100644
--- a/admin/tools/docker/base/Dockerfile
+++ b/admin/tools/docker/base/Dockerfile
@@ -232,9 +232,10 @@ WORKDIR /home/qserv
RUN mkdir -p /qserv/data && \
mkdir /config-etc && \
+ mkdir /config-etc/ssl && \
mkdir -p /qserv/run/tmp && \
mkdir -p /var/run/xrootd && \
- chown qserv:qserv /qserv/data /config-etc /qserv/run/tmp /var/run/xrootd
+ chown qserv:qserv /qserv/data /config-etc /config-etc/ssl /qserv/run/tmp /var/run/xrootd
RUN alternatives --install /usr/bin/python python /usr/bin/python3.9 1
ENV PYTHONPATH "${PYTHONPATH}:/usr/local/python"
diff --git a/src/admin/etc/integration_tests.yaml b/src/admin/etc/integration_tests.yaml
index d28634082..64a4783c3 100644
--- a/src/admin/etc/integration_tests.yaml
+++ b/src/admin/etc/integration_tests.yaml
@@ -3,7 +3,7 @@ reference-db-uri: mysql://qsmaster@integration-test-reference:3306
reference-db-admin-uri: mysql://root:CHANGEME@integration-test-reference:3306
replication-controller-uri: repl://@repl_controller:25081
qserv-uri: qserv://qsmaster@czar_proxy:4040
-qserv-http-uri: http://czar_http:4048
+qserv-http-uri: https://czar_http:4048
czar-db-admin-uri: mysql://root:CHANGEME@czar_mariadb:3306
# The folder where the itest sources will be mounted in the container:
qserv-testdata-dir: /tmp/qserv/itest_src
diff --git a/src/admin/python/lsst/qserv/admin/cli/entrypoint.py b/src/admin/python/lsst/qserv/admin/cli/entrypoint.py
index e992ebfa8..34905ec26 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",
@@ -563,6 +569,18 @@ def proxy(ctx: click.Context, **kwargs: Any) -> None:
help="The number of threads for the HTTP server of the frontend. The value of http_frontend_threads is passed"
" as a command-line parameter to the application."
)
+@click.option(
+ "--http-ssl-cert-file",
+ help="A location of a file containing an SSL/TSL certificate.",
+ default="/config-etc/ssl/czar-cert.pem",
+ show_default=True,
+)
+@click.option(
+ "--http-ssl-private-key-file",
+ help="A location of a file containing an SSL/TSL private key.",
+ default="/config-etc/ssl/czar-key.pem",
+ show_default=True,
+)
@click.option(
"--czar-cfg-file",
help="Path to the czar config file.",
@@ -595,8 +613,10 @@ def czar_http(ctx: click.Context, **kwargs: Any) -> None:
db_uri=targs["db_uri"],
czar_cfg_file=targs["czar_cfg_file"],
czar_cfg_path=targs["czar_cfg_path"],
- cmd=targs["cmd"],
log_cfg_file=targs["log_cfg_file"],
+ http_ssl_cert_file=targs["http_ssl_cert_file"],
+ http_ssl_private_key_file=targs["http_ssl_private_key_file"],
+ cmd=targs["cmd"],
)
diff --git a/src/admin/python/lsst/qserv/admin/cli/script.py b/src/admin/python/lsst/qserv/admin/cli/script.py
index 1fb0a0a73..7dbbf3471 100644
--- a/src/admin/python/lsst/qserv/admin/cli/script.py
+++ b/src/admin/python/lsst/qserv/admin/cli/script.py
@@ -23,6 +23,7 @@
import logging
import os
import shlex
+import socket
import subprocess
import sys
import time
@@ -657,6 +658,8 @@ def enter_czar_http(
czar_cfg_file: str,
czar_cfg_path: str,
log_cfg_file: str,
+ http_ssl_cert_file : str,
+ http_ssl_private_key_file : str,
cmd: str,
) -> None:
"""Entrypoint script for the proxy container.
@@ -673,6 +676,10 @@ def enter_czar_http(
Location to render the czar config file.
log_cfg_file : `str`
Location of the log4cxx config file.
+ http_ssl_cert_file : `str`
+ The path to the SSL certificate file.
+ http_ssl_private_key_file : `str`
+ The path to the SSL private key file.
cmd : `str`
The jinja2 template for the command for this function to execute.
"""
@@ -700,6 +707,32 @@ def enter_czar_http(
_do_smig_block(qmeta_smig_dir, "qmeta", db_uri)
+ # check if the SSL certificate and private key files exist and create
+ # them if they don't.
+ if not os.path.exists(http_ssl_cert_file) or not os.path.exists(http_ssl_private_key_file):
+ _log.info("Generating self-signed SSL/TLS certificate %s and private key %s for HTTPS",
+ http_ssl_cert_file, http_ssl_private_key_file)
+ country = "US"
+ state = "California"
+ loc = "Menlo Park"
+ org = "SLAC National Accelerator Laboratory"
+ org_unit = "Rubin Observatory"
+ hostname = socket.gethostbyaddr(socket.gethostname())[0] # FQDN if available
+ subj = f"/C={country}/ST={state}/L={loc}/O={org}/OU={org_unit}/CN={hostname}"
+ openssl_cmd = [
+ "openssl", "req",
+ "-x509",
+ "-newkey", "rsa:4096",
+ "-out", http_ssl_cert_file,
+ "-keyout", http_ssl_private_key_file,
+ "-sha256",
+ "-days", "365",
+ "-nodes",
+ "-subj", subj]
+ ret = subprocess.run(openssl_cmd, env=dict(os.environ,), cwd="/home/qserv")
+ if ret.returncode != 0:
+ raise RuntimeError("Failed to create SSL certificate and private key files.")
+
env = dict(
os.environ,
LD_PRELOAD=ld_preload,
diff --git a/src/admin/python/lsst/qserv/admin/itest.py b/src/admin/python/lsst/qserv/admin/itest.py
index 89ea7d200..9235cfd65 100644
--- a/src/admin/python/lsst/qserv/admin/itest.py
+++ b/src/admin/python/lsst/qserv/admin/itest.py
@@ -21,6 +21,7 @@
from filecmp import dircmp
import json
import logging
+import urllib3
import requests
import os
import re
@@ -510,7 +511,7 @@ def run_attached_http(self, connection: str, database: str) -> None:
# Submit the query, check and analyze the completion status
svc = str(urljoin(connection, '/query'))
- req = requests.post(svc, json={'query': query, 'database': database, 'binary_encoding': 'hex'})
+ req = requests.post(svc, json={'query': query, 'database': database, 'binary_encoding': 'hex'}, verify=False)
req.raise_for_status()
res = req.json()
if res['success'] == 0:
@@ -534,7 +535,7 @@ def run_detached_http(self, connection: str, database: str) -> None:
# Submit the query via the async service, check and analyze the completion status
svc = str(urljoin(connection, '/query-async'))
- req = requests.post(svc, json={'query': query, 'database': database})
+ req = requests.post(svc, json={'query': query, 'database': database}, verify=False)
req.raise_for_status()
res = req.json()
if res['success'] == 0:
@@ -547,7 +548,7 @@ def run_detached_http(self, connection: str, database: str) -> None:
while time.time() < end_time:
# Submit a request to check a status of the query
svc = str(urljoin(connection, f"/query-async/status/{query_id}"))
- req = requests.get(svc)
+ req = requests.get(svc, verify=False)
req.raise_for_status()
res = req.json()
if res['success'] == 0:
@@ -561,7 +562,7 @@ def run_detached_http(self, connection: str, database: str) -> None:
# Make another request to pull the result set
svc = str(urljoin(connection, f"/query-async/result/{query_id}?binary_encoding=hex"))
- req = requests.get(svc)
+ req = requests.get(svc, verify=False)
req.raise_for_status()
res = req.json()
if res['success'] == 0:
@@ -747,6 +748,8 @@ def __init__(
self.db_name,
skip_numbers,
)
+ # Supress the warning about the self-signed certificate
+ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def run(self) -> None:
"""Run the test queries in a test case.
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 8159d2692..a0443d6a2 100644
--- a/src/czar/HttpCzarIngestModule.cc
+++ b/src/czar/HttpCzarIngestModule.cc
@@ -37,8 +37,7 @@
#include "http/BinaryEncoding.h"
#include "http/Exceptions.h"
#include "http/MetaModule.h"
-#include "http/RequestBody.h"
-#include "qhttp/Request.h"
+#include "http/RequestBodyJSON.h"
#include "qhttp/Status.h"
using namespace std;
@@ -106,18 +105,16 @@ 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::ModuleBase(cconfig::CzarConfig::instance()->replicationAuthKey(),
- cconfig::CzarConfig::instance()->replicationAdminAuthKey(), req, resp),
+ 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() + ":" +
diff --git a/src/czar/HttpCzarIngestModule.h b/src/czar/HttpCzarIngestModule.h
index 6dfaa05b6..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/ModuleBase.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::ModuleBase {
+class HttpCzarIngestModule : public http::ChttpModule {
public:
/**
* @note supported values for parameter 'subModuleName' are:
@@ -64,8 +64,8 @@ class HttpCzarIngestModule : public http::ModuleBase {
* @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::ModuleBase {
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 79%
rename from src/czar/HttpModule.cc
rename to src/czar/QhttpModule.cc
index 1e1cc8957..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
@@ -28,7 +28,7 @@
// Qserv headers
#include "cconfig/CzarConfig.h"
#include "http/Exceptions.h"
-#include "http/RequestBody.h"
+#include "http/RequestBodyJSON.h"
#include "http/RequestQuery.h"
#include "qhttp/Request.h"
@@ -36,15 +36,15 @@ using namespace std;
namespace lsst::qserv::czar {
-HttpModule::HttpModule(string const& context, shared_ptr const& req,
- shared_ptr const& resp)
- : http::ModuleBase(cconfig::CzarConfig::instance()->replicationAuthKey(),
- cconfig::CzarConfig::instance()->replicationAdminAuthKey(), req, 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 73%
rename from src/czar/HttpModule.h
rename to src/czar/QhttpModule.h
index 566113ffb..dc7eb8b75 100644
--- a/src/czar/HttpModule.h
+++ b/src/czar/QhttpModule.h
@@ -18,15 +18,15 @@
* 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
#include
// Qserv headers
-#include "http/ModuleBase.h"
+#include "http/QhttpModule.h"
// Forward declarations
namespace lsst::qserv::qhttp {
@@ -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::ModuleBase {
+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::ModuleBase {
} // 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..85a0c451d 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:
@@ -49,7 +51,9 @@ int main(int argc, char* argv[]) {
// - the port number (0 value would result in allocating the first available port)
// - the number of service threads (0 value would assume the number of host machine's
// hardware threads)
- if (argc != 5) {
+ // - a location of the SSL/TSL certificate for the secure connections
+ // - a location of the SSL/TSL private key
+ if (argc != 7) {
cerr << __func__ << ": insufficient number of the command-line parameters\n" << ::usage << endl;
return 1;
}
@@ -71,15 +75,17 @@ int main(int argc, char* argv[]) {
cerr << __func__ << ": failed to parse command line parameters\n" << ::usage << endl;
return 1;
}
+ string const sslCertFile = argv[nextArg++];
+ string const sslPrivateKeyFile = argv[nextArg++];
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
+}
diff --git a/src/http/CMakeLists.txt b/src/http/CMakeLists.txt
index b7cb0f1ed..800a7be83 100644
--- a/src/http/CMakeLists.txt
+++ b/src/http/CMakeLists.txt
@@ -3,6 +3,8 @@ add_library(http SHARED)
target_sources(http PRIVATE
AsyncReq.cc
BinaryEncoding.cc
+ ChttpMetaModule.cc
+ ChttpModule.cc
Client.cc
ClientConnPool.cc
ClientConfig.cc
@@ -10,8 +12,9 @@ target_sources(http PRIVATE
Exceptions.cc
MetaModule.cc
Method.cc
- ModuleBase.cc
- RequestBody.cc
+ Module.cc
+ QhttpModule.cc
+ RequestBodyJSON.cc
RequestQuery.cc
Url.cc
)
@@ -24,6 +27,7 @@ target_link_libraries(http PUBLIC
Boost::filesystem
Boost::regex
Boost::system
+ cpp-httplib
)
install(TARGETS http)
diff --git a/src/http/ChttpMetaModule.cc b/src/http/ChttpMetaModule.cc
new file mode 100644
index 000000000..45aff9858
--- /dev/null
+++ b/src/http/ChttpMetaModule.cc
@@ -0,0 +1,71 @@
+/*
+ * 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 "http/ChttpMetaModule.h"
+
+// System headers
+#include
+
+using namespace std;
+using json = nlohmann::json;
+
+namespace {
+// Authorization context is not required by this module
+lsst::qserv::http::AuthType const authType = lsst::qserv::http::AuthType::NONE;
+string const authKey;
+string const adminAuthKey;
+} // namespace
+
+namespace lsst::qserv::http {
+
+unsigned int const ChttpMetaModule::version = 35;
+
+void ChttpMetaModule::process(string const& context, nlohmann::json const& info, httplib::Request const& req,
+ httplib::Response& resp, string const& subModuleName) {
+ ChttpMetaModule module(context, info, req, resp);
+ module.execute(subModuleName, ::authType);
+}
+
+ChttpMetaModule::ChttpMetaModule(string const& context, nlohmann::json const& info,
+ httplib::Request const& req, httplib::Response& resp)
+ : http::ChttpModule(::authKey, ::adminAuthKey, req, resp), _context(context), _info(info) {
+ if (!_info.is_object()) {
+ throw invalid_argument("ChttpMetaModule::" + string(__func__) + " parameter info must be an object.");
+ }
+}
+
+json ChttpMetaModule::executeImpl(string const& subModuleName) {
+ if (subModuleName == "VERSION") return _version();
+ throw invalid_argument(context() + "::" + string(__func__) + " unsupported sub-module: '" +
+ subModuleName + "'");
+}
+
+string ChttpMetaModule::context() const { return _context; }
+
+json ChttpMetaModule::_version() {
+ debug(__func__);
+ json result = _info;
+ result["version"] = ChttpMetaModule::version;
+ return result;
+}
+
+} // namespace lsst::qserv::http
diff --git a/src/http/ChttpMetaModule.h b/src/http/ChttpMetaModule.h
new file mode 100644
index 000000000..1e500b8d5
--- /dev/null
+++ b/src/http/ChttpMetaModule.h
@@ -0,0 +1,80 @@
+/*
+ * 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_HTTP_CHTTPMETAMODULE_H
+#define LSST_QSERV_HTTP_CHTTPMETAMODULE_H
+
+// System headers
+#include
+#include
+
+// Third party headers
+#include "nlohmann/json.hpp"
+
+// Qserv headers
+#include "http/ChttpModule.h"
+
+// This header declarations
+namespace lsst::qserv::http {
+
+/**
+ * Class ChttpMetaModule implements a handler for the metadata queries on the REST API itself.
+ * The service responds with an information object provided at the creation time of the module.
+ */
+class ChttpMetaModule : public http::ChttpModule {
+public:
+ typedef std::shared_ptr Ptr;
+
+ /// The current version of the REST API
+ static unsigned int const version;
+
+ /**
+ * @note supported values for parameter 'subModuleName' are:
+ * 'VERSION' - return a version of the REST API
+ *
+ * @param info The information object to be returned to clients of the service.
+ * @throws std::invalid_argument for unknown values of parameter 'subModuleName'
+ */
+ static void process(std::string const& context, nlohmann::json const& info, httplib::Request const& req,
+ httplib::Response& resp, std::string const& subModuleName);
+
+ ChttpMetaModule() = delete;
+ ChttpMetaModule(ChttpMetaModule const&) = delete;
+ ChttpMetaModule& operator=(ChttpMetaModule const&) = delete;
+
+ ~ChttpMetaModule() final = default;
+
+protected:
+ virtual nlohmann::json executeImpl(std::string const& subModuleName) final;
+ virtual std::string context() const final;
+
+private:
+ ChttpMetaModule(std::string const& context, nlohmann::json const& info, httplib::Request const& req,
+ httplib::Response& resp);
+
+ nlohmann::json _version();
+
+ std::string const _context;
+ nlohmann::json const _info;
+};
+
+} // namespace lsst::qserv::http
+
+#endif // LSST_QSERV_HTTP_CHTTPMETAMODULE_H
diff --git a/src/http/ChttpModule.cc b/src/http/ChttpModule.cc
new file mode 100644
index 000000000..8e806944a
--- /dev/null
+++ b/src/http/ChttpModule.cc
@@ -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 .
+ */
+
+// Class header
+#include "http/ChttpModule.h"
+
+// System headers
+#include
+
+// Third-party headers
+#include
+
+// Qserv headers
+#include "http/RequestQuery.h"
+
+using namespace std;
+
+namespace lsst::qserv::http {
+
+ChttpModule::ChttpModule(string const& authKey, string const& adminAuthKey, httplib::Request const& req,
+ httplib::Response& resp)
+ : Module(authKey, adminAuthKey), _req(req), _resp(resp) {}
+
+string ChttpModule::method() const { return _req.method; }
+
+unordered_map ChttpModule::params() const { return _req.path_params; }
+
+RequestQuery ChttpModule::query() const {
+ // TODO: The query parameters in CPP-HTTPLIB are stored in the std::multimap
+ // container to allow accumulating values of non-unique keys. For now we need
+ // to convert the multimap to the std::unordered_map container. This may result
+ // in losing some query parameters if they have the same key but different values.
+ // Though, the correct solution is to fix the QHTTP library to support
+ // the std::multimap container for query parameters.
+ unordered_map queryParams;
+ for (auto const& [key, value] : _req.params) queryParams[key] = value;
+ return RequestQuery(queryParams);
+}
+
+void ChttpModule::getRequestBody(string& content, string const& requiredContentType) {
+ auto itr = _req.headers.find("Content-Type");
+ if (itr != _req.headers.end() && itr->second == requiredContentType) {
+ content = _req.body;
+ }
+}
+
+void ChttpModule::sendResponse(string const& content, string const& contentType) {
+ _resp.set_content(content, contentType);
+}
+
+} // namespace lsst::qserv::http
diff --git a/src/http/ChttpModule.h b/src/http/ChttpModule.h
new file mode 100644
index 000000000..3f07f8935
--- /dev/null
+++ b/src/http/ChttpModule.h
@@ -0,0 +1,85 @@
+
+/*
+ * 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_HTTP_CHTTPMODULE_H
+#define LSST_QSERV_HTTP_CHTTPMODULE_H
+
+// System headers
+#include
+#include
+
+// Qserv headers
+#include "http/Module.h"
+
+// Forward declarations
+
+namespace httplib {
+class Request;
+class Response;
+} // namespace httplib
+
+namespace lsst::qserv::http {
+class RequestBodyJSON;
+class RequestQuery;
+} // namespace lsst::qserv::http
+
+// This header declarations
+namespace lsst::qserv::http {
+
+/**
+ * Class ChttpModule is an extended base class specialized for constructing
+ * the CPP-HTTPLIB request processing modules.
+ */
+class ChttpModule : public Module {
+public:
+ ChttpModule() = delete;
+ ChttpModule(ChttpModule const&) = delete;
+ ChttpModule& operator=(ChttpModule const&) = delete;
+
+ virtual ~ChttpModule() = default;
+
+protected:
+ /**
+ * @param authKey An authorization key for operations which require extra security.
+ * @param adminAuthKey An administrator-level authorization key.
+ * @param req The HTTP request.
+ * @param resp The HTTP response channel.
+ */
+ ChttpModule(std::string const& authKey, std::string const& adminAuthKey, httplib::Request const& req,
+ httplib::Response& resp);
+
+ httplib::Request const& req() { return _req; }
+ httplib::Response& resp() { return _resp; }
+
+ virtual std::string method() const;
+ virtual std::unordered_map params() const;
+ virtual RequestQuery query() const;
+ virtual void getRequestBody(std::string& content, std::string const& requiredContentType);
+ virtual void sendResponse(std::string const& content, std::string const& contentType);
+
+private:
+ httplib::Request const& _req;
+ httplib::Response& _resp;
+};
+
+} // namespace lsst::qserv::http
+
+#endif // LSST_QSERV_HTTP_CHTTPMODULE_H
diff --git a/src/http/MetaModule.cc b/src/http/MetaModule.cc
index 2965eefaa..f64572b08 100644
--- a/src/http/MetaModule.cc
+++ b/src/http/MetaModule.cc
@@ -39,15 +39,16 @@ namespace lsst::qserv::http {
unsigned int const MetaModule::version = 35;
-void MetaModule::process(string const& context, nlohmann::json const& info, qhttp::Request::Ptr const& req,
- qhttp::Response::Ptr const& resp, string const& subModuleName) {
+void MetaModule::process(string const& context, nlohmann::json const& info,
+ shared_ptr const& req, shared_ptr const& resp,
+ string const& subModuleName) {
MetaModule module(context, info, req, resp);
module.execute(subModuleName, ::authType);
}
-MetaModule::MetaModule(string const& context, nlohmann::json const& info, qhttp::Request::Ptr const& req,
- qhttp::Response::Ptr const& resp)
- : http::ModuleBase(::authKey, ::adminAuthKey, req, resp), _context(context), _info(info) {
+MetaModule::MetaModule(string const& context, nlohmann::json const& info,
+ shared_ptr const& req, shared_ptr const& resp)
+ : http::QhttpModule(::authKey, ::adminAuthKey, req, resp), _context(context), _info(info) {
if (!_info.is_object()) {
throw invalid_argument("MetaModule::" + string(__func__) + " parameter info must be an object.");
}
diff --git a/src/http/MetaModule.h b/src/http/MetaModule.h
index 31750d6fe..b294a1b7e 100644
--- a/src/http/MetaModule.h
+++ b/src/http/MetaModule.h
@@ -29,7 +29,7 @@
#include "nlohmann/json.hpp"
// Qserv headers
-#include "http/ModuleBase.h"
+#include "http/QhttpModule.h"
// This header declarations
namespace lsst::qserv::http {
@@ -38,7 +38,7 @@ namespace lsst::qserv::http {
* Class MetaModule implements a handler for the metadata queries on the REST API itself.
* The service responds with an information object provided at the creation time of the module.
*/
-class MetaModule : public http::ModuleBase {
+class MetaModule : public http::QhttpModule {
public:
typedef std::shared_ptr Ptr;
@@ -53,8 +53,8 @@ class MetaModule : public http::ModuleBase {
* @throws std::invalid_argument for unknown values of parameter 'subModuleName'
*/
static void process(std::string const& context, nlohmann::json const& info,
- qhttp::Request::Ptr const& req, qhttp::Response::Ptr const& resp,
- std::string const& subModuleName);
+ std::shared_ptr const& req,
+ std::shared_ptr const& resp, std::string const& subModuleName);
MetaModule() = delete;
MetaModule(MetaModule const&) = delete;
@@ -67,8 +67,8 @@ class MetaModule : public http::ModuleBase {
virtual std::string context() const final;
private:
- MetaModule(std::string const& context, nlohmann::json const& info, qhttp::Request::Ptr const& req,
- qhttp::Response::Ptr const& resp);
+ MetaModule(std::string const& context, nlohmann::json const& info,
+ std::shared_ptr const& req, std::shared_ptr const& resp);
nlohmann::json _version();
diff --git a/src/http/ModuleBase.cc b/src/http/Module.cc
similarity index 72%
rename from src/http/ModuleBase.cc
rename to src/http/Module.cc
index 82129724e..3156d7c3c 100644
--- a/src/http/ModuleBase.cc
+++ b/src/http/Module.cc
@@ -20,11 +20,12 @@
*/
// Class header
-#include "http/ModuleBase.h"
+#include "http/Module.h"
// Qserv headers
#include "http/Exceptions.h"
#include "http/MetaModule.h"
+#include "http/RequestQuery.h"
// LSST headers
#include "lsst/log/Log.h"
@@ -36,7 +37,7 @@ using namespace std;
using json = nlohmann::json;
namespace {
-LOG_LOGGER _log = LOG_GET("lsst.qserv.http.ModuleBase");
+LOG_LOGGER _log = LOG_GET("lsst.qserv.http.Module");
string packWarnings(list const& warnings) {
string packed;
@@ -50,15 +51,12 @@ string packWarnings(list const& warnings) {
namespace lsst::qserv::http {
-ModuleBase::ModuleBase(string const& authKey, string const& adminAuthKey, qhttp::Request::Ptr const& req,
- qhttp::Response::Ptr const& resp)
- : _authKey(authKey), _adminAuthKey(adminAuthKey), _req(req), _resp(resp), _query(req->query) {}
+Module::Module(string const& authKey, string const& adminAuthKey)
+ : _authKey(authKey), _adminAuthKey(adminAuthKey) {}
-ModuleBase::~ModuleBase() {}
-
-void ModuleBase::execute(string const& subModuleName, http::AuthType const authType) {
+void Module::execute(string const& subModuleName, http::AuthType const authType) {
try {
- _body = RequestBody(_req);
+ _parseRequestBodyJSON();
if (authType == http::AuthType::REQUIRED) _enforceAuthorization();
json result = executeImpl(subModuleName);
_sendData(result);
@@ -73,7 +71,7 @@ void ModuleBase::execute(string const& subModuleName, http::AuthType const authT
}
}
-void ModuleBase::checkApiVersion(string const& func, unsigned int minVersion, string const& warning) const {
+void Module::checkApiVersion(string const& func, unsigned int minVersion, string const& warning) const {
unsigned int const maxVersion = MetaModule::version;
unsigned int version = 0;
string const versionAttrName = "version";
@@ -86,7 +84,7 @@ void ModuleBase::checkApiVersion(string const& func, unsigned int minVersion, st
// Note that requests sent w/o explicitly specified API version will still be
// processed. In this case a warning will be sent in the response object.
try {
- if (req()->method == "GET") {
+ if (method() == "GET") {
if (!query().has(versionAttrName)) {
warn("No version number was provided in the request's query.");
return;
@@ -111,9 +109,9 @@ void ModuleBase::checkApiVersion(string const& func, unsigned int minVersion, st
}
}
-void ModuleBase::enforceInstanceId(string const& func, string const& requiredInstanceId) const {
- string const instanceId = req()->method == "GET" ? query().requiredString("instance_id")
- : body().required("instance_id");
+void Module::enforceInstanceId(string const& func, string const& requiredInstanceId) const {
+ string const instanceId = method() == "GET" ? query().requiredString("instance_id")
+ : body().required("instance_id");
debug(func, "instance_id: " + instanceId);
if (instanceId != requiredInstanceId) {
throw invalid_argument(context() + func + " Qserv instance identifier mismatch. Client sent '" +
@@ -121,36 +119,54 @@ void ModuleBase::enforceInstanceId(string const& func, string const& requiredIns
}
}
-void ModuleBase::info(string const& msg) const { LOGS(_log, LOG_LVL_INFO, context() << msg); }
+void Module::info(string const& msg) const { LOGS(_log, LOG_LVL_INFO, context() << msg); }
-void ModuleBase::debug(string const& msg) const { LOGS(_log, LOG_LVL_DEBUG, context() << msg); }
+void Module::debug(string const& msg) const { LOGS(_log, LOG_LVL_DEBUG, context() << msg); }
-void ModuleBase::warn(string const& msg) const {
+void Module::warn(string const& msg) const {
LOGS(_log, LOG_LVL_WARN, context() << msg);
_warnings.push_back(msg);
}
-void ModuleBase::error(string const& msg) const { LOGS(_log, LOG_LVL_ERROR, context() << msg); }
+void Module::error(string const& msg) const { LOGS(_log, LOG_LVL_ERROR, context() << msg); }
-void ModuleBase::_sendError(string const& func, string const& errorMsg, json const& errorExt) const {
+void Module::_sendError(string const& func, string const& errorMsg, json const& errorExt) {
error(func, errorMsg);
json result;
result["success"] = 0;
result["error"] = errorMsg;
result["error_ext"] = errorExt.is_null() ? json::object() : errorExt;
result["warning"] = ::packWarnings(_warnings);
- resp()->send(result.dump(), "application/json");
+ sendResponse(result.dump(), "application/json");
}
-void ModuleBase::_sendData(json& result) {
+void Module::_sendData(json& result) {
result["success"] = 1;
result["error"] = "";
result["error_ext"] = json::object();
result["warning"] = ::packWarnings(_warnings);
- resp()->send(result.dump(), "application/json");
+ sendResponse(result.dump(), "application/json");
+}
+
+void Module::_parseRequestBodyJSON() {
+ string content;
+ getRequestBody(content, "application/json");
+ if (!content.empty()) {
+ try {
+ _body.objJson = json::parse(content);
+ if (_body.objJson.is_null() || _body.objJson.is_object()) return;
+ } catch (...) {
+ // Not really interested in knowing specific details of the exception.
+ // All what matters here is that the string can't be parsed into
+ // a valid JSON object. This will be reported via another exception
+ // after this block ends.
+ ;
+ }
+ throw std::invalid_argument("invalid format of the request body. A simple JSON object was expected");
+ }
}
-void ModuleBase::_enforceAuthorization() {
+void Module::_enforceAuthorization() {
if (body().has("admin_auth_key")) {
auto const adminAuthKey = body().required("admin_auth_key");
if (adminAuthKey != _adminAuthKey) {
diff --git a/src/http/ModuleBase.h b/src/http/Module.h
similarity index 78%
rename from src/http/ModuleBase.h
rename to src/http/Module.h
index 611ae9ed1..009d2a192 100644
--- a/src/http/ModuleBase.h
+++ b/src/http/Module.h
@@ -19,8 +19,8 @@
* the GNU General Public License along with this program. If not,
* see .
*/
-#ifndef LSST_QSERV_HTTP_MODULEBASE_H
-#define LSST_QSERV_HTTP_MODULEBASE_H
+#ifndef LSST_QSERV_HTTP_MODULE_H
+#define LSST_QSERV_HTTP_MODULE_H
// System headers
#include
@@ -33,10 +33,12 @@
#include "nlohmann/json.hpp"
// Qserv headers
-#include "http/RequestBody.h"
-#include "http/RequestQuery.h"
-#include "qhttp/Request.h"
-#include "qhttp/Response.h"
+#include "http/RequestBodyJSON.h"
+
+// Forward declarations
+namespace lsst::qserv::http {
+class RequestQuery;
+} // namespace lsst::qserv::http
// This header declarations
namespace lsst::qserv::http {
@@ -45,38 +47,35 @@ namespace lsst::qserv::http {
/// module's authorization requirements.
enum class AuthType { REQUIRED, NONE };
+/// Class AuthError represent exceptions thrown when the authorization
+/// requirements aren't met.
+class AuthError : public std::invalid_argument {
+public:
+ using std::invalid_argument::invalid_argument;
+};
+
/**
- * Class ModuleBase is a base class for requests processing modules
- * of the HTTP servers built into the Replication system's services.
+ * Class Module is the very base class for the request processing modules of the HTTP servers.
*/
-class ModuleBase {
+class Module {
public:
- /**
- * Class AuthError represent exceptions thrown when the authorization
- * requirements aren't met.
- */
- class AuthError : public std::invalid_argument {
- public:
- using std::invalid_argument::invalid_argument;
- };
-
- ModuleBase() = delete;
- ModuleBase(ModuleBase const&) = delete;
- ModuleBase& operator=(ModuleBase const&) = delete;
+ Module() = delete;
+ Module(Module const&) = delete;
+ Module& operator=(Module const&) = delete;
- virtual ~ModuleBase();
+ virtual ~Module() = default;
/**
* Invokes a subclass-specific request processing provided by implementations
- * of the pure virtual method ModuleBase::executeImpl(). The current method
+ * of the pure virtual method Module::executeImpl(). The current method
* would also do an optional processing of exceptions thrown by the subclass-specific
- * implementations of method ModuleBase::executeImpl(). These error conditions will
+ * implementations of method Module::executeImpl(). These error conditions will
* be reported to as errors to callers.
*
* @param subModuleName this optional parameter allows modules to have
* multiple sub-modules. A value of this parameter will be forwarded to
* the subclass-specific implementation of the pure virtual method
- * ModuleBase::executeImpl().
+ * Module::executeImpl().
* @param authType Authorization requirements of the module. If 'http::AuthType::REQUIRED' is
* requested then the method will enforce the authorization. A lack of required
* authorization key in a request, or an incorrect value of such key would result
@@ -100,26 +99,23 @@ class ModuleBase {
/**
* @param authKey An authorization key for operations which require extra security.
* @param adminAuthKey An administrator-level authorization key.
- * @param req The HTTP request.
- * @param resp The HTTP response channel.
*/
- ModuleBase(std::string const& authKey, std::string const& adminAuthKey, qhttp::Request::Ptr const& req,
- qhttp::Response::Ptr const& resp);
-
- qhttp::Request::Ptr const& req() const { return _req; }
- qhttp::Response::Ptr const& resp() const { return _resp; }
+ Module(std::string const& authKey, std::string const& adminAuthKey);
/// @return Authorization level of the request.
bool isAdmin() const { return _isAdmin; }
- /// @return Parameters of a REST request.
- std::unordered_map const& params() const { return _req->params; }
+ /// @return The method of a request.
+ virtual std::string method() const = 0;
+
+ /// @return Captured URL path elements.
+ virtual std::unordered_map params() const = 0;
/// @return Parameters of the request's query captured from the request's URL.
- RequestQuery const& query() const { return _query; }
+ virtual RequestQuery query() const = 0;
/// @return Optional parameters of a request extracted from the request's body (if any).
- RequestBody const& body() const { return _body; }
+ RequestBodyJSON const& body() const { return _body; }
// Message loggers for the corresponding log levels
@@ -152,7 +148,7 @@ class ModuleBase {
* in the "warning" attribute at the returned JSON object.
*
* The method will look for th eversion attribute in the query string of the "GET"
- * requests. For requests that are called using methods "POIST", "PUT" or "DELETE"
+ * requests. For requests that are called using methods "POST", "PUT" or "DELETE"
* the attribute will be located in the requests's body.
*
* @note Services that are calling the method should adjust the minimum version
@@ -184,9 +180,17 @@ class ModuleBase {
*/
void enforceInstanceId(std::string const& func, std::string const& requiredInstanceId) const;
+ /**
+ * Get the raw body of a request if it's available and if the content type
+ * meets expectations.
+ * @note An assumption is made that the body is small enough to fit into memory.
+ * @param content The content of the body is set of a request if all conditions are met.
+ * @param requiredContentType The required content type of the body.
+ */
+ virtual void getRequestBody(std::string& content, std::string const& requiredContentType) = 0;
+
/**
* To implement a subclass-specific request processing.
- *
* @note All exceptions thrown by the implementations will be intercepted and
* reported as errors to callers. Exceptions are now the only way to report
* errors from modules.
@@ -197,7 +201,21 @@ class ModuleBase {
*/
virtual nlohmann::json executeImpl(std::string const& subModuleName) = 0;
+ /**
+ * Send a response back to a requester of a service.
+ * @param content The content to be sent back.
+ * @param contentType The type of the content to be sent back.
+ */
+ virtual void sendResponse(std::string const& content, std::string const& contentType) = 0;
+
private:
+ /**
+ * Pull the raw request body and translate it into a JSON object.
+ * @note The body will be set only if the request has a body and the content
+ * type is "application/json". Otherwise the body will be left empty.
+ */
+ void _parseRequestBodyJSON();
+
/**
* Inspect the body of a request or a presence of a user-supplied authorization key.
* Its value will be compared against a value of the corresponding configuration
@@ -218,7 +236,7 @@ class ModuleBase {
* @param errorExt (optional) The additional information on the error.
*/
void _sendError(std::string const& func, std::string const& errorMsg,
- nlohmann::json const& errorExt = nlohmann::json::object()) const;
+ nlohmann::json const& errorExt = nlohmann::json::object());
/**
* Report a result back to a requester of a service upon its successful
@@ -231,19 +249,12 @@ class ModuleBase {
std::string const _authKey;
std::string const _adminAuthKey;
- qhttp::Request::Ptr const _req;
- qhttp::Response::Ptr const _resp;
/// The flag indicating if a request has been granted the "administrator"-level privileges.
bool _isAdmin = false;
- /// The parser for parameters passed into the Web services via the optional
- /// query part of a URL. The object gets initialized from the request.
- RequestQuery const _query;
-
- /// The body of a request is initialized/parsed from the request before calling
- /// the overloaded method HttpModule::executeImpl.
- RequestBody _body;
+ /// The body of a request is initialized by Module::execute().
+ RequestBodyJSON _body;
/// The optional warning message to be sent to a caller if the API version
/// number wasn't mentoned in the request.
@@ -252,4 +263,4 @@ class ModuleBase {
} // namespace lsst::qserv::http
-#endif // LSST_QSERV_HTTP_MODULEBASE_H
+#endif // LSST_QSERV_HTTP_MODULE_H
diff --git a/src/http/QhttpModule.cc b/src/http/QhttpModule.cc
new file mode 100644
index 000000000..7b492862b
--- /dev/null
+++ b/src/http/QhttpModule.cc
@@ -0,0 +1,59 @@
+/*
+ * 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 "http/QhttpModule.h"
+
+// System headers
+#include
+
+// Qserv headers
+#include "http/RequestQuery.h"
+#include "qhttp/Request.h"
+#include "qhttp/Response.h"
+
+using namespace std;
+
+namespace lsst::qserv::http {
+
+QhttpModule::QhttpModule(string const& authKey, string const& adminAuthKey,
+ shared_ptr const& req, shared_ptr const& resp)
+ : Module(authKey, adminAuthKey), _req(req), _resp(resp) {}
+
+string QhttpModule::method() const { return _req->method; }
+
+unordered_map QhttpModule::params() const { return _req->params; }
+
+RequestQuery QhttpModule::query() const { return RequestQuery(_req->query); }
+
+void QhttpModule::getRequestBody(string& content, string const& requiredContentType) {
+ if (_req->header["Content-Type"] == requiredContentType) {
+ content.clear();
+ content.reserve(_req->contentLengthBytes());
+ content.append(istreambuf_iterator(_req->content), {});
+ }
+}
+
+void QhttpModule::sendResponse(string const& content, string const& contentType) {
+ _resp->send(content, contentType);
+}
+
+} // namespace lsst::qserv::http
diff --git a/src/http/QhttpModule.h b/src/http/QhttpModule.h
new file mode 100644
index 000000000..db2721311
--- /dev/null
+++ b/src/http/QhttpModule.h
@@ -0,0 +1,86 @@
+
+/*
+ * 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_HTTP_QHTTPMODULE_H
+#define LSST_QSERV_HTTP_QHTTPMODULE_H
+
+// System headers
+#include
+#include
+#include
+
+// Qserv headers
+#include "http/Module.h"
+
+// Forward declarations
+
+namespace lsst::qserv::qhttp {
+class Request;
+class Response;
+} // namespace lsst::qserv::qhttp
+
+namespace lsst::qserv::http {
+class RequestBodyJSON;
+class RequestQuery;
+} // namespace lsst::qserv::http
+
+// This header declarations
+namespace lsst::qserv::http {
+
+/**
+ * Class QhttpModule is an extended base class specialized for constructing
+ * the QHTTP request processing modules.
+ */
+class QhttpModule : public Module {
+public:
+ QhttpModule() = delete;
+ QhttpModule(QhttpModule const&) = delete;
+ QhttpModule& operator=(QhttpModule const&) = delete;
+
+ virtual ~QhttpModule() = default;
+
+protected:
+ /**
+ * @param authKey An authorization key for operations which require extra security.
+ * @param adminAuthKey An administrator-level authorization key.
+ * @param req The HTTP request.
+ * @param resp The HTTP response channel.
+ */
+ QhttpModule(std::string const& authKey, std::string const& adminAuthKey,
+ std::shared_ptr const& req, std::shared_ptr const& resp);
+
+ std::shared_ptr const& req() const { return _req; }
+ std::shared_ptr const& resp() const { return _resp; }
+
+ virtual std::string method() const;
+ virtual std::unordered_map params() const;
+ virtual RequestQuery query() const;
+ virtual void getRequestBody(std::string& content, std::string const& requiredContentType);
+ virtual void sendResponse(std::string const& content, std::string const& contentType);
+
+private:
+ std::shared_ptr const _req;
+ std::shared_ptr const _resp;
+};
+
+} // namespace lsst::qserv::http
+
+#endif // LSST_QSERV_HTTP_QHTTPMODULE_H
diff --git a/src/http/RequestBody.cc b/src/http/RequestBody.cc
deleted file mode 100644
index 6c0a2da0e..000000000
--- a/src/http/RequestBody.cc
+++ /dev/null
@@ -1,67 +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 "http/RequestBody.h"
-
-// System headers
-#include
-
-using namespace std;
-using json = nlohmann::json;
-
-namespace lsst::qserv::http {
-
-RequestBody::RequestBody(qhttp::Request::Ptr const& req) : objJson(json::object()) {
- // This way of parsing the optional body allows requests which have no body.
-
- string const contentType = req->header["Content-Type"];
- string const requiredContentType = "application/json";
-
- if (contentType == requiredContentType) {
- string content(istreambuf_iterator(req->content), {});
- if (not content.empty()) {
- try {
- objJson = json::parse(content);
- if (objJson.is_null() or objJson.is_object()) return;
- } catch (...) {
- // Not really interested in knowing specific details of the exception.
- // All what matters here is that the string can't be parsed into
- // a valid JSON object. This will be reported via another exception
- // after this block ends.
- ;
- }
- throw invalid_argument("invalid format of the request body. A simple JSON object was expected");
- }
- }
-}
-
-bool RequestBody::has(json const& obj, string const& name) const {
- if (not obj.is_object()) {
- throw invalid_argument("RequestBody::" + string(__func__) +
- " parameter 'obj' is not a valid JSON object");
- }
- return obj.find(name) != obj.end();
-}
-
-bool RequestBody::has(string const& name) const { return has(objJson, name); }
-
-} // namespace lsst::qserv::http
\ No newline at end of file
diff --git a/src/http/RequestBodyJSON.cc b/src/http/RequestBodyJSON.cc
new file mode 100644
index 000000000..7e52a9d68
--- /dev/null
+++ b/src/http/RequestBodyJSON.cc
@@ -0,0 +1,40 @@
+/*
+ * 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 "http/RequestBodyJSON.h"
+
+using namespace std;
+using json = nlohmann::json;
+
+namespace lsst::qserv::http {
+
+bool RequestBodyJSON::has(json const& obj, string const& name) const {
+ if (!obj.is_object()) {
+ throw invalid_argument("RequestBodyJSON::" + string(__func__) +
+ " parameter 'obj' is not a valid JSON object");
+ }
+ return obj.find(name) != obj.end();
+}
+
+bool RequestBodyJSON::has(string const& name) const { return has(objJson, name); }
+
+} // namespace lsst::qserv::http
\ No newline at end of file
diff --git a/src/http/RequestBody.h b/src/http/RequestBodyJSON.h
similarity index 80%
rename from src/http/RequestBody.h
rename to src/http/RequestBodyJSON.h
index 40d121583..896250d32 100644
--- a/src/http/RequestBody.h
+++ b/src/http/RequestBodyJSON.h
@@ -18,8 +18,8 @@
* the GNU General Public License along with this program. If not,
* see .
*/
-#ifndef LSST_QSERV_HTTP_REQUESTBODY_H
-#define LSST_QSERV_HTTP_REQUESTBODY_H
+#ifndef LSST_QSERV_HTTP_REQUESTBODYJSON_H
+#define LSST_QSERV_HTTP_REQUESTBODYJSON_H
// System headers
#include
@@ -29,46 +29,21 @@
// Third party headers
#include "nlohmann/json.hpp"
-// Qserv headers
-#include "qhttp/Server.h"
-
// This header declarations
namespace lsst::qserv::http {
/**
- * Helper class RequestBody parses a body of an HTTP request
- * which has the following header:
- *
- * Content-Type: application/json
- *
- * Exceptions may be thrown by the constructor of the class if
- * the request has an unexpected content type, or if its payload
- * is not a proper JSON object.
+ * Class RequestBodyJSON represents the request body parsed into a JSON object.
+ * This type of an object is only available for requests that have the following
+ * header: 'Content-Type: application/json'.
*/
-class RequestBody {
+class RequestBodyJSON {
public:
/// parsed body of the request
nlohmann::json objJson = nlohmann::json::object();
- RequestBody() = default;
- RequestBody(RequestBody const&) = default;
- RequestBody& operator=(RequestBody const&) = default;
-
- ~RequestBody() = default;
-
- /**
- * The constructor will parse and evaluate a body of an HTTP request
- * and populate the 'kv' dictionary. Exceptions may be thrown in
- * the following scenarios:
- * - the required HTTP header is not found in the request
- * - the body doesn't have a valid JSON string (unless the body is empty)
- *
- * @param req The request to be parsed.
- */
- explicit RequestBody(qhttp::Request::Ptr const& req);
-
/**
- * Check if thw specified parameter is present in the input JSON object.
+ * Check if the specified parameter is present in the input JSON object.
* @param obj JSON object to be inspected.
* @param name The name of a parameter.
* @return 'true' if the parameter was found.
@@ -95,11 +70,11 @@ class RequestBody {
template
static T required(nlohmann::json const& obj, std::string const& name) {
if (not obj.is_object()) {
- throw std::invalid_argument("RequestBody::" + std::string(__func__) +
+ throw std::invalid_argument("RequestBodyJSON::" + std::string(__func__) +
"[static] parameter 'obj' is not a valid JSON object");
}
if (obj.find(name) != obj.end()) return obj[name];
- throw std::invalid_argument("RequestBody::" + std::string(__func__) +
+ throw std::invalid_argument("RequestBodyJSON::" + std::string(__func__) +
"[static] required parameter " + name +
" is missing in the request body");
}
@@ -127,7 +102,7 @@ class RequestBody {
T required(std::string const& name, std::vector const& permitted) const {
auto const value = required(objJson, name);
if (_in(value, permitted)) return value;
- throw std::invalid_argument("RequestBody::" + std::string(__func__) +
+ throw std::invalid_argument("RequestBodyJSON::" + std::string(__func__) +
"(permitted) a value of parameter " + name + " is not allowed.");
}
@@ -155,7 +130,7 @@ class RequestBody {
T optional(std::string const& name, T const& defaultValue, std::vector const& permitted) const {
auto const value = optional(name, defaultValue);
if (_in(value, permitted)) return value;
- throw std::invalid_argument("RequestBody::" + std::string(__func__) +
+ throw std::invalid_argument("RequestBodyJSON::" + std::string(__func__) +
"(permitted) a value of parameter " + name + " is not allowed.");
}
@@ -169,11 +144,11 @@ class RequestBody {
std::vector requiredColl(std::string const& name) const {
auto const itr = objJson.find(name);
if (itr == objJson.end()) {
- throw std::invalid_argument("RequestBody::" + std::string(__func__) + " required parameter " +
- name + " is missing in the request body");
+ throw std::invalid_argument("RequestBodyJSON::" + std::string(__func__) +
+ " required parameter " + name + " is missing in the request body");
}
if (not itr->is_array()) {
- throw std::invalid_argument("RequestBody::" + std::string(__func__) +
+ throw std::invalid_argument("RequestBodyJSON::" + std::string(__func__) +
" a value of the required parameter " + name + " is not an array");
}
std::vector coll;
@@ -213,4 +188,4 @@ class RequestBody {
} // namespace lsst::qserv::http
-#endif // LSST_QSERV_HTTP_REQUESTBODY_H
+#endif // LSST_QSERV_HTTP_REQUESTBODYJSON_H
diff --git a/src/replica/contr/HttpConfigurationModule.cc b/src/replica/contr/HttpConfigurationModule.cc
index 464601d52..cce6d06e3 100644
--- a/src/replica/contr/HttpConfigurationModule.cc
+++ b/src/replica/contr/HttpConfigurationModule.cc
@@ -30,6 +30,7 @@
// Qserv headers
#include "http/Exceptions.h"
+#include "http/RequestQuery.h"
#include "replica/config/Configuration.h"
#include "replica/config/ConfigDatabase.h"
#include "replica/config/ConfigurationSchema.h"
diff --git a/src/replica/contr/HttpControllersModule.cc b/src/replica/contr/HttpControllersModule.cc
index 1d0d96bbf..1032515e6 100644
--- a/src/replica/contr/HttpControllersModule.cc
+++ b/src/replica/contr/HttpControllersModule.cc
@@ -27,6 +27,7 @@
// Qserv headers
#include "http/Exceptions.h"
+#include "http/RequestQuery.h"
#include "replica/services/DatabaseServices.h"
#include "replica/services/ServiceProvider.h"
diff --git a/src/replica/contr/HttpExportModule.cc b/src/replica/contr/HttpExportModule.cc
index 5af2f5a40..b021a9d07 100644
--- a/src/replica/contr/HttpExportModule.cc
+++ b/src/replica/contr/HttpExportModule.cc
@@ -27,7 +27,7 @@
// Qserv headers
#include "http/Exceptions.h"
-#include "http/RequestBody.h"
+#include "http/RequestBodyJSON.h"
#include "replica/config/Configuration.h"
#include "replica/config/ConfigWorker.h"
#include "replica/services/DatabaseServices.h"
@@ -201,11 +201,11 @@ json HttpExportModule::_getTables() {
for (auto&& tableJson : tablesJson) {
TableSpec spec;
- spec.tableName = http::RequestBody::required(tableJson, "table");
+ spec.tableName = http::RequestBodyJSON::required(tableJson, "table");
spec.partitioned = database.findTable(spec.tableName).isPartitioned;
if (spec.partitioned) {
- spec.overlap = http::RequestBody::required(tableJson, "overlap");
- spec.chunk = http::RequestBody::required(tableJson, "chunk");
+ spec.overlap = http::RequestBodyJSON::required(tableJson, "overlap");
+ spec.chunk = http::RequestBodyJSON::required(tableJson, "chunk");
}
ConfigWorker const worker =
spec.partitioned ? findWorkerForChunk(spec.chunk) : allConfigWorkers[0];
diff --git a/src/replica/contr/HttpIngestChunksModule.cc b/src/replica/contr/HttpIngestChunksModule.cc
index 24f39b3f4..9eac312f7 100644
--- a/src/replica/contr/HttpIngestChunksModule.cc
+++ b/src/replica/contr/HttpIngestChunksModule.cc
@@ -32,6 +32,7 @@
// Qserv headers
#include "http/Exceptions.h"
+#include "http/RequestQuery.h"
#include "replica/config/Configuration.h"
#include "replica/services/DatabaseServices.h"
#include "replica/services/ServiceProvider.h"
diff --git a/src/replica/contr/HttpIngestConfigModule.cc b/src/replica/contr/HttpIngestConfigModule.cc
index 2e1d74a92..e2c765acb 100644
--- a/src/replica/contr/HttpIngestConfigModule.cc
+++ b/src/replica/contr/HttpIngestConfigModule.cc
@@ -29,6 +29,7 @@
#include "global/stringUtil.h"
#include "http/Client.h"
#include "http/Exceptions.h"
+#include "http/RequestQuery.h"
#include "replica/config/Configuration.h"
#include "replica/services/DatabaseServices.h"
diff --git a/src/replica/contr/HttpIngestTransModule.cc b/src/replica/contr/HttpIngestTransModule.cc
index b49c8e8cc..5f34462d4 100644
--- a/src/replica/contr/HttpIngestTransModule.cc
+++ b/src/replica/contr/HttpIngestTransModule.cc
@@ -33,6 +33,7 @@
// Qserv headers
#include "http/Exceptions.h"
+#include "http/RequestQuery.h"
#include "replica/config/Configuration.h"
#include "replica/jobs/AbortTransactionJob.h"
#include "replica/jobs/DirectorIndexJob.h"
diff --git a/src/replica/contr/HttpJobsModule.cc b/src/replica/contr/HttpJobsModule.cc
index 1a049d30c..f1afda4c0 100644
--- a/src/replica/contr/HttpJobsModule.cc
+++ b/src/replica/contr/HttpJobsModule.cc
@@ -27,6 +27,7 @@
// Qserv headers
#include "http/Exceptions.h"
+#include "http/RequestQuery.h"
#include "replica/services/DatabaseServices.h"
#include "replica/services/ServiceProvider.h"
diff --git a/src/replica/contr/HttpModule.cc b/src/replica/contr/HttpModule.cc
index c090aa5ad..7cbc3ef68 100644
--- a/src/replica/contr/HttpModule.cc
+++ b/src/replica/contr/HttpModule.cc
@@ -52,8 +52,8 @@ HttpModule::HttpModule(Controller::Ptr const& controller, string const& taskName
HttpProcessorConfig const& processorConfig, qhttp::Request::Ptr const& req,
qhttp::Response::Ptr const& resp)
: EventLogger(controller, taskName),
- http::ModuleBase(controller->serviceProvider()->authKey(),
- controller->serviceProvider()->adminAuthKey(), req, resp),
+ http::QhttpModule(controller->serviceProvider()->authKey(),
+ controller->serviceProvider()->adminAuthKey(), req, resp),
_processorConfig(processorConfig) {}
string HttpModule::context() const { return name() + " "; }
diff --git a/src/replica/contr/HttpModule.h b/src/replica/contr/HttpModule.h
index ae4a4dabc..bd97f51eb 100644
--- a/src/replica/contr/HttpModule.h
+++ b/src/replica/contr/HttpModule.h
@@ -26,7 +26,7 @@
#include
// Qserv headers
-#include "http/ModuleBase.h"
+#include "http/QhttpModule.h"
#include "qhttp/Request.h"
#include "qhttp/Response.h"
#include "replica/config/Configuration.h"
@@ -53,7 +53,7 @@ namespace lsst::qserv::replica {
* Class HttpModule is a base class for requests processing modules
* of an HTTP server built into the Master Replication Controller.
*/
-class HttpModule : public EventLogger, public http::ModuleBase {
+class HttpModule : public EventLogger, public http::QhttpModule {
public:
HttpModule() = delete;
HttpModule(HttpModule const&) = delete;
@@ -79,7 +79,7 @@ class HttpModule : public EventLogger, public http::ModuleBase {
unsigned int qservSyncTimeoutSec() const { return _processorConfig.qservSyncTimeoutSec; }
unsigned int workerReconfigTimeoutSec() const { return _processorConfig.workerReconfigTimeoutSec; }
- /// @see http::ModuleBase::context()
+ /// @see http::Module::context()
virtual std::string context() const final;
/// @param database The name of a database to connect to.
diff --git a/src/replica/contr/HttpQservMonitorModule.cc b/src/replica/contr/HttpQservMonitorModule.cc
index 0d4006142..ade25e38f 100644
--- a/src/replica/contr/HttpQservMonitorModule.cc
+++ b/src/replica/contr/HttpQservMonitorModule.cc
@@ -35,6 +35,7 @@
#include "css/CssError.h"
#include "global/intTypes.h"
#include "http/Exceptions.h"
+#include "http/RequestQuery.h"
#include "qmeta/types.h"
#include "replica/config/Configuration.h"
#include "replica/config/ConfigDatabase.h"
diff --git a/src/replica/contr/HttpRequestsModule.cc b/src/replica/contr/HttpRequestsModule.cc
index 6fdb69981..bf706cbe3 100644
--- a/src/replica/contr/HttpRequestsModule.cc
+++ b/src/replica/contr/HttpRequestsModule.cc
@@ -27,6 +27,7 @@
// Qserv headers
#include "http/Exceptions.h"
+#include "http/RequestQuery.h"
#include "replica/services/DatabaseServices.h"
#include "replica/services/ServiceProvider.h"
diff --git a/src/replica/contr/HttpSqlIndexModule.cc b/src/replica/contr/HttpSqlIndexModule.cc
index 4bd8a76bd..3dcb1fb05 100644
--- a/src/replica/contr/HttpSqlIndexModule.cc
+++ b/src/replica/contr/HttpSqlIndexModule.cc
@@ -30,7 +30,8 @@
// Qserv headers
#include "http/Exceptions.h"
-#include "http/RequestBody.h"
+#include "http/RequestBodyJSON.h"
+#include "http/RequestQuery.h"
#include "replica/config/Configuration.h"
#include "replica/jobs/SqlCreateIndexesJob.h"
#include "replica/jobs/SqlDropIndexesJob.h"
@@ -154,15 +155,15 @@ json HttpSqlIndexModule::_createIndexes() {
}
vector indexColumns;
for (auto&& columnJson : columnsJson) {
- string const column = http::RequestBody::required(columnJson, "column");
+ string const column = http::RequestBodyJSON::required(columnJson, "column");
if (!table.columns.empty() and
table.columns.cend() == find_if(table.columns.cbegin(), table.columns.cend(),
[&column](auto&& c) { return c.name == column; })) {
throw invalid_argument(context() + "::" + string(__func__) + " requested column '" + column +
"' has not been found in the table schema.");
}
- indexColumns.emplace_back(column, http::RequestBody::required(columnJson, "length"),
- http::RequestBody::required(columnJson, "ascending"));
+ indexColumns.emplace_back(column, http::RequestBodyJSON::required(columnJson, "length"),
+ http::RequestBodyJSON::required(columnJson, "ascending"));
}
bool const allWorkers = true;
diff --git a/src/replica/ingest/IngestDataHttpSvcMod.cc b/src/replica/ingest/IngestDataHttpSvcMod.cc
index bc66a6b23..7007dbef3 100644
--- a/src/replica/ingest/IngestDataHttpSvcMod.cc
+++ b/src/replica/ingest/IngestDataHttpSvcMod.cc
@@ -26,6 +26,8 @@
#include "http/BinaryEncoding.h"
#include "http/Exceptions.h"
#include "http/Method.h"
+#include "qhttp/Request.h"
+#include "qhttp/Response.h"
#include "replica/config/Configuration.h"
#include "replica/services/DatabaseServices.h"
#include "replica/util/Csv.h"
@@ -43,7 +45,7 @@ namespace util = lsst::qserv::util;
namespace {
/// @return requestor's IP address
-string senderIpAddr(qhttp::Request::Ptr const& req) {
+string senderIpAddr(shared_ptr const& req) {
ostringstream ss;
ss << req->remoteAddr.address();
return ss.str();
@@ -69,16 +71,17 @@ bool isBinaryColumnType(string const& type) {
namespace lsst::qserv::replica {
void IngestDataHttpSvcMod::process(ServiceProvider::Ptr const& serviceProvider, string const& workerName,
- qhttp::Request::Ptr const& req, qhttp::Response::Ptr const& resp,
- string const& subModuleName, http::AuthType const authType) {
+ shared_ptr const& req,
+ shared_ptr const& resp, string const& subModuleName,
+ http::AuthType const authType) {
IngestDataHttpSvcMod module(serviceProvider, workerName, req, resp);
module.execute(subModuleName, authType);
}
IngestDataHttpSvcMod::IngestDataHttpSvcMod(ServiceProvider::Ptr const& serviceProvider,
- string const& workerName, qhttp::Request::Ptr const& req,
- qhttp::Response::Ptr const& resp)
- : http::ModuleBase(serviceProvider->authKey(), serviceProvider->adminAuthKey(), req, resp),
+ string const& workerName, shared_ptr const& req,
+ shared_ptr const& resp)
+ : http::QhttpModule(serviceProvider->authKey(), serviceProvider->adminAuthKey(), req, resp),
IngestFileSvc(serviceProvider, workerName) {}
string IngestDataHttpSvcMod::context() const { return "INGEST-DATA-HTTP-SVC "; }
diff --git a/src/replica/ingest/IngestDataHttpSvcMod.h b/src/replica/ingest/IngestDataHttpSvcMod.h
index bfe6ff84d..b92d3ae93 100644
--- a/src/replica/ingest/IngestDataHttpSvcMod.h
+++ b/src/replica/ingest/IngestDataHttpSvcMod.h
@@ -28,13 +28,18 @@
#include "nlohmann/json.hpp"
// Qserv headers
-#include "http/ModuleBase.h"
-#include "qhttp/Request.h"
-#include "qhttp/Response.h"
+#include "http/QhttpModule.h"
#include "replica/ingest/IngestFileSvc.h"
#include "replica/ingest/TransactionContrib.h"
#include "replica/services/ServiceProvider.h"
+// Forward declarations
+
+namespace lsst::qserv::qhttp {
+class Request;
+class Response;
+} // namespace lsst::qserv::qhttp
+
// This header declarations
namespace lsst::qserv::replica {
@@ -44,7 +49,7 @@ namespace lsst::qserv::replica {
* Unlike class IngestHttpSvcMod, the current class is meant to be used for ingesting
* payloads that are pushed directly into the service over the HTTP protocol.
*/
-class IngestDataHttpSvcMod : public http::ModuleBase, public IngestFileSvc {
+class IngestDataHttpSvcMod : public http::QhttpModule, public IngestFileSvc {
public:
IngestDataHttpSvcMod() = delete;
IngestDataHttpSvcMod(IngestDataHttpSvcMod const&) = delete;
@@ -70,21 +75,22 @@ class IngestDataHttpSvcMod : public http::ModuleBase, public IngestFileSvc {
* @throws std::invalid_argument for unknown values of parameter 'subModuleName'
*/
static void process(ServiceProvider::Ptr const& serviceProvider, std::string const& workerName,
- qhttp::Request::Ptr const& req, qhttp::Response::Ptr const& resp,
- std::string const& subModuleName,
+ std::shared_ptr const& req,
+ std::shared_ptr const& resp, std::string const& subModuleName,
http::AuthType const authType = http::AuthType::REQUIRED);
protected:
- /// @see http::ModuleBase::context()
+ /// @see http::Module::context()
virtual std::string context() const final;
- /// @see http::ModuleBase::executeImpl()
+ /// @see http::Module::executeImpl()
virtual nlohmann::json executeImpl(std::string const& subModuleName) final;
private:
/// @see method IngestDataHttpSvcMod::create()
IngestDataHttpSvcMod(ServiceProvider::Ptr const& serviceProvider, std::string const& workerName,
- qhttp::Request::Ptr const& req, qhttp::Response::Ptr const& resp);
+ std::shared_ptr const& req,
+ std::shared_ptr const& resp);
/// Process a table contribution request (SYNC).
nlohmann::json _syncProcessData();
diff --git a/src/replica/ingest/IngestHttpSvcMod.cc b/src/replica/ingest/IngestHttpSvcMod.cc
index 154ad2979..7a44ed3ea 100644
--- a/src/replica/ingest/IngestHttpSvcMod.cc
+++ b/src/replica/ingest/IngestHttpSvcMod.cc
@@ -24,6 +24,8 @@
// Qserv header
#include "http/Method.h"
+#include "qhttp/Request.h"
+#include "qhttp/Response.h"
#include "replica/util/Csv.h"
// System headers
@@ -37,7 +39,7 @@ namespace lsst::qserv::replica {
void IngestHttpSvcMod::process(ServiceProvider::Ptr const& serviceProvider,
IngestRequestMgr::Ptr const& ingestRequestMgr, string const& workerName,
- qhttp::Request::Ptr const& req, qhttp::Response::Ptr const& resp,
+ qhttp::Request::Ptr const& req, shared_ptr const& resp,
string const& subModuleName, http::AuthType const authType) {
IngestHttpSvcMod module(serviceProvider, ingestRequestMgr, workerName, req, resp);
module.execute(subModuleName, authType);
@@ -45,8 +47,8 @@ void IngestHttpSvcMod::process(ServiceProvider::Ptr const& serviceProvider,
IngestHttpSvcMod::IngestHttpSvcMod(ServiceProvider::Ptr const& serviceProvider,
IngestRequestMgr::Ptr const& ingestRequestMgr, string const& workerName,
- qhttp::Request::Ptr const& req, qhttp::Response::Ptr const& resp)
- : http::ModuleBase(serviceProvider->authKey(), serviceProvider->adminAuthKey(), req, resp),
+ qhttp::Request::Ptr const& req, shared_ptr const& resp)
+ : http::QhttpModule(serviceProvider->authKey(), serviceProvider->adminAuthKey(), req, resp),
_serviceProvider(serviceProvider),
_ingestRequestMgr(ingestRequestMgr),
_workerName(workerName) {}
diff --git a/src/replica/ingest/IngestHttpSvcMod.h b/src/replica/ingest/IngestHttpSvcMod.h
index a015e21ac..0e24f322b 100644
--- a/src/replica/ingest/IngestHttpSvcMod.h
+++ b/src/replica/ingest/IngestHttpSvcMod.h
@@ -28,9 +28,7 @@
#include "nlohmann/json.hpp"
// Qserv headers
-#include "http/ModuleBase.h"
-#include "qhttp/Request.h"
-#include "qhttp/Response.h"
+#include "http/QhttpModule.h"
#include "replica/ingest/IngestRequest.h"
#include "replica/ingest/IngestRequestMgr.h"
#include "replica/services/ServiceProvider.h"
@@ -42,7 +40,7 @@ namespace lsst::qserv::replica {
* Class IngestHttpSvcMod processes chunk/table contribution requests made over HTTP.
* The class is used by the HTTP server built into the worker Ingest service.
*/
-class IngestHttpSvcMod : public http::ModuleBase {
+class IngestHttpSvcMod : public http::QhttpModule {
public:
IngestHttpSvcMod() = delete;
IngestHttpSvcMod(IngestHttpSvcMod const&) = delete;
@@ -81,22 +79,23 @@ class IngestHttpSvcMod : public http::ModuleBase {
*/
static void process(ServiceProvider::Ptr const& serviceProvider,
IngestRequestMgr::Ptr const& ingestRequestMgr, std::string const& workerName,
- qhttp::Request::Ptr const& req, qhttp::Response::Ptr const& resp,
- std::string const& subModuleName,
+ std::shared_ptr const& req,
+ std::shared_ptr const& resp, std::string const& subModuleName,
http::AuthType const authType = http::AuthType::REQUIRED);
protected:
- /// @see http::ModuleBase::context()
+ /// @see http::Module::context()
virtual std::string context() const final;
- /// @see http::ModuleBase::executeImpl()
+ /// @see http::Module::executeImpl()
virtual nlohmann::json executeImpl(std::string const& subModuleName) final;
private:
/// @see method IngestHttpSvcMod::create()
IngestHttpSvcMod(ServiceProvider::Ptr const& serviceProvider,
IngestRequestMgr::Ptr const& ingestRequestMgr, std::string const& workerName,
- qhttp::Request::Ptr const& req, qhttp::Response::Ptr const& resp);
+ std::shared_ptr const& req,
+ std::shared_ptr const& resp);
/// Process a table contribution request (SYNC).
nlohmann::json _syncProcessRequest() const;
diff --git a/src/replica/registry/RegistryHttpSvcMod.cc b/src/replica/registry/RegistryHttpSvcMod.cc
index a150acd95..58461f01a 100644
--- a/src/replica/registry/RegistryHttpSvcMod.cc
+++ b/src/replica/registry/RegistryHttpSvcMod.cc
@@ -40,7 +40,7 @@ using namespace lsst::qserv;
namespace {
/// @return requestor's IP address
-string senderIpAddr(qhttp::Request::Ptr const& req) {
+string senderIpAddr(shared_ptr const& req) {
ostringstream ss;
ss << req->remoteAddr.address();
return ss.str();
@@ -62,16 +62,17 @@ bool isSecurityContextKey(string const& key) {
namespace lsst::qserv::replica {
void RegistryHttpSvcMod::process(ServiceProvider::Ptr const& serviceProvider, RegistryServices& services,
- qhttp::Request::Ptr const& req, qhttp::Response::Ptr const& resp,
- string const& subModuleName, http::AuthType const authType) {
+ shared_ptr const& req,
+ shared_ptr const& resp, string const& subModuleName,
+ http::AuthType const authType) {
RegistryHttpSvcMod module(serviceProvider, services, req, resp);
module.execute(subModuleName, authType);
}
RegistryHttpSvcMod::RegistryHttpSvcMod(ServiceProvider::Ptr const& serviceProvider,
- RegistryServices& services, qhttp::Request::Ptr const& req,
- qhttp::Response::Ptr const& resp)
- : http::ModuleBase(serviceProvider->authKey(), serviceProvider->adminAuthKey(), req, resp),
+ RegistryServices& services, shared_ptr const& req,
+ shared_ptr const& resp)
+ : http::QhttpModule(serviceProvider->authKey(), serviceProvider->adminAuthKey(), req, resp),
_serviceProvider(serviceProvider),
_services(services) {}
diff --git a/src/replica/registry/RegistryHttpSvcMod.h b/src/replica/registry/RegistryHttpSvcMod.h
index f4477fd15..ae33a25bd 100644
--- a/src/replica/registry/RegistryHttpSvcMod.h
+++ b/src/replica/registry/RegistryHttpSvcMod.h
@@ -28,9 +28,7 @@
#include "nlohmann/json.hpp"
// Qserv headers
-#include "http/ModuleBase.h"
-#include "qhttp/Request.h"
-#include "qhttp/Response.h"
+#include "http/QhttpModule.h"
#include "replica/services/ServiceProvider.h"
// Forward declarations
@@ -47,7 +45,7 @@ namespace lsst::qserv::replica {
* @note Each worker entry represents a collection of attributes merged from
* from two sources - Replication System's worker and Qserv worker.
*/
-class RegistryHttpSvcMod : public http::ModuleBase {
+class RegistryHttpSvcMod : public http::QhttpModule {
public:
RegistryHttpSvcMod() = delete;
RegistryHttpSvcMod(RegistryHttpSvcMod const&) = delete;
@@ -79,21 +77,22 @@ class RegistryHttpSvcMod : public http::ModuleBase {
* @throws std::invalid_argument for unknown values of parameter 'subModuleName'
*/
static void process(ServiceProvider::Ptr const& serviceProvider, RegistryServices& services,
- qhttp::Request::Ptr const& req, qhttp::Response::Ptr const& resp,
- std::string const& subModuleName,
+ std::shared_ptr const& req,
+ std::shared_ptr const& resp, std::string const& subModuleName,
http::AuthType const authType = http::AuthType::REQUIRED);
protected:
- /// @see http::ModuleBase::context()
+ /// @see http::Module::context()
virtual std::string context() const final;
- /// @see http::ModuleBase::executeImpl()
+ /// @see http::Module::executeImpl()
virtual nlohmann::json executeImpl(std::string const& subModuleName) final;
private:
/// @see method RegistryHttpSvcMod::create()
RegistryHttpSvcMod(ServiceProvider::Ptr const& serviceProvider, RegistryServices& services,
- qhttp::Request::Ptr const& req, qhttp::Response::Ptr const& resp);
+ std::shared_ptr const& req,
+ std::shared_ptr const& resp);
/// Return a collection of known services.
nlohmann::json _getServices() const;
diff --git a/src/xrdsvc/HttpModule.cc b/src/xrdsvc/HttpModule.cc
index 53beae66b..46bc058e5 100644
--- a/src/xrdsvc/HttpModule.cc
+++ b/src/xrdsvc/HttpModule.cc
@@ -27,7 +27,7 @@
// Qserv headers
#include "http/Exceptions.h"
-#include "http/RequestBody.h"
+#include "http/RequestBodyJSON.h"
#include "http/RequestQuery.h"
#include "qhttp/Request.h"
#include "wbase/TaskState.h"
@@ -41,8 +41,8 @@ namespace lsst::qserv::xrdsvc {
HttpModule::HttpModule(string const& context, shared_ptr const& foreman,
shared_ptr const& req, shared_ptr const& resp)
- : http::ModuleBase(wconfig::WorkerConfig::instance()->replicationAuthKey(),
- wconfig::WorkerConfig::instance()->replicationAdminAuthKey(), req, resp),
+ : http::QhttpModule(wconfig::WorkerConfig::instance()->replicationAuthKey(),
+ wconfig::WorkerConfig::instance()->replicationAdminAuthKey(), req, resp),
_context(context),
_foreman(foreman) {}
diff --git a/src/xrdsvc/HttpModule.h b/src/xrdsvc/HttpModule.h
index 31d85e162..fb122041d 100644
--- a/src/xrdsvc/HttpModule.h
+++ b/src/xrdsvc/HttpModule.h
@@ -26,7 +26,7 @@
#include
// Qserv headers
-#include "http/ModuleBase.h"
+#include "http/QhttpModule.h"
namespace lsst::qserv::qhttp {
class Request;
@@ -48,7 +48,7 @@ namespace lsst::qserv::xrdsvc {
/**
* Class HttpModule is an intermediate base class of the Qserv worker modules.
*/
-class HttpModule : public http::ModuleBase {
+class HttpModule : public http::QhttpModule {
public:
HttpModule() = delete;
HttpModule(HttpModule const&) = delete;
diff --git a/src/xrdsvc/HttpReplicaMgtModule.cc b/src/xrdsvc/HttpReplicaMgtModule.cc
index 5ffcb03f7..afa81d74c 100644
--- a/src/xrdsvc/HttpReplicaMgtModule.cc
+++ b/src/xrdsvc/HttpReplicaMgtModule.cc
@@ -32,7 +32,7 @@
// Qserv headers
#include "http/Exceptions.h"
-#include "http/RequestBody.h"
+#include "http/RequestBodyJSON.h"
#include "http/RequestQuery.h"
#include "mysql/MySqlUtils.h"
#include "util/String.h"