diff --git a/include/rew/decoder/http_connection.h b/include/rew/decoder/http_connection.h index c76e62df515ffc9277582b55686a0ba7a90dc4e6..925f7134d0e65f2da993ba12854fbaad9f7988c0 100644 --- a/include/rew/decoder/http_connection.h +++ b/include/rew/decoder/http_connection.h @@ -2,6 +2,8 @@ #define REW_HTTP_CONNECTION_H #include "../config.h" +#include "http_response.h" +#include "http_request.h" REW_NAMESPACE { class HttpServer; @@ -16,10 +18,12 @@ REW_NAMESPACE { virtual void start() = 0; virtual void stop() = 0; - void send(const std::string& contentType, int status, const std::string& body); - virtual void sendRaw(std::shared_ptr<std::string> payload) = 0; + protected: - void receive(const std::string& payload); + void receive(const std::string& address, const std::string& payload); + void send(const HttpRequest& req, const HttpResponse& res); + virtual void sendRaw(std::shared_ptr<std::string> payload) = 0; + HttpRequest getRequest(const std::string& address, const std::string& payload) const; private: HttpServer& server; }; diff --git a/include/rew/decoder/http_request.h b/include/rew/decoder/http_request.h new file mode 100644 index 0000000000000000000000000000000000000000..53ad777e696e71c538e2c726c3fd331e480244f3 --- /dev/null +++ b/include/rew/decoder/http_request.h @@ -0,0 +1,18 @@ +#ifndef REW_HTTP_REQUEST_H +#define REW_HTTP_REQUEST_H + +#include "../config.h" + +REW_NAMESPACE { + /*! + * @ingroup decoder + */ + struct HttpRequest { + std::string method; + std::string path; + std::string type; + std::string address; + }; +} + +#endif diff --git a/include/rew/decoder/http_response.h b/include/rew/decoder/http_response.h new file mode 100644 index 0000000000000000000000000000000000000000..e62c538191a7eb177d9fca111e96f6eff84c17f2 --- /dev/null +++ b/include/rew/decoder/http_response.h @@ -0,0 +1,17 @@ +#ifndef REW_HTTP_RESPONSE_H +#define REW_HTTP_RESPONSE_H + +#include "../config.h" + +REW_NAMESPACE { + /*! + * @ingroup decoder + */ + struct HttpResponse { + std::string contentType; + int status; + std::string body; + }; +} + +#endif diff --git a/include/rew/decoder/http_server.h b/include/rew/decoder/http_server.h index 30a0e8f90c16006097df1697cc57a0a08f2fcec4..3cd2b9577d7a8a9159c8294b8e9afe3a1a232676 100644 --- a/include/rew/decoder/http_server.h +++ b/include/rew/decoder/http_server.h @@ -9,6 +9,8 @@ #include <list> #include "../common/input.h" #include "../common/named_raw_file.h" +#include "http_request.h" +#include "http_response.h" REW_NAMESPACE { class HttpConnection; @@ -22,7 +24,7 @@ REW_NAMESPACE { virtual ~HttpServer() = default; void process(const NamedRawFile* data, size_t length) override; - void serve(HttpConnection& con, const std::string& file); + HttpResponse serve(const HttpRequest& request); void disconnect(const std::shared_ptr<HttpConnection>& connection); virtual void start() = 0; virtual void stop() = 0; diff --git a/include/rew/decoder/tcp_http_connection.h b/include/rew/decoder/tcp_http_connection.h index bd1d98b980320e58faf2a59a302df105a6393b0f..135326e84434fcfe145913b30864f140afb7f5fc 100644 --- a/include/rew/decoder/tcp_http_connection.h +++ b/include/rew/decoder/tcp_http_connection.h @@ -16,7 +16,7 @@ REW_NAMESPACE { void start() override; void stop() override; - void sendRaw(std::shared_ptr<std::string> payload) override; + void sendRaw(std::shared_ptr<std::string> payload); private: void doRead(); diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt index 268f60f0df5d57bd11d4f7a5b8b1683d7ef7fa35..e69ca893fdcf2db8299aec87c1bd568358ceff95 100644 --- a/libs/CMakeLists.txt +++ b/libs/CMakeLists.txt @@ -54,7 +54,7 @@ endif() ExternalProject_Add(libui DOWNLOAD_COMMAND "" SOURCE_DIR ${CMAKE_SOURCE_DIR}/libs/libui-cmake - CMAKE_ARGS -DLIBUI_BUILD_EXAMPLES=OFF -DLIBUI_BUILD_SHARED_LIBS=OFF + CMAKE_ARGS -DLIBUI_BUILD_EXAMPLES=OFF -DLIBUI_BUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=MinSizeRel BUILD_COMMAND cmake --build . INSTALL_COMMAND "" TEST_COMMAND "" diff --git a/src/rew/decoder-cli/main.cpp b/src/rew/decoder-cli/main.cpp index 8c1ebef7a34593e5f2c4e45c5ba945f120aecfcf..44c6e4280473b4c4d391652858414320fc4e24ae 100644 --- a/src/rew/decoder-cli/main.cpp +++ b/src/rew/decoder-cli/main.cpp @@ -140,7 +140,7 @@ private: int main (const int argc, char* argv[]) { static bool terminate = false; #ifdef _WIN32 - SetConsoleCtrlHandler([](const DWORD fdwCtrlType) -> BOOL { + SetConsoleCtrlHandler([](const DWORD fdwCtrlType) WINAPI -> BOOL { switch (fdwCtrlType) { case CTRL_C_EVENT: case CTRL_CLOSE_EVENT: diff --git a/src/rew/decoder/http_connection.cpp b/src/rew/decoder/http_connection.cpp index 5149579b4827310962ceb1888520f5be7d755166..9b664b3604f04ddbb00517298cbbcf7a2e4c915f 100644 --- a/src/rew/decoder/http_connection.cpp +++ b/src/rew/decoder/http_connection.cpp @@ -3,6 +3,49 @@ #include <rew/decoder/http_server.h> #include <rew/common/tokenizer.h> +static const std::unordered_map<int, std::string> statusCodes = { + {100, "Continue"}, + {101, "Switching Protocols"}, + {200, "OK"}, + {201, "Created"}, + {202, "Accepted"}, + {203, "Non-Authoritative Information"}, + {204, "No Content"}, + {205, "Reset Content"}, + {206, "Partial Content"}, + {300, "Multiple Choices"}, + {301, "Moved Permanently"}, + {302, "Found"}, + {303, "See Other"}, + {304, "Not Modified"}, + {305, "Use Proxy"}, + {307, "Temporary Redirect"}, + {400, "Bad Request"}, + {401, "Unauthorized"}, + {402, "Payment Required"}, + {403, "Forbidden"}, + {404, "Not Found"}, + {405, "Method Not Allowed"}, + {406, "Not Acceptable"}, + {407, "Proxy Authentication Required"}, + {408, "Request Time-out"}, + {409, "Conflict"}, + {410, "Gone"}, + {411, "Length Required"}, + {412, "Precondition Failed"}, + {413, "Request Entity Too Large"}, + {414, "Request-URI Too Large"}, + {415, "Unsupported Media Type"}, + {416, "Requested range not satisfiable"}, + {417, "Expectation Failed"}, + {500, "Internal Server Error"}, + {501, "Not Implemented"}, + {502, "Bad Gateway"}, + {503, "Service Unavailable"}, + {504, "Gateway Time-out"}, + {505, "HTTP Version not supported"}, +}; + ///===================================================================================================================== rew::HttpConnection::HttpConnection(HttpServer& server) :server(server) { @@ -10,35 +53,65 @@ rew::HttpConnection::HttpConnection(HttpServer& server) } ///===================================================================================================================== -void rew::HttpConnection::send(const std::string& contentType, int status, const std::string& body) { +void rew::HttpConnection::send(const HttpRequest& req, const HttpResponse& res) { std::stringstream ss; - ss << "HTTP/1.1 200 OK\n"; + ss << "HTTP/1.1 " << res.status << " " << statusCodes.at(res.status) << "\n"; ss << "Server: Decoder\n"; ss << "Accept-Ranges: bytes\n"; - ss << "Content-Length: " << std::to_string(body.size()) << "\n"; + ss << "Content-Length: " << std::to_string(res.body.size()) << "\n"; ss << "Connection: close\n"; - ss << "Content-Type: " << contentType << "\n"; + ss << "Content-Type: " << res.contentType << "\n"; ss << "\n"; - ss << body; + ss << res.body; sendRaw(std::make_shared<std::string>(ss.str())); + std::cout + << req.address + << " \"" + << req.method + << " " + << req.path + << " " + << req.type + << "\" " + << res.status + << " " + << res.body.size() + << std::endl; } ///===================================================================================================================== -void rew::HttpConnection::receive(const std::string& payload) { +void rew::HttpConnection::receive(const std::string& address, const std::string& payload) { + HttpRequest req; try { - const auto tokens = getTokens(payload, "\n"); - if (tokens.size() < 1) { - send("text/plain", 400, "Bad request"); + req = getRequest(address, payload); + } catch (std::exception& e) { + send(HttpRequest{"", "", "", address}, HttpResponse{"text/plain", 400, "Bad request"}); + return; + } + + try { + if (req.method != "GET") { + send(req, HttpResponse{"text/plain", 405, "Method not allowed"}); } else { - const auto words = getTokens(tokens[0], " "); - if (words.size() < 2 || words[0] != "GET") { - send("text/plain", 400, "Bad request"); - } else { - server.serve(*this, words[1]); - } + const auto res = server.serve(req); + send(req, res); } } catch (std::exception& e) { - send("text/plain", 500, "Internal server error"); + send(req, HttpResponse{"text/plain", 500, "Internal server error"}); } } +///===================================================================================================================== +rew::HttpRequest rew::HttpConnection::getRequest(const std::string& address, const std::string& payload) const { + const auto tokens = getTokens(payload, "\r\n"); + if (tokens.size() < 1) { + throw std::runtime_error("Invalid http request"); + } else { + const auto words = getTokens(tokens[0], " "); + if (words.size() != 3) { + throw std::runtime_error("Invalid http request"); + } else { + return HttpRequest{words[0], words[1], words[2], address}; + } + } +} diff --git a/src/rew/decoder/http_server.cpp b/src/rew/decoder/http_server.cpp index 1da8da76beac39a57fbefa6dfd4e91c062cba4ac..9a35b9e5733a0789146829a8e5a808e3eef6324d 100644 --- a/src/rew/decoder/http_server.cpp +++ b/src/rew/decoder/http_server.cpp @@ -1,6 +1,119 @@ #include <rew/decoder/http_server.h> #include <rew/decoder/http_connection.h> +// The following list adopted from: +// https://github.com/nginx/nginx/blob/master/conf/mime.types +static const std::unordered_map<std::string, std::string> mimeTypes = { + {"html", "text/html"}, + {"htm", "text/html"}, + {"shtml", "text/html"}, + {"css", "text/css"}, + {"xml", "text/xml"}, + {"gif", "image/gif"}, + {"jpeg", "image/jpeg"}, + {"jpg", "image/jpeg"}, + {"js", "application/javascript"}, + {"atom", "application/atom+xml"}, + {"rss", "application/rss+xml"}, + {"mml", "text/mathml"}, + {"txt", "text/plain"}, + {"jad", "text/vnd.sun.j2me.app-descriptor"}, + {"wml", "text/vnd.wap.wml"}, + {"htc", "text/x-component"}, + {"png", "image/png"}, + {"svg", "image/svg+xml"}, + {"svgz", "image/svg+xml"}, + {"tif", "image/tiff"}, + {"tiff", "image/tiff"}, + {"wbmp", "image/vnd.wap.wbmp"}, + {"webp", "image/webp"}, + {"ico", "image/x-icon"}, + {"jng", "image/x-jng"}, + {"bmp", "image/x-ms-bmp"}, + {"woff", "font/woff"}, + {"woff2", "font/woff2"}, + {"jar", "application/java-archive"}, + {"war", "application/java-archive"}, + {"ear", "application/java-archive"}, + {"json", "application/json"}, + {"hqx", "application/mac-binhex40"}, + {"doc", "application/msword"}, + {"pdf", "application/pdf"}, + {"ps", "application/postscript"}, + {"eps", "application/postscript"}, + {"ai", "application/postscript"}, + {"rtf", "application/rtf"}, + {"m3u8", "application/vnd.apple.mpegurl"}, + {"kml", "application/vnd.google-earth.kml+xml"}, + {"kmz", "application/vnd.google-earth.kmz"}, + {"xls", "application/vnd.ms-excel"}, + {"eot", "application/vnd.ms-fontobject"}, + {"ppt", "application/vnd.ms-powerpoint"}, + {"odg", "application/vnd.oasis.opendocument.graphics"}, + {"odp", "application/vnd.oasis.opendocument.presentation"}, + {"ods", "application/vnd.oasis.opendocument.spreadsheet"}, + {"odt", "application/vnd.oasis.opendocument.text"}, + {"pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"}, + {"xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}, + {"docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"}, + {"wmlc", "application/vnd.wap.wmlc"}, + {"7z", "application/x-7z-compressed"}, + {"cco", "application/x-cocoa"}, + {"jardiff", "application/x-java-archive-diff"}, + {"jnlp", "application/x-java-jnlp-file"}, + {"run", "application/x-makeself"}, + {"pl", "application/x-perl"}, + {"pm", "application/x-perl"}, + {"prc", "application/x-pilot"}, + {"pdb", "application/x-pilot"}, + {"rar", "application/x-rar-compressed"}, + {"rpm", "application/x-redhat-package-manager"}, + {"sea", "application/x-sea"}, + {"swf", "application/x-shockwave-flash"}, + {"sit", "application/x-stuffit"}, + {"tcl", "application/x-tcl"}, + {"tk", "application/x-tcl"}, + {"der", "application/x-x509-ca-cert"}, + {"pem", "application/x-x509-ca-cert"}, + {"crt", "application/x-x509-ca-cert"}, + {"xpi", "application/x-xpinstall"}, + {"xhtml", "application/xhtml+xml"}, + {"xspf", "application/xspf+xml"}, + {"zip", "application/zip"}, + {"bin", "application/octet-stream"}, + {"exe", "application/octet-stream"}, + {"dll", "application/octet-stream"}, + {"deb", "application/octet-stream"}, + {"dmg", "application/octet-stream"}, + {"iso", "application/octet-stream"}, + {"img", "application/octet-stream"}, + {"msi", "application/octet-stream"}, + {"msp", "application/octet-stream"}, + {"msm", "application/octet-stream"}, + {"mid", "audio/midi"}, + {"midi", "audio/midi"}, + {"kar", "audio/midi"}, + {"mp3", "audio/mpeg"}, + {"ogg", "audio/ogg"}, + {"m4a", "audio/x-m4a"}, + {"ra", "audio/x-realaudio"}, + {"3gpp", "video/3gpp"}, + {"3gp", "video/3gpp"}, + {"ts", "video/mp2t"}, + {"mp4", "video/mp4"}, + {"mpeg", "video/mpeg"}, + {"mpg", "video/mpeg"}, + {"mov", "video/quicktime"}, + {"webm", "video/webm"}, + {"flv", "video/x-flv"}, + {"m4v", "video/x-m4v"}, + {"mng", "video/x-mng"}, + {"asx", "video/x-ms-asf"}, + {"asf", "video/x-ms-asf"}, + {"wmv", "video/x-ms-wmv"}, + {"avi", "video/x-msvideo"}, +}; + ///===================================================================================================================== void rew::HttpServer::process(const NamedRawFile* data, size_t length) { for (size_t i = 0; i < length; i++) { @@ -33,13 +146,20 @@ void rew::HttpServer::process(const NamedRawFile* data, size_t length) { } ///===================================================================================================================== -void rew::HttpServer::serve(HttpConnection& con, const std::string& file) { +rew::HttpResponse rew::HttpServer::serve(const HttpRequest& request) { std::lock_guard<std::mutex> guard{mutex}; - const auto it = files.find(file); + const auto it = files.find(request.path); if (it == files.end()) { - con.send("text/plain", 404, "Not found"); + return HttpResponse{"text/plain", 404, "Not found"}; } else { - con.send("text/plain", 200, it->second); + const auto extPos = request.path.find_last_of("."); + const auto ext = request.path.substr(extPos + 1); + const auto mime = mimeTypes.find(ext); + auto mimeType = "application/octet-stream"; + if (mime != mimeTypes.end()) { + mimeType = mime->second.c_str(); + } + return HttpResponse{mimeType, 200, it->second}; } } diff --git a/src/rew/decoder/tcp_http_connection.cpp b/src/rew/decoder/tcp_http_connection.cpp index 6615294226011447d5db7b3824e6e90570b06fa7..eeee664cf3249b2329addcbe242da89c91e76b0a 100644 --- a/src/rew/decoder/tcp_http_connection.cpp +++ b/src/rew/decoder/tcp_http_connection.cpp @@ -49,7 +49,7 @@ void rew::TcpHttpConnection::doRead() { pimpl->socket.async_read_some(asio::buffer(pimpl->buffer), [this, self](const std::error_code ec, const size_t length) { if (!ec){ const auto payload = std::string(reinterpret_cast<const char*>(pimpl->buffer.data()), length); - self->receive(payload); + self->receive(self->pimpl->socket.remote_endpoint().address().to_string(), payload); } else if (ec != asio::error::operation_aborted) { pimpl->server.disconnect(shared_from_this()); }