在短短几分钟内用冰柱构建超快速PHP服务器

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.

对于PHP开发人员,基于事件的编程是一个奇怪的话题。 用程序语言 事件只不过是函数调用。 事件之间没有任何React,所有有意义的代码仍在阻塞。

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.

像JavaScript这样的语言向我们展示了如果以事件循环为中心,PHP将会是什么样。 一些人已经掌握了这些见解,并将其编码为事件循环和HTTP服务器。 今天,我们将用PHP创建一个HTTP服务器。 我们将其连接到Apache,以快速提供静态文件。 其他所有内容都将通过基于IciclePHP HTTP服务器传递。

Icicles illustration

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

您可以在https://github.com/sitepoint-editors/icicle-http-server上找到示例代码

配置Apache (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:

当浏览器请求现有文件时,最好在不涉及PHP解释器的情况下提供它们。 Apache在处理这些文件方面既快速又高效,因此我们将其配置为处理所有静态文件请求:

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.

这些mod_rewrite指令告诉Apache mod_rewrite丢失文件的请求发送到另一个端口。 换句话说:当浏览器请求example.com/robots.txt ,Apache将首先查看该文件是否存在。 如果是这样,Apache将返回它,而无需增加PHP解释器。 否则,Apache会将请求发送到http://example.com:9001/robots.txt

一个简单的HTTP服务器 (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:

冰柱附带事件循环。 我们可以围绕它包装一个HTTP服务器,以便以事件的形式向我们提出新的请求。 这个过程的大部分内容都是抽象的,但是无论如何,让我们看一个例子。 首先,让我们下载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.

这为我安装了0.1.0版本。 如果您无法使用我的示例,则可能有较新的版本。 尝试安装此特定版本。

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:

这是一个可以创建的最基本的HTTP服务器。 它接收所有请求并回复“ hello world”。 为了使其更有用,我们需要结合某种路由器。 League\Route似乎是一个不错的候选人:

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.

我们已经加入了League\Route ,并启用了UriStrategy 。 它是确定哪个路由属于哪个请求的四种不同方法之一。 League\Route通常与Symfony请求和响应一起使用。 我们需要将请求方法和路径/目标提供给调度程序。

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?

如果路由匹配,我们将得到一个Symfony \ HttpFoundation响应,因此我们可以使用getContent获得正文内容。 如果没有匹配的路由,或者没有匹配的路由的允许方法,那么我们将返回相应的错误。 那么, routes.php什么样的呢?

$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.

字符串适合简单页面。 但是,当我们开始构建更复杂的应用程序时,我们可能需要一个更好的工具。 我们如何使用League\Plates呢? 它是一个模板引擎,在普通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.

这是来自templates/layout.php

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

e方法转义HTML实体。 section方法将是呈现页面内容的位置:

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

The above is from templates/home.php.

上面是来自templates/home.php

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

最后,我们更改/home路由以返回渲染的模板而不是简单的字符串:

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

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

The above is from routes.php.

以上是来自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.

上面是来自helpers.php

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

…,如果我们包括了(或将其添加到Composer自动加载定义中),那么/home路由将变为:

$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…

使用Icicle\Http和几个League库,我们设法将一个合理的应用程序框架拼凑在一起。 希望这向您显示了Apache(或Nginx)之外的生活是可能的。 这仅仅是个开始……

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

我能够获得以下统计信息(在13英寸Macbook Pro Retina 2014上运行Chrome和iTunes时):

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!

我想这些数字会随着您添加更多复杂性而波动,并且与流行框架相比并没有任何意义。 关键是,这个基于事件的小型HTTP服务器可以在一分钟内处理11.1k请求,而不会出现故障。 如果您要小心避免内存泄漏,则可以由此创建一个稳定的服务器!

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:

编辑:冰柱的作者亚伦·皮奥特洛夫斯基(Aaron Piotrowski)补充了一些有关为何上述基准可能存在缺陷的额外信息(也在评论中进行了讨论)。 这是他(令人印象深刻)的结果。 他说:

“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:

“使用命令ab -n 10000 -c 100 http://127.0.0.1:9001/home我能够获得以下统计信息(在3.4 GHz i7 iMac上运行iTunes,Chrome和其他几个程序时) :”

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!

我想这些数字会随着您添加更多复杂性而波动,并且与流行框架相比并没有任何意义。 关键是,这种基于事件的小型HTTP服务器可以在一分钟内处理超过100,000个请求,而不会出现故障。 如果您要小心避免内存泄漏,则可以由此创建一个稳定的服务器!

Thanks for chiming in, Aaron!

感谢您的来信,亚伦!

翻译自: https://www.sitepoint.com/build-a-superfast-php-server-in-minutes-with-icicle/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值