-/* $Id: httpd.js,v 1.27 2006/08/16 16:47:54 mmondor Exp $ */
+/* $Id: httpd.js,v 1.28 2006/08/18 07:49:29 mmondor Exp $ */
/*
* Copyright (c) 2005-2006, Matthew Mondor
* the process/state function reference. The less conditionals each of these
* functions can perform, the more efficient.
* When possible, it is nice to be able to chain a read/write phase into the
- * same FD event processing session (such as process_transfer_file() does).
+ * same FD event processing session (such as state_http_transfer_file() does).
* Nevertheless, these require a transitional buffer.
*
* - client fd -> appserv connect state
* - file to client, must take into account client connection status
- * (process_transfer_file()).
+ * (state_http_transfer_file()).
* - client to appserv, must take into account both connections status
- * (process_transfer_toappserv()). client == POLLIN, appserv == POLLOUT
+ * (state_http_transfer_toappserv()). client == POLLIN, appserv == POLLOUT
* - appserv to client, must take into account both connections status
- * (process_transfer_fromappserv()). appserv == POLLIN, client == POLLOUT
+ * (state_http_transfer_fromappserv()). appserv == POLLIN, client == POLLOUT
*/
* Server identification
*/
const SERVER_VERSION = 'mmondor_js_httpd/0.1.0 (NetBSD)';
-const SERVER_CVSID = '$Id: httpd.js,v 1.27 2006/08/16 16:47:54 mmondor Exp $';
+const SERVER_CVSID = '$Id: httpd.js,v 1.28 2006/08/18 07:49:29 mmondor Exp $';
const TSTATE_WRITE = 1;
/*
- * General status return for processing functions
+ * General status return for state functions
*/
const PSTAT_CONTINUE = 0;
const PSTAT_CLOSE_SUCCESS = 1;
/*
- * For the mime types database
+ * Quick SID->appserv lookup table
*/
-var mimetypes_table = {};
+var sid_table = {};
+
+/*
+ * The SID object.
+ * When calling the constructor with a non-null sid, lookup is first made in
+ * the SID table to detect such SID with existing connection to an appserv
+ * already exists, and if so, returns true. If such a connection doesn't
+ * exist, an attempt to connect to an appserv and resume that SID is
+ * performed, which on success causes true to be returned, or false on error
+ * XXX which should probably redirect to an application-specific URL.
+ * If sid is null, an attempt to connect to an appserv and to create a new SID
+ * is performed, and on success new SID added into the table and true
+ * returned. On error, false is returned (XXX which should also redirect to
+ * an application-specific URL).
+ * XXX Hrm several problems exist. First, we must first launch the connection
+ * attempt but then return, to have a FD.state() function be called whenever
+ * connection is established or failed. Then can we only proceed with
+ * requests over that socket. Also, wouldn't it make more sense to return an
+ * fd or null rather than true or false here?
+ */
+function SID(sid)
+{
+}
+
+SID.prototype = {
+};
/*
- * Quick lookup table from SID to existing appserv connection, if any.
- * Simply a sid->fd mapping.
+ * For the mime types database
*/
-var sid_table = {};
+var mimetypes_table = {};
*/
/*
- * Handles request query processing, to be assigned to FD.process() while in
+ * Handles request query processing, to be assigned to FD.state() while in
* the request query state. This then invokes parseQuery() which may
* either send an HTTP response and/or cause a switch to another state.
*/
-function process_query_get(time)
+function state_http_query_get(time)
{
var done = false;
var stat = PSTAT_CONTINUE;
return stat;
}
-function process_post_get(time)
+function state_http_post_get(time)
{
var stat = PSTAT_CONTINUE;
var len;
/*
* Handles transfer processing from an open file to an open client descriptor,
- * to be assigned to FD.process() while in the transfer state.
+ * to be assigned to FD.state() while in the transfer state.
*/
-function process_transfer_file(time)
+function state_http_transfer_file(time)
{
var stat = PSTAT_CONTINUE;
var bufsiz;
* Default handler to process this FD, the request state one.
* To be changed to the transfer one as needed for transfer state.
*/
- this.process = process_query_get;
+ this.state = state_http_query_get;
/* Initial input timeout */
this.updateTimeout(time);
*/
if (this.http_method == 'POST' && this.http_content_length != -1) {
/*
- * Switch to process_post_get() state. Invoke it ourselves
+ * Switch to state_http_post_get() state. Invoke it ourselves
* at least once to empty the read buffer if any, then go back
* to polling. If that first call is enough to satisfy the
* needed size, immediately call parsePost() which will return
* close status after invoking httpRespond().
*/
this.post_data = '';
- this.process = process_post_get;
- if (!this.process(time))
+ this.state = state_http_post_get;
+ if (!this.state(time))
return PSTAT_CONTINUE;
if (this.post_data.length == this.http_content_length)
return this.parsePost(time);
- /* Go back to polling under process_post_get() state */
+ /* Go back to polling under state_http_post_get() state */
return PSTAT_CONTINUE;
}
this.transfer_dst = this;
this.transfer_eof = false;
this.transfer_data = '';
- this.process = process_transfer_file;
+ this.state = state_http_transfer_file;
this.events = FD.POLLOUT;
/*
* Return with PSTAT_CONTINUE, to delegate operations to
- * process_transfer_file().
+ * state_http_transfer_file().
*/
return PSTAT_CONTINUE;
}
+/*
+ * Triggers a connection to an appserv if possible, queueing a new FD in the
+ * event queue for asynchroneous connection. state_connect_appserv() will
+ * be called whenever the event occurs. If connection is already established
+ * to an appserv for this sid, sets appserv_fd and returns true immediately.
+ * cfd and sid are passed in a matter for state_connect_appserv() to be able
+ * to resume. sid can be null if a new sid is to be created, or a sid to be
+ * resumed.
+ * Returns true on success or false on error.
+ */
+FD.prototype.appserv_connect = function(sid, set)
+{
+ var s, fd;
+
+ /*
+ * SID already served by a current appserv connection?
+ */
+ if ((fd = sid_table[sid]) != undefined) {
+ this.appserv_fd = fd;
+ return true;
+ }
+
+ /*
+ * Allocate a new appserv slot
+ */
+ if ((s = this.vhost.server_get()) == undefined)
+ return false;
+
+ /*
+ * Create new appserv fd and initiate connection, delegating to
+ * state_connect_appserv()
+ */
+ try {
+ fd = new FD();
+ fd.stype = STYPE_CONNECT;
+ fd.fcntl(FD.F_SETFL, FD.O_NONBLOCK);
+
+ fd.connect(s.host, s.port);
+ fd.events = FD.POLLOUT;
+ fd.state = state_connect_appserv;
+
+ if (++fdidx_count > 9999999)
+ fdidx_count = fdidx_min + 1;
+ fd.index = fdidx_count;
+ set[fdidx_count] = fd;
+
+ fd.client_fd = this;
+ fd.sid = sid;
+ fd.set = set;
+ } catch (x) {
+ err.put(x + " in appserv_connect()\n");
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Once a non-blocking connect(2) has been called on a new socket, it is added
+ * to the polling set with POLLOUT, which causes this function to be called to
+ * attempt to complete or cancel the connection.
+ * This function switches to the state_appserv_sid() on success, or causes
+ * the connection to be closed on error.
+ */
+function state_connect_appserv(time)
+{
+
+ if (this.getsockopt(FD.SO_ERROR) != 0) {
+ /*
+ * Error. Close appserv fd, removing it from the polling set
+ * and send error to corresponding client fd.
+ */
+ try {
+ this.close();
+ } catch (x) {};
+ /*
+ * XXX Hmm this appears ridiculous.
+ * We ideally shouldn't care about the http client fd here,
+ * but instead the client fd should remain in a state to
+ * continue processing once the sid it awaits for is being
+ * served by an appserv connection. It also should get status
+ * via the same system, so that it may return an error
+ * instead in response to the client request.
+ * Possibly that instead of only POLL* events, we could also
+ * have other types of internal events? This would still
+ * expect poll to return regularily, though. Maybe that we
+ * just need a state that does nothing until response is
+ * obtained, and a way for this system to "notify" that fd?
+ * Then we still need the client_fd, though.
+ * Hmm... interestingly we could mute the client fd from poll
+ * events setting events to 0, and put it back to POLLOUT
+ * causing the state function to be called again whenever it
+ * makes sense to. This would prevent the main loop from
+ * being clobbered by dummy POLLOUT events for nothing while
+ * the client fd waits for an appserv connection to be
+ * established. Remains to see if we need to awake multiple
+ * pending requests at the same time... Then those requests
+ * must also be paused whenever a connection is attempted for
+ * a particular sid, too, using a table for lookup.
+ * say... sid_connecting{} containing objects such as <sid>
+ * indexed arrays, holding a list of client FD objects which
+ * are queued waiting for an appserv to be available for that
+ * sid.
+ * XXX Then we have another potential problem: multiple
+ * concurrent http client requests must be able to query the
+ * appserv, in which case they should be serialized. In a
+ * similar manner, we would need to block those until they can
+ * gain right to send a query, which case that particular fd
+ * must also be blocked until answer or appserv disconnection
+ * takes place.
+ * Hence we seem to need some general purpose FD based
+ * semaphore system...
+ */
+ http_error(this.client_fd, 500, 'Internal Server Error',
+ 'There was an error establishing connection to an ' +
+ 'application server. Please try again later.');
+ this.client_fd.state = state_close_success;
+ this.client_fd.events = FD.POLLOUT;
+ this.set[this.index] = undefined;
+ return PSTAT_CLOSE_ERROR;
+ }
+
+ /* Success */
+ this.stype = STYPE_APPSERV;
+ this.client_fd = undefined;
+ this.transfer_state = TSTATE_WRITE;
+ /* XXX */
+
+ return PSTAT_CONTINUE;
+}
+
+/*
+ * Upon successful connection, a switch to this state is made.
+ * This sends the initial sid creation or loading/resuming request to the
+ * server and closes on error, or sets up the sid->fd relationship on success
+ * for client requests to be handled through that fd for that sid later on.
+ */
+function state_appserv_sid(time)
+{
+ var data;
+
+ if (this.transfer_state == TSTATE_WRITE) {
+ } else { /* TSTATE_READ */
+ while ((data = this.breadline(256, false)) != null) {
+ }
+ if (data == null) {
+ /* Error */
+ }
+ }
+}
+
+/*
+ * Dummy function allowing to cause a wanted fd from the set to be closed.
+ */
+function state_close_success(time)
+{
+
+ return PSTAT_CLOSE_SUCCESS;
+}
+
+
FD.prototype.httpDebug = function()
{
var table, tr, td, pre, font;
+var fdidx_count = 0;
+var fdidx_min = 0;
+
/*
* Main program
*/
function main() {
var i;
var sess_gc_secs = 0;
- var count = 0, count_min;
var set, fd, e, efd, flush;
/*
*/
fd.events = FD.POLLIN;
fd.stype = STYPE_LISTEN;
- set[count++] = fd;
+ set[fdidx_count++] = fd;
} catch (x) {
err.put(x + " preparing listening socket\n");
}
}
- if (count == 0)
+ if (fdidx_count == 0)
exit();
- count_min = count;
+ fdidx_min = fdidx_count;
/*
* Main loop
* Setup client's initial state and
* add FD to polling set
*/
- if (++count > 9999999)
- count = count_min + 1;
- fd.init(cur, count);
- set[count] = fd;
+ if (++fdidx_count > 9999999)
+ fdidx_count = fdidx_min + 1;
+ fd.init(cur, fdidx_count);
+ set[fdidx_count] = fd;
} catch (x) {
err.put(x + " at accept(2)\n");
fd.close();
}
/*
- * If descriptor is one for which a connect(2) to an
- * appserv is in progress, attempt to complete
- * connection. Close the associated HTTP client
- * connection as necessary on error.
- */
- if (efd.stype == STYPE_CONNECT &&
- (efd.revents & FD.POLLOUT) != 0) {
- /* XXX */
- continue;
- }
-
- /*
* Most of the code for STYPE_HTTP and STYPE_APPSERV
- * is shared. However, we duplicate it to only need
- * to perform a single conditional to differenciate
- * them for performance.
+ * is shared. STYPE_CONNECT is transitional and
+ * handled as other events.
*/
- if (efd.stype == STYPE_APPSERV) {
- /* XXX */
- }
-
/*
* Close connection on error conditions,
- * Call the FD's process function on interesting
+ * Call the FD's state function on interesting
* events, which will tell when we should drop the
* client.
*/
efd.stat = PSTAT_CLOSE_ERROR;
else if (flush == 0 &&
efd.stat == PSTAT_CONTINUE)
- efd.stat = efd.process(cur);
+ efd.stat = efd.state(cur);
else if (flush == 1 &&
efd.stat != PSTAT_CLOSE_ERROR)
continue;
}
- /*
- * XXX
- * If we have an associated appserv connection, we
- * must close it on error, but sustain it otherwise?
- */
if (efd.stat != PSTAT_CONTINUE) {
try {
efd.close();
} catch (x) {}
+ if (efd.stype == STYPE_APPSERV)
+ sid_table[efd.sid] = undefined;
efd = undefined;
counters_dec(e[i]);
delete set[e[i].index];