1
0
forked from blue/pica

Compare commits

..

4 Commits

15 changed files with 273 additions and 20 deletions

View File

@ -4,7 +4,8 @@ project(pica
LANGUAGES CXX 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 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
@ -20,6 +21,7 @@ add_subdirectory(server)
add_subdirectory(request) add_subdirectory(request)
add_subdirectory(response) add_subdirectory(response)
add_subdirectory(stream) add_subdirectory(stream)
add_subdirectory(database)
target_link_libraries(pica PRIVATE target_link_libraries(pica PRIVATE
FCGI::FCGI FCGI::FCGI

View File

@ -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 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 Start apache
``` ```

25
cmake/FindMariaDB.cmake Normal file
View File

@ -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 ()

11
database/CMakeLists.txt Normal file
View File

@ -0,0 +1,11 @@
set(HEADERS
dbinterface.h
)
set(SOURCES
dbinterface.cpp
)
target_sources(pica PRIVATE ${SOURCES})
add_subdirectory(mysql)

23
database/dbinterface.cpp Normal file
View File

@ -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> DBInterface::create(Type type) {
switch (type) {
case Type::mysql:
return std::make_unique<MySQL>();
}
throw std::runtime_error("Unexpected database type: " + std::to_string((uint8_t)type));
}
DBInterface::State DBInterface::currentState() const {
return state;
}

36
database/dbinterface.h Normal file
View File

@ -0,0 +1,36 @@
#pragma once
#include <stdexcept>
#include <string>
#include <memory>
class DBInterface {
public:
enum class Type {
mysql
};
enum class State {
disconnected,
connecting,
connected
};
static std::unique_ptr<DBInterface> 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;
};

View File

@ -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)

85
database/mysql/mysql.cpp Normal file
View File

@ -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!
}

24
database/mysql/mysql.h Normal file
View File

@ -0,0 +1,24 @@
#pragma once
#include <stdexcept>
#include <mysql.h>
#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;
};

View File

@ -6,6 +6,13 @@ constexpr static const char* REQUEST_METHOD("REQUEST_METHOD");
constexpr static const char* SCRIPT_FILENAME("SCRIPT_FILENAME"); constexpr static const char* SCRIPT_FILENAME("SCRIPT_FILENAME");
constexpr static const char* SERVER_NAME("SERVER_NAME"); 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 (): Request::Request ():
state(State::initial), state(State::initial),
raw() raw()

View File

@ -34,7 +34,8 @@ void Response::replyTo(Request& request) const {
if (!body.empty()) if (!body.empty())
out << '\n' out << '\n'
<< contentTypes[static_cast<uint8_t>(type)] << contentTypes[static_cast<uint8_t>(type)]
<< '\n' << '\n' << '\n'
<< '\n'
<< body; << body;
} }

View File

@ -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"; std::cerr << "could'not add route " + path + " to the routing table";
} }
void Router::route(const std::string& path, std::unique_ptr<Request> request) { void Router::route(const std::string& path, std::unique_ptr<Request> request, Server* server) {
auto itr = table.find(path); auto itr = table.find(path);
if (itr == table.end()) if (itr == table.end())
return handleNotFound(path, std::move(request)); return handleNotFound(path, std::move(request));
try { try {
bool result = itr->second(request.get()); bool result = itr->second(request.get(), server);
if (!result) if (!result)
handleInternalError(std::runtime_error("handler failed to handle the request"), std::move(request)); handleInternalError(std::runtime_error("handler failed to handle the request"), std::move(request));
} catch (const std::exception& e) { } catch (const std::exception& e) {

View File

@ -8,14 +8,16 @@
#include "request/request.h" #include "request/request.h"
#include "response/response.h" #include "response/response.h"
class Server;
class Router { class Router {
public: public:
using Handler = std::function<bool(Request*)>; using Handler = std::function<bool(Request*, Server*)>;
Router(); Router();
void addRoute(const std::string& path, const Handler& handler); void addRoute(const std::string& path, const Handler& handler);
void route(const std::string& path, std::unique_ptr<Request> request); void route(const std::string& path, std::unique_ptr<Request> request, Server* server);
private: private:
void handleNotFound(const std::string& path, std::unique_ptr<Request> request); void handleNotFound(const std::string& path, std::unique_ptr<Request> request);

View File

@ -1,19 +1,27 @@
#include "server.h" #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(): Server::Server():
terminating(false), terminating(false),
requestCount(0), requestCount(0),
serverName(std::nullopt), 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("info", Server::info);
router.addRoute("env", Server::printEnvironment); router.addRoute("env", Server::printEnvironment);
} }
@ -54,7 +62,7 @@ void Server::handleRequest(std::unique_ptr<Request> request) {
try { try {
std::string path = request->getPath(serverName.value()); 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) { } catch (const std::exception e) {
Response error(Response::Status::internalError); Response error(Response::Status::internalError);
error.setBody(std::string(e.what())); error.setBody(std::string(e.what()));
@ -62,7 +70,8 @@ void Server::handleRequest(std::unique_ptr<Request> request) {
} }
} }
bool Server::printEnvironment(Request* request) { bool Server::printEnvironment(Request* request, Server* server) {
(void)server;
nlohmann::json body = nlohmann::json::object(); nlohmann::json body = nlohmann::json::object();
request->printEnvironment(body); request->printEnvironment(body);
@ -73,7 +82,8 @@ bool Server::printEnvironment(Request* request) {
return true; return true;
} }
bool Server::info(Request* request) { bool Server::info(Request* request, Server* server) {
(void)server;
Response res; Response res;
nlohmann::json body = nlohmann::json::object(); nlohmann::json body = nlohmann::json::object();
body["type"] = "Pica"; body["type"] = "Pica";

View File

@ -17,6 +17,7 @@
#include "request/request.h" #include "request/request.h"
#include "response/response.h" #include "response/response.h"
#include "router.h" #include "router.h"
#include "database/dbinterface.h"
class Server { class Server {
public: public:
@ -28,12 +29,13 @@ public:
private: private:
void handleRequest(std::unique_ptr<Request> request); void handleRequest(std::unique_ptr<Request> request);
static bool info(Request* request); static bool info(Request* request, Server* server);
static bool printEnvironment(Request* request); static bool printEnvironment(Request* request, Server* server);
private: private:
bool terminating; bool terminating;
uint64_t requestCount; uint64_t requestCount;
std::optional<std::string> serverName; std::optional<std::string> serverName;
Router router; Router router;
std::unique_ptr<DBInterface> db;
}; };