diff options
Diffstat (limited to 'server/src/httpd.cc')
-rw-r--r-- | server/src/httpd.cc | 369 |
1 files changed, 369 insertions, 0 deletions
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 <errno.h> +#include <stdlib.h> + +// For fork +#include <sys/types.h> +#include <unistd.h> +#include <string.h> + +#include <microhttpd.h> + +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 <curl/curl.h> + +#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*/ |