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 + +#include "base/functional.h" +#include "base/buffer.h" +#include "net/http_headers.h" +#include "thread/executor.h" + +namespace http { + +class Request { + public: + Request(int fd); + ~Request(); + + const char *resource() const { + return header_.resource; + } + + bool GetParamValue(const char *param_name, std::string *value) const { + return header_.GetParamValue(param_name, value); + } + + Buffer *in_buffer() const { return in_buffer_; } + Buffer *out_buffer() const { return out_buffer_; } + + // TODO: Remove, in favor of PartialWrite and friends. + int fd() const { return fd_; } + + void WritePartial() const; + void Write(); + void Close(); + + bool IsOK() const { return fd_ > 0; } + + // If size is negative, no Content-Length: line is written. + void WriteHttpResponseHeader(int status, int size = -1) const; + + private: + Buffer *in_buffer_; + Buffer *out_buffer_; + RequestHeader header_; + int fd_; +}; + +// Register handlers on this class to serve stuff. +class Server { + public: + Server(threading::Executor *executor); + + typedef std::function UrlHandlerFunc; + typedef std::map UrlHandlerMap; + + // Runs forever, serving request. If you want to do something else than serve pages, + // better put this on a thread. Returns false if failed to start serving, never + // returns if successful. + bool Run(int port); + + void RegisterHandler(const char *url_path, UrlHandlerFunc handler); + + // If you want to customize things at a lower level than just a simple path handler, + // then inherit and override this. Implementations should forward to HandleRequestDefault + // if they don't recognize the url. + virtual void HandleRequest(const Request &request); + + private: + void HandleConnection(int conn_fd); + + void GetRequest(Request *request); + + // Things like default 404, etc. + void HandleRequestDefault(const Request &request); + + // Neat built-in handlers that are tied to the server. + void HandleListing(const Request &request); + + int port_; + + UrlHandlerMap handlers_; + + threading::Executor *executor_; +}; + +} // namespace http + +#endif // _HTTP_SERVER_H diff --git a/thin3d/thin3d_gl.cpp b/thin3d/thin3d_gl.cpp index 08516fb19e..530a3bd9ea 100644 --- a/thin3d/thin3d_gl.cpp +++ b/thin3d/thin3d_gl.cpp @@ -285,11 +285,7 @@ public: Thin3DShader *CreateFragmentShader(const char *glsl_source, const char *hlsl_source); void SetScissorEnabled(bool enable) override { - if (enable) { - glstate.scissorTest.enable(); - } else { - glstate.scissorTest.disable(); - } + glstate.scissorTest.set(enable); } void SetScissorRect(int left, int top, int width, int height) override { diff --git a/thread/executor.cpp b/thread/executor.cpp new file mode 100644 index 0000000000..abba1fb809 --- /dev/null +++ b/thread/executor.cpp @@ -0,0 +1,10 @@ +#include "thread/executor.h" +#include "base/functional.h" + +namespace threading { + +void SameThreadExecutor::Run(std::function func) { + func(); +} + +} // namespace threading diff --git a/thread/executor.h b/thread/executor.h new file mode 100644 index 0000000000..dcc7cd8175 --- /dev/null +++ b/thread/executor.h @@ -0,0 +1,18 @@ +#pragma once + +#include "base/functional.h" + +namespace threading { + +// Stuff that can execute other stuff, like threadpools, should inherit from this. +class Executor { + public: + virtual void Run(std::function func) = 0; +}; + +class SameThreadExecutor : public Executor { + public: + virtual void Run(std::function func); +}; + +} // namespace threading