First really working router Version

This commit is contained in:
Pablu23
2024-07-08 16:08:10 +02:00
parent 752cb229c3
commit 986be64e03
12 changed files with 424 additions and 73 deletions

View File

@@ -2,4 +2,4 @@ run: build
./server
build:
g++ -std=c++20 server.cpp request.cpp response.cpp -g -o server
g++ -std=c++20 main.cpp router.cpp path.cpp http.hpp request.cpp response.cpp -g -o server

161
http.hpp Normal file
View File

@@ -0,0 +1,161 @@
#ifndef STATUSCODE_H
#define STATUSCODE_H
#include <string>
class http {
public:
enum statusCode {
CONTINUE = 100,
SWITCHING_PROTOCOLS = 101,
PROCESSING = 102,
EARLY_HINTS = 103,
OK = 200,
CREATED = 201,
ACCEPTED = 202,
// Non auth 203
NO_CONTENT = 204,
RESET_CONTENT = 205,
PARTIAL_CONTENT = 206,
MULTIPLE_CHOICES = 300,
MOVED_PERMANENTLY = 301,
FOUND = 302,
SEE_OTHER = 303,
NOT_MODIFIED = 304,
USE_PROXY = 305,
SWITCH_PROXY = 306,
TEMPORARY_REDIRECT = 307,
PERMANENT_REDIRECT = 308,
BAD_REQUEST = 400,
UNAUTHORIZED = 401,
PAYMENT_REQUIRED = 402,
FORBIDDEN = 403,
NOT_FOUND = 404,
METHOD_NOT_ALLOWED = 405,
NOT_ACCEPTABLE = 406,
PROXY_AUTHENTICATION_REQUIRED = 407,
REQUEST_TIMEOUT = 408,
CONFLICT = 409,
GONE = 410,
LENGTH_REQUIRED = 411,
PRECONDITION_FAILED = 412,
PAYLOAD_TOO_LARGE = 413,
URI_TOO_LONG = 414,
UNSUPPORTED_MEDIA_TYPE = 415,
RANGE_NOT_SATISFIABLE = 416,
EXPECTATION_FAILED = 417,
IM_A_TEAPOT = 418,
MISDIRECTION_REQUEST = 421,
UNPROCESSABLE_CONTENT = 422,
UPGRADE_REQUIRED = 426,
INTERNAL_SERVER_ERROR = 500,
NOT_IMPLEMENTED = 501,
BAD_GATEWAY = 502,
SERVICE_UNAVAILABLE = 503,
GATEWAY_TIMEOUT = 504,
HTTP_VERSION_NOT_SUPPORTED = 505
};
constexpr static std::string StatusCodeString(const statusCode code) {
switch (code) {
case CONTINUE:
return "Continue";
case SWITCHING_PROTOCOLS:
return "Switching Protocols";
case PROCESSING:
return "Processing";
case EARLY_HINTS:
return "Early Hints";
case OK:
return "OK";
case CREATED:
return "Created";
case ACCEPTED:
return "Accepted";
case NO_CONTENT:
return "No Content";
case RESET_CONTENT:
return "Reset Content";
case PARTIAL_CONTENT:
return "Partial Content";
case MULTIPLE_CHOICES:
return "Multiple Choices";
case MOVED_PERMANENTLY:
return "Moved Permanently";
case FOUND:
return "Found";
case SEE_OTHER:
return "See Other";
case NOT_MODIFIED:
return "Not Modified";
case USE_PROXY:
return "Use Proxy";
case SWITCH_PROXY:
return "Switch Proxy";
case TEMPORARY_REDIRECT:
return "Temporary Redirect";
case PERMANENT_REDIRECT:
return "Permanent Redirect";
case BAD_REQUEST:
return "Bad Request";
case UNAUTHORIZED:
return "Unauthorized";
case PAYMENT_REQUIRED:
return "Payment Required";
case FORBIDDEN:
return "Forbidden";
case NOT_FOUND:
return "Not Found";
case METHOD_NOT_ALLOWED:
return "Method Not Allowed";
case NOT_ACCEPTABLE:
return "Not Acceptable";
case PROXY_AUTHENTICATION_REQUIRED:
return "Proxy Authentication Required";
case REQUEST_TIMEOUT:
return "Request Timeout";
case CONFLICT:
return "Conflict";
case GONE:
return "Gone";
case LENGTH_REQUIRED:
return "Length Required";
case PRECONDITION_FAILED:
return "Precondition Failed";
case PAYLOAD_TOO_LARGE:
return "Payload Too Large";
case URI_TOO_LONG:
return "URI Too Long";
case UNSUPPORTED_MEDIA_TYPE:
return "Unsupported Media Type";
case RANGE_NOT_SATISFIABLE:
return "Range Not Satisfiable";
case EXPECTATION_FAILED:
return "Expectation Failed";
case IM_A_TEAPOT:
return "I'm a teapot";
case MISDIRECTION_REQUEST:
return "Misdirection Request";
case UNPROCESSABLE_CONTENT:
return "Unprocessable Content";
case UPGRADE_REQUIRED:
return "Upgrade Required";
case INTERNAL_SERVER_ERROR:
return "Internal Server Error";
case NOT_IMPLEMENTED:
return "Not Implemented";
case BAD_GATEWAY:
return "Bad Gateway";
case SERVICE_UNAVAILABLE:
return "Service Unavailable";
case GATEWAY_TIMEOUT:
return "Gateway Timeout";
case HTTP_VERSION_NOT_SUPPORTED:
return "HTTP Version Not Supported";
default: // This is undefined Behaviour
return "";
}
}
};
#endif // !STATUSCODE_H

78
main.cpp Normal file
View File

@@ -0,0 +1,78 @@
#include "request.hpp"
#include "response.hpp"
#include "router.hpp"
int main() {
Router router(8080);
router.Handle("/helloWorld", [](Request req, Response res) -> Response {
res.SetPayload("Hello World!");
res.SetContentType("text/plain");
return res;
});
router.Handle("/echo/{name}", [](Request req, Response res) -> Response {
std::string name = req.path.Get("name").value_or("No Name given");
res.SetPayload("Hello " + name);
res.SetContentType("text/plain");
return res;
});
router.Handle("/", [](Request req, Response res) -> Response {
res.SetPayload("Main");
res.SetContentType("text/plain");
return res;
});
router.Start();
return 0;
}
// void server() {
// int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
// sockaddr_in serverAddress;
// serverAddress.sin_family = AF_INET;
// serverAddress.sin_port = htons(8080);
// serverAddress.sin_addr.s_addr = INADDR_ANY;
//
// int err = bind(serverSocket, (struct sockaddr *)&serverAddress,
// sizeof(serverAddress));
// if (err != 0) {
// std::cout << "error binding " << strerror(errno) << std::endl;
// exit(1);
// }
//
// err = listen(serverSocket, 5);
// if (err != 0) {
// std::cout << "error listening " << strerror(errno) << std::endl;
// exit(1);
// }
//
// std::vector<std::byte> buffer(1024);
// while (true) {
// int clientSocket = accept(serverSocket, nullptr, nullptr);
// int read = recv(clientSocket, buffer.data(), buffer.size(), 0);
// buffer.resize(read);
// Request req(buffer);
// Response res = Response(http::statusCode::NOT_FOUND);
// if (req.HasData()) {
// auto data = req.Data();
// res.SetPayload(data);
// } else {
// res.SetPayload("Hello world!");
// }
//
// res.SetContentType("text/plain");
// res.Send(clientSocket);
// close(clientSocket);
// }
//
// if (shutdown(serverSocket, SHUT_RDWR) < 0) {
// std::cout << "error shutdown " << strerror(errno) << std::endl;
// exit(1);
// }
//
// if (close(serverSocket) < 0) {
// std::cout << "error closing socket " << strerror(errno) << std::endl;
// exit(1);
// }
// }

37
path.cpp Normal file
View File

@@ -0,0 +1,37 @@
#include "path.hpp"
#include <optional>
Path::Path(std::string path) {
int pos = path.find("?");
m_base = path.substr(0, pos);
path.erase(0, pos + 1);
m_query = path;
}
std::optional<std::string> Path::Get(std::string name) {
if (m_variables.contains(name))
return m_variables.at(name);
return std::nullopt;
}
std::string Path::Base() { return m_base; }
std::string Path::Query() { return m_query; }
void Path::Match(std::string pattern) {
int pos = 0;
std::string path = m_base;
while (pos != -1) {
pos = pattern.find('/');
std::string p = pattern.substr(0, pos);
int uPos = path.find('/');
std::string u = path.substr(0, uPos);
if (p.starts_with('{')) {
std::string name = p.substr(1, p.size() - 2);
m_variables.insert_or_assign(name, u);
}
pattern.erase(0, pos + 1);
path.erase(0, uPos + 1);
}
}

23
path.hpp Normal file
View File

@@ -0,0 +1,23 @@
#ifndef PATH_H
#define PATH_H
#include <map>
#include <optional>
#include <string>
class Path {
private:
// std::string m_path;
std::string m_base;
std::string m_query;
std::map<std::string, std::string> m_variables;
public:
Path(std::string path);
std::optional<std::string> Get(std::string name);
std::string Query();
std::string Base();
void Match(std::string pattern);
};
#endif // !PATH_H

View File

@@ -8,7 +8,7 @@ bool Request::protocol(std::stringstream *ss, int *procPart, char c) {
m_method = ss->str();
break;
case 1:
m_path = ss->str();
m_pathRaw = ss->str();
break;
case 2:
m_protocol = ss->str();
@@ -24,7 +24,8 @@ bool Request::protocol(std::stringstream *ss, int *procPart, char c) {
return true;
}
Request::Request(std::vector<std::byte> buf) {
// This is shit
Request::Request(std::vector<std::byte> buf) : path("") {
std::string name;
std::stringstream ss;
bool header = true;
@@ -69,11 +70,12 @@ Request::Request(std::vector<std::byte> buf) {
ss << c;
}
}
path = Path{m_pathRaw};
}
void Request::Print() {
std::cout << "Protocol: " << m_protocol << "\n"
<< "Req: " << m_method << " " << m_path << std::endl;
<< "Req: " << m_method << " " << m_pathRaw << std::endl;
for (const auto &[key, value] : m_headers) {
std::cout << "[" << key << "]: [" << value << "]\n";
}

View File

@@ -1,5 +1,7 @@
#ifndef REQUEST_HEADER_H
#define REQUEST_HEADER_H
#include "path.hpp"
#include <map>
#include <sstream>
#include <string>
@@ -9,7 +11,7 @@ class Request {
private:
std::map<std::string, std::string> m_headers;
std::string m_method;
std::string m_path;
std::string m_pathRaw;
std::vector<std::byte> m_payload;
std::string m_protocol;
@@ -17,7 +19,8 @@ private:
bool protocol(std::stringstream *ss, int *procPart, char c);
public:
Request(std::vector<std::byte> buf);
Path path;
explicit Request(std::vector<std::byte> buf);
void Print();
bool HasData();
std::vector<std::byte> Data();

View File

@@ -7,13 +7,15 @@
#include <string>
#include <vector>
Response::Response(std::vector<std::byte> data) {
Response::Response(http::statusCode statusCode) { m_statusCode = statusCode; }
void Response::SetPayload(std::vector<std::byte> data) {
m_headers.insert(std::pair<std::string, std::string>(
"content-length", std::to_string(data.size())));
m_payload = data;
}
Response::Response(std::string data) {
void Response::SetPayload(std::string data) {
m_headers.insert(std::pair<std::string, std::string>(
"content-length", std::to_string(std::strlen(data.data()))));
@@ -28,7 +30,8 @@ void Response::SetContentType(const std::string type) {
void Response::Send(int clientSocket) {
std::stringstream ss;
ss << "HTTP/1.1 200 OK\n";
ss << "HTTP/1.1 " << m_statusCode << " "
<< http::StatusCodeString(m_statusCode) << "\n";
for (const auto &[key, value] : m_headers) {
ss << key << ": " << value << "\n";
}

View File

@@ -1,16 +1,23 @@
#ifndef RESPONSE_H
#define RESPONSE_H
#include "http.hpp"
#include <map>
#include <string>
#include <vector>
class Response {
private:
std::map<std::string, std::string> m_headers;
std::vector<std::byte> m_payload;
http::statusCode m_statusCode;
public:
Response(std::vector<std::byte> data);
Response(std::string data);
Response(http::statusCode statusCode);
// Response(std::vector<std::byte> data);
// Response(std::string data);
void SetPayload(std::vector<std::byte> data);
void SetPayload(std::string data);
void SetContentType(const std::string type);
void Send(int clientSocket);
void Print();

73
router.cpp Normal file
View File

@@ -0,0 +1,73 @@
#include "router.hpp"
#include <csignal>
#include <iostream>
#include <netinet/in.h>
#include <sys/socket.h>
#include <vector>
Router::Router(int port) {
m_socket = socket(AF_INET, SOCK_STREAM, 0);
m_address.sin_family = AF_INET;
m_address.sin_port = htons(port);
m_address.sin_addr.s_addr = INADDR_ANY;
}
int Router::Start() {
int err = bind(m_socket, (struct sockaddr *)&m_address, sizeof(m_address));
if (err != 0)
return err;
err = listen(m_socket, 5);
if (err != 0)
return err;
std::vector<std::byte> buffer(1024);
while (true) {
int client = accept(m_socket, nullptr, nullptr);
int read = recv(client, buffer.data(), buffer.size(), 0);
Request req(buffer);
Response res = Route(req);
res.Send(client);
close(client);
}
}
void Router::Handle(std::string pathPattern,
std::function<Response(Request, Response)> func) {
m_routes.insert_or_assign(pathPattern, func);
}
// This should be better
// Probably dont use map but a tree for it, then traverse tree for routing
Response Router::Route(Request req) {
for (const auto &[key, value] : m_routes) {
int pos = 0;
std::string path = req.path.Base();
std::string pattern = key;
std::string patternCopy = key;
bool found = false;
while (pos != -1) {
found = true;
pos = pattern.find('/');
std::string p = pattern.substr(0, pos);
int uPos = path.find('/');
std::string u = path.substr(0, uPos);
if (!p.starts_with('{') && p != u) {
found = false;
break;
}
pattern.erase(0, pos + 1);
path.erase(0, uPos + 1);
}
if (found) {
Response res(http::OK);
req.path.Match(patternCopy);
return value(req, res);
}
}
return Response(http::NOT_FOUND);
}

26
router.hpp Normal file
View File

@@ -0,0 +1,26 @@
#ifndef ROUTER_H
#define ROUTER_H
#include "request.hpp"
#include "response.hpp"
#include <functional>
#include <map>
#include <netinet/in.h>
#include <string>
class Router {
private:
std::map<std::string, std::function<Response(Request, Response)>> m_routes;
int m_socket;
sockaddr_in m_address;
Response Route(Request req);
public:
Router(int port);
void Handle(std::string pathPattern,
std::function<Response(Request, Response)> func);
int Start();
int Stop();
};
#endif // !ROUTER_H

View File

@@ -1,62 +0,0 @@
#include "request.hpp"
#include "response.hpp"
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <netinet/in.h>
#include <ostream>
#include <sys/socket.h>
#include <unistd.h>
#include <vector>
int main() {
int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in serverAddress;
serverAddress.sin_family = AF_INET;
serverAddress.sin_port = htons(8080);
serverAddress.sin_addr.s_addr = INADDR_ANY;
int err = bind(serverSocket, (struct sockaddr *)&serverAddress,
sizeof(serverAddress));
if (err != 0) {
std::cout << "error binding " << strerror(errno) << std::endl;
exit(1);
}
err = listen(serverSocket, 5);
if (err != 0) {
std::cout << "error listening " << strerror(errno) << std::endl;
exit(1);
}
std::vector<std::byte> buffer(1024);
while (true) {
int clientSocket = accept(serverSocket, nullptr, nullptr);
int read = recv(clientSocket, buffer.data(), buffer.size(), 0);
buffer.resize(read);
Request req(buffer);
Response res = Response("Hello world!");
if (req.HasData()) {
auto data = req.Data();
res = Response(data);
}
res.SetContentType("text/plain");
res.Send(clientSocket);
close(clientSocket);
}
if (shutdown(serverSocket, SHUT_RDWR) < 0) {
std::cout << "error shutdown " << strerror(errno) << std::endl;
exit(1);
}
if (close(serverSocket) < 0) {
std::cout << "error closing socket " << strerror(errno) << std::endl;
exit(1);
}
return 0;
}