Basically Working
This commit is contained in:
2
Makefile
2
Makefile
@ -1,4 +1,4 @@
|
|||||||
install:
|
install:
|
||||||
@echo "Start compiling..."
|
@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!"
|
@echo "Finished compiling!"
|
||||||
|
71
src/Path.cpp
Normal file
71
src/Path.cpp
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
|
40
src/Path.h
Normal file
40
src/Path.h
Normal file
@ -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
|
206
src/client.cpp
206
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,27 +5,135 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "necronda-server.h"
|
||||||
|
#include <magic.h>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <thread>
|
||||||
#include "network/Connection.cpp"
|
#include <sys/time.h>
|
||||||
#include "network/http/HttpHeader.cpp"
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
|
||||||
using namespace std;
|
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() {
|
int main() {
|
||||||
cout << "Necronda Server 3.0" << endl << "by Lorenz Stechauner" << endl << endl;
|
cout << "Necronda Server 3.0" << endl << "by Lorenz Stechauner" << endl << endl;
|
||||||
|
|
||||||
unsigned short PORT = 8080;
|
unsigned short PORT = 8080;
|
||||||
|
|
||||||
Connection *s;
|
Socket *s;
|
||||||
try {
|
try {
|
||||||
s = new Connection();
|
s = new Socket();
|
||||||
} catch (char *msg) {
|
} catch (char *msg) {
|
||||||
cout << "Unable to create socket: " << msg << endl;
|
cout << "Unable to create socket: " << msg << endl;
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
s->setReuseAddress(true);
|
||||||
|
} catch (char *msg) {
|
||||||
|
cout << "Unable to set socket option: " << msg << endl;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
s->bind(PORT);
|
s->bind(PORT);
|
||||||
} catch (char *msg) {
|
} catch (char *msg) {
|
||||||
@ -42,9 +150,9 @@ int main() {
|
|||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
Connection *client = s->accept();
|
Socket *socket = s->accept();
|
||||||
cout << client->getPeerAddress()->toString() << ":" << client->getPeerPort() << " <-> "
|
clientnum++;
|
||||||
<< client->getSocketAddress()->toString() << ":" << client->getSocketPort() << endl;
|
thread *t = new thread(client_handler, socket, clientnum);
|
||||||
} catch (char *msg) {
|
} catch (char *msg) {
|
||||||
cout << msg << endl;
|
cout << msg << endl;
|
||||||
break;
|
break;
|
||||||
@ -54,3 +162,4 @@ int main() {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
28
src/necronda-server.h
Normal file
28
src/necronda-server.h
Normal file
@ -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
|
Reference in New Issue
Block a user