diff --git a/CMakeLists.txt b/CMakeLists.txt index 6103b5b..1cd5fff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,8 @@ project(pica LANGUAGES CXX ) -cmake_policy(SET CMP0076 NEW) +cmake_policy(SET CMP0076 NEW) #allow adding sources from subdir +cmake_policy(SET CMP0079 NEW) #allow linking from subdirs set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -20,6 +21,7 @@ add_subdirectory(server) add_subdirectory(request) add_subdirectory(response) add_subdirectory(stream) +add_subdirectory(database) target_link_libraries(pica PRIVATE FCGI::FCGI diff --git a/README.md b/README.md index 3f77108..b92f5c0 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,18 @@ Incude this file from apache config file (in my case it's `/etc/httpd/conf/httpd Include conf/extra/fcgi-pica.conf ``` +Also you need to have 2 modules enabled `proxy` and `proxy_fcgi`, usually you need to uncomment following lines in `/etc/httpd/conf/httpd.conf` +``` +LoadModule proxy_module modules/mod_proxy.so +LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so +``` + +In case you use debian-like distro (ubuntu) you should use `a2enmod` command instead: +``` +# a2enmod proxy_fcgi +``` +This should enable both modules, because module `proxy_fcgi` has module `proxy` as a dependency. + Start apache ``` diff --git a/cmake/FindMariaDB.cmake b/cmake/FindMariaDB.cmake new file mode 100644 index 0000000..623de5f --- /dev/null +++ b/cmake/FindMariaDB.cmake @@ -0,0 +1,25 @@ +find_library(MariaDB_CLIENT_LIBRARIES mysqlclient NAMES mariadbclient) +find_path(MariaDB_INCLUDE_DIR mysql/mysql.h) + +if (MariaDB_CLIENT_LIBRARIES AND MariaDB_INCLUDE_DIR) + set(MariaDB_FOUND TRUE) +endif() + +if (MariaDB_FOUND) + add_library(MariaDB::client SHARED IMPORTED) + set_target_properties(MariaDB::client PROPERTIES + IMPORTED_LOCATION "${MariaDB_CLIENT_LIBRARIES}" + INTERFACE_LINK_LIBRARIES "${MariaDB_CLIENT_LIBRARIES}" + INTERFACE_INCLUDE_DIRECTORIES ${MariaDB_INCLUDE_DIR}/mysql + ) + + if (NOT MariaDB_FIND_QUIETLY) + message(STATUS "Found MariaDB includes: ${MariaDB_INCLUDE_DIR}/mysql") + message(STATUS "Found MariaDB client library: ${MariaDB_CLIENT_LIBRARIES}") + endif () +else () + if (MariaDB_FIND_REQUIRED) + message(FATAL_ERROR "Could NOT find MariaDB development files") + endif () +endif () + diff --git a/database/CMakeLists.txt b/database/CMakeLists.txt new file mode 100644 index 0000000..cd024ca --- /dev/null +++ b/database/CMakeLists.txt @@ -0,0 +1,11 @@ +set(HEADERS + dbinterface.h +) + +set(SOURCES + dbinterface.cpp +) + +target_sources(pica PRIVATE ${SOURCES}) + +add_subdirectory(mysql) diff --git a/database/dbinterface.cpp b/database/dbinterface.cpp new file mode 100644 index 0000000..54d7d15 --- /dev/null +++ b/database/dbinterface.cpp @@ -0,0 +1,23 @@ +#include "dbinterface.h" + +#include "mysql/mysql.h" + +DBInterface::DBInterface(Type type): + type(type), + state(State::disconnected) +{} + +DBInterface::~DBInterface() {} + +std::unique_ptr DBInterface::create(Type type) { + switch (type) { + case Type::mysql: + return std::make_unique(); + } + + throw std::runtime_error("Unexpected database type: " + std::to_string((uint8_t)type)); +} + +DBInterface::State DBInterface::currentState() const { + return state; +} diff --git a/database/dbinterface.h b/database/dbinterface.h new file mode 100644 index 0000000..80334f7 --- /dev/null +++ b/database/dbinterface.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include + +class DBInterface { +public: + enum class Type { + mysql + }; + enum class State { + disconnected, + connecting, + connected + }; + static std::unique_ptr create(Type type); + + virtual ~DBInterface(); + + State currentState() const; + + const Type type; + +public: + virtual void connect(const std::string& path) = 0; + virtual void disconnect() = 0; + virtual void setDatabase(const std::string& newDatabase) = 0; + virtual void setCredentials(const std::string& login, const std::string& password) = 0; + +protected: + DBInterface(Type type); + +protected: + State state; +}; diff --git a/database/mysql/CMakeLists.txt b/database/mysql/CMakeLists.txt new file mode 100644 index 0000000..c64280d --- /dev/null +++ b/database/mysql/CMakeLists.txt @@ -0,0 +1,13 @@ +set(HEADERS + mysql.h +) + +set(SOURCES + mysql.cpp +) + +find_package(MariaDB REQUIRED) + +target_sources(pica PRIVATE ${SOURCES}) + +target_link_libraries(pica PRIVATE MariaDB::client) diff --git a/database/mysql/mysql.cpp b/database/mysql/mysql.cpp new file mode 100644 index 0000000..c9da2a3 --- /dev/null +++ b/database/mysql/mysql.cpp @@ -0,0 +1,85 @@ +#include "mysql.h" + +MySQL::MySQL(): + DBInterface(Type::mysql), + connection(), + login(), + password(), + database() +{ + mysql_init(&connection); +} + +MySQL::~MySQL() { + mysql_close(&connection); +} + + +void MySQL::connect(const std::string& path) { + if (state != State::disconnected) + return; + + MYSQL* con = &connection; + MYSQL* res = mysql_real_connect( + con, + NULL, + login.c_str(), + password.c_str(), + database.empty() ? NULL : database.c_str(), + 0, + path.c_str(), + 0 + ); + + if (res != con) + throw std::runtime_error(std::string("Error changing connecting: ") + mysql_error(con)); + + state = State::connected; +} + +void MySQL::setCredentials(const std::string& login, const std::string& password) { + if (MySQL::login == login && MySQL::password == password) + return; + + MySQL::login = login; + MySQL::password = password; + + if (state == State::disconnected) + return; + + MYSQL* con = &connection; + int result = mysql_change_user( + con, + login.c_str(), + password.c_str(), + database.empty() ? NULL : database.c_str() + ); + + if (result != 0) + throw std::runtime_error(std::string("Error changing credetials: ") + mysql_error(con)); +} + +void MySQL::setDatabase(const std::string& database) { + if (MySQL::database == database) + return; + + MySQL::database = database; + + if (state == State::disconnected) + return; + + MYSQL* con = &connection; + int result = mysql_select_db(con, database.c_str()); + + if (result != 0) + throw std::runtime_error(std::string("Error changing db: ") + mysql_error(con)); +} + +void MySQL::disconnect() { + if (state == State::disconnected) + return; + + MYSQL* con = &connection; + mysql_close(con); + mysql_init(con); //this is ridiculous! +} diff --git a/database/mysql/mysql.h b/database/mysql/mysql.h new file mode 100644 index 0000000..35b8243 --- /dev/null +++ b/database/mysql/mysql.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +#include + +#include "database/dbinterface.h" + +class MySQL : public DBInterface { +public: + MySQL(); + ~MySQL() override; + + void connect(const std::string& path) override; + void disconnect() override; + void setCredentials(const std::string& login, const std::string& password) override; + void setDatabase(const std::string& database) override; + +protected: + MYSQL connection; + std::string login; + std::string password; + std::string database; +}; diff --git a/request/request.cpp b/request/request.cpp index a3c717b..e23435f 100644 --- a/request/request.cpp +++ b/request/request.cpp @@ -6,6 +6,13 @@ constexpr static const char* REQUEST_METHOD("REQUEST_METHOD"); constexpr static const char* SCRIPT_FILENAME("SCRIPT_FILENAME"); constexpr static const char* SERVER_NAME("SERVER_NAME"); +// constexpr static const char* REQUEST_URI("REQUEST_URI"); +// +// constexpr static const char* DOCUMENT_URI("DOCUMENT_URI"); +// constexpr static const char* DOCUMENT_ROOT("DOCUMENT_ROOT"); +// +// constexpr static const char* SCRIPT_NAME("SCRIPT_NAME"); + Request::Request (): state(State::initial), raw() diff --git a/response/response.cpp b/response/response.cpp index e6fb400..0a6b0ce 100644 --- a/response/response.cpp +++ b/response/response.cpp @@ -34,7 +34,8 @@ void Response::replyTo(Request& request) const { if (!body.empty()) out << '\n' << contentTypes[static_cast(type)] - << '\n' << '\n' + << '\n' + << '\n' << body; } diff --git a/server/router.cpp b/server/router.cpp index 469a371..57a2660 100644 --- a/server/router.cpp +++ b/server/router.cpp @@ -12,13 +12,13 @@ void Router::addRoute(const std::string& path, const Handler& handler) { std::cerr << "could'not add route " + path + " to the routing table"; } -void Router::route(const std::string& path, std::unique_ptr request) { +void Router::route(const std::string& path, std::unique_ptr request, Server* server) { auto itr = table.find(path); if (itr == table.end()) return handleNotFound(path, std::move(request)); try { - bool result = itr->second(request.get()); + bool result = itr->second(request.get(), server); if (!result) handleInternalError(std::runtime_error("handler failed to handle the request"), std::move(request)); } catch (const std::exception& e) { diff --git a/server/router.h b/server/router.h index f85dd0f..3ac9c5d 100644 --- a/server/router.h +++ b/server/router.h @@ -8,14 +8,16 @@ #include "request/request.h" #include "response/response.h" +class Server; + class Router { public: - using Handler = std::function; + using Handler = std::function; Router(); void addRoute(const std::string& path, const Handler& handler); - void route(const std::string& path, std::unique_ptr request); + void route(const std::string& path, std::unique_ptr request, Server* server); private: void handleNotFound(const std::string& path, std::unique_ptr request); diff --git a/server/server.cpp b/server/server.cpp index c8b524b..e2f13d0 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -1,19 +1,27 @@ #include "server.h" -constexpr static const char* REQUEST_URI("REQUEST_URI"); - -constexpr static const char* DOCUMENT_URI("DOCUMENT_URI"); -constexpr static const char* DOCUMENT_ROOT("DOCUMENT_ROOT"); - -constexpr static const char* SCRIPT_NAME("SCRIPT_NAME"); -constexpr static const char* SCRIPT_FILENAME("SCRIPT_FILENAME"); - Server::Server(): terminating(false), requestCount(0), serverName(std::nullopt), - router() + router(), + db() { + std::cout << "Startig pica..." << std::endl; + + db = DBInterface::create(DBInterface::Type::mysql); + std::cout << "Database type: MySQL" << std::endl; + + db->setCredentials("pica", "pica"); + db->setDatabase("pica"); + + try { + db->connect("/run/mysqld/mysqld.sock"); + std::cout << "Successfully connected to the database" << std::endl; + } catch (const std::runtime_error& e) { + std::cerr << "Couldn't connect to the database: " << e.what() << std::endl; + } + router.addRoute("info", Server::info); router.addRoute("env", Server::printEnvironment); } @@ -54,7 +62,7 @@ void Server::handleRequest(std::unique_ptr request) { try { std::string path = request->getPath(serverName.value()); - router.route(path.data(), std::move(request)); + router.route(path.data(), std::move(request), this); } catch (const std::exception e) { Response error(Response::Status::internalError); error.setBody(std::string(e.what())); @@ -62,7 +70,8 @@ void Server::handleRequest(std::unique_ptr request) { } } -bool Server::printEnvironment(Request* request) { +bool Server::printEnvironment(Request* request, Server* server) { + (void)server; nlohmann::json body = nlohmann::json::object(); request->printEnvironment(body); @@ -73,7 +82,8 @@ bool Server::printEnvironment(Request* request) { return true; } -bool Server::info(Request* request) { +bool Server::info(Request* request, Server* server) { + (void)server; Response res; nlohmann::json body = nlohmann::json::object(); body["type"] = "Pica"; diff --git a/server/server.h b/server/server.h index 6f814cb..4ebdf80 100644 --- a/server/server.h +++ b/server/server.h @@ -17,6 +17,7 @@ #include "request/request.h" #include "response/response.h" #include "router.h" +#include "database/dbinterface.h" class Server { public: @@ -28,12 +29,13 @@ public: private: void handleRequest(std::unique_ptr request); - static bool info(Request* request); - static bool printEnvironment(Request* request); + static bool info(Request* request, Server* server); + static bool printEnvironment(Request* request, Server* server); private: bool terminating; uint64_t requestCount; std::optional serverName; Router router; + std::unique_ptr db; };