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 <utility>
+
+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 <iostream>
+
+#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 <string>
+#include <iostream>
+#include <zlib.h>
+#include <cassert>
+#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 <magic.h>
 #include <iostream>
-
-#include "network/Connection.cpp"
-#include "network/http/HttpHeader.cpp"
+#include <thread>
+#include <sys/time.h>
+#include <sys/stat.h>
 
 
 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 <string>
+
+#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