summaryrefslogtreecommitdiffstatshomepage
path: root/src/emu/http.cpp
blob: 48599258aaa1046035a999f2223f119234d34a93 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
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
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
// license:BSD-3-Clause
// copyright-holders:Miodrag Milanovic
/***************************************************************************

http.cpp

HTTP server handling

***************************************************************************/
#include "emu.h"

#ifdef __sun
#define ASIO_DISABLE_DEV_POLL
#define ASIO_HAS_EPOLL
#endif

#include "server_ws_impl.hpp"
#include "server_http_impl.hpp"
#include <fstream>

#include <inttypes.h>
#include <stdint.h>


const static struct mapping
{
	const char* extension;
	const char* mime_type;
} mappings[] =
{
	{ "aac",     "audio/aac" },
	{ "aat",     "application/font-sfnt" },
	{ "aif",     "audio/x-aif" },
	{ "arj",     "application/x-arj-compressed" },
	{ "asf",     "video/x-ms-asf" },
	{ "avi",     "video/x-msvideo" },
	{ "bmp",     "image/bmp" },
	{ "cff",     "application/font-sfnt" },
	{ "css",     "text/css" },
	{ "csv",     "text/csv" },
	{ "doc",     "application/msword" },
	{ "eps",     "application/postscript" },
	{ "exe",     "application/octet-stream" },
	{ "gif",     "image/gif" },
	{ "gz",      "application/x-gunzip" },
	{ "htm",     "text/html" },
	{ "html",    "text/html" },
	{ "ico",     "image/x-icon" },
	{ "ief",     "image/ief" },
	{ "jpeg",    "image/jpeg" },
	{ "jpg",     "image/jpeg" },
	{ "jpm",     "image/jpm" },
	{ "jpx",     "image/jpx" },
	{ "js",      "application/javascript" },
	{ "json",    "application/json" },
	{ "lay",     "text/xml" },
	{ "m3u",     "audio/x-mpegurl" },
	{ "m4v",     "video/x-m4v" },
	{ "mid",     "audio/x-midi" },
	{ "mov",     "video/quicktime" },
	{ "mp3",     "audio/mpeg" },
	{ "mp4",     "video/mp4" },
	{ "mpeg",    "video/mpeg" },
	{ "mpg",     "video/mpeg" },
	{ "oga",     "audio/ogg" },
	{ "ogg",     "audio/ogg" },
	{ "ogv",     "video/ogg" },
	{ "otf",     "application/font-sfnt" },
	{ "pct",     "image/x-pct" },
	{ "pdf",     "application/pdf" },
	{ "pfr",     "application/font-tdpfr" },
	{ "pict",    "image/pict" },
	{ "png",     "image/png" },
	{ "ppt",     "application/x-mspowerpoint" },
	{ "ps",      "application/postscript" },
	{ "qt",      "video/quicktime" },
	{ "ra",      "audio/x-pn-realaudio" },
	{ "ram",     "audio/x-pn-realaudio" },
	{ "rar",     "application/x-arj-compressed" },
	{ "rgb",     "image/x-rgb" },
	{ "rtf",     "application/rtf" },
	{ "sgm",     "text/sgml" },
	{ "shtm",    "text/html" },
	{ "shtml",   "text/html" },
	{ "sil",     "application/font-sfnt" },
	{ "svg",     "image/svg+xml" },
	{ "swf",     "application/x-shockwave-flash" },
	{ "tar",     "application/x-tar" },
	{ "tgz",     "application/x-tar-gz" },
	{ "tif",     "image/tiff" },
	{ "tiff",    "image/tiff" },
	{ "torrent", "application/x-bittorrent" },
	{ "ttf",     "application/font-sfnt" },
	{ "txt",     "text/plain" },
	{ "wav",     "audio/x-wav" },
	{ "webm",    "video/webm" },
	{ "woff",    "application/font-woff" },
	{ "wrl",     "model/vrml" },
	{ "xhtml",   "application/xhtml+xml" },
	{ "xls",     "application/x-msexcel" },
	{ "xml",     "text/xml" },
	{ "xsl",     "application/xml" },
	{ "xslt",    "application/xml" },
	{ "zip",     "application/x-zip-compressed" }
};

static std::string extension_to_type(const std::string& extension)
{
	for (mapping m : mappings)
	{
		if (m.extension == extension)
		{
			return m.mime_type;
		}
	}

	return "text/plain";
}

/** An HTTP Request. */
struct http_request_impl : public http_manager::http_request
{
public:
	std::shared_ptr<webpp::Request> m_request;
	std::size_t m_query;
	std::size_t m_fragment;
	std::size_t m_path_end;
	std::size_t m_query_end;

	http_request_impl(std::shared_ptr<webpp::Request> request) : m_request(std::move(request)) {
		std::size_t len = m_request->path.length();

		m_fragment = m_request->path.find('#');
		m_query_end = m_fragment == std::string::npos ? len : m_fragment;

		m_query = m_request->path.find('?');
		m_path_end = m_query == std::string::npos ? m_query_end : m_query;
	}

	virtual ~http_request_impl() = default;

	/** Retrieves the requested resource. */
	virtual const std::string get_resource() {
		// The entire resource: path, query and fragment.
		return m_request->path;
	}

	/** Returns the path part of the requested resource. */
	virtual const std::string get_path() {
		return m_request->path.substr(0, m_path_end);
	}

	/** Returns the query part of the requested resource. */
	virtual const std::string get_query() {
		return m_query == std::string::npos ? "" : m_request->path.substr(m_query, m_query_end);
	}

	/** Returns the fragment part of the requested resource. */
	virtual const std::string get_fragment() {
		return m_fragment == std::string::npos ? "" : m_request->path.substr(m_fragment);
	}

	/** Retrieves a header from the HTTP request. */
	virtual const std::string get_header(const std::string &header_name) {
		auto i = m_request->header.find(header_name);
		if (i != m_request->header.end()) {
			return (*i).second;
		} else {
			return "";
		}
	}

	/** Retrieves a header from the HTTP request. */
	virtual const std::list<std::string> get_headers(const std::string &header_name) {
		std::list<std::string> result;
		auto range = m_request->header.equal_range(header_name);
		for (auto i = range.first; i != range.second; i++) {
			result.push_back((*i).second);
		}
		return result;
	}

	/** Returns the body that was submitted with the HTTP request. */
	virtual const std::string get_body() {
		// TODO(cbrunschen): What to return here - http_server::Request has a 'content' feld that is never filled in!
		return "";
	}
};

/** An HTTP response. */
struct http_response_impl : public http_manager::http_response {
	std::shared_ptr<webpp::Response> m_response;
	int m_status;
	std::string m_content_type;
	std::stringstream m_headers;
	std::stringstream m_body;

	http_response_impl(std::shared_ptr<webpp::Response> response) : m_response(std::move(response)) { }

	virtual ~http_response_impl() = default;

	/** Sets the HTTP status to be returned to the client. */
	virtual void set_status(int status) {
		m_status = status;
	}

	/** Sets the HTTP content type to be returned to the client. */
	virtual void set_content_type(const std::string &content_type) {
		m_content_type = content_type;
	}

	/** Sets the body to be sent to the client. */
	virtual void set_body(const std::string &body) {
		m_body.str("");
		append_body(body);
	}

	/** Appends something to the body to be sent to the client. */
	virtual void append_body(const std::string &body) {
		m_body << body;
	}

	/** Sends the response to the client. */
	void send() {
		m_response->type(m_content_type);
		m_response->status(m_status);
		m_response->send(m_body.str());
	}
};

struct websocket_endpoint_impl : public http_manager::websocket_endpoint {
	/** The underlying edpoint. */
	webpp::ws_server::Endpoint *m_endpoint;

	websocket_endpoint_impl(webpp::ws_server::Endpoint *endpoint,
		http_manager::websocket_open_handler on_open,
		http_manager::websocket_message_handler on_message,
		http_manager::websocket_close_handler on_close,
		http_manager::websocket_error_handler on_error)
	: m_endpoint(endpoint) {
		this->on_open = std::move(on_open);
		this->on_message = std::move(on_message);
		this->on_close = std::move(on_close);
		this->on_error = std::move(on_error);
	}
};

struct websocket_connection_impl : public http_manager::websocket_connection {
	/** The server */
	webpp::ws_server *m_wsserver;
	/* The underlying Commection. */
	std::weak_ptr<webpp::Connection> m_connection;
	websocket_connection_impl(webpp::ws_server *server, std::shared_ptr<webpp::Connection> connection)
		: m_wsserver(server), m_connection(connection) { }

	/** Sends a message to the client that is connected on the other end of this Websocket connection. */
	virtual void send_message(const std::string &payload, int opcode) {
		if (auto connection = m_connection.lock()) {
			std::shared_ptr<webpp::ws_server::SendStream> message_stream = std::make_shared<webpp::ws_server::SendStream>();
			(*message_stream) << payload;
			m_wsserver->send(connection, message_stream, nullptr, opcode | 0x80);
		}
	}

	/** Closes this open Websocket connection. */
	virtual void close() {
		if (auto connection = m_connection.lock()) {
			m_wsserver->send_close(connection, 1000 /* normal close */);
		}
	}
};

http_manager::http_manager(bool active, short port, const char *root)
  : m_active(active), m_io_context(std::make_shared<asio::io_context>()), m_root(root)
{
	if (!active) return;

	m_server = std::make_unique<webpp::http_server>();
	m_server->m_config.port = port;
	m_server->set_io_context(m_io_context);
	m_wsserver = std::make_unique<webpp::ws_server>();

	auto& endpoint = m_wsserver->m_endpoint["/"];

	m_server->on_get([root](auto response, auto request) {
		std::string doc_root = root;

		auto request_impl = std::make_shared<http_request_impl>(request);

		std::string path = request_impl->get_path();
		// If path ends in slash (i.e. is a directory) then add "index.html".
		if (path[path.size() - 1] == '/')
		{
			path += "index.html";
		}

		// Determine the file extension.
		std::size_t last_slash_pos = path.find_last_of('/');
		std::size_t last_dot_pos = path.find_last_of('.');
		std::string extension;
		if (last_dot_pos != std::string::npos && last_dot_pos > last_slash_pos)
		{
			extension = path.substr(last_dot_pos + 1);
		}

		// Open the file to send back.
		std::string full_path = doc_root + path;
		std::ifstream is(full_path.c_str(), std::ios::in | std::ios::binary);
		if (!is)
		{
			response->status(400).send("Error");
		}
		else
		{
			// Fill out the reply to be sent to the client.
			std::string content;
			char buf[512];
			while (is.read(buf, sizeof(buf)).gcount() > 0)
				content.append(buf, size_t(is.gcount()));

			response->type(extension_to_type(extension));
			response->status(200).send(content);
		}
	});

	endpoint.on_open = [&](auto connection) {
		auto send_stream = std::make_shared<webpp::ws_server::SendStream>();
		*send_stream << "update_machine";
		m_wsserver->send(connection, send_stream);
	};

	m_server->on_upgrade = [this](auto socket, auto request) {
		auto connection = std::make_shared<webpp::ws_server::Connection>(socket);
		connection->method = std::move(request->method);
		connection->path = std::move(request->path);
		connection->http_version = std::move(request->http_version);
		connection->header = std::move(request->header);
		connection->remote_endpoint_address = std::move(request->remote_endpoint_address);
		connection->remote_endpoint_port = request->remote_endpoint_port;
		m_wsserver->upgrade(connection);
	};

	m_server->start();

	m_server_thread = std::thread([this]() {
		m_io_context->run();
	});
}

http_manager::~http_manager()
{
	if (!m_server) return;

	m_server->stop();
	m_io_context->stop();
	if (m_server_thread.joinable())
		m_server_thread.join();
}

static void on_get(http_manager::http_handler handler, std::shared_ptr<webpp::Response> response, std::shared_ptr<webpp::Request> request) {
	auto request_impl = std::make_shared<http_request_impl>(request);
	auto response_impl = std::make_shared<http_response_impl>(response);

	handler(request_impl, response_impl);

	response_impl->send();
}

void http_manager::on_open(http_manager::websocket_endpoint_ptr endpoint, std::shared_ptr<webpp::Connection> connection) {
	std::lock_guard<std::mutex> lock(m_connections_mutex);
	webpp::ws_server *ws_server = m_wsserver.get();

	http_manager::websocket_connection_ptr connection_impl = std::make_shared<websocket_connection_impl>(ws_server, connection);
	m_connections[connection.get()] = connection_impl;

	if (endpoint->on_open) {
		endpoint->on_open(connection_impl);
	}
}

void http_manager::on_message(http_manager::websocket_endpoint_ptr endpoint, std::shared_ptr<webpp::Connection> connection, const std::string &payload, int opcode) {
	if (endpoint->on_message) {
		std::lock_guard<std::mutex> lock(m_connections_mutex);
		auto i = m_connections.find(connection.get());
		if (i != m_connections.end()) {
			http_manager::websocket_connection_ptr websocket_connection_impl = (*i).second;
			endpoint->on_message(websocket_connection_impl, payload, opcode);
		}
	}
}

void http_manager::on_close(http_manager::websocket_endpoint_ptr endpoint, std::shared_ptr<webpp::Connection> connection,
			  int status, const std::string& reason) {
	std::lock_guard<std::mutex> lock(m_connections_mutex);
	auto i = m_connections.find(connection.get());
	if (i != m_connections.end()) {
		if (endpoint->on_close) {
			http_manager::websocket_connection_ptr websocket_connection_impl = (*i).second;
			endpoint->on_close(websocket_connection_impl, status, reason);
		}

		m_connections.erase(connection.get());
	}
}

void http_manager::on_error(http_manager::websocket_endpoint_ptr endpoint, std::shared_ptr<webpp::Connection> connection,
			  const std::error_code& error_code) {
	std::lock_guard<std::mutex> lock(m_connections_mutex);
	auto i = m_connections.find(connection.get());
	if (i != m_connections.end()) {
		if (endpoint->on_error) {
			http_manager::websocket_connection_ptr websocket_connection_impl = (*i).second;
			endpoint->on_error(websocket_connection_impl, error_code);
		}

		m_connections.erase(connection.get());
	}
}

bool http_manager::read_file(std::ostream &os, const std::string &path) {
	std::ostringstream full_path;
	full_path << m_root << path;
	util::core_file::ptr f;
	osd_file::error e = util::core_file::open(full_path.str(), OPEN_FLAG_READ, f);
	if (e == osd_file::error::NONE)
	{
		int c;
		while ((c = f->getc()) >= 0)
		{
			os.put(c);
		}
	}
	return e == osd_file::error::NONE;
}

void http_manager::serve_document(http_request_ptr request, http_response_ptr response, const std::string &filename) {
	if (!m_active) return;

	std::ostringstream os;
	if (read_file(os, filename))
	{
		response->set_status(200);
		response->set_body(os.str());
	}
	else
	{
		response->set_status(500);
	}
}

void http_manager::serve_template(http_request_ptr request, http_response_ptr response,
								  const std::string &filename, substitution substitute, char init, char term)
{
	if (!m_active) return;

	// printf("webserver: serving template '%s' at path '%s'\n", filename.c_str(), request->get_path().c_str());
	std::stringstream ss;
	if (read_file(ss, filename))
	{
		std::ostringstream os;
		while (ss.good())
		{
			std::string s;
			getline(ss, s, init);
			os << s;
			if (ss.good())
			{
				// printf("webserver: found initiator '%c'\n", init);
				getline(ss, s, term);
				if (ss.good())
				{
					if (substitute(s))
					{
						os << s;
					}
					else
					{
						os << init << s << term;
					}
				}
				else
				{
					// printf("webserver: reached end before terminator\n");
					os << init;
					os << s;
				}
			}
			else
			{
				// printf("webserver: reached end before initiator\n");
			}
		}

		response->set_status(200);
		response->set_body(os.str());
	}
	else
	{
		response->set_status(500);
	}
}

void http_manager::add_http_handler(const std::string &path, http_manager::http_handler handler)
{
	if (!m_active) return;

	using namespace std::placeholders;
	m_server->on_get(path, std::bind(on_get, handler, _1, _2));

	std::lock_guard<std::mutex> lock(m_handlers_mutex);
	m_handlers.emplace(path, handler);
}

void http_manager::remove_http_handler(const std::string &path) {
	if (!m_active) return;

	m_server->remove_handler(path);

	std::lock_guard<std::mutex> lock(m_handlers_mutex);
	m_handlers.erase(path);
}

void http_manager::clear() {
	if (!m_active) return;

	m_server->clear();

	std::lock_guard<std::mutex> lock(m_handlers_mutex);
	m_handlers.clear();
}

http_manager::websocket_endpoint_ptr http_manager::add_endpoint(const std::string &path,
		http_manager::websocket_open_handler on_open,
		http_manager::websocket_message_handler on_message,
		http_manager::websocket_close_handler on_close,
		http_manager::websocket_error_handler on_error)  {
	if (!m_active) return std::shared_ptr<websocket_endpoint_impl>(nullptr);

	auto i = m_endpoints.find(path);
	if (i == m_endpoints.end()) {
		using namespace std::placeholders;

		auto &endpoint = m_wsserver->m_endpoint[path];
		webpp::ws_server::Endpoint *endpoint_ptr(&endpoint);
		auto endpoint_impl = std::make_shared<websocket_endpoint_impl>(endpoint_ptr, on_open, on_message, on_close, on_error);

		endpoint.on_open = [&, this, endpoint_impl](std::shared_ptr<webpp::Connection> connection) {
			this->on_open(endpoint_impl, std::move(connection));
		};

		endpoint.on_message = [&, this, endpoint_impl](std::shared_ptr<webpp::Connection> connection, std::shared_ptr<webpp::ws_server::Message> message) {
			std::string payload = message->string();
			int opcode = message->fin_rsv_opcode & 0x0f;
			this->on_message(endpoint_impl, std::move(connection), payload, opcode);
		};

		endpoint.on_close = [&, this, endpoint_impl](std::shared_ptr<webpp::Connection> connection, int status, const std::string& reason) {
			this->on_close(endpoint_impl, std::move(connection), status, reason);
		};

		endpoint.on_error = [&, this, endpoint_impl](std::shared_ptr<webpp::Connection> connection, const std::error_code& error_code) {
			this->on_error(endpoint_impl, std::move(connection), error_code);
		};

		m_endpoints[path] = endpoint_impl;
		return endpoint_impl;
	} else {
		return (*i).second;
	}
}

void http_manager::remove_endpoint(const std::string &path) {
	if (!m_active) return;

	m_endpoints.erase(path);
}