Add config parsing

This commit is contained in:
Julien CLEMENT 2020-05-05 14:11:54 +02:00
parent 229ea933cd
commit d7b740b86e
33 changed files with 1564 additions and 88 deletions

@ -37,31 +37,11 @@ bin_PROGRAMS = paxos
CONFIG = \ CONFIG = \
src/config/config.hh \ src/config/config.hh \
src/config/config.cc \ src/config/config.cc \
src/config/proxy.hh \
src/config/proxy.cc \
src/config/parse.hh \ src/config/parse.hh \
src/config/parse.hxx \ src/config/parse.hxx \
src/config/parse.cc \ src/config/parse.cc
src/config/timeout.hh \
src/config/timeout.cc \
src/config/upstream.hh \
src/config/upstream.cc
VHOST = \
src/vhost/vhost-factory.cc \
src/vhost/vhost-static-file.cc \
src/vhost/vhost-reverse-proxy.cc \
src/vhost/connection.hh \
src/vhost/connection.cc \
src/vhost/apm.hh \
src/vhost/apm.cc \
src/vhost/vhost.cc
MISC = \ MISC = \
src/misc/buffer.hh \
src/misc/buffer.cc \
src/misc/option.hh \
src/misc/option.cc \
src/misc/addrinfo/addrinfo-error.cc \ src/misc/addrinfo/addrinfo-error.cc \
src/misc/addrinfo/addrinfo-error.hh \ src/misc/addrinfo/addrinfo-error.hh \
src/misc/addrinfo/addrinfo-iterator.hh \ src/misc/addrinfo/addrinfo-iterator.hh \
@ -70,17 +50,9 @@ MISC = \
src/misc/fd.cc \ src/misc/fd.cc \
src/misc/fd.hh \ src/misc/fd.hh \
src/misc/json.hh \ src/misc/json.hh \
src/misc/openssl/base64.cc \
src/misc/openssl/base64.hh \
src/misc/openssl/ssl-error.cc \
src/misc/openssl/ssl-error.hh \
src/misc/openssl/ssl-wrapper.hh \
src/misc/openssl/ssl.hh \
src/misc/socket.hh \ src/misc/socket.hh \
src/misc/sys-wrapper.hh \ src/misc/sys-wrapper.hh \
src/misc/unistd.hh \ src/misc/unistd.hh \
src/misc/readiness/readiness.cc \
src/misc/readiness/readiness.hh \
src/misc/logger.cc \ src/misc/logger.cc \
src/misc/logger.hh src/misc/logger.hh
@ -109,76 +81,22 @@ EVENTS = \
src/events/reverse-proxy.cc \ src/events/reverse-proxy.cc \
src/events/reverse-proxy.hh src/events/reverse-proxy.hh
ERROR = \
src/error/init-error.hh \
src/error/not-implemented.hh \
src/error/parsing-error.hh \
src/error/connection-closed.hh \
src/error/connection-failed.hh \
src/error/backend-unavailable.hh
SOCKET = \ SOCKET = \
src/socket/default-socket.cc \ src/socket/default-socket.cc \
src/socket/default-socket.hh \ src/socket/default-socket.hh \
src/socket/socket.hh \ src/socket/socket.hh
src/socket/ssl-socket.cc \
src/socket/ssl-socket.hh
TIMER = \
src/timer/timer.cc \
src/timer/timer.hh \
src/timer/queue.cc \
src/timer/queue.hh \
src/timer/throughput-queue.cc \
src/timer/throughput-queue.hh \
src/timer/proxy-queue.cc \
src/timer/procy-queue.hh \
src/timer/queue-manager.cc \
src/timer/queue-manager.hh
SCHEDULER = \
src/scheduler/scheduler.cc \
src/scheduler/scheduler.hh \
src/scheduler/round-robin.cc \
src/scheduler/round-robin.hh \
src/scheduler/failover.cc \
src/scheduler/failover.hh \
src/scheduler/fail-robin.cc \
src/scheduler/fail-robin.hh \
src/scheduler/send-health.cc \
src/scheduler/send-health.hh \
src/scheduler/recv-health.cc \
src/scheduler/recv-health.hh
SOURCES = \ SOURCES = \
src/request/error.cc \
src/request/error.hh \
src/request/request.cc \
src/request/request.hh \
src/request/message.hh \
src/request/message.cc \
src/request/response.hh \
src/request/response.cc \
src/request/types.hh \
src/vhost/dispatcher.cc \
src/vhost/dispatcher.hh \
src/vhost/vhost-factory.hh \
src/vhost/vhost-reverse-proxy.hh \
src/vhost/vhost-static-file.hh \
src/vhost/vhost.hh \
$(CONFIG) \ $(CONFIG) \
$(VHOST) \
$(MISC) \ $(MISC) \
$(EVENTS) \
$(ERROR) \
$(SOCKET) \ $(SOCKET) \
$(TIMER) \
$(SCHEDULER) \ $(SCHEDULER) \
$(NULL) $(NULL)
# Compile source files again to avoid silenting undefined references. # Compile source files again to avoid silenting undefined references.
paxos_SOURCES = \ paxos_SOURCES = \
$(SOURCES) \
src/main.cc \ src/main.cc \
$(NULL) $(NULL)

@ -0,0 +1,10 @@
{
"legislators" : [
{
"ip" : "127.0.0.1",
"port" : 8000,
"name" : "test",
"self" : true
}
]
}

112
src/config/config.cc Normal file

@ -0,0 +1,112 @@
#include <arpa/inet.h>
#include <iostream>
#include <fstream>
#include "config.hh"
#include "parse.hh"
namespace paxos
{
ServerConfig::ServerConfig(const std::vector<LegislatorConfig>& legislators)
: legislators_(legislators)
{
}
static std::string format_ip(std::string ip)
{
int family = 0;
/* Check if it is an IPv4 or Ipv6 address */
if (ip.find(":") != std::string::npos)
family = AF_INET6;
else
family = AF_INET;
/* Allocated static buffer to make conversion */
unsigned char buf[sizeof(struct in6_addr)];
char formatted_ip[INET6_ADDRSTRLEN];
/* Convert ip string to network address structure */
int success = inet_pton(family, ip.c_str(), buf);
if (success <= 0)
error_and_exit(1, "parse_configuration: " + ip + " is an invalid ip");
/* Convert back the network address to an string */
inet_ntop(family, buf, formatted_ip, INET6_ADDRSTRLEN);
return formatted_ip;
}
static void parse_mandatory_keys(const json& j, LegislatorConfig& config)
{
int port_int;
safe_get_key(j, "ip", config.ip, true);
config.ip = format_ip(config.ip);
safe_get_key(j, "port", port_int, true);
config.port = std::to_string(port_int);
safe_get_key(j, "name", config.name, true);
safe_get_key(j, "self", config.is_self, true);
}
static void from_json(const json& j, LegislatorConfig& config)
{
parse_mandatory_keys(j, config);
}
static std::vector<LegislatorConfig> parse_legislators(const json& j)
{
/* Get 'vhosts' value */
std::vector<json> legislators;
safe_get_key(j, "legislators", legislators, true);
/* Create VHostConfig vector and fill with VHostConfig */
std::vector<LegislatorConfig> legislator_configs;
for (auto it : legislators)
{
/* Differenciable vhost checking */
auto legislator = it.get<paxos::LegislatorConfig>();
legislator_configs.push_back(legislator);
}
return legislator_configs;
}
ServerConfig ServerConfig::parse(const std::string& path)
{
json json_dict;
std::ifstream json_file(path);
/* Check if file is corretly opened */
if (!json_file.is_open())
{
error_and_exit(1, "parse_configuration: file " + path
+ " doesn't exist.");
}
/* Check if file respect the json syntax */
try
{
json_file >> json_dict;
}
catch (const nlohmann::detail::parse_error& e)
{
error_and_exit(1, e.what());
}
std::vector<LegislatorConfig> legislators = parse_legislators(json_dict);
return ServerConfig(legislators);
}
}

25
src/config/config.hh Normal file

@ -0,0 +1,25 @@
#pragma once
#include<iostream>
#include <string>
#include "misc/json.hh"
namespace paxos
{
struct LegislatorConfig
{
std::string ip;
std::string port;
std::string name;
bool is_self;
};
struct ServerConfig
{
ServerConfig(const std::vector<LegislatorConfig>& legislators);
std::vector<LegislatorConfig> legislators_;
static ServerConfig parse(const std::string& path);
};
}

12
src/config/parse.cc Normal file

@ -0,0 +1,12 @@
#include "parse.hh"
namespace paxos
{
void error_and_exit(int code, const std::string& error)
{
/* Display 'error' on stderr and exit with 'code' */
std::cerr << error << "\n";
exit(code);
}
} // namespace http

12
src/config/parse.hh Normal file

@ -0,0 +1,12 @@
#pragma once
#include <string>
#include "misc/json.hh"
namespace paxos
{
[[noreturn]] void error_and_exit(int code, const std::string& error);
} // namespace http
#include "parse.hxx"

32
src/config/parse.hxx Normal file

@ -0,0 +1,32 @@
#pragma once
#include <iostream>
#include "parse.hh"
namespace paxos
{
template <typename ValueType>
bool safe_get_key(const json& json_obj, const std::string& key,
ValueType& value, bool mandatory = false)
{
try
{
json_obj.at(key).get_to(value);
}
catch (json::out_of_range& e)
{
if (mandatory)
error_and_exit(1, "parse_configuration: mandatory key '"
+ key + "' not found.");
return 0;
}
catch (const std::exception& e)
{
std::cout << e.what() << " in " << key << "\n";
error_and_exit(1, "parse_configuration: key '" + key +
"' is associated with a value of bad type");
}
return 1;
}
} // namespace http

@ -0,0 +1,15 @@
#pragma once
#include <stdexcept>
#include <string>
#include <system_error>
namespace http
{
struct ConnectionRecvClosedError: public std::system_error
{
explicit ConnectionRecvClosedError()
: std::system_error{errno, std::system_category()}
{}
};
} //namespace http

@ -0,0 +1,15 @@
#pragma once
#include <stdexcept>
#include <string>
#include <system_error>
namespace http
{
struct ConnectionFailed : public std::system_error
{
explicit ConnectionFailed()
: std::system_error{errno, std::system_category()}
{}
};
} //namespace http

15
src/error/init-error.hh Normal file

@ -0,0 +1,15 @@
#pragma once
#include <stdexcept>
#include <string>
namespace http
{
struct InitializationError : public std::logic_error
{
explicit InitializationError(const std::string& what_arg)
: std::logic_error{"Initialization error: " + what_arg}
{}
virtual ~InitializationError() = default;
};
} // namespace http

@ -0,0 +1,15 @@
#pragma once
#include <stdexcept>
namespace http
{
class NotImplemented : public std::logic_error
{
public:
NotImplemented()
: std::logic_error{"not implemented exception"}
{}
virtual ~NotImplemented() = default;
};
} // namespace http

@ -0,0 +1,15 @@
#pragma once
#include <stdexcept>
#include <string>
namespace http
{
struct ParsingError : public std::logic_error
{
explicit ParsingError(const std::string& what_arg)
: std::logic_error{"Parsing error: " + what_arg}
{}
virtual ~ParsingError() = default;
};
} // namespace http

@ -0,0 +1,12 @@
#pragma once
#include "legislator-config.hh"
namespace paxos
{
class Legislator
{
public:
LegislatorConfig config;
}
}

@ -1,5 +1,10 @@
int main(int argc, char **argv) #include <iostream>
#include "config/config.hh"
int main(int, char **argv)
{ {
argv = argv; paxos::ServerConfig config = paxos::ServerConfig::parse(argv[1]);
return argc; std::cout << config.legislators_[0].name << "\n";
} }

@ -0,0 +1,15 @@
#include "addrinfo-error.hh"
namespace misc
{
const std::error_category& addrinfo_category()
{
static addrinfo_error_category instance;
return instance;
}
} // namespace misc
inline std::error_code std::make_error_code(misc::addrinfo_error e)
{
return std::error_code(static_cast<int>(e), misc::addrinfo_category());
}

@ -0,0 +1,82 @@
/**
* \file misc/addrinfo/addrinfo-error.hh
* \brief Implementation of an extention to system_errors for addrinfo.
*/
#pragma once
#include <boost/iterator/iterator_facade.hpp>
#include <netdb.h>
#include <system_error>
namespace misc
{
/**
* \enum addrinfo_error
* \brief Possible errors returned by getaddrinfo.
*/
enum addrinfo_error
{
BADFLAGS = EAI_BADFLAGS,
NONAME = EAI_NONAME,
AGAIN = EAI_AGAIN,
FAIL = EAI_FAIL,
FAMILY = EAI_FAMILY,
SOCKTYPE = EAI_SOCKTYPE,
SERVICE = EAI_SERVICE,
MEMORY = EAI_MEMORY,
SYSTEM = EAI_SYSTEM,
OVERFLOW = EAI_OVERFLOW,
NODATA = EAI_NODATA,
ADDRFAMILY = EAI_ADDRFAMILY,
INPROGRESS = EAI_INPROGRESS,
CANCELED = EAI_CANCELED,
NOTCANCELED = EAI_NOTCANCELED,
ALLDONE = EAI_ALLDONE,
INTR = EAI_INTR,
IDN_ENCODE = EAI_IDN_ENCODE,
};
/**
* \class addrinfo_error_category
* \brief Error category signifying the system error coming from
* getaddrinfo.
*/
class addrinfo_error_category : public std::error_category
{
public:
/**
* \brief Name of the category.
*/
virtual const char* name() const noexcept override
{
return "addrinfo_error_category";
}
/**
* \brief Error message.
*/
virtual std::string message(int value) const override
{
return gai_strerror(value);
}
};
/**
* \brief Singleton instance of the new error_category.
*/
const std::error_category& addrinfo_category();
} // namespace misc
namespace std
{
/**
* \brief Setup addrinfo_error to be used as error_codes.
*/
template <>
struct is_error_code_enum<misc::addrinfo_error> : public std::true_type
{};
/**
* \brief Translation between addrinfo_errors and error_codes.
*/
inline std::error_code make_error_code(misc::addrinfo_error e);
} // namespace std

@ -0,0 +1,102 @@
/**
* /file misc/addrinfo/addrinfo-iterator.hh
* /brief Iterator over AddrInfo declaration.
*/
#pragma once
#include <boost/iterator/iterator_facade.hpp>
#include <netdb.h>
namespace misc
{
/**
* \class addrinfo_iterator
* \brief Iterator over AddrInfo objects.
*/
template <class Value>
class addrinfo_iter
: public boost::iterator_facade<addrinfo_iter<Value>, Value,
boost::forward_traversal_tag>
{
public:
/**
* \brief Default constructor (end iterator).
*/
addrinfo_iter()
: m_node(nullptr)
{}
/**
* \brief Constructor from a given AddrInfo.
* \param p Value* the AddrInfo
*/
explicit addrinfo_iter(Value* p)
: m_node(p)
{}
/**
* \brief Begin of the iterator range.
*/
addrinfo_iter<Value> begin()
{
return *this;
}
/**
* \brief End of the iterator range.
*/
addrinfo_iter<Value> end()
{
return addrinfo_iter<Value>{};
}
private:
friend class boost::iterator_core_access;
/**
* \brief Advance the iterator by one step.
*/
void increment()
{
m_node = m_node->ai_next;
}
/**
* \brief Check equality between iterators.
*/
bool equal(const addrinfo_iter<Value>& other) const
{
return this->m_node == other.m_node;
}
/**
* \brief Access to the value through dereferenciation
*/
Value& dereference() const
{
return *m_node;
}
/**
* \brief Begin of a given iterator.
*/
Value& begin(const addrinfo_iter<Value>& n)
{
return n->m_node;
}
/**
* \brief Current value of the iterator.
*/
Value* m_node;
};
/**
* \brief Alias to AddrInfo iterators type.
*/
using addrinfo_iterator = addrinfo_iter<addrinfo>;
/**
* \brief Alias to const AddrInfo iterators type.
*/
using addrinfo_const_iterator = addrinfo_iter<const addrinfo>;
} // namespace misc

@ -0,0 +1,24 @@
#include "addrinfo.hh"
namespace misc
{
AddrInfo getaddrinfo(const char* node, const char* service,
const AddrInfoHint& hints)
{
struct addrinfo* res;
auto rc = ::getaddrinfo(node, service, &hints, &res);
if (rc)
throw std::system_error(rc, addrinfo_error_category(),
"getaddrinfo");
return AddrInfo(res);
}
} // namespace misc
std::ostream& operator<<(std::ostream& os, const addrinfo&)
{
os << "addrinfo{"
<< "}";
return os;
}

@ -0,0 +1,185 @@
/**
* \file misc/addrinfo/addrinfo.hh
* \brief Structures used to wrap getaddrinfo(3).
*/
#pragma once
#include <cstring>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <utility>
#include "addrinfo-error.hh"
#include "addrinfo-iterator.hh"
namespace misc
{
/**
* \class AddrInfo
* \brief Object representing list of network addresses (IP, Port, Host).
*/
class AddrInfo
{
public:
/**
* \brief Constructor from an addrinfo struct.
*/
AddrInfo(::addrinfo* info)
: info_{info}
{}
~AddrInfo()
{
freeaddrinfo(info_);
}
AddrInfo(const AddrInfo&) = delete;
AddrInfo& operator=(const AddrInfo&) = delete;
AddrInfo(AddrInfo&& addr)
: info_{std::exchange(addr.info_, nullptr)}
{}
AddrInfo& operator=(AddrInfo&& addr)
{
freeaddrinfo(info_);
info_ = std::exchange(addr.info_, nullptr);
return *this;
}
/**
* \brief Begin of the list of addresses.
*/
addrinfo_iterator begin()
{
return addrinfo_iterator(info_);
}
/**
* \brief Begin of the const list of addresses.
*/
addrinfo_const_iterator begin() const
{
return addrinfo_const_iterator(info_);
}
/**
* \brief End of the list of addresses.
*/
addrinfo_iterator end()
{
return addrinfo_iterator();
}
/**
* \brief End of the const list of addresses.
*/
addrinfo_const_iterator end() const
{
return addrinfo_const_iterator();
}
private:
/**
* \brief Addrinfo structure held by the object.
*/
::addrinfo* info_;
};
/**
* \class AddrInfoHint
* \brief Builder class used to specify requirements about addresses
* returned by getaddrinfo(3).
*/
struct AddrInfoHint : public ::addrinfo
{
AddrInfoHint()
{
memset(this, 0, sizeof(::addrinfo));
}
/**
* \brief Specify the flag member of the structure.
*/
AddrInfoHint& flags(int val)
{
this->ai_flags = val;
return *this;
}
/**
* \brief Specify the family member of the structure.
*/
AddrInfoHint& family(int val)
{
this->ai_family = val;
return *this;
}
/**
* \brief Specify the socktype member of the structure.
*/
AddrInfoHint& socktype(int val)
{
this->ai_socktype = val;
return *this;
}
/**
* \brief Specify the protocol member of the structure.
*/
AddrInfoHint& protocol(int val)
{
this->ai_protocol = val;
return *this;
}
/**
* \brief Specify the member of the structure.
*/
AddrInfoHint& addrlen(socklen_t val)
{
this->ai_addrlen = val;
return *this;
}
/**
* \brief Specify the addr member of the structure.
*/
AddrInfoHint& addr(::sockaddr* val)
{
this->ai_addr = val;
return *this;
}
/**
* \brief Specify the canonname member of the structure.
*/
AddrInfoHint& canonname(char* val)
{
this->ai_canonname = val;
return *this;
}
/**
* \brief Specify the member next of the structure.
*/
AddrInfoHint& next(::addrinfo* val)
{
this->ai_next = val;
return *this;
}
};
/**
* \brief Wrapper around getaddrinfo(3).
*/
AddrInfo getaddrinfo(const char* node, const char* service,
const AddrInfoHint& hints);
} // namespace misc
/**
* \brief Outputs the addrinfo to a stream.
*
* Only used for debug purposes.
*/
std::ostream& operator<<(std::ostream&, const ::addrinfo&);

113
src/misc/buffer.cc Normal file

@ -0,0 +1,113 @@
#include <iostream>
#include <exception>
#include "error/connection-closed.hh"
#include "misc/buffer.hh"
namespace misc
{
constexpr size_t CRLF_LENGTH = 2;
constexpr size_t BUFF_SIZE = 8192;
Buffer::Buffer()
{
data_ = std::deque<std::string>(1);
}
void Buffer::append(const std::string& tab)
{
size_t crlf_pos = tab.find("\r\n");
data_.back() += tab.substr(0, crlf_pos);
size_t line_begin = crlf_pos + CRLF_LENGTH;
while (crlf_pos != tab.npos && line_begin < tab.size())
{
data_.back() += "\r\n";
crlf_pos = tab.find("\r\n", line_begin);
data_.emplace_back(tab, line_begin, crlf_pos - line_begin);
line_begin = crlf_pos + CRLF_LENGTH;
}
if (crlf_pos != tab.npos && line_begin == tab.size())
{
data_.back() += "\r\n";
data_.emplace_back();
}
}
size_t Buffer::receive(http::Socket& socket)
{
char buf[BUFF_SIZE];
ssize_t n = socket.recv(buf, BUFF_SIZE);
if (n == 0)
throw http::ConnectionRecvClosedError();
if (n == -1)
return 0;
auto str = std::string(buf, n);
this->append(str);
return n;
}
std::string Buffer::popline()
{
if (this->empty())
throw std::logic_error("Buffer::popline : pop in a empty Buffer");
std::string toReturn = data_.front();
data_.pop_front();
if (data_.size() == 0)
data_.emplace_back();
return toReturn;
}
void Buffer::pop_front(size_t content_length)
{
while (data_.front().size() < content_length)
{
content_length -= data_.front().size();
data_.pop_front();
}
data_.front() = data_.front().substr(content_length);
if (data_.front().empty() && data_.size() > 1)
data_.pop_front();
}
bool Buffer::empty() const
{
return this->get_nb_lines() == 0 && data_.back().empty();
}
const std::string& Buffer::getline(size_t line) const
{
try
{
return data_[line];
}
catch (const std::out_of_range& e)
{
std::cerr << "[Buffer::getline(size_t line)] "
<< line << "out of " << data_.size();
exit(1);
}
}
size_t Buffer::get_nb_lines() const
{
return data_.size() - 1;
}
std::string Buffer::to_string() const
{
std::string str;
for (auto line : data_)
str += line;
return str;
}
}

49
src/misc/buffer.hh Normal file

@ -0,0 +1,49 @@
#pragma once
#include <deque>
#include <string>
#include "socket/default-socket.hh"
namespace misc
{
class Buffer
{
public:
Buffer();
~Buffer() = default;
Buffer(const Buffer&) = default;
Buffer& operator=(const Buffer&) = default;
/**
** \brief Append parsed tab on CRLF characters in data_.
** \param tab The buffer received from Socket::recv() to parse.
*/
void append(const std::string& tab);
/**
** \brief Call the socket recv method and append to the buffer
** \param socket The default socket
*/
size_t receive(http::Socket& socket);
/**
** \brief Pop the string in front of data_ and returns it.
*/
std::string popline();
void pop_front(size_t content_length);
bool empty() const;
const std::string& getline(size_t line) const;
size_t get_nb_lines() const;
std::string to_string() const;
private:
std::deque<std::string> data_;
};
}

42
src/misc/fd.cc Normal file

@ -0,0 +1,42 @@
#include "misc/fd.hh"
#include "misc/socket.hh"
#include "misc/unistd.hh"
#include <iostream>
namespace misc
{
FileDescriptor& FileDescriptor::operator=(FileDescriptor&& fileDescriptor)
{
if (fd_ != -1)
sys::close(fd_);
fd_ = std::exchange(fileDescriptor.fd_, -1);
return *this;
}
FileDescriptor::~FileDescriptor()
{
if (*this)
{
sys::close(fd_);
std::cerr << "Closed " << fd_ << "\n";
}
}
FileDescriptor::operator int() const&
{
return fd_;
}
FileDescriptor::operator bool() const&
{
return fd_ >= 0;
}
void FileDescriptor::fcntl_set(int flags)
{
sys::fcntl_set(fd_, flags);
}
} // namespace misc

70
src/misc/fd.hh Normal file

@ -0,0 +1,70 @@
/**
* \file misc/fd.hh
* \brief FileDescriptor declaration.
*/
#pragma once
#include <fcntl.h>
#include <memory>
#include <unistd.h>
#include <utility>
#include "misc/sys-wrapper.hh"
namespace misc
{
/**
* \class FileDescriptor
* \brief A resource object representing a fd.
*/
struct FileDescriptor
{
/**
* \brief Construct a FileDescriptor from a given fd.
* \param fd int
*/
explicit FileDescriptor(int fd)
: fd_{fd}
{}
FileDescriptor() = default;
FileDescriptor(const FileDescriptor&) = delete;
FileDescriptor& operator=(const FileDescriptor&) = delete;
FileDescriptor(FileDescriptor&& fileDescriptor)
: fd_{std::exchange(fileDescriptor.fd_, -1)}
{}
FileDescriptor& operator=(FileDescriptor&& fileDescriptor);
~FileDescriptor();
/**
* \brief Implicit conversion to int.
*/
operator int() const&;
/**
* \brief Implicit conversion to bool.
*
* \return Whether or not the FileDescriptor holds a valid fd.
*/
operator bool() const&;
/**
* \brief man 2 fcntl
*/
void fcntl_set(int flags);
/**
* \brief The fd held by the FileDescriptor object.
*/
int fd_ = -1;
};
/**
* \brief Alias to the smart pointer used to manage FileDescriptors
* memory allocation.
*/
using shared_fd = std::shared_ptr<FileDescriptor>;
} // namespace misc

13
src/misc/json.hh Normal file

@ -0,0 +1,13 @@
#pragma once
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch-enum"
#pragma GCC diagnostic ignored "-Wswitch-default"
#pragma GCC diagnostic ignored "-Winline"
#ifdef __clang__
#pragma GCC diagnostic ignored "-Wlogical-op-parentheses"
#endif
#include <json.hpp>
#pragma GCC diagnostic pop
using json = nlohmann::json;

11
src/misc/logger.cc Normal file

@ -0,0 +1,11 @@
#include "logger.hh"
#include <iostream>
namespace http
{
void log(std::string str, Color color)
{
std::cout << "\033[1;" + std::to_string(color) + "m" + str + "\033[0m\n";
}
} // namespace http

20
src/misc/logger.hh Normal file

@ -0,0 +1,20 @@
#pragma once
#include <string>
namespace http
{
enum Color
{
black = 30,
red = 31,
green = 32,
yellow = 33,
blue = 34,
magenta = 35,
cyan = 36,
white = 37
};
void log(std::string str, Color color);
} // namespace http

100
src/misc/socket.hh Normal file

@ -0,0 +1,100 @@
/**
* \file misc/socket.hh
* \brief Socket related syscalls.
*/
#pragma once
#include <fcntl.h>
#include <sys/sendfile.h>
#include <sys/socket.h>
#include <sys/types.h>
#include "misc/fd.hh"
#include "misc/sys-wrapper.hh"
namespace sys
{
/**
* \brief accept(2).
*/
inline auto accept = make_wrapper<misc::FileDescriptor>(::accept);
/**
* \brief bind(2).
*/
inline auto bind = make_wrapper<void>(::bind);
/**
* \brief connect(2).
*/
inline auto connect = make_wrapper<void>(::connect);
static int fcntl_wrapper(int fildes, int opts)
{
auto flags = ::fcntl(fildes, F_GETFL);
return ::fcntl(fildes, F_SETFL, flags | opts);
}
/**
* \brief fcntl(2).
*
* Since fcntl is a variadic syscall we need to call a helper function
* with a fixed number of parameter to create a wrapper out of it.
*/
inline auto fcntl_set = make_wrapper<int>(fcntl_wrapper);
/**
* \brief getsockname(2).
*/
inline auto getsockname = make_wrapper<void>(::getsockname);
/**
* \brief listen(2).
*/
inline auto listen = make_wrapper<void>(::listen);
/**
* \brief lseek(2).
*/
inline auto lseek = make_wrapper<off_t>(::lseek);
/**
* \brief read(2).
*/
inline auto read = make_wrapper<ssize_t>(::read);
/**
* \brief recv(2).
*/
inline auto recv = make_wrapper<ssize_t>(::recv);
/**
* \brief send(2).
*/
inline auto send = make_wrapper<ssize_t>(::send);
/**
* \brief sendfile(2).
*/
inline auto sendfile = make_wrapper<ssize_t>(::sendfile);
/**
* \brief setsockopt(2).
*/
inline auto setsockopt = make_wrapper<void>(::setsockopt);
/**
* \brief getsockopt(2).
*/
inline auto getsockopt = make_wrapper<void>(::getsockopt);
/**
* \brief socket(2).
*/
inline auto socket = make_wrapper<misc::FileDescriptor>(::socket);
/**
* \brief getpeername(2).
*/
inline auto getpeername = make_wrapper<void>(::getpeername);
} // namespace sys

63
src/misc/sys-wrapper.hh Normal file

@ -0,0 +1,63 @@
/**
* \file misc/sys-wrapper.hh
* \brief SysWrapper declaration.
*/
#pragma once
#include <functional>
#include <system_error>
#include <utility>
namespace sys
{
/**
* \class SysWrapper
* \brief Wrapping around syscalls to convert failure into exceptions.
*
* Wrapping syscalls this way helps to insure the RAII idiom.
*/
template <typename RetType, typename SysRetType, typename... Args>
class SysWrapper
{
public:
SysWrapper(SysRetType syscall(Args...))
: syscall_{syscall}
{}
/* Needs template to enable universal referencing.
Template deduction should be done automatically. */
template <typename... UniversalArgs>
RetType operator()(UniversalArgs&&... args)
{
SysRetType ret;
do
{
errno = 0;
ret = syscall_(std::forward<UniversalArgs>(args)...);
} while (errno == EINTR);
if (ret == SysRetType(-1) && errno != EWOULDBLOCK
&& errno != EAGAIN)
{
throw std::system_error(errno, std::system_category());
}
return RetType(ret);
}
private:
std::function<SysRetType(Args...)> syscall_;
};
/**
* \brief Helper function to build SysWrapper.
*/
template <typename RetType, typename SysRetType, typename... Args>
SysWrapper<RetType, SysRetType, Args...>
make_wrapper(SysRetType syscall(Args...))
{
return SysWrapper<RetType, SysRetType, Args...>(syscall);
}
} // namespace sys

55
src/misc/unistd.hh Normal file

@ -0,0 +1,55 @@
/**
* \file misc/unistd.hh
* \brief unistd.h syscall wrappers.
*/
#pragma once
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "misc/fd.hh"
#include "misc/sys-wrapper.hh"
namespace sys
{
/**
* \brief close(2).
*/
inline auto close = make_wrapper<void>(::close);
/**
* \brief fork(2).
*/
inline auto fork = make_wrapper<pid_t>(::fork);
/**
* \brief fstat(2).
*/
inline auto fstat = make_wrapper<int>(::fstat);
/**
* \brief kill(2).
*/
inline auto kill = make_wrapper<void>(::kill);
static int open_wrapper(const char* path, int flags)
{
return ::open(path, flags);
}
/**
* \brief open(2).
*
* Since open is a variadic syscall we need to call a helper with a
* fixed number of parameters to create a wrapper out of it.
*/
inline auto open = make_wrapper<misc::FileDescriptor>(open_wrapper);
/**
* \brief wait(2).
*/
inline auto waitpid = make_wrapper<void>(::waitpid);
} // namespace sys

@ -0,0 +1,81 @@
#include <cerrno>
#include "socket/default-socket.hh"
#include "misc/socket.hh"
#include "misc/unistd.hh"
#include "error/connection-closed.hh"
#include <iostream>
namespace paxos
{
DefaultSocket::DefaultSocket(const misc::shared_fd& fd)
: Socket(fd)
{}
DefaultSocket::DefaultSocket(int domain, int type, int protocol)
: Socket{std::make_shared<misc::FileDescriptor>(
sys::socket(domain, type, protocol))}
{
}
void DefaultSocket::listen(int backlog)
{
sys::listen(*fd_, backlog);
}
ssize_t DefaultSocket::recv(void* dst, size_t len)
{
return sys::recv(*fd_, dst, len, MSG_NOSIGNAL);
}
ssize_t DefaultSocket::send(const void* src, size_t len)
{
return sys::send(*fd_, src, len, MSG_NOSIGNAL);
}
ssize_t DefaultSocket::sendfile(misc::shared_fd& fd, off_t& offset, size_t len)
{
return sys::sendfile(*fd_, *fd, &offset, len);
}
void DefaultSocket::bind(const sockaddr* addr, socklen_t addrlen)
{
sys::bind(*fd_, addr, addrlen);
}
void DefaultSocket::fcntl_set_O_NONBLOCK()
{
fd_->fcntl_set(O_NONBLOCK);
}
void DefaultSocket::setsockopt(int level, int optname, int optval)
{
sys::setsockopt(*fd_, level, optname, &optval, sizeof(int));
}
void DefaultSocket::getsockopt(int level, int optname, int& optval)
{
unsigned int optlen;
sys::getsockopt(*fd_, level, optname, &optval, &optlen);
}
shared_socket DefaultSocket::accept(sockaddr* addr, socklen_t* addrlen)
{
misc::shared_fd client_fd = std::make_shared<misc::FileDescriptor>(sys::accept(*fd_, addr, addrlen));
auto ret = std::make_shared<DefaultSocket>(client_fd);
//Set the ip and listener_port of the accepted socket
ret->set_listener_port(port_);
ret->set_ip(ip_);
return ret;
}
void DefaultSocket::connect(const sockaddr* addr, socklen_t len)
{
sys::connect(*fd_, addr, len);
}
// Implement all other methods (see header).
} // namespace http

@ -0,0 +1,49 @@
/**
* \file socket/default_socket.hh
* \brief DefaultSocket declaration.
*/
#pragma once
#include "misc/socket.hh"
#include "socket/socket.hh"
namespace paxos
{
/**
* \struct DefaultSocket
* \brief Implementation of the Socket interface.
*/
struct DefaultSocket : public Socket
{
explicit DefaultSocket(const misc::shared_fd&);
DefaultSocket(int domain, int type, int protocol);
DefaultSocket() = default;
DefaultSocket(const DefaultSocket&) = delete;
DefaultSocket& operator=(const DefaultSocket&) = delete;
DefaultSocket(DefaultSocket&&) = default;
DefaultSocket& operator=(DefaultSocket&&) = default;
~DefaultSocket() = default;
ssize_t recv(void* dst, size_t len) final;
ssize_t send(const void* src, size_t len) final;
ssize_t sendfile(misc::shared_fd& fd, off_t& offset, size_t len) final;
void bind(const sockaddr* addr, socklen_t addrlen) final;
void listen(int backlog) final;
void fcntl_set_O_NONBLOCK() final;
void setsockopt(int level, int optname, int optval) final;
void getsockopt(int level, int optname, int& optval) final;
shared_socket accept(sockaddr* addr, socklen_t* addrlen) final;
void connect(const sockaddr*, socklen_t) final;
};
} // namespace http

189
src/socket/socket.hh Normal file

@ -0,0 +1,189 @@
/**
* \file socket/socket.hh
* \brief Socket declaration.
*/
#pragma once
#include <iostream>
#include <arpa/inet.h>
#include <cstring>
#include <memory>
#include <string>
#include <sys/socket.h>
#include "misc/fd.hh"
#include "misc/socket.hh"
namespace paxos
{
/**
* \struct Socket
* \brief Value object representing a socket.
*
* socket(7)
*/
struct Socket
{
/**
* \brief Create a Socket from a fd.
*/
explicit Socket(const misc::shared_fd& fd)
: fd_{fd}
{}
Socket() = default;
Socket(const Socket&) = delete;
Socket& operator=(const Socket&) = delete;
Socket(Socket&&) = default;
Socket& operator=(Socket&&) = default;
virtual ~Socket() = default;
/**
* \brief recv(2).
*/
virtual ssize_t recv(void* dst, size_t len) = 0;
/**
* \brief send(2).
*/
virtual ssize_t send(const void* src, size_t len) = 0;
/**
* \brief sendfile(2).
*/
virtual ssize_t sendfile(misc::shared_fd&, off_t&, size_t) = 0;
/**
* \brief bind(2).
*/
virtual void bind(const sockaddr* addr, socklen_t addrlen) = 0;
/**
* \brief listen(2).
*/
virtual void listen(int backlog) = 0;
/**
* \brief fcntl(2). Demander aux yaka
*/
virtual void fcntl_set_O_NONBLOCK() = 0;
/**
* \brief setsockopt(2).
*/
virtual void setsockopt(int level, int optname, int optval) = 0;
/**
* \brief getsockopt(2).
*/
virtual void getsockopt(int level, int optname, int& optval) = 0;
/**
* \brief accept(2).
*/
virtual std::shared_ptr<Socket> accept(sockaddr* addr,
socklen_t* addrlen) = 0;
std::string getsockname() const
{
char ip[INET6_ADDRSTRLEN];
struct sockaddr_in6 addr;
bzero(&addr, sizeof(struct sockaddr_in6));
unsigned int len = sizeof(struct sockaddr_in6);
sys::getsockname(fd_->fd_, (struct sockaddr *) &addr, &len);
if (addr.sin6_family == AF_INET6)
inet_ntop(addr.sin6_family, &addr.sin6_addr, ip,
INET6_ADDRSTRLEN);
else
inet_ntop(addr.sin6_family,
&((struct sockaddr_in *)&addr)->sin_addr, ip,
INET6_ADDRSTRLEN);
return ip;
}
std::string getpeername()
{
char ip[INET6_ADDRSTRLEN];
struct sockaddr_in6 addr;
bzero(&addr, sizeof(struct sockaddr_in6));
unsigned int len = sizeof(struct sockaddr_in6);
sys::getpeername(fd_->fd_, (struct sockaddr *) &addr, &len);
if (addr.sin6_family == AF_INET6)
inet_ntop(addr.sin6_family, &addr.sin6_addr, ip,
INET6_ADDRSTRLEN);
else
inet_ntop(addr.sin6_family,
&((struct sockaddr_in *)&addr)->sin_addr, ip,
INET6_ADDRSTRLEN);
return ip;
}
/**
* \brief connect(2).
*/
virtual void connect(const sockaddr*, socklen_t) = 0;
const misc::shared_fd fd_get() const noexcept
{
return fd_;
}
const std::string& get_port() const
{
return port_;
}
void set_port(const std::string& port)
{
port_ = port;
}
const std::string& get_listener_port() const
{
return listener_port_;
}
void set_listener_port(const std::string& listener_port)
{
listener_port_ = listener_port;
}
const std::string& get_ip() const
{
return ip_;
}
void set_ip(const std::string& ip)
{
ip_ = ip;
}
protected:
/**
* \brief File descriptor of the socket.
*/
misc::shared_fd fd_;
/**
* \brief The port of the socket
*/
std::string port_;
/**
* \brief The port of the listening socket which accepted this socket
*/
std::string listener_port_;
/**
* \brief The ip of the socket
*/
std::string ip_;
};
using shared_socket = std::shared_ptr<Socket>;
} // namespace http