HttpsServer.cpp 12.2 KB
Newer Older
1
2
3
4
5
6
7
8
/*
 * HttpsServer.cpp
 *
 *  Created on: 25.05.2018
 *      Author: Micha Mueller
 */

#include "HttpsServer.h"
9
#include "timestamp.h"
10

11
#include <iostream>
12
13
#include <memory>
#include <functional>
14
#include <string>
15

16
17
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/info_parser.hpp>
Micha Mueller's avatar
Micha Mueller committed
18
19
#include <boost/algorithm/string/split.hpp>

20
21
#define LOGH(sev) LOG(sev) << "HttpsServer: "

22
23
HttpsServer::requestHandler::requestHandler(HttpsServer& httpsServer) :	_httpsServer(httpsServer) {}

24
void HttpsServer::requestHandler::operator()(server::request const &request, server::connection_ptr connection) {
25
	//first log some info about client
26
27
28
	server::string_type ip = source(request);
	unsigned int port = request.source_port;

29
	LOGH(info) << ip << ":" << port << " connected";
30

31
	//set appropriate default value to connection status
Micha Mueller's avatar
Micha Mueller committed
32
33
34
	connection->set_status(server::connection::internal_server_error);

	std::ostringstream data;
35
36

	boost::network::uri::uri uri("https://" + request.destination);
Micha Mueller's avatar
Micha Mueller committed
37
	server::string_type method = request.method;
38
39
40
41
42
	server::string_type path = uri.path();
	server::string_type query = uri.query();

	std::string response = "";
	std::string auth_value = "";
43
	bool json = false;
44
45
	std::vector<std::string> pathStrs;
	std::vector<std::pair<std::string, std::string>> queries;
46

Micha Mueller's avatar
Micha Mueller committed
47
48
	//first check if request is supported at all
	if (method != "GET" && method != "PUT") {
49
		LOGH(warning) << "Unsupported " << method << " request was made";
Micha Mueller's avatar
Micha Mueller committed
50
		connection->set_status(server::connection::not_supported);
51
52
		goto error;
	}
53

54
	LOGH(info) << method << " request of " << request.destination << " was made";
Micha Mueller's avatar
Micha Mueller committed
55

56
57
58
59
60
61
62
63
	//do some string processing
	//split path into its hierarchical parts
	if (path.size() >= 2) {
		if (path[0] == '/') {
			path.erase(0,1);
		}
		if (path[path.size() -1] == '/') {
			path.erase(path.size() -1);
Micha Mueller's avatar
Micha Mueller committed
64
		}
65

66
67
68
69
		boost::split(pathStrs, path, boost::is_any_of("/"), boost::token_compress_off);
	}

	//split query part into the individual queries (key-value pairs)
Micha Mueller's avatar
Micha Mueller committed
70
71
	{	//do not remove the enclosing brackets
		//need to encapsulate this code block to keep queryStrs local
Micha Mueller's avatar
Micha Mueller committed
72
73
74
75
		std::vector<std::string> queryStrs;
		boost::split(queryStrs, query, boost::is_any_of(";"), boost::token_compress_on);
		for(auto& key : queryStrs) {
			size_t pos = key.find("=");
76
			if (pos != std::string::npos) {
Micha Mueller's avatar
Micha Mueller committed
77
78
79
80
				std::string value;
				value = key.substr(pos+1);
				key.erase(pos);
				queries.push_back(std::make_pair(key, value));
81
82
			}
		}
83
84
	}
	//finished string processing
85

86
	for (auto& p : queries) {
87
		//authkey is required in every case
88
89
		if (p.first == "authkey") {
			auth_value = p.second;
90
91
92
93
		} else if (p.first == "json") {
			if (stoi(p.second) > 0) {
				json = true;
			}
94
		}
95
	}
96

97
	if (pathStrs.size() < 1) {
98
		LOGH(warning) << "Received malformed request: No first path part";
99
100
101
102
103
104
105
106
		connection->set_status(server::connection::bad_request);
		goto error;
	}

	//select code depending on request
	if (method == "GET") {
		if (pathStrs[0] == "help") {
			response = "dcdbpusher RESTful API cheatsheet:\n"
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
					   " -GET:  /help     This help message\n"
					   "        /plugins  List of currently loaded plugins (Discovery)\n"
					   "        /[plugin]/sensors\n"
					   "                  List of currently running sensors which belong\n"
					   "                  to the specified plugin (Discovery)\n"
					   "        /[plugin]/[sensor]/avg?interval=[timeInSec]\n"
					   "                  Average of last sensor readings from the last\n"
					   "                  [interval] seconds or of all cached readings\n"
					   "                  if no interval is given\n"
					   " -PUT:  /[plugin]/[start|stop|reload]\n"
					   "                  Start/stop the sensors of the plugin or\n"
					   "                  reload the plugin configuration\n"
					   "\n"
					   "All resources have to be prepended by host:port and need at\n"
					   "least the query ?authkey=[token] at the end. Multiple queries\n"
					   "need to be separated by semicolons(';')\n";
Micha Mueller's avatar
Micha Mueller committed
123
		} else {
124
125
126
127
			//first check permission
			if (!_httpsServer.check_authkey(auth_value, permission::GETReq)) {
				LOGH(warning) << "Provided authentication token has insufficient permissions";
				connection->set_status(server::connection::unauthorized);
128
129
				goto error;
			}
130

131
132
133
134
135
136
137
138
139
140
141
			if (pathStrs[0] == "plugins") {
				if (json) {
					boost::property_tree::ptree root, plugins;
					for(auto& p : _httpsServer._plugins) {
						plugins.put(p.id, "");
					}
					root.add_child("plugins", plugins);
					boost::property_tree::write_info(data, root);
				} else {
					for(auto& p : _httpsServer._plugins) {
						data << p.id << "\n";
142
143
					}
				}
144
			} else {
145
146
147
				//do some prior checks
				if (pathStrs.size() < 2) {
					LOGH(warning) << "Received malformed request: No second path part";
Micha Mueller's avatar
Micha Mueller committed
148
					connection->set_status(server::connection::bad_request);
149
150
					goto error;
				}
151

152
153
154
				if (pathStrs[1] == "sensors") {
					response = "Plugin not found!";
					connection->set_status(server::connection::not_found);
155

156
157
158
159
					for(auto& p : _httpsServer._plugins) {
						if (p.id == pathStrs[0]) {
							if (json) {
								boost::property_tree::ptree root, sensors;
160

161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
								for(auto g : p.configurator->getSensorGroups()) {
									boost::property_tree::ptree group;
									for(auto s : g->getSensors()) {
										group.put(s->getName(), s->getMqtt());
									}
									sensors.add_child(g->getGroupName(), group);
								}
								root.add_child(p.id, sensors);
								boost::property_tree::write_info(data, root);
							} else {
								for(auto g : p.configurator->getSensorGroups()) {
									for(auto s : g->getSensors()) {
										data << g->getGroupName() << "." << s->getName() << " " << s->getMqtt() << "\n";
									}
								}
							}
							response = "";
							connection->set_status(server::connection::ok);
							break;
						}
Micha Mueller's avatar
Micha Mueller committed
181
					}
182
				} else {
183

184
185
186
187
188
					if (pathStrs.size() < 3) {
						LOGH(warning) << "Received malformed request: No third path part";
						connection->set_status(server::connection::bad_request);
						goto error;
					}
189

190
191
192
193
194
					if (pathStrs[2] != "avg") {
						LOGH(warning) << "Unknown action " << pathStrs[2] << " requested";
						connection->set_status(server::connection::not_supported);
						goto error;
					}
195

196
197
198
					std::string sensor = pathStrs[1];
					std::string action = pathStrs[2];
					uint64_t time = 0;
199

200
201
202
203
204
205
					for (auto& p : queries) {
						if (p.first == "interval") {
							time = getTimestamp();
							time -= S_TO_NS(std::stoul(p.second));
						}
					}
206

207
208
209
					//process actual request
					response = "Plugin not found!";
					connection->set_status(server::connection::not_found);
210

211
212
213
					for(auto& p : _httpsServer._plugins) {
						if (p.id == pathStrs[0]) {
							response = "Sensor not found!";
Michael Ott's avatar
Michael Ott committed
214
215
							for(const auto& g : p.configurator->getSensorGroups()) {
								for(const auto& s : g->getSensors()) {
216
									if (s->getName() == sensor) {
Michael Ott's avatar
Michael Ott committed
217
										response = pathStrs[0] + "::" + sensor + _httpsServer.calcAvg(*s, time);
218
219
220
										connection->set_status(server::connection::ok);
										break;
									}
221
222
223
								}
							}
						}
224
					}
225
226
227
228
				}
			}
		}
	} else if (method == "PUT") {
229

230
231
		//first check permission
		if (!_httpsServer.check_authkey(auth_value, permission::PUTReq)) {
232
			LOGH(warning) << "Provided authentication token has insufficient permissions";
233
234
235
			connection->set_status(server::connection::unauthorized);
			goto error;
		}
236

237
		if (pathStrs.size() < 2) {
238
			LOGH(warning) << "Received malformed request: No second path part";
239
240
241
			connection->set_status(server::connection::bad_request);
			goto error;
		}
242

243
		std::string action = pathStrs[1];
244

245
246
247
		//process actual request
		response = "Plugin not found!";
		connection->set_status(server::connection::not_found);
248

249
250
251
252
		//switch code depending on selected action
		if (action == "start") {
			for(auto& p : _httpsServer._plugins) {
				if (p.id == pathStrs[0]) {
253
					for(const auto& g : p.configurator->getSensorGroups()) {
254
						g->start();
255
					}
256
257
					response = "Plugin " + pathStrs[0] + ": Sensors started";
					connection->set_status(server::connection::ok);
258
259
260
261
262
263
					break;
				}
			}
		} else if (action == "stop") {
			for(auto& p : _httpsServer._plugins) {
				if (p.id == pathStrs[0]) {
264
					for(const auto& g : p.configurator->getSensorGroups()) {
265
						g->stop();
266
					}
267
268
					response = "Plugin " + pathStrs[0] + ": Sensors stopped";
					connection->set_status(server::connection::ok);
269
					break;
270
271
				}
			}
272
273
274
275
276
277
278
279
280
281
		} else if (action == "reload") {
			for(auto& p : _httpsServer._plugins) {
				if (p.id == pathStrs[0]) {
					//before modifying the plugin we need to ensure that we have exclusive access
					//therefore pause the only other concurrent user (MQTTPusher)
					_httpsServer._mqttPusher->halt();
					//wait until MQTTPusher is paused
					while (!_httpsServer._mqttPusher->isHalted()) {
						sleep(1);
					}
282

283
284
285
286
287
288
289
290
					if (p.configurator->reReadConfig()) {
						response = "Plugin " + pathStrs[0] + ": Configuration reloaded";
						connection->set_status(server::connection::ok);
					} else {
						response = "Plugin " + pathStrs[0] + ": Could not reload configuration";
						connection->set_status(server::connection::internal_server_error);
					}

291
					for(const auto& g : p.configurator->getSensorGroups()) {
292
						g->init(_httpsServer._io);
293
						g->start();
294
					}
295

296
297
298
299
300
301
302
303
304
					//continue MQTTPusher
					_httpsServer._mqttPusher->cont();
					break;
				}
			}
		} else {
			LOGH(warning) << "Unknown action " << pathStrs[1] << " requested";
			connection->set_status(server::connection::not_supported);
			goto error;
305
		}
306
307
308
309
310
311
312
313
314
315
316
317
318

		//Updating the SensorNavigator on plugin changes
		QueryEngine& qEngine = QueryEngine::getInstance();
		std::shared_ptr<SensorNavigator> navigator = std::make_shared<SensorNavigator>();
		vector<std::string> names, topics;
		for(const auto& p : _httpsServer._plugins)
			for(const auto& g : p.configurator->getSensorGroups())
				for(const auto& s : g->getSensors()) {
					names.push_back(s->getName());
					topics.push_back(s->getMqtt());
				}
		navigator->buildTree(qEngine.getSensorHierarchy(), &names, &topics);
		qEngine.setNavigator(navigator);
Alessio Netti's avatar
Alessio Netti committed
319
		qEngine.triggerUpdate();
320

321
322
	}

323
	LOGH(info) << "Responding: " << response;
324
325
326
327
328
	data << response << std::endl;

	//jump right here if an error was encountered.
	//An empty response will be send while the connections status should be set to an appropriate error state
	error:
329
	server::response_header headers[] = { {"Connection", "close"}, {"Content-Type", "text/plain"} };
330
331
332
333
334
	connection->set_headers(boost::make_iterator_range(headers, headers + 2));
	connection->write(data.str());
}

void HttpsServer::requestHandler::log(const server::string_type& message) {
335
	LOGH(error) << message;
336
}
337

338
339
HttpsServer::HttpsServer(restAPISettings_t restAPISettings, pluginVector_t& plugins, MQTTPusher* mqttPusher, boost::asio::io_service& io) :
		_plugins(plugins), _mqttPusher(mqttPusher), _io(io), _handler(*this) {
340

341
342
343
	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);

344
	// Set certificate, private key and DH parameters
345
	//ctx->set_password_callback(HttpsServer::password_callback);
346
347
348
	ctx->use_certificate_chain_file(restAPISettings.certificate);
	ctx->use_private_key_file(restAPISettings.privateKey, asio::ssl::context::pem);
	ctx->use_tmp_dh_file(restAPISettings.dhFile);
349

350
	server::options options(_handler);
351
	_server = new server(options.address(restAPISettings.restHost).port(restAPISettings.restPort).context(ctx));
352
353
354
355
356
357
}

HttpsServer::~HttpsServer() {
	delete _server;
}

Micha Mueller's avatar
Micha Mueller committed
358
std::string HttpsServer::calcAvg(SensorBase& s, uint64_t time) {
359
	int64_t avg = 0;
360
	const reading_t * const cache = s.getCache();
361
362

	unsigned count = 0;
Micha Mueller's avatar
Micha Mueller committed
363
	for(unsigned i = 0; i < s.getCacheSize(); i++) {
364
365
366
367
368
369
370
371
372
373
374
		if (cache[i].timestamp > time) {
			avg += cache[i].value;
			count++;
		}
	}
	if (count > 0)
		avg /= count;

	return " Average of last " + std::to_string(count) + " values is " + std::to_string(avg);
}

375
376
377
378
379
380
381
382
bool HttpsServer::check_authkey(const std::string& authkey, permission requiredPerm) {
	authkeyMap_t::iterator it = _authkeys.find(authkey);
	if (it != _authkeys.end()) {
		return it->second[requiredPerm];
	}
	return false;
}

383
384
385
386
387
/*
std::string HttpsServer::password_callback(std::size_t max_length, asio::ssl::context_base::password_purpose purpose) {
    return std::string("pwd");
}
*/