//-< WWWAPI.CPP >----------------------------------------------------*--------*
// FastDB Version 1.0 (c) 1999 GARRET * ? *
// (Main Memory Database Management System) * /\| *
// * / \ *
// Created: 27-Mar-99 K.A. Knizhnik * / [] \ *
// Last update: 1-Jul-99 K.A. Knizhnik * GARRET *
//-------------------------------------------------------------------*--------*
// Implementation of WWWapi class
//-------------------------------------------------------------------*--------*
#define INSIDE_FASTDB
#include "wwwapi.h"
#include <ctype.h>
const size_t init_reply_buffer_size = 4*1024;
inline unsigned string_hash_function(const char* name)
{
unsigned h = 0, g;
while(*name) {
h = (h << 4) + *name++;
if ((g = h & 0xF0000000) != 0) {
h ^= g >> 24;
}
h &= ~g;
}
return h;
}
#define ERROR_TEXT(x) \
"HTTP/1.1 " x "\r\n\
Connection: close\r\n\r\n\
<HTML><HEAD><TITLE>Invalid request to the database</TITLE>\r\n\
</HEAD><BODY>\n\r\
<H1>" x "</H1>\n\r\
</BODY></HTML>\r\n\r\n"
WWWconnection::WWWconnection()
{
memset(hash_table, 0, sizeof hash_table);
sock = NULL;
reply_buf = new char[init_reply_buffer_size];
reply_buf_size = init_reply_buffer_size;
free_pairs = NULL;
peer = NULL;
userData = NULL;
}
WWWconnection::~WWWconnection()
{
reset();
name_value_pair *nvp, *next;
for (nvp = free_pairs; nvp != NULL; nvp = next) {
next = nvp->next;
delete nvp;
}
delete[] reply_buf;
delete[] peer;
}
inline char* WWWconnection::extendBuffer(size_t inc)
{
if (reply_buf_used + inc >= reply_buf_size) {
reply_buf_size = reply_buf_size*2 > reply_buf_used + inc
? reply_buf_size*2 : reply_buf_used + inc;
char* new_buf = new char[reply_buf_size+1];
memcpy(new_buf, reply_buf, reply_buf_used);
delete[] reply_buf;
reply_buf = new_buf;
}
reply_buf_used += inc;
return reply_buf;
}
bool WWWconnection::terminatedBy(char const* str) const
{
size_t len = strlen(str);
if (len > reply_buf_used - 4) {
return false;
}
return memcmp(reply_buf + reply_buf_used - len, str, len) == 0;
}
WWWconnection& WWWconnection::append(const void *buf, int len) {
int pos = reply_buf_used;
char *dst = extendBuffer(len);
memcpy(dst + pos, buf, len);
return *this;
}
WWWconnection& WWWconnection::append(char const* str)
{
int pos = reply_buf_used;
char* dst = extendBuffer(strlen(str));
unsigned char ch;
switch (encoding) {
case TAG:
strcpy(dst + pos, str);
encoding = HTML;
break;
case HTML:
encoding = TAG;
#if 1 // MS-Explorer handle " " in HTML string literals in very strange way
if (str[0] == ' ' && str[1] == '\0') {
strcpy(extendBuffer(5) + pos, " ");
return *this;
}
#endif
while (true) {
switch(ch = *str++) {
case '<':
dst = extendBuffer(3);
dst[pos++] = '&';
dst[pos++] = 'l';
dst[pos++] = 't';
dst[pos++] = ';';
break;
case '>':
dst = extendBuffer(3);
dst[pos++] = '&';
dst[pos++] = 'g';
dst[pos++] = 't';
dst[pos++] = ';';
break;
case '&':
dst = extendBuffer(4);
dst[pos++] = '&';
dst[pos++] = 'a';
dst[pos++] = 'm';
dst[pos++] = 'p';
dst[pos++] = ';';
break;
case '"':
dst = extendBuffer(5);
dst[pos++] = '&';
dst[pos++] = 'q';
dst[pos++] = 'u';
dst[pos++] = 'o';
dst[pos++] = 't';
dst[pos++] = ';';
break;
case '\0':
dst[pos] = '\0';
return *this;
#if 0 // MS-Explorer handle " " in HTML string literals in very strange way
case ' ':
dst = extendBuffer(5);
dst[pos++] = '&';
dst[pos++] = 'n';
dst[pos++] = 'b';
dst[pos++] = 's';
dst[pos++] = 'p';
dst[pos++] = ';';
break;
#endif
default:
dst[pos++] = ch;
}
}
case URL:
encoding = TAG;
while (true) {
ch = *str++;
if (ch == '\0') {
dst[pos] = '\0';
return *this;
} else if (ch == ' ') {
dst[pos++] = '+';
} else if (!isalnum(ch)) {
dst = extendBuffer(2);
dst[pos++] = '%';
dst[pos++] = (ch >> 4) >= 10
? (ch >> 4) + 'A' - 10 : (ch >> 4) + '0';
dst[pos++] = (ch & 0xF) >= 10
? (ch & 0xF) + 'A' - 10 : (ch & 0xF) + '0';
} else {
dst[pos++] = ch;
}
}
}
return *this;
}
void WWWconnection::reset()
{
reply_buf_used = 0;
encoding = TAG;
for (int i = itemsof(hash_table); --i >= 0;) {
name_value_pair *nvp, *next;
for (nvp = hash_table[i]; nvp != NULL; nvp = next) {
next = nvp->next;
nvp->next = free_pairs;
free_pairs = nvp;
}
hash_table[i] = NULL;
}
}
void WWWconnection::addPair(char const* name, char const* value)
{
name_value_pair* nvp;
if (free_pairs != NULL) {
nvp = free_pairs;
free_pairs = nvp->next;
} else {
nvp = new name_value_pair;
}
unsigned hash_code = string_hash_function(name);
nvp->hash_code = hash_code;
hash_code %= hash_table_size;
nvp->next = hash_table[hash_code];
hash_table[hash_code] = nvp;
nvp->value = value;
nvp->name = name;
}
#define HEX_DIGIT(ch) ((ch) >= 'a' ? ((ch) - 'a' + 10) : (ch) >= 'A' ? ((ch) - 'A' + 10) : ((ch) - '0'))
char* WWWconnection::unpack(char* body, size_t length)
{
char *src = body, *end = body + length;
while (src < end) {
char* name = src;
char ch;
char* dst = src;
while (src < end && (ch = *src++) != '=') {
if (ch == '+') {
ch = ' ';
} else if (ch == '%') {
ch = (HEX_DIGIT(src[0]) << 4) | HEX_DIGIT(src[1]);
src += 2;
}
*dst++ = ch;
}
*dst = '\0';
char* value = dst = src;
while (src < end && (ch = *src++) != '&') {
if (ch == '+') {
ch = ' ';
} else if (ch == '%') {
ch = (HEX_DIGIT(src[0]) << 4) | HEX_DIGIT(src[1]);
src += 2;
}
*dst++ = ch;
}
*dst = '\0';
addPair(name, value);
}
stub = get("stub");
return get("page");
}
char* WWWconnection::get(char const* name, int n)
{
unsigned hash_code = string_hash_function(name);
name_value_pair* nvp;
for (nvp = hash_table[hash_code % hash_table_size];
nvp != NULL;
nvp = nvp->next)
{
if (nvp->hash_code == hash_code && strcmp(nvp->name, name) == 0) {
if (n == 0) {
return (char*)nvp->value;
}
n -= 1;
}
}
return NULL;
}
//--------------------------------------------------
WWWapi::WWWapi(dbDatabase& dbase, int n_handlers, dispatcher* dispatch_table)
: db(dbase)
{
memset(hash_table, 0, sizeof hash_table);
sock = NULL;
address = NULL;
dispatcher* disp = dispatch_table;
while (--n_handlers >= 0) {
unsigned hash_code = string_hash_function(disp->page);
disp->hash_code = hash_code;
hash_code %= hash_table_size;
disp->collision_chain = hash_table[hash_code];
hash_table[hash_code] = disp;
disp += 1;
}
}
bool WWWapi::open(char const* socket_address,
socket_t::socket_domain domain,
int listen_queue)
{
if (sock != NULL) {
close();
}
address = new char[strlen(socket_address) + 1];
strcpy(address, socket_address);
sock = domain != socket_t::sock_global_domain
? socket_t::create_local(socket_address, listen_queue)
: socket_t::create_global(socket_address, listen_queue);
canceled = false;
if (!sock->is_ok()) {
char buf[64];
sock->get_error_text(buf, sizeof buf);
fprintf(stderr, "WWWapi::open: create socket failed: %s\n", buf);
return false;
}
return true;
}
bool WWWapi::connect(WWWconnection& con)
{
assert(sock != NULL);
con.reset();
delete con.sock;
con.sock = sock->accept();
con.address = address;
if (con.sock == NULL) {
if (!canceled) {
char buf[64];
sock->get_error_text(buf, sizeof buf);
fprintf(stderr, "WWWapi::connect: accept failed: %s\n", buf);
}
return false;
}
return true;
}
void WWWapi::cancel()
{
canceled = true;
sock->cancel_accept();
}
void WWWapi::close()
{
delete sock;
delete[] address;
sock = NULL;
}
bool WWWapi::dispatch(WWWconnection& con, char* page)
{
unsigned hash_code = string_hash_function(page);
for (dispatcher* disp = hash_table[hash_code % hash_table_size];
disp != NULL;
disp = disp->collision_chain)
{
if (disp->hash_code == hash_code && strcmp(disp->page, page) == 0)
{
bool result = disp->func(con);
db.commit();
return result;
}
}
return true;
}
void URL2ASCII(char* src)
{
char* dst = src;
char ch;
while ((ch = *src++) != '\0') {
if (ch == '%') {
*dst++ = ((src[0] - '0') << 8) | (src[1] - '0');
} else if (ch == '+') {
*dst++ = ' ';
} else if (ch == '.' && *src == '.') {
// for security reasons do not allow access to parent directory
break;
} else {
*dst++ = ch;
}
}
*dst = '\0';
}
bool CGIapi::serve(WWWconnection& con)
{
nat4 length;
con.reset();
if ((size_t)con.sock->read(&length, sizeof length, sizeof length)
!= sizeof(length))
{
return true;
}
int size = length - sizeof length;
char* buf = new char[size];
if (con.sock->read(buf, size, size) != size) {
return true;
}
char* page = con.unpack(buf + buf[0], length - sizeof length - buf[0]);
char* peer = con.get("peer");
con.peer = new char[strlen(peer)+1];
strcpy(con.peer, peer);
bool result = true;
if (page != NULL) {
con.extendBuffer(4);
result = dispatch(con, page);
*(int4*)con.reply_buf = con.reply_buf_used;
con.sock->write(con.reply_buf, con.reply_buf_used);
}
delete[] con.peer;
con.peer = NULL;
delete con.sock;
con.sock = NULL; // close connection
return result;
}
inline char* stristr(char* s, char* p) {
while (*s != '\0') {
int i;
for (i = 0; (s[i] & ~('a'-'A')) == (p[i] & ~('a' - 'A')) && p[i] != '\0'; i++);
if (p[i] == '\0') {
return s;
}
s += 1;
}
return NULL;
}
bool HTTPapi::serve(WWWconnection& con)
{
const size_t inputBufferSize = 16*1024;
char buf[inputBufferSize];
bool result = false;
size_t size = 0;
con.peer = con.sock->get_peer_name();
while (true) {
con.reset();
char* p = buf;
char prev_ch = 0;
do {
if (p == buf + size) {
int rc = con.sock->read(buf + size, 5, sizeof(buf) - size - 1,
connectionHoldTimeout);
if (rc < 0) {
delete con.sock;
con.sock = NULL;
return true;
}
if (rc < 5) {
con.append(ERROR_TEXT("200 OK")); // connection closed due to timeout expiration
break;
}
size += rc;
}
buf[size] = '\0';
while (*p != '\0' && (prev_ch != '\n' || *p != '\r')) {
prev_ch = *p++;
}
} while (*p == '\0' && p == buf + size); // p now points to the message body
if (*p != '\r' || *(p+1) != '\n') {
con.append(ERROR_TEXT("400 Bad Request"));
break;
}
p += 2;
int length = INT_MAX;
char* lenptr = stristr(buf, (char*)"content-length: ");
bool persistentConnection =
stristr(buf, (char*)"Connection: keep-alive") != NULL;
char* host = stristr(buf, (char*) "host: ");
if (host != NULL) {
char* q = host += 6;
while (*q != '\n' && *q != '\r' && *q != '\0') q += 1;
*q = '\0';
}
if (lenptr != NULL) {
sscanf(lenptr+15, "%d", &length);
}
if (strncmp(buf, "GET ", 4) == 0) {
char* file, *uri = buf;
file = strchr(uri, '/');
if (file == NULL) {
con.append(ERROR_TEXT("400 Bad Request"));
break;
}
if (*++file == '/') {
if (host == NULL) {
host = file+1;
}
file = strchr(uri, '/');
if (file == NULL) {
con.append(ERROR_TEXT("400 Bad Request"));
break;
}
*file++ = '\0';
}
char* file_end = strchr(file, ' ');
char index_html[] = "index.html";
if (file_end == NULL) {
con.append(ERROR_TEXT("400 Bad Request"));
break;
}
if (file_end == file) {
file = index_html;
} else {
*file_end = '\0';
}
if (host == NULL) {
host = (char*) "localhost";
}
char* params = strchr(file, '?');
if (params != NULL) {
if (!handleRequest(con, params+1, file_end, host, result)) {
delete con.sock;
con.sock = NULL;
return result;
}
} else {
URL2ASCII(file);
FILE* f = fopen(file, "rb");
if (f == NULL) {
if (strcmp(file, index_html) == 0) {
char* defaultPage = (char*) "page=defaultPage";
if (!handleRequest(con, defaultPage, defaultPage + strlen(defaultPage), host, result)) {
delete con.sock;
con.sock = NULL;
return result;
}
} else {
con.append(ERROR_TEXT("404 File Not Found"));
break;
}
} else {
fseek(f, 0, SEEK_END);
size_t file_size = ftell(f);
fseek(f, 0, SEEK_SET);
char reply[1024];
sprintf(reply, "HTTP/1.1 200 OK\r\nContent-Length: %u\r\n"
"Content-Type: text/html\r\nConnection: %s\r\n\r\n",
file_size,
keepConnectionAlive ? "Keep-Alive" : "close");
con.append(reply);
size_t pos = con.reply_buf_used;
char* dst = con.extendBuffer(file_size);
if (dst == NULL) {
con.reset();
con.append(ERROR_TEXT("413 Request Entity Too Large"));
break;
}
if (fread(dst + pos, 1, file_size, f) != file_size) {
con.reset();
con.append(ERROR_TEXT("500 Internal server error"));
break;
}
fclose(f);
if (!con.sock->write(dst, con.reply_buf_used)
|| !keepConnectionAlive)
{
delete con.sock;
con.sock = NULL;
return true;
}
}
}
} else if (strncmp(buf, "POST ", 5) == 0) {
char* body = p;
ScanNextPart:
int n = length < buf + size - p
? length : buf + size - p;
while (--n >= 0 && *p != '\r' && *p != '\n')
{
p += 1;
}
if (n < 0 && p - body != length) {
if (size >= sizeof(buf) - 1) {
con.append(ERROR_TEXT("413 Request Entity Too Large"));
break;
}
int rc = con.sock->read(p, 1, sizeof(buf) - size - 1,
connectionHoldTimeout);
if (rc < 0) {
delete con.sock;
con.sock = NULL;
return true;
}
size += rc;
goto ScanNextPart;
} else {
if (host == NULL) {
host = (char*) "localhost";
}
if (!handleRequest(con, body, p, host, result)) {
delete con.sock;
con.sock = NULL;
return result;
}
while (n >= 0 && (*p == '\n' || *p == '\r')) {
p += 1;
n -= 1;
}
}
} else {
con.append(ERROR_TEXT("405 Method not allowed"));
break;
}
if (!persistentConnection) {
delete con.sock;
con.sock = NULL;
return true;
}
if (p - buf < (long)size) {
size -= p - buf;
memcpy(buf, p, size);
} else {
size = 0;
}
}
if (con.sock != NULL) {
con.sock->write(con.reply_buf, con.reply_buf_used);
con.sock->shutdown();
delete con.sock;
con.sock = NULL;
}
return true;
}
bool HTTPapi::handleRequest(WWWconnection& con, char* begin, char* end,
char* host, bool& result)
{
char buf[64];
char ch = *end;
char* page = con.unpack(begin, end - begin);
if (page != NULL) {
con.append("HTTP/1.1 200 OK\r\nContent-Length: \r\n");
int length_pos = con.reply_buf_used - 8;
con.append(keepConnectionAlive
? "Connection: Keep-Alive\r\n"
: "Connection: close\r\n");
sprintf(buf, "http://%s/", host);
con.stub = buf;
result = dispatch(con, page);
char* body = con.reply_buf + length_pos;
char prev_ch = 0;
con.reply_buf[con.reply_buf_used] = '\0';
while ((*body != '\n' || prev_ch != '\n') &&
(*body != '\r' || prev_ch != '\n') &&
*body != '\0')
{
prev_ch = *body++;
}
if (*body == '\0') {
con.reset();
con.append(ERROR_TEXT("404 Not found"));
con.sock->write(con.reply_buf, con.reply_buf_used);
return false;
}
body += *body == '\n' ? 1 : 2;
sprintf(buf, "%u",
con.reply_buf_used - (body - con.reply_buf));
memcpy(con.reply_buf + length_pos,
buf, strlen(buf));
if (!con.sock->write(con.reply_buf, con.reply_buf_used)) {
return false;
}
*end = ch;
return result && keepConnectionAlive;
} else {
con.append(ERROR_TEXT("Not acceptable"));
con.sock->write(con.reply_buf, con.reply_buf_used);
result = true;
*end = ch;
return false;
}
}
//----------------------------------------------------
void thread_proc QueueManager::handleThread(void* arg)
{
((QueueManager*)arg)->handle();
}
QueueManager::QueueManager(WWWapi& api,
dbDatabase& dbase,
int nThreads,
int connectionQueueLen)
: db(dbase)
{
assert(nThreads >= 1 && connectionQueueLen >= 1);
this->nThreads = nThreads;
go.open();
done.open();
threads = new dbThread[nThreads];
while (--nThreads >= 0) {
threads[nThreads].create(handleThread, this);
threads[nThreads].detach();
}
connectionPool = new WWWconnection[connectionQueueLen];
connectionPool[--connectionQueueLen].next = NULL;
while (--connectionQueueLen >= 0) {
connectionPool[connectionQueueLen].next =
&connectionPool[connectionQueueLen+1];
}
freeList = connectionPool;
waitList = NULL;
server = &api;
}
void QueueManager::start()
{
mutex.lock();
while (server != NULL) {
if (freeList == NULL) {
done.reset();
done.wait(mutex);
if (server == NULL) {
break;
}
assert(freeList != NULL);
}
WWWconnection* con = freeList;
freeList = con->next;
WWWapi* srv = server;
mutex.unlock();
if (!srv->connect(*con) || server == NULL) {
return;
}
mutex.lock();
con->next = waitList;
waitList = con;
go.signal();
}
mutex.unlock();
}
void QueueManager::handle()
{
db.attach();
mutex.lock();
while (true) {
go.wait(mutex);
WWWapi* api = server;
if (api == NULL) {
break;
}
WWWconnection* con = waitList;
assert(con != NULL);
waitList = con->next;
mutex.unlock();
if (!api->serve(*con)) {
stop();
}
mutex.lock();
if (freeList == NULL) {
done.signal();
}
con->next = freeList;
freeList = con;
}
mutex.unlock();
db.detach();
}
void QueueManager::stop()
{
mutex.lock();
WWWapi* server = this->server;
this->server = NULL;
server->cancel();
while (--nThreads >= 0) {
go.signal();
}
done.signal();
mutex.unlock();
}
QueueManager::~QueueManager()
{
go.close();
done.close();
delete[] threads;
delete[] connectionPool;
}
syntax highlighted by Code2HTML, v. 0.9.1