16.12.2021, 9:00 - 11:00: Due to updates GitLab may be unavailable for some minutes between 09:00 and 11:00.

Commit 3f5c8649 authored by Michael Ott's avatar Michael Ott
Browse files

Leverage Boost BEAST for HTTP communication

parent 1afc3337
...@@ -3,7 +3,7 @@ DCDBDEPSPATH ?= $(DCDBBASEPATH)/deps ...@@ -3,7 +3,7 @@ DCDBDEPSPATH ?= $(DCDBBASEPATH)/deps
DCDBDEPLOYPATH ?= $(DCDBBASEPATH)/install DCDBDEPLOYPATH ?= $(DCDBBASEPATH)/install
# dcdbpusher plugins to be built # dcdbpusher plugins to be built
PLUGINS = sysfs ipmi pdu bacnet snmp procfs tester gpfsmon msr PLUGINS = sysfs ipmi pdu bacnet snmp procfs tester gpfsmon msr rest
# data analytics plugins to be built # data analytics plugins to be built
OPERATORS = aggregator smoothing regressor classifier clustering cssignatures job_aggregator testeroperator filesink smucngperf persystsql coolingcontrol healthchecker OPERATORS = aggregator smoothing regressor classifier clustering cssignatures job_aggregator testeroperator filesink smucngperf persystsql coolingcontrol healthchecker
......
...@@ -46,12 +46,13 @@ void RESTConfigurator::sensorBase(RESTSensorBase &s, CFG_VAL config) { ...@@ -46,12 +46,13 @@ void RESTConfigurator::sensorBase(RESTSensorBase &s, CFG_VAL config) {
void RESTConfigurator::sensorGroup(RESTSensorGroup &s, CFG_VAL config) { void RESTConfigurator::sensorGroup(RESTSensorGroup &s, CFG_VAL config) {
ADD { ADD {
ATTRIBUTE("endpoint", setEndpoint);
ATTRIBUTE("request", setRequest); ATTRIBUTE("request", setRequest);
} }
} }
void RESTConfigurator::sensorEntity(RESTUnit &s, CFG_VAL config) { void RESTConfigurator::sensorEntity(RESTUnit &s, CFG_VAL config) {
ADD { ADD {
ATTRIBUTE("host", setHost); ATTRIBUTE("baseurl", setBaseURL);
} }
} }
...@@ -38,12 +38,14 @@ RESTSensorGroup::RESTSensorGroup(const std::string &name) ...@@ -38,12 +38,14 @@ RESTSensorGroup::RESTSensorGroup(const std::string &name)
RESTSensorGroup::RESTSensorGroup(const RESTSensorGroup &other) RESTSensorGroup::RESTSensorGroup(const RESTSensorGroup &other)
: SensorGroupTemplateEntity(other), : SensorGroupTemplateEntity(other),
_endpoint(other._endpoint),
_request(other._request) {} _request(other._request) {}
RESTSensorGroup::~RESTSensorGroup() {} RESTSensorGroup::~RESTSensorGroup() {}
RESTSensorGroup &RESTSensorGroup::operator=(const RESTSensorGroup &other) { RESTSensorGroup &RESTSensorGroup::operator=(const RESTSensorGroup &other) {
SensorGroupTemplate::operator=(other); SensorGroupTemplate::operator=(other);
_endpoint = other._endpoint;
_request = other._request; _request = other._request;
return *this; return *this;
...@@ -52,7 +54,7 @@ RESTSensorGroup &RESTSensorGroup::operator=(const RESTSensorGroup &other) { ...@@ -52,7 +54,7 @@ RESTSensorGroup &RESTSensorGroup::operator=(const RESTSensorGroup &other) {
void RESTSensorGroup::read() { void RESTSensorGroup::read() {
//send request //send request
std::string response; std::string response;
if (!_entity->sendRequest(_request, response)) { if (!_entity->sendRequest(_endpoint, _request, response)) {
LOG(error) << _groupName << " could not send request!"; LOG(error) << _groupName << " could not send request!";
return; return;
} }
...@@ -112,7 +114,35 @@ void RESTSensorGroup::read() { ...@@ -112,7 +114,35 @@ void RESTSensorGroup::read() {
if (readStr == "") { if (readStr == "") {
throw std::runtime_error("Value not found!"); throw std::runtime_error("Value not found!");
} }
reading.value = stoll(readStr);
size_t idx;
uint64_t ival = stoll(readStr, &idx);
double dval = .0;
bool have_float = false;
if (idx < readStr.size()) {
if (readStr[idx] == '.') {
dval = stod(readStr, &idx);
have_float = true;
}
}
uint64_t factor = 1;
if (idx < readStr.size()) {
switch(readStr[idx]) {
case 'k':
case 'K': factor = 1000ll;
break;
case 'm':
case 'M': factor = 1000000ll;
break;
default:
break;
}
}
if (have_float) {
reading.value = dval * factor;
} else {
reading.value = ival * factor;
}
#ifdef DEBUG #ifdef DEBUG
LOG(debug) << _groupName << "::" << s->getName() << " raw reading: \"" << reading.value << "\""; LOG(debug) << _groupName << "::" << s->getName() << " raw reading: \"" << reading.value << "\"";
...@@ -127,5 +157,6 @@ void RESTSensorGroup::read() { ...@@ -127,5 +157,6 @@ void RESTSensorGroup::read() {
void RESTSensorGroup::printGroupConfig(LOG_LEVEL ll, unsigned int leadingSpaces) { void RESTSensorGroup::printGroupConfig(LOG_LEVEL ll, unsigned int leadingSpaces) {
std::string leading(leadingSpaces, ' '); std::string leading(leadingSpaces, ' ');
LOG_VAR(ll) << leading << "Endpoint: " << _endpoint;
LOG_VAR(ll) << leading << "Request: " << _request; LOG_VAR(ll) << leading << "Request: " << _request;
} }
...@@ -44,6 +44,8 @@ class RESTSensorGroup : public SensorGroupTemplateEntity<RESTSensorBase, RESTUni ...@@ -44,6 +44,8 @@ class RESTSensorGroup : public SensorGroupTemplateEntity<RESTSensorBase, RESTUni
virtual ~RESTSensorGroup(); virtual ~RESTSensorGroup();
RESTSensorGroup &operator=(const RESTSensorGroup &other); RESTSensorGroup &operator=(const RESTSensorGroup &other);
void setEndpoint(const std::string &endpoint) { _endpoint = endpoint; }
const std::string &getEndpoint() { return _endpoint; }
void setRequest(const std::string &request) { _request = request; } void setRequest(const std::string &request) { _request = request; }
const std::string &getRequest() { return _request; } const std::string &getRequest() { return _request; }
...@@ -52,6 +54,7 @@ class RESTSensorGroup : public SensorGroupTemplateEntity<RESTSensorBase, RESTUni ...@@ -52,6 +54,7 @@ class RESTSensorGroup : public SensorGroupTemplateEntity<RESTSensorBase, RESTUni
private: private:
void read() final override; void read() final override;
std::string _endpoint;
std::string _request; std::string _request;
}; };
......
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
//================================================================================ //================================================================================
#include "RESTUnit.h" #include "RESTUnit.h"
#include "globalconfiguration.h"
#include <iostream> #include <iostream>
...@@ -34,92 +35,80 @@ ...@@ -34,92 +35,80 @@
RESTUnit::RESTUnit(const std::string &name) RESTUnit::RESTUnit(const std::string &name)
: EntityInterface(name), : EntityInterface(name),
_ctx(nullptr) { _ctx(ssl::context::tlsv12_client),
_ssl(false) {
} }
RESTUnit::RESTUnit(const RESTUnit &other) RESTUnit::RESTUnit(const RESTUnit &other)
: EntityInterface(other), : EntityInterface(other),
_ctx(nullptr) { _ctx(ssl::context::tlsv12_client),
_ssl(false) {
} }
RESTUnit::~RESTUnit() { RESTUnit::~RESTUnit() {
if (_ctx) {
SSL_CTX_free(_ctx);
}
} }
RESTUnit &RESTUnit::operator=(const RESTUnit &other) { RESTUnit &RESTUnit::operator=(const RESTUnit &other) {
EntityInterface::operator=(other); EntityInterface::operator=(other);
_ctx = nullptr; _ssl = other._ssl;
return *this; return *this;
} }
void RESTUnit::execOnInit() { void RESTUnit::execOnInit() {
SSL_library_init();
SSL_load_error_strings();
ERR_load_BIO_strings();
OpenSSL_add_all_algorithms();
_ctx = SSL_CTX_new(SSLv23_method());
} }
bool RESTUnit::sendRequest(const std::string &request, std::string &response) { bool RESTUnit::sendRequest(const std::string &endpoint, const std::string &request, std::string &response) {
// Look up the domain name
BIO *bio; tcp::resolver resolver(_ioc);
auto const hosts = resolver.resolve(_hostname, _port);
if (!_ctx) {
LOG(error) << "OpenSSL: Could not create context: " << ERR_reason_error_string(ERR_get_error()); beast::ssl_stream<beast::tcp_stream> stream(_ioc, _ctx);
return false; // Make the connection on the IP address we get from a lookup
beast::get_lowest_layer(stream).connect(hosts);
if (_ssl) {
// Set SNI Hostname (many hosts need this to handshake successfully)
if(! SSL_set_tlsext_host_name(stream.native_handle(), _hostname.c_str()))
{
beast::error_code ec{static_cast<int>(::ERR_get_error()), net::error::get_ssl_category()};
throw beast::system_error{ec};
} }
/* // Perform the SSL handshake
if (!SSL_CTX_load_verify_locations(ctx, "/home/micha/LRZ/dcdbOwnFork/deps/openssl-1.0.2l/certs/demo/ca-cert.pem", NULL)) { stream.handshake(ssl::stream_base::client);
LOG(error) << "OpenSSL: Could not load certificate: " << ERR_reason_error_string(ERR_get_error());
SSL_CTX_free(ctx);
return;
} }
*/
bio = BIO_new_ssl_connect(_ctx);
BIO_set_conn_hostname(bio, _name.c_str()); // Set up an HTTP GET request message
http::request<http::string_body> req{http::verb::get, _path + endpoint, 11};
if (BIO_do_connect(bio) <= 0) { req.set(http::field::host, _hostname);
LOG(error) << "OpenSSL: Could not connect: " << ERR_reason_error_string(ERR_get_error()); req.body() = request;
BIO_free_all(bio); req.prepare_payload();
return false;
// Send the HTTP request to the remote host
if (_ssl) {
http::write(stream, req);
} else {
http::write(beast::get_lowest_layer(stream), req);
} }
size_t len = request.length(); // This buffer is used for reading and must be persisted
const char *reqBuf = request.c_str(); //request beast::flat_buffer buffer;
char resBuf[2048]; // Declare a container to hold the response
memset(resBuf, 0, 2048); http::response<http::string_body> res;
// do not bother too long if a write/read failed. Sensor intervals are usually small, just try again when next sensor wants to read. // Receive the HTTP response
if (_ssl) {
// send request http::read(stream, buffer, res);
if (BIO_write(bio, reqBuf, len) <= 0) { } else {
LOG(error) << "OpenSSL: Could not send request: " << ERR_reason_error_string(ERR_get_error()); http::read(beast::get_lowest_layer(stream), buffer, res);
BIO_free_all(bio);
return false;
}
// read reply
response.clear();
while ((len = BIO_read(bio, resBuf, sizeof(resBuf))) > 0) {
resBuf[len] = 0;
response.append(resBuf);
} }
if (len < 0) { if (res.body().size() > 0) {
std::cerr << "OpenSSL: Could not read response: " << ERR_reason_error_string(ERR_get_error()) << std::endl; response = res.body();
BIO_free_all(bio); return true;
} else {
return false; return false;
} }
BIO_free_all(bio);
return true;
} }
...@@ -35,8 +35,24 @@ ...@@ -35,8 +35,24 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/ssl.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl/error.hpp>
#include <boost/asio/ssl/stream.hpp>
#include <boost/regex.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/property_tree/ptree.hpp> #include <boost/property_tree/ptree.hpp>
#include <openssl/ssl.h>
namespace beast = boost::beast; // from <boost/beast.hpp>
namespace http = beast::http; // from <boost/beast/http.hpp>
namespace net = boost::asio; // from <boost/asio.hpp>
namespace ssl = net::ssl; // from <boost/asio/ssl.hpp>
using tcp = net::ip::tcp; // from <boost/asio/ip/tc
typedef std::vector<std::pair<std::string, std::string>> attributesVector_t; typedef std::vector<std::pair<std::string, std::string>> attributesVector_t;
typedef std::vector<std::tuple<std::string, std::string, attributesVector_t>> xmlPathVector_t; typedef std::vector<std::tuple<std::string, std::string, attributesVector_t>> xmlPathVector_t;
...@@ -54,16 +70,31 @@ class RESTUnit : public EntityInterface { ...@@ -54,16 +70,31 @@ class RESTUnit : public EntityInterface {
virtual ~RESTUnit(); virtual ~RESTUnit();
RESTUnit &operator=(const RESTUnit &other); RESTUnit &operator=(const RESTUnit &other);
void setHost(const std::string &host) { void setBaseURL(const std::string &baseURL) {
size_t pos = host.find(':'); _baseURL = baseURL;
if (pos != std::string::npos) { // URL parsing courtesy of https://stackoverflow.com/a/61214599
_name = host; boost::regex ex("(http|https)://([^/ :]+):?([^/ ]*)(/?[^ #?]*)\\x3f?([^ #]*)#?([^ ]*)");
boost::cmatch what;
if(regex_match(baseURL.c_str(), what, ex))
{
std::string protocol = std::string(what[1].first, what[1].second);
_hostname = std::string(what[2].first, what[2].second);
_port = std::string(what[3].first, what[3].second);
_path = std::string(what[4].first, what[4].second);
std::string query = std::string(what[5].first, what[5].second);
if (boost::iequals(protocol, "https")) {
_ssl = true;
}
if (_port.size() == 0) {
if (_ssl) {
_port = "443";
} else { } else {
_name = host + ":443"; _port = "80";
} }
} }
}
const std::string &getHost() { return _name; } }
const std::string &getBaseURL() { return _baseURL; }
/** /**
* Send the request to the host and write the response into response. * Send the request to the host and write the response into response.
...@@ -73,12 +104,18 @@ class RESTUnit : public EntityInterface { ...@@ -73,12 +104,18 @@ class RESTUnit : public EntityInterface {
* *
* @return True on success, false otherwise * @return True on success, false otherwise
*/ */
bool sendRequest(const std::string &request, std::string &response); bool sendRequest(const std::string &endpoint, const std::string &request, std::string &response);
void execOnInit() final override; void execOnInit() final override;
private: private:
SSL_CTX *_ctx; net::io_context _ioc;
ssl::context _ctx;
std::string _baseURL;
std::string _hostname;
std::string _port;
std::string _path;
bool _ssl;
}; };
#endif /* RESTUNIT_H_ */ #endif /* RESTUNIT_H_ */
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment