-/* $Id: httpd.js,v 1.12 2005/07/04 19:06:31 mmondor Exp $ */
+/* $Id: httpd.js,v 1.13 2005/07/04 23:14:54 mmondor Exp $ */
/*
* Copyright (c) 2005, Matthew Mondor
* using multiple threads or processes. Must be ran through ../../src/test,
* compiled with ../../src/classes/js_fd.[ch] support.
*
- * We support a maximum of 4 concurrent connections per IP address, as well
- * as a total maximum of 32 concurrent connections by default.
- * Change MAX_CONNECTIONS and MAX_CONNECTIONS_ADDR as needed.
- *
- * We also support per-connection I/O timeouts, defaulting to 60 seconds.
- * Change IO_TIMEOUT as wanted.
+ * Configuration options can be found on options.js
*
* XXX Possibly that with close var semantics changes or such, we could
* support Keep-Alive for HTTP/1.1 connections. This however is not a
* priority at current time.
*
+ * TODO:
* - Implement path sanity checking (or import mmpath(3))?
* So that clients could invoke static files within vhosts
* - We'll need a vhosts configuration file
- * - Move to a config file configurable options;
- * - We might also want to allow multiple listening addresses.
- * However, if doing this, we'll not be able to rely anymore on our unique
- * 'bind' property name associated to the listening socket.
- * We probably instead can add a property to the FD to distinguish bound
- * FDs from client ones.
* - We probably want to support proxy/cache queries that request if a
* document was modified, which would be ideal for static files, but
* would always return modified status for dynamic data.
+ * A query which has a field such as:
+ * ...
+ * If-Modified-Since: Sat, 02 Oct 2004 10:52:44 GMT
+ * Could receive a response such as:
+ * HTTP/1.1 304 Not Modified
+ * Date: Mon, 04 Jul 2005 22:45:37 GMT
+ * Server: Apache/1.3.33 (Unix) mod_ssl/2.8.21 OpenSSL/0.9.7d
+ * Connection: close
+ * - See what to do for HEAD and PUT
+ * - Think about how I'll handle invokation of scripts. I'll most probably
+ * need to eval() them initially and cache them once evaluated, until
+ * modified, and to invoke them prior to pre-evaluation multiple times
+ * as needed. Possibly that these scripts could simply define an object
+ * with an entry point or such. At worse I'll need special a C support
+ * object to implement this, which would at necessary use another context,
+ * possibly persistent.
*/
const LISTEN_PORT = 8080;
SERVER_VERSION = 'mmondor_js_httpd/0.0.1 (NetBSD)';
-SERVER_CVSID = '$Id: httpd.js,v 1.12 2005/07/04 19:06:31 mmondor Exp $';
+SERVER_CVSID = '$Id: httpd.js,v 1.13 2005/07/04 23:14:54 mmondor Exp $';
*/
FD.prototype.init = function(time, idx)
{
+ /* Not a bound socket */
+ this.bound = false;
/* Initial state */
this.transfer_state = STATE_TRANSFER_READ;
this.transfer_src = this.transfer_dst = null;
this.http_vars_cookies = {};
this.http_agent = '';
this.http_content_length = -1;
+ this.http_modified_since = undefined;
/* Split request lines */
lines = this.request_data.split("\r\n");
evil = true;
} else if (words[0] == 'Content-Length:')
this.http_content_length = words[1].valueOf();
+ else if (words[0] == 'If-Modified-Since:')
+ this.http_modified_since = Math.round(
+ Date.parse(lines[i].substr(19)) / 60000);
}
}
tr.addContent(td);
table.addContent(tr);
+ tr = new MLTag('tr', true);
+ td = new MLTag('td', true);
+ td.addAttr('align', 'right');
+ td.addContent('Mod-Since:');
+ tr.addContent(td);
+ td = new MLTag('td', true);
+ td.addContent(this.http_modified_since);
+ tr.addContent(td);
+ table.addContent(tr);
+
body.addContent(table);
p = new MLTag('p', true);
* Main program
*/
function main() {
- var sock;
var i;
/*
/*
* Initialize server
*/
- try {
- sock = new FD();
- sock.socket(FD.AF_INET, FD.SOCK_STREAM, 0);
- sock.bind(LISTEN_ADDRESS, LISTEN_PORT);
- /*
- * XXX Must either fix netbsd kernel bug and/or js_fd.c
- * before using these, since at least one of the following
- * calls causes a system panic!
- */
- /*
- sock.setsockopt(FD.SO_REUSEADDR, 1);
- sock.setsockopt(FD.SO_LINGER, -1);
- sock.setsockopt(FD.SO_KEEPALIVE, 1);
- */
- sock.setsockopt(FD.TCP_NODELAY, 1);
- sock.listen(options.max_connections);
- } catch (x) {
- err.put(x + "\n");
- exit();
- }
+ for (i in listen) {
+ var fd;
- /*
- * Add listening socket to polling set as a property to easily
- * distinguish it from client FDs
- */
- sock.events = FD.POLLIN;
- set['bind'] = sock;
+ try {
+ fd = new FD();
+ fd.socket(FD.AF_INET, FD.SOCK_STREAM, 0);
+ /*
+ * XXX How comes bind(2) fails if I don't first print
+ * out the address!? An odd bug it seems.
+ * Like if listen[i] first needs to be
+ * referenced/instanciated...
+ * I then get error "Address family not supported by
+ * protocol family".
+ */
+ err.put(uneval(listen[i]) + "\n");
+ fd.bind(listen[i].address, listen[i].port);
+ /*
+ * XXX Must either fix netbsd kernel bug
+ * and/or js_fd.c before using these, since
+ * at least one of the following calls causes
+ * a system panic!
+ */
+ /*
+ sock.setsockopt(FD.SO_REUSEADDR, 1);
+ sock.setsockopt(FD.SO_LINGER, -1);
+ sock.setsockopt(FD.SO_KEEPALIVE, 1);
+ */
+ fd.setsockopt(FD.TCP_NODELAY, 1);
+ fd.listen(options.max_connections);
+ /*
+ * Mark socket as bound one and add it to
+ * polling set
+ */
+ fd.events = FD.POLLIN;
+ fd.bound = true;
+ set.push(fd);
+ } catch (x) {
+ err.put(x + "\n");
+ }
+ }
+ if (set.length == 0)
+ exit();
/*
* Used for unique index associated to each FD for efficient removal
* from polling set when closing connection
*/
- var count = 0;
+ var count = listen.length + 1;
/*
* Main loop
for (i in set) {
var fd;
- if ((fd = set[i]) == undefined || i == 'bind')
+ if ((fd = set[i]) == undefined || fd.bound == true)
continue;
if (fd.expires <= cur) {
/*
/*
* Verify if a timeout occurred. Because our poll
* implementation returns an array, its timeout event can be
- * checked against by verifying if the set is empty. However,
- * associative-array/object-attributes are not accounted
- * properly with 'length' in JS, so also test fo the case of
- * the 'bind' entry.
+ * checked against by verifying if the set is empty.
*/
- if (e.length == 0 && e['bind'] == undefined) {
+ if (e.length == 0) {
/* Timeout */
continue;
}
cur = (Date.parse(new Date) / 1000);
/*
- * Process occurred events. First handle new connections,
- * if any.
+ * Run through set of descriptors with pending events
*/
- if (e['bind'] != undefined) {
+ for (i in e) {
+ if (e[i] == undefined)
+ continue;
+
/*
- * New connection, accept it
+ * If descriptor is a bound one, attempt to accept
+ * the new client connection
*/
- var fd = sock.accept();
+ if (e[i].bound == true &&
+ (e[i].revents & FD.POLLIN) != 0) {
+ var fd;
- if (!counters_inc(fd)) {
- http_error(fd, 403.9, 'Too Many Connections',
+ try {
+ fd = e[i].accept();
+ if (!counters_inc(fd)) {
+ http_error(fd, 403.9,
+ 'Too Many Connections',
'Your browser has exceeded its maximum ' +
'allowed number of concurrent ' +
'connections.');
- fd.close();
- delete fd;
- } else {
- /*
- * Setup client's initial state
- */
- fd.init(cur, ++count);
- /*
- * Add descriptor to polling set
- */
- set[count] = fd;
- }
- }
-
- /*
- * Run through set of descriptors with pending events
- */
- for (i in e) {
- if (e[i] == undefined || i == 'bind')
+ fd.close();
+ delete fd;
+ } else {
+ /*
+ * Setup client's initial
+ * state and add FD to polling
+ * set
+ */
+ fd.init(cur, ++count);
+ set[count] = fd;
+ }
+ } catch (x) {
+ err.put(x + "\n");
+ }
continue;
+ }
+ /*
+ * Not a new connection event
+ */
var close = false;
+ /*
+ * Close connection on error conditions,
+ * Call the FD's process function on interesting
+ * events, which will tell when we should drop the
+ * client.
+ */
if ((e[i].revents & (FD.POLLHUP | FD.POLLERR)) != 0)
close = true;
else if ((e[i].revents & (FD.POLLIN | FD.POLLOUT))
}
/* NOTREACHED */
- sock.close();
err.close();
}