From 653eb23b01c2066daccfe9f29ae1044802ef7481 Mon Sep 17 00:00:00 2001 From: deva Date: Mon, 14 Jun 2010 12:25:23 +0000 Subject: Isolated all microhttpd code in Httpd class. --- server/src/Makefile.am | 2 + server/src/connection.cc | 6 + server/src/connection.h | 2 + server/src/httpd.cc | 369 +++++++++++++++++++++++++++++++++++++++++++++++ server/src/httpd.h | 89 ++++++++++++ server/src/server.cc | 188 ++++++++---------------- 6 files changed, 525 insertions(+), 131 deletions(-) create mode 100644 server/src/httpd.cc create mode 100644 server/src/httpd.h diff --git a/server/src/Makefile.am b/server/src/Makefile.am index 942ced5..fdfeb42 100644 --- a/server/src/Makefile.am +++ b/server/src/Makefile.am @@ -22,6 +22,7 @@ pracrod_SOURCES = \ entitylist.cc \ environment.cc \ exception.cc \ + httpd.cc \ inotify.cc \ journal_commit.cc \ journalwriter.cc \ @@ -101,6 +102,7 @@ EXTRA_DIST = \ environment.h \ entitylist.h \ exception.h \ + httpd.h \ inotify.h \ journal_commit.h \ journalwriter.h \ diff --git a/server/src/connection.cc b/server/src/connection.cc index ed5a7a9..43d1ea9 100644 --- a/server/src/connection.cc +++ b/server/src/connection.cc @@ -57,6 +57,7 @@ Connection::Connection(Environment &e, std::string sid, bool c, bool d) did_commit = false; #endif + parser_complete = false; } Connection::~Connection() @@ -116,6 +117,8 @@ bool Connection::handle(const char *data, size_t size) } if(parser.parse(data, size)) { + parser_complete = true; + { SessionAutolock lock(*session); response = handleTransaction(transaction, env, *session); @@ -123,6 +126,7 @@ bool Connection::handle(const char *data, size_t size) commit(session); discard(session); + return true; } } catch(...) { @@ -136,6 +140,8 @@ bool Connection::handle(const char *data, size_t size) std::string Connection::getResponse() { + if(parser_complete == false) + return error_box(xml_encode("XML Parser need more data.")); return response; } diff --git a/server/src/connection.h b/server/src/connection.h index 61997da..f1735a8 100644 --- a/server/src/connection.h +++ b/server/src/connection.h @@ -58,6 +58,8 @@ private: TransactionParser parser; std::string response; + + bool parser_complete; }; #endif/*__PRACRO_CONNECTION_H__*/ diff --git a/server/src/httpd.cc b/server/src/httpd.cc new file mode 100644 index 0000000..9150520 --- /dev/null +++ b/server/src/httpd.cc @@ -0,0 +1,369 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set et sw=2 ts=2: */ +/*************************************************************************** + * httpd.cc + * + * Thu Jun 10 09:05:10 CEST 2010 + * Copyright 2010 Bent Bisballe Nyeng + * deva@aasimon.org + ****************************************************************************/ + +/* + * This file is part of Pracro. + * + * Pracro is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Pracro is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Pracro; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ +#include "httpd.h" + +#include +#include + +// For fork +#include +#include +#include + +#include + +typedef struct { + void *ptr; + bool firstrun; + size_t total; + size_t acc; +} connection_t; + +int hdit(void *cls, enum MHD_ValueKind kind, const char *key, const char *value) +{ + headers_t *headers = (headers_t*)cls; + + (*headers)[key] = value; + + return MHD_YES; +} + +static int request_handler(void *cls, + struct MHD_Connection *con, + const char *url, + const char *method, + const char *version, + const char *data, + unsigned int *data_size, + void **con_cls) +{ + int ret = MHD_YES; + + Httpd *httpd = (Httpd*)cls; + + if(*con_cls == NULL) { + headers_t headers; + + MHD_get_connection_values(con, MHD_HEADER_KIND, hdit, &headers); + + connection_t* c = new connection_t; + + c->firstrun = true; + c->total = 0; + if(headers.find("Content-Length") != headers.end()) { + c->total = atoi(headers["Content-Length"].c_str()); + } + c->acc = 0; + c->ptr = httpd->begin(url, method, version, headers); + + *con_cls = c; + } + + connection_t* c = (connection_t*)*con_cls; + + if(c == NULL) return MHD_NO; + + if(*data_size && data) { + if(!httpd->data(c->ptr, data, *data_size)) return MHD_NO; + } + + if(c->total == c->acc && c->firstrun == false) { + Httpd::Reply reply; + if(!httpd->complete(c->ptr, reply)) return MHD_NO; + + struct MHD_Response *rsp; + rsp = MHD_create_response_from_data(reply.data.length(), + (void*)reply.data.data(), + MHD_NO, // must free + MHD_YES); // must copy + + headers_t::iterator i = reply.headers.begin(); + while(i != reply.headers.end()) { + MHD_add_response_header(rsp, i->first.c_str(), i->second.c_str()); + i++; + } + + ret = MHD_queue_response(con, reply.status, rsp); + MHD_destroy_response(rsp); + } + + c->firstrun = false; + c->acc += *data_size; + + *data_size = 0; + return ret; +} + +static void completed_handler(void *cls, + struct MHD_Connection *con, + void **con_cls, + enum MHD_RequestTerminationCode toe) +{ + Httpd *httpd = (Httpd*)cls; + + connection_t* c = (connection_t*)*con_cls; + + if(c) { + httpd->cleanup(c->ptr); + c->ptr = NULL; + delete c; + } + + *con_cls = NULL; +} + +static void error_handler(void *cls, const char *fmt, va_list ap) +{ + Httpd *httpd = (Httpd*)cls; + char *cmsg; + int sz = vasprintf(&cmsg, fmt, ap); + std::string msg; + msg.append(cmsg, sz); + httpd->error(msg); + free(cmsg); +} + +Httpd::Httpd() +{ + d = NULL; + d_ssl = NULL; +} + +Httpd::~Httpd() +{ + stop(); + stop_ssl(); +} + +void Httpd::listen(unsigned short int port, + unsigned int cn_limit, unsigned int cn_timeout) +{ + int flags = MHD_USE_DEBUG | MHD_USE_SELECT_INTERNALLY; + + d = MHD_start_daemon(flags, port, NULL, NULL, + request_handler, this, + MHD_OPTION_NOTIFY_COMPLETED, completed_handler, this, + MHD_OPTION_EXTERNAL_LOGGER, error_handler, this, + MHD_OPTION_CONNECTION_LIMIT, cn_limit, + MHD_OPTION_CONNECTION_TIMEOUT, cn_timeout, + MHD_OPTION_END); + + if(!d) { + //PRACRO_ERR(server, "Failed to initialise MHD_start_daemon!\n"); + return; + } +} + +void Httpd::listen_ssl(unsigned short int port, + std::string key, std::string cert, + unsigned int cn_limit, unsigned int cn_timeout) +{ + int flags = MHD_USE_DEBUG | MHD_USE_SELECT_INTERNALLY | MHD_USE_SSL; + + d_ssl = MHD_start_daemon(flags, port, NULL, NULL, + request_handler, this, + MHD_OPTION_NOTIFY_COMPLETED, completed_handler, this, + MHD_OPTION_EXTERNAL_LOGGER, error_handler, this, + MHD_OPTION_CONNECTION_LIMIT, cn_limit, + MHD_OPTION_CONNECTION_TIMEOUT, cn_timeout, + MHD_OPTION_HTTPS_MEM_KEY, key.c_str(), + MHD_OPTION_HTTPS_MEM_CERT, cert.c_str(), + MHD_OPTION_END); + + if(!d_ssl) { + // PRACRO_ERR(server, "Failed to initialise MHD_start_daemon!\n"); + return; + } +} + +void Httpd::stop() +{ + if(is_running()) { + MHD_stop_daemon(d); + d = NULL; + } +} + +void Httpd::stop_ssl() +{ + if(is_running_ssl()) { + MHD_stop_daemon(d_ssl); + d_ssl = NULL; + } +} + +bool Httpd::is_running() +{ + return d != NULL; +} + +bool Httpd::is_running_ssl() +{ + return d_ssl != NULL; +} + +#ifdef TEST_HTTPD +//deps: +//cflags: $(HTTPD_CFLAGS) +//libs: $(HTTPD_LIBS) -lcurl +#include "test.h" + +#include + +#define PORT 10008 + +#define LONG_LEN 20000 + +class TestHttpd : public Httpd { +public: + TestHttpd() { fprintf(stderr, "TestHttpd()\n"); fflush(stderr); } + ~TestHttpd() { fprintf(stderr, "~TestHttpd()\n"); fflush(stderr); } + void error(const std::string &err) + { + fprintf(stderr, "ERROR: %s\n", err.c_str()); + fflush(stderr); + } + + void *begin(const std::string &url, + const std::string &method, + const std::string &version, + headers_t &headers) + { + fprintf(stderr, "begin(...)\n"); fflush(stderr); + std::string *s = new std::string; + + headers_t::iterator i = headers.begin(); + while(i != headers.end()) { + fprintf(stderr, "%s = \"%s\"\n", i->first.c_str(), i->second.c_str()); + fflush(stderr); + i++; + } + /* + if(headers.find("foo") != headers.end() && headers["foo"] == "bar") + *s = "hdrok"; + */ + return s; + } + + bool data(void *ptr, const char *data, unsigned int data_size) + { + std::string *s = (std::string*)ptr; + if(data && data_size) s->append(data, data_size); + + fprintf(stderr, "data(...) (%p +%d %d)\n", ptr, data_size, s->length()); + fflush(stderr); + + return true; + } + + bool complete(void *ptr, Httpd::Reply &reply) + { + std::string *s = (std::string*)ptr; + fprintf(stderr, "complete(...) (%p %d)\n", ptr, s->length()); + fflush(stderr); + reply.data = *s; + reply.status = MHD_HTTP_OK; + //reply.headers[MHD_HTTP_HEADER_CONTENT_TYPE] = "text/plain; charset=UTF-8"; + //reply.headers[MHD_HTTP_HEADER_CONTENT_TYPE] = "application/octet-stream"; + + return true; + } + + void cleanup(void *ptr) + { + fprintf(stderr, "cleanup(...)\n"); fflush(stderr); + + std::string *s = (std::string*)ptr; + delete s; + } +}; + +static size_t write_data(void *buffer, size_t size, size_t nmemb, void *userp) +{ + std::string *str = (std::string*)userp; + str->append((char*)buffer, size * nmemb); + return size * nmemb; +} + +static std::string send(const std::string &msg, std::string name, + std::string value, CURLcode *ret) +{ + CURL *c = curl_easy_init(); + curl_easy_setopt(c, CURLOPT_URL, "localhost"); + curl_easy_setopt(c, CURLOPT_PORT, PORT); + curl_easy_setopt(c, CURLOPT_FAILONERROR, 1L); + curl_easy_setopt(c, CURLOPT_TIMEOUT, 2L); + curl_easy_setopt(c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + curl_easy_setopt(c, CURLOPT_CONNECTTIMEOUT, 2L); + curl_easy_setopt(c, CURLOPT_NOSIGNAL, 1L); + curl_easy_setopt(c, CURLOPT_USERAGENT, "TEST_HTTPD"); + + curl_easy_setopt(c, CURLOPT_POSTFIELDSIZE, (long)msg.length()); + curl_easy_setopt(c, CURLOPT_POSTFIELDS, msg.data()); + curl_easy_setopt(c, CURLOPT_POST, 1L); + + std::string response; + curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, write_data); + curl_easy_setopt(c, CURLOPT_WRITEDATA, &response); + + struct curl_slist *slist=NULL; + slist = curl_slist_append(slist, (name + ": " + value).c_str()); + slist = curl_slist_append(slist, "Content-Type: application/octet-stream"); + + curl_easy_setopt(c, CURLOPT_HTTPHEADER, slist); + + *ret = curl_easy_perform(c); + + curl_slist_free_all(slist); + curl_easy_cleanup(c); + + return response; +} + +TEST_BEGIN; + +TestHttpd httpd; +httpd.listen(PORT); +TEST_TRUE(httpd.is_running(), "Is the server running?"); + +std::string r; + +CURLcode errornum; +r = send("hello world", "foo", "bar", &errornum); +TEST_EQUAL_INT(errornum, CURLE_OK, "Did perfom go well?"); +TEST_EQUAL_STR(r, "hello world", "Did we receive the correct answer?"); + +std::string msg; +msg.append(LONG_LEN, 0x00); +r = send(msg, "foo", "bar", &errornum); +TEST_EQUAL_INT(errornum, CURLE_OK, "Did perfom go well?"); +TEST_EQUAL(r, msg, "Did we receive the correct answer?"); + +TEST_END; + +#endif/*TEST_HTTPD*/ diff --git a/server/src/httpd.h b/server/src/httpd.h new file mode 100644 index 0000000..49d1517 --- /dev/null +++ b/server/src/httpd.h @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set et sw=2 ts=2: */ +/*************************************************************************** + * httpd.h + * + * Thu Jun 10 09:05:10 CEST 2010 + * Copyright 2010 Bent Bisballe Nyeng + * deva@aasimon.org + ****************************************************************************/ + +/* + * This file is part of Pracro. + * + * Pracro is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Pracro is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Pracro; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ +#ifndef __PRACRO_HTTPD_H__ +#define __PRACRO_HTTPD_H__ + +#include +#include + +class headers_t : public std::map< std::string, std::string > { +public: + bool contains(std::string name) { + return find(name) != end(); + } +}; + +struct MHD_Daemon; + +class Httpd { +public: + class Reply { + public: + std::string data; + headers_t headers; + int status; + }; + + Httpd(); + ~Httpd(); + + void listen(unsigned short int port, + unsigned int cn_limit = 1, unsigned int cn_timeout = 0); + void stop(); + bool is_running(); + + void listen_ssl(unsigned short int port, + std::string key, std::string cert, + unsigned int cn_limit = 1, unsigned int cn_timeout = 0); + void stop_ssl(); + bool is_running_ssl(); + + virtual void error(const std::string &err) {} + + // The retruned void pointer will be given as an argument to data, complete + // and cleanup methods. + virtual void *begin(const std::string &url, + const std::string &method, + const std::string &version, + headers_t &headers) { return NULL; } + + // Return false indicates error, and terminates connetion (no reply) + virtual bool data(void *ptr, const char *data, + unsigned int data_size) { return false; } + + // Return false indicates error, and terminates connetion (no reply) + virtual bool complete(void *ptr, Httpd::Reply &reply) { return false; } + + virtual void cleanup(void *ptr) {} + +private: + struct MHD_Daemon *d; + struct MHD_Daemon *d_ssl; +}; + +#endif/*__PRACRO_HTTPD_H__*/ diff --git a/server/src/server.cc b/server/src/server.cc index 1f85479..f8f2d83 100644 --- a/server/src/server.cc +++ b/server/src/server.cc @@ -28,168 +28,94 @@ #include -#include "tcpsocket.h" -#include - -#include - -// For fork -#include -#include -#include - -#include - +#include "httpd.h" #include "configuration.h" +#include "environment.h" #include "connection.h" -#include "log.h" -static int handle_request_callback(void *cls, - struct MHD_Connection *con, - const char *url, - const char *method, - const char *version, - const char *data, - unsigned int *data_size, - void **con_cls) -{ - int ret = MHD_YES; - - Connection *connection = (Connection*)*con_cls; +class PracroHttpd : public Httpd { +public: + PracroHttpd() {} + ~PracroHttpd() + { + env.sessions.store(); + } - PRACRO_DEBUG(httpd, "handle_request_callback con:%p condata:%p\n", - con, *con_cls); + void error(const std::string &err) + { + fprintf(stderr, "ERROR: %s\n", err.c_str()); + fflush(stderr); + } - // Test for new connection - if(connection == NULL) { + void *begin(const std::string &url, + const std::string &method, + const std::string &version, + headers_t &headers) + { std::string sessionid; - const char *sid = MHD_lookup_connection_value(con, MHD_HEADER_KIND, - "SessionID"); - if(sid) sessionid = sid; - - const char *scm = MHD_lookup_connection_value(con, MHD_HEADER_KIND, - "SessionCommit"); + if(headers.contains("SessionID")) sessionid = headers["SessionID"]; + + bool commit = headers.contains("SessionCommit"); + bool discard = headers.contains("Sessiondiscard"); - const char *sdc = MHD_lookup_connection_value(con, MHD_HEADER_KIND, - "SessionDiscard"); + Connection *connection = new Connection(env, sessionid, commit, discard); - Environment *env = (Environment *)cls; - connection = new Connection(*env, sessionid, scm != NULL, sdc != NULL); - *con_cls = connection; + return connection; } - if(!connection) return MHD_NO; - - if(connection->handle(data, *data_size)) { - std::string response = connection->getResponse(); - - PRACRO_DEBUG(httpd, "Sending response: [[%s]]\n", response.c_str()); + bool data(void *ptr, const char *data, unsigned int data_size) + { + Connection *connection = (Connection *)ptr; + connection->handle(data, data_size); + return true; + } - struct MHD_Response *rsp = - MHD_create_response_from_data(response.size(), - (void*)response.data(), - MHD_NO, // must free - MHD_YES); // must copy - - MHD_add_response_header(rsp, MHD_HTTP_HEADER_CONTENT_TYPE, - "text/plain; charset=UTF-8"); + bool complete(void *ptr, Httpd::Reply &reply) + { + Connection *connection = (Connection *)ptr; - MHD_add_response_header(rsp, "SessionID", - connection->getSessionID().c_str()); + reply.data = connection->getResponse(); + reply.headers["Content-Type"] = "text/plain; charset=UTF-8"; + reply.headers["SessionID"] = connection->getSessionID(); + reply.status = 200; // http 'OK' - ret = MHD_queue_response(con, MHD_HTTP_OK, rsp); - MHD_destroy_response(rsp); - - delete connection; - *con_cls = NULL; + return true; } - *data_size = 0; - - return ret; -} - -void requestCompletedCallback(void *cls, - struct MHD_Connection *con, - void **con_cls, - enum MHD_RequestTerminationCode toe) -{ - PRACRO_DEBUG(httpd, "requestCompletedCallback %p\n", con); - - // If connection was interrupted prematurely delete the content data here. - if(*con_cls) { - Connection *connection = (Connection*)*con_cls; + void cleanup(void *ptr) + { + Connection *connection = (Connection *)ptr; delete connection; - *con_cls = NULL; } -} -static void httpderr(void *arg, const char *fmt, va_list ap) -{ - PRACRO_ERR_VA(server, fmt, ap); -} +private: + Environment env; +}; + + extern bool pracro_is_running; void server() { - srand(time(NULL)); - - // bool forceshutdown = false; - port_t port = Conf::server_port; - - int flags = MHD_USE_DEBUG | MHD_USE_SELECT_INTERNALLY; - // | MHD_USE_PEDANTIC_CHECKS -#ifndef WITHOUT_SSL - if(Conf::use_ssl) flags |= MHD_USE_SSL; -#endif - - PRACRO_DEBUG(server, "Server running on port %d.\n", port); - - Environment env; + PracroHttpd httpd; - struct MHD_Daemon *d; - d = MHD_start_daemon(flags, port, NULL, NULL, - handle_request_callback, &env, - MHD_OPTION_NOTIFY_COMPLETED, - requestCompletedCallback, NULL, - MHD_OPTION_CONNECTION_LIMIT, Conf::connection_limit, #ifndef WITHOUT_SSL - MHD_OPTION_HTTPS_MEM_KEY, Conf::ssl_key.c_str(), - MHD_OPTION_HTTPS_MEM_CERT, Conf::ssl_cert.c_str(), + if(Conf::use_ssl) httpd.listen_ssl(Conf::server_port, + Conf::ssl_key, + Conf::ssl_cert, + Conf::connection_limit, + Conf::connection_timeout); + else #endif - MHD_OPTION_CONNECTION_TIMEOUT, Conf::connection_timeout, - MHD_OPTION_EXTERNAL_LOGGER, httpderr, NULL, - MHD_OPTION_END); + httpd.listen(Conf::server_port, + Conf::connection_limit, + Conf::connection_timeout); - if(!d) { - PRACRO_ERR(server, "Failed to initialise MHD_start_daemon!\n"); - return; - } - // again: while(pracro_is_running) sleep(1); - /* - if(!forceshutdown && env.sessions.size() != 0) { - char *errbuf; - if(asprintf(&errbuf, "There are %d live sessions." - " Kill again to force shutdown.\n", - env.sessions.size()) != -1) { - PRACRO_ERR_LOG(server, "%s", errbuf); - log(errbuf); - free(errbuf); - } - pracro_is_running = true; - forceshutdown = true; - goto again; - } - */ - env.sessions.store(); - - MHD_stop_daemon(d); PRACRO_DEBUG(server, "Server gracefully shut down.\n"); } - #ifdef TEST_SERVER #include -- cgit v1.2.3