Created
January 23, 2014 06:24
-
-
Save brad-jones/8573837 to your computer and use it in GitHub Desktop.
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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