Commit 9fcc1835 authored by Mathieu Stefani's avatar Mathieu Stefani Committed by Mathieu STEFANI

Added a basic HTTP handler and request / response classes

parent 17282a44
#include "net.h"
#include "peer.h"
#include "http.h"
#include <iostream>
#include <cstring>
using namespace std;
class MyHandler : public Net::Http::Handler {
void onRequest(const Net::Http::Request& req, Net::Tcp::Peer& peer) {
cout << "Received " << methodString(req.method) << " request on " << req.resource << endl;
if (req.resource == "/ping") {
if (req.method == Net::Http::Method::Get) {
cout << "PONG" << endl;
Net::Http::Response response(Net::Http::Code::Ok, "PONG");
response.writeTo(peer);
}
}
}
};
int main(int argc, char *argv[]) {
Net::Port port(9080);
......@@ -18,5 +34,7 @@ int main(int argc, char *argv[]) {
cout << "Cores = " << hardware_concurrency() << endl;
Net::Http::Server server(addr);
server.setHandler(std::make_shared<MyHandler>());
server.serve();
}
......@@ -2,6 +2,9 @@ set(SOURCE_FILES
listener.cc
net.cc
peer.cc
os.cc
peer.cc
http.cc
)
add_library(net ${SOURCE_FILES})
......
......@@ -6,6 +6,7 @@
#pragma once
#include <sstream>
#include <cstdio>
#include <cassert>
#include <sys/types.h>
......@@ -26,8 +27,10 @@
[&]() { \
auto ret = __VA_ARGS__; \
if (ret < 0) { \
perror(#__VA_ARGS__); \
throw std::runtime_error(#__VA_ARGS__); \
const char *str = #__VA_ARGS__; \
std::ostringstream oss; \
oss << str << ": " << strerror(errno); \
throw std::runtime_error(oss.str()); \
} \
return ret; \
}(); \
......
......@@ -7,8 +7,10 @@
#include <cstring>
#include <iostream>
#include <stdexcept>
#include "common.h"
#include "http.h"
#include "net.h"
#include "peer.h"
using namespace std;
......@@ -16,10 +18,11 @@ namespace Net {
namespace Http {
namespace Private {
static constexpr char CR = 0xD;
static constexpr char LF = 0xA;
static constexpr char CRLF[] = {CR, LF};
static constexpr char CR = 0xD;
static constexpr char LF = 0xA;
namespace Private {
void
Parser::advance(size_t count) {
......@@ -32,7 +35,25 @@ namespace Private {
bool
Parser::eol() const {
return buffer[cursor] == CR && buffer[cursor + 1] == LF;
return buffer[cursor] == CR && next() == LF;
}
char
Parser::next() const {
if (cursor + 1 >= len) {
throw std::runtime_error("Early EOF");
}
return buffer[cursor + 1];
}
Request
Parser::expectRequest() {
expectRequestLine();
expectHeaders();
expectBody();
return request;
}
// 5.1 Request-Line
......@@ -99,6 +120,7 @@ namespace Private {
void
Parser::expectHeaders() {
while (!eol()) {
// Read the header name
size_t start = cursor;
while (buffer[cursor] != ':')
......@@ -111,51 +133,135 @@ namespace Private {
// Skip the ':'
advance(1);
// Read the header value
start = cursor;
while (!eol())
advance(1);
std::string fieldValue = std::string(buffer + start, cursor - start);
advance(2);
if (fieldName == "Content-Length") {
size_t pos;
contentLength = std::stol(fieldValue, &pos);
}
request.headers.push_back(make_pair(std::move(fieldName), std::move(fieldValue)));
// CRLF
advance(2);
}
}
void
Parser::expectBody() {
if (contentLength > 0) {
advance(2); // CRLF
if (cursor + contentLength > len) {
throw std::runtime_error("Corrupted HTTP Body");
}
request.body = std::string(buffer + cursor, contentLength);
}
}
} // namespace Private
const char* methodString(Method method)
{
switch (method) {
#define METHOD(name, str) \
case Method::name: \
return str;
HTTP_METHODS
#undef METHOD
}
return nullptr;
}
const char* codeString(Code code)
{
switch (code) {
#define CODE(_, name, str) \
case Code::name: \
return str;
STATUS_CODES
#undef CODE
}
return nullptr;
}
Message::Message()
{ }
Request::Request()
: Message()
{ }
Response::Response(Code code, std::string body)
: Message()
{
this->body = std::move(body);
code_ = code;
}
const char* methodString(Method method)
void
Response::writeTo(Tcp::Peer& peer)
{
static constexpr size_t MethodsCount
= sizeof priv__methodStrings / sizeof *priv__methodStrings;
int fd = peer.fd();
char buffer[Const::MaxBuffer];
std::memset(buffer, 0, Const::MaxBuffer);
char *p_buf = buffer;
int index = static_cast<int>(method);
if (index >= MethodsCount)
throw std::logic_error("Invalid method index");
auto writeRaw = [&](const void* data, size_t len) {
p_buf = static_cast<char *>(memcpy(p_buf, data, len));
p_buf += len;
};
return priv__methodStrings[index];
auto writeString = [&](const char* str) {
const size_t len = std::strlen(str);
writeRaw(str, std::strlen(str));
};
auto writeInt = [&](uint64_t value) {
auto str = std::to_string(value);
writeRaw(str.c_str(), str.size());
};
auto writeChar = [&](char c) {
*p_buf++ = c;
};
writeString("HTTP/1.1 ");
writeInt(static_cast<int>(code_));
writeChar(' ');
writeString(codeString(code_));
writeRaw(CRLF, 2);
writeString("Content-Length:");
writeInt(body.size());
writeRaw(CRLF, 2);
writeRaw(CRLF, 2);
writeString(body.c_str());
const size_t len = p_buf - buffer;
ssize_t bytes = send(fd, buffer, len, 0);
cout << bytes << " bytes sent" << endl;
}
void
Handler::onInput(const char* buffer, size_t len) {
cout << "Received http request " << string(buffer, len) << endl;
Handler::onInput(const char* buffer, size_t len, Tcp::Peer& peer) {
Private::Parser parser(buffer, len);
parser.expectRequestLine();
parser.expectHeaders();
auto request = parser.expectRequest();
auto req = parser.request;
cout << "method = " << methodString(req.method) << endl;
cout << "resource = " << req.resource << endl;
cout << "headers = " << endl;
for (const auto& header: req.headers) {
cout << string(' ', 4) << header.first << " -> " << header.second << endl;
}
onRequest(request, peer);
}
void
......@@ -169,11 +275,19 @@ Server::Server(const Net::Address& addr)
: listener(addr)
{ }
void
Server::setHandler(const std::shared_ptr<Handler>& handler) {
handler_ = handler;
}
void
Server::serve()
{
if (!handler_)
throw std::runtime_error("Must call setHandler() prior to serve()");
listener.init(4);
listener.setHandler(make_shared<Handler>());
listener.setHandler(handler_);
if (listener.bind()) {
const auto& addr = listener.address();
......
......@@ -23,6 +23,48 @@ namespace Http {
METHOD(Trace, "TRACE") \
METHOD(Connect, "CONNECT")
// 10. Status Code Definitions
#define STATUS_CODES \
CODE(100, Continue, "Continue") \
CODE(101, Switching_Protocols, "Switching Protocols") \
CODE(200, Ok, "OK") \
CODE(201, Created, "Created") \
CODE(202, Accepted, "Accepted") \
CODE(203, NonAuthoritative_Information, "Non-Authoritative Information") \
CODE(204, No_Content, "No Content") \
CODE(205, Reset_Content, "Reset Content") \
CODE(206, Partial_Content, "Partial Content") \
CODE(300, Multiple_Choices, "Multiple Choices") \
CODE(301, Moved_Permanently, "Moved Permanently") \
CODE(302, Found, "Found") \
CODE(303, See_Other, "See Other") \
CODE(304, Not_Modified, "Not Modified") \
CODE(305, Use_Proxy, "Use Proxy") \
CODE(307, Temporary_Redirect, "Temporary Redirect") \
CODE(400, Bad_Request, "Bad Request") \
CODE(401, Unauthorized, "Unauthorized") \
CODE(402, Payment_Required, "Payment Required") \
CODE(403, Forbidden, "Forbidden") \
CODE(404, Not_Found, "Not Found") \
CODE(405, Method_Not_Allowed, "Method Not Allowed") \
CODE(406, Not_Acceptable, "Not Acceptable") \
CODE(407, Proxy_Authentication_Required, "Proxy Authentication Required") \
CODE(408, Request_Timeout, "Request Timeout") \
CODE(409, Conflict, "Conflict") \
CODE(410, Gone, "Gone") \
CODE(411, Length_Required, "Length Required") \
CODE(412, Precondition_Failed, "Precondition Failed") \
CODE(413, Request_Entity_Too_Large, "Request Entity Too Large") \
CODE(414, RequestURI_Too_Long, "Request-URI Too Long") \
CODE(415, Unsupported_Media_Type, "Unsupported Media Type") \
CODE(416, Requested_Range_Not_Satisfiable, "Requested Range Not Satisfiable") \
CODE(417, Expectation_Failed, "Expectation Failed") \
CODE(500, Internal_Server_Error, "Internal Server Error") \
CODE(501, Not_Implemented, "Not Implemented") \
CODE(502, Bad_Gateway, "Bad Gateway") \
CODE(503, Service_Unavailable, "Service Unavailable") \
CODE(504, Gateway_Timeout, "Gateway Timeout")
enum class Method {
#define METHOD(m, _) m,
......@@ -30,23 +72,42 @@ enum class Method {
#undef METHOD
};
static constexpr const char* priv__methodStrings[] = {
#define METHOD(_, str) #str,
HTTP_METHODS
#undef METHOD
enum class Code {
#define CODE(value, name, _) name = value,
STATUS_CODES
#undef CODE
};
const char* methodString(Method method);
const char* codeString(Code code);
// 4. HTTP Message
class Message {
public:
Message();
std::vector<std::pair<std::string, std::string>> headers;
std::string body;
};
class Request {
// 5. Request
class Request : public Message {
public:
Request();
Method method;
std::string resource;
std::string body;
std::vector<std::pair<std::string, std::string>> headers;
};
// 6. Response
class Response : public Message {
public:
Response(Code code, std::string body);
void writeTo(Tcp::Peer& peer);
private:
Code code_;
};
namespace Private {
......@@ -55,10 +116,13 @@ namespace Private {
: buffer(buffer)
, len(len)
, cursor(0)
, contentLength(-1)
{ }
Request expectRequest();
void expectRequestLine();
void expectHeaders();
void expectBody();
void advance(size_t count);
bool eol() const;
......@@ -67,7 +131,9 @@ namespace Private {
size_t len;
size_t cursor;
char next() const;
private:
ssize_t contentLength;
Request request;
};
......@@ -75,8 +141,10 @@ namespace Private {
class Handler : public Net::Tcp::Handler {
public:
void onInput(const char* buffer, size_t len);
void onInput(const char* buffer, size_t len, Tcp::Peer& peer);
void onOutput();
virtual void onRequest(const Request& request, Tcp::Peer& peer) = 0;
};
class Server {
......@@ -84,9 +152,11 @@ public:
Server();
Server(const Net::Address& addr);
void setHandler(const std::shared_ptr<Handler>& handler);
void serve();
private:
std::shared_ptr<Handler> handler_;
Net::Tcp::Listener listener;
};
......
......@@ -16,6 +16,7 @@
#include "listener.h"
#include "peer.h"
#include "common.h"
#include "os.h"
using namespace std;
......@@ -23,6 +24,33 @@ namespace Net {
namespace Tcp {
struct Message {
virtual ~Message() { }
enum class Type { NewPeer, Shutdown };
virtual Type type() const = 0;
};
struct PeerMessage : public Message {
PeerMessage(const std::shared_ptr<Peer>& peer)
: peer_(peer)
{ }
Type type() const { return Type::NewPeer; }
std::shared_ptr<Peer> peer() const { return peer_; }
private:
std::shared_ptr<Peer> peer_;
};
template<typename To>
To *message_cast(const std::unique_ptr<Message>& from)
{
return static_cast<To *>(from.get());
}
IoWorker::IoWorker() {
epoll_fd = TRY_RET(epoll_create(128));
}
......@@ -34,20 +62,33 @@ IoWorker::~IoWorker() {
}
void
IoWorker::start() {
IoWorker::start(const std::shared_ptr<Handler>& handler) {
handler_ = handler;
thread.reset(new std::thread([this]() {
this->run();
}));
}
std::shared_ptr<Peer>
IoWorker::getPeer(Fd fd) const
{
auto it = peers.find(fd);
if (it == std::end(peers))
{
throw std::runtime_error("No peer found for fd: " + to_string(fd));
}
return it->second;
}
void
IoWorker::readIncomingData(int fd) {
IoWorker::handleIncoming(const std::shared_ptr<Peer>& peer) {
char buffer[Const::MaxBuffer];
memset(buffer, 0, sizeof buffer);
ssize_t totalBytes = 0;
int fd = peer->fd();
for (;;) {
......@@ -56,14 +97,15 @@ IoWorker::readIncomingData(int fd) {
bytes = recv(fd, buffer + totalBytes, Const::MaxBuffer - totalBytes, 0);
if (bytes == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
cout << "Received " << buffer << endl;
handler_->onInput(buffer, totalBytes, *peer);
} else {
throw std::runtime_error(strerror(errno));
}
break;
}
else if (bytes == 0) {
cout << "Peer has been shutdowned" << endl;
cout << "Peer " << *peer << " has disconnected" << endl;
close(fd);
break;
}
......@@ -78,6 +120,21 @@ IoWorker::readIncomingData(int fd) {
}
void
IoWorker::handleNewPeer(const std::shared_ptr<Peer>& peer)
{
std::cout << "New peer: " << *peer << std::endl;
int fd = peer->fd();
struct epoll_event event;
event.events = EPOLLIN;
event.data.fd = fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event);
peers.insert(std::make_pair(fd, peer));
}
void
IoWorker::run() {
struct epoll_event events[Const::MaxEvents];
......@@ -89,21 +146,19 @@ IoWorker::run() {
break;
case 0:
if (!mailbox.isEmpty()) {
int *fd = mailbox.clear();
struct epoll_event event;
event.events = EPOLLIN;
event.data.fd = *fd;
std::unique_ptr<Message> msg(mailbox.clear());
if (msg->type() == Message::Type::NewPeer) {
auto peer_msg = message_cast<PeerMessage>(msg);
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, *fd, &event);
handleNewPeer(peer_msg->peer());
}
delete fd;
}
break;
default:
for (int i = 0; i < ready_fds; ++i) {
const struct epoll_event *event = events + i;
readIncomingData(event->data.fd);
handleIncoming(getPeer(event->data.fd));
}
break;
......@@ -111,13 +166,62 @@ IoWorker::run() {
}
}
Handler::Handler()
{ }
Handler::~Handler()
{ }
void
Handler::onConnection() {
}
void
Handler::onDisconnection() {
}
Listener::Listener()
: listen_fd(-1)
{ }
Listener::Listener(const Address& address)
: addr_(address)
, listen_fd(-1)
{
}
bool Listener::bind() {
void
Listener::init(size_t workers)
{
if (workers > hardware_concurrency()) {
// Log::warning() << "More workers than available cores"
}
for (size_t i = 0; i < workers; ++i) {
auto wrk = std::unique_ptr<IoWorker>(new IoWorker);
ioGroup.push_back(std::move(wrk));
}
}
void
Listener::setHandler(const std::shared_ptr<Handler>& handler)
{
handler_ = handler;
}
bool
Listener::bind() {
return bind(addr_);
}
bool
Listener::bind(const Address& address) {
if (ioGroup.empty()) {
throw std::runtime_error("Call init() before calling bind()");
}
addr_ = address;
struct addrinfo hints;
hints.ai_family = AF_INET;
......@@ -135,7 +239,7 @@ bool Listener::bind() {
char port[MaxPortLen];
std::fill(port, port + MaxPortLen, 0);
std::snprintf(port, MaxPortLen, "%d", addr_.port());
std::snprintf(port, MaxPortLen, "%d", static_cast<uint16_t>(addr_.port()));
struct addrinfo *addrs;
TRY(::getaddrinfo(host.c_str(), port, &hints, &addrs));
......@@ -156,16 +260,15 @@ bool Listener::bind() {
listen_fd = fd;
for (int i = 0; i < 4; ++i) {
auto wrk = std::unique_ptr<IoWorker>(new IoWorker);
wrk->start();
ioGroup.push_back(std::move(wrk));
for (auto& io: ioGroup) {
io->start(handler_);
}
return true;
}
void Listener::run() {
void
Listener::run() {
for (;;) {
struct sockaddr_in peer_addr;
socklen_t peer_addr_len = sizeof(peer_addr);
......@@ -177,31 +280,40 @@ void Listener::run() {
if (getnameinfo((struct sockaddr *)&peer_addr, peer_addr_len, peer_host, NI_MAXHOST, nullptr, 0, 0) == 0) {
Address addr = Address::fromUnix((struct sockaddr *)&peer_addr);
Peer peer(addr, peer_host);
auto peer = make_shared<Peer>(addr, peer_host);
peer->associateFd(client_fd);
std::cout << "New peer: " << peer << std::endl;
dispatchConnection(client_fd);
dispatchPeer(peer);
}
}
}
Address
Listener::address() const {
return addr_;
}
void
Listener::dispatchPeer(const std::shared_ptr<Peer>& peer) {
const size_t workers = ioGroup.size();
size_t start = peer->fd() % workers;
void Listener::dispatchConnection(int fd) {
size_t start = fd % 4;
/* Find the first available worker */
size_t current = start;
for (;;) {
auto& mailbox = ioGroup[current]->mailbox;
if (mailbox.isEmpty()) {
int *message = new int(fd);
int *old = mailbox.post(message);
auto message = new PeerMessage(peer);
auto *old = mailbox.post(message);
assert(old == nullptr);
return;
}
current = (current + 1) % 4;
current = (current + 1) % workers;
if (current == start) {
break;
}
......
......@@ -8,45 +8,81 @@
#include "net.h"
#include "mailbox.h"
#include "os.h"
#include <vector>
#include <memory>
#include <thread>
#include <unordered_map>
namespace Net {
namespace Tcp {
class Peer;
class Message;
class Handler;
class IoWorker {
public:
Mailbox<int> mailbox;
Mailbox<Message> mailbox;
IoWorker();
~IoWorker();
void start();
void start(const std::shared_ptr<Handler> &handler);
private:
int epoll_fd;
std::unique_ptr<std::thread> thread;
std::unordered_map<Fd, std::shared_ptr<Peer>> peers;
std::shared_ptr<Handler> handler_;
std::shared_ptr<Peer> getPeer(Fd fd) const;
void handleIncoming(const std::shared_ptr<Peer>& peer);
void handleNewPeer(const std::shared_ptr<Peer>& peer);
void run();
};
class Listener {
public:
friend class IoWorker;
Listener();
Listener(const Address& address);
void init(size_t workers);
void setHandler(const std::shared_ptr<Handler>& handler);
bool bind();
bool bind(const Address& adress);
void readIncomingData(int fd);
void run();
Address address() const;
private:
Address addr_;
int listen_fd;
std::vector<std::unique_ptr<IoWorker>> ioGroup;
std::shared_ptr<Handler> handler_;
void dispatchPeer(const std::shared_ptr<Peer>& peer);
};
class Listener {
public:
Listener(const Address& address);
class Handler {
public:
Handler();
~Handler();
bool bind();
void run();
virtual void onInput(const char *buffer, size_t len, Tcp::Peer& peer) = 0;
virtual void onOutput() = 0;
private:
virtual void onConnection();
virtual void onDisconnection();
Address addr_;
int listen_fd;
std::vector<std::unique_ptr<IoWorker>> ioGroup;
};
void dispatchConnection(int fd);
};
} // namespace Tcp
......
......@@ -13,6 +13,10 @@
template<typename T>
class Mailbox {
public:
Mailbox() {
data.store(nullptr);
}
const T *get() const {
if (isEmpty()) {
throw std::runtime_error("Can not retrieve mail from empty mailbox");
......
......@@ -9,7 +9,6 @@
#include <stdexcept>
#include <limits>
#include <cstring>
#include <fcntl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
......@@ -17,16 +16,19 @@ using namespace std;
namespace Net {
bool make_non_blocking(int sfd)
{
int flags = fcntl (sfd, F_GETFL, 0);
if (flags == -1) return false;
Port::Port(uint16_t port)
: port(port)
{ }
flags |= O_NONBLOCK;
int ret = fcntl (sfd, F_SETFL, flags);
if (ret == -1) return false;
bool
Port::isReserved() const {
return port < 1024;
}
return true;
bool
Port::isUsed() const {
throw std::runtime_error("Unimplemented");
return false;
}
Ipv4::Ipv4(uint8_t a, uint8_t b, uint8_t c, uint8_t d)
......@@ -36,11 +38,13 @@ Ipv4::Ipv4(uint8_t a, uint8_t b, uint8_t c, uint8_t d)
, d(d)
{ }
Ipv4 Ipv4::any() {
Ipv4
Ipv4::any() {
return Ipv4(0, 0, 0, 0);
}
std::string Ipv4::toString() const {
std::string
Ipv4::toString() const {
static constexpr size_t MaxSize = sizeof("255") * 4 + 3 + 1; /* 4 * 255 + 3 * dot + \0 */
char buff[MaxSize];
......@@ -88,6 +92,7 @@ Address
Address::fromUnix(struct sockaddr* addr) {
struct sockaddr_in *in_addr = reinterpret_cast<struct sockaddr_in *>(addr);
std::string host = TRY_RET(inet_ntoa(in_addr->sin_addr));
int port = in_addr->sin_port;
return Address(std::move(host), port);
......
......@@ -10,9 +10,18 @@
namespace Net {
typedef uint16_t Port;
class Port {
public:
Port(uint16_t port = 0);
operator uint16_t() const { return port; }
bool isReserved() const;
bool isUsed() const;
bool make_non_blocking(int fd);
private:
uint16_t port;
};
class Ipv4 {
public:
......
......@@ -5,17 +5,22 @@
#include "peer.h"
#include <iostream>
#include <stdexcept>
namespace Net {
namespace Tcp {
using namespace std;
Peer::Peer()
: fd_(-1)
{ }
Peer::Peer(const Address& addr, const string& host)
: addr(addr)
, host(host)
, fd_(-1)
{ }
Address
......@@ -28,10 +33,26 @@ Peer::hostname() const {
return host;
}
void
Peer::associateFd(int fd) {
fd_ = fd;
}
int
Peer::fd() const {
if (fd_ == -1) {
throw std::runtime_error("The peer has no associated fd");
}
return fd_;
}
std::ostream& operator<<(std::ostream& os, const Peer& peer) {
const auto& addr = peer.address();
os << "(" << addr.host() << ", " << addr.port() << ") [" << peer.hostname() << "]";
return os;
}
} // namespace Tcp
} // namespace Net
......@@ -7,25 +7,33 @@
#pragma once
#include "net.h"
#include "os.h"
#include <string>
#include <iostream>
namespace Net {
namespace Tcp {
class Peer {
public:
Peer();
Peer(const Address& addr, const std::string& hostname);
Address address() const;
std::string hostname() const;
void associateFd(Fd fd);
Fd fd() const;
private:
Address addr;
std::string host;
Fd fd_;
};
std::ostream& operator<<(std::ostream& os, const Peer& peer);
} // namespace Tcp
} // namespace Net
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment