From 4d79c7e1e40ab2eb26583a49419cefa361519461 Mon Sep 17 00:00:00 2001 From: Hannes Matuschek Date: Mon, 8 Jun 2015 10:19:09 +0200 Subject: [PATCH] Fixed minors. --- cmd/main.cc | 7 --- src/http.cc | 131 ++++++++++++++++++++++++++++++++++------------------ src/http.hh | 98 +++++++++++++++++++++++++++++++++------ 3 files changed, 170 insertions(+), 66 deletions(-) diff --git a/cmd/main.cc b/cmd/main.cc index be72fdd..c0e9f63 100644 --- a/cmd/main.cc +++ b/cmd/main.cc @@ -35,13 +35,6 @@ const char *index_html = "" ""; -bool -json_echo(const http::JSON &request, http::JSON &response) { - response = request; - return true; -} - - int main(int argc, char *argv[]) { Application app; server = new http::Server(8080); diff --git a/src/http.cc b/src/http.cc index ef1fed6..ba3373c 100644 --- a/src/http.cc +++ b/src/http.cc @@ -4,6 +4,7 @@ #include #include +#include using namespace sdr; using namespace sdr::http; @@ -48,7 +49,6 @@ inline bool is_id_part(char c) { return (is_alpha_num(c) || '_'); } - inline bool is_space(char c) { return (' ' == c); } @@ -61,8 +61,19 @@ inline bool is_header_value_part(char c) { return ((c>=32) && (c<=127)); } +inline bool is_url_unreserved(char c) { + return (is_alpha_num(c) || ('-'==c) || ('_'==c) || ('.'==c) || ('~'==c)); +} + +inline bool is_url_reserved(char c) { + return (('!'==c) || ('*'==c) || ('\''==c) || ('('==c) || (')'==c) || + (';'==c) || (':'==c) || ('@'==c) || ('&'==c) || ('='==c) || + ('+'==c) || ('$'==c) || (','==c) || ('/'==c) || ('?'==c) || + ('#'==c) || ('['==c) || (']'==c) || ('%'==c)); +} + inline bool is_url_part(char c) { - return (is_alpha_num(c) || ('/'==c) || ('&'==c) || ('%'==c) || ('-'==c) || ('_'==c) || ('='==c)); + return is_url_unreserved(c) || is_url_reserved(c); } inline bool is_http_version_part(char c) { @@ -215,7 +226,7 @@ Server::addHandler(Handler *handler) { /* ********************************************************************************************* * * Implementation of HTTPD::Connection * ********************************************************************************************* */ -http::Connection::Connection(Server *server, int socket) +Connection::Connection(Server *server, int socket) : _server(server), _socket(socket) { // Start new thread to parse requests @@ -227,13 +238,13 @@ http::Connection::Connection(Server *server, int socket) } } -http::Connection::~Connection() { +Connection::~Connection() { // Close the socket this->close(false); } void -http::Connection::close(bool wait) { +Connection::close(bool wait) { if (-1 != _socket) { int socket = _socket; _socket = -1; LogMessage msg(LOG_DEBUG); @@ -249,12 +260,12 @@ http::Connection::close(bool wait) { } bool -http::Connection::isClosed() const { +Connection::isClosed() const { return ((-1 == _socket) && (0 != pthread_kill(_thread, 0))); } void * -http::Connection::_main(void *ctx) +Connection::_main(void *ctx) { Connection *self = (Connection *)ctx; // While socket is open @@ -283,26 +294,26 @@ http::Connection::_main(void *ctx) /* ********************************************************************************************* * * Implementation of HTTPD::URL * ********************************************************************************************* */ -http::URL::URL() +URL::URL() : _protocol(), _host(), _path(), _query() { // pass... } -http::URL::URL(const std::string &proto, const std::string &host, const std::string &path) +URL::URL(const std::string &proto, const std::string &host, const std::string &path) : _protocol(proto), _host(host), _path(path) { // pass... } -http::URL::URL(const URL &other) +URL::URL(const URL &other) : _protocol(other._protocol), _host(other._host), _path(other._path), _query(other._query) { // pass... } -http::URL & -http::URL::operator =(const URL &other) { +URL & +URL::operator =(const URL &other) { _protocol = other._protocol; _host = other._host; _path = other._path; @@ -310,8 +321,8 @@ http::URL::operator =(const URL &other) { return *this; } -http::URL -http::URL::fromString(const std::string &url) +URL +URL::fromString(const std::string &url) { std::string text(url), proto, host, path, query_str; @@ -365,26 +376,58 @@ http::URL::fromString(const std::string &url) std::string -http::URL::toString() const { +URL::toString() const { std::stringstream buffer; + // serialize protocol if present if (_protocol.size()) { buffer << _protocol << "://"; } + // serialize host if present if (_host.size()) { buffer << _host; } + // serialize path (even if not present) if (_path.size()) { buffer << _path; } else { buffer << "/"; } + // serialize query if (_query.size()) { buffer << "?"; std::list< std::pair >::const_iterator pair = _query.begin(); - buffer << pair->first; - if (pair->second.size()) { buffer << "=" << pair->second; } + buffer << encode(pair->first); + if (pair->second.size()) { buffer << "=" << encode(pair->second); } pair++; for (; pair != _query.end(); pair++) { - buffer << "&" << pair->first; - if (pair->second.size()) { buffer << "=" << pair->second; } + buffer << "&" << encode(pair->first); + if (pair->second.size()) { buffer << "=" << encode(pair->second); } } } return buffer.str(); } +std::string +URL::encode(const std::string &str) { + std::stringstream buffer; + for (size_t i=0; i str[i]) { + buffer << "%" << std::setw(2) << std::setfill('0') << std::hex << uint(str[i]); + } else if (127 > str[i]) { + buffer << "%" << std::setw(2) << std::setfill('0') << std::hex << uint(str[i]); + } else if (is_url_reserved(str[i])) { + buffer << "%" << std::setw(2) << std::setfill('0') << std::hex << uint(str[i]); + } else { + buffer << str[i]; + } + } + return buffer.str(); +} + +std::string +URL::decode(const std::string &str) { + std::stringstream buffer; + for (size_t i=0; i::const_iterator item = _headers.find("Connection"); @@ -561,18 +604,18 @@ http::Request::isKeepAlive() const { } bool -http::Request::hasHeader(const std::string &name) const { +Request::hasHeader(const std::string &name) const { return (0 != _headers.count(name)); } std::string -http::Request::header(const std::string &name) const { +Request::header(const std::string &name) const { std::map::const_iterator item = _headers.find(name); return item->second; } bool -http::Request::readBody(std::string &body) const { +Request::readBody(std::string &body) const { if (! hasContentLength()) { return false; } size_t N = contentLength(); body.reserve(N); char buffer[65536]; @@ -588,41 +631,41 @@ http::Request::readBody(std::string &body) const { /* ********************************************************************************************* * * Implementation of HTTPD::Response * ********************************************************************************************* */ -http::Response::Response(int socket) +Response::Response(int socket) : _socket(socket), _status(STATUS_SERVER_ERROR), _close_connection(false) { // pass... } void -http::Response::setStatus(Status status) { +Response::setStatus(Status status) { _status = status; } bool -http::Response::hasHeader(const std::string &name) const { +Response::hasHeader(const std::string &name) const { return (0 != _headers.count(name)); } std::string -http::Response::header(const std::string &name) const { +Response::header(const std::string &name) const { std::map::const_iterator item = _headers.find(name); return item->second; } void -http::Response::setHeader(const std::string &name, const std::string &value) { +Response::setHeader(const std::string &name, const std::string &value) { _headers[name] = value; } void -http::Response::setContentLength(size_t length) { +Response::setContentLength(size_t length) { std::stringstream buffer; buffer << length; setHeader("Content-Length", buffer.str()); } bool -http::Response::send(const std::string &data) const { +Response::send(const std::string &data) const { const char *ptr = data.c_str(); size_t count = data.size(); while (count) { @@ -634,7 +677,7 @@ http::Response::send(const std::string &data) const { } bool -http::Response::sendHeaders() const { +Response::sendHeaders() const { std::stringstream buffer; buffer << "HTTP/1.1 "; // Serialize response status @@ -657,12 +700,12 @@ http::Response::sendHeaders() const { /* ********************************************************************************************* * * Implementation of HTTPD::Handler * ********************************************************************************************* */ -http::Handler::Handler() +Handler::Handler() { // pass... } -http::Handler::~Handler() { +Handler::~Handler() { // pass... } @@ -670,23 +713,23 @@ http::Handler::~Handler() { /* ********************************************************************************************* * * Implementation of HTTPD::StaticHandler * ********************************************************************************************* */ -http::StaticHandler::StaticHandler(const std::string &url, const std::string &text, const std::string mimeType) +StaticHandler::StaticHandler(const std::string &url, const std::string &text, const std::string mimeType) : Handler(), _url(url), _mimeType(mimeType), _text(text) { // pass.. } -http::StaticHandler::~StaticHandler() { +StaticHandler::~StaticHandler() { // pass... } bool -http::StaticHandler::match(const Request &request) { +StaticHandler::match(const Request &request) { return _url == request.url().path(); } void -http::StaticHandler::handle(const Request &request, Response &response) { +StaticHandler::handle(const Request &request, Response &response) { response.setStatus(Response::STATUS_OK); if (_mimeType.size()) { response.setHeader("Content-type", _mimeType); @@ -700,14 +743,14 @@ http::StaticHandler::handle(const Request &request, Response &response) { /* ********************************************************************************************* * * Implementation of HTTPD::JSONHandler * ********************************************************************************************* */ -http::JSONHandler::JSONHandler(const std::string &url) +JSONHandler::JSONHandler(const std::string &url) : http::Handler(), _url(url) { // pass... } bool -http::JSONHandler::match(const http::Request &request) { +JSONHandler::match(const http::Request &request) { if (http::HTTP_POST != request.method()) { return false; } if (request.url().path() != _url) { return false; } if (! request.hasHeader("Content-Type")) { return false; } @@ -715,7 +758,7 @@ http::JSONHandler::match(const http::Request &request) { } void -http::JSONHandler::handle(const http::Request &request, http::Response &response) { +JSONHandler::handle(const http::Request &request, http::Response &response) { std::string body; if (! request.readBody(body)) { response.setStatus(http::Response::STATUS_BAD_REQUEST); diff --git a/src/http.hh b/src/http.hh index 5594fad..2b6c51a 100644 --- a/src/http.hh +++ b/src/http.hh @@ -1,4 +1,62 @@ -/** @defgroup httpd A rather trivia HTTP daemon implementation. */ +/** @defgroup http A rather trivia HTTP daemon implementation. + * + * This module collects some classes allowing to implement a simple HTTP server to serve static + * and dynamic content. The central class is the @c Server class which dispatches incomming + * requests to the registered @c Handler instances. + * + * There are several specializations of the @c Handler class avaliable suited to perform specific + * tasks. I.e. the @c StaticHandler serves some static content (i.e. strings) while the + * @c DelegateJSONHandler allows to implement a REST api easily. + * + * An example of a server, serving a static index page and provides a trivial JSON echo method. + * \code + * #include "http.hh" + * #include "logger.hh" + * + * using namespace sdr; + * + * // Implements an application, a collection of methods being called from the http::Server. + * class Application { + * public: + * // contstructor. + * Application() {} + * + * // The callback to handle the JSON echo api. + * bool echo(const http::JSON &request, http::JSON &result) { + * // just echo + * result = request; + * // signal success + * return true; + * } + * }; + * + * // Static content + * const char *index_html = " ... "; + * + * + * int main(int argc, char *argv[]) { + * // install log handler + * sdr::Logger::get().addHandler( + * new sdr::StreamLogHandler(std::cerr, sdr::LOG_DEBUG)); + * + * // serve on port 8080 + * http::Server server(8080); + * + * // Instantiate application + * Application app; + * + * // Register static content handlers + * server.addStatic("/", index_html, "text/html"); + * // Register JSON echo method + * server.addJSON("/echo", &app, &Application::echo); + * + * // Start server + * server.start(true); + * + * return 0; + * } + * \endcode + */ #ifndef __SDR_HTTPD_HH__ #define __SDR_HTTPD_HH__ @@ -23,6 +81,8 @@ class Server; /** Represents a JSON object. + * JSON is a popular means to implement remote procedure calls (RPCs) using java script. JSON is + * valid java script code and allows to transfer numbers, string, lists and objects. * @ingroup http */ class JSON { @@ -114,20 +174,21 @@ protected: /** Lists the possible HTTP methods. * @ingroup http */ typedef enum { - HTTP_UNKNOWN, - HTTP_GET, - HTTP_HEAD, - HTTP_POST + HTTP_UNKNOWN, ///< Unknown method. Results into an invalid request. + HTTP_GET, ///< The get method. + HTTP_HEAD, ///< The head method. + HTTP_POST ///< The post method. } Method; /** Lists the possible HTTP versions. * @ingroup http */ typedef enum { - UNKNOWN_VERSION, - HTTP_1_0, - HTTP_1_1 + UNKNOWN_VERSION, ///< Unknown http version. Results into an invalid request. + HTTP_1_0, ///< HTTP/1.0 + HTTP_1_1 ///< HTTP/1.1 } Version; + /** Represents a URL. * @ingroup http */ class URL @@ -148,6 +209,11 @@ public: /** Serializes the URL into a string. */ std::string toString() const; + /** Encode a string. */ + static std::string encode(const std::string &str); + /** Decodes a string. */ + static std::string decode(const std::string &str); + /** Returns @c true if the URL specifies a protocol. */ inline bool hasProtocol() const { return (0 != _protocol.size()); } /** Returns the protocol. */ @@ -242,10 +308,10 @@ class Response public: /** Defines all possible responses. */ typedef enum { - STATUS_OK = 200, - STATUS_BAD_REQUEST = 400, - STATUS_NOT_FOUND = 404, - STATUS_SERVER_ERROR = 500 + STATUS_OK = 200, ///< OK. + STATUS_BAD_REQUEST = 400, ///< Your fault. + STATUS_NOT_FOUND = 404, ///< Resource not found. + STATUS_SERVER_ERROR = 500 ///< My fault. } Status; public: @@ -451,7 +517,7 @@ protected: /** Implements a trivial HTTP/1.1 server. - * @ingroup httpd*/ + * @ingroup http */ class Server { public: @@ -461,9 +527,11 @@ public: /** Destructor. */ ~Server(); - /** Starts the server. */ + /** Starts the server. + * If @c wait is @c true, the call to this method will bock until the server thread stops. */ void start(bool wait=false); - /** Stops the server. */ + /** Stops the server. + * If @c wait is @c true, the call will block until the server thread stopped. */ void stop(bool wait=false); /** Wait for the server thread to join. */ void wait();