Commit cd80651e authored by Micha Mueller's avatar Micha Mueller
Browse files

Add first functionality to HttpsServer: allow switching on/off plugins via a PUT request

parent f6ac60c2
......@@ -13,7 +13,7 @@ include $(DCDBCOREPATH)/common.mk
CXXFLAGS = -std=c++11 -DBOOST_DATE_TIME_POSIX_TIME_STD_CONFIG -DBOOST_NETWORK_ENABLE_HTTPS -O2 -g -Wall -Wno-unused-function -Wno-deprecated-declarations -Wno-unused-variable -DBOOST_LOG_DYN_LINK -I$(DCDBBASEPATH)/dcdb/include -I$(DCDBDEPLOYPATH)/include -I$(DCDBDEPSPATH)/cpp-netlib-0.12.0-final/deps/asio/asio/include
LIBS = -L../deps/mosquitto_build/lib -L$(DCDBDEPLOYPATH)/lib/ -ldl -lmosquitto -lboost_system -lboost_thread -lboost_log_setup -lboost_log -lpthread -lcrypto -lssl -lcppnetlib-server-parsers -rdynamic
LIBS = -L../deps/mosquitto_build/lib -L$(DCDBDEPLOYPATH)/lib/ -ldl -lmosquitto -lboost_system -lboost_thread -lboost_log_setup -lboost_log -lpthread -lcrypto -lssl -lcppnetlib-server-parsers -lcppnetlib-uri -rdynamic
OBJS = src/dcdbpusher.o src/Configuration.o src/Sensor.o src/MQTTPusher.o src/HttpsServer.o
PLUGINS_BASE = libdcdbplugin_pdu libdcdbplugin_sysfs libdcdbplugin_ipmi libdcdbplugin_bacnet
......
......@@ -36,6 +36,7 @@ Dcdbpusher will check the given file-path for the global configuration file `glo
`global.conf` should have the following scheme:
```
global {
restAddr localhost:8000
mqttBroker localhost:1883
mqttprefix /00112233445566778899AABB0000
threads 24
......@@ -62,6 +63,7 @@ Explanation of the values:
| Value             | Explanation |
|:----- |:----------- |
| global | Wrapper structure for the global values.
|   restAddr | Define address and port where the REST API should run on. See the corresponding [section](#restAPI) for more information.
|   mqttBroker | Define address and port of the MQTT-broker which collects the messages (sensor values) send by dcdbpusher.
|   mqttprefix | To not rewrite a full MQTT-topic for every sensor one can specify here a consistend prefix.
|   threads | Specify how many threads should be created to handle the sensors async. Default value of threads is 1. Note that the MQTTPusher always starts an extra thread. So the actual number of started threads is always one more than defined here. Specifying not enough threads can result in a delay for some sensors until they are read.
......@@ -79,6 +81,12 @@ Explanation of the values:
Formats of the other sensor-specific config-files are explained in the corresponding [subsections](#IPMI). Example configuration-files can be found in the `config/` directory.
## <a name="restApi">REST API</a>
Dcdbpusher provides some functionality to be controlled over a REST API. The API is by default hosted at port 8000 on the localhost but the address can be changed in the [`global.conf`](#GC).
Currently dcdbpusher allows to switch plugins on or off via the REST API. To do so, one has to send a PUT request of the form `host:port/pluginName/start|stop?authkey=YourToken` to the API.
## MQTT topic
For communication between the different DCDB-components (database, dcdbpusher) the [MQTT protocol](https://mqtt.org/) is used. In order to identify each sensor, everyone has to have a unique MQTT topic assigned. A MQTT topic for DCDB consists of exactly 128 bits (= 32 hex characters), not including '/' separators.
......
......@@ -6,19 +6,107 @@
*/
#include "HttpsServer.h"
#include <iostream>
#include <memory>
#include <functional>
HttpsServer::requestHandler::requestHandler(HttpsServer& httpsServer) : _httpsServer(httpsServer) {}
void HttpsServer::requestHandler::operator()(server::request const &request, server::connection_ptr connection) {
//first log some info about client
server::string_type ip = source(request);
unsigned int port = request.source_port;
server::string_type method = request.method;
std::ostringstream data;
connection->set_status(server::connection::internal_server_error);
server::response_header headers[] = { {"Connection", "close"}, {"Content-Type", "text/plain"} };
data << "Hello, " << ip << ':' << port << '!' << std::endl;
LOG(info) << "HttpsServer: " << ip << ":" << port << " connected";
//select code depending on request
if(method == "PUT") {
LOG(info) << "HttpsServer: PUT request to " << request.destination << " was made";
std::string response = "";
boost::network::uri::uri uri("https://" + request.destination);
server::string_type path = uri.path();
server::string_type query = uri.query();
std::string plugin = path;
std::string action = "";
std::string auth_key = query;
std::string auth_value = "";
//do some string processing
//split up query into key and value
size_t pos = auth_key.find("=");
if (pos != std::string::npos) {
auth_value = auth_key.substr(pos+1);
auth_key.erase(pos);
}
//split up path into plugin and action
if (plugin.size() >= 2) {
if (plugin[0] == '/') {
plugin.erase(0,1);
}
if (plugin[plugin.size() -1] == '/') {
plugin.erase(plugin.size() -1);
}
connection->set_status(server::connection::ok);
pos = plugin.find("/");
if (pos != std::string::npos) {
action = plugin.substr(pos+1);
plugin.erase(pos);
}
}
//finished string processing
//process actual request
//check if query and action are valid values
if (auth_key == "authkey" && ((action == "start") || (action == "stop"))) {
//check if authkey is valid
if (check_authkey(auth_value)) {
response = "Plugin not found!";
connection->set_status(server::connection::not_found);
for(auto& p : _httpsServer._plugins) {
if (p.id == plugin) {
if (action == "start") {
for(auto s : p.configurator->getSensors()) {
s->startPolling();
}
response = "Plugin " + plugin + ": Sensors started";
connection->set_status(server::connection::ok);
} else if (action == "stop") {
for(auto s : p.configurator->getSensors()) {
s->stopPolling();
}
response = "Plugin " + plugin + ": Sensors stopped";
connection->set_status(server::connection::ok);
}
}
}
} else {
response = "Invalid authentication key!";
connection->set_status(server::connection::unauthorized);
}
} else {
response = "PUT requests should be of the form \"host:port/[plugin]/[action]?authkey=[token]\"\n"
"Where [plugin] names a plugin, [action] is either \"start\" or \"stop\" and [token] is your authentication key";
connection->set_status(server::connection::bad_request);
}
LOG(info) << "HttpsServer: Responding: " << response;
data << response << std::endl;
} else {
LOG(info) << "HttpsServer: Received unsupported " << method << " request";
connection->set_status(server::connection::not_supported);
}
//send response
server::response_header headers[] = { {"Connection", "close"}, {"Content-Type", "text/plain"} };
connection->set_headers(boost::make_iterator_range(headers, headers + 2));
connection->write(data.str());
}
......@@ -27,9 +115,12 @@ void HttpsServer::requestHandler::log(const server::string_type& message) {
LOG(error) << message;
}
HttpsServer::HttpsServer(const std::string& host, const std::string& port,
pluginVector_t& plugins) :
_host(host), _port(port), _plugins(plugins) {
bool HttpsServer::requestHandler::check_authkey(const std::string& authkey) {
return authkey == "qwertz" ? true : false;
}
HttpsServer::HttpsServer(const std::string& host, const std::string& port, pluginVector_t& plugins) :
_host(host), _port(port), _plugins(plugins), _handler(*this) {
std::shared_ptr<asio::ssl::context> ctx = std::make_shared<asio::ssl::context>(asio::ssl::context::sslv23);
ctx->set_options(asio::ssl::context::default_workarounds | asio::ssl::context::no_sslv3 | asio::ssl::context::single_dh_use);
......
......@@ -41,11 +41,26 @@ private:
struct requestHandler;
typedef boost::network::http::server<requestHandler> server;
/*
* Struct which handles requests. The methods defined in here are called by the server if a client makes a request
*/
struct requestHandler {
requestHandler(HttpsServer& httpsServer);
void operator()(server::request const &request, server::connection_ptr connection);
void log(const server::string_type& message);
/*
* Check if the authkey is valid and/or authorized to make requests
*
* @param authkey The authkey to check
* @return True if authkey is valid/authorized, false otherwise
*/
bool check_authkey(const std::string& authkey);
private:
HttpsServer& _httpsServer; //to remember who is our parent. We need access to its _plugins
boost::log::sources::severity_logger<boost::log::trivial::severity_level> lg;
};
......
Supports Markdown
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