diff options
Diffstat (limited to 'src/emu/http.cpp')
-rw-r--r-- | src/emu/http.cpp | 369 |
1 files changed, 352 insertions, 17 deletions
diff --git a/src/emu/http.cpp b/src/emu/http.cpp index 48d45e89fa1..9f911ed1252 100644 --- a/src/emu/http.cpp +++ b/src/emu/http.cpp @@ -13,6 +13,10 @@ HTTP server handling #include "server_http.hpp" #include <fstream> +#include <inttypes.h> +#include <stdint.h> + + const static struct mapping { const char* extension; @@ -107,8 +111,153 @@ static std::string extension_to_type(const std::string& extension) return "text/plain"; } +/** An HTTP Request. */ +struct http_request_impl : public http_manager::http_request +{ +public: + std::shared_ptr<webpp::http_server::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::http_server::Request> request) : m_request(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; + } + + /** 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::http_server::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::http_server::Response> response) : m_response(response) { } + + /** 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. */ + std::shared_ptr<webpp::ws_server::Endpoint> m_endpoint; + + websocket_endpoint_impl(std::shared_ptr<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 = on_open; + this->on_message = on_message; + this->on_close = on_close; + this->on_error = on_error; + } +}; + +struct websocket_connection_impl : public http_manager::websocket_connection { + /** The server */ + webpp::ws_server *m_wsserver; + /* The underlying Commection. */ + std::shared_ptr<webpp::ws_server::Connection> m_connection; + websocket_connection_impl(webpp::ws_server *server, std::shared_ptr<webpp::ws_server::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) { + std::shared_ptr<webpp::ws_server::SendStream> message_stream = std::make_shared<webpp::ws_server::SendStream>(); + (*message_stream) << payload; + m_wsserver->send(m_connection, message_stream, nullptr, opcode | 0x80); + } + + /** Closes this open Websocket connection. */ + virtual void close() { + m_wsserver->send_close(m_connection, 1000 /* normal close */); + } +}; + http_manager::http_manager(bool active, short port, const char *root) - : m_io_context(std::make_shared<asio::io_context>()) + : m_io_context(std::make_shared<asio::io_context>()), m_root(root) { if (!active) return; @@ -117,22 +266,20 @@ http_manager::http_manager(bool active, short port, const char *root) m_server->set_io_context(m_io_context); m_wsserver = std::make_unique<webpp::ws_server>(); - auto& endpoint = m_wsserver->endpoint["/"]; + auto& endpoint = m_wsserver->m_endpoint["/"]; m_server->on_get([this, root](auto response, auto request) { std::string doc_root = root; - std::string path = request->path; + 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"; } - std::size_t last_qmark_pos = path.find_last_of("?"); - if (last_qmark_pos != std::string::npos) - path = path.substr(0, last_qmark_pos - 1); - // Determine the file extension. std::size_t last_slash_pos = path.find_last_of("/"); std::size_t last_dot_pos = path.find_last_of("."); @@ -195,19 +342,207 @@ http_manager::~http_manager() m_server_thread.join(); } +static void on_get(http_manager::http_handler handler, std::shared_ptr<webpp::http_server::Response> response, std::shared_ptr<webpp::http_server::Request> request) { + auto request_impl = std::make_shared<http_request_impl>(request); + auto response_impl = std::make_shared<http_response_impl>(response); -void http_manager::update() -{ - if (!m_server) return; + handler(request_impl, response_impl); - m_server->clear(); - for (auto handler : m_handlers) + response_impl->send(); +} + +void http_manager::on_open(http_manager::websocket_endpoint_ptr endpoint, void *connection) { + std::lock_guard<std::mutex> lock(m_connections_mutex); + webpp::ws_server *ws_server = m_wsserver.get(); + // Keep an oening shared_ptr to the Connection, so it won't go away while we are using it. + std::shared_ptr<webpp::ws_server::Connection> conn = (static_cast<webpp::ws_server::Connection *>(connection))->ptr(); + http_manager::websocket_connection_ptr connection_impl = std::make_shared<websocket_connection_impl>(ws_server, conn); + m_connections[connection] = connection_impl; + + if (endpoint->on_open) { + endpoint->on_open(connection_impl); + } +} + +void http_manager::on_message(http_manager::websocket_endpoint_ptr endpoint, void *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); + 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, void *connection, + int status, const std::string& reason) { + std::lock_guard<std::mutex> lock(m_connections_mutex); + auto i = m_connections.find(connection); + 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); + } +} + +void http_manager::on_error(http_manager::websocket_endpoint_ptr endpoint, void *connection, + const std::error_code& error_code) { + std::lock_guard<std::mutex> lock(m_connections_mutex); + auto i = m_connections.find(connection); + 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); + } +} + +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) { + 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) +{ + // 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)) { - m_server->on_get(handler.first, [handler](auto response, auto request) + std::ostringstream os; + while (ss.good()) { - std::tuple<std::string,int, std::string> output = handler.second(request->path); - response->type(std::get<2>(output)); - response->status(std::get<1>(output)).send(std::get<0>(output).c_str()); - }); + 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) +{ + 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) { + m_server->remove_handler(path); + + std::lock_guard<std::mutex> lock(m_handlers_mutex); + m_handlers.erase(path); +} + +void http_manager::clear() { + 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) { + auto i = m_endpoints.find(path); + if (i == m_endpoints.end()) { + using namespace std::placeholders; + + auto &endpoint = m_wsserver->m_endpoint[path]; + std::shared_ptr<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::ws_server::Connection> conn) { + this->on_open(endpoint_impl, conn.get()); + }; + + endpoint.on_message = [&, this, endpoint_impl](std::shared_ptr<webpp::ws_server::Connection> conn, 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, conn.get(), payload, opcode); + }; + + endpoint.on_close = [&, this, endpoint_impl](std::shared_ptr<webpp::ws_server::Connection> conn, int status, const std::string& reason) { + this->on_close(endpoint_impl, conn.get(), status, reason); + }; + + endpoint.on_error = [&, this, endpoint_impl](std::shared_ptr<webpp::ws_server::Connection> conn, const std::error_code& error_code) { + this->on_error(endpoint_impl, conn.get(), error_code); + }; + + m_endpoints[path] = endpoint_impl; + return endpoint_impl; + } else { + return (*i).second; } } + +void http_manager::remove_endpoint(const std::string &path) { + m_endpoints.erase(path); +} |