PHP
Article

Build a Superfast PHP Server in Minutes with Icicle

By Christopher Pitt

Event-based programming is a strange topic for PHP developers. In a language as procedural; events are little more than function calls. Nothing happens between events, and all meaningful code is still blocking.

Languages like JavaScript show us what PHP could be like if event loops were at the center. Some folks have taken these insights and coded them into event loops and HTTP servers. Today we’re going to create an HTTP server, in PHP. We’ll connect it to Apache to serve static files quickly. Everything else will pass through our PHP HTTP server, based on Icicle.

Icicles illustration

You can find the example code at https://github.com/sitepoint-editors/icicle-http-server

Configuring Apache

When browsers request existing files, it’s best to serve them without involving the PHP interpreter. Apache is fast and efficient at serving these files, so let’s configure it to handle all static file requests:

RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*) http://%{SERVER_NAME}:9001%{REQUEST_URI} [P]

You can place this code inside a virtual host entry.

These mod_rewrite directives tell Apache to send requests to missing files to a different port. In other words: when a browser requests example.com/robots.txt, Apache will first look to see if the file exists. If so, Apache will return it without spinning up the PHP interpreter. If not, Apache will send the request to http://example.com:9001/robots.txt.

A Simple HTTP Server

Icicle ships with an event loop. We can wrap an HTTP server around that, so new requests come to us in the form of events. Much of this process is abstracted away, but let’s take a look at an example anyway. To begin, let’s download icicleio/http:

composer require icicleio/http

This installed version 0.1.0 for me. If you’re having trouble getting my examples to work, you may have a newer version. Try installing this specific version.

This will allow you to run the following code:

// server.php

require __DIR__ . "/vendor/autoload.php";

use Icicle\Http\Message\RequestInterface;
use Icicle\Http\Message\Response;
use Icicle\Http\Server\Server;
use Icicle\Loop;
use Icicle\Socket\Client\ClientInterface;

$server = new Server(
    function(RequestInterface $request, ClientInterface $client) {
        $response = new Response(200);
        $response = $response->withHeader(
            "Content-Type", "text/plain"
        );

        yield $response->getBody()->end("hello world");
        yield $response;
    }
);

$server->listen(9001);

Loop\run();

Handling Different Routes

This is the most basic HTTP server one can create. It receives all requests and replies “hello world”. To make it more useful, we would need to incorporate some kind of router. League\Route seems like a good candidate:

composer require league/route

Now we can split up individual requests, and send more meaningful responses:

// server.php

use League\Route\Http\Exception\MethodNotAllowedException;
use League\Route\Http\Exception\NotFoundException;
use League\Route\RouteCollection;
use League\Route\Strategy\UriStrategy;

$server = new Server(
    function(RequestInterface $request, ClientInterface $client) {
        $router = new RouteCollection();
        $router->setStrategy(new UriStrategy());

        require __DIR__ . "/routes.php";

        $dispatcher = $router->getDispatcher();

        try {
            $result = $dispatcher->dispatch(
                $request->getMethod(),
                $request->getRequestTarget()
            );

            $status = 200;
            $content = $result->getContent();
        } catch (NotFoundException $exception) {
            $status = 404;
            $content = "not found";
        } catch (MethodNotAllowedException $exception) {
            $status = 405;
            $content = "method not allowed";
        }

        $response = new Response($status);
        $response = $response->withHeader(
            "Content-Type", "text/html"
        );

        yield $response->getBody()->end($content);
        yield $response;
    }
);

We’ve pulled in League\Route, and enabled the UriStrategy. It’s one of four different methods for determining which route belongs to which request. League\Route is often used alongside Symfony requests and responses. We’ll need to feed the request method and path/target to the dispatcher.

If a route is matched, we get a Symfony\HttpFoundation Response, so we get the body content with getContent. If there isn’t a matching route, or an allowed method for a matching route, then we return the appropriate errors. So what does routes.php look like?

$router->addRoute("GET", "/home", function() {
    return "hello world";
});

Rendering Complex Views

Strings are fine for simple pages. But when we start to build more complex applications, we may need a better tool. How about we use League\Plates? It’s a template engine that adds things like layouts and template inheritance on top of plain PHP.

composer require league/plates

Then we’ll create a layout template, for all the views in our site to inherit from:

<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title>
            <?php print $this->e($title); ?>
        </title>
    </head>
    <body>
        <?php print $this->section("content"); ?>
    </body>
</html>

This is from templates/layout.php.

The e method escapes HTML entities. The section method will be where the page content gets rendered:

<?php $this->layout("layout", ["title" => "Home"]); ?>
<p>
    Hello, <?php print $this->e($name); ?>
</p>

The above is from templates/home.php.

Finally, we change our /home route to return a rendered template instead of a simple string:

$router->addRoute("GET", "/home", function() {
    $engine = new League\Plates\Engine(
        __DIR__ . "/templates"
    );

    return $engine->render("home", [
        "name" => "Chris"
    ]);
});

The above is from routes.php.

Of course, we could create a shortcut function, to save us having to create the engine each time:

function view($name, array $data = []) {
    static $engine = null;

    if ($engine === null) {
        $engine = new League\Plates\Engine(
            __DIR__ . "/templates"
        );
    }

    return $engine->render($name, $data);
}

The above is from helpers.php.

… and if we include that (or add it to the Composer autoload definition), then our /home route becomes:

$router->addRoute("GET", "/home", function() {
    return view("home", [
        "name" => "Chris"
    ]);
});

Conclusion

We’ve managed to cobble together a reasonable application framework, using Icicle\Http and a couple of League libraries. Hopefully this has shown you that life outside of Apache (or Nginx) is possible. And that’s just the beginning…

I was able to get the following stats (while running Chrome and iTunes, on a 13” Macbook Pro Retina 2014):

Concurrency Level:    100
Time taken for tests: 60.003 seconds
Complete requests:    11108
Failed requests:      0
Total transferred:    3810044 bytes
HTML transferred:     2243816 bytes
Requests per second:  185.12 [#/sec] (mean)
Time per request:     540.182 [ms] (mean)
Time per request:     5.402 [ms] (mean, across all concurrent requests)
Transfer rate:        62.01 [Kbytes/sec] received

I imagine those figures will fluctuate as you add more complexity, and they don’t mean anything when compared to popular frameworks. The point is that this little event-based HTTP server can serve 11.1k requests in a minute, without failures. If you’re careful to avoid memory leaks, you can create a stable server out of this!

That’s exciting, isn’t it?

What are your thoughts about this setup? Have you played with Icicle yet? Let us know!


Edit: Aaron Piotrowski, the author of Icicle chimed in with some extra info on why the benchmark above may have been flawed (also discussed in the comments). Here are his (much more impressive) results. He says:

“I was able to get the following stats (while running iTunes, Chrome, and several other programs on a 3.4 GHz i7 iMac) using the command ab -n 10000 -c 100 http://127.0.0.1:9001/home:

Concurrency Level:      100
Time taken for tests:   5.662 seconds
Complete requests:      10000
Failed requests:        0
Total transferred:      2650000 bytes
HTML transferred:       2020000 bytes
Requests per second:    1766.04 [#/sec] (mean)
Time per request:       56.624 [ms] (mean)
Time per request:       0.566 [ms] (mean, across all concurrent requests)
Transfer rate:          457.03 [Kbytes/sec] received

I imagine those figures will fluctuate as you add more complexity, and they don’t mean anything when compared to popular frameworks. The point is that this little event-based HTTP server could potentially serve over 100,000 requests in a minute, without failures. If you’re careful to avoid memory leaks, you can create a stable server out of this!

Thanks for chiming in, Aaron!

  • http://mrxxiv.com/ Terrence Campbell

    The moment you said Apache, I don’t know about “super fast”, but this is a great example.

    • http://stephencoakley.com/ Stephen Coakley

      Yeah, Apache isn’t the fastest server around, but Icicle itself should serve the dynamic pages “super fast” by itself. See @trowski:disqus ‘s comments here for a better explanation.

    • Chris

      Yeah, it’s definitely not the only (or arguably the fastest) way to serve static files. I wanted to illustrate how static files could be served without blocking other things in the Icicle server process. Substitute Apache with Nginx or Lighttpd or whatever…

      There’s been a lot of interesting work, recently, for doing non-blocking file stuff with Icicle. It’s not far enough along for me to write about it yet. When it is, that will be far better than splitting traffic between an external server and Icicle.

    • http://www.kelunik.com/ kelunik

      Indeed, this isn’t a perfect setup. In production you probably want to have that server as pure application server and serve your static files using a CDN. Setting it up that way can have the advantage that you use this for development purposes and have your static files and application logic still separated. The title might be misleading, but it’s really an approach that can lead to pretty good results in combination with the right async libraries, which are needed for it to be non-blocking.

  • Radek Dvořák

    Hi Christopher,

    thanks for the article. I think that event loops in PHP are a really interesting topic. I look forward to your further articles.

    It will be great if you elaborate more about limits of event-loops and share your experience how to work around them.

  • acerb

    Nice article.

    You could also combine the power of event loops and non-blocking i/o by using reactphp (http://www.reactphp.org). There are nice wrap ups out there using it to create an HTTP server which can go up to 2000 requets per sec, like https://github.com/reactphp/http

    Slim and Lumen are also good alternatives, as Hari pointed out.

  • https://icicle.io Aaron Piotrowski

    Unfortunately Apache (and potentially Xdebug) seems to have hindered performance significantly. Icicle’s performance should easily match that of ReactPHP.

    Icicle eliminates the need for writing callbacks typically associated with event-based programming by using coroutines built with promises. I encourage you to read the other comments I made and read Icicle’s documentation to understand how it differs from ReactPHP.

  • http://stephencoakley.com/ Stephen Coakley

    The web server handling the PHP page requests in the article is written in PHP itself. That is, a web server written in PHP powered by Icicle. The only non-PHP component in the example is Apache, which is serving static files. If you didn’t need to serve static files, you could run the above completely without Apache.

    As an aside, it is difficult for web frameworks to claim a particular “requests per second” speed, when it depends a lot on what web server you are using as well. The same framework may perform faster behind Icicle and slower behind Apache, for example. Comparing requests per second from two different sources doesn’t make complete sense then.

  • Chris

    ReactPHP does exactly the same thing (non-blocking IO) as Icicle in this context. It just does it in a different way. Try to do routing and serve static files with a web server (with the same version of PHP and installed extensions as I have) and the performance will be much the same.

    The purpose (as I clearly stated) of the benchmark was not how many requests it servers per second compared to other libraries or frameworks, but that the memory is manageable and there are no errors. That it is stable enough to run a real-world application on. As stable as ReactPHP.

    So use ReactPHP if you’re more comfortable with it. Or use Icicle if you prefer. But probably don’t assert that one is better than the other in this context, without proving and/or motivating that statement… :)

    • acerb

      Hi Chris,

      Thanks for this clear answer, and apologies for my rather flaming comment (I’ll remove the unuseful last part).
      I think the article might be not clear about the way Icicle is working and that it has non-blocking ability. And the number of requests per seconds you specify should probably be removed or it should be specified that it’s hindered by xdebug and Apache.

      I hope this will help other readers. I encourage them to read Icicle’s documentation too.

      PS: I edited my first comment to make it more constructive… :)

  • Chris

    Thanks for pointing this out. The environment was not optimised for performance review, but then the purpose was to show that memory was manageable and there were no errors over a large number of served requests. Having the fastest server is largely irrelevant if if fails for 50% of the requests or has unmanageable memory leaks.

    Thankfully I found Icicle to be stable and memory efficient throughout.

    • http://harikt.com/ Hari K T

      The problem was with the title “Build a superfast PHP server” .

      • http://careersreport.com Katherine Anthony

        Check out a &great way how you can earn a lot of extra $ by finishing basic jobs online from home for few hrs /daily VISIT MY-DISQUS-PROFILE to find out more

  • http://harikt.com/ Hari K T

    Thank you @trowski:disqus for the explanation. I will see if I can find sometime to check out the documentation and play with icicle. I don’t know if PHP server is good enough to go in production for it is simply a light weight package and the core developers itself warn not to use it on production.

  • Rafał

    I’d just like to add that for the [P] flag in the vhost config to work you might need to enable proxy in Apache:

    sudo a2enmod proxy
    sudo a2enmod proxy_http

    Took me a while to figure that out. You can do the tutorial without Apache of course, but it’s nice to be able to configure transparent forwarding from Apache to Icicle just like you did.

    • Chris

      Good to note! It must have been installed/enabled before I started…

  • Ri Sto

    None-blocking event servers are great but please notice that the persistence of static properties in this solution is different from standard php behavior. Static class property will live between requests as full class unloading does not happen.This is why the performance is so good. Once you load the class it will stay in memory till the server gets restarted. You can unset the object but its static properties will stay in memory so when the next request arrives you will get the old values. The side effect might be for example that if the component you are using holds login information in static properties it will pass this information from request to request regardless of the logged user. I may be wrong but the same may happen to session.

    • http://www.kelunik.com/ kelunik

      This is a pretty good observation and you’re right, you have to be careful with static and global state. The same may happen to every component, not only login information or session data, that’s another reason why static and global are evil. It does not only apply to non-blocking servers, it applies to everything long-running, could also be a worker that persists its environment between execution of its tasks.

      • Ri Sto

        Unfortunately there are a lot libraries build with standard PHP static properties in packagist that can not be used with ReactPHP or Icicle. This is pity because async event-loop is a great concept. Maybe some day PHP developers will came with the idea how to reset all static properties to its inital state :)

        • http://www.kelunik.com/ kelunik

          It’s not only static properties, the bigger issue is blocking I/O.

  • https://snetts.com/blog/ Victor Okech

    Amazing, this would work great for my next project. I am strained on server resources so I need a mobile app back-end that is fast and highly scalable. Chris great post…

  • Warthog

    now do it with nginx. apache is so 90s.

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in PHP, once a week, for free.