Skip to content

Instantly share code, notes, and snippets.

@brad-jones
Created January 29, 2014 06:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brad-jones/8682878 to your computer and use it in GitHub Desktop.
Save brad-jones/8682878 to your computer and use it in GitHub Desktop.
Daemonized Scgi Server
<?php
/**
* Class: MyConnection
* =============================================================================
* This class works with an individual connection to our server.
* And deals more with the actual SCGI protocol.
*/
class MyConnection
{
/**
* Property: bev
* =========================================================================
* This is where we store the EventBufferEvent object.
*/
private $bev;
/**
* Method: __construct
* =========================================================================
* This creates the EventBufferEvent object along with some call backs.
*
* Parameters
* -------------------------------------------------------------------------
* $base - A copy of EventBase
* $fd - The file descriptor or a resource associated with the listener.
*
* Returns:
* -------------------------------------------------------------------------
* void
*/
public function __construct($base, $fd, $log)
{
$this->log = $log;
$this->bev = new EventBufferEvent
(
$base, $fd, EventBufferEvent::OPT_CLOSE_ON_FREE
);
$this->bev->setCallbacks
(
[$this, "readCallback"],
[$this, "writeCallback"],
[$this, "eventCallback"],
NULL
);
if (!$this->bev->enable(Event::READ))
{
file_put_contents($this->log, "ERROR: Failed to enable READ\n", FILE_APPEND);
return;
}
}
/**
* Method: readCallback
* =========================================================================
* This is where we read in an actual request.
* For details on the protocol see: http://www.python.ca/scgi/protocol.txt
*
* Parameters
* -------------------------------------------------------------------------
* $bev - The buffer event
*
* Returns:
* -------------------------------------------------------------------------
* void
*/
public function readCallback($bev)
{
// Grab the scgi request
$request = $bev->input->read($bev->input->length);
// Grab the length of the netstring
$length = substr($request, 0, strpos($request, ':'));
// Read in the headers
$headers_string = substr($request, strpos($request, ':')+1, $length);
// Parse the headers
$headers = [];
preg_match_all('/(.*?)\x00(.*?)\x00/s', $headers_string, $matches);
foreach ($matches[1] as $x => $name) $headers[$name] = $matches[2][$x];
// Output the headers to console for debug purposes
file_put_contents($this->log, 'STATUS: Request Headers'."\n", FILE_APPEND);
file_put_contents($this->log, print_r($headers, true), FILE_APPEND);
// Write the response
$bev->write
(
'Status: 200 OK'."\r\n".
'Content-Type: text/plain'."\r\n".
"\r\n".
time()
);
}
/**
* Method: writeCallback
* =========================================================================
* This is called after we have written to the buffer. Because the SCGI
* protocol calls for the connection to be closed. We must then destroy
* ourselves.
*
* Parameters
* -------------------------------------------------------------------------
* $bev - The buffer event
*
* Returns:
* -------------------------------------------------------------------------
* void
*/
public function writeCallback($bev)
{
file_put_contents($this->log, "STATUS: Response Sent\n\n", FILE_APPEND);
$this->__destruct();
}
/**
* Method: eventCallback
* =========================================================================
* Some basic error handling.
*
* Parameters
* -------------------------------------------------------------------------
* $bev - A copy of the buffer event.
* $events - Bit mask of events
*
* Returns:
* -------------------------------------------------------------------------
* void
*/
public function eventCallback($bev, $events, $log)
{
$this->log = $log;
if ($events & EventBufferEvent::ERROR)
{
file_put_contents($this->log, "ERROR: Error from bufferevent\n", FILE_APPEND);
}
if ($events & (EventBufferEvent::EOF | EventBufferEvent::ERROR))
{
$this->__destruct();
}
}
/**
* Method: __destruct
* =========================================================================
* Destroy ourselves.
*
* Parameters
* -------------------------------------------------------------------------
* n/a
*
* Returns:
* -------------------------------------------------------------------------
* void
*/
public function __destruct()
{
$this->bev->free();
}
}
<?php
/**
* Class: MyListener
* =============================================================================
* This sets up the underlying socket server.
* Using all this nice new LibEvent code which is now part of PHP.
*/
class MyListener
{
/**
* Property: base
* =========================================================================
* This is where the EventBase instance is stored.
*/
public $base;
/**
* Property: listener
* =========================================================================
* This is where the EventListener instance is stored.
*/
public $listener;
/**
* Property: connections
* =========================================================================
* This is where we store an array of MyConnection objects.
*/
private $connections = array();
/**
* Method: __construct
* =========================================================================
* Creates the EventBase and EventListener objects, sets up some call backs.
* Along with some very basic error handling. It finally dispatches the
* events.
*
* Parameters
* -------------------------------------------------------------------------
* $who - Who are we listening too, can be IP:PORT or unix:/tmp/my.sock
*
* Returns:
* -------------------------------------------------------------------------
* void
*/
public function __construct($who, $log)
{
$this->log = $log;
// Create the base
$this->base = new EventBase();
// Check the base got created okay
if (!$this->base)
{
file_put_contents($this->log, "ERROR: Couldn't open event base. Shutting down...\n", FILE_APPEND);
exit(1);
}
// Create the listener
$this->listener = new EventListener
(
$this->base,
[$this, "acceptConnectionCb"],
null,
EventListener::OPT_CLOSE_ON_FREE | EventListener::OPT_REUSEABLE,
-1,
$who
);
// Check the listener got created okay
if (!$this->listener)
{
file_put_contents($this->log, "ERROR: Couldn't create listener. Shutting down...\n", FILE_APPEND);
exit(1);
}
// Add an error handler for the listener
$this->listener->setErrorCallback([$this, "acceptErrorCb"]);
// Start the server basically
$this->base->dispatch();
}
/**
* Method: acceptConnectionCb
* =========================================================================
* When ever a new connection to our server comes in this is called.
* It creates a new instance of MyConnection and it then takes over the
* processing of the request.
*
* Parameters
* -------------------------------------------------------------------------
* $listener - A copy of EventListener
* $fd - The file descriptor or a resource associated with the listener.
*
* Returns:
* -------------------------------------------------------------------------
* void
*/
public function acceptConnectionCb($listener, $fd)
{
$this->connections[] = new MyConnection($this->base, $fd, $this->log);
}
/**
* Method: acceptErrorCb
* =========================================================================
* When ever the listener has some sort of error this will be called.
*
* Parameters
* -------------------------------------------------------------------------
* n/a
*
* Returns:
* -------------------------------------------------------------------------
* void
*/
public function acceptErrorCb()
{
// Output the error
file_put_contents($this->log, "ERROR: Got an error ".EventUtil::getLastSocketError()." (".EventUtil::getLastSocketErrno().") on the listener. Shutting down...\n", FILE_APPEND);
// And stop the server.
$this->base->exit(NULL);
}
/**
* Method: __destruct
* =========================================================================
* Destroy all the connections
*
* Parameters
* -------------------------------------------------------------------------
* n/a
*
* Returns:
* -------------------------------------------------------------------------
* void
*/
public function __destruct()
{
foreach ($this->connections as &$c) $c = NULL;
}
}
#!/bin/sh
#
# scgiserver Startup script for scgiserver
#
# chkconfig: - 85 15
# processname: scgiserver
# description: scgiserver is a daemon
#
### BEGIN INIT INFO
# Provides: scgiserver
# Required-Start: $local_fs $remote_fs $network
# Required-Stop: $local_fs $remote_fs $network
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: start and stop scgiserver
### END INIT INFO
# Source function library.
. /etc/init.d/functions
# Some variables
name=scgiserver
bin="/usr/bin/php /opt/scgiserver/scgiserver.php"
lockfile=/var/lock/subsys/scgiserver
RETVAL=0
start() {
echo -n $"Starting $name: "
daemon ${bin}
RETVAL=$?
echo
[ $RETVAL = 0 ] && touch ${lockfile}
return $RETVAL
}
stop() {
echo -n $"Stopping $name: "
killproc ${bin}
RETVAL=$?
echo
[ $RETVAL = 0 ] && rm -f ${lockfile}
}
restart() {
stop
start
}
reload() {
restart
}
status_at() {
status $bin
}
case "$1" in
start)
start
;;
stop)
stop
;;
reload|restart)
restart
;;
condrestart)
if [ -f $proc ]; then
restart
fi
;;
status)
status_at
;;
*)
echo $"Usage: $0 {start|stop|restart|condrestart|status}"
exit 1
esac
exit $?
exit $RETVAL
#!/usr/bin/env php
<?php
// clear the shebang and anyother possible previous buffer
@ob_end_clean();
// turn on full error reporting
error_reporting(E_ALL);
// include our classes
require('MyListener.php');
require('MyConnection.php');
// define some variables
$log = '/var/log/scgiserver.log';
$address = '127.0.0.1';
$port = 9000;
// Tell the world we are starting up
file_put_contents($log, "STATUS: starting up.\n", FILE_APPEND);
// daemonize ourselves
$pid = pcntl_fork();
if($pid == -1)
{
// error
file_put_contents($log, "ERROR: could not daemonize process. Shutting down...\n", FILE_APPEND);
return 1;
}
else if($pid)
{
// success
file_put_contents($log, "STATUS: daemonized process.\n", FILE_APPEND);
return 0;
}
else
{
// start the server
new MyListener($address.':'.$port, $log);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment