Initial setup
This commit is contained in:
parent
3a0fa57a06
commit
68e795f0e6
@ -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)
|
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)
|
install(TARGETS pica RUNTIME DESTINATION bin)
|
||||||
|
27
main.cpp
27
main.cpp
@ -1,6 +1,27 @@
|
|||||||
|
#include <fcgiapp.h>
|
||||||
|
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
#include "server/server.h"
|
||||||
std::cout << "Hello, world!" << std::endl;
|
|
||||||
return 0;
|
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);
|
||||||
}
|
}
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
[Project]
|
|
||||||
Name=pica
|
|
||||||
Manager=KDevCMakeManager
|
|
9
request/CMakeLists.txt
Normal file
9
request/CMakeLists.txt
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
set(HEADERS
|
||||||
|
request.h
|
||||||
|
)
|
||||||
|
|
||||||
|
set(SOURCES
|
||||||
|
request.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_sources(pica PRIVATE ${SOURCES})
|
93
request/request.cpp
Normal file
93
request/request.cpp
Normal 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
40
request/request.h
Normal 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
9
response/CMakeLists.txt
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
set(HEADERS
|
||||||
|
response.h
|
||||||
|
)
|
||||||
|
|
||||||
|
set(SOURCES
|
||||||
|
response.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_sources(pica PRIVATE ${SOURCES})
|
42
response/response.cpp
Normal file
42
response/response.cpp
Normal 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
34
response/response.h
Normal 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
9
server/CMakeLists.txt
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
set(HEADERS
|
||||||
|
server.h
|
||||||
|
)
|
||||||
|
|
||||||
|
set(SOURCES
|
||||||
|
server.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_sources(pica PRIVATE ${SOURCES})
|
78
server/server.cpp
Normal file
78
server/server.cpp
Normal 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
35
server/server.h
Normal 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
11
stream/CMakeLists.txt
Normal 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
9
stream/ostream.cpp
Normal 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
24
stream/ostream.h
Normal 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
8
stream/stream.cpp
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#include "stream.h"
|
||||||
|
|
||||||
|
Stream::Stream(FCGX_Stream* raw):
|
||||||
|
raw(raw)
|
||||||
|
{}
|
||||||
|
|
||||||
|
Stream::~Stream() {
|
||||||
|
}
|
18
stream/stream.h
Normal file
18
stream/stream.h
Normal 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;
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user