Initial commit

This commit is contained in:
2019-06-15 12:00:21 +02:00
commit eda5d9df6b
31 changed files with 1328 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
.vscode
bin/www/
build/
bin/*.out
src/*.o
src/*.d

35
bin/server.cfg Executable file
View File

@@ -0,0 +1,35 @@
# Configuration file for HTTPSoup
# Format:
# key: value (space is optional)
server-port: 80
www-root: /home/tijmen/project/http-server/bin/www/
# List of supported content per MIME type
# Supported MIME types are: text, image, audio, video, application and multipart
# Unknown types are usually shoved under application
type: text
css
html
htm
js
php
type: image
bmp
gif
jpg
png
type: audio
flac
mp3
ogg
type: video
avi
mp4
type: application
type: multipart

37
makefile Normal file
View File

@@ -0,0 +1,37 @@
CC = g++
CFLAGS = -std=c++17 -Wall -g
LFLAGS = -lstdc++fs
CPPS = $(shell find ./src/ -name *.cpp)
OBJS = $(patsubst ./src/%.cpp, ./build/%.o, ${CPPS})
DEPS = $(patsubst %.o, %.d, $(OBJS))
BUILDDIRS = $(patsubst ./src/%, ./build/%, $(shell find ./src/ -type d))
BINARY_NAME = server.out
BINARY_OUT = ./build/${BINARY_NAME}
-include $(DEPS)
./build/%.o: ./src/%.cpp
${CC} ${CFLAGS} -MMD -c $< -o $@
${BINARY_OUT}: directories ${OBJS}
${CC} ${CFLAGS} ${OBJS} ${LFLAGS} -o $@
.PHONY: all clean check syntax directories
all: ${BINARY_OUT}
clean:
-rm -r ./build/
check: ${BINARY_OUT}
cp -uv $^ ./bin/
./bin/${BINARY_NAME}
syntax: ${CPPS}
${CC} ${CFLAGS} -fsyntax-only $^
directories:
mkdir -p ${BUILDDIRS}

View File

@@ -0,0 +1,111 @@
#pragma once
#include <string>
#include <vector>
namespace HttpRequest
{
enum class Type
{
UNKNOWN = -1,
GET,
HEAD,
POST,
PUT,
DELETE,
TRACE,
OPTIONS,
CONNECT,
PATCH
};
const std::vector<std::string> typeStrings = {
"GET",
"HEAD",
"POST",
"PUT",
"DELETE",
"TRACE",
"OPTIONS",
"CONNECT",
"PATCH",
};
enum class Header
{
UNKNOWN = -1,
// https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
ACCEPT,
ACCEPT_CHARSET,
ACCEPT_ENCODING,
ACCEPT_LANGUAGE,
ACCEPT_DATETIME,
ACCESS_CONTROL_REQUEST_METHOD,
ACCESS_CONTROL_REQUEST_HEADERS,
AUTHORIZATION,
CACHE_CONTROL,
CONNECTION,
COOKIE,
CONTENT_LENGTH,
CONTENT_MD5, // obsolete
CONTENT_TYPE,
DATE,
EXPECT,
FORWARDED,
FROM,
HOST,
IF_MATCH,
IF_MODIFIED_SINCE,
IF_NONE_MATCH,
IF_RANGE,
IF_UNMODIFIED_SINCE,
MAX_FORWARDS,
ORIGIN,
PRAGMA,
PROXY_AUTHORIZATION,
RANGE,
REFERER,
TE, // transfer encoding
USER_AGENT,
UPGRADE,
VIA,
WARNING
};
const std::vector<std::string> headerStrings = {
"Accept",
"Accept-Charset",
"Accept-Encoding",
"Accept-Language",
"Accept-Datetime",
"Access-Control-Request-Method",
"Access-Control-Request-Headers",
"Authorization",
"Cache-Control",
"Connection",
"Cookie",
"Content-Length",
"Content-MD5",
"Content-Type",
"Date",
"Expect",
"Forwarded",
"From",
"Host",
"If-Match",
"If-Modified-Since",
"If-None-Match",
"If-Range",
"If-Unmodified-Since",
"Max-Forwards",
"Origin",
"Pragma",
"Proxy-Authorization",
"Range",
"Referer",
"TE",
"User-Agent",
"Upgrade",
"Via",
"Warning",
};
}

View File

@@ -0,0 +1,308 @@
#pragma once
#include <string>
#include <vector>
namespace HttpResponse
{
enum class CodeRange
{
// https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
INFORMATIONAL = 100,
SUCCESS = 200,
REDIRECTION = 300,
CLIENT_ERROR = 400,
SERVER_ERROR = 500
};
const std::vector<int> codeValues
{
100,
101,
102,
200,
201,
202,
203,
204,
205,
206,
207,
208,
226,
300,
301,
302,
303,
304,
305,
306,
307,
308,
400,
401,
402,
403,
404,
405,
406,
407,
408,
409,
410,
411,
412,
413,
414,
415,
416,
417,
418,
421,
422,
423,
424,
426,
428,
429,
431,
451,
500,
501,
502,
503,
504,
505,
506,
507,
508,
510,
511,
};
enum class Code : int
{
UNKNOWN = -1,
CONTINUE,
SWITCHING_PROTOCOLS,
PROCESSING,
OK,
CREATED,
ACCEPTED,
NON_AUTHORITATIVE_INFORMATION,
NO_CONTENT,
RESET_CONTENT,
PARTIAL_CONTENT,
MULTI_STATUS,
ALREADY_REPORTED,
IM_USED,
MULTIPLE_CHOICES,
MOVED_PERMANENTLY,
FOUND,
SEE_OTHER,
NOT_MODIFIED,
USE_PROXY,
UNUSED,
TEMPORARY_REDIRECT,
PERMANENT_REDIRECT,
BAD_REQUEST,
UNAUTHORIZED,
PAYMENT_REQUIRED,
FORBIDDEN,
NOT_FOUND,
METHOD_NOT_ALLOWED,
NOT_ACCEPTABLE,
PROXY_AUTHENTICATION_REQUIRED,
REQUEST_TIMEOUT,
CONFLICT,
GONE,
LENGTH_REQUIRED,
PRECONDITION_FAILED,
PAYLOAD_TOO_LARGE,
URI_TOO_LONG,
UNSUPPORTED_MEDIA_TYPE,
RANGE_NOT_SATISFIABLE,
EXPECTATION_FAILED,
I_AM_A_TEAPOT,
MISDIRECTED_REQUEST,
UNPROCESSABLE_ENTITY,
LOCKED,
FAILED_DEPENDENCY,
UPGRADE_REQUIRED,
PRECONDITION_REQUIRED,
TOO_MANY_REQUESTS,
REQUEST_HEADER_FIELDS_TOO_LARGE,
UNAVAILABLE_FOR_LEGAL_REASONS,
INTERNAL_SERVER_ERROR,
NOT_IMPLEMENTED,
BAD_GATEWAY,
SERVICE_UNAVAILABLE,
GATEWAY_TIMEOUT,
HTTP_VERSION_NOT_SUPPORTED,
VARIANT_ALSO_NEGOTIATES,
INSUFFICIENT_STORAGE,
LOOP_DETECTED,
NOT_EXTENDED,
NETWORK_AUTHENTICATION_REQUIRED
};
const std::vector<std::string> codeStrings = {
"Continue",
"Switching Protocols",
"Processing",
"OK",
"Created",
"Accepted",
"Non-Authoritative Information",
"No Content",
"Reset Content",
"Partial Content",
"Multi-Status",
"Already Reported",
"IM Used",
"Multiple Choices",
"Moved Permanently",
"Found",
"See Other",
"Not Modified",
"Use Proxy",
"(Unused)",
"Temporary Redirect",
"Permanent Redirect",
"Bad Request",
"Unauthorized",
"Payment Required",
"Forbidden",
"Not Found",
"Method Not Allowed",
"Not Acceptable",
"Proxy Authentication Required",
"Request Timeout",
"Conflict",
"Gone",
"Length Required",
"Precondition Failed",
"Payload Too Large",
"URI Too Long",
"Unsupported Media Type",
"Range Not Satisfiable",
"Expectation Failed",
"I am a teapot",
"Misdirected Request",
"Unprocessable Entity",
"Locked",
"Failed Dependency",
"Upgrade Required",
"Precondition Required",
"Too Many Requests",
"Request Header Fields Too Large",
"Unavailable For Legal Reasons",
"Internal Server Error",
"Not Implemented",
"Bad Gateway",
"Service Unavailable",
"Gateway Timeout",
"HTTP Version Not Supported",
"Variant Also Negotiates",
"Insufficient Storage",
"Loop Detected",
"Not Extended",
"Network Authentication Required",
};
enum class Header
{
UNKNOWN = -1,
// https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
ACCESS_CONTROL_ALLOW_ORIGIN,
ACCESS_CONTROL_ALLOW_CREDENTIALS,
ACCESS_CONTROL_EXPOSE_HEADERS,
ACCESS_CONTROL_MAX_AGE,
ACCESS_CONTROL_ALLOW_METHODS,
ACCESS_CONTROL_ALLOW_HEADERS,
ACCEPT_PATCH,
ACCEPT_RANGES,
AGE,
ALLOW,
ALT_SVC,
CACHE_CONTROL,
CONNECTION,
CONTENT_DISPOSITION,
CONTENT_ENCODING,
CONTENT_LANGUAGE,
CONTENT_LENGTH,
CONTENT_LOCATION,
CONTENT_MD5,
CONTENT_RANGE,
CONTENT_TYPE,
DATE,
ETAG,
EXPIRES,
LAST_MODIFIED,
LINK,
LOCATION,
P3P,
PRAGMA,
PROXY_AUTHENTICATE,
PUBLIC_KEY_PINS,
RETRY_AFTER,
SERVER,
SET_COOKIE,
STRICT_TRANSPORT_SECURITY,
TRAILER,
TRANSFER_ENCODING,
TK,
UPGRADE,
VARY,
VIA,
WARNING,
WWW_AUTHENTICATE,
X_FRAME_OPTION
};
const std::vector<std::string> headerStrings = {
"Access-Control-Allow-Origin",
"Access-Control-Allow-Credentials",
"Access-Control-Expose-Headers",
"Access-Control-Max-Age",
"Access-Control-Allow-Methods",
"Access-Control-Allow-Headers",
"Accept-Patch",
"Accept-Ranges",
"Age",
"Allow",
"Alt-Svc",
"Cache-Control",
"Connection",
"Content-Disposition",
"Content-Encoding",
"Content-Language",
"Content-Length",
"Content-Location",
"Content-MD5",
"Content-Range",
"Content-Type",
"Date",
"ETag",
"Expires",
"Last-Modified",
"Link",
"Location",
"P3P",
"Pragma",
"Proxy-Authenticate",
"Public-Key-Pins",
"Retry-After",
"Server",
"Set-Cookie",
"Strict-Transport-Security",
"Trailer",
"Transfer-Encoding",
"Tk",
"Upgrade",
"Vary",
"Via",
"Warning",
"WWW-Authenticate",
"X-Frame-Option",
};
}

76
src/http/mime.cpp Normal file
View File

@@ -0,0 +1,76 @@
#include "../logger.hpp"
#include "mime.hpp"
#include <unordered_map>
namespace Http
{
/* Existing MIME types
text
image
audio
video
application
multipart
*/
std::string GetMimeType(std::filesystem::path const & path)
{
auto const extension = path.extension();
return GetMimeType(extension.string());
}
std::string GetMimeType(std::string const & extension)
{
static std::unordered_map<std::string, FileType> fileExtensionMap
{
{".html", FileType::HTML},
{".htm", FileType::HTML},
{".css", FileType::CSS},
{".js", FileType::JS},
{".jpg", FileType::JPG},
{".jpeg", FileType::JPG},
{".png", FileType::PNG},
{".mp3", FileType::MP3},
{".mp4", FileType::MP4},
};
auto const findResult = fileExtensionMap.find(extension);
if (findResult == fileExtensionMap.end())
{
return GetMimeType(FileType::UNKNOWN);
}
return GetMimeType(findResult->second);
}
std::string GetMimeType(FileType const fileType)
{
switch(fileType)
{
default:
case FileType::UNKNOWN:
return "text/plain";
case FileType::HTML:
return "text/html";
case FileType::CSS:
return "text/css";
case FileType::JS:
return "application/javascript";
case FileType::JPG:
return "image/jpeg";
case FileType::PNG:
return "image/png";
case FileType::MP3:
return "audio/mpeg3";
case FileType::MP4:
return "video/mp4";
}
}
}

23
src/http/mime.hpp Normal file
View File

@@ -0,0 +1,23 @@
#pragma once
#include <filesystem>
#include <string>
#include <vector>
namespace Http
{
enum class FileType
{
UNKNOWN = -1,
HTML,
CSS,
JS,
PNG,
JPG,
MP3,
MP4
};
std::string GetMimeType(std::filesystem::path const & path);
std::string GetMimeType(std::string const & extension);
std::string GetMimeType(FileType const fileType);
}

38
src/http/request.cpp Normal file
View File

@@ -0,0 +1,38 @@
#include "request.hpp"
#include <sstream>
namespace Http
{
template<class T>
T ToEnum(std::string const & str, std::vector<std::string> const & enumStrings)
{
for(unsigned i = 0; i < enumStrings.size(); ++i)
{
if (str.compare(enumStrings[i]) == 0)
{
return static_cast<T>(i);
}
}
return static_cast<T>(-1);
}
Http::Request Request::Deserialize(std::vector<char> const & bytes)
{
// TODO serialize more than just the start
Http::Request request;
std::stringstream ss(std::string(bytes.begin(), bytes.end()));
std::string requestTypeString;
ss >> requestTypeString;
request.requestType = ToEnum<HttpRequest::Type>(requestTypeString, HttpRequest::typeStrings);
ss >> request.path;
std::string httpProtocolString;
ss >> httpProtocolString;
return request;
}
}

13
src/http/request.hpp Normal file
View File

@@ -0,0 +1,13 @@
#pragma once
#include "../constants/httprequest.hpp"
namespace Http
{
struct Request
{
HttpRequest::Type requestType;
std::string path;
static Request Deserialize(std::vector<char> const & bytes);
};
}

37
src/http/response.cpp Normal file
View File

@@ -0,0 +1,37 @@
#include "response.hpp"
#include <sstream>
namespace Http
{
std::vector<char> Response::Serialize()
{
// TODO implement headers properly
std::stringstream ss;
ss << "HTTP/1.1";
ss << ' ' << HttpResponse::codeValues[static_cast<int>(code)];
ss << ' ' << HttpResponse::codeStrings[static_cast<int>(code)];
ss << "\r\n";
ss << "Server: http-server/0.1\r\n";
if (contentType.size() > 0)
{
ss << "Content-Type: ";
ss << contentType << "\r\n";
}
ss << "\r\n";
auto header = ss.str();
std::vector<char> buffer (header.begin(), header.end());
buffer.insert(buffer.end(), content.begin(), content.end());
return buffer;
}
Response::Response()
: code(HttpResponse::Code::UNKNOWN),
contentType(),
content()
{
}
}

16
src/http/response.hpp Normal file
View File

@@ -0,0 +1,16 @@
#pragma once
#include "../constants/httpresponse.hpp"
namespace Http
{
struct Response
{
HttpResponse::Code code;
std::string contentType;
std::vector<char> content;
std::vector<char> Serialize();
Response();
};
}

32
src/logger.cpp Normal file
View File

@@ -0,0 +1,32 @@
#include <cstdio>
#include "logger.hpp"
Logger::~Logger()
{
}
Logger::Logger()
{
}
void Logger::Success(const std::string & s)
{
std::printf("[SUCCESS] %s\n", s.c_str());
}
void Logger::Error(const std::string & s)
{
std::printf("[ERROR] %s\n", s.c_str());
}
void Logger::Info(const std::string & s)
{
std::printf("[INFO] %s\n", s.c_str());
}
Logger & Logger::GetInstance()
{
static Logger logger;
return logger;
}

20
src/logger.hpp Normal file
View File

@@ -0,0 +1,20 @@
#pragma once
#include <string>
class Logger
{
private:
Logger();
~Logger();
public:
void Success(const std::string & s);
void Error(const std::string & s);
void Info(const std::string & s);
static Logger & GetInstance();
Logger(Logger & other) = delete;
Logger(Logger && other) = delete;
Logger & operator=(Logger & other) = delete;
};

21
src/main.cpp Executable file
View File

@@ -0,0 +1,21 @@
#pragma once
#include "server/configuration.hpp"
#include "logger.hpp"
#include "server/server.hpp"
int main(int argc, char ** argv)
{
/*
ServerConfiguration & serverConfiguration;
if (serverConfiguration.LoadFromFile("./server.cfg") && !serverConfiguration.IsValid())
{
Logger::GetInstance().Error("Error loading configuration file, aborting.");
return 1;
}
*/
HttpServer httpServer;
httpServer.Execute();
return 0;
}

17
src/middleware/base.hpp Normal file
View File

@@ -0,0 +1,17 @@
#pragma once
#include "../http/request.hpp"
#include "../http/response.hpp"
#include <cstdint>
#include <string>
namespace Middleware
{
class BaseMiddleware
{
public:
virtual void HandleRequest(Http::Request const & request, Http::Response & response) = 0;
BaseMiddleware() = default;
virtual ~BaseMiddleware() = default;
};
}

View File

@@ -0,0 +1,27 @@
#include "notfound.hpp"
#include <sstream>
namespace Middleware
{
void NotFound::HandleRequest(Http::Request const & request, Http::Response & response)
{
if (response.code != HttpResponse::Code::UNKNOWN)
{
return;
}
response.code = HttpResponse::Code::NOT_FOUND;
std::stringstream ss;
ss << "404 - file not found\n";
ss << "File: ";
ss << request.path << '\n';
auto responseContent = ss.str();
response.content.insert(response.content.begin(),
responseContent.begin(),
responseContent.end());
response.contentType = "text/plain";
}
}

View File

@@ -0,0 +1,11 @@
#pragma once
#include "base.hpp"
namespace Middleware
{
class NotFound : public BaseMiddleware
{
public:
void HandleRequest(Http::Request const & request, Http::Response & Response) override;
};
}

View File

@@ -0,0 +1,72 @@
#include "../http/mime.hpp"
#include "../logger.hpp"
#include <filesystem>
#include <fstream>
#include <ios>
#include "staticcontent.hpp"
#include <sstream>
namespace Middleware
{
void ReadAllBytes(std::filesystem::path const & path, std::vector<char> & buffer)
{
std::ifstream ifs(path, std::ios_base::binary | std::ios_base::ate);
std::ifstream::pos_type length = ifs.tellg();
auto const oldBufferSize = buffer.size();
buffer.resize(oldBufferSize + length);
ifs.seekg(0, std::ios_base::beg);
ifs.read(&buffer[oldBufferSize], length);
}
void StaticContent::HandleRequest(Http::Request const & request, Http::Response & response)
{
switch(request.requestType)
{
case HttpRequest::Type::GET:
break;
default:
{
return;
}
}
std::filesystem::path path;
if (request.path.size() == 1)
{
// TODO make configurable?
path = root + "/index.html";
}
else
{
path = root + request.path;
}
if (!std::filesystem::exists(path))
{
std::stringstream ss;
ss << "Static file <";
ss << path.string();
ss << "> not found";
Logger::GetInstance().Info(ss.str());
return;
}
response.code = HttpResponse::Code::OK;
ReadAllBytes(path, response.content);
response.contentType = Http::GetMimeType(path);
return;
}
StaticContent::StaticContent(std::string const & staticFileRoot)
: root(staticFileRoot)
{
std::stringstream ss;
ss << "Using static file root " << root;
Logger::GetInstance().Info(ss.str());
}
}

View File

@@ -0,0 +1,19 @@
#pragma once
#include "base.hpp"
#include <cstdint>
#include <string>
#include <vector>
namespace Middleware
{
class StaticContent : public BaseMiddleware
{
private:
std::string root;
public:
virtual void HandleRequest(Http::Request const & request, Http::Response & response) override;
StaticContent(std::string const & staticFileRoot);
};
}

View File

@@ -0,0 +1,51 @@
#include "configuration.hpp"
bool ServerConfiguration::LoadFromFile(std::string const & filePath)
{
// TODO implement
return false;
}
ServerConfiguration::ServerConfiguration()
: wwwRoot("/home/tijmen/project/http-server/bin/www"),
serverName("http-server"),
port(8080)
{
}
int ServerConfiguration::GetMajorVersion() const
{
return 0;
}
int ServerConfiguration::GetMinorVersion() const
{
return 1;
}
std::string const & ServerConfiguration::GetWwwRoot() const
{
return wwwRoot;
}
std::string const & ServerConfiguration::GetServerName() const
{
return serverName;
}
int ServerConfiguration::GetPort() const
{
return port;
}
bool ServerConfiguration::IsValid() const
{
return isValid;
}
ServerConfiguration const & ServerConfiguration::GetInstance()
{
static ServerConfiguration config;
return config;
}

View File

@@ -0,0 +1,29 @@
#pragma once
#include <string>
class ServerConfiguration
{
private:
std::string wwwRoot;
std::string serverName;
int port;
bool isValid;
bool LoadFromFile(std::string const & filePath);
ServerConfiguration();
~ServerConfiguration() = default;
public:
int GetMajorVersion() const;
int GetMinorVersion() const;
std::string const & GetWwwRoot() const;
std::string const & GetServerName() const;
int GetPort() const;
bool IsValid() const;
static ServerConfiguration const & GetInstance();
ServerConfiguration(ServerConfiguration & other) = delete;
ServerConfiguration(ServerConfiguration && other) = delete;
};

68
src/server/connection.cpp Normal file
View File

@@ -0,0 +1,68 @@
#include "connection.hpp"
#include <stdexcept>
#include <unistd.h>
std::vector<char> Connection::ReadBytes(size_t limit) const
{
size_t const readChunkSize = 128;
std::vector<char> buffer;
ssize_t totalBytesRead = 0;
ssize_t bytesRead = 0;
do
{
buffer.resize(buffer.size() + readChunkSize);
bytesRead = read(fileDescriptor, &buffer[totalBytesRead], readChunkSize);
if (bytesRead < 0)
{
throw std::runtime_error("Error reading from filedescriptor");
}
totalBytesRead += bytesRead;
} while (bytesRead == readChunkSize && bytesRead < limit);
buffer.resize(totalBytesRead);
return buffer;
}
size_t Connection::WriteBytes(std::vector<char> const & bytes) const
{
ssize_t totalBytesWritten = 0;
size_t const sizeToWrite = bytes.size();
while (totalBytesWritten < sizeToWrite)
{
ssize_t bytesWritten = write(fileDescriptor, &bytes[totalBytesWritten], sizeToWrite - totalBytesWritten);
if (bytesWritten <= 0)
{
throw std::runtime_error("Error writing to filedescriptor");
}
totalBytesWritten += bytesWritten;
}
return totalBytesWritten;
}
Connection::Connection(int _fileDescriptor)
: fileDescriptor(_fileDescriptor)
{
if (_fileDescriptor < 0)
{
throw std::runtime_error("connection created with invalid file descriptor");
}
}
Connection::~Connection()
{
if (fileDescriptor >= 0)
{
close(fileDescriptor);
}
}
Connection::Connection(Connection && other)
{
fileDescriptor = other.fileDescriptor;
other.fileDescriptor = -1;
}

23
src/server/connection.hpp Normal file
View File

@@ -0,0 +1,23 @@
#pragma once
#include <cstdint>
#include <vector>
class Connection
{
private:
int fileDescriptor;
public:
// Parameter limit is a multiple of 128
std::vector<char> ReadBytes(size_t limit = 512) const;
size_t WriteBytes(std::vector<char> const & bytes) const;
Connection(int _fileDescriptor);
~Connection();
Connection(Connection && other);
Connection(Connection & other) = delete;
Connection & operator=(Connection & other) = delete;
};

View File

@@ -0,0 +1,45 @@
#include "../middleware/notfound.hpp"
#include "../middleware/staticcontent.hpp"
#include "../logger.hpp"
#include "configuration.hpp"
#include "connectionoperator.hpp"
#include <cstdio>
#include <sstream>
void ConnectionOperator::HandleNewConnection(Connection const & newConnection)
{
auto requestBytes = newConnection.ReadBytes();
Http::Request request = Http::Request::Deserialize(requestBytes);
Http::Response response;
for(size_t i = 0; i < middlewares.size(); ++i)
{
middlewares[i]->HandleRequest(request, response);
}
if (response.code == HttpResponse::Code::UNKNOWN)
{
std::stringstream ss;
ss << "Unhandled request for file <";
ss << request.path;
ss << '>';
Logger::GetInstance().Error(ss.str());
return;
}
auto bytesToSend = response.Serialize();
newConnection.WriteBytes(bytesToSend);
}
ConnectionOperator::ConnectionOperator()
{
// Base static file server
auto const & staticFileRoot = ServerConfiguration::GetInstance().GetWwwRoot();
if (staticFileRoot.size() > 0)
{
middlewares.emplace_back(std::make_unique<Middleware::StaticContent>(staticFileRoot));
}
// ALWAYS LAST!
middlewares.emplace_back(std::make_unique<Middleware::NotFound>());
}

View File

@@ -0,0 +1,17 @@
#pragma once
#include "../middleware/base.hpp"
#include "connection.hpp"
#include <memory>
#include <string>
#include <vector>
class ConnectionOperator
{
private:
std::vector<std::unique_ptr<Middleware::BaseMiddleware>> middlewares;
public:
void HandleNewConnection(Connection const & newConnection);
ConnectionOperator();
};

View File

@@ -0,0 +1,54 @@
#include "listeningsocket.hpp"
#include <stdexcept>
#include <unistd.h>
int const connectionLimit = 10;
Connection ListeningSocket::AcceptNextConnection()
{
unsigned sockaddrSize = sizeof(sockaddr_in);
int connectionFileDescriptor = accept(
socketFileDescriptor,
reinterpret_cast<sockaddr *>(&socketAddress),
&sockaddrSize);
return Connection(connectionFileDescriptor);
}
ListeningSocket::ListeningSocket(int const port)
: socketFileDescriptor(-1),
socketAddress()
{
socketFileDescriptor = socket(AF_INET, SOCK_STREAM, 0);
if (socketFileDescriptor < 0)
{
throw std::runtime_error("socket creation error");
}
socketAddress.sin_family = AF_INET;
socketAddress.sin_addr.s_addr = INADDR_ANY;
socketAddress.sin_port = htons(port);
int const bindResult = bind(
socketFileDescriptor,
reinterpret_cast<sockaddr*>(&socketAddress),
sizeof(sockaddr_in));
if (bindResult < 0)
{
throw std::runtime_error("socket bind error");
}
int const listenResult = listen(socketFileDescriptor, connectionLimit);
if (listenResult < 0)
{
throw std::runtime_error("socket listening error");
}
}
ListeningSocket::~ListeningSocket()
{
if (socketFileDescriptor >= 0)
{
close(socketFileDescriptor);
}
}

View File

@@ -0,0 +1,20 @@
#pragma once
#include "connection.hpp"
#include <sys/socket.h>
#include <netinet/in.h>
class ListeningSocket
{
private:
int socketFileDescriptor;
sockaddr_in socketAddress;
public:
Connection AcceptNextConnection();
ListeningSocket(int const port);
~ListeningSocket();
ListeningSocket(ListeningSocket & other) = delete;
ListeningSocket & operator=(ListeningSocket & other) = delete;
};

31
src/server/server.cpp Executable file
View File

@@ -0,0 +1,31 @@
#include "../logger.hpp"
#include "configuration.hpp"
#include "server.hpp"
#include <sstream>
#include <stdexcept>
void HttpServer::Execute()
{
while(isOpen)
{
try
{
Connection newConnection = listeningSocket.AcceptNextConnection();
connectionOperator.HandleNewConnection(newConnection);
}
catch (std::runtime_error & e)
{
Logger::GetInstance().Info("Connection dropped on accept");
}
}
}
HttpServer::HttpServer()
: listeningSocket(ServerConfiguration::GetInstance().GetPort()),
connectionOperator(),
isOpen(true)
{
std::stringstream ss;
ss << "Listening on port " << ServerConfiguration::GetInstance().GetPort();
Logger::GetInstance().Info(ss.str());
}

18
src/server/server.hpp Executable file
View File

@@ -0,0 +1,18 @@
#pragma once
#include "connectionoperator.hpp"
#include "listeningsocket.hpp"
class HttpServer
{
private:
ListeningSocket listeningSocket;
ConnectionOperator connectionOperator;
bool isOpen;
public:
void Execute();
HttpServer();
HttpServer(HttpServer & other) = delete;
HttpServer & operator=(HttpServer & other) = delete;
};

33
src/server/url.cpp Normal file
View File

@@ -0,0 +1,33 @@
#include "url.hpp"
bool Url::HasPath() const
{
return path.size() > 1;
}
bool Url::HasQuery() const
{
// TODO implement
return false;
}
bool Url::HasFragment() const
{
// TODO implement
return false;
}
std::string const & Url::GetPath() const
{
return path;
}
std::string const & Url::GetQuery() const
{
return query;
}
std::string const & Url::GetFragment() const
{
return fragment;
}

19
src/server/url.hpp Normal file
View File

@@ -0,0 +1,19 @@
#pragma once
#include <string>
class Url
{
private:
std::string path;
std::string query;
std::string fragment;
public:
bool HasPath() const;
bool HasQuery() const;
bool HasFragment() const;
std::string const & GetPath() const;
std::string const & GetQuery() const;
std::string const & GetFragment() const;
};