phastlight / phastlight
Phastlight -- Asynchronous, event-driven command line tool and web server written in PHP 5.3+ inspired by Node.js
Installs: 120
Dependents: 0
Suggesters: 0
Security: 0
Stars: 162
Watchers: 26
Forks: 12
Open Issues: 0
Requires
- php: >=5.3.0
- symfony/process: *
README
Phastlight is an asynchronous, event-driven command line tool and web server written in PHP 5.3+ inspired by Node.js
Phastlight is built on top of libuv, the same library used behind Node.js.
At this time, Phastlight is on its very early development phrases,it currently supports the following features:
- Dynamic method creation
- Module Creation
- Event
- Error and Exception Handling
- HTTP
-
PHP server variables simulation on HTTP Request
Phastlight simulates the following PHP server variables on each HTTP Request:
$_SERVER['SERVER_PORT'] $_SERVER['SERVER_ADDR'] $_SERVER['REMOTE_ADDR'] $_SERVER['HTTP_HOST'] $_SERVER['REQUEST_METHOD'] $_SERVER['REQUEST_URI'] $_SERVER['PATH_INFO'], $_SERVER['HTTP_USER_AGENT']
- Timer
- Async Timer similar to http://nodejs.org/api/timers.html
- Process
- Child Process
- Console
- Log message to the console similar to Console.log in Javascript
- File System:
- Asynchronous Network Wrapper
- Operating System
- Cluster
More features will be on the way, stay tuned...
Phastlight Application Examples:
- Mult-Tasking in one single event loop
- Simple Microframework on top of Phalcon PHP Framework Routing Component
- Working with Symfony2 HTTP Foundation Request and Response component
- Simple asynchronous MYSQL query through dbslayer
- Simple asynchronous Memcache get and set
- Simple asynchronous Redis get and set
At this phrase, phastlight is good for high concurrency, low data transfer, non cpu intensive web or moble applications.
##Installation:
Tested on:
- CentOS 6.2 64bit with gcc 4.4.x
- Mac OS 10.8 with gcc 4.2.1
install with the installation script
curl -sS https://raw.github.com/phastlight/phastlight/master/scripts/install.sh | sh
This will install phastlight to /usr/local/phastlight directory, and the phastlight executable will be at /usr/local/bin/phastlight
To install phastlight executable to a different directory, for example, the ~/bin directory, do:
curl -sS https://raw.github.com/phastlight/phastlight/master/scripts/install.sh > phastlight_install.sh
sh phastlight_install.sh --phastlight_executable_path=~/bin
rm -f phastlight_install.sh
To run the server, do phastlight [server file full path]
Dynamic method creation
Phastlight allows dynamic method creation, in the example below, we create a hello method in the system object
<?php $system = new \Phastlight\System(); $system->method("hello", function($word){ echo "Hello $word"; }); $system->hello("world");
Save this file as server.php, then do: phastlight server.php, we should see the result "Hello world".
Module creation
Phastlight supports a flexible module system with export and import, the following example shows how to create a simple module that can print "hello world"
<?php class MyModule extends \Phastlight\Module { public function hello($word) { echo "hello $word"; } } $system = new \Phastlight\System(); $system->export("mymodule", "\MyModule"); //we first export the MyModule module $module = $system->import("mymodule"); //now we can import it $module->hello("world");
Event Emitting
Event Emitter is a core component in phastlight, we can use it to emit and handle an event
<?php $eventEmitter = new \Phastlight\EventEmitter(); $eventEmitter->on("test", function(){ echo "hello in test\n"; }); $eventEmitter->emit("test");
Error Handling
When error occured, phastlight will emit system.error event, and we can use this event to further polish the error handling
<?php $system = new \Phastlight\System(); $system->on("system.error", function($error){ print $error->getFilePath()."\n"; print $error->getMessage()."\n"; print $error->getLine()."\n"; print $error->getSeverity()."\n"; }); $i = 12/0; //we purposely divide an integer by 0
Exception Handling
When exception occurred, phastlight will emit system.exception event, and we can use this event to further polish the exception handling
<?php $system = new \Phastlight\System(); $system->on("system.exception", function($exception){ print $exception->getMessage()."\n"; print $exception->getCode()."\n"; print $exception->getLine()."\n"; }); throw new Exception('Uncaught Exception');
Simple HTTP server, benchmarked with PHP 5.5.9 and Node.js v0.10.25
<?php $system = new \Phastlight\System(); $console = $system->import("console"); $http = $system->import("http"); $http->createServer(function($req, $res){ $res->writeHead(200, array('Content-Type' => 'text/plain')); $res->end($req->getURL()); })->listen(1337, '127.0.0.1'); $console->log('Server running at http://127.0.0.1:1337/');
Save this file as server.php, then run phastlight server.php and go to http://127.0.0.1:1337/ to see the result.
Below is the benchmark performed with Apache AB against the following Node.js script, the operating system is CENTOS 6 64bit, we simulate 200k requests and 5k concurrent requests. Result shows phastlight is faster than Node.js.
Node.js script
var http = require('http'); http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end(req.url); }).listen(1337, '127.0.0.1'); console.log('Server running at http://127.0.0.1:1337/');
The PHP HTTP Server
Concurrency Level: 5000
Time taken for tests: 38.994 seconds
Complete requests: 200000
Failed requests: 0
Write errors: 0
Total transferred: 9057240 bytes
HTML transferred: 201272 bytes
Requests per second: 5129.06 [#/sec] (mean)
Time per request: 974.838 [ms] (mean)
Time per request: 0.195 [ms] (mean, across all concurrent requests)
Transfer rate: 226.83 [Kbytes/sec] received
Node.js
Concurrency Level: 5000
Time taken for tests: 53.565 seconds
Complete requests: 200000
Failed requests: 0
Write errors: 0
Total transferred: 20451000 bytes
HTML transferred: 200500 bytes
Requests per second: 3733.75 [#/sec] (mean)
Time per request: 1339.136 [ms] (mean)
Time per request: 0.268 [ms] (mean, across all concurrent requests)
Transfer rate: 372.85 [Kbytes/sec] received
Server side timer
In the script below, we import the timer module and make the timer run every 1 second, after the counter hits 3, we stop the timer.
<?php $system = new \Phastlight\System(); $timer = $system->import("timer"); $count = 0; $intervalId = $timer->setInterval(function($word) use (&$count, &$intervalId, $timer){ $count ++; if($count <=3){ echo $count.":".$word."\n"; } else{ $timer->clearInterval($intervalId); } }, 1000, "world");
console log like javascript
Phastlight can do console logging like javascript
<?php $system = new \Phastlight\System(); $console = $system->import("console"); $console->log("a message log to the console");
Process next tick
We can distribute some heavy tasks into every "tick" of the server and make it non-blocking for other tasks.
In the script below, we do sum from 1 to 100 keeping track of the counter value, we distribute the sum operation into every "tick"
<?php $system = new \Phastlight\System(); $console = $system->import("console"); $process = $system->import("process"); $count = 0; $sum = 0; $system->method("sumFromOneToOneHundred", function() use ($system, &$count, &$sum){ $console = $system->import("console"); //use the console module $count ++; if($count <= 100){ $sum += $count; $process = $system->import("process"); //use the process module $process->nextTick(array($system,"sumFromOneToOneHundred")); } else{ $console->log("Sum is $sum"); } }); $system->sumFromOneToOneHundred(); $console->log("Start Computing Sum From 1 to 100...");
Now in the command line, run phastlight server/server.php, we should see:
Start Computing Sum From 1 to 100...
Sum is 5050
Execute Command In Child Process
Phastlight can create child processes to execute a command.
<?php $system = new \Phastlight\System(); $childProcess = $system->import("child_process"); $childProcess->exec("ls -latr", function($error, $stdout, $stderr){ if($error !== null){ print "error occured\n"; } else{ print $stdout."\n"; } });
File System : reads the contents of a directory in async fashion
The example belows show how to read the content of the current directory in the async fashion
<?php $system = new \Phastlight\System(); $fs = $system->import("fs"); $fs->readDir(".",function($result, $data){ print_r($data); });
File System: create a new file and write content to it
The example below will create a file named "test" and write the string "hello world" in it in async fashion
<?php $system = new \Phastlight\System(); $fs = $system->import("fs"); $fs->open("test", "w", function($fd) use ($fs) { $fs->write($fd, "hello world", 0, function(){ echo "done!\n"; }); });
File System: on each http request, append a message to a file named "weblog" in async fashion
The example below shows how to log a message into weblog in async fashion when there is a http request comes in
<?php $system = new \Phastlight\System(); $console = $system->import("console"); $http = $system->import("http"); $fs = $system->import("fs"); $http->createServer(function($req, $res) use ($fs) { $fs->open("weblog", "a", function($fd) use ($fs) { $time_string = microtime(true); $msg = "request coming in at $time_string\n"; $fs->write($fd, $msg, null, function($fd) use ($fs) { //when the position is null, we append the message after the current position $fs->close($fd, function(){ }); }); }); $res->writeHead(200, array('Content-Type' => 'text/plain')); $res->end("hello, you are connected"); })->listen(1337, '127.0.0.1'); $console->log('Server running at http://127.0.0.1:1337/');
Rename file asynchronously
In the example below, we rename a file named "test" in the current directory to "test2"
<?php $system = new \Phastlight\System(); $fs = $system->import("fs"); $console = $system->import("console"); $fs->rename("test","test2",function($result) use ($console){ if($result == 0){ $console->log("rename ok!"); } });
Remove file asynchronously
In the example below, we remove a filed named "test" in the current directory
<?php $system = new \Phastlight\System(); $fs = $system->import("fs"); $fs->unlink("test",function($result){ if($result == 0){ echo "File test is successfully removed.\n"; } });
Get file stat asynchronously
In the example below, we will monitor the php script itself and see its information in async fashion
<?php $system = new \Phastlight\System(); $fs = $system->import("fs"); $fs->lstat(__FILE__, function($result, $data){ if($result == 0){ print_r($data); } });
TCP Server
Below is an example to create a TCP server using the network module
<?php $system = new \Phastlight\System(); $net = $system->import("net"); $net->createTCPServer(function($socket){ $output = "<h1>hello jim</h1>"; $buffer = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n$output"; $socket->end($buffer); })->listen(array( 'port' => 8888, 'host' => '127.0.0.1' ));
TCP Connection
Below is an example to show how to create a tcp connection. We first create a tcp server listening to port 8888 in 127.0.0.1 When a tcp connection is created and starts writing to the server, we then can see what is coming back from the server.
<?php $system = new \Phastlight\System(); $net = $system->import("net"); $net->createTCPServer(function($client){ $output = "<h1>hello jim</h1>"; $buffer = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n$output"; $client->end($buffer); })->listen(array( 'port' => 8888, 'host' => '127.0.0.1' )); $client = $net->connect(array('host' => '127.0.0.1', 'port' => 8888), function() use (&$client){ $client->write('world!\r\n'); $client->end(); }); $client->on('data', function($data){ print $data; });
Operating System Information
Phastlight has the os module to return some data related to the operating system, like cpu and memory information
<?php /** * OS Information in phastlight */ $system = new \Phastlight\System(); $os = $system->import("os"); $console = $system->import("console"); $util= $system->import("util"); $console->log($os->getFreeMemoryInfo()); $console->log($os->getTotalMemoryInfo()); $console->log($util->inspect($os->getCPUInfo()));
Handle multiple tasks in one single event loop
Using process next tick technique, we can perform mult-tasking in one single event loop.
In the script below, we perform a heavy task for suming 1 to 1 million, while also setting up a http server listening to port 1337
<?php $system = new \Phastlight\System(); $console = $system->import("console"); $process = $system->import("process"); $count = 0; $sum = 0; $n = 1000000; $system->method("heavySum", function() use ($system, &$count, &$sum, $n){ $console = $system->import("console"); //use the console module $count ++; if($count <= $n){ $sum += $count; $process = $system->import("process"); //use the process module $process->nextTick(array($system,"heavySum")); } else{ $console->log("Sum is $sum"); } }); $system->heavySum(); $console->log("Start Computing Sum From 1 to $n..."); $http = $system->import("http"); $http->createServer(function($req, $res){ $res->writeHead(200, array('Content-Type' => 'text/plain')); $res->end("Requet path is ".$req->getURL()); })->listen(1337, '127.0.0.1'); $console->log('Server running at http://127.0.0.1:1337/');
Integrating phastlight with Phalcon PHP Framework Routing Component
Phalcon is a web framework delivered as a C extension providing high performance and low resource consumption, the example below shows a basic micro framework integrating phastlight with Phalcon's routing component. The benchmark is quite good, benchark on ab -n 200000 -c 5000 shows 4593.84 requests per second in centos 6 server with 512MB memory.
<?php class ClosureRouter extends \Phalcon_Router_Regex { private $routes; public function addRoute($route, $closure) { $this->routes[$route] = $closure; $routeComps = explode("/", $route); $routeCompsCount = count($routeComps); $params = array('_closure' => $closure); if($routeCompsCount > 0){ for($k = 1; $k < $routeCompsCount; $k++){ if($routeComps[$k][0] == ":"){ $name = $routeComps[$k]; $name[0] = ""; $name = trim($name); $params[$name] = $k; $routeComps[$k] = "([a-zA-Z0-9_-].+)"; //include -,_ and . } } } $route = implode("/", $routeComps); $this->add($route, $params); } } $router = new \ClosureRouter(); $router->addRoute('/news/:year/:month/:day', function($req, $res, $params){ return json_encode($params); }); $front = \Phalcon_Controller_Front::getInstance(); $front->setRouter($router); ///////////////////////////// Start The Server /////////////////////////////// $system = new \Phastlight\System(); $console = $system->import("console"); $http = $system->import("http"); $http->createServer(function($req, $res) use (&$router, &$front) { $res->writeHead(200, array('Content-Type' => 'application/json')); $_GET['_url'] = $req->getURL(); $router->handle(); $params = $router->getParams(); $content = ""; $route = $router->getCurrentRoute(); $closure = $route['paths']['_closure']; unset($params['_closure']); $content = $closure($req, $res, $params); $res->end($content); })->listen(8000, '127.0.0.1'); $console->log('Server running at http://127.0.0.1:8000/');
Output HTML with Symfony2 HTTP Foundation component
The following example shows how to use Symfony2 HTTP Foundation component and phastlight to output HTML
The benchmark is not bad, ab -n 10000 -c 500 shows 3245.07 reqs/second in centos 6 server with 512MB memory.
<?php use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; $system = new \Phastlight\System(); $console = $system->import("console"); $http = $system->import("http"); $http->createServer(function($req, $res){ $res->writeHead(200, array('Content-Type' => 'text/html')); $request = Request::createFromGlobals(); $response = Response::create("<h1>Hello World</h1>"); $res->end($response->getContent()); })->listen(1337, '127.0.0.1'); $console->log('Server running at http://127.0.0.1:1337/');
Simple asynchronous MYSQL query through dbslayer
DBSlayer is a lightweight database abstraction layer allowing sql queries to MYSQL database through REST and JSON. With the net module in phastlight, we can now perform sql queries asynchronously over TCP through DBSlayer. Assuming DBSlayer is running at host 127.0.0.1 and port 9090, the example below shows how to get the current database time in mysql asynchronously.
<?php $system = new \Phastlight\System(); $net = $system->import("net"); $client = $net->connect(array('host' => '127.0.0.1', 'port' => 9090), function() use (&$client) { $client->on('data', function($data) use (&$client) { //we can now see the details of the data print_r($data); }); $crlf = "\r\n"; $sql = "SELECT NOW()"; $sqlEncodedObject = urlencode(json_encode(array("SQL" => $sql))); $msg = "GET /db?".$sqlEncodedObject." HTTP/1.1".$crlf."Accept:application/json".$crlf.$crlf; $client->write($msg); });
Asynchronous Memcache Get and Set
With the net module in phastlight, we can now do some interesting things with memcache over TCP. The example below shows how to set a key in memcache asynchronously over TCP, and then when the key is successfully stored, we read the details of the key.
For more details on the memcached tcp protocol, please click here, we can now create some async memcache libraries just by following the protocol.
<?php $system = new \Phastlight\System(); $net = $system->import("net"); $client = $net->connect(array('host' => '127.0.0.1', 'port' => 11211), function() use (&$client){ $key = "samplekey"; $duration = 3600; //duration of 1 hour $value = 250; $valueLength = strlen($value); $crlf = "\r\n"; $client->on('data', function($data) use ($key, $crlf, &$client){ //according to the protocol, when server returns "STORED\r\n", we know that the key is successfully stored if($data == "STORED$crlf"){ $client->removeAllListeners('data'); //we unbind the previous 'data' event listeners //we know re-add a new listener for event 'data' $client->on('data', function($data) use(&$client){ print $data; //here we can see the details of the key that we just stored $client->end(); //we now close the connection }); $client->write("get $key$crlf"); //getting the memcache key is another simple command over tcp } }); $client->write("set $key 0 $duration $valueLength$crlf$value$crlf"); //setting the memcache key is a simple command over tcp });
Asynchronous Redis Get and Set
With the net module in phastlight, we can now do some interesting things with Redis over TCP. The example below shows how to set a key in redis asynchronously over TCP, and then when the key is successfully stored, we read the details of the key.
For more details on the redis tcp protocol, please click here, we can now create some async redis libraries just by following the protocol.
<?php $system = new \Phastlight\System(); $net = $system->import("net"); $client = $net->connect(array('host' => '127.0.0.1', 'port' => 6379), function() use (&$client){ $crlf = "\r\n"; $client->on('data', function($data) use ($key, $crlf, &$client){ if($data == "+OK$crlf"){ //from the protocol, we know that now the key is successfully stored $client->removeAllListeners('data'); $client->on('data', function($data) use(&$client){ print $data; //here we can see the details of the key that we just stored $client->end(); //close the connection }); $client->write("GET mykey$crlf"); //we can get the key in one simple command over tcp } }); $client->write("SET mykey myvalue234$crlf"); //we can set the value of a key in one simple command over tcp });
Working Forking
Phastlight now has a simple cluster module to allow forking worker processes
In the example below, we fork 3 worker processes, and we detect when it is closed.
<?php $system = new \Phastlight\System(); $cluster = $system->import("cluster"); $cluster->fork(function($worker) { echo "This is worker ".$worker->getProcess()->getPid()."\n"; $worker->on("close", function($signal) use (&$worker) { echo "Worker ".$worker->getProcess()->getPid()." is closed now with signal: $signal\n"; }); for(;;) { $worker->kill(); } }, 3); //we fork 3 workers $workers = $cluster->getAllWorkers(); foreach($workers as $pid => $woker) { print "forked worker with pid: ".$pid."\n"; }