Initial setup

This commit is contained in:
Blue 2023-11-21 19:19:08 -03:00
parent 3a0fa57a06
commit 68e795f0e6
Signed by: blue
GPG Key ID: 9B203B252A63EE38
17 changed files with 466 additions and 8 deletions

View File

@ -1,7 +1,28 @@
cmake_minimum_required(VERSION 3.0)
cmake_minimum_required(VERSION 3.5)
project(pica
VERSION 0.0.1
LANGUAGES CXX
)
project(pica)
cmake_policy(SET CMP0076 NEW)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(PkgConfig REQUIRED)
pkg_check_modules(FCGI fcgi)
add_executable(pica main.cpp)
target_include_directories(pica PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
add_subdirectory(server)
add_subdirectory(request)
add_subdirectory(response)
add_subdirectory(stream)
target_link_libraries(pica
fcgi
fcgi++
)
install(TARGETS pica RUNTIME DESTINATION bin)

View File

@ -1,6 +1,27 @@
#include <fcgiapp.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <iostream>
int main(int argc, char **argv) {
std::cout << "Hello, world!" << std::endl;
return 0;
#include "server/server.h"
int main() {
const char* socketPath = "/run/pica/pica.sock";
int sockfd = FCGX_OpenSocket(socketPath, 1024);
if (sockfd < 0) {
std::cerr << "Error opening socket" << std::endl;
return 1;
}
if (chmod(socketPath, 0770) != 0) {
std::cerr << "Couldn't set socket permissions" << std::endl;
return 2;
}
FCGX_Init();
Server server;
server.run(sockfd);
}

View File

@ -1,3 +0,0 @@
[Project]
Name=pica
Manager=KDevCMakeManager

9
request/CMakeLists.txt Normal file
View File

@ -0,0 +1,9 @@
set(HEADERS
request.h
)
set(SOURCES
request.cpp
)
target_sources(pica PRIVATE ${SOURCES})

93
request/request.cpp Normal file
View File

@ -0,0 +1,93 @@
#include "request.h"
constexpr static const char* GET("GET");
constexpr static const char* REQUEST_METHOD("REQUEST_METHOD");
constexpr static const char* SCRIPT_FILENAME("SCRIPT_FILENAME");
constexpr static const char* SERVER_NAME("SERVER_NAME");
Request::Request ():
state(State::initial),
raw()
{}
Request::~Request() {
terminate();
}
void Request::terminate() {
switch (state) {
case State::accepted:
case State::responded:
FCGX_Finish_r(&raw);
break;
default:
break;
}
}
bool Request::isGet() const {
if (state != State::accepted)
throw std::runtime_error("An attempt to read request type on a wrong request state");
std::string_view method(FCGX_GetParam(REQUEST_METHOD, raw.envp));
return method == GET;
}
bool Request::wait(int socketDescriptor) {
if (state != State::initial)
throw std::runtime_error("An attempt to wait for new incomming request on a wrong request state");
FCGX_Request* request = &raw;
FCGX_InitRequest(request, socketDescriptor, 0);
int rc = FCGX_Accept_r(request);
bool result = rc == 0;
if (result)
state = State::accepted;
return result;
}
OStream Request::getOutputStream() {
if (state != State::accepted)
throw std::runtime_error("An attempt to request output stream on a wrong request state");
return OStream(raw.out);
}
OStream Request::getErrorStream() {
if (state != State::accepted)
throw std::runtime_error("An attempt to request error stream on a wrong request state");
return OStream(raw.err);
}
std::string_view 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_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);
}
return path;
}
std::string Request::getServerName() const {
if (state != State::accepted)
throw std::runtime_error("An attempt to request server name on a wrong request state");
return FCGX_GetParam(SERVER_NAME, raw.envp);;
}
void Request::printEnvironment(std::ostream& out) {
char **envp = raw.envp;
for (int i = 0; envp[i] != nullptr; ++i) {
out << envp[i] << "</br>";
}
}

40
request/request.h Normal file
View File

@ -0,0 +1,40 @@
#pragma once
#include <memory>
#include <stdexcept>
#include <string_view>
#include <fcgiapp.h>
#include "stream/ostream.h"
class Request {
public:
enum class State {
initial,
accepted,
responded
};
Request();
~Request();
Request(const Request& other) = delete;
Request(Request&& other) = delete;
Request& operator = (const Request& other) = delete;
Request& operator = (Request&& other) = delete;
bool wait(int socketDescriptor);
void terminate();
bool isGet() const;
OStream getOutputStream();
OStream getErrorStream();
std::string_view getPath(const std::string& serverName) const;
std::string getServerName() const;
void printEnvironment(std::ostream& out);
private:
State state;
FCGX_Request raw;
};

9
response/CMakeLists.txt Normal file
View File

@ -0,0 +1,9 @@
set(HEADERS
response.h
)
set(SOURCES
response.cpp
)
target_sources(pica PRIVATE ${SOURCES})

42
response/response.cpp Normal file
View File

@ -0,0 +1,42 @@
#include "response.h"
constexpr std::array<std::string_view, static_cast<uint8_t>(Response::Status::__size)> statusCodes = {
"Status: 200 OK",
"Status: 404 Not Found",
"Status: 405 Method Not Allowed",
"Status: 500 Internal Error"
};
constexpr std::array<std::string_view, static_cast<uint8_t>(Response::ContentType::__size)> contentTypes = {
"Content-type: text/html"
};
Response::Response():
status(Status::ok),
type(ContentType::text),
body()
{}
Response::Response(Status status):
status(status),
type(ContentType::text),
body()
{}
void Response::replyTo(Request& request) const {
// OStream out = status == Status::ok ?
// request.getOutputStream() :
// request.getErrorStream();
OStream out = request.getOutputStream();
out << statusCodes[static_cast<uint8_t>(status)];
if (!body.empty())
out << '\n'
<< contentTypes[static_cast<uint8_t>(type)]
<< '\n' << '\n'
<< body;
}
void Response::setBody(const std::string& body) {
Response::body = body;
}

34
response/response.h Normal file
View File

@ -0,0 +1,34 @@
#pragma once
#include <string>
#include <array>
#include <string_view>
#include "request/request.h"
#include "stream/ostream.h"
class Response {
public:
enum class Status {
ok,
notFound,
methodNotAllowed,
internalError,
__size
};
enum class ContentType {
text,
__size
};
Response();
Response(Status status);
void replyTo(Request& request) const;
void setBody(const std::string& body);
private:
Status status;
ContentType type;
std::string body;
};

9
server/CMakeLists.txt Normal file
View File

@ -0,0 +1,9 @@
set(HEADERS
server.h
)
set(SOURCES
server.cpp
)
target_sources(pica PRIVATE ${SOURCES})

78
server/server.cpp Normal file
View File

@ -0,0 +1,78 @@
#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");
constexpr std::string_view info("info");
constexpr std::string_view env("env");
Server::Server():
terminating(false),
requestCount(0),
serverName(std::nullopt)
{}
Server::~Server() {}
void Server::run(int socketDescriptor) {
while (!terminating) {
std::unique_ptr<Request> request = std::make_unique<Request>();
bool result = request->wait(socketDescriptor);
if (!result) {
std::cerr << "Error accepting a request" << std::endl;
return;
}
handleRequest(std::move(request));
}
}
void Server::handleRequest(std::unique_ptr<Request> request) {
++requestCount;
if (!serverName) {
try {
serverName = request->getServerName();
std::cout << "received server name " << serverName.value() << std::endl;
} catch (...) {
std::cerr << "failed to read server name" << std::endl;
Response error(Response::Status::internalError);
error.replyTo(*request.get());
return;
}
}
if (!request->isGet()) {
static const Response methodNotAllowed(Response::Status::methodNotAllowed);
methodNotAllowed.replyTo(*request.get());
return;
}
try {
std::string_view sPath = request->getPath(serverName.value());
if (sPath == info) {
Response res;
res.setBody("Pica<br>\nVersion: 1.0.0<br>\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;
}
} catch (const std::exception e) {
Response error(Response::Status::internalError);
error.setBody(e.what());
error.replyTo(*request.get());
}
}

35
server/server.h Normal file
View File

@ -0,0 +1,35 @@
#pragma once
#include <iostream>
#include <string>
#include <sstream>
#include <optional>
#include <string_view>
#include <vector>
#include <memory>
#include <fcgiapp.h>
#include <fcgio.h>
#include <stdint.h>
#include "request/request.h"
#include "response/response.h"
class Server {
public:
Server();
~Server();
void run(int socketDescriptor);
private:
void handleRequest(std::unique_ptr<Request> request);
void router(const std::vector<std::string_view>& path);
void printEnv(std::ostream& out, FCGX_Request& request);
std::string_view getPath(const FCGX_Request& request);
private:
bool terminating;
uint64_t requestCount;
std::optional<std::string> serverName;
};

11
stream/CMakeLists.txt Normal file
View File

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

9
stream/ostream.cpp Normal file
View File

@ -0,0 +1,9 @@
#include "ostream.h"
OStream::OStream(FCGX_Stream* _raw):
Stream(_raw),
std(&raw)
{
}
OStream::~OStream() {}

24
stream/ostream.h Normal file
View File

@ -0,0 +1,24 @@
#pragma once
#include <ios>
#include "stream.h"
class Request;
class OStream : Stream {
friend class Request;
private:
OStream(FCGX_Stream* raw);
public:
~OStream();
template <typename T>
OStream& operator << (const T& value) {
std << value;
return *this;
};
private:
std::ostream std;
};

8
stream/stream.cpp Normal file
View File

@ -0,0 +1,8 @@
#include "stream.h"
Stream::Stream(FCGX_Stream* raw):
raw(raw)
{}
Stream::~Stream() {
}

18
stream/stream.h Normal file
View File

@ -0,0 +1,18 @@
#pragma once
#include <fcgio.h>
class Stream {
protected:
Stream(FCGX_Stream* raw);
Stream(const Stream& other) = delete;
Stream(Stream&& other) = delete;
Stream& operator = (const Stream& other) = delete;
Stream& operator = (Stream&& other) = delete;
virtual ~Stream();
public:
protected:
fcgi_streambuf raw;
};