#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "httpcpp.h"
// HttpRequest
HttpRequest* HttpRequest::from_sequence(const string& sequence) {
size_t p0 = sequence.find("\r\n\r\n");
if (p0 != string::npos) {
p0 += 4;
size_t p1 = sequence.find(" ");
string method = sequence.substr(0, p1);
size_t p2 = sequence.find(" ", ++p1);
string path = sequence.substr(p1, p2 - p1);
size_t p3 = sequence.find("Content-Length:");
if (p3 != string::npos) {
p3 += 15;
size_t p4 = sequence.find("\r\n", p3);
int length = atoi(sequence.substr(p3, p4 - p3).data());
if (sequence.size() >= p0 + length) {
string body = sequence.substr(p0, length);
return new HttpRequest(method, path, body);
}
} else {
return new HttpRequest(method, path);
}
}
return NULL;
}
HttpRequest::HttpRequest(const string& method, const string& path,
const string& body) {
this->method = method;
this->path = path;
this->body = body;
}
const string& HttpRequest::get_method() {
return this->method;
}
const string& HttpRequest::get_path() {
return this->path;
}
const string& HttpRequest::get_body() {
return this->body;
}
// HttpResponse
const string HttpResponse::to_sequence(int code, const string& body) {
stringstream packet;
string reason;
switch (code) {
case 100: reason = "Continue"; break;
case 101: reason = "Switching Protocols"; break;
case 200: reason = "OK"; break;
case 201: reason = "Created"; break;
case 202: reason = "Accepted"; break;
case 203: reason = "Non-Authoritative Information"; break;
case 204: reason = "No Content"; break;
case 205: reason = "Reset Content"; break;
case 206: reason = "Partial Content"; break;
case 300: reason = "Multiple Choices"; break;
case 301: reason = "Moved Permanently"; break;
case 302: reason = "Found"; break;
case 303: reason = "See Other"; break;
case 304: reason = "Not Modified"; break;
case 305: reason = "Use Proxy"; break;
case 307: reason = "Temporary Redirect"; break;
case 400: reason = "Bad Request"; break;
case 401: reason = "Unauthorized"; break;
case 403: reason = "Forbidden"; break;
case 404: reason = "Not Found"; break;
case 405: reason = "Method Not Allowed"; break;
case 406: reason = "Not Acceptable"; break;
case 407: reason = "Proxy Authentication Required"; break;
case 408: reason = "Request Timeout"; break;
case 409: reason = "Conflict"; break;
case 410: reason = "Gone"; break;
case 411: reason = "Length Required"; break;
case 412: reason = "Precondition Failed"; break;
case 413: reason = "Request Entity Too Large"; break;
case 414: reason = "Request-URI Too Long"; break;
case 415: reason = "Unsupported Media Type"; break;
case 416: reason = "Requested Range Not Satisfiable"; break;
case 417: reason = "Expectation Failed"; break;
case 500: reason = "Internal Server Error"; break;
case 501: reason = "Not Implemented"; break;
case 502: reason = "Bad Gateway"; break;
case 503: reason = "Service Unavailable"; break;
case 504: reason = "Gateway Timeout"; break;
case 505: reason = "HTTP Version Not Supported"; break;
default: code = 500; reason = "Internal Server Error"; break;
}
packet << "HTTP/1.0 " << code << " " << reason << "\r\n";
packet << "Content-Length: " << body.size() << "\r\n\r\n";
packet << body;
return packet.str();
}
HttpResponse* HttpResponse::from_sequence(const string& sequence) {
// the algorithm only works if Content-Length exists
size_t p0 = sequence.find("\r\n\r\n");
if (p0 != string::npos) {
p0 += 4;
size_t p1 = sequence.find("Content-Length:") + 15;
size_t p2 = sequence.find("\r\n", p1);
int length = atoi(sequence.substr(p1, p2 - p1).data());
if (sequence.size() >= p0 + length) {
size_t p1 = sequence.find(" ");
size_t p2 = sequence.find(" ", ++p1);
int code = atoi(sequence.substr(p1, p2 - p1).data());
string body = sequence.substr(p0, length);
return new HttpResponse(code, body);
}
}
return NULL;
}
HttpResponse::HttpResponse(const int& code, const string& body) {
this->code = code;
this->body = body;
}
const int& HttpResponse::get_code() {
return this->code;
}
const string& HttpResponse::get_body() {
return this->body;
}
// HttpRequestHandler
void HttpRequestHandler::reply(HttpRequest* const request, const int& code,
const string& body) {
if (request->done) {
throw runtime_error("Reply to reqeust is already done");
} else {
request->server->reply(request->fd, code, body);
request->done = true;
}
}
void HttpRequestHandler::get(HttpRequest* const request,
const vector& args) {
this->reply(request, 405);
}
void HttpRequestHandler::post(HttpRequest* const request,
const vector& args) {
this->reply(request, 405);
}
// IOHandler
void IOHandler::clear_buffers(const int& fd) {
this->read_buffers.erase(fd);
this->write_buffers.erase(fd);
}
// AsyncHttpClient
void AsyncHttpClient::on_read(const int& fd) {
char buffer[BUFFER_SIZE];
bool done = false;
bool error = false;
while (true) {
ssize_t n = read(fd, buffer, BUFFER_SIZE);
if (n > 0) {
this->read_buffers[fd].append(buffer, n);
} else if (n == 0) {
// somehow it gets n=0 instead of n=-1 with errno=EAGAIN
HttpResponse* response =
HttpResponse::from_sequence(this->read_buffers[fd]);
if (response != NULL) {
this->handlers[fd]->handle(response);
delete response;
} else {
error = true;
}
done = true;
// delete the handler to de-allocate the memory
delete this->handlers[fd];
break;
} else {
if (errno == EAGAIN) {
// try again later
} else {
done = true;
}
break;
}
}
if (done) {
this->on_close(fd);
}
if (error) {
throw runtime_error("AsyncHttpClient read error");
}
}
void AsyncHttpClient::on_write(const int& fd) {
bool error = false;
int n_is_zero = 0;
while (true) {
size_t size = this->write_buffers[fd].size();
ssize_t n = write(fd, this->write_buffers[fd].data(), size);
if (n > 0) {
this->write_buffers[fd].erase(0, n);
} else if (n == 0) {
// somehow it gets n=0 instead of n=-1 with errno=EAGAIN
n_is_zero++;
if (this->write_buffers[fd].size() == 0) {
// prepare the read buffer
this->clear_buffers(fd);
this->read_buffers[fd] = string();
this->loop->set_handler(fd, this);
break;
} else {
if (n_is_zero == 3) {
error = true;
break;
}
}
} else {
if (errno == EAGAIN) {
// try again later
} else {
error = true;
}
break;
}
}
if (error) {
this->on_close(fd);
}
}
void AsyncHttpClient::on_close(const int& fd) {
this->clear_buffers(fd);
this->handlers.erase(fd);
close(fd);
}
AsyncHttpClient::AsyncHttpClient(IOLoop* const loop) {
// set the IO loop
if (loop == NULL) {
this->loop = IOLoop::instance();
} else {
this->loop = loop;
}
}
void AsyncHttpClient::fetch(const string& host, const int& port,
const string& method, const string& path, const string& body,
HttpResponseHandler* const handler) {
int fd;
struct sockaddr_in addr;
if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
throw runtime_error(strerror(errno));
}
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
if (inet_aton(host.data(), &addr.sin_addr) <= 0) {
close(fd);
throw runtime_error(strerror(errno));
}
if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
close(fd);
throw runtime_error(strerror(errno));
}
stringstream packet;
packet << method << " " << path << " HTTP/1.0\r\n" <<
"Content-Length: " << body.size() << "\r\n\r\n" << body;
// set the write buffer and the handler.
this->clear_buffers(fd);
this->write_buffers[fd] = packet.str();
this->handlers[fd] = handler;
this->loop->set_handler(fd, this, 'w');
}
// AsyncHttpServer
HttpRequestHandler* AsyncHttpServer::find_handler(const string& path) {
vector >::iterator it;
for (it = this->handlers.begin(); it != this->handlers.end(); it++) {
regex_t preg;
if (regcomp(&preg, (*it).first.data(), REG_EXTENDED | REG_NOSUB) == 0) {
if (regexec(&preg, path.data(), 0, NULL, 0) == 0) {
regfree(&preg);
return (*it).second;
}
regfree(&preg);
}
}
return NULL;
}
vector AsyncHttpServer::get_arguments(const string& path) {
vector args;
vector >::iterator it;
for (it = this->handlers.begin(); it != this->handlers.end(); it++) {
regex_t preg;
if (regcomp(&preg, (*it).first.data(), REG_EXTENDED) == 0) {
size_t nmatch = MAX_NMATCH;
regmatch_t pmatch[nmatch];
if (regexec(&preg, path.data(), nmatch, pmatch, 0) == 0) {
for (int i = 1; i < nmatch; i++) {
if (pmatch[i].rm_so == -1) {
break;
}
int n = pmatch[i].rm_eo - pmatch[i].rm_so;
args.push_back(string(path.data() + pmatch[i].rm_so, n));
}
regfree(&preg);
break;
}
regfree(&preg);
}
}
return args;
}
void AsyncHttpServer::reply(const int& fd, const int& code,
const string& body) {
this->clear_buffers(fd);
this->write_buffers[fd] = HttpResponse::to_sequence(code, body);
}
void AsyncHttpServer::on_read(const int& fd) {
if (fd == this->fd) {
// read on listening socket, keep accepting
while (true) {
struct sockaddr_in addr;
socklen_t addr_len = sizeof(addr);
int cfd = accept(fd, (struct sockaddr*)&addr, &addr_len);
if (cfd < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
break;
} else {
throw runtime_error(strerror(errno));
}
} else {
// prepare the read buffer for the accepted socket
this->clear_buffers(fd);
this->read_buffers[fd] = string();
this->loop->set_handler(cfd, this);
}
}
} else {
// read on existing socket, keep reading until EAGAIN
char buffer[BUFFER_SIZE];
bool error = false;
while (true) {
ssize_t n = read(fd, buffer, BUFFER_SIZE);
if (n > 0) {
this->read_buffers[fd].append(buffer, n);
} else if (n == 0) {
// socket close
error = true;
break;
} else {
if (errno != EAGAIN) {
error = true;
} else {
// no more data, try if request is available
HttpRequest* request =
HttpRequest::from_sequence(this->read_buffers[fd]);
if (request != NULL) {
// find a handler to handle the request
HttpRequestHandler* handler =
this->find_handler(request->path);
if (handler != NULL) {
vector args =
this->get_arguments(request->path);
request->server = this;
request->fd = fd;
request->done = false;
if (request->method.compare("GET") == 0) {
handler->get(request, args);
} else if (request->method.compare("POST") == 0) {
handler->post(request, args);
} else {
handler->reply(request, 405);
}
} else {
this->reply(fd, 404);
}
if (!request->done) {
this->reply(fd, 500);
}
delete request;
this->loop->set_handler(fd, this, 'w');
break;
}
}
break;
}
}
if (error) {
this->on_close(fd);
}
}
}
void AsyncHttpServer::on_write(const int& fd) {
bool done = false;
bool error = false;
int n_is_zero = 0;
while (true) {
size_t size = this->write_buffers[fd].size();
ssize_t n = write(fd, this->write_buffers[fd].data(), size);
if (n > 0) {
this->write_buffers[fd].erase(0, n);
} else if (n == 0) {
// somehow it gets n=0 instead of n=-1 with errno=EAGAIN
n_is_zero++;
if (this->write_buffers[fd].size() == 0) {
done = true;
break;
} else {
if (n_is_zero == 3) {
error = true;
break;
}
}
} else {
if (errno == EAGAIN) {
// try again later
} else {
error = true;
}
break;
}
}
if (done || error) {
this->on_close(fd);
}
if (error) {
throw runtime_error("AsyncHttpServer write error");
}
}
void AsyncHttpServer::on_close(const int& fd) {
this->clear_buffers(fd);
this->loop->unset_handler(fd);
close(fd);
}
AsyncHttpServer::AsyncHttpServer(const int& port, IOLoop* const loop) {
// set the IO loop
if (loop == NULL) {
this->loop = IOLoop::instance();
} else {
this->loop = loop;
}
// create a socket, bind and listen to the port
if ((this->fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
throw runtime_error(strerror(errno));
}
int opt = 1;
if (setsockopt(this->fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
throw runtime_error(strerror(errno));
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(this->fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
throw runtime_error(strerror(errno));
}
if (listen(this->fd, LISTEN_BACKLOG) < 0) {
throw runtime_error(strerror(errno));
}
// set itself as the read handler for the socket
this->loop->set_handler(this->fd, this);
}
AsyncHttpServer::~AsyncHttpServer() {
vector >::iterator it;
for (it = this->handlers.begin(); it != this->handlers.end(); it++) {
delete (*it).second;
}
this->read_buffers.clear();
this->write_buffers.clear();
this->handlers.clear();
}
void AsyncHttpServer::add_handler(const string& pattern,
HttpRequestHandler* const handler) {
this->handlers.push_back(make_pair(pattern, handler));
}
HttpRequestHandler* AsyncHttpServer::remove_handler(const string& pattern) {
HttpRequestHandler* removed = NULL;
vector >::iterator it;
for (it = this->handlers.begin(); it != this->handlers.end(); it++) {
if ((*it).first.compare(pattern) == 0) {
this->handlers.erase(it);
removed = (*it).second;
break;
}
}
return removed;
}
// IOLoop
IOLoop* IOLoop::loop = new IOLoop();
IOLoop::IOLoop() {
this->fd = epoll_create(EPOLL_SIZE);
}
IOHandler* IOLoop::set_handler(const int& fd, IOHandler* const handler,
char mode) {
// set the socket non-blocking
int flags;
if ((flags = fcntl(fd, F_GETFL, 0)) < 0) {
throw runtime_error(strerror(errno));
}
flags = flags | O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) < 0) {
throw runtime_error(strerror(errno));
}
// add the socket to epoll
struct epoll_event event;
event.data.fd = fd;
if (mode == 'r') {
event.events = EPOLLIN | EPOLLET;
} else {
event.events = EPOLLOUT | EPOLLET;
}
// unset the previous handler if any and set the new one
IOHandler* previous = this->unset_handler(fd);
if (epoll_ctl(this->fd, EPOLL_CTL_ADD, fd, &event) < 0) {
throw runtime_error(strerror(errno));
}
this->handlers[fd] = handler;
return previous;
}
IOHandler* IOLoop::unset_handler(const int& fd) {
if (epoll_ctl(this->fd, EPOLL_CTL_DEL, fd, NULL) < 0) {
if (errno != ENOENT) {
throw runtime_error(strerror(errno));
}
}
if (this->handlers.count(fd) == 0) {
return NULL;
} else {
IOHandler* found = this->handlers[fd];
this->handlers.erase(fd);
return found;
}
}
void IOLoop::start() {
// at the moment run forever unless an error occurs
struct epoll_event* events = (struct epoll_event*)malloc(
sizeof(struct epoll_event) * MAX_EVENTS);
while (true) {
int n;
if ((n = epoll_wait(this->fd, events, MAX_EVENTS, -1)) < 0) {
throw runtime_error(strerror(errno));
}
for (int i = 0; i < n; i++) {
int fd = events[i].data.fd;
if ((events[i].events & EPOLLERR) || (events[i].events & EPOLLHUP)) {
this->handlers[fd]->on_close(fd);
this->unset_handler(fd);
close(fd);
}
else if (events[i].events & EPOLLOUT) {
this->handlers[fd]->on_write(fd);
}
else if (events[i].events & EPOLLIN) {
this->handlers[fd]->on_read(fd);
}
}
}
}
IOLoop* IOLoop::instance() {
return IOLoop::loop;
}