init
This commit is contained in:
commit
c62c4f4ff9
9
.clang-format
Normal file
9
.clang-format
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
BasedOnStyle: Chromium
|
||||||
|
IndentWidth: 2
|
||||||
|
ColumnLimit: 160
|
||||||
|
SpaceAfterCStyleCast: false
|
||||||
|
UseTab: Never
|
||||||
|
AllowShortIfStatementsOnASingleLine: false
|
||||||
|
AlignTrailingComments: false
|
||||||
|
SpacesBeforeTrailingComments: 1
|
||||||
|
AlignConsecutiveMacros: Consecutive
|
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
.vscode
|
||||||
|
.cache
|
||||||
|
|
||||||
|
build
|
||||||
|
|
||||||
|
inc/flags
|
||||||
|
src/flags
|
31
CMakeLists.txt
Normal file
31
CMakeLists.txt
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.25)
|
||||||
|
|
||||||
|
project(Rum VERSION 1.0)
|
||||||
|
|
||||||
|
# compiler setup
|
||||||
|
set(CMAKE_CXX_COMPILER "clang++")
|
||||||
|
#set(CMAKE_CXX_STANDARD 23)
|
||||||
|
#set(CMAKE_CXX_STANDARD_REQUIRED true)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Werror") # warnings and errors
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++2b") # std c++23 for clang
|
||||||
|
|
||||||
|
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static") # static compile
|
||||||
|
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g") # debug symbols
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2") # enable optimisations
|
||||||
|
|
||||||
|
|
||||||
|
# IDE setup (clangd)
|
||||||
|
set(CMAKE_EXPORT_COMPILE_COMMANDS true)
|
||||||
|
|
||||||
|
file(GLOB_RECURSE SOURCES "${CMAKE_SOURCE_DIR}/src/*.cpp")
|
||||||
|
|
||||||
|
add_executable(server.bin ${SOURCES})
|
||||||
|
|
||||||
|
target_include_directories(server.bin PRIVATE "${CMAKE_SOURCE_DIR}/inc")
|
||||||
|
|
||||||
|
add_custom_target(run
|
||||||
|
COMMAND $<TARGET_FILE:server.bin>
|
||||||
|
DEPENDS server.bin
|
||||||
|
COMMENT "Running server.bin"
|
||||||
|
)
|
9
inc/rum/http/http.h
Normal file
9
inc/rum/http/http.h
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "method.h"
|
||||||
|
#include "request.h"
|
||||||
|
#include "response.h"
|
||||||
|
#include "server.h"
|
||||||
|
|
||||||
|
#define WORKERS 10
|
||||||
|
#define BUFFER_LEN 8196
|
9
inc/rum/http/method.h
Normal file
9
inc/rum/http/method.h
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
namespace Rum::HTTP {
|
||||||
|
|
||||||
|
enum Method { GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE };
|
||||||
|
|
||||||
|
std::string method_to_string(Method m);
|
||||||
|
} // namespace Rum::HTTP
|
33
inc/rum/http/request.h
Normal file
33
inc/rum/http/request.h
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include "method.h"
|
||||||
|
|
||||||
|
namespace Rum::HTTP {
|
||||||
|
class Request {
|
||||||
|
public:
|
||||||
|
Request() : method(GET), path(""), body("") {}
|
||||||
|
Request(Method method, std::string path) : method(method), path(path), body("") {}
|
||||||
|
|
||||||
|
Method get_method() const { return method; }
|
||||||
|
std::string get_path() const { return path; }
|
||||||
|
|
||||||
|
const std::map<std::string, std::string>& get_headers() const { return headers; }
|
||||||
|
std::string get_header(std::string name) const { return headers.at(name); }
|
||||||
|
void set_header(std::string name, std::string value) { headers[name] = value; }
|
||||||
|
|
||||||
|
std::string get_body() const { return body; }
|
||||||
|
void set_body(std::string body) { this->body = body; }
|
||||||
|
|
||||||
|
operator std::string () const;
|
||||||
|
|
||||||
|
friend std::ostream& operator<<(std::ostream& stream, const Request req);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Method method;
|
||||||
|
std::string path;
|
||||||
|
std::map<std::string, std::string> headers;
|
||||||
|
std::string body;
|
||||||
|
};
|
||||||
|
} // namespace Rum::HTTP
|
21
inc/rum/http/response.h
Normal file
21
inc/rum/http/response.h
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Rum::HTTP {
|
||||||
|
class Response {
|
||||||
|
private:
|
||||||
|
const int client_sock;
|
||||||
|
bool sent_header;
|
||||||
|
bool sent_body;
|
||||||
|
unsigned int code;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Response(int client_sock) : client_sock(client_sock), sent_header(false), sent_body(false), code(200) {}
|
||||||
|
~Response();
|
||||||
|
|
||||||
|
void send_header(const std::string& name, const std::string& value);
|
||||||
|
void send_body(const std::string& value);
|
||||||
|
void set_code(unsigned int code) { this->code = code; }
|
||||||
|
};
|
||||||
|
} // namespace Rum::HTTP
|
52
inc/rum/http/server.h
Normal file
52
inc/rum/http/server.h
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <map>
|
||||||
|
#include <mutex>
|
||||||
|
#include <queue>
|
||||||
|
#include <regex>
|
||||||
|
#include <vector>
|
||||||
|
#include "../tcp/server.h"
|
||||||
|
#include "request.h"
|
||||||
|
#include "response.h"
|
||||||
|
|
||||||
|
#define WORKERS 10
|
||||||
|
#define BUFFER_LEN 8196
|
||||||
|
|
||||||
|
namespace Rum::HTTP {
|
||||||
|
|
||||||
|
class Server : public Rum::TCP::Server {
|
||||||
|
private:
|
||||||
|
struct Task {
|
||||||
|
Task() {}
|
||||||
|
Task(int client_sock, const sockaddr_in& sockaddr) : client_sock(client_sock), sockaddr(sockaddr) {}
|
||||||
|
int client_sock;
|
||||||
|
sockaddr_in sockaddr;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::thread> workers;
|
||||||
|
std::queue<Task> tasks;
|
||||||
|
std::mutex mtx;
|
||||||
|
std::condition_variable condition;
|
||||||
|
bool stop;
|
||||||
|
|
||||||
|
std::map<Method, std::vector<std::pair<std::regex, std::function<void(const Request&, Response&)>>>> paths;
|
||||||
|
|
||||||
|
void handler(int client_sock, const sockaddr_in& client_address, char* buffer);
|
||||||
|
|
||||||
|
public:
|
||||||
|
Server(unsigned int port, size_t worker_count);
|
||||||
|
Server(unsigned int port) : Server(port, WORKERS) {}
|
||||||
|
~Server();
|
||||||
|
void listen();
|
||||||
|
|
||||||
|
void end() override;
|
||||||
|
|
||||||
|
template <Method M>
|
||||||
|
void add_path(const std::string& path, const std::function<void(const Request&, Response&)>& callback) {
|
||||||
|
paths[M].push_back(std::pair<std::regex, std::function<void(const Request&, Response&)>>(std::regex(path), callback));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Rum::HTTP
|
18
inc/rum/tcp/error.h
Normal file
18
inc/rum/tcp/error.h
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <exception>
|
||||||
|
|
||||||
|
namespace Rum::TCP {
|
||||||
|
|
||||||
|
class Error : public std::exception {
|
||||||
|
public:
|
||||||
|
enum Type { CLOSED = 0, UNKNOWN = -1 };
|
||||||
|
|
||||||
|
Error(Type type) : type(type) {}
|
||||||
|
|
||||||
|
const char* what() const noexcept override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Type type;
|
||||||
|
};
|
||||||
|
} // namespace Rum::TCP
|
22
inc/rum/tcp/server.h
Normal file
22
inc/rum/tcp/server.h
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace Rum::TCP {
|
||||||
|
class Server {
|
||||||
|
private:
|
||||||
|
const int sock;
|
||||||
|
bool stop;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Server(unsigned short port);
|
||||||
|
virtual ~Server();
|
||||||
|
void listen(std::function<void(int, sockaddr_in)> handler) const;
|
||||||
|
virtual void end() {
|
||||||
|
stop = true;
|
||||||
|
close(sock);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace Rum::TCP
|
5
inc/rum/tcp/tcp.h
Normal file
5
inc/rum/tcp/tcp.h
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "error.h"
|
||||||
|
#include "server.h"
|
||||||
|
#include "utility.h"
|
10
inc/rum/tcp/utility.h
Normal file
10
inc/rum/tcp/utility.h
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Rum::TCP {
|
||||||
|
|
||||||
|
std::string address_to_string(sockaddr_in address);
|
||||||
|
|
||||||
|
} // namespace Rum::TCP
|
24
src/rum/http/method.cpp
Normal file
24
src/rum/http/method.cpp
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#include <rum/http/method.h>
|
||||||
|
|
||||||
|
namespace Rum::HTTP {
|
||||||
|
std::string method_to_string(Method m) {
|
||||||
|
switch (m) {
|
||||||
|
case GET:
|
||||||
|
return "GET";
|
||||||
|
case HEAD:
|
||||||
|
return "HEAD";
|
||||||
|
case POST:
|
||||||
|
return "POST";
|
||||||
|
case PUT:
|
||||||
|
return "PUT";
|
||||||
|
case DELETE:
|
||||||
|
return "DELETE";
|
||||||
|
case CONNECT:
|
||||||
|
return "CONNECT";
|
||||||
|
case OPTIONS:
|
||||||
|
return "OPTIONS";
|
||||||
|
case TRACE:
|
||||||
|
return "TRACE";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace Rum::HTTP
|
23
src/rum/http/request.cpp
Normal file
23
src/rum/http/request.cpp
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#include <rum/http/request.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Rum::HTTP {
|
||||||
|
|
||||||
|
Request::operator std::string() const {
|
||||||
|
std::string result("Request{\n\tmethod: " + method_to_string(method) + "\n\tpath: \"" + path + "\"\n\theaders:\n");
|
||||||
|
for (auto header : get_headers()) {
|
||||||
|
result += "\t\t\"" + header.first + "\": \"" + header.second + "\"\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
result += "\tbody: \"" + body + "\"\n}";
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& stream, const Request req) {
|
||||||
|
stream << (std::string)req;
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Rum::HTTP
|
113
src/rum/http/response.cpp
Normal file
113
src/rum/http/response.cpp
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
#include <rum/http/response.h>
|
||||||
|
#include <rum/tcp/error.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <map>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace Rum::HTTP {
|
||||||
|
std::string status_code_to_string(int code) {
|
||||||
|
static std::map<int, std::string> codes{{100, "Continue"},
|
||||||
|
{101, "Switching Protocols"},
|
||||||
|
{102, "Processing"},
|
||||||
|
{103, "Early Hints"},
|
||||||
|
{200, "OK"},
|
||||||
|
{201, "Created"},
|
||||||
|
{202, "Accepted"},
|
||||||
|
{203, "Non-Authoritative Information"},
|
||||||
|
{204, "No Content"},
|
||||||
|
{205, "Reset Content"},
|
||||||
|
{206, "Partial Content"},
|
||||||
|
{207, "Multi-Status"},
|
||||||
|
{208, "Already Reported"},
|
||||||
|
{226, "IM Used"},
|
||||||
|
{300, "Multiple Choices"},
|
||||||
|
{301, "Moved Permanently"},
|
||||||
|
{302, "Found"},
|
||||||
|
{303, "See Other"},
|
||||||
|
{304, "Not Modified"},
|
||||||
|
{307, "Temporary Redirect"},
|
||||||
|
{308, "Permanent Redirect"},
|
||||||
|
{400, "Bad Request"},
|
||||||
|
{401, "Unauthorized"},
|
||||||
|
{402, "Payment Required"},
|
||||||
|
{403, "Forbidden"},
|
||||||
|
{404, "Not Found"},
|
||||||
|
{405, "Method Not Allowed"},
|
||||||
|
{406, "Not Acceptable"},
|
||||||
|
{407, "Proxy Authentication Required"},
|
||||||
|
{408, "Request Timeout"},
|
||||||
|
{409, "Conflict"},
|
||||||
|
{410, "Gone"},
|
||||||
|
{411, "Length Required"},
|
||||||
|
{412, "Precondition Failed"},
|
||||||
|
{413, "Content Too Large"},
|
||||||
|
{414, "URI Too Long"},
|
||||||
|
{415, "Unsupported Media Type"},
|
||||||
|
{416, "Range Not Satisfiable"},
|
||||||
|
{417, "Expectation Failed"},
|
||||||
|
{418, "I'm a teapot"},
|
||||||
|
{421, "Misdirected Request"},
|
||||||
|
{422, "Unprocessable Content"},
|
||||||
|
{423, "Locked"},
|
||||||
|
{424, "Failed Dependency"},
|
||||||
|
{425, "Too Early"},
|
||||||
|
{426, "Upgrade Required"},
|
||||||
|
{428, "Precondition Required"},
|
||||||
|
{429, "Too Many Requests"},
|
||||||
|
{431, "Request Header Fields Too Large"},
|
||||||
|
{451, "Unavailable For Legal Reasons"},
|
||||||
|
{500, "Internal Server Error"},
|
||||||
|
{501, "Not Implemented"},
|
||||||
|
{502, "Bad Gateway"},
|
||||||
|
{503, "Service Unavailable"},
|
||||||
|
{504, "Gateway Timeout"},
|
||||||
|
{505, "HTTP Version Not Supported"},
|
||||||
|
{506, "Variant Also Negotiates"},
|
||||||
|
{507, "Insufficient Storage"},
|
||||||
|
{508, "Loop Detected"},
|
||||||
|
{510, "Not Extended"},
|
||||||
|
{511, "Network Authentication Required"}};
|
||||||
|
|
||||||
|
try {
|
||||||
|
return codes.at(code);
|
||||||
|
} catch (std::out_of_range) {
|
||||||
|
return codes.at((code / 100) * 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Response::send_header(const std::string& name, const std::string& value) {
|
||||||
|
if (!sent_header) {
|
||||||
|
std::string resp("HTTP/1.1 " + std::to_string(code) + " " + status_code_to_string(code) + "\r\n");
|
||||||
|
if (-1 == send(client_sock, resp.c_str(), resp.size(), 0))
|
||||||
|
throw TCP::Error(TCP::Error::CLOSED);
|
||||||
|
sent_header = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string header = name + ": " + value + "\r\n";
|
||||||
|
if (-1 == send(client_sock, header.c_str(), header.size(), 0))
|
||||||
|
throw TCP::Error(TCP::Error::CLOSED);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Response::send_body(const std::string& value) {
|
||||||
|
if (!sent_header) {
|
||||||
|
send_header("Content-Type", "text/html");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sent_body) {
|
||||||
|
if (-1 == send(client_sock, "\r\n", 2, 0))
|
||||||
|
throw TCP::Error(TCP::Error::CLOSED);
|
||||||
|
sent_body = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-1 == send(client_sock, value.c_str(), value.size(), 0))
|
||||||
|
throw TCP::Error(TCP::Error::CLOSED);
|
||||||
|
}
|
||||||
|
|
||||||
|
Response::~Response() {
|
||||||
|
if (!sent_header) {
|
||||||
|
std::string resp("HTTP/1.1 " + std::to_string(code) + " " + status_code_to_string(code));
|
||||||
|
send(client_sock, resp.c_str(), resp.size(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Rum::HTTP
|
165
src/rum/http/server.cpp
Normal file
165
src/rum/http/server.cpp
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
#include <rum/http/server.h>
|
||||||
|
#include <rum/tcp/error.h>
|
||||||
|
#include <rum/tcp/utility.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <regex>
|
||||||
|
|
||||||
|
namespace Rum::HTTP {
|
||||||
|
Server::Server(unsigned int port, size_t worker_count) : Rum::TCP::Server(port), stop(false) {
|
||||||
|
for (size_t i = 0; i < worker_count; i++) {
|
||||||
|
std::thread worker([this, i]() {
|
||||||
|
char* buffer = new char[BUFFER_LEN]();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
Task task;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(mtx);
|
||||||
|
condition.wait(lock, [this]() { return stop || !tasks.empty(); });
|
||||||
|
|
||||||
|
if (stop && tasks.empty()) {
|
||||||
|
delete[] buffer;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
task = tasks.front();
|
||||||
|
tasks.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Worker #" << i << " accepted a connection." << std::endl;
|
||||||
|
|
||||||
|
handler(task.client_sock, task.sockaddr, buffer);
|
||||||
|
if (int status = close(task.client_sock); status == TCP::Error::UNKNOWN) {
|
||||||
|
std::cerr << TCP::address_to_string(task.sockaddr) << ": " << TCP::Error((TCP::Error::Type)status).what() << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
workers.emplace_back(std::move(worker));
|
||||||
|
std::cout << "Worker #" << i << " created" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server::end() {
|
||||||
|
if (stop)
|
||||||
|
return;
|
||||||
|
|
||||||
|
TCP::Server::end();
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(mtx);
|
||||||
|
stop = true;
|
||||||
|
}
|
||||||
|
condition.notify_all();
|
||||||
|
for (auto& worker : workers)
|
||||||
|
worker.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
Server::~Server() {
|
||||||
|
end();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server::listen() {
|
||||||
|
Rum::TCP::Server::listen([this](int client_sock, sockaddr_in client_address) {
|
||||||
|
tasks.emplace(client_sock, client_address);
|
||||||
|
condition.notify_one();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Method string_to_method(std::string text) {
|
||||||
|
if (text.starts_with("GET"))
|
||||||
|
return GET;
|
||||||
|
if (text.starts_with("HEAD"))
|
||||||
|
return HEAD;
|
||||||
|
if (text.starts_with("POST"))
|
||||||
|
return POST;
|
||||||
|
if (text.starts_with("PUT"))
|
||||||
|
return PUT;
|
||||||
|
if (text.starts_with("DELETE"))
|
||||||
|
return DELETE;
|
||||||
|
if (text.starts_with("CONNECT"))
|
||||||
|
return CONNECT;
|
||||||
|
if (text.starts_with("OPTIONS"))
|
||||||
|
return OPTIONS;
|
||||||
|
// if (text.starts_with("TRACE"))
|
||||||
|
return TRACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server::handler(int client_sock, const sockaddr_in& client_address, char* buffer) {
|
||||||
|
std::string address = TCP::address_to_string(client_address);
|
||||||
|
std::cout << address << ": connected" << std::endl;
|
||||||
|
|
||||||
|
Request request;
|
||||||
|
|
||||||
|
enum Stage { METHOD, HEADER, BODY };
|
||||||
|
Stage stage = METHOD;
|
||||||
|
|
||||||
|
std::string message;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
ssize_t recieved = recv(client_sock, buffer, BUFFER_LEN, 0);
|
||||||
|
switch (recieved) {
|
||||||
|
case TCP::Error::CLOSED:
|
||||||
|
std::cout << address << ": connection closed" << std::endl;
|
||||||
|
return;
|
||||||
|
case TCP::Error::UNKNOWN:
|
||||||
|
std::cout << "socket error" << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
message += std::string(buffer, buffer + recieved);
|
||||||
|
|
||||||
|
if (stage == METHOD && message.contains("\r\n")) {
|
||||||
|
std::regex method_regex("(GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE) (\\/.*) .*");
|
||||||
|
std::smatch match;
|
||||||
|
if (std::regex_match(message.cbegin(), message.cbegin() + message.find("\r\n"), match, method_regex)) {
|
||||||
|
request = Request(string_to_method(match.str(1)), match.str(2));
|
||||||
|
message = message.substr(message.find("\r\n"));
|
||||||
|
stage = HEADER;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stage == HEADER && message.contains("\r\n\r\n")) {
|
||||||
|
std::regex header_regex("(.*): (.*)");
|
||||||
|
for (std::sregex_iterator it = std::sregex_iterator(message.cbegin(), message.cbegin() + message.find("\r\n\r\n"), header_regex);
|
||||||
|
it != std::sregex_iterator(); it++) {
|
||||||
|
request.set_header(it->str(1), it->str(2));
|
||||||
|
}
|
||||||
|
message = message.substr(message.find("\r\n\r\n"));
|
||||||
|
|
||||||
|
if (Method method = request.get_method(); method == POST || method == PUT)
|
||||||
|
stage = BODY;
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stage == BODY) {
|
||||||
|
request.set_body(message);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << request << std::endl;
|
||||||
|
|
||||||
|
try {
|
||||||
|
Response resp(client_sock);
|
||||||
|
|
||||||
|
bool found = false;
|
||||||
|
|
||||||
|
for (auto it = paths[request.get_method()].cbegin(); it != paths[request.get_method()].cend(); it++) {
|
||||||
|
if (std::regex_match(request.get_path(), it->first)) {
|
||||||
|
it->second(request, resp);
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
resp.set_code(404);
|
||||||
|
resp.send_body("<h1>404: Page not found :C</h1>");
|
||||||
|
}
|
||||||
|
} catch (std::out_of_range) {
|
||||||
|
} catch (TCP::Error) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace Rum::HTTP
|
13
src/rum/tcp/error.cpp
Normal file
13
src/rum/tcp/error.cpp
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#include <rum/tcp/error.h>
|
||||||
|
|
||||||
|
namespace Rum::TCP {
|
||||||
|
|
||||||
|
const char* Error::what() const noexcept {
|
||||||
|
switch (type) {
|
||||||
|
case CLOSED:
|
||||||
|
return "the connection is already closed";
|
||||||
|
case UNKNOWN:
|
||||||
|
return "an unknown error has occured";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace Rum::TCP
|
47
src/rum/tcp/server.cpp
Normal file
47
src/rum/tcp/server.cpp
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <rum/tcp/error.h>
|
||||||
|
#include <rum/tcp/server.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#define MAX_PENDING 10
|
||||||
|
|
||||||
|
namespace Rum::TCP {
|
||||||
|
|
||||||
|
std::string address_to_string(sockaddr_in address) {
|
||||||
|
return std::string(inet_ntoa(address.sin_addr)) + ":" + std::to_string(ntohs(address.sin_port));
|
||||||
|
}
|
||||||
|
|
||||||
|
Server::Server(unsigned short port) : sock(socket(AF_INET, SOCK_STREAM, 0)), stop(false) {
|
||||||
|
if (sock == -1)
|
||||||
|
throw Error(Error::UNKNOWN);
|
||||||
|
|
||||||
|
sockaddr_in address = {.sin_family = AF_INET, .sin_port = htons(port), .sin_addr = {.s_addr = INADDR_ANY}};
|
||||||
|
|
||||||
|
if (bind(sock, (struct sockaddr*)&address, sizeof(address)) == -1)
|
||||||
|
throw Error(Error::UNKNOWN);
|
||||||
|
}
|
||||||
|
|
||||||
|
Server::~Server() {
|
||||||
|
end();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server::listen(std::function<void(int, sockaddr_in)> handler) const {
|
||||||
|
if (::listen(sock, MAX_PENDING) == -1)
|
||||||
|
throw Error(Error::UNKNOWN);
|
||||||
|
;
|
||||||
|
|
||||||
|
while (!stop) {
|
||||||
|
sockaddr_in client_address;
|
||||||
|
socklen_t client_address_len(sizeof(client_address));
|
||||||
|
int client_sock = accept(sock, (struct sockaddr*)&client_address, &client_address_len);
|
||||||
|
if (client_sock == -1)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
handler(client_sock, client_address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Rum::TCP
|
31
src/server.cpp
Normal file
31
src/server.cpp
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#include <flags/flags.h>
|
||||||
|
#include <rum/http/http.h>
|
||||||
|
#include <rum/tcp/error.h>
|
||||||
|
#include <csignal>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
Rum::HTTP::Server* server;
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
Flags::Parser parser;
|
||||||
|
int* port = parser.add<int>("port", "tcp port number", true, 8080);
|
||||||
|
parser.parse(argc, argv);
|
||||||
|
|
||||||
|
std::cout << "Port: " << *port << std::endl;
|
||||||
|
|
||||||
|
try {
|
||||||
|
server = new Rum::HTTP::Server(*port);
|
||||||
|
std::signal(SIGINT, [](int) {
|
||||||
|
std::cout << "\nStopping server..." << std::endl;
|
||||||
|
server->end();
|
||||||
|
});
|
||||||
|
server->add_path<Rum::HTTP::GET>("/", [](const Rum::HTTP::Request&, Rum::HTTP::Response& resp) {
|
||||||
|
std::cout << "request accepted" << std::endl;
|
||||||
|
resp.send_body("<h1>Hello World</h1>");
|
||||||
|
});
|
||||||
|
server->listen();
|
||||||
|
delete server;
|
||||||
|
} catch (Rum::TCP::Error&) {
|
||||||
|
std::cerr << "Failed to bind port " << *port << std::endl;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user