From 183c79a048cca8ed81f9d5d172ba701c20e83ad8 Mon Sep 17 00:00:00 2001 From: Uriah Shaul Mandel <43012268+UriahShaulMandel@users.noreply.github.com> Date: Sat, 29 Jan 2022 18:03:50 +0200 Subject: [PATCH] Delete src/chttp directory --- src/chttp/RequestHandler.cpp | 60 ------- src/chttp/Router.cpp | 99 ------------ src/chttp/Server.cpp | 184 --------------------- src/chttp/Threadpool/Threadpool.cpp | 102 ------------ src/chttp/data/GetRequest.cpp | 53 ------ src/chttp/data/HttpRequest.cpp | 225 -------------------------- src/chttp/data/HttpResponse.cpp | 130 --------------- src/chttp/data/PostRequest.cpp | 60 ------- src/chttp/util/MultipartField.cpp | 95 ----------- src/chttp/util/Socket.cpp | 240 ---------------------------- src/chttp/util/Url.cpp | 65 -------- 11 files changed, 1313 deletions(-) delete mode 100644 src/chttp/RequestHandler.cpp delete mode 100644 src/chttp/Router.cpp delete mode 100644 src/chttp/Server.cpp delete mode 100644 src/chttp/Threadpool/Threadpool.cpp delete mode 100644 src/chttp/data/GetRequest.cpp delete mode 100644 src/chttp/data/HttpRequest.cpp delete mode 100644 src/chttp/data/HttpResponse.cpp delete mode 100644 src/chttp/data/PostRequest.cpp delete mode 100644 src/chttp/util/MultipartField.cpp delete mode 100644 src/chttp/util/Socket.cpp delete mode 100644 src/chttp/util/Url.cpp diff --git a/src/chttp/RequestHandler.cpp b/src/chttp/RequestHandler.cpp deleted file mode 100644 index bdfd8d1..0000000 --- a/src/chttp/RequestHandler.cpp +++ /dev/null @@ -1,60 +0,0 @@ -/** - * @file - * @author Yotam Shvartsun - * @version 1.0 - * @section DESCRIPTION - * This file contains the implementation for the RequestHandler class, which - * represent a route and its user-defined handler - */ -#include - -#include -#include -#include -#include - -RequestHandler::RequestHandler(RequestType_t type, - RequestHandlerFunction handler, - const Url &urlMatch) - : urlMatch(urlMatch) { - this->requestType = type; - this->handler = std::move(handler); -} - -bool RequestHandler::IsMatching( - const std::shared_ptr &request) const { - // Check what is the request type: dynamic_cast will return nullptr if the - // casting failed - if (dynamic_cast(request.get())) - if (this->requestType != RequestType_t::GET) - return false; - if (dynamic_cast(request.get())) - if (this->requestType != RequestType_t::POST) - return false; - - return this->urlMatch.IsMatch(request->GetUrl()); -} - -void RequestHandler::operator()(const std::shared_ptr &request, - std::shared_ptr response) { - if (this->IsMatching(request)) { - request->PopulateParams(this->urlMatch); - this->handler(request, std::move(response)); - } -} - -std::unordered_map -RequestHandler::CreateParamMap(std::string url) { - url = url.substr(1); - std::unordered_map result; - std::istringstream iss(url); - std::string item; - int i = 0; - while (std::getline(iss, item, '/')) { - if (item[0] == ':') { - result[i] = item.substr(1); - } - i++; - } - return result; -} diff --git a/src/chttp/Router.cpp b/src/chttp/Router.cpp deleted file mode 100644 index facf1aa..0000000 --- a/src/chttp/Router.cpp +++ /dev/null @@ -1,99 +0,0 @@ -/** - * @file - * @author Yotam Shvartsun - * @version 1.0 - * @section DESCRIPTION - * This file contains the implementation for the Router class - */ -#include - -#include -#include - -Router *Router::instance = nullptr; - -Router *Router::GetInstance(std::vector handlers) { - // if the instance already exists, return the existing one - if (Router::instance != nullptr) - return Router::instance; - Router::instance = new Router(std::move(handlers)); - return Router::instance; -} - -Router *Router::GetInstance() { - // if the instance already exists, return the existing one - if (Router::instance != nullptr) - return Router::instance; - throw std::runtime_error("Object not initialized"); -} - -Router *Router::GetInstance( - const std::vector &routes, - const std::function, - std::shared_ptr)> ¬FoundHandler, - const std::function, - std::shared_ptr, std::string)> - &errorHandler) { - // if the instance already exists, return the existing one - if (Router::instance != nullptr) - return Router::instance; - Router::instance = new Router(routes, notFoundHandler, errorHandler); - return Router::instance; -} - -void Router::Route(const std::shared_ptr &req, - const std::shared_ptr &res) { - // check if the routes match, and execute the function - for (auto handler : this->routes) { - if (handler.IsMatching(req)) { - try { - handler(req, res); - } catch (CHttpError &e) { - this->errorHandler(req, res, e.what()); - res->SetStatus(e.GetErrorCode()); - } catch (std::exception &e) { - std::cerr << "Error thrown while serving " << req->GetUrl() << std::endl << "Error message: " << e.what() << std::endl; - this->errorHandler(req, res, "Something went wrong!"); - } - return; - } - } - this->notFoundHandler(req, res); -} - -Router::Router(std::vector routes, - RequestHandlerFunction notFoundHandler, - std::function, - std::shared_ptr, std::string)> - errorHandler) { - this->routes = std::move(routes); - this->errorHandler = std::move(errorHandler); - this->notFoundHandler = std::move(notFoundHandler); -} - -Router::Router(std::vector routes) { - this->routes = std::move(routes); - this->notFoundHandler = Router::DefaultNotFound; - this->errorHandler = Router::DefaultError; -} - -void Router::DefaultNotFound(const std::shared_ptr &req, - const std::shared_ptr &res) { - std::string response = "Url '" + req->GetUrl() + "' was not found"; - std::vector responseBytes(response.begin(), response.end()); - res->SetStatus(HTTP_STATUS::Not_Found); - res->Raw(responseBytes); -} - -void Router::DefaultError(const std::shared_ptr &req, - const std::shared_ptr &res, - const std::string &err) { - std::string responseStr = err; - std::vector responseBytes(responseStr.begin(), responseStr.end()); - res->SetStatus(HTTP_STATUS::Internal_Server_Error); - res->Raw(responseBytes); -} -void Router::AddHandler(const RequestHandler &route) { - this->routes.push_back(route); -} -void Router::Reset() { Router::instance = nullptr; } diff --git a/src/chttp/Server.cpp b/src/chttp/Server.cpp deleted file mode 100644 index f28650a..0000000 --- a/src/chttp/Server.cpp +++ /dev/null @@ -1,184 +0,0 @@ -/** - * @file - * @author Yotam Shvartsun - * @version 1.0 - * @section DESCRIPTION - * This file contains the implementation for the Server class, which is the main - * class of CHttp - */ -#include - -#ifdef __linux__ -#include -#include -#include -#else -#include - -#include -#endif - -void Server::OnClient(Router *router, - const std::shared_ptr &clientSocket) { - std::vector data; - char thisChar = '\0', lastChar; - size_t numberOfNewlines = 0; - bool firstLine = true; - std::shared_ptr req; - RequestType_t type = RequestType_t::GET; - // Read the request char by char, until there is a \r\n\r\n sequence - while (numberOfNewlines != 2) { - lastChar = thisChar; - try { - thisChar = clientSocket->GetData(1)[0]; - } catch ( - std::runtime_error &) {// If the socket was closed by the client, stop - // reading from it and close the connection - return; - } - data.push_back(thisChar); - // a new line - if (lastChar == '\r' && thisChar == '\n') { - numberOfNewlines++; - // Find out what is the request type - if (firstLine) { - firstLine = false; - std::string asString(data.begin(), data.end()); - if (asString.find("GET") != std::string::npos) { - type = RequestType_t::GET; - } else if (asString.find("POST") != std::string::npos) { - type = RequestType_t::POST; - } else { - type = RequestType_t::Unknown; - } - } - } else { - if (lastChar != '\n') { - numberOfNewlines = 0; - } - } - } - // If it's a post request, read the body as well, and create an instance - if (RequestType_t::POST == type) { - auto contentSizeRaw = HttpRequest::ParseHTTPHeaders(data)["content-length"]; - std::size_t contentSize = - std::stoll(contentSizeRaw); - std::vector body = clientSocket->GetData(contentSize); - if (contentSize != body.size()) - throw std::runtime_error("Invalid post"); - data.insert(data.end(), body.begin(), body.end()); - req = std::make_shared(data); - } else if (RequestType_t::GET == type) - req = std::make_shared(data); - - std::shared_ptr res; - res = std::make_shared(); - if (type != RequestType_t::Unknown) { - // Serve the client - router->Route(req, res); - // Send the result - } else { - res->SetStatus(HTTP_STATUS::Method_Not_Allowed); - } - clientSocket->SendData(res->Format()); -} -std::function, std::shared_ptr)> -Server::StaticFileHandler() { -#ifdef _WIN32 - const char PATH_SEP = '\\'; -#else - const char PATH_SEP = '/'; -#endif - return [this, PATH_SEP](const std::shared_ptr &request, - const std::shared_ptr &response) { - std::string requestUrl = request->GetUrl(); - std::string filePath = - this->staticFolderPath + PATH_SEP + - requestUrl.substr(requestUrl.find(this->staticFolderUrl) + - this->staticFolderUrl.size()); - response->SendFile(filePath); - }; -} -Server::Server() { - std::vector defaultHandlers; - this->router = Router::GetInstance(defaultHandlers); - this->pool = Threadpool::GetInstance(&this->ClientHandler, this->router); -} - -void Server::Run(int port, const std::function OnStart) { - std::shared_ptr client; - this->port = port; - this->server.Bind(this->port); - this->server.Listen(); - OnStart(); - while (true) { - client = std::make_shared(this->server.Accept()); - this->pool->AddWork(client); - } -} -void Server::ServeStaticFolder(std::string url, std::string folderPath) { - this->staticFolderUrl = std::move(url); - // Get absolute path based on the relative path supplied -#ifdef __linux__ - char *pathBuffer = const_cast(folderPath.c_str()); - char *absolutePath[PATH_MAX + 1] = {0}; - char *ptr = realpath(pathBuffer, reinterpret_cast(absolutePath)); - // realpath will return NULL for non-successful operation - if (NULL == ptr) { - std::cerr << "Directory " << folderPath << " not found!" << std::endl; - exit(1); - } - folderPath = std::string(ptr); -#else - std::replace(folderPath.begin(), folderPath.end(), '/', '\\'); - char *relativePath = const_cast(folderPath.c_str()); - char abdolutePath[MAX_PATH] = {0}; - if (GetFullPathNameA(relativePath, MAX_PATH, abdolutePath, nullptr) == 0) { - std::cerr << "Directory " << folderPath << " not found!" << std::endl; - exit(1); - } - folderPath = std::string(abdolutePath); -#endif - this->staticFolderPath = folderPath; - Url urlMatch(this->staticFolderUrl + "/:*"); - RequestHandler handler(RequestType_t::GET, this->StaticFileHandler(), - urlMatch); - this->router->AddHandler(handler); -} - -Server::~Server() { - this->server.Close(); - delete this->pool; - delete this->router; - Threadpool::Reset(); - Router::Reset(); -} -void Server::Get(std::string urlTemplate, - const RequestHandlerFunction &function, - const std::vector &middlewares) { - Url urlMatch(std::move(urlTemplate)); - RequestHandlerFunction actualFunction = - [middlewares, function](const std::shared_ptr &req, - const std::shared_ptr &res) { - for (const auto &middleware : middlewares) - middleware(req, res); - function(req, res); - }; - RequestHandler handler(RequestType_t::GET, std::move(actualFunction), - urlMatch); - this->router->AddHandler(handler); -} -void Server::Post(std::string urlTemplate, - const RequestHandlerFunction &function, - const std::vector &middlewares) { - Url urlMatch(std::move(urlTemplate)); - RequestHandlerFunction actualFunction = - [middlewares, function](const std::shared_ptr &req, - const std::shared_ptr &res) { - for (const auto &middleware : middlewares) - middleware(req, res); - function(req, res); - }; - RequestHandler handler(RequestType_t::POST, actualFunction, urlMatch); - this->router->AddHandler(handler); -} diff --git a/src/chttp/Threadpool/Threadpool.cpp b/src/chttp/Threadpool/Threadpool.cpp deleted file mode 100644 index 4dc0547..0000000 --- a/src/chttp/Threadpool/Threadpool.cpp +++ /dev/null @@ -1,102 +0,0 @@ -/** - * @file - * @author Yotam Shvartsun - * @version 1.0 - * @section DESCRIPTION - * This file contains the implementation of the Threadpool class, which (as its - * name suggests) is the CHttp version of the well-known design pattern - * 'thread-pool' - */ -#include - -Threadpool *Threadpool::instance = nullptr; - -Threadpool::Threadpool( - const std::function)> - &socketFunction, - Router *router) { - using std::placeholders::_1; - this->numTasks = 0; - this->socketHandler = std::bind(socketFunction, router, _1); - size_t workerNumber = std::thread::hardware_concurrency(); - /** - * This function (stored in worker) is the main function run by the workers - * It waits for a new client, and then tries to serve it, using the - * ``Threadpool::socketHandler`` - */ - auto worker = [&] { - std::shared_ptr client; - for (;;) { - std::unique_lock lock(this->clientMutex); - // Check if the lock is free and if the worker can serve clients (there - // are clients in the line and the pool is running) - this->canExecuteWorker.wait(lock, [&] { - return !this->isPoolRunning || !this->clientQueue.empty(); - }); - if (this->isPoolRunning) { - client = this->clientQueue.front(); - this->clientQueue.pop(); - // After finishing everything that should be done with the clientQueue, - // the lock can be freed - lock.unlock(); - if (!client->IsClosed()) { // If the socket is already closed (for some - // reason) skip this client - this->socketHandler(client); // Handle the client - client->Close(); // Close the resources - this->numTasks--; // Task completed! - } - } else - return; - } - }; - std::thread t; - for (int i = 0; i < workerNumber; i++) { - t = std::thread(worker); - this->threads.push_back(std::move(t)); - } - std::cout << "[INFO]: Threadpool runing" << std::endl; -} - -Threadpool::~Threadpool() { - this->clientMutex.lock(); - // Empty the client queue - auto tmp = std::queue>(); - this->clientQueue.swap(tmp); - this->numTasks = 0; - // The pool is no longer running - this->isPoolRunning = false; - // Allow all workers to die - this->clientMutex.unlock(); - this->canExecuteWorker.notify_all(); - // Clear the worker threads - this->threads.clear(); -} - -Threadpool *Threadpool::GetInstance( - std::function)> *socketFunction, - Router *router) { - if (router == nullptr || socketFunction == nullptr) { - if (Threadpool::instance != nullptr) { - return Threadpool::instance; - } - // If no instance exists, the arguments must supply a way to create it! - throw std::invalid_argument( - "Invalid arguments - Router/socketFunctions cannot be nullptr!"); - } - Threadpool::instance = new Threadpool(*socketFunction, router); - return Threadpool::instance; -} - -void Threadpool::AddWork(const std::shared_ptr &client) { - if (!this->isPoolRunning) - throw std::runtime_error("Pool not started!"); - // Add client to queue - this->clientMutex.lock(); - this->clientQueue.push(client); - this->clientMutex.unlock(); - // Allow one worker to handle the client - this->canExecuteWorker.notify_one(); - this->numTasks++; -} - -void Threadpool::Reset() { Threadpool::instance = nullptr; } diff --git a/src/chttp/data/GetRequest.cpp b/src/chttp/data/GetRequest.cpp deleted file mode 100644 index 907d0e2..0000000 --- a/src/chttp/data/GetRequest.cpp +++ /dev/null @@ -1,53 +0,0 @@ -/** - * @file - * @author Yotam Shvartsun - * @version 1.0 - * @section DESCRIPTION - * This file contains the implementation of the GetRequest class -*/ -#include "chttp/data/GetRequest.h" - -#include -#include -#include - -GetRequest::GetRequest(std::vector data) { - this->FromString(std::move(data)); -} - -void GetRequest::FromString(std::vector data) { - std::size_t firstSpace = 0; - std::size_t lastSpace = 0; - std::string asString(data.begin(), data.end()); - std::string firstLine = asString.substr(0, asString.find("\r\n")); // METHOD URL PROTO - if (firstLine.find("GET") == std::string::npos) // make sure this is a GET request - { - throw std::runtime_error("Request is not a valid GET request!"); - } - // Get the url - firstSpace = firstLine.find_first_of(' '); - lastSpace = firstLine.find_last_of(' ') - 1; - if (firstSpace != std::string::npos) { - this->rawUrl = firstLine.substr(firstSpace + 1, lastSpace - firstSpace); - } else { -#ifdef DEBUG - std::cerr << "Request does not match the HTTP request format" << firstLine << std::endl; -#endif - throw std::runtime_error("Request is not a valid HTTP request!"); - } - this->BuildQuery(rawUrl); - std::string headers = asString.substr(firstLine.length()); -#ifdef DEBUG - std::cout << "Headers: " << std::endl << headers << std::endl; -#endif - std::vector tmp(headers.begin(), headers.end()); - this->headers = HttpRequest::ParseHTTPHeaders(tmp); -} - -GetRequest &GetRequest::operator=(const GetRequest &other) { - std::copy(other.rawUrl.begin(), other.rawUrl.end(), this->rawUrl.begin()); - this->query = other.query; - this->headers = other.headers; - this->urlParams = other.urlParams; - return *this; -} \ No newline at end of file diff --git a/src/chttp/data/HttpRequest.cpp b/src/chttp/data/HttpRequest.cpp deleted file mode 100644 index 10deb21..0000000 --- a/src/chttp/data/HttpRequest.cpp +++ /dev/null @@ -1,225 +0,0 @@ -/** - * @file - * @author Yotam Shvartsun - * @version 1.0 - * @section DESCRIPTION - * This file contains the implementation of the HttpRequest non-virtual - * functions In this file there are also helper functions used by the CHttp - * library, in order to preform repetetive string relaeted actions - */ -#include -#include -#include -#include - -HttpRequest::HttpRequest() = default; - -/** - * Splits data to two parts: everything before del and everything after it - * @throws std::out_of_range when data doesn't contain del - * @param data string to split - * @param del delimiter to split by - * @return The splitted string - */ -std::vector SplitOnce(std::string &data, const std::string &del) { - std::string tmp = data.substr(0, data.find(del, 0)); - std::vector res; - res.push_back(tmp); - res.push_back(data.substr(tmp.length() + 1)); - return res; -} - -/** - * @brief Splits string using one of the delimiters given in - * @param data: data to split - * @param delimiters: optional delimiters to split by - * @return Splitted data - */ -std::vector SplitOptional(std::string &data, - const std::string &delimiters) { - std::vector result; - std::size_t pos, prev = 0; - while ((pos = data.find_first_of(delimiters, prev)) != std::string::npos) { - if (pos > prev) - result.push_back(data.substr(prev, pos - prev)); - prev = pos + 1; - } - if (prev < data.length()) - result.push_back(data.substr(prev, std::string::npos)); - - return result; -} - -void HttpRequest::BuildQuery(std::string url) { -#ifdef DEBUG - std::cout << "In BuildQuery" << std::endl - << "this: " << this << std::endl - << "url: " << url << std::endl; -#endif - std::string queryRaw; - std::vector querySplitted; - std::vector urlSplitted; - std::string tmp; - std::size_t fragmentPos; - std::vector queryTmp; - std::string key, value; - - urlSplitted = SplitOptional(url, "?"); - queryRaw = urlSplitted.back(); - // Remove the fragment from the url - fragmentPos = queryRaw.find('#'); - if (fragmentPos != std::string::npos) { - queryRaw.erase(fragmentPos); - } -#ifdef DEBUG - std::cout << "Url raw query string: " << queryRaw << std::endl; -#endif - // split the query using the delimiters of a query string - //! @note that the query string is defined as everything between the ? to a # - //! or the end of the url - //! @see RFC 3986, - //! section 3.4 - querySplitted = SplitOptional(queryRaw, "&;"); - if (querySplitted.size() == 1 && querySplitted[0] == queryRaw) - return; - - for (auto &queryPart : querySplitted) { - //! note that = is not allowed as part of the query data (for example, this - //! url is not allowed: /base/page?p1=a==) - // ref: RFC 3986 section 3.4 - queryTmp = SplitOptional(queryPart, "="); - if (queryTmp.size() == - 1) // in case there is a url like this: proto:path?a&b=5, a="" - { - key = queryTmp.front(); - value = ""; - } else { - key = queryTmp.front(); - value = queryTmp.back(); - } - this->query.insert(std::pair(key, value)); - } -} - -bool HttpRequest::IsInUrlParams(const std::string &key) const { - return this->urlParams.find(key) != this->urlParams.end(); -} - -std::string HttpRequest::GetUrl() const { return this->rawUrl; } - -void HttpRequest::PopulateParams(const Url &urlSpec) { - // If the url dose not match the template, throw an error and stop - if (!urlSpec.IsMatch(this->rawUrl)) - throw std::runtime_error("Url given dose not match to url spec"); - - std::unordered_map spec = urlSpec.GetUrlParamSpec(); - std::vector splittedUrl = SplitOptional(this->rawUrl, "/"); - for (size_t i = 0; i < splittedUrl.size(); i++) { - if (spec.find(i) != spec.end()) { - this->urlParams.insert( - std::pair(spec.at(i), splittedUrl[i])); -#ifdef DEBUG - std::cout << "Key: " << spec.at(i) << " Value: " << splittedUrl[i] - << std::endl; -#endif - } - } -} - -std::unordered_map -HttpRequest::ParseHTTPHeaders(std::vector data) { - std::string asString(data.begin(), data.end()); - std::vector lines, filteredLines; - std::unordered_map res; - std::size_t prev = 0, pos; - // Split the request to lines - while ((pos = asString.find("\r\n", prev)) != std::string::npos) { - if (asString[prev] == '\n') { - prev++; - } - if (pos > prev) - lines.push_back(asString.substr(prev, pos - prev)); - prev = pos + 1; - } - // also include the last line - if (prev < asString.length()) - lines.push_back(asString.substr(prev, std::string::npos)); -#ifdef DEBUG - for (auto &l : lines) - std::cout << l << std::endl; -#endif - // filter the lines to the headers part (contains ':') - for (auto &l : lines) { - if (l == "\n") - continue; // \n means this is the last line - if (l.find(':') != std::string::npos) - filteredLines.push_back(l); - } - -#ifdef DEBUG - std::cout << "Filtered lines:" << std::endl; - for (auto &l : filteredLines) - std::cout << l << std::endl; -#endif - std::vector tmp; - std::string headerName; - for (auto &l : filteredLines) { - /** - * A valid HTTP header must have a colon in it, which seperates between the - * field name and the value - * @see RFC 2616, - * section 4.2 - */ - tmp = SplitOnce(l, ":"); - headerName = tmp.front(); - if(headerName[0] == '\n'){ - headerName = headerName.substr(1); - } - std::transform(headerName.begin(), headerName.end(), headerName.begin(), ::tolower); - res.insert(std::make_pair(headerName, tmp.back())); - } -#ifdef DEBUG - std::cout << "RESULT:" << std::endl; - for (auto &i : res) - std::cout << i.first << ":" << i.second << std::endl; -#endif - return res; -} - -std::unordered_map HttpRequest::GetHeaders() const { - return this->headers; -} - -std::map HttpRequest::GetQuery() const { - return this->query; -} - -std::string HttpRequest::GetUrlParam(const std::string &name) const { - if (!this->IsInUrlParams(name)) // if the parameter is not in the list, throw - // runtime error instead of the out_of_range - throw std::runtime_error("Parameter named " + name + - " was not found in the url"); - return this->urlParams.at(name); -} -void HttpRequest::AddAdditional(std::string key, std::string value) { -#ifdef DEBUG - if (this->additionalData.find(key) != this->additionalData.end()) { - std::cout << "Key named " << key << " alredy exists, overwriting" - << std::endl; - } -#endif - this->additionalData[key] = std::move(value); -} -std::string HttpRequest::GetAdditional(const std::string &key) { - return this->additionalData[key]; -} -bool HttpRequest::IsInHeaders(std::string headerName) { - std::transform(headerName.begin(), headerName.end(), headerName.begin(), ::tolower); - return this->headers.find(headerName) != this->headers.end(); -} -std::string HttpRequest::GetHeaderByName(std::string requestedHeader) { - std::transform(requestedHeader.begin(), requestedHeader.end(), requestedHeader.begin(), ::tolower); - if(!this->IsInHeaders(requestedHeader)) - throw std::runtime_error("Header not found"); - return this->headers.at(requestedHeader); -} diff --git a/src/chttp/data/HttpResponse.cpp b/src/chttp/data/HttpResponse.cpp deleted file mode 100644 index aceb072..0000000 --- a/src/chttp/data/HttpResponse.cpp +++ /dev/null @@ -1,130 +0,0 @@ -/** - * @file - * @author Yotam Shvartsun - * @version 1.0 - * @section DESCRIPTION - * This file contains the implementation of the HttpResponse class, and - * functions to handle file reading - */ -#include -#include -#include -// Since file op.s require using syscalls, the headers are different between -// OSs. -#ifndef _WIN32 -#include -#else -#include -#include -// define access to avoid changing the FileExists function -#define access _access_s -#endif - -#include - -#include -#include - -/** - * Check if file in path exists - * @param path path to check - * @return bool - */ -bool FileExists(const std::string &path) { - return access(path.c_str(), 0) == 0; -} - -std::string HttpResponse::BuildHeaders() { - std::string - raw;// it's much easier to do this as a number of string operations - for (auto &pair : this->headers) { - raw += pair.first + ": " + pair.second + "\r\n"; - } - for (auto &cookiePair : this->newCookies) { - raw += "Set-Cookie: " + cookiePair.first + "=" + cookiePair.second + "\r\n"; - } - return raw; -} - -HttpResponse::HttpResponse() : status(OK), dataSet(false) {} - -void HttpResponse::SetStatus(HTTP_STATUS code) { this->status = code; } - -void HttpResponse::Header(const std::string &key, const std::string &value) { - this->headers.insert(std::make_pair(key, value)); -} - -void HttpResponse::SendFile(const std::string &path) { - // If request body already set, throw error - if (this->dataSet) - throw std::logic_error("Body already set!"); - std::string pathTmp = path; -#ifdef _WIN32 - std::replace(pathTmp.begin(), pathTmp.end(), '/', '\\'); -#endif - // If the file requested dose not exists, throw FileNotFound error - if (!FileExists(pathTmp)) - throw std::runtime_error("File not found in path specified!"); - - // read the file and set Content-Type - std::string ext = pathTmp.substr(path.find_last_of('.')); - std::ifstream file(pathTmp, std::ios::binary); - this->payload = std::vector((std::istreambuf_iterator(file)), - std::istreambuf_iterator()); - this->Header("Content-type", HttpResponse::MIME_TYPES.at(ext)); - this->dataSet = true; -} - -void HttpResponse::Raw(const std::vector &data) { - if (this->dataSet) - throw std::logic_error("Body already set!"); - this->payload = data; - this->dataSet = true; -} - -std::vector HttpResponse::Format() { - // Set the Content-Length header - this->Header("Content-Length", std::to_string(this->payload.size())); - // Build the status line - std::string responseMeta = "HTTP/1.1 " + std::to_string(this->status) + " " + - HttpResponse::HttpCodeStrings.at(this->status) + - "\r\n"; - std::vector response; - - responseMeta += this->BuildHeaders(); - responseMeta += "\r\n"; - // Add the body - response = std::vector(responseMeta.begin(), responseMeta.end()); - response.insert(response.end(), this->payload.begin(), this->payload.end()); - // HTTP response ends with \r\n\r\n - response.push_back('\r'); - response.push_back('\n'); - response.push_back('\r'); - response.push_back('\n'); - return response; -} - -bool HttpResponse::isSet() const { return this->dataSet; } - -void HttpResponse::Redirect(std::string url) { - this->Header("Location", url); - this->SetStatus(HTTP_STATUS::Found); - this->dataSet = true; -} -void HttpResponse::Raw(std::string body) { - std::vector asVector(body.begin(), body.end()); - this->Raw(asVector); -} -void HttpResponse::RemoveCookie(const std::string &cookieName) { - if (this->newCookies.find(cookieName) == this->newCookies.end()) { - this->newCookies.insert(std::make_pair(cookieName, " ; Max-Age=-1")); - } else { - this->newCookies.erase(cookieName); - } -} -void HttpResponse::AddCookie(std::string cookieName, std::string cookieValue, int maxAge) { - this->newCookies[cookieName] = cookieValue + " ; Max-Age: " + std::to_string(maxAge); -} -void HttpResponse::AddCookie(std::string cookieName, std::string cookieValue) { - this->newCookies[cookieName] = cookieValue; -} diff --git a/src/chttp/data/PostRequest.cpp b/src/chttp/data/PostRequest.cpp deleted file mode 100644 index 573e556..0000000 --- a/src/chttp/data/PostRequest.cpp +++ /dev/null @@ -1,60 +0,0 @@ -/** - * @file - * @author Yotam Shvartsun - * @version 1.0 - * @section DESCRIPTION - * This file contains the implementation of the PostRequest class - */ -#include -#include -#include -#include - -void PostRequest::FromString(std::vector data) { - std::size_t firstSpace = 0; - std::size_t lastSpace = 0; - std::string asString(data.begin(), data.end()); - std::string firstLine = asString.substr(0, asString.find("\r\n")); - // A post request must contain "POST" in the request line (aka first line) - if (firstLine.find("POST") == std::string::npos) { - throw std::runtime_error("Request is not a valid POST request!"); - } - // Get the url - firstSpace = firstLine.find_first_of(' '); - lastSpace = firstLine.find_last_of(' ') - 1; - if (firstSpace != std::string::npos) { - this->rawUrl = firstLine.substr(firstSpace + 1, lastSpace - firstSpace); - } else { - throw std::runtime_error("Request is not a valid HTTP request!"); - } - this->BuildQuery(rawUrl); - // Parse the headers - std::size_t headersEnd = asString.find("\r\n\r\n"); - std::string headers = - asString.substr(firstLine.length(), headersEnd - firstLine.length()); - std::vector tmp(headers.begin(), headers.end()); - this->headers = HttpRequest::ParseHTTPHeaders(tmp); - try { - // Parse the body - long bodySize = std::stol(this->headers.at("content-length")); - std::string bodyString = asString.substr(headersEnd + 4, bodySize); - this->body = std::vector(bodyString.begin(), bodyString.end()); - } catch (std::exception &e) { -#ifdef DEBUG - std::cerr << "POST: " << e.what() << std::endl; -#endif - } -} - -PostRequest &PostRequest::operator=(const PostRequest &other) { - // deep copy the body - std::copy(other.rawUrl.begin(), other.rawUrl.end(), this->rawUrl.begin()); - this->query = other.query; - this->headers = other.headers; - this->urlParams = other.urlParams; - return *this; -} - -PostRequest::PostRequest(std::vector data) { this->FromString(data); } - -std::vector PostRequest::GetBody() const { return this->body; } diff --git a/src/chttp/util/MultipartField.cpp b/src/chttp/util/MultipartField.cpp deleted file mode 100644 index 5f66d95..0000000 --- a/src/chttp/util/MultipartField.cpp +++ /dev/null @@ -1,95 +0,0 @@ -/** - * @file - * @author Yotam Shvartsun - * @version 1.0 - * @section DESCRIPTION - * This file contains the implementation for the MultipartField class, which - * represents a multipart/form-data field in a POST request - */ -#include "chttp/util/MultipartField.h" - -#include -#include -#include -#include - -/** - * @brief This function parses multipart/form-data of a single file object - * @param rawData raw multipart data of a single field, from boundary to - * boundary - */ -void MultipartField::FromMultipart(std::vector rawData, - const std::string &contentType) { - std::string s(rawData.begin(), rawData.end()); - // Get the boundary: - std::string boundary = contentType.substr(contentType.find(';') + 1); - boundary = boundary.substr(boundary.find("boundary=") + 9); - auto start = std::search(rawData.begin(), rawData.end(), boundary.begin(), - boundary.end()); - auto end = std::search(start + boundary.size(), rawData.end(), - boundary.begin(), boundary.end()); - std::vector fileData(start, end); - std::string newline("\r\n\r\n"); - auto headerEnd = std::search(fileData.begin(), fileData.end(), - newline.begin(), newline.end()); - std::vector headerVector(fileData.begin() + boundary.size(), headerEnd); - std::string headerString(headerVector.begin(), headerVector.end()); - std::istringstream iss(headerString); - std::vector tmpHeaders; - std::string token; - while (std::getline(iss, token, '\n')) { - tmpHeaders.push_back(token); - } - std::string type, hData; - std::vector contentDisposition; - for (auto &header : tmpHeaders) { - if (header.size() == 1) - continue; - type = header.substr(0, header.find(':')); - hData = header.substr(header.find(':')); - /** - * The 'Content-Disposition' header stores the field's name, and some other - * 'sub-headers', so it must be parsed - */ - if (type.find("Content-Disposition") != std::string::npos) { - iss = std::istringstream(hData.substr(2)); - while (std::getline(iss, token, ';')) { - contentDisposition.push_back(token); - } - for (auto &content : contentDisposition) { - if (content == "form-data") - continue; - std::string contentCopy(contentType); - if (contentCopy.find("name") != std::string::npos && - contentCopy.find("file") == std::string::npos) { - this->fieldName = contentCopy.substr(contentCopy.find('=')); - } else { - this->headers.insert( - std::make_pair(contentCopy.substr(0, contentCopy.find('=')), - contentCopy.substr(contentCopy.find('=')))); - } - } - } else { - this->headers.insert( - (std::make_pair(header.substr(0, header.find(':')), - header.substr(header.find(':') + 1)))); - } - } - // Set the body - everything except the headers - this->data = - std::vector(headerEnd + newline.size(), fileData.end() - 4); -} - -MultipartField::MultipartField(std::vector data, - const std::string &contentType) { - this->FromMultipart(std::move(data), contentType); -} - -const std::string &MultipartField::getFieldName() const { return fieldName; } - -const std::unordered_map & -MultipartField::getHeaders() const { - return headers; -} - -std::vector MultipartField::getData() const { return this->data; } diff --git a/src/chttp/util/Socket.cpp b/src/chttp/util/Socket.cpp deleted file mode 100644 index fc2629c..0000000 --- a/src/chttp/util/Socket.cpp +++ /dev/null @@ -1,240 +0,0 @@ -/** - * @file - * @author Yotam Shvartsun - * @version 1.0 - * @section DESCRIPTION - * This file contains the implementation of the Socket class - a part of the - * chttp framework. The Socket class is a RAII wrapper class for a TCP socket - - * when the destructor of this class is called, the socket it represents is - * closed. - * @note Since this is a part of a server framework, this class will not support - * any type of client side operations (`connect`, .etc). - */ -#define DEBUG -#include - -#include -#ifdef DEBUG -#ifndef _WIN32 -#include // in order to print the IP address of a client -#endif -#endif - -#ifdef _WIN32 -bool Socket::ShouldCallWSAStartup = true; -#endif - -// constructor -Socket::Socket() : referenceCount() { -#ifdef _WIN32 - if (Socket::ShouldCallWSAStartup) { - WSADATA ws; - if (WSAStartup(MAKEWORD(2, 2), &ws) != NO_ERROR) { - wprintf(L"Error at WSAStartup()\n"); - exit(-1); - } - Socket::ShouldCallWSAStartup = false; - } -#endif // _WIN32 - - this->referenceCount.store(new int(1)); - this->sockfd = - ::socket(AF_INET, SOCK_STREAM, 0); // create the file descriptor - if (this->sockfd < 0) - throw std::system_error(errno, std::system_category(), - "Unable to create socket!"); -} - -// constructor -Socket::Socket(const Socket &other) : referenceCount() { - this->referenceCount.store(other.referenceCount.load()); - (*this->referenceCount.load())++; - this->sockfd = other.sockfd; -} - -// constructor -Socket::Socket(SOCKET fileDes) : referenceCount() { - this->referenceCount.store(new int(1)); - this->sockfd = fileDes; -} - -// destructor -Socket::~Socket() { - (*this->referenceCount.load())--; - if ((*this->referenceCount.load()) == 0) { - this->Close(); - delete this->referenceCount.load(); - this->referenceCount.store(nullptr); - } -} - -void Socket::Bind(int port) { - if (this->isBound) { - // If trying to bind a different port, throw an error - a socket can only - // bind one port If trying to bind the same port again, do nothing. - if (this->server_addr.sin_port != - htons(port)) { // parse network-byte order to host-byte order (regular - // numbers) - throw std::runtime_error( - "Socket already listening on port " + - std::to_string(::ntohs(this->server_addr.sin_port))); - } - return; - } - int optionValue = 1; - // The SO_REUSEADDR | SO_REUSEPORT allows the socket to bind an address in - // time-wait state. Allows a TCP socket to use an address which was used by - // another (killed) process. -#ifndef _WIN32 - if (setsockopt(this->sockfd, SOL_SOCKET, - SO_REUSEADDR | SO_REUSEPORT, // NOLINT - &optionValue, sizeof(optionValue)) != 0) { - throw std::system_error(errno, std::system_category(), - "Unable to setup socket!"); - } - // make sure that this->server_addr is clean - all data in it is ours. - bzero((char *)&(this->server_addr), sizeof(this->server_addr)); - this->server_addr.sin_family = AF_INET; - this->server_addr.sin_addr.s_addr = INADDR_ANY; - this->server_addr.sin_port = - htons(port); // parse host-byte order to network-byte order - - if (bind(this->sockfd, (struct sockaddr *)&(this->server_addr), - sizeof(this->server_addr)) < 0) - throw std::system_error(errno, std::system_category(), "Unable to bind"); -#else - if (setsockopt(this->sockfd, SOL_SOCKET, - SO_REUSEADDR, // NOLINT - (char *)&optionValue, sizeof(optionValue)) == SOCKET_ERROR) { - throw std::system_error(errno, std::system_category(), - "Unable to setup socket!"); - } - this->server_addr = {0}; - this->server_addr.sin_port = htons(port); - this->server_addr.sin_family = AF_INET; - this->server_addr.sin_addr.s_addr = INADDR_ANY; - if (::bind(this->sockfd, (struct sockaddr *)&(this->server_addr), - sizeof(this->server_addr)) == SOCKET_ERROR) - throw std::runtime_error("Unable to bind"); -#endif // !_WIN32 - this->isBound = true; -} - -void Socket::Listen() { - // When 'bind' is not called and listen is called, OS will bind a random port - // for the software. Since Socket::Listen method is used in order to build a - // server (with a well-defined port), this scenario should raise an error - if (!this->isBound) - throw std::logic_error("Can't listen on unbound socket"); - - // 128 is the default number in /proc/sys/net/core/somaxconn, - // and according to `listen` man, a bigger number will change to the number in - // this file. The constant SOMAXCONN is the value of - // /proc/sys/net/core/somaxconn - if (::listen(this->sockfd, SOMAXCONN) < 0) - throw std::system_error(errno, std::system_category(), "Unable to listen"); -} - -Socket Socket::Accept() { - if (!this->isBound) { // If the socket is unbound, it cannot accept clients. - throw std::logic_error("Unable to accept from a non-bound socket!"); - } - - struct sockaddr_in client_addr {}; - socklen_t client_addr_len = sizeof(struct sockaddr_in); - SOCKET sock = - ::accept(this->sockfd, reinterpret_cast(&client_addr), - &client_addr_len); -#ifdef DEBUG - std::cout << "Client connected!" << std::endl - << "IP: " << ::inet_ntoa(client_addr.sin_addr) - << " PORT: " << client_addr.sin_port << std::endl; -#endif - if (sock < 0) { // client socket is invalid - throw std::system_error(errno, std::system_category(), - "Connection failed!"); - } - struct timeval timeout { - 1, 0 - }; - if (::setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, - sizeof(timeout)) < 0) { - throw std::runtime_error("Cannot change socket settings"); - } - return Socket(sock); -} - -// maxBufferSize define the max reading buffer maxBufferSize -std::vector Socket::GetData(unsigned int maxBufferSize) { - std::vector res; - if (maxBufferSize == 0) // if requesting to read 0 bytes, return empty array - return res; - std::unique_ptr rawData = std::make_unique(maxBufferSize); - int recvCode; - if (this->sockfd < 0) { - throw std::runtime_error("Unable to read data from a closed socket!"); - } - // If 'maxBufferSize' is bigger then the actual sent data length ::recv will - // output all data in the socket buffer If the socket buffer is empty, ::recv - // will wait - recvCode = ::recv(this->sockfd, rawData.get(), maxBufferSize, 0); - if (recvCode > 0) { - std::copy(rawData.get(), rawData.get() + recvCode, std::back_inserter(res)); - } else if (recvCode == 0) { - // When recv returns 0, it means that the client disconnected. - this->Close(); - throw std::runtime_error("Unable to read data from a closed socket!"); - } else { - if (errno != EAGAIN && errno != EWOULDBLOCK) { - // when recv returns -1, it means that something is wrong - throw an - // error. - throw std::system_error(errno, std::system_category()); - } else { - throw std::runtime_error("Timed out"); - } - } - return res; -} - -void Socket::SendData(std::vector data) { - if (this->sockfd < 0) { - throw std::runtime_error("Unable to send data to a closed socket!"); - } - std::unique_ptr toSend = std::make_unique(data.size() + 1); - std::copy(data.begin(), data.end(), toSend.get()); // copy our data to a char* - if (::send(this->sockfd, toSend.get(), data.size(), 0) < 0) { - this->Close(); - throw std::system_error(errno, std::system_category()); - } -} - -void Socket::Close() { - this->isBound = false; -#ifndef _WIN32 - if (this->sockfd > 0) { - if (::close(this->sockfd)) { - // handle errors - this->sockfd = -1; - throw std::system_error(errno, std::system_category(), - "Unable to close socket"); - } - this->sockfd = -1; - } -#else - if (this->sockfd != INVALID_SOCKET) { - if (::closesocket(this->sockfd)) { - this->sockfd = INVALID_SOCKET; - throw std::system_error(errno, std::system_category(), - "Unable to close socket"); - } - } - this->sockfd = INVALID_SOCKET; -#endif // !_WIN32 -} -bool Socket::IsClosed() const { -#ifdef _WIN32 - return this->sockfd == INVALID_SOCKET; -#else - return this->sockfd == -1; -#endif -} diff --git a/src/chttp/util/Url.cpp b/src/chttp/util/Url.cpp deleted file mode 100644 index 998a2d0..0000000 --- a/src/chttp/util/Url.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include -#include -#include -#include -#include -#include - - -std::unordered_map Url::GetUrlParamSpec() const { - return this->parameters; -} -Url::Url(std::string urlTemplate) { - urlTemplate = urlTemplate.substr(1); - std::istringstream iss(urlTemplate); - std::string item; - int i = 0; - while (std::getline(iss, item, '/')) { - if (item[0] == ':') { - if (item != ":*") { - this->parameters[i] = item.substr(1); - } else { - this->parameters[-1] = "*"; - } - } else { - if (item != "") { - this->mustBe[i] = item; - } - } - i++; - } -} -bool Url::IsMatch(const std::string &url) const { - std::string urlTmp = url.substr(1); - if (urlTmp.empty()) { - return this->mustBe.empty() && this->parameters.empty(); - } - if (urlTmp.find_first_of('?') != std::string::npos) { - urlTmp = urlTmp.substr(0, urlTmp.find_first_of('?')); - } - bool checkOnlyBase = this->parameters.find(-1) != this->parameters.end(); - std::stringstream asStream(urlTmp); - std::string part; - int idx = 0; - int paramsToEnter = this->parameters.size(); - while (std::getline(asStream, part, '/')) { - if (!checkOnlyBase && - this->parameters.find(idx) != this->parameters.end()) { - paramsToEnter--; - } else { - if (this->mustBe.find(idx) != this->mustBe.end()) { - if (this->mustBe.at(idx) != part) { - return false;// this means that the url doesnt fit the template, it - // must fit to the given value! - } - } else { - if (!checkOnlyBase) { - return false;// The url is longer then the template! - } - } - } - idx++; - } - // If all parameters were given value, the url is valid! - return checkOnlyBase || paramsToEnter == 0; -}