From 0c2590b3f508f0c243a7796d452987cda215bd76 Mon Sep 17 00:00:00 2001 From: Lorenz Stechauner Date: Fri, 18 May 2018 17:22:01 +0200 Subject: [PATCH] Basically Working --- Makefile | 2 +- src/Path.cpp | 71 ++++++++++++++ src/Path.h | 40 ++++++++ src/client.cpp | 206 +++++++++++++++++++++++++++++++++++++++- src/necronda-server.cpp | 125 ++++++++++++++++++++++-- src/necronda-server.h | 28 ++++++ 6 files changed, 460 insertions(+), 12 deletions(-) create mode 100644 src/Path.cpp create mode 100644 src/Path.h create mode 100644 src/necronda-server.h diff --git a/Makefile b/Makefile index 7791d24..c283ef9 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ install: @echo "Start compiling..." - g++ src/necronda-server.cpp -o bin/necronda-server -std=c++17 -fPIC + g++ src/necronda-server.cpp -o bin/necronda-server -std=c++17 -fPIC -pthread -lz -lmagic @echo "Finished compiling!" diff --git a/src/Path.cpp b/src/Path.cpp new file mode 100644 index 0000000..a55c459 --- /dev/null +++ b/src/Path.cpp @@ -0,0 +1,71 @@ + +#include "Path.h" +#include "necronda-server.h" +#include + +using namespace std; + +Path::Path(string webroot, string reqpath) { + unsigned long pos = reqpath.find('?'); + if (pos != string::npos) { + query = reqpath.substr(pos + 1, reqpath.length() - pos); + reqpath.erase(pos + 1, reqpath.length() - pos); + } + if (webroot[webroot.length() - 1] == '/') { + webroot.erase(webroot.length() - 1); + } + if (reqpath.find("/../") != string::npos) { + throw (char *) "Invalid path"; + } + if (reqpath[0] != '/') { + reqpath = '/' + reqpath; + } + this->webroot = webroot; + this->relpath = reqpath; +} + +string Path::getWebRoot() { + return webroot; +} + +string Path::getRelativePath() { + return relpath; +} + +string Path::getAbsolutePath() { + return webroot + relpath; +} + +string Path::getFilePath() { + string abs = webroot; + // TODO + return getAbsolutePath(); +} + +string Path::getRelativeFilePath() { + string rel = getRelativePath(); + // TODO +} + +string Path::getNewPath() { + string rel = getRelativeFilePath(); + // TODO + return nullptr; +} + +FILE *Path::openFile() { + return fopen64(getFilePath().c_str(), "r"); +} + +string Path::getFilePathInfo() { + return getAbsolutePath().erase(getFilePath().length(), getAbsolutePath().length()); +} + +string Path::getFileType() { + return getMimeType(getFilePath()); +} + +bool Path::isStatic() { + return true; +} + diff --git a/src/Path.h b/src/Path.h new file mode 100644 index 0000000..4389bb6 --- /dev/null +++ b/src/Path.h @@ -0,0 +1,40 @@ + +#include + +#ifndef NECRONDA_PATH +#define NECRONDA_PATH + +using namespace std; + +class Path { +private: + string webroot; + string relpath; + string query; + +public: + Path(string webroot, string reqpath); + + string getWebRoot(); + + string getRelativePath(); + + string getAbsolutePath(); + + string getFilePath(); + + string getRelativeFilePath(); + + string getNewPath(); + + FILE *openFile(); + + string getFilePathInfo(); + + string getFileType(); + + bool isStatic(); + +}; + +#endif diff --git a/src/client.cpp b/src/client.cpp index 92ad3a3..0985e04 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -1,4 +1,204 @@ -// -// Created by lorenz on 5/13/18. -// +/** + * Necronda Web Server 3.0 + * client.cpp - Client and Connection handler + * Lorenz Stechauner, 2018-05-16 + */ + + +#include +#include +#include +#include +#include "network/Socket.h" +#include "network/http/HttpRequest.h" +#include "network/http/HttpConnection.h" +#include "necronda-server.h" +#include "network/http/HttpStatusCode.h" +#include "Path.h" + + +/** + * Writes log messages to the console + * @param prefix The connection prefix + * @param string The string to be written + */ +void log(const char *prefix, const string &string) { + printf("%s%s\r\n", prefix, string.c_str()); + flush(cout); +} + +/** + * Handles (keep-alive) HTTP connections + * @param prefix The connection prefix + * @param socket The socket + * @param id The client ID + * @param num The Connection Number in the client + * @return Should the server wait for another header? + */ +bool connection_handler(const char *prefix, Socket socket, long id, long num) { + bool error = false; + try { + HttpConnection req(socket); + try { + if (req.isExistingField("Connection") && req.getField("Connection") == "keep-alive") { + req.setField("Connection", "keep-alive"); + req.setField("Keep-Alive", "timeout=30, max=200"); + } else { + req.setField("Connection", "close"); + error = true; + } + + if (!req.isExistingField("Host")) { + req.respond(400); + } else { + string host = req.getField("Host"); + + string str = string(prefix); + unsigned long pos = str.find('[', 8); + string n = str.substr(0, 8) + host + str.substr(pos - 1, str.length() - pos + 1); + + char buffer[256]; + sprintf(buffer, "%s", n.c_str()); + prefix = buffer; + + Path path = Path(getWebRoot(host), req.getPath()); + log(prefix, req.getMethod() + " " + req.getPath()); + + FILE *file = path.openFile(); + + if (file == nullptr) { + req.respond(404); + } else { + string type = path.getFileType(); + + if (type.find("inode/") == 0) { + req.respond(403); + } else { + req.setField("Content-Type", type); + req.setField("Last-Modified", getHttpDate(path.getAbsolutePath())); + + bool invalidMethod = false; + + if (path.isStatic()) { + req.setField("Accept-Ranges", "bytes"); + req.setField("Cache-Control", "public, max-age=10"); + req.setField("Allow", "GET"); + if (req.getMethod() != "GET") { + invalidMethod = true; + } + } else { + req.setField("Accept-Ranges", "none"); + req.setField("Cache-Control", "private, no-cache"); + req.setField("Allow", "GET, POST, PUT"); + if (req.getMethod() != "GET" && req.getMethod() != "POST" && req.getMethod() != "PUT") { + invalidMethod = true; + } + } + + if (invalidMethod) { + req.respond(405); + } else { + bool compress = type.find("text/") == 0 && req.isExistingField("Accept-Encoding") && + req.getField("Accept-Encoding").find("deflate") != string::npos; + req.respond(200, file, compress); + } + } + } + + HttpStatusCode status = req.getStatusCode(); + log(prefix, to_string(status.code) + " " + status.message + " (" + formatTime(req.getDuration()) + ")"); + } + } catch (char *msg) { + try { + if (msg == "timeout") { + log(prefix, "Timeout!"); + req.setField("Connection", "close"); + req.respond(408); + error = true; + } else if (msg == "Invalid path") { + log(prefix, "Timeout!"); + req.respond(400); + } else { + log(prefix, (string) "Unable to receive from socket: " + msg); + error = true; + } + } catch (char *msg2) { + + } + } + } catch (char *msg) { + try { + if (msg == "Malformed header") { + log(prefix, "Unable to parse header: Malformed header"); + socket << "HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n"; + error = true; + } else if (msg == "timeout") { + log(prefix, "Timeout!"); + socket << "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\n\r\n"; + error = true; + } else { + log(prefix, (string) "Unable to receive from socket: " + msg); + error = true; + } + } catch (char *msg2) { + + } + } + return !error; +} + +/** + * Handles HTTP clients + * @param socket The socket + * @param id The client ID + */ +void client_handler(Socket *socket, long id) { + const char *prefix; + { + char const *col1; + char const *col2 = "\x1B[0m"; + int group = (int) (id % 6); + if (group == 0) { + col1 = "\x1B[1;31m"; + } else if (group == 1) { + col1 = "\x1B[1;32m"; + } else if (group == 2) { + col1 = "\x1B[1;34m"; + } else if (group == 3) { + col1 = "\x1B[1;33m"; + } else if (group == 4) { + col1 = "\x1B[1;35m"; + } else { + col1 = "\x1B[1;36m"; + } + string *a = new string((string) + col1 + "[" + socket->getSocketAddress()->toString() + "][" + + to_string(socket->getSocketPort()) + "]" + + "[" + socket->getPeerAddress()->toString() + "][" + to_string(socket->getPeerPort()) + + "]" + col2 + " "); + prefix = a->c_str(); + } + + log(prefix, "Connection established"); + + bool err = false; + try { + socket->setReceiveTimeout(30000); + socket->setSendTimeout(30000); + } catch (char *msg) { + log(prefix, (string) "Unable to set timeout on socket: " + msg); + err = true; + } + + long reqnum = 0; + if (!err) { + while (connection_handler(prefix, *socket, id, ++reqnum)); + reqnum--; + } + + log(prefix, + "Connection terminated (#:" + to_string(reqnum) + ", R:, S:, T: " + formatTime(socket->getDuration()) + ")"); + socket->close(); +} + diff --git a/src/necronda-server.cpp b/src/necronda-server.cpp index c8036f4..fc901fe 100644 --- a/src/necronda-server.cpp +++ b/src/necronda-server.cpp @@ -5,27 +5,135 @@ */ +#include "necronda-server.h" +#include #include - -#include "network/Connection.cpp" -#include "network/http/HttpHeader.cpp" +#include +#include +#include using namespace std; +/** + * Returns UNIX time in microseconds + * @return UNIX time [µs] + */ +unsigned long getMicros() { + struct timeval tv; + gettimeofday(&tv, nullptr); + return (unsigned long) (1000000 * tv.tv_sec + tv.tv_usec); +} + + +string getMimeType(string path) { + + unsigned long pos = path.find_last_of('.'); + string ext; + if (pos != string::npos) { + ext = path.substr(pos + 1, path.length() - pos); + } + + magic_t magic = magic_open(MAGIC_MIME_TYPE); + magic_load(magic, "/usr/share/misc/magic.mgc"); + string type = magic_file(magic, path.c_str()); + magic_setflags(magic, MAGIC_MIME_ENCODING); + string charset = magic_file(magic, path.c_str()); + + if (type == "text/plain") { + if (ext == "css") { + type = "text/css"; + } else if (ext == "js") { + type = "text/javascript"; + } + } + + return type + "; charset=" + charset; +} + +/** + * Sun, 06 Nov 1994 08:49:37 GMT + * @return + */ +string getHttpDate() { + time_t rawtime; + time(&rawtime); + return getHttpDate(rawtime); +} + +string getHttpDate(string filename) { + struct stat attrib; + stat(filename.c_str(), &attrib); + return getHttpDate(attrib.st_ctime); +} + +string getHttpDate(time_t time) { + char buffer[64]; + struct tm *timeinfo = gmtime(&time); + strftime(buffer, sizeof(buffer), "%a, %d %b %Y %H:%M:%S GMT", timeinfo); + return string(buffer); +} + +/** + * Returns a formatted time string + * @param micros Delta time to be formatted + * @return A formatted time string + */ +std::string formatTime(long micros) { + char buffer[64]; + if (micros < 1000) { + sprintf(buffer, "%.3f ms", micros / 1000.0); + } else if (micros < 10000) { + sprintf(buffer, "%.2f ms", micros / 1000.0); + } else if (micros < 100000) { + sprintf(buffer, "%.1f ms", micros / 1000.0); + } else if (micros < 1000000) { + sprintf(buffer, "%.0f ms", micros / 1000.0); + } else { + sprintf(buffer, "%.1f s", micros / 1000000.0); + } + return std::string(buffer); +} + +string getWebRoot(string host) { + return "/home/lorenz/Documents/Projects/Necronda-Server/necronda-server-3.0/webroot"; +} + + +#include "network/Address.cpp" +#include "network/Socket.cpp" +#include "Path.cpp" +#include "network/http/HttpStatusCode.cpp" +#include "network/http/HttpHeader.cpp" +#include "network/http/HttpRequest.cpp" +#include "network/http/HttpResponse.cpp" +#include "network/http/HttpConnection.cpp" + +#include "client.cpp" + + +long clientnum = 0; + int main() { cout << "Necronda Server 3.0" << endl << "by Lorenz Stechauner" << endl << endl; unsigned short PORT = 8080; - Connection *s; + Socket *s; try { - s = new Connection(); + s = new Socket(); } catch (char *msg) { cout << "Unable to create socket: " << msg << endl; exit(1); } + try { + s->setReuseAddress(true); + } catch (char *msg) { + cout << "Unable to set socket option: " << msg << endl; + exit(1); + } + try { s->bind(PORT); } catch (char *msg) { @@ -42,9 +150,9 @@ int main() { while (true) { try { - Connection *client = s->accept(); - cout << client->getPeerAddress()->toString() << ":" << client->getPeerPort() << " <-> " - << client->getSocketAddress()->toString() << ":" << client->getSocketPort() << endl; + Socket *socket = s->accept(); + clientnum++; + thread *t = new thread(client_handler, socket, clientnum); } catch (char *msg) { cout << msg << endl; break; @@ -54,3 +162,4 @@ int main() { return 0; } + diff --git a/src/necronda-server.h b/src/necronda-server.h new file mode 100644 index 0000000..95c438f --- /dev/null +++ b/src/necronda-server.h @@ -0,0 +1,28 @@ +// +// Created by lorenz on 5/17/18. +// + +#include + +#ifndef NECRONDA_SERVER +#define NECRONDA_SERVER + +#define CHUNK 16384 + +using namespace std; + +unsigned long getMicros(); + +string formatTime(long micros); + +string getWebRoot(string host); + +string getMimeType(string path); + +string getHttpDate(time_t time); + +string getHttpDate(); + +string getHttpDate(string filename); + +#endif