diff --git a/Android.mk b/Android.mk
index d9d0e595e2..0bfee1bbb4 100644
--- a/Android.mk
+++ b/Android.mk
@@ -67,9 +67,12 @@ LOCAL_SRC_FILES :=\
math/lin/matrix4x4.cpp.arm \
midi/midi_input.cpp \
net/http_client.cpp \
+ net/http_server.cpp \
+ net/http_headers.cpp \
net/resolve.cpp \
net/url.cpp \
profiler/profiler.cpp \
+ thread/executor.cpp \
thread/threadutil.cpp \
thread/prioritizedworkqueue.cpp \
thread/threadpool.cpp \
diff --git a/base/stringutil.cpp b/base/stringutil.cpp
index 134a9b1dfd..848993349e 100644
--- a/base/stringutil.cpp
+++ b/base/stringutil.cpp
@@ -37,6 +37,34 @@ void OutputDebugStringUTF8(const char *p) {
#endif
+void StringTrimEndNonAlphaNum(char *str) {
+ int n = strlen(str);
+ while (!isalnum(str[n]) && n >= 0) {
+ str[n--] = '\0';
+ }
+}
+
+void SkipSpace(const char **ptr) {
+ while (**ptr && isspace(**ptr)) {
+ (*ptr)++;
+ }
+}
+
+void StringUpper(char *str) {
+ while (*str) {
+ *str = toupper(*str);
+ str++;
+ }
+}
+
+void StringUpper(char *str, int len) {
+ while (len--) {
+ *str = toupper(*str);
+ str++;
+ }
+}
+
+
unsigned int parseHex(const char *_szValue)
{
int Value = 0;
diff --git a/base/stringutil.h b/base/stringutil.h
index 24b6092096..aa6157383c 100644
--- a/base/stringutil.h
+++ b/base/stringutil.h
@@ -159,3 +159,7 @@ static std::string ValueToString(const N value)
return string.str();
}
+void StringTrimEndNonAlphaNum(char *str);
+void SkipSpace(const char **ptr);
+void StringUpper(char *str);
+void StringUpper(char *str, int len);
diff --git a/native.vcxproj b/native.vcxproj
index da563a52cd..3cd20ba3af 100644
--- a/native.vcxproj
+++ b/native.vcxproj
@@ -270,11 +270,14 @@
+
+
+
@@ -759,6 +762,8 @@
+
+
@@ -766,6 +771,7 @@
+
diff --git a/native.vcxproj.filters b/native.vcxproj.filters
index 3cc3807123..abb54be747 100644
--- a/native.vcxproj.filters
+++ b/native.vcxproj.filters
@@ -351,6 +351,15 @@
thin3d
+
+ net
+
+
+ net
+
+
+ thread
+
@@ -822,6 +831,15 @@
thin3d
+
+ net
+
+
+ net
+
+
+ thread
+
diff --git a/net/http_headers.cpp b/net/http_headers.cpp
new file mode 100644
index 0000000000..2e7103ca51
--- /dev/null
+++ b/net/http_headers.cpp
@@ -0,0 +1,164 @@
+#include "net/http_headers.h"
+
+#ifdef _WIN32
+
+#include // for timeval
+
+#endif
+
+#include
+#include
+
+#include "base/logging.h"
+#include "base/stringutil.h"
+#include "file/fd_util.h"
+
+namespace http {
+
+RequestHeader::RequestHeader()
+ : status(200), referer(0), user_agent(0),
+ resource(0), params(0), content_length(-1), first_header_(true) {
+}
+
+RequestHeader::~RequestHeader() {
+ delete [] referer;
+ delete [] user_agent;
+ delete [] resource;
+ delete [] params;
+}
+
+bool RequestHeader::GetParamValue(const char *param_name, std::string *value) const {
+ if (!params)
+ return false;
+ std::string p(params);
+ std::vector v;
+ SplitString(p, '&', v);
+ for (size_t i = 0; i < v.size(); i++) {
+ std::vector parts;
+ SplitString(v[i], '=', parts);
+ ILOG("Param: %s Value: %s", parts[0].c_str(), parts[1].c_str());
+ if (parts[0] == param_name) {
+ *value = parts[1];
+ return true;
+ }
+ }
+ return false;
+}
+
+// Intended to be a mad fast parser. It's not THAT fast currently, there's still
+// things to optimize, but meh.
+int RequestHeader::ParseHttpHeader(const char *buffer) {
+ if (first_header_) {
+ // Step 1: Method
+ first_header_ = false;
+ if (!memcmp(buffer, "GET ", 4)) {
+ method = GET;
+ buffer += 4;
+ } else if (!memcmp(buffer, "HEAD ", 5)) {
+ method = HEAD;
+ buffer += 5;
+ } else if (!memcmp(buffer, "POST ", 5)) {
+ method = POST;
+ buffer += 5;
+ } else {
+ method = UNSUPPORTED;
+ status = 501;
+ return -1;
+ }
+ SkipSpace(&buffer);
+
+ // Step 2: Resource, params (what's after the ?, if any)
+ const char *endptr = strchr(buffer, ' ');
+ const char *q_ptr = strchr(buffer, '?');
+
+ int resource_name_len;
+ if (q_ptr)
+ resource_name_len = q_ptr - buffer;
+ else
+ resource_name_len = endptr - buffer;
+ if (!resource_name_len) {
+ status = 400;
+ return -1;
+ }
+ resource = new char[resource_name_len + 1];
+ memcpy(resource, buffer, resource_name_len);
+ resource[resource_name_len] = '\0';
+ if (q_ptr) {
+ int param_length = endptr - q_ptr - 1;
+ params = new char[param_length + 1];
+ memcpy(params, q_ptr + 1, param_length);
+ params[param_length] = '\0';
+ }
+ if (strstr(buffer, "HTTP/"))
+ type = FULL;
+ else
+ type = SIMPLE;
+ return 0;
+ }
+
+ // We have a real header to parse.
+ const char *colon = strchr(buffer, ':');
+ if (!colon) {
+ status = 400;
+ return -1;
+ }
+
+ // The header is formatted as key: value.
+ int key_len = colon - buffer;
+ char *key = new char[key_len + 1];
+ strncpy(key, buffer, key_len);
+ key[key_len] = 0;
+ StringUpper(key, key_len);
+
+ // Go to after the colon to get the value.
+ buffer = colon + 1;
+ SkipSpace(&buffer);
+ int value_len = strlen(buffer);
+
+ if (!strcmp(key, "USER-AGENT")) {
+ user_agent = new char[value_len + 1];
+ memcpy(user_agent, buffer, value_len + 1);
+ ILOG("user-agent: %s", user_agent);
+ } else if (!strcmp(key, "REFERER")) {
+ referer = new char[value_len + 1];
+ memcpy(referer, buffer, value_len + 1);
+ } else if (!strcmp(key, "CONTENT-LENGTH")) {
+ content_length = atoi(buffer);
+ ILOG("Content-Length: %i", (int)content_length);
+ }
+
+ delete [] key;
+}
+
+void RequestHeader::ParseHeaders(int fd) {
+ // Get the request, with a timeout.
+ struct ::timeval tv;
+ tv.tv_sec = 5;
+ tv.tv_usec = 0;
+
+ int line_count = 0;
+ // Loop through request headers.
+ while (true) {
+ if (!fd_util::WaitUntilReady(fd, 5.0)) { // Wait max 5 secs.
+ // Timed out or error.
+ ok = false;
+ return;
+ }
+ char buffer[1024];
+ fd_util::ReadLine(fd, buffer, 1023);
+ StringTrimEndNonAlphaNum(buffer);
+ if (buffer[0] == '\0')
+ break;
+ ParseHttpHeader(buffer);
+ line_count++;
+ if (type == SIMPLE) {
+ // Done!
+ ILOG("Simple: Done parsing http request.");
+ break;
+ }
+ }
+ ILOG("finished parsing request.");
+ ok = line_count > 1;
+}
+
+} // namespace http
diff --git a/net/http_headers.h b/net/http_headers.h
new file mode 100644
index 0000000000..d333e1f91a
--- /dev/null
+++ b/net/http_headers.h
@@ -0,0 +1,43 @@
+#ifndef _NET_HTTP_HTTP_HEADERS
+#define _NET_HTTP_HTTP_HEADERS
+
+#include "base/buffer.h"
+
+namespace http {
+
+class RequestHeader {
+ public:
+ RequestHeader();
+ ~RequestHeader();
+ // Public variables since it doesn't make sense
+ // to bother with accessors for all these.
+ int status;
+ char *referer;
+ char *user_agent;
+ char *resource;
+ char *params;
+ int content_length;
+ enum RequestType {
+ SIMPLE, FULL,
+ };
+ RequestType type;
+ enum Method {
+ GET,
+ HEAD,
+ POST,
+ UNSUPPORTED,
+ };
+ Method method;
+ bool ok;
+ void ParseHeaders(int fd);
+ bool GetParamValue(const char *param_name, std::string *value) const;
+ private:
+ int ParseHttpHeader(const char *buffer);
+ bool first_header_;
+
+ DISALLOW_COPY_AND_ASSIGN(RequestHeader);
+};
+
+} // namespace http
+
+#endif
diff --git a/net/http_server.cpp b/net/http_server.cpp
new file mode 100644
index 0000000000..a12b9cc5cf
--- /dev/null
+++ b/net/http_server.cpp
@@ -0,0 +1,167 @@
+#include "base/timeutil.h"
+
+#ifdef _WIN32
+
+#include
+#include
+#include
+
+#else
+
+#include /* socket definitions */
+#include /* socket types */
+#include /* for waitpid() */
+#include /* inet (3) funtions */
+#include /* misc. UNIX functions */
+
+#endif
+
+#include
+#include
+
+#include "base/functional.h"
+#include "base/logging.h"
+#include "base/buffer.h"
+#include "file/fd_util.h"
+#include "net/http_server.h"
+
+namespace http {
+
+Request::Request(int fd)
+ : fd_(fd) {
+ in_buffer_ = new Buffer;
+ out_buffer_ = new Buffer;
+ header_.ParseHeaders(fd_);
+
+ if (header_.ok) {
+ // Read the rest, too.
+ if (header_.content_length >= 0) {
+ in_buffer_->Read(fd_, header_.content_length);
+ }
+ ILOG("The request carried with it %i bytes", (int)in_buffer_->size());
+ } else {
+ Close();
+ }
+}
+
+Request::~Request() {
+ Close();
+
+ CHECK(in_buffer_->empty());
+ delete in_buffer_;
+ CHECK(out_buffer_->empty());
+ delete out_buffer_;
+}
+
+void Request::WriteHttpResponseHeader(int status, int size) const {
+ Buffer *buffer = out_buffer_;
+ buffer->Printf("HTTP/1.0 %d OK\r\n", status);
+ buffer->Append("Server: SuperDuperServer v0.1\r\n");
+ buffer->Append("Content-Type: text/html\r\n");
+ if (size >= 0) {
+ buffer->Printf("Content-Length: %i\r\n", size);
+ }
+ buffer->Append("\r\n");
+}
+
+void Request::WritePartial() const {
+ CHECK(fd_);
+ out_buffer_->Flush(fd_);
+}
+
+void Request::Write() {
+ CHECK(fd_);
+ WritePartial();
+ Close();
+}
+
+void Request::Close() {
+ if (fd_) {
+ close(fd_);
+ fd_ = 0;
+ }
+}
+
+Server::Server(threading::Executor *executor)
+ : port_(0), executor_(executor) {
+ RegisterHandler("/", std::bind(&Server::HandleListing, this, placeholder::_1));
+}
+
+void Server::RegisterHandler(const char *url_path, UrlHandlerFunc handler) {
+ handlers_[std::string(url_path)] = handler;
+}
+
+bool Server::Run(int port) {
+ ILOG("HTTP server started on port %i", port);
+ port_ = port;
+
+ int listener = socket(AF_INET, SOCK_STREAM, 0);
+ CHECK_GE(listener, 0);
+
+ struct sockaddr_in server_addr;
+ memset(&server_addr, 0, sizeof(server_addr));
+ server_addr.sin_family = AF_INET;
+ server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
+ server_addr.sin_port = htons(port);
+
+ int opt = 1;
+ // Enable re-binding to avoid the pain when restarting the server quickly.
+ setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt));
+
+ if (bind(listener, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
+ ELOG("Failed to bind to port %i. Bailing.", port);
+ return false;
+ }
+
+ // 1024 is the max number of queued requests.
+ CHECK_GE(listen(listener, 1024), 0);
+ while (true) {
+ sockaddr client_addr;
+ socklen_t client_addr_size = sizeof(client_addr);
+ int conn_fd = accept(listener, &client_addr, &client_addr_size);
+ if (conn_fd >= 0) {
+ executor_->Run(std::bind(&Server::HandleConnection, this, conn_fd));
+ } else {
+ FLOG("socket accept failed: %i", conn_fd);
+ }
+ }
+
+ // We'll never get here. Ever.
+ return true;
+}
+
+void Server::HandleConnection(int conn_fd) {
+ Request request(conn_fd);
+ if (!request.IsOK()) {
+ WLOG("Bad request, ignoring.");
+ return;
+ }
+ HandleRequestDefault(request);
+ request.WritePartial();
+}
+
+void Server::HandleRequest(const Request &request) {
+ HandleRequestDefault(request);
+}
+
+void Server::HandleRequestDefault(const Request &request) {
+ // First, look through all handlers. If we got one, use it.
+ for (auto iter = handlers_.begin(); iter != handlers_.end(); ++iter) {
+ if (iter->first == request.resource()) {
+ (iter->second)(request);
+ return;
+ }
+ }
+ ILOG("No handler for '%s', falling back to 404.", request.resource());
+ const char *payload = "404 not found\r\n";
+ request.WriteHttpResponseHeader(404, strlen(payload));
+ request.out_buffer()->Append(payload);
+}
+
+void Server::HandleListing(const Request &request) {
+ for (auto iter = handlers_.begin(); iter != handlers_.end(); ++iter) {
+ request.out_buffer()->Printf("%s", iter->first.c_str());
+ }
+}
+
+} // namespace http
diff --git a/net/http_server.h b/net/http_server.h
new file mode 100644
index 0000000000..5d45722671
--- /dev/null
+++ b/net/http_server.h
@@ -0,0 +1,88 @@
+#ifndef _HTTP_SERVER_H
+#define _HTTP_SERVER_H
+
+#include