blob: 4f4ab1bcfb8bb96bf16573f34f13470df5ce351c [file] [log] [blame]
/* TODO(benh): TCP_CORK!!!!! */
/* TODO(benh): Complete HttpParser & HttpMessage implementation. */
/* TODO(benh): Turn off Nagle (on TCP_NODELAY) for pipelined requests. */
#include <string.h>
#include <process.hpp>
#include <tuple.hpp>
#include <iostream>
#include <map>
#include <sstream>
#include <stdexcept>
#include <arpa/inet.h>
#include <stout/os.hpp>
#include "net.hpp"
#include "http-parser/http_parser.h"
using std::cerr;
using std::cout;
using std::endl;
using std::runtime_error;
using std::string;
using std::map;
using process::tuple::Tuple;
#define malloc(bytes) \
({ void *tmp; if ((tmp = malloc(bytes)) == NULL) abort(); tmp; })
#define realloc(address, bytes) \
({ void *tmp; if ((tmp = realloc(address, bytes)) == NULL) abort(); tmp; })
#define HTTP_500 \
"HTTP/1.1 500 Internal Server Error\r\n\r\n"
#define HTTP_501 \
"HTTP/1.1 501 Not Implemented\r\n\r\n"
#define HTTP_404 \
"HTTP/1.1 404 Not Found\r\n\r\n"
struct HttpMessage
{
unsigned short method;
/* TODO(*): Use HTTP_MAX_URI_SIZE. */
string uri;
};
class HttpParser
{
protected:
static int on_uri(http_parser *parser, const char *p, size_t len)
{
HttpMessage *message = (HttpMessage *) parser->data;
message->uri += string(p, len);
return 0;
}
static int on_headers_complete(http_parser *parser)
{
HttpMessage *message = (HttpMessage *) parser->data;
message->method = parser->method;
return 0;
}
public:
static HttpMessage * parse(const string &raw)
{
http_parser parser;
http_parser_init(&parser, HTTP_REQUEST);
HttpMessage *message = new HttpMessage;
parser.data = message;
parser.on_message_begin = NULL;
parser.on_header_field = NULL;
parser.on_header_value = NULL;
parser.on_path = NULL;
parser.on_uri = &HttpParser::on_uri;
parser.on_fragment = NULL;
parser.on_query_string = NULL;
parser.on_body = NULL;
parser.on_headers_complete = &HttpParser::on_headers_complete;
parser.on_message_complete = NULL;
http_parser_execute(&parser, raw.c_str(), raw.length());
if (http_parser_has_error(&parser)) {
//cerr << "parser error" << endl;
abort();
}
return message;
}
};
class HttpConnection : public SocketProcess<TCP>
{
protected:
void operator () ()
{
//cout << ht_id() << ": running " << this << " connection (1)" << endl;
string raw;
/* Read headers (until CRLF CRLF). */
do {
char buf[512];
ssize_t len = recv(buf, 512);
raw += string(buf, len);
} while (raw.find("\r\n\r\n") == string::npos);
//cout << ht_id() << ": running " << this << " connection (2)" << endl;
/* Parse headers. */
HttpMessage *message = HttpParser::parse(raw);
/* Handle request. */
switch (message->method) {
case HTTP_GET: {
message->uri =
message->uri != "/"
? "." + message->uri
: "./index.html";
//cout << "URI: " << message->uri << endl;
/* Open file (if possible). */
int fd;
if ((fd = open(message->uri.c_str(), O_RDONLY, 0)) < 0) {
send(HTTP_404, strlen(HTTP_404));
return;
}
/* Lookup file size. */
struct stat fd_stat;
if (fstat(fd, &fd_stat) < 0) {
send(HTTP_500, strlen(HTTP_500));
os::close(fd);
return;
}
/* TODO(benh): Use TCP_CORK. */
/* Transmit reply header. */
std::stringstream out;
out << "HTTP/1.1 200 OK\r\n";
/* Determine the content type. */
if (message->uri.find(".jpg") != string::npos) {
out << "Content-Type: image/jpeg\r\n";
} else if (message->uri.find(".gif") != string::npos) {
out << "Content-Type: image/gif\r\n";
} else if (message->uri.find(".png") != string::npos) {
out << "Content-Type: image/png\r\n";
} else if (message->uri.find(".css") != string::npos) {
out << "Content-Type: text/css\r\n";
} else {
out << "Content-Type: text/html\r\n";
}
out <<
"Content-Length: " << fd_stat.st_size << "\r\n"
"\r\n";
//cout << out.str() << endl;
send(out.str().c_str(), out.str().size());
//cout << ht_id() << ": running " << this << " connection (3)" << endl;
/* Transmit file (TODO(benh): Use file cache.). */
sendfile(fd, fd_stat.st_size);
//cout << ht_id() << ": running " << this << " connection (4)" << endl;
os::close(fd);
break;
}
default:
/* Unimplemented. */
send(HTTP_501, strlen(HTTP_501));
break;
}
}
public:
explicit HttpConnection(int s) : SocketProcess<TCP>(s) {}
~HttpConnection() {}
};
enum HTTP_MESSAGES { ACCEPT = PROCESS_MSGID };
namespace process { namespace tuple { TUPLE(::ACCEPT, (int)); }}
class HttpAcceptor : public Tuple< Acceptor<TCP> >
{
private:
PID server;
protected:
void operator () ()
{
do {
struct sockaddr_in addr;
int c = accept(addr);
//cout << ht_id() << ": running acceptor" << endl;
send<ACCEPT>(server, c);
} while (true);
}
public:
HttpAcceptor(const PID &_server, int s) : server(_server) { socket(s); }
};
class HttpServer : public Tuple< Server<TCP> >
{
private:
map<PID, HttpConnection *> connections;
protected:
void operator () ()
{
int on = 1;
setsockopt(SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
bind();
listen(100000);
HttpAcceptor *acceptor = new HttpAcceptor(self(), s);
link(spawn(acceptor));
do {
switch (receive()) {
case ACCEPT: {
//cout << ht_id() << ": running server (accept)" << endl;
int c;
unpack<ACCEPT>(c);
HttpConnection *connection = new HttpConnection(c);
connections[link(spawn(connection))] = connection;
//cout << "...started (" << connection << ")..." << endl;
break;
}
case PROCESS_EXIT: {
//cout << ht_id() << ": running server (exit)" << endl;
if (from() == acceptor->getPID()) {
throw runtime_error("unimplemented acceptor failure");
} else if (connections.find(from()) != connections.end()) {
HttpConnection *connection = connections[from()];
connections.erase(from());
delete connection;
//cout << "...finished (" << connection << ")..." << endl;
}
break;
}
default:
cout << "HttpServer received unexpected message" << endl;
break;
}
} while (true);
}
public:
HttpServer(int port) { init(INADDR_ANY, port); }
};
int main(int argc, char **argv)
{
/* TODO(benh): Blah, 'sendfile' doesn't let us use MSG_NOSIGNAL. :( */
signal(SIGPIPE, SIG_IGN);
if (argc != 2) {
cerr << "usage: " << argv[0] << " <port>" << endl;
return -1;
}
#ifdef USE_LITHE
ProcessScheduler *scheduler = new ProcessScheduler();
Process::spawn(new HttpServer(atoi(argv[1])));
/* TODO(benh): Make Process::wait take and use the hart if using Lithe! */
for (;;)
sleep(10000);
#else
Process::wait(Process::spawn(new HttpServer(atoi(argv[1]))));
#endif /* USE_LITHE */
return 0;
}