*** empty log message ***
authorMatthew Mondor <mmondor@pulsar-zone.net>
Fri, 18 Aug 2006 07:49:29 +0000 (07:49 +0000)
committerMatthew Mondor <mmondor@pulsar-zone.net>
Fri, 18 Aug 2006 07:49:29 +0000 (07:49 +0000)
mmsoftware/js/js-sh/app/httpd/httpd.js

index 956bc37..3c681fc 100644 (file)
@@ -1,4 +1,4 @@
-/* $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
  */
 
 
@@ -76,7 +76,7 @@
  * 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 $';
 
 
 
@@ -157,7 +157,7 @@ const TSTATE_READ           = 0;
 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;
@@ -366,17 +366,41 @@ VHost.prototype = {
 
 
 /*
- * 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 = {};
 
 
 
@@ -493,11 +517,11 @@ function http_error(fd, code, desc, ldesc)
  */
 
 /*
- * 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;
@@ -553,7 +577,7 @@ function process_query_get(time)
        return stat;
 }
 
-function process_post_get(time)
+function state_http_post_get(time)
 {
        var stat = PSTAT_CONTINUE;
        var len;
@@ -578,9 +602,9 @@ function process_post_get(time)
 
 /*
  * 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;
@@ -682,7 +706,7 @@ FD.prototype.init = function(time, idx)
         * 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);
 
@@ -883,19 +907,19 @@ FD.prototype.parseQuery = function(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;
        }
 
@@ -1177,16 +1201,177 @@ FD.prototype.httpRespond = function(time)
        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;
@@ -1387,13 +1572,15 @@ function counters_dec(fd)
 
 
 
+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;
 
        /*
@@ -1471,14 +1658,14 @@ function main() {
                         */
                        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
@@ -1582,10 +1769,10 @@ function main() {
                                         * 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();
@@ -1596,31 +1783,14 @@ function main() {
                        }
 
                        /*
-                        * 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.
                         */
@@ -1642,21 +1812,18 @@ function main() {
                                        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];