webserver.php
changeset 15 2adca0f363fd
parent 13 b5db2345c397
child 16 23d4cf2f183b
--- a/webserver.php	Mon Mar 31 07:40:30 2008 -0400
+++ b/webserver.php	Wed Apr 02 00:23:51 2008 -0400
@@ -95,6 +95,21 @@
   var $allow_dir_list = false;
   
   /**
+   * Switch to control forking support.
+   * @var bool
+   */
+  
+  var $allow_fork = true;
+  
+  /**
+   * Keep-alive support uses this to track what the client requested.
+   * Only used if $allow_fork is set to true.
+   * @var bool
+   */
+  
+  var $in_keepalive = false;
+  
+  /**
    * Constructor.
    * @param string IPv4 address to bind to
    * @param int Port number
@@ -130,8 +145,12 @@
   
   function __destruct()
   {
-    status('WebServer: destroying socket');
-    @socket_close($this->sock);
+    if ( !defined('HTTPD_WS_CHILD') )
+    {
+      status('WebServer: destroying socket');
+      @socket_shutdown($this->sock, 2);
+      @socket_close($this->sock);
+    }
   }
   
   /**
@@ -142,24 +161,59 @@
   {
     while ( true )
     {
+      // if this is a child process, we're finished - close up shop
+      if ( defined('HTTPD_WS_CHILD') && !$this->in_keepalive )
+      {
+        exit(0);
+      }
+      
       // wait for connection...
       // trick from http://us.php.net/manual/en/function.socket-accept.php
-      $remote = false;
-      switch(@socket_select($r = array($this->sock), $w = array($this->sock), $e = array($this->sock), 5)) {
-        case 2:
-          break;
-        case 1:
-          $remote = @socket_accept($this->sock);
-          break;
-        case 0:
-          break;
+      if ( !defined('HTTPD_WS_CHILD') )
+      {
+        $remote = false;
+        $timeout = 5;
+        switch(@socket_select($r = array($this->sock), $w = array($this->sock), $e = array($this->sock), $timeout)) {
+          case 2:
+            break;
+          case 1:
+            $remote = @socket_accept($this->sock);
+            break;
+          case 0:
+            break;
+        }
       }
          
       if ( !$remote )
       {
+        $this->in_keepalive = false;
         continue;
       }
       
+      // fork off if possible
+      if ( function_exists('pcntl_fork') && $this->allow_fork && !$this->in_keepalive )
+      {
+        $pid = pcntl_fork();
+        if ( $pid == -1 )
+        {
+          // do nothing; continue responding to request in single-threaded mode
+        }
+        else if ( $pid )
+        {
+          // we are the parent, continue listening
+          $remote = false;
+          continue;
+        }
+        else
+        {
+          // this is the child
+          define('HTTPD_WS_CHILD', 1);
+          $this->sock = false;
+        }
+      }
+      
+      $this->in_keepalive = false;
+      
       // read request
       $last_line = '';
       $client_headers = '';
@@ -198,6 +252,12 @@
         $_SERVER[$key] = $match[2];
       }
       
+      // enable keep-alive if requested
+      if ( isset($_SERVER['HTTP_CONNECTION']) && defined('HTTPD_WS_CHILD') )
+      {
+        $this->in_keepalive = ( $_SERVER['HTTP_CONNECTION'] === 'keep-alive' );
+      }
+      
       if ( isset($_SERVER['HTTP_AUTHORIZATION']) )
       {
         $data = $_SERVER['HTTP_AUTHORIZATION'];
@@ -285,7 +345,19 @@
       
       $this->send_standard_response($remote, $handler, $uri, $params);
       
-      @socket_close($remote);
+      if ( !$this->in_keepalive )
+      {
+        // if ( defined('HTTPD_WS_CHILD') )
+        //   status('Closing connection');
+        @socket_close($remote);
+        exit(0);
+      }
+      else
+      {
+        // if ( defined('HTTPD_WS_CHILD') )
+        //   status('Continuing connection');
+        @socket_write($remote, "\r\n\r\n");
+      }
     }
   }
   
@@ -302,15 +374,17 @@
     global $http_responses;
     $reason_code = ( isset($http_responses[$http_code]) ) ? $http_responses[$http_code] : 'Unknown';
     
+    $_SERVER['HTTP_USER_AGENT'] = ( isset($_SERVER['HTTP_USER_AGENT']) ) ? $_SERVER['HTTP_USER_AGENT'] : '(no user agent)';
     status("{$_SERVER['REMOTE_ADDR']} {$_SERVER['REQUEST_METHOD']} {$_SERVER['REQUEST_URI']} $http_code {$_SERVER['HTTP_USER_AGENT']}");
     
     $headers = str_replace("\r\n", "\n", $headers);
     $headers = str_replace("\n", "\r\n", $headers);
     $headers = preg_replace("#[\r\n]+$#", '', $headers);
+    $connection = ( $this->in_keepalive ) ? 'keep-alive' : 'close';
     
     @socket_write($socket, "HTTP/1.1 $http_code $reason_code\r\n");
     @socket_write($socket, "Server: $this->server_string");
-    @socket_write($socket, "Connection: close\r\n");
+    @socket_write($socket, "Connection: $connection\r\n");
     @socket_write($socket, "Content-Type: $contenttype\r\n");
     if ( !empty($headers) )
     {
@@ -518,6 +592,7 @@
           return true;
         }
         
+        $this->header("Content-length: " . strlen($output));
         $headers = implode("\r\n", $this->response_headers);
         
         // write headers