Initial commit
This commit is contained in:
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
.vscode
|
||||
bin/www/
|
||||
build/
|
||||
|
||||
bin/*.out
|
||||
src/*.o
|
||||
src/*.d
|
||||
35
bin/server.cfg
Executable file
35
bin/server.cfg
Executable 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
37
makefile
Normal 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}
|
||||
111
src/constants/httprequest.hpp
Normal file
111
src/constants/httprequest.hpp
Normal 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",
|
||||
};
|
||||
}
|
||||
308
src/constants/httpresponse.hpp
Normal file
308
src/constants/httpresponse.hpp
Normal 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
76
src/http/mime.cpp
Normal 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
23
src/http/mime.hpp
Normal 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
38
src/http/request.cpp
Normal 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
13
src/http/request.hpp
Normal 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
37
src/http/response.cpp
Normal 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
16
src/http/response.hpp
Normal 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
32
src/logger.cpp
Normal 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
20
src/logger.hpp
Normal 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
21
src/main.cpp
Executable 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
17
src/middleware/base.hpp
Normal 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;
|
||||
};
|
||||
}
|
||||
27
src/middleware/notfound.cpp
Normal file
27
src/middleware/notfound.cpp
Normal 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";
|
||||
}
|
||||
}
|
||||
11
src/middleware/notfound.hpp
Normal file
11
src/middleware/notfound.hpp
Normal 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;
|
||||
};
|
||||
}
|
||||
72
src/middleware/staticcontent.cpp
Normal file
72
src/middleware/staticcontent.cpp
Normal 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());
|
||||
}
|
||||
}
|
||||
19
src/middleware/staticcontent.hpp
Normal file
19
src/middleware/staticcontent.hpp
Normal 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);
|
||||
};
|
||||
}
|
||||
51
src/server/configuration.cpp
Normal file
51
src/server/configuration.cpp
Normal 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;
|
||||
}
|
||||
29
src/server/configuration.hpp
Normal file
29
src/server/configuration.hpp
Normal 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
68
src/server/connection.cpp
Normal 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
23
src/server/connection.hpp
Normal 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;
|
||||
};
|
||||
45
src/server/connectionoperator.cpp
Normal file
45
src/server/connectionoperator.cpp
Normal 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>());
|
||||
}
|
||||
17
src/server/connectionoperator.hpp
Normal file
17
src/server/connectionoperator.hpp
Normal 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();
|
||||
};
|
||||
54
src/server/listeningsocket.cpp
Normal file
54
src/server/listeningsocket.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
20
src/server/listeningsocket.hpp
Normal file
20
src/server/listeningsocket.hpp
Normal 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
31
src/server/server.cpp
Executable 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
18
src/server/server.hpp
Executable 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
33
src/server/url.cpp
Normal 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
19
src/server/url.hpp
Normal 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;
|
||||
};
|
||||
Reference in New Issue
Block a user