summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPixel <pixel@nobis-crew.org>2011-11-17 18:41:23 -0800
committerPixel <pixel@nobis-crew.org>2011-11-17 18:41:51 -0800
commit1b7a57d7f88cab0a1968e8c886eac3629dc74617 (patch)
tree3d60366ee3caa2ec3061a42abd2a61c78a1fa5d2
parente617d26a9291c69988321a812dc5e1f3578e743b (diff)
HTTP server's first real test, alongside multiple taskmanager threads.
I'm not really sure I fully like the way I'm designing this, but I guess it could be solved with an HTTP/HTML helper class around the Action class. However, the HTTP server awfully need reference counting, so it doesn't go away before all of the workers disappear, which means a bit of a redesign of the Listener template.
-rw-r--r--Makefile1
-rw-r--r--includes/BRegex.h6
-rw-r--r--includes/HttpServer.h33
-rw-r--r--includes/TaskMan.h1
-rw-r--r--src/BRegex.cc2
-rw-r--r--src/HttpServer.cc142
-rw-r--r--src/TaskMan.cc23
-rw-r--r--tests/test-Http.cc62
8 files changed, 233 insertions, 37 deletions
diff --git a/Makefile b/Makefile
index e836703..f4ee968 100644
--- a/Makefile
+++ b/Makefile
@@ -169,6 +169,7 @@ test-Tasks.cc \
test-Threads.cc \
test-Handles.cc \
test-Sockets.cc \
+test-Http.cc \
test-Lua.cc \
test-Regex.cc \
diff --git a/includes/BRegex.h b/includes/BRegex.h
index 128597e..d7957bd 100644
--- a/includes/BRegex.h
+++ b/includes/BRegex.h
@@ -19,4 +19,10 @@ class Regex {
regex_t m_regex;
};
+class Regexes {
+ public:
+ static Regex any;
+ static Regex empty;
+};
+
};
diff --git a/includes/HttpServer.h b/includes/HttpServer.h
index 7ae0dc1..2226d23 100644
--- a/includes/HttpServer.h
+++ b/includes/HttpServer.h
@@ -1,7 +1,14 @@
#pragma once
+#include <map>
+#include <list>
+
+#include <Atomic.h>
#include <BString.h>
+#include <BRegex.h>
#include <Exceptions.h>
+#include <Threads.h>
+#include <Handle.h>
namespace Balau {
@@ -9,17 +16,43 @@ class HttpWorker;
class HttpServer {
public:
+
+ typedef std::map<String, String> StringMap;
+ typedef std::map<String, IO<Handle> > FileList;
+
+ class Action {
+ public:
+ Action(Regex & regex, Regex & host = Regexes::any) : m_regex(regex), m_host(host), m_refCount(0) { }
+ ~Action() { Assert(m_refCount == 0); }
+ typedef std::pair<Regex::Captures, Regex::Captures> ActionMatches;
+ ActionMatches matches(const char * uri, const char * host);
+ void unref() { if (Atomic::Prefetch::Decrement(&m_refCount) == 0) delete this; }
+ void ref() { Atomic::Increment(&m_refCount); }
+ void registerMe(HttpServer * server) { server->registerAction(this); }
+ virtual bool Do(HttpServer * server, ActionMatches & m, IO<Handle> out, StringMap & vars, StringMap & headers, FileList & files) = 0;
+ private:
+ Regex m_regex, m_host;
+ volatile int m_refCount;
+ };
+
HttpServer() : m_started(false), m_listenerPtr(NULL), m_port(80) { }
~HttpServer() { if (!m_started) stop(); }
void start();
void stop();
void setPort(int port) { Assert(!m_started); m_port = port; }
void setLocal(const char * local) { Assert(!m_started); m_local = local; }
+ void registerAction(Action * action);
+ void flushAllActions();
+ typedef std::pair<Action *, Action::ActionMatches> ActionFound;
+ ActionFound findAction(const char * uri, const char * host);
private:
bool m_started;
void * m_listenerPtr;
int m_port;
String m_local;
+ typedef std::list<Action *> ActionList;
+ ActionList m_actions;
+ Lock m_actionsLock;
friend class HttpWorker;
};
diff --git a/includes/TaskMan.h b/includes/TaskMan.h
index c63695a..026ea88 100644
--- a/includes/TaskMan.h
+++ b/includes/TaskMan.h
@@ -33,6 +33,7 @@ class TaskMan {
void signalTask(Task * t);
static void stop();
void stopMe() { m_stopped = true; }
+ static Thread * createThreadedTaskMan();
private:
static void registerTask(Task * t, Task * stick);
void * getStack();
diff --git a/src/BRegex.cc b/src/BRegex.cc
index a7b37b0..d3a36f7 100644
--- a/src/BRegex.cc
+++ b/src/BRegex.cc
@@ -47,3 +47,5 @@ Balau::String Balau::Regex::getError(int err) {
return r;
}
+Balau::Regex Balau::Regexes::any(".*");
+Balau::Regex Balau::Regexes::empty("^$");
diff --git a/src/HttpServer.cc b/src/HttpServer.cc
index b027824..c57c63d 100644
--- a/src/HttpServer.cc
+++ b/src/HttpServer.cc
@@ -1,5 +1,3 @@
-#include <map>
-
#include "Http.h"
#include "HttpServer.h"
#include "Socket.h"
@@ -10,8 +8,6 @@ static const ev_tstamp s_httpTimeout = 15;
namespace Balau {
-typedef std::map<String, String> StringMap;
-
class HttpWorker : public Task {
public:
HttpWorker(IO<Handle> io, void * server);
@@ -21,21 +17,23 @@ class HttpWorker : public Task {
virtual const char * getName();
bool handleClient();
- void send400(Events::BaseEvent * evt);
+ void send400();
+ void send404();
String httpUnescape(const char * in);
- void readVariables(StringMap & variables, char * str);
+ void readVariables(HttpServer::StringMap & variables, char * str);
IO<Handle> m_socket;
IO<BStream> m_strm;
String m_name;
+ HttpServer * m_server;
};
};
Balau::HttpWorker::HttpWorker(IO<Handle> io, void * _server) : m_socket(new WriteOnly(io)), m_strm(new BStream(io)) {
- HttpServer * server = (HttpServer *) _server;
+ m_server = (HttpServer *) _server;
m_name.set("HttpWorker(%s)", m_socket->getName());
- // copy stuff from server, such as port number, root document, base URL, etc...
+ // get stuff from server, such as port number, root document, base URL, default 400/404 actions, etc...
}
Balau::HttpWorker::~HttpWorker() {
@@ -72,7 +70,7 @@ Balau::String Balau::HttpWorker::httpUnescape(const char * in) {
return r;
}
-void Balau::HttpWorker::readVariables(StringMap & variables, char * str) {
+void Balau::HttpWorker::readVariables(HttpServer::StringMap & variables, char * str) {
char * ampPos;
do {
ampPos = strchr(str, '&');
@@ -92,7 +90,7 @@ void Balau::HttpWorker::readVariables(StringMap & variables, char * str) {
} while (ampPos);
}
-void Balau::HttpWorker::send400(Events::BaseEvent * evt) {
+void Balau::HttpWorker::send400() {
static const char str[] =
"HTTP/1.0 400 Bad Request\r\n"
"Content-Type: text/html; charset=UTF-8\r\n"
@@ -111,8 +109,29 @@ void Balau::HttpWorker::send400(Events::BaseEvent * evt) {
" </body>\n"
"</html>\n";
- setOkayToEAgain(false);
- m_socket->forceWrite(str, sizeof(str) - 1, evt);
+ m_socket->forceWrite(str, sizeof(str) - 1);
+ Balau::Printer::elog(Balau::E_HTTPSERVER, "%s had an invalid request", m_name.to_charp());
+}
+
+void Balau::HttpWorker::send404() {
+ static const char str[] =
+"HTTP/1.1 404 Not Found\r\n"
+"Content-Type: text/html; charset=UTF-8\r\n"
+"Server: " DAEMON_NAME "\r\n"
+"\r\n"
+"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
+"\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
+"<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
+" <head>\n"
+" <title>404 Not Found</title>\n"
+" </head>\n"
+"\n"
+" <body>\n"
+" The HTTP request you've sent didn't match any action on this server.\n"
+" </body>\n"
+"</html>\n";
+
+ m_socket->forceWrite(str, sizeof(str) - 1);
Balau::Printer::elog(Balau::E_HTTPSERVER, "%s had an invalid request", m_name.to_charp());
}
@@ -127,8 +146,9 @@ bool Balau::HttpWorker::handleClient() {
String host;
String uri;
String httpVersion;
- StringMap httpHeaders;
- StringMap variables;
+ HttpServer::StringMap httpHeaders;
+ HttpServer::StringMap variables;
+ HttpServer::FileList files;
bool persistent = false;
// read client's request
@@ -168,14 +188,14 @@ bool Balau::HttpWorker::handleClient() {
break;
}
if (urlBegin == 0) {
- send400(&evtTimeout);
+ send400();
return false;
}
int urlEnd = line.strrchr(' ') - 1;
if (urlEnd < urlBegin) {
- send400(&evtTimeout);
+ send400();
return false;
}
@@ -184,7 +204,7 @@ bool Balau::HttpWorker::handleClient() {
int httpBegin = urlEnd + 2;
if ((httpBegin + 5) >= line.strlen()) {
- send400(&evtTimeout);
+ send400();
return false;
}
@@ -195,19 +215,19 @@ bool Balau::HttpWorker::handleClient() {
(line[httpBegin + 4] == '/')) {
httpVersion = line.extract(httpBegin + 5);
} else {
- send400(&evtTimeout);
+ send400();
return false;
}
if ((httpVersion != "1.0") && (httpVersion != "1.1")) {
- send400(&evtTimeout);
+ send400();
return false;
}
} else {
// parse HTTP header.
int colon = line.strchr(':');
if (colon <= 0) {
- send400(&evtTimeout);
+ send400();
return false;
}
@@ -221,16 +241,16 @@ bool Balau::HttpWorker::handleClient() {
} while(true);
if (!gotFirst) {
- send400(&evtTimeout);
+ send400();
return false;
}
if (httpVersion == "1.1") {
- StringMap::iterator i = httpHeaders.find("Connection");
+ HttpServer::StringMap::iterator i = httpHeaders.find("Connection");
if (i != httpHeaders.end()) {
if (i->second != "close") {
- send400(&evtTimeout);
+ send400();
return false;
}
} else {
@@ -240,7 +260,7 @@ bool Balau::HttpWorker::handleClient() {
if (method == Http::POST) {
int length = 0;
- StringMap::iterator i;
+ HttpServer::StringMap::iterator i;
bool multipart = false;
String boundary;
@@ -255,10 +275,10 @@ bool Balau::HttpWorker::handleClient() {
static const String multipartStr = "multipart/form-data";
if (i->second.extract(multipartStr.strlen()) == multipartStr) {
if (i->second[multipartStr.strlen() + 1] != ';') {
- send400(&evtTimeout);
+ send400();
return false;
}
- StringMap t;
+ HttpServer::StringMap t;
char * b = i->second.extract(sizeof(multipartStr) + 1).trim().strdup();
readVariables(t, b);
free(b);
@@ -266,7 +286,7 @@ bool Balau::HttpWorker::handleClient() {
i = t.find("boundary");
if (i == t.end()) {
- send400(&evtTimeout);
+ send400();
return false;
}
@@ -319,11 +339,11 @@ bool Balau::HttpWorker::handleClient() {
}
}
- StringMap::iterator hostIter = httpHeaders.find("host");
+ HttpServer::StringMap::iterator hostIter = httpHeaders.find("host");
if (hostIter != httpHeaders.end()) {
if (host != "") {
- send400(&evtTimeout);
+ send400();
return false;
}
@@ -332,10 +352,16 @@ bool Balau::HttpWorker::handleClient() {
// process query; everything should be here now
-
+ HttpServer::ActionFound f = m_server->findAction(uri.to_charp(), host.to_charp());
+ if (f.first) {
+ if (!f.first->Do(m_server, f.second, m_socket, variables, httpHeaders, files)) {
+ persistent = false;
+ }
+ } else {
+ send404();
+ }
// query process finished; wrapping up and exiting.
-
return persistent;
}
@@ -354,7 +380,7 @@ typedef Balau::Listener<Balau::HttpWorker> HttpListener;
void Balau::HttpServer::start() {
Assert(!m_started);
- m_listenerPtr = createTask(new HttpListener(m_port, m_local.to_charp()));
+ m_listenerPtr = createTask(new HttpListener(m_port, m_local.to_charp(), this));
m_started = true;
}
@@ -363,3 +389,55 @@ void Balau::HttpServer::stop() {
reinterpret_cast<HttpListener *>(m_listenerPtr)->stop();
m_started = false;
}
+
+void Balau::HttpServer::registerAction(Action * action) {
+ m_actionsLock.enter();
+ action->ref();
+ m_actions.push_front(action);
+ m_actionsLock.leave();
+}
+
+void Balau::HttpServer::flushAllActions() {
+ m_actionsLock.enter();
+ Action * a;
+ while (!m_actions.empty()) {
+ a = m_actions.front();
+ m_actions.pop_front();
+ a->unref();
+ }
+ m_actionsLock.leave();
+}
+
+Balau::HttpServer::Action::ActionMatches Balau::HttpServer::Action::matches(const char * uri, const char * host) {
+ ActionMatches r;
+
+ r.second = m_host.match(host);
+ if (r.second.empty())
+ return r;
+
+ r.first = m_regex.match(uri);
+ return r;
+}
+
+Balau::HttpServer::ActionFound Balau::HttpServer::findAction(const char * uri, const char * host) {
+ m_actionsLock.enter();
+
+ ActionList::iterator i;
+ ActionFound r;
+
+ for (i = m_actions.begin(); i != m_actions.end(); i++) {
+ r.first = *i;
+ r.second = r.first->matches(uri, host);
+ if (!r.second.first.empty())
+ break;
+ }
+
+ if (r.second.first.empty())
+ r.first = NULL;
+ else
+ r.first->ref();
+
+ m_actionsLock.leave();
+
+ return r;
+}
diff --git a/src/TaskMan.cc b/src/TaskMan.cc
index 0980a3b..73bce5c 100644
--- a/src/TaskMan.cc
+++ b/src/TaskMan.cc
@@ -206,7 +206,7 @@ void Balau::TaskMan::mainLoop() {
Task * t;
bool noWait = false;
- Printer::elog(E_TASK, "TaskMan::mainLoop() with m_tasks.size = %i", m_tasks.size());
+ Printer::elog(E_TASK, "TaskMan::mainLoop() at %p with m_tasks.size = %i", this, m_tasks.size());
// checking "STARTING" tasks, and running them once; also try to build the status of the noWait boolean.
for (iH = m_tasks.begin(); iH != m_tasks.end(); iH++) {
@@ -226,9 +226,9 @@ void Balau::TaskMan::mainLoop() {
// libev's event "loop". We always runs it once though.
m_allowedToSignal = true;
- Printer::elog(E_TASK, "Going to libev main loop");
+ Printer::elog(E_TASK, "TaskMan at %p Going to libev main loop", this);
ev_run(m_loop, noWait || m_stopped ? EVRUN_NOWAIT : EVRUN_ONCE);
- Printer::elog(E_TASK, "Getting out of libev main loop");
+ Printer::elog(E_TASK, "TaskMan at %p Getting out of libev main loop", this);
// let's check what task got stopped, and signal them
for (iH = m_tasks.begin(); iH != m_tasks.end(); iH++) {
@@ -247,7 +247,7 @@ void Balau::TaskMan::mainLoop() {
// let's check who got signaled, and call them
for (iH = m_signaledTasks.begin(); iH != m_signaledTasks.end(); iH++) {
t = *iH;
- Printer::elog(E_TASK, "Switching to task %p (%s - %s) that got signaled somehow.", t, t->getName(), ClassName(t).c_str());
+ Printer::elog(E_TASK, "TaskMan at %p Switching to task %p (%s - %s) that got signaled somehow.", this, t, t->getName(), ClassName(t).c_str());
Assert(t->getStatus() == Task::IDLE);
t->switchTo();
}
@@ -279,7 +279,7 @@ void Balau::TaskMan::mainLoop() {
} while (didDelete);
} while (!m_stopped);
- Printer::elog(E_TASK, "TaskManager stopping.");
+ Printer::elog(E_TASK, "TaskManager at %p stopping.", this);
}
void Balau::TaskMan::registerTask(Balau::Task * t, Balau::Task * stick) {
@@ -305,3 +305,16 @@ void Balau::TaskMan::signalTask(Task * t) {
void Balau::TaskMan::stop() {
s_scheduler.stopAll();
}
+
+class ThreadedTaskMan : public Balau::Thread {
+ virtual void * proc() {
+ m_taskMan = new Balau::TaskMan();
+ m_taskMan->mainLoop();
+ }
+ Balau::TaskMan * m_taskMan;
+};
+
+Balau::Thread * Balau::TaskMan::createThreadedTaskMan() {
+ Thread * r = new ThreadedTaskMan();
+ r->threadStart();
+}
diff --git a/tests/test-Http.cc b/tests/test-Http.cc
new file mode 100644
index 0000000..496d7e6
--- /dev/null
+++ b/tests/test-Http.cc
@@ -0,0 +1,62 @@
+#include <Main.h>
+#include <HttpServer.h>
+
+BALAU_STARTUP;
+
+#define DAEMON_NAME "Balau/1.0"
+
+using namespace Balau;
+
+class TestAction : public HttpServer::Action {
+ public:
+ TestAction() : Action(Regexes::any) { }
+ virtual bool Do(HttpServer * server, HttpServer::Action::ActionMatches & m, IO<Handle> out, HttpServer::StringMap & vars, HttpServer::StringMap & headers, HttpServer::FileList & files);
+};
+
+bool TestAction::Do(HttpServer * server, HttpServer::Action::ActionMatches & m, IO<Handle> out, HttpServer::StringMap & vars, HttpServer::StringMap & headers, HttpServer::FileList & files) {
+ static const char str[] =
+"HTTP/1.1 200 Found\r\n"
+"Content-Type: text/html; charset=UTF-8\r\n"
+"Content-Length: 266\r\n"
+"Server: " DAEMON_NAME "\r\n"
+"\r\n"
+"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
+"\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
+"<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
+" <head>\n"
+" <title>Test</title>\n"
+" </head>\n"
+"\n"
+" <body>\n"
+" This is a test document.\n"
+" </body>\n"
+"</html>\n";
+
+ out->forceWrite(str, sizeof(str) - 1);
+ return true;
+}
+
+void MainTask::Do() {
+ Printer::log(M_STATUS, "Test::Http running.");
+
+ Thread * tms[4];
+
+ for (int i = 0; i < 4; i++)
+ tms[i] = TaskMan::createThreadedTaskMan();
+
+ HttpServer * s = new HttpServer();
+ TestAction * a = new TestAction();
+ a->registerMe(s);
+ s->setPort(8080);
+ s->setLocal("localhost");
+ s->start();
+
+ yield();
+
+ s->stop();
+
+ for (int i = 0; i < 4; i++)
+ tms[i]->join();
+
+ Printer::log(M_STATUS, "Test::Http passed.");
+}