charlesportwoodii/yii2-psr7-bridge

为 Yii2 服务的 PSR7 桥接器

0.0.2 2023-01-17 15:31 UTC

This package is auto-updated.

Last update: 2024-09-18 00:25:58 UTC


README

为 Yii2 网络应用提供 PSR-7 桥接器和 PSR-15 适配器。

此桥接器的用例是使 Yii2 能够使用 PSR-7 和 PSR-15 中间件和任务运行器,例如 RoadRunner 和 PHP-PM,而无需对您的应用程序进行(例如,无需修改应用程序中任何对 Yii::$app->requestYii::$app->response 的调用)最小代码更改。

请注意,这目前处于非常原始的 alpha 质量。它“工作”是因为接受 PSR7 请求作为输入,并返回一个有效的 PSR7 响应,该响应通常与您所期望的相符。

但是,缺少功能和功能。许多事情不起作用,其他事情有意外的影响。建议您在此阶段不要使用此软件包。

请参阅 README 文件底部的 当前状态 清单,了解当前已实施的内容以及我们可以使用的帮助。

需要帮助什么?

欢迎贡献者!查看 当前状态 清单,了解仍需实施的事项,帮助添加测试或添加新功能!

安装

此软件包可以通过 Composer 安装。

composer require charlesportwoodii/yii2-psr7-bridge

测试

可以使用 phpunit 运行测试。

./vendor/bin/phpunit

使用方法

由于此软件包的性质,需要对您的应用程序进行一些更改。

分发器

  1. 修改您的网络应用程序配置中的 requestresponse 组件,使其分别成为 yii\Psr7\web\Requestyii\Psr7\web\Response 的实例。
return [
    // Other flags
    'components' => [
        'request' => [
            'class' => \yii\Psr7\web\Request::class,
        ],
        'response' => [
            'class' => \yii\Psr7\web\Response::class
        ],
        // Other components
    ]
];

如果您正在使用自定义 Request 类,只需让它覆盖 yii\Psr7\web\Request 以继承基本功能即可。

  1. 将以下环境变量设置到您的任务运行器。对于 RoadRunner,您的配置可能如下所示
env:
  YII_ALIAS_WEBROOT: /path/to/webroot
  YII_ALIAS_WEB: '127.0.0.1:8080'

必须定义所有环境变量。

  1. 使用与 PSR-15 兼容的分发器运行您的应用程序。

例如,对于 Go/Roadrunner,您可以使用以下组件

#!/usr/bin/env php
<?php

ini_set('display_errors', 'stderr');

// Set your normal YII_ definitions
defined('YII_DEBUG') or define('YII_DEBUG', true);
// Alternatives set this in your rr.yaml file
//defined('YII_DEBUG') or define('YII_DEBUG', \getenv('YII_DEBUG'));

defined('YII_ENV') or define('YII_ENV', 'dev');
// Alternatives set this in your rr.yaml file
//defined('YII_ENV') or define('YII_ENV', \getenv('YII_ENV'));

require __DIR__ . '/../vendor/autoload.php';
require __DIR__ . '/../vendor/yiisoft/yii2/Yii.php';

$worker = Spiral\RoadRunner\Worker::create();
$psrServerFactory = new Laminas\Diactoros\ServerRequestFactory();
$psrStreamFactory = new Laminas\Diactoros\StreamFactory();
$psrUploadFileFactory = new Laminas\Diactoros\UploadedFileFactory();
$psr7 = new Spiral\RoadRunner\Http\PSR7Worker($worker, $psrServerFactory, $psrStreamFactory, $psrUploadFileFactory);

$config = require __DIR__ . '/../config/web.php';

$application = (new \yii\Psr7\web\Application($config));

// Handle each request in a loop
try {
    while ($request = $psr7->waitRequest()) {
        if (($request instanceof Psr\Http\Message\ServerRequestInterface)) {
            try {
                $response = $application->handle($request);
                $psr7->respond($response);
            } catch (\Throwable $e) {
                $psr7->getWorker()->error((string)$e);
            }

            if ($application->clean()) {
                $psr7->getWorker()->stop();
                return;
            }
        }
    }
} catch (\Throwable $e) {
    $psr7->getWorker()->error((string)$e);
}

工作进程崩溃保护

由于每个请求 PHP 的内存使用量都会逐渐增加。这是不可避免的,因为 Yii2 不是为正常的 PHP 调用堆栈(Nginx + PHP-FPM、Apache2 CGI 等)设计的,而是为请求-请求工具(如 RoadRunner)设计的。调用 $application->clean() 将告诉您当前脚本使用是否在 memory_limit ini 设置的 10% 以内。虽然大多数工作进程将处理内存不足崩溃异常,但您可以使用此方法明确告诉当前工作进程停止并重建,以避免由内存不足问题引发的 HTTP 500 错误。这将解决由于框架内存预留而产生的任何意外内存泄漏。

会话

此库完全兼容 yii\web\Session 和派生自它的类,但有几点注意事项。

  1. 应用程序组件在运行时添加以下 session ini 设置。不要覆盖这些设置,因为它们对于 yii\web\Session 是必要的。
ini_set('use_cookies', 'false');
ini_set('use_only_cookies', 'true');
  1. 不要在您的任务进程中访问 $application->getSession()

请求

yii\Psr7\web\Request 定义了 yii\web\Request 的替代品。要访问原始 PSR-7 对象,请调用 Yii::$app->request->getPsr7Request()

响应

yii\Psr7\web\Response 直接扩展自 yii\web\Response。所有功能都通过 yii\Psr7\web\traits\Psr7ResponseTrait 实现,您可以在任何自定义响应类中作为特性使用它。或者,您也可以直接扩展 yii\Psr7\web\Response

错误处理器

yii\Psr7\web\ErrorHandler 实现了与 yii\web\ErrorHandler 兼容的自定义错误处理。 yii\Psr7\web\Application 自动使用此错误处理器。如果您有自定义错误处理器,让它扩展 yii\Psr7\web\ErrorHandler

支持通过 errorAction 的常规功能。Yii2 的标准错误和异常页面应能正常工作。

PSR-7 和 PSR-15 兼容性

\yii\Psr7\web\Application 扩展自 \yii\web\Application 并实现了 PSR-15 的 \Psr\Http\Server\RequestHandlerInterface,提供了完整的 PSR-15 兼容性。

PSR-7

如果您的应用程序不需要 PSR-15 中间件,您可以从应用程序中简单地返回 PSR-7 响应,如下所示

$response = $application->handle($request);
$psr7->respond($response);

此配置中不需要调度器。

带有中间件/工具包的 PSR-15

由于 \yii\Psr7\web\Application 与 PSR-15 中间件兼容,您也可以将其与任何 PSR-15 调度器一起使用。

此库不实现自己的调度器,允许开发人员自由选择用于中间件处理的任何 PSR-15 兼容调度器。

以下是一个使用 middlewares/utils 的示例

$response = \Middlewares\Utils\Dispatcher::run([
    // new Middleware,
    // new NextMiddleware, // and so forth...
    function($request, $next) use ($application) {
        return $application->handle($request);
    }
], $request);

// rr response
$psr7->respond($response);

PSR-15 中间件过滤器

此包还提供通过 yii\base\ActionFilter 扩展按路由处理 PSR-15 兼容中间件的能力。

通过这些方法运行的中间件不是 100% 遵守 PSR-15,因为它们在独立于任何调度器中声明的中间件的自定义沙盒中执行。这些中间件仅在其自己的操作过滤器上下文中运行。例如,middlewares/request-time 中间件将只测量操作过滤器的运行时间,而不是整个请求。如果您需要这些中间件在更高的链中运行,请将它们链接到您的首选调度器,或者考虑使用本地的 Yii2 ActionFilter。

身份验证

如果您的应用程序需要 PSR-15 身份验证中间件(非现有 yii\filters\auth\AuthInterface 类提供的),您可以使用 yii\Psr7\filters\auth\MiddlewareAuth 来处理您的 PSR-15 身份验证中间件。

\yii\Psr7\filters\auth\MiddlewareAuth 将运行身份验证中间件。如果您的中间件返回了一个响应,它将发送那个响应。否则,它将查找您的身份验证中间件指定的 attribute,并使用存储在该属性中的值运行 yii\web\User::loginByAccessToken()

注意您的 yii\web\UserIdentityInterface 应配置为处理您提供的请求属性。由于大多数身份验证中间件导出一个包含用户信息的属性,因此应使用它来与 Yii2 的 IdentityInterface 进行接口交互。

以下是一个使用 middlewares/http-authentication 的简单示例,使用 Middlewares\BasicAuthentication 填充的 username 属性。

public function behaviors()
{
    return \array_merge(parent::behaviors(), [
        [
            'class' => \yii\Psr7\filters\auth\MiddlewareAuth::class,
            'attribute' => 'username',
            'middleware' => (new \Middlewares\BasicAuthentication(
                Yii::$app->user->getUsers() // Assumes your `IdentityInterface` class has a method call `getUsers()` that returns an array of username/password pairs
                /**
                 * Alternatively, just a simple array for you to map back to Yii2
                 *
                 * [
                 *     'username1' => 'password1',
                 *     'username2' => 'password2'
                 * ]
                 */
            ))->attribute('username')
        ]
    ]);
}

注意:此类应与 yii\filters\auth\CompositeAuth 兼容,因为它返回 null。但是,如果返回 HTTP 状态码或消息,yii\web\Response 对象 被填充。如果您需要自定义响应,应扩展此类或手动触发 handleFailure()

其他中间件

yii\Psr7\filters\MiddlewareActionFilter 可以用于处理其他 PSR-15 兼容中间件。列出的每个中间件将按顺序执行,并返回该中间件的有效响应。

例如:使用 middlewares/client-ipmiddlewares/uuid 返回请求的响应时间和一个 UUID。

public function behaviors()
{
    return \array_merge(parent::behaviors(), [
        [
            'class' => \yii\Psr7\filters\MiddlewareActionFilter::class,
            'middlewares' => [
                // Yii::$app->request->getAttribute('client-ip') will return the client IP
                new \Middlewares\ClientIp,
                // Yii::$app->response->headers['X-Uuid'] will be set
                new \Middlewares\Uuid,
            ]
        ]
    ]);
}

中间件处理也支持 PSR-15 兼容的闭包。

public function behaviors()
{
    return \array_merge(parent::behaviors(), [
        [
            'class' => \yii\Psr7\filters\MiddlewareActionFilter::class,
            'middlewares' => [
                function ($request, $next) {
                    // Yii::$app->request->getAttribute('foo') will be set to `bar`
                    // Yii::$app->response->headers['hello'] will be set to `world`
                    return $next->handle(
                        $request->withAttribute('foo', 'bar')
                    )->withHeader('hello', 'world')
                }
            ]
        ]
    ]);
}

中间件按顺序处理,直到返回一个响应(例如 HTTP 重定向)或所有中间件都已处理。

如果在执行任何中间件后返回响应,则before action过滤器将返回false,并将生成的响应发送到客户端。

任何先前中间件添加的请求属性或头将包含在响应中。

这个包为什么存在?

性能

任务运行器(如RoadRunner和PHP-PM)的性能优势非常难以忽视。

虽然PHP自7.0(phpng)以来已经进行了速度提升,但基于Web的PHP应用程序的性能受到每次HTTP请求都需要重新引导每个文件的需求的限制。尽管Nginx + PHP-FPM速度很快,即使使用opcache,每个文件也必须在每个HTTP请求中重新读入内存。

PSR-7服务器使我们能够在请求之间保持几乎所有类和代码在内存中,这基本上消除了最大的性能瓶颈。

请务必查看性能比较维基页面,了解更多关于yii2-app-basic应用程序的实际性能影响信息。

预计PHP 7.4预加载将进一步提高性能。

PSR-7和PSR-15兼容性

虽然这个项目的目标不是严格意义上的PSR-7和PSR-15中间件,但越来越难以忽视。由于Yii2团队将PSR-7兼容性推迟到Yii 2.1或Yii 3,现有的Yii2项目无法利用标准化的请求/响应模式或链式中间件。

遵守PSR-7和PSR-15的开发者需要为Yii2重新实现自定义中间件,这与Yii2的“快速、安全、高效”的座右铭相悖。这个库有助于缓解一些这种痛苦。

这是如何工作的

该包在yii\Psr7\web命名空间内提供了三个类,分别是ApplicationRequestResponse,以及一个Psr7ResponseTrait特性,该特性可以用于将PSR7响应能力添加到您的现有类中,这些类扩展了yii\web\Response

为了处理传入的请求,yii\Psr7\web\Application组件充当标准yii\web\Application的替代品,用于在您的任务运行器中使用。它的构造函数接受标准的Yii2配置数组和一个额外的ServerRequestInterface实例。然后,Application组件使用提供的ServerRequestInterface实例实例化一个yii\Psr7\web\Request对象。

由于yii\web\Application::bootstrap使用request组件,因此需要在应用程序构造函数中正确构造请求组件,而不是简单地调用$app->handleRequest($psr7Request);

yii\Psr7\web\Request是标准yii\web\Request的替代品。它的唯一目的是在ServerRequestInterface和标准yii\web\Request API之间提供一个接口。

在您的模块、控制器、操作中,可以正常使用Yii::$app->requestYii::$app->response,无需任何更改。

在应用程序存在之前,它将对您的响应组件调用getPsr7Response。如果您使用的是yii\web\Response,只需将您的应用程序配置中的响应组件类更改为yii\Psr7\web\Response。如果您使用的是自定义的Response对象,只需将yii\Psr7\web\traits\Psr7ResponseTrait特性添加到扩展yii\web\ResponseResponse对象中,以获得必要的行为。

限制

  • 文件流当前不起作用(例如 yii\web\Response::sendFileyii\web\Response::sendContentAsFileyii\web\Response::sendStreamAsFile)。
  • Yii2调试工具栏yii2-debug将显示错误的请求时间和内存使用。
  • Yii2无法发送SameSitecookie。

当前状态

  • 实现自定义`Application`组件。
  • 将PSR7请求转换为yii\web\Request对象。
  • 返回简单响应。
  • 路由。
  • 处理yii\web\Response::$format
  • 与标准Yii2格式化程序一起工作。
  • 处理HeaderCollection
  • 处理CookieCollection
  • 处理 yii\web\Response::$streamyii\web\Response::$content
  • 实现 yii\web\Response::redirect
  • 实现 yii\web\Response::refresh
  • GET 查询参数 yii\web\Request::get()
  • POST 参数 yii\web\Request::post()
  • yii\web\Request::getAuthCredentials().
  • yii\web\Request::loadCookies().
  • 每个操作的中间件身份验证处理。
  • 每个操作的中间件链。
  • 重用 Application 组件,而不是在每次循环中重新实例化。
  • yii\web\ErrorHandler 实现。
  • 运行 yii-app-basic
  • 使用 yii\log\Target 启动。
  • 会话处理
  • yii-debug.
  • yii-gii.
  • 在负载下修复致命内存泄漏
  • yii\filters\auth\CompositeAuth 兼容性。
  • 实现可比较的 sendFilesendStreamAsFile
  • yii\web\Request::$methodParam 支持。(实际上不适用于 ServerRequestInterface
  • 测试覆盖率

本项目受 BSD-3-Clause 许可协议许可。有关更多详细信息,请参阅 LICENSE