diff --git a/CMakeLists.txt b/CMakeLists.txt index 0fb7424..79dc05a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,7 @@ cmake_policy(SET CMP0076 NEW) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) +find_package(nlohmann_json REQUIRED) find_package(PkgConfig REQUIRED) pkg_check_modules(FCGI fcgi) @@ -20,9 +21,10 @@ add_subdirectory(request) add_subdirectory(response) add_subdirectory(stream) -target_link_libraries(pica +target_link_libraries(pica PRIVATE fcgi fcgi++ + nlohmann_json::nlohmann_json ) install(TARGETS pica RUNTIME DESTINATION bin) diff --git a/main.cpp b/main.cpp index 977ea62..36b0a3e 100644 --- a/main.cpp +++ b/main.cpp @@ -15,7 +15,7 @@ int main() { return 1; } - if (chmod(socketPath, 0770) != 0) { + if (chmod(socketPath, 0777) != 0) { std::cerr << "Couldn't set socket permissions" << std::endl; return 2; } diff --git a/request/request.cpp b/request/request.cpp index 18505f9..a3c717b 100644 --- a/request/request.cpp +++ b/request/request.cpp @@ -63,16 +63,23 @@ OStream Request::getErrorStream() { return OStream(raw.err); } -std::string_view Request::getPath(const std::string& serverName) const { +std::string Request::getPath(const std::string& serverName) const { if (state != State::accepted) throw std::runtime_error("An attempt to request path on a wrong request state"); - std::string_view path; + std::string path; std::string_view scriptFileName(FCGX_GetParam(SCRIPT_FILENAME, raw.envp)); std::string::size_type snLocation = scriptFileName.find(serverName); + if (snLocation != std::string::npos) { if (snLocation + serverName.size() < scriptFileName.size()) path = scriptFileName.substr(snLocation + serverName.size() + 1); + + } + + if (!path.empty()) { + while (path.back() == '/') + path.erase(path.end() - 1); } return path; @@ -88,6 +95,19 @@ std::string Request::getServerName() const { void Request::printEnvironment(std::ostream& out) { char **envp = raw.envp; for (int i = 0; envp[i] != nullptr; ++i) { - out << envp[i] << "
"; + out << envp[i] << "\n"; + } +} + +void Request::printEnvironment(nlohmann::json& out) { + if (!out.is_object()) + return; + + char **envp = raw.envp; + for (int i = 0; envp[i] != nullptr; ++i) { + std::string_view value(envp[i]); + std::string::size_type pos = value.find('='); + if (pos != std::string::npos) + out[std::string(value.substr(0, pos))] = std::string(value.substr(pos + 1, value.size())); } } diff --git a/request/request.h b/request/request.h index bff6d4e..29ea8d4 100644 --- a/request/request.h +++ b/request/request.h @@ -6,6 +6,8 @@ #include +#include + #include "stream/ostream.h" class Request { @@ -30,9 +32,10 @@ public: OStream getOutputStream(); OStream getErrorStream(); - std::string_view getPath(const std::string& serverName) const; + std::string getPath(const std::string& serverName) const; std::string getServerName() const; void printEnvironment(std::ostream& out); + void printEnvironment(nlohmann::json& out); private: State state; diff --git a/response/response.cpp b/response/response.cpp index 262cbbf..e6fb400 100644 --- a/response/response.cpp +++ b/response/response.cpp @@ -8,7 +8,8 @@ constexpr std::array(Response::Status::__ }; constexpr std::array(Response::ContentType::__size)> contentTypes = { - "Content-type: text/html" + "Content-type: text/plain", + "Content-type: application/json" }; Response::Response(): @@ -38,5 +39,11 @@ void Response::replyTo(Request& request) const { } void Response::setBody(const std::string& body) { + type = ContentType::text; Response::body = body; } + +void Response::setBody(const nlohmann::json& body) { + type = ContentType::json; + Response::body = body.dump(); +} diff --git a/response/response.h b/response/response.h index 60e3ef8..977ef50 100644 --- a/response/response.h +++ b/response/response.h @@ -4,6 +4,8 @@ #include #include +#include + #include "request/request.h" #include "stream/ostream.h" @@ -19,6 +21,7 @@ public: enum class ContentType { text, + json, __size }; Response(); @@ -26,6 +29,7 @@ public: void replyTo(Request& request) const; void setBody(const std::string& body); + void setBody(const nlohmann::json& body); private: Status status; diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 393a4f2..694b3a5 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -1,9 +1,11 @@ set(HEADERS server.h + router.h ) set(SOURCES server.cpp + router.cpp ) target_sources(pica PRIVATE ${SOURCES}) diff --git a/server/router.cpp b/server/router.cpp new file mode 100644 index 0000000..469a371 --- /dev/null +++ b/server/router.cpp @@ -0,0 +1,41 @@ +#include "router.h" + +Router::Router(): + table() +{ + +} + +void Router::addRoute(const std::string& path, const Handler& handler) { + auto result = table.emplace(path, handler); + if (!result.second) + std::cerr << "could'not add route " + path + " to the routing table"; +} + +void Router::route(const std::string& path, std::unique_ptr request) { + auto itr = table.find(path); + if (itr == table.end()) + return handleNotFound(path, std::move(request)); + + try { + bool result = itr->second(request.get()); + if (!result) + handleInternalError(std::runtime_error("handler failed to handle the request"), std::move(request)); + } catch (const std::exception& e) { + handleInternalError(e, std::move(request)); + } +} + +void Router::handleNotFound(const std::string& path, std::unique_ptr request) { + Response notFound(Response::Status::notFound); + notFound.setBody(std::string("Path \"") + path + "\" was not found"); + notFound.replyTo(*request.get()); + std::cerr << "Not found: " << path << std::endl; +} + +void Router::handleInternalError(const std::exception& exception, std::unique_ptr request) { + Response error(Response::Status::internalError); + error.setBody(std::string(exception.what())); + error.replyTo(*request.get()); + std::cerr << "Internal error: " << exception.what() << std::endl; +} diff --git a/server/router.h b/server/router.h new file mode 100644 index 0000000..f85dd0f --- /dev/null +++ b/server/router.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include +#include + +#include "request/request.h" +#include "response/response.h" + +class Router { +public: + using Handler = std::function; + + Router(); + + void addRoute(const std::string& path, const Handler& handler); + void route(const std::string& path, std::unique_ptr request); + +private: + void handleNotFound(const std::string& path, std::unique_ptr request); + void handleInternalError(const std::exception& exception, std::unique_ptr request); + +private: + std::map table; + +}; diff --git a/server/server.cpp b/server/server.cpp index aa56860..c8b524b 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -8,14 +8,15 @@ constexpr static const char* DOCUMENT_ROOT("DOCUMENT_ROOT"); constexpr static const char* SCRIPT_NAME("SCRIPT_NAME"); constexpr static const char* SCRIPT_FILENAME("SCRIPT_FILENAME"); -constexpr std::string_view info("info"); -constexpr std::string_view env("env"); - Server::Server(): terminating(false), requestCount(0), - serverName(std::nullopt) -{} + serverName(std::nullopt), + router() +{ + router.addRoute("info", Server::info); + router.addRoute("env", Server::printEnvironment); +} Server::~Server() {} @@ -52,27 +53,34 @@ void Server::handleRequest(std::unique_ptr request) { } try { - std::string_view sPath = request->getPath(serverName.value()); - - if (sPath == info) { - Response res; - res.setBody("Pica
\nVersion: 1.0.0
\nRequestsHandled: " + std::to_string(requestCount)); - res.replyTo(*request.get()); - } else if (sPath == env) { - std::ostringstream ss; - request->printEnvironment(ss); - Response res; - res.setBody(ss.str()); - res.replyTo(*request.get()); - } else { - Response notFound(Response::Status::notFound); - notFound.setBody(std::string("Path ") + sPath.data() + " was not found"); - notFound.replyTo(*request.get()); - std::cerr << "Not found: " << sPath.data() << std::endl; - } + std::string path = request->getPath(serverName.value()); + router.route(path.data(), std::move(request)); } catch (const std::exception e) { Response error(Response::Status::internalError); - error.setBody(e.what()); + error.setBody(std::string(e.what())); error.replyTo(*request.get()); } } + +bool Server::printEnvironment(Request* request) { + nlohmann::json body = nlohmann::json::object(); + request->printEnvironment(body); + + Response res; + res.setBody(body); + res.replyTo(*request); + + return true; +} + +bool Server::info(Request* request) { + Response res; + nlohmann::json body = nlohmann::json::object(); + body["type"] = "Pica"; + body["version"] = "0.0.1"; + + res.setBody(body); + res.replyTo(*request); + + return true; +} diff --git a/server/server.h b/server/server.h index 2ce2377..6f814cb 100644 --- a/server/server.h +++ b/server/server.h @@ -12,8 +12,11 @@ #include #include +#include + #include "request/request.h" #include "response/response.h" +#include "router.h" class Server { public: @@ -24,12 +27,13 @@ public: private: void handleRequest(std::unique_ptr request); - void router(const std::vector& path); - void printEnv(std::ostream& out, FCGX_Request& request); - std::string_view getPath(const FCGX_Request& request); + + static bool info(Request* request); + static bool printEnvironment(Request* request); private: bool terminating; uint64_t requestCount; std::optional serverName; + Router router; };