Jump to content

PHP Webserver


Recommended Posts

So over the past few days I've been writing an HTTP1.1 webserver in PHP.  I have implemented the responses for GET and POST requests on static files (inlcuding most MIME types).  I'm serving a demo site with my new, as of yet, unnamed PHP server (any suggestions?) here: http://www.patrickmizer.com:9191/.  I have the same demo site served by Apache2 here: http://www.patrickmizer.com/apaBench/www/.  Was wondering if you guys wouldn't mind hitting the site a few times.  This is my first round of testing so I expect the server to go down occasionally, I'm just trying to identify bottlenecks etc.

 

Thanks for the help,

 

Patrick

 

P.S.

 

If anyone is interested in seeing the source just shoot me an email and I'll be happy to share.

Link to comment
Share on other sites

looking good so far! i'll have a good dig though, this is definitely an interesting one. i wondered how long before you did this one anyway! ;)

 

ps i've sent you an email as i'd be pretty keen to see how it works

 

Thanks for having a look.  The source should be in your email.

 

Patrick

Link to comment
Share on other sites

Someone crashed the server with this request:

 

TRACK /TRACK_test HTTP/1.0

Accept: */*

User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.1.4322)

Host: www.patrickmizer.com:9191

Connection: Close

Pragma: no-cache

 

Thanks.  If the Request type (TRACK) was undefined it was throwing a fatal exception instead of sending an "unimplemented" response.  This issue has been fixed.

 

Patrick

Link to comment
Share on other sites

Not yet, that's the next step, The performance under load will lag because Apache is multi threaded.  I'm in the process of rigging a pseudo thread pool in my webserver using pcntl_form. 

 

Thanks for having a look.

 

Patrick

Link to comment
Share on other sites

Not yet, that's the next step, The performance under load will lag because Apache is multi threaded.  I'm in the process of rigging a pseudo thread pool in my webserver using pcntl_form. 

 

Thanks for having a look.

 

Patrick

 

The "makes sense" translation of the above is:

 

Not yet, that's the next step.  The performance under load will lag behind Apache because PHP does not natively support threads.  I'm in the process of rigging a pseudo thread pool in my webserver using pcntl_fork

 

Thanks for having a look.

 

Patrick

 

(sigh)

Link to comment
Share on other sites

I managed to get my webserver to fork processes last night.  So now each HTTP request is handled by its own subprocess.  In theory this should speed the server up... we'll see.

 

 

Patrick

 

In case anyone is interested I've attached the code below:

 

<?php
/**
* Http.class.php
* 
* This class listens on the defined port for incoming
* HTTP requests.  Once a request is received this class 
* will attempt to spawn a ServerThread which parses the 
* request and sends a response.
* 
*/

class Http
{
        /**
         * Socket which listens for requests.
         */
        private $serverSocket;
        /**
         * Mimetype registry instance.
         */
        private $mimeRegistry;

        /**
         * Number of spawned child processes.
         */
        private static $childProcesses;

        /**
         * Whether or not to attempt process forking.
         */
        private $canFork;

        /**
         * IPC Message Queue.
         */
        private $messageQueue;


        /**
         * Constructor.
         */
        public function __construct($address, $port)
        {
                $this->mimeRegistry = new MimeRegistry();
                $this->serverSocket = HttpSocket::createNewSocket();
                $this->serverSocket->bindSocket($address, $port);

                $this->canFork = (HTTP_CONFIG_FORK_PROCESSES && 
                                                                function_exists('pcntl_fork'));

                /**
                 * If we can fork then we need to init a few more things.
                 */
                if($this->canFork){
                        $this->messageQueue = msg_get_queue(@ftok(HTTP_CONFIG_MSG_QUEUE, 'R'), 
                                                                                                0666 | IPC_CREAT);
                        self::$childProcesses = 0;
                        pcntl_signal(SIGCHLD, array("Http", "sigHandler"));
                }
        }

        /**
         * Listen for incoming sockets.
         */
        public function listen()
        {
            Logger::log('Http Daemon initialized and listening on port ' . 
                                HTTP_CONFIG_PORT, HTTP_LOG_DEBUG);
            while(1){
                    $clientSocket = HttpSocket::createSocketFromResource(
                                                                $this->serverSocket->acceptConnection());
                        if($clientSocket){
                                if($this->canFork){
                                        $this->forkServerThread($clientSocket);
                                }else{
                                        $this->runServerThread($clientSocket);
                                }
                        }
            }
        }
        /**
         * Handle sigs from processes.
         * 
         * @param sig number.
         */
        public static function sigHandler($signo)
        {
                switch ($signo) {
                        case SIGCHLD:
                                Logger::log('Child process ' . self::$childProcesses .' killed.', HTTP_LOG_DEBUG);
                                -- self::$childProcesses;
                        break;
                }
        }

        /**
         * Attempt to fork the process and run a server thread.
         * 
         * @param client socket
         */
        private function forkServerThread($clientSocket)
        {

                /**
                 * If we are at our child process threshold, wait one 
                 * second and try again.
                 */
                while ( self::$childProcesses >= HTTP_MAX_CHILD_PROCESSES ){
                Logger::log('Maximum child processes threshold met.', HTTP_LOG_DEBUG);
                sleep(1);
                }
                ++ self::$childProcesses;
                $pid = @pcntl_fork();
                if($pid == -1) {
                        /**
                         * If we get a -1 we were unable to fork process.
                         * So we'll just handle the request in serial.
                         */
                        Logger::log('Unable to fork.', HTTP_LOG_DEBUG);
                        $this->runServerThread($clientSocket);                   
                }else if($pid){
                        /**
                         * I am the parent process.
                         */
                         Logger::log('Parent process PID = ' . $pid, HTTP_LOG_DEBUG);
                         
                         
                         /**
                          * Possibly look for zombies.
                          */
                         if(rand(1, 100) <= HTTP_CONF_KILL_ZOMBIE_PERC){
                                 /**
                                  * I need to look in the IPC message queue for any zombie
                                  * children which must be killed off.
                                  */
                                  $currentqueue = msg_stat_queue($this->messageQueue);
                                  $n = $currentqueue['msg_qnum'];
                                  
                                  if($n > 0){
                                        $this->cleanUpZombies($n);
                                  }
                         }
                          
                         
                }else {
                        /**
                         * I am a childprocess.
                         */
                        Logger::log('Child process ' . self::$childProcesses . 
                                                ' spawned.', HTTP_LOG_DEBUG);
                 
                        /**
                         * First I will process the request.
                         */
                        $this->runServerThread($clientSocket);
                 
                        /**
                         * Then, I will tell my parent that I am done.
                         */
                        $error = '';
                        if (!msg_send($this->messageQueue, 1, posix_getpid(), true, true, $error)) {
                                throw new Exception('Error sending message via msg_send(): ' . $error);
                        }
                 
                        /**
                         * Finally I become a zombie and wait for my parent to clean me up.
                         */
                         Logger::log('Zombie.', HTTP_LOG_DEBUG);
                        exit();
                }
        }

        /**
         * Instantiate and run a server thread.
         * 
         * @param client socket
         */
        private function runServerThread($clientSocket)
        {

                $serverThread = new ServerThread($clientSocket, 
                                                                                $this->mimeRegistry);
                $serverThread->run();
        }

        private function cleanUpZombies($num)
        {
        Logger::log('There are '.$num.' zombies to clean up.', HTTP_LOG_DEBUG);
                for ($i = 0; $i < $num; $i++) {
                        /**
                         * Pop the kid's PID from the IPC message queue
                         */
                        $msgType = '';
                        $msg = '';
                        $error = '';
                        if (!msg_receive ($this->messageQueue, 1, $msgType, 16384, $msg, true, 0, $error)) {
                                 throw new Exception('Error getting message via msg_receive(): ' . $error);
                        }else {
                                /**
                                 * Terminate child.
                                 */
                                $tmpstat = '';
                                pcntl_waitpid($msg, $tmpstat, 0);
                        }
                }      

        }
}

Link to comment
Share on other sites

Hey, question: for my own framework's Response class I want to add a basic check if a header is a valid HTTP1.1 header... Shy of making an array of all valid headers in the RFC, any way to do that that you can think of?

Link to comment
Share on other sites

Very impressive. You using the HTTP extension?

 

Thanks.  No -- I'm trying not to use any 3rd party extension such that it can be run "out of the box" with the noyl requirement being PHP5 (and *nix) plat form if you want process forking.

 

Hey, question: for my own framework's Response class I want to add a basic check if a header is a valid HTTP1.1 header.

 

My server currently only implements a relatively small subset of the headers defined in HTTP/1.1.  The headers which are supported are validated via an associative array.

 

Best,

 

Patrick

 

P.S.

 

I just finished my CGI module and got PHP running I'll post a demo this evening... :)

Link to comment
Share on other sites

Yeah.... it does.  I think that something is different about the php-cgi binary on my server at home (it's a 64 bit arch).  On my two other systems the POST works fine.  I'm going to have a look when I get home this evening.

 

Thanks for testing.

 

Patrick

Link to comment
Share on other sites

Very impressive. I know this is the PHP Beta test, but I figure I'd mention that the javascript function on the contact page is throwing an error.

 

I'm very impressed with how quickly things are being processed.

Link to comment
Share on other sites

So... I've been trying to figure out was going on with the POST.  It seems that when the server is accessed locally i.e., http://localhost:9191/test.php the post data is parsed from HTTP request header and passed on to the CGI binary without incident.  However when it's accessed via http://www.patrickmizer.com:9191/test.php request headers get truncated about 80% of the time, like this:

 

Actual HTTP Request

POST /test.php HTTP/1.1
Host: www.patrickmizer.com:9191
User-Agent: Mozilla/5.0 (X11; U; Linux i686 (x86_64); en-US; rv:1.8.1.1) Gecko/20061208 Firefox/2.0.0.1
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Referer: http://www.patrickmizer.com:9191/test.php
Content-Type: application/x-www-form-urlencoded
Content-Length: 28

height=0&width=0&submitted=1

 

Truncated HTTP Request:

POST /test.php HTTP/1.1
Host: www.patrickmizer.com:9191
User-Agent: Mozilla/5.0 (X11; U; Linux i686 (x86_64); en-US; rv:1.8.1.1) Gecko/20061208 Firefox/2.0.0.1
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Referer: http://www.patrickmizer.com:9191/test.php

 

I have no idea why the request is being truncated when accessing the server from its external IP address.  The only thing I can think of is that maybe the socket is timing out or something during the read... but it's weird that it always cuts off at Referer.  I was hoping myabe you guys could bang away at this URL: http://www.patrickmizer.com:9191/test.php and test out the POST form, maybe a pattern will emerge.

 

Thanks,

 

Patrick

Link to comment
Share on other sites

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.