用 Swoole HTTP 服务器运行 Lumen 项目的实现方法 | Laravel China 社区 - 高品质的 Laravel 和 PHP 开发者社区

[用 Swoole HTTP 服务器运行 Lumen 项目的实现方法 | Laravel China 社区 - 高品质的 Laravel 和 PHP 开发者社区]

原文链接:https://blog.breezelin.cn/swoole-lumen-run-lumen-with-swoole.html

LNMP虽是传统的Web应用架构组合,无奈NginX + PHP-FPM的搭配运行效率实在太低;而Swoole HTTP服务器具有NginX级别的性能,且本身嵌入在PHP中,完全可以替代NginX + PHP-FPM。于是一直在探索用Swoole HTTP服务器运行传统PHP应用的途径。这里主要介绍一下Swoole HTTP服务器运行Lumen项目的实现方法:

Swoole HTTP服务器

Swoole1.7.7增加了内置HTTP服务器的支持,使用了跟NginX一样的IO多路复用机制epoll,可以达到NginX级别的性能,并且只需几行代码即可写出一个异步非阻塞多进程的HTTP服务器(epoll属于同步非阻塞,这里说“异步非阻塞”主要是因为“多进程”,单个进程内部依然是同步非阻塞)。

开启一个Swoole HTTP服务器的面向对象编程代码样板大致如下(具体参考Swoole官方文档HttpServer):

<?php

class Service {
    public $app;
    public $server;

    public function __construct($host, $port) {
        $this->server = new \swoole_http_server($host, $port);
    }

    public function start()
    {
        // $this->server->on('start', array($this, 'onStart'));
        // $this->server->on('shutdown', array($this, 'onShutdown'));
        // $this->server->on('workerStop', array($this, 'onWorkerStop'));
        $this->server->on('workerStart', array($this, 'onWorkerStart'));
        $this->server->on('request', array($this, 'onRequest'));
        $this->server->start();
    }

    public function onWorkerStart($serv, $worker_id) {
        // 应用初始化
        $this->app = 1;
    }

    public function onRequest(\swoole_http_request $request,\swoole_http_response $response) {
        $app = $this->app;
        // 处理用户请求
        $get = json_encode($request->get);
        // 响应用户请求
        $response->end("App is {$app}. Get {$get}");
    }
}

$s = new Service("127.0.0.1", 9080);
$s->start();

直接用php命令执行以上代码,浏览器访问http://127.0.0.1:9080,会得到:

App is 1. Get null

浏览器访问http://127.0.0.1:9080/?user=guest,会得到:

App is 1. Get {"user":"guest"}

就是这么简单。

加载Lumen项目

Lumen项目的入口文件是项目路径下的public/index.php,里面只有简单的两行代码:

<?php
$app = require __DIR__.'/../bootstrap/app.php';
$app->run();

第一行代码就是加载整个Lumen框架代码,第二行代码则是处理请求,生成响应。

所以,在Swoole HTTP服务器中加载Lumen项目,也很简单,只需修改onWorkerStart方法:

<?php
    public function onWorkerStart($serv, $worker_id) {
        // 应用初始化
        $this->app = require '/THE/FULL/PATH/TO/bootstrap/app.php';
    }

处理用户请求

虽然可以加载Lumen框架,但是要怎样才能用Lumen框架来处理用户请求呢?

由于Lumen框架的解耦程度非常高,我们可以很轻松地将swoole_http_request对象$request,转换成Lumen框架熟悉的Illuminate\Http\Request对象,这样Lumen框架就能处理用户请求了。
这里实现一个parseRequest方法,接收swoole_http_request类参数,返回\Illuminate\Http\Request类结果:

<?php
    protected function parseRequest(\swoole_http_request $request)
    {
        $get     = isset($request->get) ? $request->get : array();
        $post    = isset($request->post) ? $request->post : array();
        $cookie  = isset($request->cookie) ? $request->cookie : array();
        $server  = isset($request->server) ? $request->server : array();
        $header  = isset($request->header) ? $request->header : array();
        $files   = isset($request->files) ? $request->files : array();
        $fastcgi = array();

        $new_server = array();
        foreach ($server as $key => $value) {
            $new_server[strtoupper($key)] = $value;
        }
        foreach ($header as $key => $value) {
            $new_server['HTTP_' . strtoupper($key)] = $value;
        }

        $content = $request->rawContent() ?: null;

        $http_request = new \Illuminate\Http\Request($get, $post, $fastcgi, $cookie, $files, $new_server, $content);

        return $http_request;
    }

生成请求响应

Lumen框架处理用户请求,十分方便,只需调用应用的dispatch方法即可。不过dispatch方法返回结果一般是Symfony\Component\HttpFoundation\Response对象,需要做一些转化才能交由swoole_http_response对象给用户输出响应。
这里实现一个makeResponse方法:

<?php
    protected function parseRequest(\swoole_http_response $request, \Symfony\Component\HttpFoundation\Response $http_response)
    {
        // status
        $response->status($http_response->getStatusCode());
        // headers
        foreach ($http_response->headers->allPreserveCase() as $name => $values) {
            foreach ($values as $value) {
                $response->header($name, $value);
            }
        }
        // cookies
        foreach ($http_response->headers->getCookies() as $cookie) {
            $response->rawcookie(
                $cookie->getName(),
                $cookie->getValue(),
                $cookie->getExpiresTime(),
                $cookie->getPath(),
                $cookie->getDomain(),
                $cookie->isSecure(),
                $cookie->isHttpOnly()
            );
        }
        // content
        $content = $http_response->getContent();
        // send content
        $response->end($content);
    }

有了parseRequest方法和makeResponse方法,要实现以Swoole HTTP服务器运行Lumen项目来处理用户请求只要几行代码。onRequest方法这样修改:

<?php
    public function onRequest(\swoole_http_request $request,\swoole_http_response $response) {
        $app = $this->app;
        // 处理用户请求
        $http_request = $this->parseRequest($request);
        $http_response = $app->dispatch($http_request);
        // 响应用户请求
        $this->makeResponse($response, $http_response);
    }

最后

就这样,不需要修改Lumen项目原本任何代码,就能让其运行在Swoole HTTP服务器上。
当然,用户请求各种各样,有请求文件上传,有请求静态资源等等;响应类型也是各种各样,有Gzip压缩,有JSON格式,有设置Cookie等待。要实现一个健全的Swoole+Lumen网关服务,以上代码还须更多优化,具体可以参考Service.php

按照以上思路,要实现以Swoole HTTP服务器运行Laravel项目也不难,不过,为了效率选择Swoole,自然会为了效率选择Lumen而不是Laravel。

下一篇文章介绍如何在Lumen中使用异步MySQL客户端,减少IO阻塞。

BL


Original url: Access

Created at: 2018-10-10 17:59:46

Category: default

Tags: none

请先后发表评论
  • 最新评论
  • 总共0条评论