Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .clang-format-ignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
third_party/**
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,16 @@ SRCS = $(addprefix $(SRC_DIR)/, \
handler/static_file_handler.hpp \
handler/handler.cpp \
handler/handler.hpp \
http/http_parser.cpp \
http/http_parser.hpp \
http/http_request.hpp \
http/http_response.cpp \
http/http_response.hpp \
router/router.cpp \
router/router.hpp \
util/str_split.cpp \
util/str_trim.cpp \
util/string.hpp \
util/syscall_error.cpp \
util/syscall_error.hpp \
main.cpp \
Expand Down Expand Up @@ -94,9 +99,11 @@ TEST_TARGET = $(BIN_DIR)/$(TEST_NAME)

# All tests sources (keep in alphabetical order)
TEST_SRCS = $(addprefix $(TEST_SRC_DIR)/, \
http_parser_unittest.cpp \
http_response_unittest.cpp \
router_unittest.cpp \
static_file_handler_unittest.cpp \
str_split_unittest.cpp \
main.cpp \
)

Expand Down
3 changes: 1 addition & 2 deletions src/core/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ Client::Client(uint64_t id, int fd, const sockaddr_in& addr)
fd_(fd),
addr_(addr),
handler_(NULL)

{
set_nonblocking();
std::cout << "Client constructor called" << std::endl;
Expand All @@ -24,7 +23,7 @@ Client::~Client()
close(fd_);
}
if (handler_) {
delete (handler_);
delete handler_;
}
std::cout << "Client destructor called" << std::endl;
}
Expand Down
6 changes: 4 additions & 2 deletions src/core/client.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define CORE_CLIENT_HPP_

#include "handler/handler.hpp"
#include "http/http_parser.hpp"
#include "http/http_request.hpp"
#include "http/http_response.hpp"

Expand All @@ -26,9 +27,10 @@ class Client {
const sockaddr_in& addr() const { return addr_; }
const HttpRequest& req() const { return req_; }
HttpResponse& res() { return res_; }
std::string& recv_buffer() { return recv_buffer_; }

std::string& send_buffer() { return send_buffer_; }
Handler* handler() const { return handler_; }
HttpParser& parser() { return parser_; }

private:
Client();
Expand All @@ -41,8 +43,8 @@ class Client {
sockaddr_in addr_;
HttpRequest req_;
HttpResponse res_; // TODO: remove
std::string recv_buffer_;
std::string send_buffer_;
HttpParser parser_;
Handler* handler_;
};

Expand Down
100 changes: 59 additions & 41 deletions src/core/server.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#include "core/server.hpp"

#include "core/client.hpp"
#include "core/signals.hpp"
#include "http/http_request.hpp"
#include "http/http_parser.hpp"
#include "router/router.hpp"
#include "util/syscall_error.hpp"

Expand All @@ -15,6 +16,7 @@
#include <sys/stat.h>
#include <unistd.h>

#include <cassert>
#include <cstring>
#include <iostream>

Expand Down Expand Up @@ -75,15 +77,20 @@ uint64_t Server::add_connection(int client_fd, const sockaddr_in& addr)
return client_id;
}

void Server::remove_connection(uint64_t id)
void Server::close_connection(Client& conn)
{
std::map<uint64_t, Client*>::iterator it = clients_.find(id);
uint64_t client_id = conn.id();

epoll_ctl(epoll_fd_, EPOLL_CTL_DEL, conn.fd(), NULL);

std::map<uint64_t, Client*>::iterator it = clients_.find(client_id);
if (it == clients_.end()) {
std::cerr << "[WARNING] Attempted to remove inexistant client id" << std::endl;
return;
}

delete it->second;
clients_.erase(id);
clients_.erase(client_id);
}

Client& Server::get_client(uint64_t id)
Expand Down Expand Up @@ -174,57 +181,68 @@ static void print_raw_data(const std::string& s)
void Server::handle_events(Client& conn, uint32_t events)
{
char tmp[4096];

// No handler yet, assume it's a new request (hardcoded for now)
if (!conn.handler()) {
HttpRequest req;
req.method = "GET";
req.path = "/files/42.txt";
conn.set_request(req);

Handler* h = router_.handle_request(conn.req());
conn.set_handler(h);
}
uint32_t next_events = 0;

// Can read from socket
if (events & EPOLLIN) {
int n = recv(conn.fd(), tmp, sizeof(tmp), 0);
if (n == 0) {
epoll_ctl(epoll_fd_, EPOLL_CTL_DEL, conn.fd(), NULL);
remove_connection(conn.id());

size_t bytes_received = recv(conn.fd(), tmp, sizeof(tmp), 0);

if (bytes_received == 0) {
close_connection(conn);
return;
}
if (n > 0) {
conn.recv_buffer().append(tmp, n);

std::cout << "* Received from socket\n";
print_raw_data(conn.recv_buffer());
std::cout << "* Received data from socket" << std::endl;
print_raw_data(tmp);

epoll_event ev;
ev.events = EPOLLOUT;
ev.data.u64 = conn.id();
epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, conn.fd(), &ev);
// Feed data to the parser
HttpParser& parser = conn.parser();
parser.feed_data(tmp, bytes_received);

if (parser.status() == HttpParser::kError) {
close_connection(conn);
return;
}

if (!conn.handler() && parser.status() >= HttpParser::kHeadersDone) {
Handler* h = router_.handle_request(parser.request());
conn.set_handler(h);
}
if (conn.handler()) {
if (conn.handler()->needs_input()) {
size_t n = parser.slurp_data(tmp, sizeof(tmp));
(void) conn.handler()->write_data(tmp, n);
}
}
}

// Can write to socket
if (events & EPOLLOUT) {
if (conn.handler()->has_output()) {
int n = conn.handler()->read_data(tmp, sizeof(tmp));
if (n > 0)
conn.send_buffer().append(tmp, n);
}
if (conn.handler()) {

if (!conn.send_buffer().empty()) {
int n = send(conn.fd(), conn.send_buffer().data(), conn.send_buffer().size(), 0);
if (n > 0)
conn.send_buffer().erase(0, n);
}
Handler& handler = *conn.handler();
std::string& send_buffer = conn.send_buffer();

if (conn.handler()->is_done() && conn.send_buffer().empty()) {
epoll_ctl(epoll_fd_, EPOLL_CTL_DEL, conn.fd(), NULL);
remove_connection(conn.id());
return;
if (handler.has_output()) {
size_t n = handler.read_data(tmp, sizeof(tmp));
send_buffer.append(tmp, n);
}
if (!send_buffer.empty()) {
size_t bytes_sent = send(conn.fd(), send_buffer.data(), send_buffer.size(), 0);
send_buffer.erase(0, bytes_sent);
}
}
}

// Update next event subscription
if (!conn.handler() || conn.handler()->needs_input())
next_events |= EPOLLIN;
if (conn.handler() && (conn.handler()->has_output() || !conn.send_buffer().empty()))
next_events |= EPOLLOUT;

epoll_event ev;
ev.data.u64 = conn.id();
ev.events = next_events;
epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, conn.fd(), &ev);
}
2 changes: 1 addition & 1 deletion src/core/server.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class Server {
void handle_events(Client& conn, uint32_t events);

uint64_t add_connection(int client_fd, const sockaddr_in& addr);
void remove_connection(uint64_t id);
void close_connection(Client& conn);

Client& get_client(uint64_t id);

Expand Down
121 changes: 121 additions & 0 deletions src/http/http_parser.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
#include "http/http_parser.hpp"

#include "util/string.hpp"

#include <cstring>

HttpParser::HttpParser()
: status_(HttpParser::kIncomplete)
{
}

void HttpParser::feed_data(const char* buf, size_t n)
{
if (status_ == kError)
return;

buffer_.append(buf, n);

parse_request_line();
parse_headers();
parse_body();
}

static size_t min3(size_t a, size_t b, size_t c)
{
return std::min(std::min(a, b), c);
}

size_t HttpParser::slurp_data(char* buf, size_t n)
{
if (status_ != kHeadersDone)
return 0;

size_t bytes_left = req_.content_length - bytes_slurped_;
size_t to_copy = min3(buffer_.size(), bytes_left, n);

std::memcpy(buf, buffer_.data(), to_copy);
bytes_slurped_ += to_copy;
buffer_.erase(0, to_copy);

if (bytes_slurped_ == req_.content_length)
status_ = kBodyDone;

return to_copy;
}

static bool is_valid_request_line(const std::vector<std::string>& v)
{
if (v.size() != 3)
return false;

if (v[0] != "GET" && v[0] != "POST" && v[0] != "DELETE")
return false;

if (v[2] != "HTTP/1.0" && v[2] != "HTTP/1.1")
return false;

return true;
}

// Format: METHOD SP REQUEST-URI SP HTTP-VERSION CRLF
void HttpParser::parse_request_line()
{
if (status_ != kIncomplete)
return;

size_t crlf = buffer_.find("\r\n");
if (crlf == std::string::npos)
return;

std::string line = buffer_.substr(0, crlf);
std::vector<std::string> v = str_split(line, " ");

if (!is_valid_request_line(v)) {
status_ = kError;
return;
}

req_.method = v[0];
req_.path = v[1];
req_.http_version = v[2];

buffer_.erase(0, crlf + 2);

status_ = kRequestLineDone;
}

// Need to parse: content_length, is_chunked?, keep_alive?
void HttpParser::parse_headers()
{
if (status_ != kRequestLineDone)
return;

size_t pos = buffer_.find("\r\n\r\n");
if (pos == std::string::npos)
return;

std::vector<std::string> v = str_split(buffer_, "\r\n");

for (size_t i = 0; i < v.size() && !v[i].empty(); i++) {

size_t colon = v[i].find(':');
if (colon == std::string::npos) {
status_ = kError;
return;
}

std::string name = v[i].substr(0, colon);
std::string value = str_trim(v[i].substr(colon + 1));
req_.headers[name] = value;
}

buffer_.erase(0, pos + 4);
status_ = kHeadersDone;
}

void HttpParser::parse_body()
{
if (status_ != kHeadersDone)
return;
}
31 changes: 31 additions & 0 deletions src/http/http_parser.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#ifndef HTTP_HTTP_PARSER_HPP_
#define HTTP_HTTP_PARSER_HPP_

#include "http/http_request.hpp"

#include <string>

class HttpParser {
public:
HttpParser();

enum Status { kError = 0, kIncomplete, kRequestLineDone, kHeadersDone, kBodyDone };

void feed_data(const char* buf, size_t n);
size_t slurp_data(char* buf, size_t n);

Status status() { return status_; }
const HttpRequest& request() { return req_; }

private:
void parse_request_line();
void parse_headers();
void parse_body();

Status status_;
std::string buffer_;
size_t bytes_slurped_;
HttpRequest req_;
};

#endif // HTTP_HTTP_PARSER_HPP_
9 changes: 9 additions & 0 deletions src/http/http_request.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#include "http/http_request.hpp"

HttpRequest::HttpRequest()
: content_length(0),
is_chunked(false),
keep_alive(false),
headers_done(false)
{
}
Loading