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

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

			if (pathStrs[1] == "sensors") {
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
				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;
					}
				}
158
			} else {
Micha Mueller's avatar
Micha Mueller committed
159

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

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

				//process actual request
184
185
186
187
188
189
190
191
192
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) {
								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
201
202
									}
								}
203
204
205
206
207
208
								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;
209
210
211
							}
						}
					}
212
213
214
215
				}
			}
		}
	} else if (method == "PUT") {
Micha Mueller's avatar
Micha Mueller committed
216

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

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

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

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

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

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

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

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

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

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

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

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