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