diff --git a/Makefile b/Makefile index 516e5e5..9ab412a 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/http.hpp b/http.hpp new file mode 100644 index 0000000..4ec1a94 --- /dev/null +++ b/http.hpp @@ -0,0 +1,161 @@ +#ifndef STATUSCODE_H +#define STATUSCODE_H + +#include + +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 diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..01c1060 --- /dev/null +++ b/main.cpp @@ -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 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); +// } +// } diff --git a/path.cpp b/path.cpp new file mode 100644 index 0000000..621de70 --- /dev/null +++ b/path.cpp @@ -0,0 +1,37 @@ +#include "path.hpp" +#include + +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 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); + } +} diff --git a/path.hpp b/path.hpp new file mode 100644 index 0000000..18fb58c --- /dev/null +++ b/path.hpp @@ -0,0 +1,23 @@ +#ifndef PATH_H +#define PATH_H + +#include +#include +#include + +class Path { +private: + // std::string m_path; + std::string m_base; + std::string m_query; + std::map m_variables; + +public: + Path(std::string path); + std::optional Get(std::string name); + std::string Query(); + std::string Base(); + void Match(std::string pattern); +}; + +#endif // !PATH_H diff --git a/request.cpp b/request.cpp index 5d512b7..2effb5d 100644 --- a/request.cpp +++ b/request.cpp @@ -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 buf) { +// This is shit +Request::Request(std::vector buf) : path("") { std::string name; std::stringstream ss; bool header = true; @@ -69,11 +70,12 @@ Request::Request(std::vector 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"; } diff --git a/request.hpp b/request.hpp index 64d8df1..c933e89 100644 --- a/request.hpp +++ b/request.hpp @@ -1,5 +1,7 @@ #ifndef REQUEST_HEADER_H #define REQUEST_HEADER_H + +#include "path.hpp" #include #include #include @@ -9,7 +11,7 @@ class Request { private: std::map m_headers; std::string m_method; - std::string m_path; + std::string m_pathRaw; std::vector m_payload; std::string m_protocol; @@ -17,7 +19,8 @@ private: bool protocol(std::stringstream *ss, int *procPart, char c); public: - Request(std::vector buf); + Path path; + explicit Request(std::vector buf); void Print(); bool HasData(); std::vector Data(); diff --git a/response.cpp b/response.cpp index dd2f8da..e069269 100644 --- a/response.cpp +++ b/response.cpp @@ -7,13 +7,15 @@ #include #include -Response::Response(std::vector data) { +Response::Response(http::statusCode statusCode) { m_statusCode = statusCode; } + +void Response::SetPayload(std::vector data) { m_headers.insert(std::pair( "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( "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"; } diff --git a/response.hpp b/response.hpp index 59ce516..bc8a97e 100644 --- a/response.hpp +++ b/response.hpp @@ -1,16 +1,23 @@ #ifndef RESPONSE_H +#define RESPONSE_H +#include "http.hpp" #include #include #include + class Response { private: std::map m_headers; std::vector m_payload; + http::statusCode m_statusCode; public: - Response(std::vector data); - Response(std::string data); + Response(http::statusCode statusCode); + // Response(std::vector data); + // Response(std::string data); + void SetPayload(std::vector data); + void SetPayload(std::string data); void SetContentType(const std::string type); void Send(int clientSocket); void Print(); diff --git a/router.cpp b/router.cpp new file mode 100644 index 0000000..f29ece2 --- /dev/null +++ b/router.cpp @@ -0,0 +1,73 @@ +#include "router.hpp" +#include +#include +#include +#include +#include + +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 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 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); +} diff --git a/router.hpp b/router.hpp new file mode 100644 index 0000000..06a3da2 --- /dev/null +++ b/router.hpp @@ -0,0 +1,26 @@ +#ifndef ROUTER_H +#define ROUTER_H + +#include "request.hpp" +#include "response.hpp" +#include +#include +#include +#include + +class Router { +private: + std::map> m_routes; + int m_socket; + sockaddr_in m_address; + Response Route(Request req); + +public: + Router(int port); + void Handle(std::string pathPattern, + std::function func); + int Start(); + int Stop(); +}; + +#endif // !ROUTER_H diff --git a/server.cpp b/server.cpp deleted file mode 100644 index 29311ca..0000000 --- a/server.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#include "request.hpp" -#include "response.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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 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; -}