diff --git a/cmd/CMakeLists.txt b/cmd/CMakeLists.txt index ca963fc..33a0d17 100644 --- a/cmd/CMakeLists.txt +++ b/cmd/CMakeLists.txt @@ -1,2 +1,4 @@ +#add_subdirectory(libhttpd) + add_executable(sdr_cmd main.cc) target_link_libraries(sdr_cmd ${LIBS} libsdr) diff --git a/cmd/json.cc b/cmd/json.cc new file mode 100644 index 0000000..7422728 --- /dev/null +++ b/cmd/json.cc @@ -0,0 +1,5 @@ +#include "json.hh" + +JSON::JSON() +{ +} diff --git a/cmd/json.hh b/cmd/json.hh new file mode 100644 index 0000000..5e2d2d9 --- /dev/null +++ b/cmd/json.hh @@ -0,0 +1,5 @@ +#ifndef __SDR_HTTP_JSON_HH__ +#define __SDR_HTTP_JSON_HH__ + + +#endif // __SDR_HTTP_JSON_HH__ diff --git a/cmd/main.cc b/cmd/main.cc index 6ccd0fd..be72fdd 100644 --- a/cmd/main.cc +++ b/cmd/main.cc @@ -1,225 +1,64 @@ -#include "options.hh" +#include + #include "queue.hh" +#include "http.hh" #include "logger.hh" -#include "wavfile.hh" -#include "autocast.hh" -#include "portaudio.hh" -#include "rtlsource.hh" -#include "baseband.hh" -#include "demod.hh" #include #include using namespace sdr; -// On SIGINT -> stop queue properly -static void __sigint_handler(int signo) { - Queue::get().stop(); -} +class Application +{ +public: + Application() {} -// Command line options -static Options::Definition options[] = { - {"rtl2832", 'R', Options::FLOAT, - "Specifies a RTL2832 USB dongle as the input source"}, - {"rtl-device", 0, Options::INTEGER, - "Specifies the RTL2832 device index. (default 0)"}, - {"disable-rtl-agc", 0 , Options::FLAG, - "Disables the IF AGC of the RTL2832 device, default on."}, - {"rtl-agc-gain", 0, Options::INTEGER, - "In conjecture with --disable-rtl-agc, specifies the fixed IF gain of the RTL2832 device."}, - {"rtl-ppm", 0, Options::FLOAT, - "Specifies the frequency correction for the RTL2832 device in parts-per-million (ppm)."}, - {"audio", 'a', Options::FLAG, - "Specifies the system audio as the input source."}, - {"audio-iq", 'a', Options::FLAG, - "Specifies the system audio as the input source (I/Q channels)."}, - {"source-rate", 0, Options::FLOAT, - "Specifies the sample rate of the input device."}, - {"file", 'f', Options::ANY, - "Specifies a WAV file as input source."}, - {"demod", 'd', Options::ANY, - "Specifies the demodulator (wfm, nfm, am, usb, lsb)."}, - {"demod-offset", 0, Options::FLOAT, - "Specifies the reception offset in Hz. (default 0)"}, - {"loglevel", 0, Options::INTEGER, - "Specifies the log-level. 0 = DEBUG, 1 = INFO, 2 = WARNING, 3 = ERROR."}, - {"help", '?', Options::FLAG, - "Prints this help."}, - {0,0,Options::FLAG,0} + bool echo(const http::JSON &request, http::JSON &response) { + response = request; + return true; + } }; -void print_help() { - std::cerr << "USAGE: sdr_cmd SOURCE [OPTIONS] OUTPUT" << std::endl << std::endl; - Options::print_help(std::cerr, options); +static http::Server *server = 0; + +static void __sigint_handler(int signo) { + server->stop(true); +} + +const char *index_html = "" + "" + "" + "It is alive!" + "" + ""; + + +bool +json_echo(const http::JSON &request, http::JSON &response) { + response = request; + return true; } int main(int argc, char *argv[]) { - // Parse command line options. - Options opts; - if (! Options::parse(options, argc, argv, opts)) { - print_help(); return -1; - } + Application app; + server = new http::Server(8080); - // If help flag is present -> print and done. - if (opts.has("help")) { print_help(); return 0; } - - // Install log-handler - LogLevel loglevel = LOG_INFO; - if (opts.has("loglevel")) { - if (0 >= opts.get("loglevel").toInteger()) { loglevel = LOG_DEBUG; } - else if (1 >= opts.get("loglevel").toInteger()) { loglevel = LOG_INFO; } - else if (2 >= opts.get("loglevel").toInteger()) { loglevel = LOG_WARNING; } - else { loglevel = LOG_ERROR; } - } + // Install log handler sdr::Logger::get().addHandler( - new sdr::StreamLogHandler(std::cerr, loglevel)); + new sdr::StreamLogHandler(std::cerr, sdr::LOG_DEBUG)); - // If no source has been selected -> error - if (! (opts.has("rtl2832")|opts.has("audio")|opts.has("file"))) { - std::cerr << "No source has been selected!" << std::endl; - print_help(); return -1; - } - - // If no demodulator has been selected. - if (!opts.has("demod")) { - std::cerr << "No demodulator has been selected!" << std::endl; - print_help(); return -1; - } - - double bbFc = 0, bbFf = 0, bbFw = 0, bbFs = 0; - // Configure baseband node depending on the selected demodulator - if ("wfm" == opts.get("demod").toString()) { - bbFw = 100e3; bbFs = 100e3; - } else if ("nfm" == opts.get("demod").toString()) { - bbFw = 12.5e3; bbFs = 22.05e3; - } else if ("am" == opts.get("demod").toString()) { - bbFw = 10.0e3; bbFs = 22.05e3; - } else if ("usb" == opts.get("demod").toString()) { - bbFf = 1.5e3; bbFw = 3e3; bbFs = 22.05e3; - } else if ("lsb" == opts.get("demod").toString()) { - bbFf = -1.5e3; bbFw = 3e3; bbFs = 22.05e3; - } else { - std::cerr << "Unknown demodulator '" << opts.get("demod").toString() << "'." << std::endl; - print_help(); return -1; - } - - // Register signal handler to stop queue on SIGINT + // Register signal handler: signal(SIGINT, __sigint_handler); - // Init audio system - PortAudio::init(); + // Register callbacks + server->addStatic("/", index_html, "text/html"); + server->addJSON("/echo", &app, &Application::echo); - // Get the global queue - Queue &queue = Queue::get(); + // start server + server->start(true); - // reference to selected source node - Source *source = 0; - // Nodes for file input - WavSource *wav_src=0; - AutoCast< std::complex > *wav_cast=0; - // nodes for real audio input - PortSource *raudio_src=0; - AutoCast< std::complex > *raudio_cast=0; - // nodes for IQ audio input - PortSource< std::complex > *iqaudio_src=0; - // nodes for RTL2832 input - RTLSource *rtl_source=0; - AutoCast< std::complex > *rtl_cast=0; - - /* Demodulator nodes. */ - Source *demod = 0; - FMDemod *fm_demod = 0; - FMDeemph *fm_deemph = 0; - AMDemod *am_demod = 0; - USBDemod *ssb_demod = 0; - - // Dispatch by selected source - if (opts.has("rtl2832")) { - // Instantiate & config RTL2832 node - size_t devIdx = 0; - double sampleRate = 1e6; - if (opts.has("rtl-device")) { devIdx = opts.get("rtl-device").toInteger(); } - if (opts.has("source-rate")) { sampleRate = opts.get("source-rate").toFloat(); } - rtl_source = new RTLSource(opts.get("rtl2832").toFloat(), sampleRate, devIdx); - if (opts.has("rtl-disable-agc")) { rtl_source->enableAGC(false); } - if (opts.has("rtl-gain")) { rtl_source->setGain(opts.get("rtl-gain").toFloat()); } - if (opts.has("rtl-ppm")) { rtl_source->setFreqCorrection(opts.get("rtl-ppm").toFloat()); } - rtl_cast = new AutoCast< std::complex >(); - rtl_source->connect(rtl_cast); - source = rtl_cast; - // Connect start & stop event to RTL2832 device - queue.addStart(rtl_source, &RTLSource::start); - queue.addStop(rtl_source, &RTLSource::stop); - } else if (opts.has("audio")) { - double sampleRate = 44100; - if (opts.has("source-rate")) { sampleRate = opts.get("source-rate").toFloat(); } - raudio_src = new PortSource(sampleRate, 1024); - raudio_cast = new AutoCast< std::complex >(); - source = raudio_cast; - // Connect idle event to source - queue.addIdle(raudio_src, &PortSource::next); - } else if (opts.has("audio-iq")) { - double sampleRate = 44100; - if (opts.has("source-rate")) { sampleRate = opts.get("source-rate").toFloat(); } - iqaudio_src = new PortSource< std::complex >(sampleRate, 1024); - source = iqaudio_src; - // Connect idle event to source - queue.addIdle(iqaudio_src, &PortSource< std::complex >::next); - } - - // Shift baseband by offset - if (opts.has("demod-offset")) { - bbFc += opts.get("demod-offset").toFloat(); - bbFf += opts.get("demod-offset").toFloat(); - } - - IQBaseBand< int16_t > baseband(bbFc, bbFf, bbFw, 31, 0, bbFs); - source->connect(&baseband, true); - - // Dispatch by selected demodulator - if (("wfm" == opts.get("demod").toString()) || ("nfm" == opts.get("demod").toString())) { - fm_demod = new FMDemod(); - fm_deemph = new FMDeemph(); - source->connect(fm_demod, true); - fm_demod->connect(fm_deemph, true); - demod = fm_deemph; - } else if ("am" == opts.get("demod").toString()) { - am_demod = new AMDemod(); - source->connect(am_demod, true); - demod = am_demod; - } else if (("usb" == opts.get("demod").toString()) || ("lsb" == opts.get("demod").toString())) { - ssb_demod = new USBDemod(); - source->connect(ssb_demod, true); - demod = ssb_demod; - } - - // Enable playback - PortSink audio_sink; - demod->connect(&audio_sink, true); - - // Start queue & processing - queue.start(); - // Wait for queue to to stop - queue.wait(); - - // Free allocated input nodes - if (rtl_source) { delete rtl_source; } - if (rtl_cast) { delete rtl_cast; } - if (raudio_src) { delete raudio_src; } - if (raudio_cast) { delete raudio_cast; } - if (iqaudio_src) { delete iqaudio_src; } - if (wav_src) { delete wav_src; } - if (wav_cast) { delete wav_cast; } - if (fm_demod) { delete fm_demod; } - if (fm_deemph) { delete fm_deemph; } - if (am_demod) { delete am_demod; } - if (ssb_demod) { delete ssb_demod; } - - PortAudio::terminate(); - - // Done. return 0; } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 33cb475..aab6bea 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,13 +1,13 @@ # Sources of libsdr set(LIBSDR_SOURCES buffer.cc node.cc queue.cc traits.cc portaudio.cc utils.cc wavfile.cc exception.cc logger.cc - psk31.cc options.cc fsk.cc ax25.cc aprs.cc baudot.cc pocsag.cc bch31_21.cc) + psk31.cc options.cc fsk.cc ax25.cc aprs.cc baudot.cc pocsag.cc bch31_21.cc http.cc) set(LIBSDR_HEADERS sdr.hh math.hh buffer.hh node.hh queue.hh buffernode.hh filternode.hh traits.hh autocast.hh siggen.hh portaudio.hh utils.hh wavfile.hh demod.hh firfilter.hh fftplan.hh fftplan_native.hh exception.hh baseband.hh freqshift.hh subsample.hh combine.hh logger.hh psk31.hh interpolate.hh operators.hh options.hh fsk.hh ax25.hh - aprs.hh baudot.hh pocsag.hh bch31_21.hh) + aprs.hh baudot.hh pocsag.hh bch31_21.hh http.hh) if(SDR_WITH_PORTAUDIO) set(LIBSDR_SOURCES ${LIBSDR_SOURCES} portaudio.cc) diff --git a/src/demod.hh b/src/demod.hh index 0741180..16f59f0 100644 --- a/src/demod.hh +++ b/src/demod.hh @@ -241,34 +241,28 @@ protected: /** The actual demodulation. */ void _process(const Buffer< std::complex > &in, const Buffer &out) { - // The last input value - std::complex last_value = _last_value; // calc first value - SScalar a = (SScalar(in[0].real())*SScalar(last_value.real()))/2 - + (SScalar(in[0].imag())*SScalar(last_value.imag()))/2; - SScalar b = (SScalar(in[0].imag())*SScalar(last_value.real()))/2 - - (SScalar(in[0].real())*SScalar(last_value.imag()))/2; + SScalar a = (SScalar(in[0].real())*SScalar(_last_value.real()))/2 + + (SScalar(in[0].imag())*SScalar(_last_value.imag()))/2; + SScalar b = (SScalar(in[0].imag())*SScalar(_last_value.real()))/2 + - (SScalar(in[0].real())*SScalar(_last_value.imag()))/2; a >>= Traits::shift; b >>= Traits::shift; // update last value - last_value = in[0]; + _last_value = in[0]; // calc output (prob. overwriting the last value) out[0] = fast_atan2(a, b); - //out[0] = (1<<12)*(std::atan2(float(a),float(b))/M_PI); // Calc remaining values for (size_t i=1; i>= Traits::shift; b >>= Traits::shift; - last_value = in[i]; + _last_value = in[i]; out[i] = fast_atan2(a, b); - //out[i] = (1<<12)*(std::atan2(float(a),float(b))/M_PI); } - // Store last value - _last_value = last_value; // propergate result this->send(out.head(in.size())); } diff --git a/src/http.cc b/src/http.cc new file mode 100644 index 0000000..ef1fed6 --- /dev/null +++ b/src/http.cc @@ -0,0 +1,1029 @@ +#include "http.hh" +#include "exception.hh" +#include "logger.hh" + +#include +#include + +using namespace sdr; +using namespace sdr::http; + + +/* ********************************************************************************************* * + * Utility functions + * ********************************************************************************************* */ +inline bool is_cr(char c) { + return ('\r'==c); +} + +inline bool is_nl(char c) { + return ('\n'==c); +} + +inline bool is_colon(char c) { + return (':'==c); +} + +inline bool is_alpha(char c) { + return ( ((c>='A') && (c<='Z')) || ((c>='a') && (c<='z')) ); +} + +inline bool is_num(char c) { + return ((c>='0') && (c<='9')); +} + +inline bool is_ws(char c) { + return ((' '==c) || ('\t'==c) || ('\n'==c) || ('\r'==c)); +} + +inline bool is_alpha_num(char c) { + return (is_alpha(c) || is_num(c)); +} + +inline bool is_id_start(char c) { + return (is_alpha(c) || '_'); +} + +inline bool is_id_part(char c) { + return (is_alpha_num(c) || '_'); +} + + +inline bool is_space(char c) { + return (' ' == c); +} + +inline bool is_header_part(char c) { + return (is_alpha_num(c) || ('-' == c) || ('_' == c)); +} + +inline bool is_header_value_part(char c) { + return ((c>=32) && (c<=127)); +} + +inline bool is_url_part(char c) { + return (is_alpha_num(c) || ('/'==c) || ('&'==c) || ('%'==c) || ('-'==c) || ('_'==c) || ('='==c)); +} + +inline bool is_http_version_part(char c) { + return ( is_alpha_num(c) || ('/'==c) || ('.'==c)); +} + +inline http::Method to_method(const std::string &method) { + if ("GET" == method) { return http::HTTP_GET; } + else if ("HEAD" == method) { return http::HTTP_HEAD; } + else if ("POST" == method) { return http::HTTP_POST; } + return http::HTTP_UNKNOWN; +} + +inline http::Version to_version(const std::string &version) { + if ("HTTP/1.0" == version) { return http::HTTP_1_0; } + else if ("HTTP/1.1" == version) { return http::HTTP_1_1; } + return http::UNKNOWN_VERSION; +} + + +/* ********************************************************************************************* * + * Implementation of Server + * ********************************************************************************************* */ +Server::Server(uint port) + : _port(port), _socket(-1) +{ + // pass... +} + +Server::~Server() { + _is_running = false; + // Close all connections + std::set::iterator con = _connections.begin(); + for (; con != _connections.end(); con++) { + // Wait for the connection to close + (*con)->close(true); + delete *con; + } + // Free all handler + std::list::iterator item = _handler.begin(); + for (; item != _handler.end(); item++) { delete *item; } +} + +void +Server::start(bool wait) { + _socket = socket(AF_INET, SOCK_STREAM, 0); + if (_socket < 0) { + ConfigError err; + err << "httpd: Error opening socket."; + throw err; + } + + struct sockaddr_in serv_addr; + bzero((char *) &serv_addr, sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); + serv_addr.sin_port = htons(_port); + if (bind(_socket, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) { + ConfigError err; + err << "httpd: Can bind to address."; + throw err; + } + + int reuseaddr = 1; + if (setsockopt(_socket, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr))) { + LogMessage msg(LOG_WARNING); + msg << "httpd: Can not set SO_REUSEADDR flag for socket."; + Logger::get().log(msg); + } + + _is_running = true; + if (pthread_create(&_thread, 0, &Server::_listen_main, this)) { + ConfigError err; + err << "Can not create listen thread."; + throw err; + } + + if (wait) { this->wait(); } +} + +void +Server::stop(bool wait) { + _is_running = false; + // Close all open connections + std::set::iterator con = _connections.begin(); + for (; con != _connections.end(); con++) { + (*con)->close(wait); + } + // Close the socket we listen on + ::close(_socket); _socket = -1; +} + +void +Server::wait() { + void *ret=0; + pthread_join(_thread, &ret); +} + +void * +Server::_listen_main(void *ctx) { + Server *self = (Server *)ctx; + while (self->_is_running) { + listen(self->_socket, 5); + struct sockaddr_in cli_addr; + socklen_t clilen = sizeof(cli_addr); + int socket = accept(self->_socket, (struct sockaddr *) &cli_addr, &clilen); + if (socket < 0) { continue; } + try { self->_connections.insert(new Connection(self, socket)); } + catch (...) { } + // Free closed connections + std::set::iterator item = self->_connections.begin(); + for (; item != self->_connections.end(); item++) { + if ((*item)->isClosed()) { delete *item; item = self->_connections.erase(item); } + } + } + return 0; +} + +void +Server::dispatch(const Request &request, Response &response) { + LogMessage msg(LOG_DEBUG); + msg << "httpd: "; + switch (request.method()) { + case HTTP_GET: msg << "GET "; break; + case HTTP_HEAD: msg << "HEAD "; break; + case HTTP_POST: msg << "POST "; break; + case HTTP_UNKNOWN: msg << "UNKNOWN "; break; + } + msg << " " << request.url().toString(); + Logger::get().log(msg); + + std::list::iterator item = _handler.begin(); + for (; item != _handler.end(); item++) { + if ((*item)->match(request)) { + (*item)->handle(request, response); + return; + } + } + response.setStatus(Response::STATUS_NOT_FOUND); + response.setHeader("Content-length", "0"); + response.sendHeaders(); +} + +void +Server::addHandler(Handler *handler) { + _handler.push_back(handler); +} + + +/* ********************************************************************************************* * + * Implementation of HTTPD::Connection + * ********************************************************************************************* */ +http::Connection::Connection(Server *server, int socket) + : _server(server), _socket(socket) +{ + // Start new thread to parse requests + if (pthread_create(&_thread, 0, &Connection::_main, (void *)this)) { + ::close(_socket); _socket = -1; + RuntimeError err; + err << "httpd: Can not create thread for connection."; + throw err; + } +} + +http::Connection::~Connection() { + // Close the socket + this->close(false); +} + +void +http::Connection::close(bool wait) { + if (-1 != _socket) { + int socket = _socket; _socket = -1; + LogMessage msg(LOG_DEBUG); + msg << "httpd: Close connection " << socket << "."; + Logger::get().log(msg); + ::close(socket); + } + if (wait && (0 == pthread_kill(_thread, 0))) { + // Wait for the thread to exit. + void *ret = 0; + pthread_join(_thread, &ret); + } +} + +bool +http::Connection::isClosed() const { + return ((-1 == _socket) && (0 != pthread_kill(_thread, 0))); +} + +void * +http::Connection::_main(void *ctx) +{ + Connection *self = (Connection *)ctx; + // While socket is open + int error = 0; socklen_t errorlen = sizeof (error); + while (0 == getsockopt(self->_socket, SOL_SOCKET, SO_ERROR, &error, &errorlen)) { + Request request(self->_socket); + Response response(self->_socket); + // Parse request + if (!request.parse()) { + // On parser error or no keep-alive -> exit + self->close(); return 0; + } + // on success -> dispatch request by server + self->_server->dispatch(request, response); + // If the connection is kept alive -> continue + if ((! request.isKeepAlive()) || response.closeConnection()) { + // Signal server to close connection + self->close(self); return 0; + } + } + // Signal server to close connection + self->close(self); return 0; +} + + +/* ********************************************************************************************* * + * Implementation of HTTPD::URL + * ********************************************************************************************* */ +http::URL::URL() + : _protocol(), _host(), _path(), _query() +{ + // pass... +} + +http::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) + : _protocol(other._protocol), _host(other._host), _path(other._path), _query(other._query) +{ + // pass... +} + +http::URL & +http::URL::operator =(const URL &other) { + _protocol = other._protocol; + _host = other._host; + _path = other._path; + _query = other._query; + return *this; +} + +http::URL +http::URL::fromString(const std::string &url) +{ + std::string text(url), proto, host, path, query_str; + + size_t idx = 0; + // split proto + idx = text.find("://"); + if ((0 != idx) && (std::string::npos != idx)) { + proto = text.substr(0, idx); + text = text.substr(idx+3); + } + + // split host + if (text.size() && ('/' != text[0])) { + idx = text.find('/'); + if (std::string::npos != idx) { + host = text.substr(0, idx); + path = text.substr(idx); + } + } else { + path = text; + } + + // split query + idx = path.find('?'); + if (std::string::npos != idx) { + query_str = path.substr(idx+1); + path = path.substr(0, idx); + } + + URL res(proto, host, path); + while (query_str.size()) { + idx = query_str.find('&'); + std::string pair; + if (std::string::npos != idx) { + pair = query_str.substr(0, idx); + query_str = query_str.substr(idx+1); + } else { + pair = query_str; + query_str.clear(); + } + idx = pair.find("="); + if (std::string::npos == idx) { + res.addQuery(pair, ""); + } else { + res.addQuery(pair.substr(0, idx), pair.substr(idx+1)); + } + } + + return res; +} + + +std::string +http::URL::toString() const { + std::stringstream buffer; + if (_protocol.size()) { buffer << _protocol << "://"; } + if (_host.size()) { buffer << _host; } + if (_path.size()) { buffer << _path; } + else { buffer << "/"; } + if (_query.size()) { + buffer << "?"; + std::list< std::pair >::const_iterator pair = _query.begin(); + buffer << pair->first; + if (pair->second.size()) { buffer << "=" << pair->second; } + pair++; + for (; pair != _query.end(); pair++) { + buffer << "&" << pair->first; + if (pair->second.size()) { buffer << "=" << pair->second; } + } + } + return buffer.str(); +} + + + +/* ********************************************************************************************* * + * Implementation of HTTPD::Request + * ********************************************************************************************* */ +typedef enum { + READ_METHOD, + START_URL, READ_URL, + START_HTTP_VERSION, READ_HTTP_VERSION, + REQUEST_END, + START_HEADER, READ_HEADER, START_HEADER_VALUE, READ_HEADER_VALUE, END_HEADER, + END_HEADERS +} HttpdRequestState; + + +http::Request::Request(int socket) + : _socket(socket), _method(HTTP_UNKNOWN) +{ + // pass... +} + +bool +http::Request::parse() { + char c; + std::stringstream buffer; + std::string current_header_name; + HttpdRequestState state = READ_METHOD; + + // while getting a char from stream + while (::read(_socket, &c, 1)) { + switch (state) { + case READ_METHOD: + if (is_space(c)) { + // Get method enum + _method = to_method(buffer.str()); + if (HTTP_UNKNOWN == _method) { + LogMessage msg(LOG_DEBUG); + msg << "http: Got unexpected method '"<::const_iterator item = _headers.find("Connection"); + if (_headers.end() == item) { return false; } + if ("Keep-alive" == item->second) { return true; } + } + return false; +} + +bool +http::Request::hasHeader(const std::string &name) const { + return (0 != _headers.count(name)); +} + +std::string +http::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 { + if (! hasContentLength()) { return false; } + size_t N = contentLength(); body.reserve(N); + char buffer[65536]; + while (N>0) { + int res = ::read(_socket, buffer, std::min(N, size_t(65536))); + if (res>=0) { body.append(buffer, size_t(res)); N -= res; } + else { return false; } + } + return true; +} + + +/* ********************************************************************************************* * + * Implementation of HTTPD::Response + * ********************************************************************************************* */ +http::Response::Response(int socket) + : _socket(socket), _status(STATUS_SERVER_ERROR), _close_connection(false) +{ + // pass... +} + +void +http::Response::setStatus(Status status) { + _status = status; +} + +bool +http::Response::hasHeader(const std::string &name) const { + return (0 != _headers.count(name)); +} + +std::string +http::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) { + _headers[name] = value; +} + +void +http::Response::setContentLength(size_t length) { + std::stringstream buffer; buffer << length; + setHeader("Content-Length", buffer.str()); +} + +bool +http::Response::send(const std::string &data) const { + const char *ptr = data.c_str(); + size_t count = data.size(); + while (count) { + int c = ::write(_socket, ptr, count); + if (c < 0) { return false; } + count -= c; ptr += c; + } + return true; +} + +bool +http::Response::sendHeaders() const { + std::stringstream buffer; + buffer << "HTTP/1.1 "; + // Serialize response status + switch (_status) { + case STATUS_OK: buffer << "200 OK\r\n"; break; + case STATUS_BAD_REQUEST: buffer << "400 BAD REQUEST\r\n"; break; + case STATUS_NOT_FOUND: buffer << "404 NOT FOUND\r\n"; break; + case STATUS_SERVER_ERROR: buffer << "500 SERVER ERROR\r\n"; break; + } + // serialize headers + std::map::const_iterator item = _headers.begin(); + for (; item != _headers.end(); item++) { + buffer << item->first << ": " << item->second << "\r\n"; + } + buffer << "\r\n"; + return send(buffer.str()); +} + + +/* ********************************************************************************************* * + * Implementation of HTTPD::Handler + * ********************************************************************************************* */ +http::Handler::Handler() +{ + // pass... +} + +http::Handler::~Handler() { + // pass... +} + + +/* ********************************************************************************************* * + * Implementation of HTTPD::StaticHandler + * ********************************************************************************************* */ +http::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() { + // pass... +} + +bool +http::StaticHandler::match(const Request &request) { + return _url == request.url().path(); +} + +void +http::StaticHandler::handle(const Request &request, Response &response) { + response.setStatus(Response::STATUS_OK); + if (_mimeType.size()) { + response.setHeader("Content-type", _mimeType); + } + response.setContentLength(_text.size()); + response.sendHeaders(); + response.send(_text); +} + + +/* ********************************************************************************************* * + * Implementation of HTTPD::JSONHandler + * ********************************************************************************************* */ +http::JSONHandler::JSONHandler(const std::string &url) + : http::Handler(), _url(url) +{ + // pass... +} + +bool +http::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; } + return ("application/json" == request.header("Content-Type")); +} + +void +http::JSONHandler::handle(const http::Request &request, http::Response &response) { + std::string body; + if (! request.readBody(body)) { + response.setStatus(http::Response::STATUS_BAD_REQUEST); + response.setContentLength(0); + response.sendHeaders(); + } + JSON obj; + if (!JSON::parse(body, obj)) { + response.setStatus(http::Response::STATUS_BAD_REQUEST); + response.setContentLength(0); + response.sendHeaders(); + } + + JSON result; + std::string result_string; + if (this->process(obj, result)) { + response.setStatus(http::Response::STATUS_OK); + result.serialize(result_string); + response.setContentLength(result_string.size()); + response.sendHeaders(); + response.send(result_string); + } else { + response.setStatus(http::Response::STATUS_BAD_REQUEST); + response.setContentLength(0); + response.sendHeaders(); + } +} + + +/* ********************************************************************************************* * + * Implementation of JSON + * ********************************************************************************************* */ +JSON::JSON() + : _type(EMPTY) +{ + // pass.. +} + +JSON::JSON(bool value) + : _type(BOOLEAN) +{ + _value.boolean = new bool(value); +} + +JSON::JSON(double value) + : _type(NUMBER) +{ + _value.number = new double(value); +} + +JSON::JSON(const std::string &value) + : _type(STRING) +{ + _value.string = new std::string(value); +} + +JSON::JSON(const std::list &list) + : _type(ARRAY) +{ + _value.list = new std::list(list); +} + +JSON::JSON(const std::map &table) + : _type(TABLE) +{ + _value.table = new std::map(table); +} + +JSON::~JSON() { + this->clear(); +} + +JSON::JSON(const JSON &other) + : _type(other._type) +{ + switch (_type) { + case EMPTY: break; + case BOOLEAN: _value.boolean = new bool(other.asBoolean()); break; + case NUMBER: _value.number = new double(other.asNumber()); break; + case STRING: _value.string = new std::string(other.asString()); break; + case ARRAY: _value.list = new std::list(other.asArray()); break; + case TABLE: _value.table = new std::map(other.asTable()); break; + } +} + +JSON & +JSON::operator =(const JSON &other) { + this->clear(); + _type = other._type; + switch (_type) { + case EMPTY: break; + case BOOLEAN: _value.boolean = new bool(other.asBoolean()); break; + case NUMBER: _value.number = new double(other.asNumber()); break; + case STRING: _value.string = new std::string(other.asString()); break; + case ARRAY: _value.list = new std::list(other.asArray()); break; + case TABLE: _value.table = new std::map(other.asTable()); break; + } + return *this; +} + +void +JSON::clear() { + switch (_type) { + case EMPTY: break; + case BOOLEAN: delete _value.boolean; break; + case NUMBER: delete _value.number; break; + case STRING: delete _value.string; break; + case ARRAY: delete _value.list; break; + case TABLE: delete _value.table; break; + } + _type = EMPTY; +} + +void +JSON::serialize(std::ostream &stream) const { + switch (_type) { + case EMPTY: + stream << "null"; + break; + + case BOOLEAN: + if (*_value.boolean) { stream << "true"; } + else { stream << "false"; } + break; + + case NUMBER: + stream << *_value.number; + break; + + case STRING: + stream << "\""; + for (size_t i=0; i<_value.string->size(); i++) { + if ('"' == _value.string->at(i)) { stream << "\\\""; } + else { stream << _value.string->at(i); } + } + stream << "\""; + break; + + case ARRAY: + stream << "["; + if (0 < _value.list->size()) { + std::list::iterator item = _value.list->begin(); + item->serialize(stream); item++; + for (; item != _value.list->end(); item++) { + stream << ","; item->serialize(stream); + } + } + stream << "]"; + break; + + case TABLE: + stream << "{"; + if (0 < _value.table->size()) { + std::map::iterator item = _value.table->begin(); + stream << item->first << ":"; item->second.serialize(stream); item++; + for (; item != _value.table->end(); item++) { + stream << "," << item->first << ":"; item->second.serialize(stream); + } + } + stream << "}"; + break; + } +} + +void +_json_skip_ws(const char *&text, size_t &n) { + while ((n>0) && is_ws(*text)) { text++; n--; } +} + +bool _json_parse(const char *&text, size_t &n, JSON &obj); + + bool +_json_parse_null(const char *&text, size_t &n, JSON &obj) { + if ((n<4) || (0 != strncmp(text, "null", 4))) { return false; } + text+=4; n-=4; + obj = JSON(); + if (0 == n) { return true; } + if (! is_alpha_num(*text)) { return true; } + return false; +} + +bool +_json_parse_true(const char *&text, size_t &n, JSON &obj) { + if ((n<4) || (0 != strncmp(text, "true", 4))) { return false; } + text+=4; n-=4; + obj = JSON(true); + if (0 == n) { return true; } + if (! is_alpha_num(*text)) { return true; } + return false; +} + +bool +_json_parse_false(const char *&text, size_t &n, JSON &obj) { + if ((n<5) || (0 != strncmp(text, "false", 5))) { return false; } + text+=5; n-=5; + if (0 == n) { return true; } + if (! is_alpha_num(*text)) { return true; } + obj = JSON(false); + return false; +} + +bool +_json_parse_string(const char *&text, size_t &n, JSON &obj) { + std::stringstream buffer; + text++; n--; // skip '"' + bool escape = false; + while (n > 0) { + if (escape) { buffer << *text; escape = false; } + else if (('\\' == *text) && (!escape)) { escape = true; } + else if ('"' == *text) { text++; n--; obj = JSON(buffer.str()); return true;} + else { buffer << *text; } + text++; n--; + } + return false; +} + +bool +_json_parse_list(const char *&text, size_t &n, JSON &obj) { + text++; n--; // skip [ + std::list lst; + + _json_skip_ws(text, n); + if (0 == n) { return false; } + if (']' == *text) { obj = JSON(lst); text++; n--; return true; } + + JSON tmp; + while (n>0) { + if (! _json_parse(text, n, tmp)) { return false; } + lst.push_back(tmp); + _json_skip_ws(text, n); + if (0 == n) { return false; } + if (']'==*text) { text++; n--; obj = JSON(lst); return true; } + if (',' != *text) { return false; } + text++; n--; _json_skip_ws(text, n); + } + return false; +} + +bool +_json_parse_identifier(const char *&text, size_t &n, std::string &name) { + if (0 == n) { return false; } + std::stringstream buffer; + if (! is_id_start(*text)) { return false; } + buffer << *text; text++; n--; + while ((n>0) && is_id_part(*text)) { + buffer << *text; text++; n--; + } + name = buffer.str(); + return true; +} + +bool +_json_parse_table(const char *&text, size_t &n, JSON &obj) { + text++; n--; // skip { + std::map table; + + _json_skip_ws(text, n); + if (0 == n) { return false; } + if ('}' == *text) { obj = JSON(table); text++; n--; return true; } + + std::string name; + JSON tmp; + while (n>0) { + if (! _json_parse_identifier(text, n, name)) { return false; } + _json_skip_ws(text, n); + if (0 == n) { return false; } + if (':' != *text) { return false; } + text++; n--; _json_skip_ws(text, n); + if (! _json_parse(text, n, tmp)) { return false; } + table[name] = tmp; + _json_skip_ws(text, n); + if (0 == n) { return false; } + if (']'==*text) { text++; n--; obj = JSON(table); return true; } + if (',' != *text) { return false; } + text++; n--; _json_skip_ws(text, n); + } + return false; +} + +bool +_json_parse_number(const char *&text, size_t &n, JSON &obj) { + const char *ptr = text; + double value = strtof(text, (char **)&ptr); + if (text == ptr) { return false; } + obj = JSON(value); text=ptr; n-=(ptr-text); + return true; +} + + +bool +_json_parse(const char *&text, size_t &n, JSON &obj) { + _json_skip_ws(text, n); + if (0 == n) { return false; } + // Dispatch by first char + switch (*text) { + case 'n': return _json_parse_null(text, n, obj); + case 't': return _json_parse_true(text, n, obj); + case 'f': return _json_parse_false(text, n, obj); + case '"': return _json_parse_string(text, n, obj); + case '[': return _json_parse_list(text, n, obj); + case '{': return _json_parse_table(text, n, obj); + default: return _json_parse_number(text, n, obj); + } +} + +bool +JSON::parse(const std::string &text, JSON &obj) { + const char *ptr = text.c_str(); + size_t n = text.size(); + return _json_parse(ptr, n, obj); +} diff --git a/src/http.hh b/src/http.hh new file mode 100644 index 0000000..5594fad --- /dev/null +++ b/src/http.hh @@ -0,0 +1,521 @@ +/** @defgroup httpd A rather trivia HTTP daemon implementation. */ + +#ifndef __SDR_HTTPD_HH__ +#define __SDR_HTTPD_HH__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace sdr { +namespace http { + +// Forward declarations +class Server; + + +/** Represents a JSON object. + * @ingroup http */ +class JSON +{ +public: + /** Specifies the possible json types. */ + typedef enum { + EMPTY, ///< null. + BOOLEAN, ///< A boolean value. + NUMBER, ///< A number. + STRING, ///< A string. + ARRAY, ///< An array or list. + TABLE ///< A table or object. + } Type; + +public: + /** Empty constructor (null). */ + JSON(); + /** Constructs a boolean value. */ + JSON(bool value); + /** Constructs a number. */ + JSON(double value); + /** Constructs a string. */ + JSON(const std::string &value); + /** Constructs a list. */ + JSON(const std::list &list); + /** Constructs a table. */ + JSON(const std::map &table); + /** Copy constructor (deep copy). */ + JSON(const JSON &other); + /** Destructor. */ + ~JSON(); + /** Assignment operator (deep copy). */ + JSON &operator=(const JSON &other); + + /** Retruns @c true for the null. */ + inline bool isNull() const { return EMPTY == _type; } + /** Returns @c true if this is a boolean value. */ + inline bool isBoolean() const { return BOOLEAN == _type; } + /** Returns the boolean value. */ + inline const bool &asBoolean() const { return *_value.boolean; } + /** Returns @c true if this is a number. */ + inline bool isNumber() const { return NUMBER == _type; } + /** Returns the number. */ + inline const double &asNumber() const { return *_value.number; } + /** Returns @c true if this is a string. */ + inline bool isString() const { return STRING == _type; } + /** Returns the string. */ + inline const std::string &asString() const { return *_value.string; } + /** Returns @c true if this is an array. */ + inline bool isArray() const { return ARRAY == _type; } + /** Retruns the array as a list. */ + inline const std::list &asArray() const { return *_value.list; } + /** Returns @c true if this is a table. */ + inline bool isTable() const { return TABLE == _type; } + /** Returns the table. */ + inline const std::map &asTable() const { return *_value.table; } + /** Resets the JSON object to null. */ + void clear(); + + /** Parses the given string. Retruns @c true on success. */ + static bool parse(const std::string &text, JSON &obj); + /** Serializes the JSON object. */ + void serialize(std::ostream &stream) const; + /** Serializes the JSON object. */ + inline void serialize(std::string &text) const { + std::stringstream buffer; this->serialize(buffer); + text = buffer.str(); + } + +protected: + /** The type of the object. */ + Type _type; + /** Union of value pointers. */ + union { + /** The boolean value. */ + bool *boolean; + /** The number. */ + double *number; + /** The string. */ + std::string *string; + /** The array. */ + std::list *list; + /** The table. */ + std::map *table; + } _value; +}; + + +/** Lists the possible HTTP methods. + * @ingroup http */ +typedef enum { + HTTP_UNKNOWN, + HTTP_GET, + HTTP_HEAD, + HTTP_POST +} Method; + +/** Lists the possible HTTP versions. + * @ingroup http */ +typedef enum { + UNKNOWN_VERSION, + HTTP_1_0, + HTTP_1_1 +} Version; + +/** Represents a URL. + * @ingroup http */ +class URL +{ +public: + /** Empty constructor. */ + URL(); + /** Constructor from protocol, host and path. */ + URL(const std::string &proto, const std::string &host, const std::string &path); + /** Copy constructor. */ + URL(const URL &other); + + /** Assignment operator. */ + URL &operator=(const URL &other); + + /** Parses a URL from the given string. */ + static URL fromString(const std::string &url); + /** Serializes the URL into a string. */ + std::string toString() const; + + /** Returns @c true if the URL specifies a protocol. */ + inline bool hasProtocol() const { return (0 != _protocol.size()); } + /** Returns the protocol. */ + inline const std::string protocol() const { return _protocol; } + /** Sets the protocol. */ + inline void setProtocol(const std::string &proto) { _protocol = proto; } + /** Returns @c true if the URL specified a host name. */ + inline bool hasHost() const { return (0 != _host.size()); } + /** Returns the host name. */ + inline const std::string &host() const { return _host; } + /** Set the host name. */ + inline void setHost(const std::string &host) { _host = host; } + /** Retruns the path of the URL. */ + inline const std::string &path() const { return _path; } + /** Sets the path of the URL. */ + inline void setPath(const std::string &path) { _path = path; } + /** Adds a query pair (key, value) to the URL. */ + inline void addQuery(const std::string &name, const std::string &value) { + _query.push_back(std::pair(name, value)); + } + /** Returns the list of query (key, value) pairs. */ + inline const std::list< std::pair > &query() const { + return _query; + } + +protected: + /** Holds the protocol. */ + std::string _protocol; + /** Holds the host name. */ + std::string _host; + /** Holds the path. */ + std::string _path; + /** Holds the query pairs. */ + std::list< std::pair > _query; +}; + + +/** Represents a HTTP request. + * @ingroup http */ +class Request +{ +public: + /** Constructor. */ + Request(int socket); + + /** Parses the HTTP request header, returns @c true on success. */ + bool parse(); + + /** Returns @c true if the connection to the client is kept alive after the response + * has been send. */ + bool isKeepAlive() const; + + /** Returns @c true if the given header is present. */ + bool hasHeader(const std::string &name) const; + /** Returns the value of the given header. */ + std::string header(const std::string &name) const; + + /** Returns @c true if the Content-Length header is present. */ + inline bool hasContentLength() const { return hasHeader("Content-Length"); } + /** Returns the value of the Content-Length header. */ + size_t contentLength() const { return atol(header("Content-Length").c_str()); } + + /** Retruns the request method. */ + inline Method method() const { return _method; } + /** Returns the request URL. */ + inline const URL &url() const { return _url; } + /** Allows to read data from the connection (i.e. the request body). */ + inline int read(const char *data, size_t size) { + return ::read(_socket, (void *)data, size); + } + /** Reads the complete body (if Content-Length header is present). + * Retruns @c true on success.*/ + bool readBody(std::string &body) const; + +protected: + /** The connection socket. */ + int _socket; + /** The request method. */ + Method _method; + /** The HTTP version. */ + Version _version; + /** The request URL. */ + URL _url; + /** The request headers. */ + std::map _headers; +}; + + +/** Represents a HTTP response. */ +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; + +public: + /** Constructor. + * @param socket Specifies the socket over which the response will be send.*/ + Response(int socket); + + /** Specifies the response code. */ + void setStatus(Status status); + /** Returns @c true if the response has the given header. */ + bool hasHeader(const std::string &name) const; + /** Returns the value of the header. */ + std::string header(const std::string &name) const; + /** Sets the header. */ + void setHeader(const std::string &name, const std::string &value); + /** Helper function to set the content length. */ + void setContentLength(size_t length); + + /** Sends the response code and all defined headers. */ + bool sendHeaders() const; + + /** Sends some data through the socket. To send the response body, call + * @c sendHeaders() first. */ + bool send(const std::string &data) const; + + /** Returns @c true if the connection will be closed after the response has been send. + * @c I.e. if there was an error. */ + inline bool closeConnection() const { return _close_connection; } + /** Marks that the connection will be closed after the response has been send. */ + inline void setCloseConnection() { _close_connection = true; } + +protected: + /** The socket over which the response will be send. */ + int _socket; + /** The response code. */ + Status _status; + /** The response headers. */ + std::map _headers; + /** If @c true, the connection will be closed after the response has been send. */ + bool _close_connection; +}; + + +/** Implements a HTTP connection to a client. + * @c ingroup http */ +class Connection +{ +public: + /** Constructor. */ + Connection(Server *server, int socket); + /** Destructor. */ + ~Connection(); + + /** Closes the connection. + * If @c wait is @c true, the method will wait until the thread listening for incomming + * request joined. */ + void close(bool wait=false); + /** Returns @c true if the connection is closed. */ + bool isClosed() const; + +protected: + /** Main loop for incomming requests. */ + static void *_main(void *ctx); + +protected: + /** A weak reference to the server instance. */ + Server *_server; + /** The connection socket. */ + int _socket; + /** The thread processing requests for this connection. */ + pthread_t _thread; +}; + + +/** Base class of all HTTP request handlers. + * @ingroup http */ +class Handler +{ +protected: + /** Hidden constructor. */ + Handler(); + +public: + /** Destructor. */ + virtual ~Handler(); + /** Needs to be implemented to accept the given request. + * If this method returns @c true, the server will call the @c handle instance next + * in expectation that this instance will process the request. */ + virtual bool match(const Request &request) = 0; + /** Needs to be implemented to process requests, which have been accepted by the @c match() + * method. */ + virtual void handle(const Request &request, Response &response) = 0; +}; + + +/** Serves some static content. + * @ingroup http */ +class StaticHandler: public Handler +{ +public: + /** Constructor. + * @param url Specifies the URL (path) of the static content. + * @param text Speciefies the content. + * @param mimeType Speficies the mime-type of the content. */ + StaticHandler(const std::string &url, const std::string &text, + const std::string mimeType="text/text"); + /** Destructor. */ + virtual ~StaticHandler(); + + bool match(const Request &request); + void handle(const Request &request, Response &response); + +protected: + /** Holds the URL (path) of the content. */ + std::string _url; + /** Holds the mime-type of the content. */ + std::string _mimeType; + /** Holds the content itself. */ + std::string _text; +}; + + +/** Utility function to provide a handler as a delegate. + * @ingroup http */ +template +class DelegateHandler: public Handler +{ +public: + /** Constructor. + * @param url Specifies the path of the handler. + * @param instance Specifies the instance of the delegate. + * @param func Specifies the method of the delegate instance being called. */ + DelegateHandler(const std::string &url, T *instance, void (T::*func)(const Request &, Response &)) + : Handler(), _url(url), _instance(instance), _callback(func) + { + // pass... + } + + bool match(const Request &request) { + return this->_url == request.url().path(); + } + + void handle(const Request &request, Response &response) { + (this->_instance->*(this->_callback))(request, response); + } + +protected: + /** Holds the path of the handler. */ + std::string _url; + /** Holds the instance of the delegate. */ + T *_instance; + /** Holds the method of the instance being called. */ + void (T::*_callback)(const Request &, Response &); +}; + + +/** Implements a specialized handler to ease the processing of JSON REST-APIs. + * @ingroup http */ +class JSONHandler: public http::Handler +{ +public: + /** Constructor. + * @param url Speficies the path of the method. */ + JSONHandler(const std::string &url); + + bool match(const http::Request &request); + void handle(const http::Request &request, http::Response &response); + + /** Needs to be implemented to process the request and assemble the result. + * An error (400 BAD REQUEST) will be send if the function does not return @c true. */ + virtual bool process(const JSON &request, JSON &result) = 0; + +protected: + /** The URL of the method. */ + std::string _url; +}; + + +/** A delegate JSON handler. + * @ingroup http */ +template +class DelegateJSONHandler: public JSONHandler +{ +public: + /** Constructor. */ + DelegateJSONHandler(const std::string &url, T *instance, + bool (T::*func)(const JSON &request, JSON &response)) + : JSONHandler(url), _instance(instance), _callback(func) + { + // pass... + } + + virtual bool process(const JSON &request, JSON &result) { + return (this->_instance->*(this->_callback))(request, result); + } + +protected: + /** The delegate instance. */ + T *_instance; + /** The method to be called from the delegate instance. */ + bool (T::*_callback)(const JSON &request, JSON &response); +}; + + +/** Implements a trivial HTTP/1.1 server. + * @ingroup httpd*/ +class Server +{ +public: + /** Constructor. + * @param port Specifies the port number to listen on. */ + Server(uint port); + /** Destructor. */ + ~Server(); + + /** Starts the server. */ + void start(bool wait=false); + /** Stops the server. */ + void stop(bool wait=false); + /** Wait for the server thread to join. */ + void wait(); + + /** Adds a generic handler to the dispatcher. */ + void addHandler(Handler *handler); + /** Adds a delegate to the dispatcher. */ + template + void addHandler(const std::string &url, + T *instance, void (T::*func)(const Request &, Response &)) { + addHandler(new DelegateHandler(url, instance, func)); + } + /** Adds a JSON delegate to the dispatcher. */ + template + void addJSON(const std::string &url, + T *instance, bool (T::*func)(const JSON &request, JSON &result)) { + addHandler(new DelegateJSONHandler(url, instance, func)); + } + /** Adds some static content to the dispatcher. */ + inline void addStatic(const std::string &url, const std::string &text) { + addHandler(new StaticHandler(url, text)); + } + /** Adds a generic handler to the dispatcher. */ + inline void addStatic(const std::string &url, const std::string &text, const std::string &mimeType) { + addHandler(new StaticHandler(url, text, mimeType)); + } + +protected: + /** Dispatches a request. */ + void dispatch(const Request &request, Response &response); + /** The thread waiting for incomming connections. */ + static void *_listen_main(void *ctx); + +protected: + /** Port to bind to. */ + uint _port; + /** The socket to listen on. */ + int _socket; + /** While true, the server is listening on the port for incomming connections. */ + bool _is_running; + /** The listen thread. */ + pthread_t _thread; + /** All registered handler. */ + std::list _handler; + /** All open connections. */ + std::set _connections; + + /* Allow Connection to access dispatch(). */ + friend class Connection; +}; + +} + +} +#endif // __SDR_HTTPD_HH__ diff --git a/src/math.hh b/src/math.hh index 51d2476..ca4f27c 100644 --- a/src/math.hh +++ b/src/math.hh @@ -29,7 +29,6 @@ template <> inline int16_t fast_atan2(uint8_t ua, uint8_t ub) /** Implementation of atan2 approximation using integers. */ template <> inline int16_t fast_atan2(int16_t a, int16_t b) { - //return (1<<15)*(std::atan2(float(a), float(b))/M_PI); const int32_t pi4 = (1<<12); const int32_t pi34 = 3*(1<<12); int32_t aabs, angle;