HttpsServer.cpp 10.6 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
68
		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
69
70
71
72
		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("=");
73
			if (pos != std::string::npos) {
Micha Mueller's avatar
Micha Mueller committed
74
75
76
77
				std::string value;
				value = key.substr(pos+1);
				key.erase(pos);
				queries.push_back(std::make_pair(key, value));
78
79
			}
		}
80
81
	}
	//finished string processing
82

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

91
	if (pathStrs.size() < 1) {
92
		LOGH(warning) << "Received malformed request: No first path part";
93
94
95
96
97
98
99
100
101
		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)) {
102
			LOGH(warning) << "Provided authentication token has insufficient permissions";
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
			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"
118
119
120
					" -PUT:  /[plugin]/[start|stop|reload]\n"
					"                  Start/stop the sensors of the plugin or\n"
					"                  reload the plugin configuration\n"
121
122
123
124
125
					"\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") {
126
127
128
129
130
			boost::property_tree::ptree root, plugins;
			for(auto& p : _httpsServer._plugins) {
				plugins.put(p.id, "");
			}
			root.add_child("plugins", plugins);
131
			boost::property_tree::write_info(data, root);
Micha Mueller's avatar
Micha Mueller committed
132
133
		} else {

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

			if (pathStrs[1] == "sensors") {
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
				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(), "");
						}
						root.add_child(p.id, sensors);
						boost::property_tree::write_info(data, root);
						response = "";
						connection->set_status(server::connection::ok);
						break;
					}
				}
159
			} else {
Micha Mueller's avatar
Micha Mueller committed
160

161
				if (pathStrs.size() < 3) {
162
					LOGH(warning) << "Received malformed request: No third path part";
Micha Mueller's avatar
Micha Mueller committed
163
					connection->set_status(server::connection::bad_request);
164
165
					goto error;
				}
166

167
				if (pathStrs[2] != "avg") {
168
					LOGH(warning) << "Unknown action " << pathStrs[2] << " requested";
169
170
171
172
173
174
175
176
177
178
179
180
					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
181
182
183
184
					}
				}

				//process actual request
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
				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) {
								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++;
Micha Mueller's avatar
Micha Mueller committed
202
203
									}
								}
204
205
206
207
208
209
								if (count > 0)
									avg /= count;

								response = pathStrs[0] + "::" + sensor + " Average of last " + std::to_string(count) + " values is " + std::to_string(avg);
								connection->set_status(server::connection::ok);
								break;
210
211
212
							}
						}
					}
213
214
215
216
				}
			}
		}
	} else if (method == "PUT") {
Micha Mueller's avatar
Micha Mueller committed
217

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

		if (pathStrs.size() < 2) {
226
			LOGH(warning) << "Received malformed request: No second path part";
227
228
229
230
231
232
233
234
235
236
			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);

237
238
239
240
		//switch code depending on selected action
		if (action == "start") {
			for(auto& p : _httpsServer._plugins) {
				if (p.id == pathStrs[0]) {
241
242
243
244
245
					for(auto s : p.configurator->getSensors()) {
						s->startPolling();
					}
					response = "Plugin " + pathStrs[0] + ": Sensors started";
					connection->set_status(server::connection::ok);
246
247
248
249
250
251
					break;
				}
			}
		} else if (action == "stop") {
			for(auto& p : _httpsServer._plugins) {
				if (p.id == pathStrs[0]) {
252
253
254
255
256
					for(auto s : p.configurator->getSensors()) {
						s->stopPolling();
					}
					response = "Plugin " + pathStrs[0] + ": Sensors stopped";
					connection->set_status(server::connection::ok);
257
					break;
258
259
				}
			}
260
261
262
263
264
265
266
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
		} 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;
293
294
295
		}
	}

296
	LOGH(info) << "Responding: " << response;
297
298
299
300
301
	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:
302
	server::response_header headers[] = { {"Connection", "close"}, {"Content-Type", "text/plain"} };
303
304
305
306
307
	connection->set_headers(boost::make_iterator_range(headers, headers + 2));
	connection->write(data.str());
}

void HttpsServer::requestHandler::log(const server::string_type& message) {
308
	LOGH(error) << message;
309
}
310

311
312
HttpsServer::HttpsServer(restAPISettings_t restAPISettings, pluginVector_t& plugins, MQTTPusher* mqttPusher, boost::asio::io_service& io) :
		_plugins(plugins), _mqttPusher(mqttPusher), _io(io), _handler(*this) {
313

314
315
316
	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);

317
	// Set certificate, private key and DH parameters
318
	//ctx->set_password_callback(HttpsServer::password_callback);
319
320
321
	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);
322

323
	server::options options(_handler);
324
	_server = new server(options.address(restAPISettings.restHost).port(restAPISettings.restPort).context(ctx));
325
326
327
328
329
330
}

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

331
332
333
334
335
336
337
338
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;
}

339
340
341
342
343
/*
std::string HttpsServer::password_callback(std::size_t max_length, asio::ssl::context_base::password_purpose purpose) {
    return std::string("pwd");
}
*/