HttpsServer.cpp 11.5 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

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

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

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

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

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

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

	std::ostringstream data;
34
35

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

	std::string response = "";
	std::string auth_value = "";
	std::vector<std::string> pathStrs;
	std::vector<std::pair<std::string, std::string>> queries;
44

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

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

54
55
56
57
58
59
60
61
	//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
62
		}
63

64
65
66
67
		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
68
69
	{	//do not remove the enclosing brackets
		//need to encapsulate this code block to keep queryStrs local
Micha Mueller's avatar
Micha Mueller committed
70
71
72
73
		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("=");
74
			if (pos != std::string::npos) {
Micha Mueller's avatar
Micha Mueller committed
75
76
77
78
				std::string value;
				value = key.substr(pos+1);
				key.erase(pos);
				queries.push_back(std::make_pair(key, value));
79
80
			}
		}
81
82
	}
	//finished string processing
83

84
85
86
87
88
	//authkey is required in every case
	for (auto& p : queries) {
		if (p.first == "authkey") {
			auth_value = p.second;
			break;
89
		}
90
	}
91

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

	//select code depending on request
	if (method == "GET") {

		//first check permission
		if (!_httpsServer.check_authkey(auth_value, permission::GETReq)) {
103
			LOGH(warning) << "Provided authentication token has insufficient permissions";
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
			connection->set_status(server::connection::unauthorized);
			goto error;
		}

		if (pathStrs[0] == "help") {
			response = "dcdbpusher RESTful API cheatsheet:\n"
					" -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"
119
120
121
					" -PUT:  /[plugin]/[start|stop|reload]\n"
					"                  Start/stop the sensors of the plugin or\n"
					"                  reload the plugin configuration\n"
122
123
124
125
126
					"\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";
		} else if (pathStrs[0] == "plugins") {
127
128
129
130
131
			boost::property_tree::ptree root, plugins;
			for(auto& p : _httpsServer._plugins) {
				plugins.put(p.id, "");
			}
			root.add_child("plugins", plugins);
132
			boost::property_tree::write_info(data, root);
Micha Mueller's avatar
Micha Mueller committed
133
134
		} else {

135
136
			//do some prior checks
			if (pathStrs.size() < 2) {
137
				LOGH(warning) << "Received malformed request: No second path part";
138
139
140
141
142
				connection->set_status(server::connection::bad_request);
				goto error;
			}

			if (pathStrs[1] == "sensors") {
143
144
145
146
147
148
149
150
151
152
				response = "Plugin not found!";
				connection->set_status(server::connection::not_found);

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

						for(auto s : p.configurator->getSensors()) {
							sensors.put(s->getName(), "");
						}
153
154
155
156
157
158
159
						for(auto g : p.configurator->getSensorGroups()) {
							boost::property_tree::ptree group;
							for(auto s : g->getSensors()) {
								group.put(s->getName(), "");
							}
							sensors.add_child(g->getName(), group);
						}
160
161
162
163
164
165
166
						root.add_child(p.id, sensors);
						boost::property_tree::write_info(data, root);
						response = "";
						connection->set_status(server::connection::ok);
						break;
					}
				}
167
			} else {
Micha Mueller's avatar
Micha Mueller committed
168

169
				if (pathStrs.size() < 3) {
170
					LOGH(warning) << "Received malformed request: No third path part";
Micha Mueller's avatar
Micha Mueller committed
171
					connection->set_status(server::connection::bad_request);
172
173
					goto error;
				}
174

175
				if (pathStrs[2] != "avg") {
176
					LOGH(warning) << "Unknown action " << pathStrs[2] << " requested";
177
178
179
180
181
182
183
184
185
186
187
188
					connection->set_status(server::connection::not_supported);
					goto error;
				}

				std::string sensor = pathStrs[1];
				std::string action = pathStrs[2];
				uint64_t time = 0;

				for (auto& p : queries) {
					if (p.first == "interval") {
						time = getTimestamp();
						time -= S_TO_NS(std::stoul(p.second));
Micha Mueller's avatar
Micha Mueller committed
189
190
191
192
					}
				}

				//process actual request
193
194
195
196
197
198
199
200
				response = "Plugin not found!";
				connection->set_status(server::connection::not_found);

				for(auto& p : _httpsServer._plugins) {
					if (p.id == pathStrs[0]) {
						response = "Sensor not found!";
						for(auto s : p.configurator->getSensors()) {
							if (s->getName() == sensor) {
201
								response = pathStrs[0] + "::" + sensor + _httpsServer.calcAvg(s, time);
202
203
								connection->set_status(server::connection::ok);
								break;
204
205
							}
						}
206
207
208
209
210
211
212
213
214
						for(auto g : p.configurator->getSensorGroups()) {
							for(auto s : g->getSensors()) {
								if (s->getName() == sensor) {
									response = pathStrs[0] + "::" + sensor + _httpsServer.calcAvg(s, time);
									connection->set_status(server::connection::ok);
									break;
								}
							}
						}
215
					}
216
217
218
219
				}
			}
		}
	} else if (method == "PUT") {
Micha Mueller's avatar
Micha Mueller committed
220

221
222
		//first check permission
		if (!_httpsServer.check_authkey(auth_value, permission::PUTReq)) {
223
			LOGH(warning) << "Provided authentication token has insufficient permissions";
224
225
226
227
228
			connection->set_status(server::connection::unauthorized);
			goto error;
		}

		if (pathStrs.size() < 2) {
229
			LOGH(warning) << "Received malformed request: No second path part";
230
231
232
233
234
235
236
237
238
239
			connection->set_status(server::connection::bad_request);
			goto error;
		}

		std::string action = pathStrs[1];

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

240
241
242
243
		//switch code depending on selected action
		if (action == "start") {
			for(auto& p : _httpsServer._plugins) {
				if (p.id == pathStrs[0]) {
244
245
246
					for(auto s : p.configurator->getSensors()) {
						s->startPolling();
					}
247
248
249
					for(auto g : p.configurator->getSensorGroups()) {
						g->startPolling();
					}
250
251
					response = "Plugin " + pathStrs[0] + ": Sensors started";
					connection->set_status(server::connection::ok);
252
253
254
255
256
257
					break;
				}
			}
		} else if (action == "stop") {
			for(auto& p : _httpsServer._plugins) {
				if (p.id == pathStrs[0]) {
258
259
260
					for(auto s : p.configurator->getSensors()) {
						s->stopPolling();
					}
261
262
263
					for(auto g : p.configurator->getSensorGroups()) {
						g->stopPolling();
					}
264
265
					response = "Plugin " + pathStrs[0] + ": Sensors stopped";
					connection->set_status(server::connection::ok);
266
					break;
267
268
				}
			}
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
		} 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);
					}

					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);
					}

					for(auto s : p.configurator->getSensors()) {
						s->init(_httpsServer._io);
						s->startPolling();
					}

					//continue MQTTPusher
					_httpsServer._mqttPusher->cont();
					break;
				}
			}
		} else {
			LOGH(warning) << "Unknown action " << pathStrs[1] << " requested";
			connection->set_status(server::connection::not_supported);
			goto error;
302
303
304
		}
	}

305
	LOGH(info) << "Responding: " << response;
306
307
308
309
310
	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:
311
	server::response_header headers[] = { {"Connection", "close"}, {"Content-Type", "text/plain"} };
312
313
314
315
316
	connection->set_headers(boost::make_iterator_range(headers, headers + 2));
	connection->write(data.str());
}

void HttpsServer::requestHandler::log(const server::string_type& message) {
317
	LOGH(error) << message;
318
}
319

320
321
HttpsServer::HttpsServer(restAPISettings_t restAPISettings, pluginVector_t& plugins, MQTTPusher* mqttPusher, boost::asio::io_service& io) :
		_plugins(plugins), _mqttPusher(mqttPusher), _io(io), _handler(*this) {
322

323
324
325
	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);

326
	// Set certificate, private key and DH parameters
327
	//ctx->set_password_callback(HttpsServer::password_callback);
328
329
330
	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);
331

332
	server::options options(_handler);
333
	_server = new server(options.address(restAPISettings.restHost).port(restAPISettings.restPort).context(ctx));
334
335
336
337
338
339
}

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

340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
std::string HttpsServer::calcAvg(Sensor* const s, uint64_t time) {
	uint64_t avg = 0;
	const reading_t * const cache = s->getCache();
	unsigned size = s->getCacheSize();

	unsigned count = 0;
	for(unsigned i = 0; i < size; i++) {
		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);
}

358
359
360
361
362
363
364
365
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;
}

366
367
368
369
370
/*
std::string HttpsServer::password_callback(std::size_t max_length, asio::ssl::context_base::password_purpose purpose) {
    return std::string("pwd");
}
*/