Fixed minors.

master
Hannes Matuschek 11 years ago
parent a3a6165ede
commit 4d79c7e1e4

@ -35,13 +35,6 @@ const char *index_html = "<html>"
"</html>"; "</html>";
bool
json_echo(const http::JSON &request, http::JSON &response) {
response = request;
return true;
}
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
Application app; Application app;
server = new http::Server(8080); server = new http::Server(8080);

@ -4,6 +4,7 @@
#include <arpa/inet.h> #include <arpa/inet.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <iomanip>
using namespace sdr; using namespace sdr;
using namespace sdr::http; using namespace sdr::http;
@ -48,7 +49,6 @@ inline bool is_id_part(char c) {
return (is_alpha_num(c) || '_'); return (is_alpha_num(c) || '_');
} }
inline bool is_space(char c) { inline bool is_space(char c) {
return (' ' == c); return (' ' == c);
} }
@ -61,8 +61,19 @@ inline bool is_header_value_part(char c) {
return ((c>=32) && (c<=127)); 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) { 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) { inline bool is_http_version_part(char c) {
@ -215,7 +226,7 @@ Server::addHandler(Handler *handler) {
/* ********************************************************************************************* * /* ********************************************************************************************* *
* Implementation of HTTPD::Connection * Implementation of HTTPD::Connection
* ********************************************************************************************* */ * ********************************************************************************************* */
http::Connection::Connection(Server *server, int socket) Connection::Connection(Server *server, int socket)
: _server(server), _socket(socket) : _server(server), _socket(socket)
{ {
// Start new thread to parse requests // 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 // Close the socket
this->close(false); this->close(false);
} }
void void
http::Connection::close(bool wait) { Connection::close(bool wait) {
if (-1 != _socket) { if (-1 != _socket) {
int socket = _socket; _socket = -1; int socket = _socket; _socket = -1;
LogMessage msg(LOG_DEBUG); LogMessage msg(LOG_DEBUG);
@ -249,12 +260,12 @@ http::Connection::close(bool wait) {
} }
bool bool
http::Connection::isClosed() const { Connection::isClosed() const {
return ((-1 == _socket) && (0 != pthread_kill(_thread, 0))); return ((-1 == _socket) && (0 != pthread_kill(_thread, 0)));
} }
void * void *
http::Connection::_main(void *ctx) Connection::_main(void *ctx)
{ {
Connection *self = (Connection *)ctx; Connection *self = (Connection *)ctx;
// While socket is open // While socket is open
@ -283,26 +294,26 @@ http::Connection::_main(void *ctx)
/* ********************************************************************************************* * /* ********************************************************************************************* *
* Implementation of HTTPD::URL * Implementation of HTTPD::URL
* ********************************************************************************************* */ * ********************************************************************************************* */
http::URL::URL() URL::URL()
: _protocol(), _host(), _path(), _query() : _protocol(), _host(), _path(), _query()
{ {
// pass... // 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) : _protocol(proto), _host(host), _path(path)
{ {
// pass... // pass...
} }
http::URL::URL(const URL &other) URL::URL(const URL &other)
: _protocol(other._protocol), _host(other._host), _path(other._path), _query(other._query) : _protocol(other._protocol), _host(other._host), _path(other._path), _query(other._query)
{ {
// pass... // pass...
} }
http::URL & URL &
http::URL::operator =(const URL &other) { URL::operator =(const URL &other) {
_protocol = other._protocol; _protocol = other._protocol;
_host = other._host; _host = other._host;
_path = other._path; _path = other._path;
@ -310,8 +321,8 @@ http::URL::operator =(const URL &other) {
return *this; return *this;
} }
http::URL URL
http::URL::fromString(const std::string &url) URL::fromString(const std::string &url)
{ {
std::string text(url), proto, host, path, query_str; std::string text(url), proto, host, path, query_str;
@ -365,26 +376,58 @@ http::URL::fromString(const std::string &url)
std::string std::string
http::URL::toString() const { URL::toString() const {
std::stringstream buffer; std::stringstream buffer;
// serialize protocol if present
if (_protocol.size()) { buffer << _protocol << "://"; } if (_protocol.size()) { buffer << _protocol << "://"; }
// serialize host if present
if (_host.size()) { buffer << _host; } if (_host.size()) { buffer << _host; }
// serialize path (even if not present)
if (_path.size()) { buffer << _path; } if (_path.size()) { buffer << _path; }
else { buffer << "/"; } else { buffer << "/"; }
// serialize query
if (_query.size()) { if (_query.size()) {
buffer << "?"; buffer << "?";
std::list< std::pair<std::string, std::string> >::const_iterator pair = _query.begin(); std::list< std::pair<std::string, std::string> >::const_iterator pair = _query.begin();
buffer << pair->first; buffer << encode(pair->first);
if (pair->second.size()) { buffer << "=" << pair->second; } if (pair->second.size()) { buffer << "=" << encode(pair->second); }
pair++; pair++;
for (; pair != _query.end(); pair++) { for (; pair != _query.end(); pair++) {
buffer << "&" << pair->first; buffer << "&" << encode(pair->first);
if (pair->second.size()) { buffer << "=" << pair->second; } if (pair->second.size()) { buffer << "=" << encode(pair->second); }
} }
} }
return buffer.str(); return buffer.str();
} }
std::string
URL::encode(const std::string &str) {
std::stringstream buffer;
for (size_t i=0; i<str.size(); i++) {
if (32 > 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<str.size(); i++) {
// If there is a %XX string
if (('%' == str[i]) && (3 <= (str.size()-i))) {
buffer << char(strtol(str.substr(i+1, 2).c_str(), 0, 16));
} else { buffer << str[i]; }
}
return buffer.str();
}
/* ********************************************************************************************* * /* ********************************************************************************************* *
@ -397,21 +440,21 @@ typedef enum {
REQUEST_END, REQUEST_END,
START_HEADER, READ_HEADER, START_HEADER_VALUE, READ_HEADER_VALUE, END_HEADER, START_HEADER, READ_HEADER, START_HEADER_VALUE, READ_HEADER_VALUE, END_HEADER,
END_HEADERS END_HEADERS
} HttpdRequestState; } HttpRequestParserState;
http::Request::Request(int socket) Request::Request(int socket)
: _socket(socket), _method(HTTP_UNKNOWN) : _socket(socket), _method(HTTP_UNKNOWN)
{ {
// pass... // pass...
} }
bool bool
http::Request::parse() { Request::parse() {
char c; char c;
std::stringstream buffer; std::stringstream buffer;
std::string current_header_name; std::string current_header_name;
HttpdRequestState state = READ_METHOD; HttpRequestParserState state = READ_METHOD;
// while getting a char from stream // while getting a char from stream
while (::read(_socket, &c, 1)) { while (::read(_socket, &c, 1)) {
@ -550,7 +593,7 @@ http::Request::parse() {
} }
bool bool
http::Request::isKeepAlive() const { Request::isKeepAlive() const {
if (HTTP_1_1 == _version) { return true; } if (HTTP_1_1 == _version) { return true; }
if (HTTP_1_0 == _version) { if (HTTP_1_0 == _version) {
std::map<std::string, std::string>::const_iterator item = _headers.find("Connection"); std::map<std::string, std::string>::const_iterator item = _headers.find("Connection");
@ -561,18 +604,18 @@ http::Request::isKeepAlive() const {
} }
bool bool
http::Request::hasHeader(const std::string &name) const { Request::hasHeader(const std::string &name) const {
return (0 != _headers.count(name)); return (0 != _headers.count(name));
} }
std::string std::string
http::Request::header(const std::string &name) const { Request::header(const std::string &name) const {
std::map<std::string, std::string>::const_iterator item = _headers.find(name); std::map<std::string, std::string>::const_iterator item = _headers.find(name);
return item->second; return item->second;
} }
bool bool
http::Request::readBody(std::string &body) const { Request::readBody(std::string &body) const {
if (! hasContentLength()) { return false; } if (! hasContentLength()) { return false; }
size_t N = contentLength(); body.reserve(N); size_t N = contentLength(); body.reserve(N);
char buffer[65536]; char buffer[65536];
@ -588,41 +631,41 @@ http::Request::readBody(std::string &body) const {
/* ********************************************************************************************* * /* ********************************************************************************************* *
* Implementation of HTTPD::Response * Implementation of HTTPD::Response
* ********************************************************************************************* */ * ********************************************************************************************* */
http::Response::Response(int socket) Response::Response(int socket)
: _socket(socket), _status(STATUS_SERVER_ERROR), _close_connection(false) : _socket(socket), _status(STATUS_SERVER_ERROR), _close_connection(false)
{ {
// pass... // pass...
} }
void void
http::Response::setStatus(Status status) { Response::setStatus(Status status) {
_status = status; _status = status;
} }
bool bool
http::Response::hasHeader(const std::string &name) const { Response::hasHeader(const std::string &name) const {
return (0 != _headers.count(name)); return (0 != _headers.count(name));
} }
std::string std::string
http::Response::header(const std::string &name) const { Response::header(const std::string &name) const {
std::map<std::string, std::string>::const_iterator item = _headers.find(name); std::map<std::string, std::string>::const_iterator item = _headers.find(name);
return item->second; return item->second;
} }
void 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; _headers[name] = value;
} }
void void
http::Response::setContentLength(size_t length) { Response::setContentLength(size_t length) {
std::stringstream buffer; buffer << length; std::stringstream buffer; buffer << length;
setHeader("Content-Length", buffer.str()); setHeader("Content-Length", buffer.str());
} }
bool bool
http::Response::send(const std::string &data) const { Response::send(const std::string &data) const {
const char *ptr = data.c_str(); const char *ptr = data.c_str();
size_t count = data.size(); size_t count = data.size();
while (count) { while (count) {
@ -634,7 +677,7 @@ http::Response::send(const std::string &data) const {
} }
bool bool
http::Response::sendHeaders() const { Response::sendHeaders() const {
std::stringstream buffer; std::stringstream buffer;
buffer << "HTTP/1.1 "; buffer << "HTTP/1.1 ";
// Serialize response status // Serialize response status
@ -657,12 +700,12 @@ http::Response::sendHeaders() const {
/* ********************************************************************************************* * /* ********************************************************************************************* *
* Implementation of HTTPD::Handler * Implementation of HTTPD::Handler
* ********************************************************************************************* */ * ********************************************************************************************* */
http::Handler::Handler() Handler::Handler()
{ {
// pass... // pass...
} }
http::Handler::~Handler() { Handler::~Handler() {
// pass... // pass...
} }
@ -670,23 +713,23 @@ http::Handler::~Handler() {
/* ********************************************************************************************* * /* ********************************************************************************************* *
* Implementation of HTTPD::StaticHandler * 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) : Handler(), _url(url), _mimeType(mimeType), _text(text)
{ {
// pass.. // pass..
} }
http::StaticHandler::~StaticHandler() { StaticHandler::~StaticHandler() {
// pass... // pass...
} }
bool bool
http::StaticHandler::match(const Request &request) { StaticHandler::match(const Request &request) {
return _url == request.url().path(); return _url == request.url().path();
} }
void void
http::StaticHandler::handle(const Request &request, Response &response) { StaticHandler::handle(const Request &request, Response &response) {
response.setStatus(Response::STATUS_OK); response.setStatus(Response::STATUS_OK);
if (_mimeType.size()) { if (_mimeType.size()) {
response.setHeader("Content-type", _mimeType); response.setHeader("Content-type", _mimeType);
@ -700,14 +743,14 @@ http::StaticHandler::handle(const Request &request, Response &response) {
/* ********************************************************************************************* * /* ********************************************************************************************* *
* Implementation of HTTPD::JSONHandler * Implementation of HTTPD::JSONHandler
* ********************************************************************************************* */ * ********************************************************************************************* */
http::JSONHandler::JSONHandler(const std::string &url) JSONHandler::JSONHandler(const std::string &url)
: http::Handler(), _url(url) : http::Handler(), _url(url)
{ {
// pass... // pass...
} }
bool bool
http::JSONHandler::match(const http::Request &request) { JSONHandler::match(const http::Request &request) {
if (http::HTTP_POST != request.method()) { return false; } if (http::HTTP_POST != request.method()) { return false; }
if (request.url().path() != _url) { return false; } if (request.url().path() != _url) { return false; }
if (! request.hasHeader("Content-Type")) { return false; } if (! request.hasHeader("Content-Type")) { return false; }
@ -715,7 +758,7 @@ http::JSONHandler::match(const http::Request &request) {
} }
void void
http::JSONHandler::handle(const http::Request &request, http::Response &response) { JSONHandler::handle(const http::Request &request, http::Response &response) {
std::string body; std::string body;
if (! request.readBody(body)) { if (! request.readBody(body)) {
response.setStatus(http::Response::STATUS_BAD_REQUEST); response.setStatus(http::Response::STATUS_BAD_REQUEST);

@ -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 = "<html> ... </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__ #ifndef __SDR_HTTPD_HH__
#define __SDR_HTTPD_HH__ #define __SDR_HTTPD_HH__
@ -23,6 +81,8 @@ class Server;
/** Represents a JSON object. /** 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 */ * @ingroup http */
class JSON class JSON
{ {
@ -114,20 +174,21 @@ protected:
/** Lists the possible HTTP methods. /** Lists the possible HTTP methods.
* @ingroup http */ * @ingroup http */
typedef enum { typedef enum {
HTTP_UNKNOWN, HTTP_UNKNOWN, ///< Unknown method. Results into an invalid request.
HTTP_GET, HTTP_GET, ///< The get method.
HTTP_HEAD, HTTP_HEAD, ///< The head method.
HTTP_POST HTTP_POST ///< The post method.
} Method; } Method;
/** Lists the possible HTTP versions. /** Lists the possible HTTP versions.
* @ingroup http */ * @ingroup http */
typedef enum { typedef enum {
UNKNOWN_VERSION, UNKNOWN_VERSION, ///< Unknown http version. Results into an invalid request.
HTTP_1_0, HTTP_1_0, ///< HTTP/1.0
HTTP_1_1 HTTP_1_1 ///< HTTP/1.1
} Version; } Version;
/** Represents a URL. /** Represents a URL.
* @ingroup http */ * @ingroup http */
class URL class URL
@ -148,6 +209,11 @@ public:
/** Serializes the URL into a string. */ /** Serializes the URL into a string. */
std::string toString() const; 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. */ /** Returns @c true if the URL specifies a protocol. */
inline bool hasProtocol() const { return (0 != _protocol.size()); } inline bool hasProtocol() const { return (0 != _protocol.size()); }
/** Returns the protocol. */ /** Returns the protocol. */
@ -242,10 +308,10 @@ class Response
public: public:
/** Defines all possible responses. */ /** Defines all possible responses. */
typedef enum { typedef enum {
STATUS_OK = 200, STATUS_OK = 200, ///< OK.
STATUS_BAD_REQUEST = 400, STATUS_BAD_REQUEST = 400, ///< Your fault.
STATUS_NOT_FOUND = 404, STATUS_NOT_FOUND = 404, ///< Resource not found.
STATUS_SERVER_ERROR = 500 STATUS_SERVER_ERROR = 500 ///< My fault.
} Status; } Status;
public: public:
@ -451,7 +517,7 @@ protected:
/** Implements a trivial HTTP/1.1 server. /** Implements a trivial HTTP/1.1 server.
* @ingroup httpd*/ * @ingroup http */
class Server class Server
{ {
public: public:
@ -461,9 +527,11 @@ public:
/** Destructor. */ /** Destructor. */
~Server(); ~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); 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); void stop(bool wait=false);
/** Wait for the server thread to join. */ /** Wait for the server thread to join. */
void wait(); void wait();

Loading…
Cancel
Save