The SCGI protocol is a replacement for the Common Gateway Interface (CGI) protocol. It is a standard for applications to interface with HTTP servers. It is similar to FastCGI but is designed to be easier to implement. This is a PHP Implementation.
<?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) | |
{ | |
// Create the base | |
$this->base = new EventBase(); | |
// Check the base got created okay | |
if (!$this->base) | |
{ | |
echo "Couldn't open event base"; | |
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) | |
{ | |
echo "Couldn't create listener"; | |
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); | |
} | |
/** | |
* 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 | |
fprintf | |
( | |
STDERR, | |
"Got an error %d (%s) on the listener. Shutting down.\n", | |
EventUtil::getLastSocketErrno(), | |
EventUtil::getLastSocketError() | |
); | |
// 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; | |
} | |
} | |
/** | |
* 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) | |
{ | |
$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)) | |
{ | |
echo "Failed to enable READ\n"; | |
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 | |
echo 'Request Headers:'."\n"; | |
print_r($headers); | |
// 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) | |
{ | |
echo "Response Sent\n\n"; | |
$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) | |
{ | |
if ($events & EventBufferEvent::ERROR) | |
{ | |
echo "Error from bufferevent\n"; | |
} | |
if ($events & (EventBufferEvent::EOF | EventBufferEvent::ERROR)) | |
{ | |
$this->__destruct(); | |
} | |
} | |
/** | |
* Method: __destruct | |
* ========================================================================= | |
* Destroy ourselves. | |
* | |
* Parameters | |
* ------------------------------------------------------------------------- | |
* n/a | |
* | |
* Returns: | |
* ------------------------------------------------------------------------- | |
* void | |
*/ | |
public function __destruct() | |
{ | |
$this->bev->free(); | |
} | |
} | |
// Start the server | |
new MyListener('127.0.0.1:9000'); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment