Laravel-s Dingo (Lumen) 支援

ruby發表於2019-08-20

原因: 專案重度依賴 Dingo, 直接放棄 Dingo 的代價太大

有興趣瞭解原理的可以看 Dingo api 處理請求機制

各個依賴版本

  • laravel-s(3.5.8)

  • swoole 4.3.3

  • php-7.1.14

  • Lumen 5.5.38

  • Dingo/api 2.0.0-alpha2.2

步驟

修改 Laravel.php 的 handleDynamic 方法,如下

這一步需要另外 copy 一份 Laravel.php 出來,假設放在 App\Swoole\Laravel.php

App\Swoole\Laravel.php

... // 其他內容和原檔案一致

public function handleDynamic(IlluminateRequest $request)
{
    ob_start();

    if ($this->isLumen()) {
        // dingo router 路由處理
        $response = app('Dingo\Api\Http\Middleware\Request')->handle($request, function ($request) {
            return $request;
        });

        // 不是 dingo router 裡面定義的路由
        if ($response instanceof \Illuminate\Http\Request) {
            $response = $this->app->dispatch($response);
        }

        if ($response instanceof SymfonyResponse) {
            $content = $response->getContent();
        } else {
            $content = (string)$response;
        }

        if ($response instanceof \Symfony\Component\HttpFoundation\Response) {
            $this->reflectionApp->callTerminableMiddleware($response);
        }
    } else {
        $response = $this->kernel->handle($request);
        $content = $response->getContent();
        $this->kernel->terminate($request, $response);
    }

    // prefer content in response, secondly ob
    if (!($response instanceof StreamedResponse) && strlen($content) === 0 && ob_get_length() > 0) {
        $response->setContent(ob_get_contents());
    }

    ob_end_clean();

    return $response;
}

... // 其他內容和原檔案一致

原來的處理方式: $response = $this->app->dispatch($request); 這個處理方式會只使用 Lumen 的 app() 來處理 HTTP 請求,第一次請求正常,但是後續請求沒什麼意外是無法正常處理的。

新的處理方式: 使用 app('Dingo\Api\Http\Middleware\Request') 來處理 HTTP 請求,這是正常情況下 Dingo 處理請求的第一個經過的中介軟體,如果請求之後返回的還是一個 Request,而不是 Response,說明這不是使用 app('api.router') 定義的路由,退化為使用 $response = $this->app->dispatch($request); 處理該請求。

自動載入處理

然後在 bin/laravels.php $basePath 後面加上:

require_once $basePath . '/app/Swoole/Laravel.php';

目的: 自動載入機制在載入 Laravel 類的時候載入的是自定義的 Laravel.php,而不是原來的 Laravel.php 這樣我們的修改就起效了。

Dingo 控制器在請求結束清理

新增檔案 app/Cleaners/ControllerCleaner.php

<?php

namespace App\Cleaners;

use Hhxsv5\LaravelS\Illuminate\Cleaners\CleanerInterface;
use Illuminate\Container\Container;

class ControllerCleaner implements CleanerInterface
{
    public function clean(Container $app, Container $snapshot)
    {
        $ref = new \ReflectionObject($app);
        $property = $ref->getProperty('instances');
        $property->setAccessible(true);
        $instances = $property->getValue($app);

        foreach ($instances as $key => $instance) {
            if ($instance instanceof \Laravel\Lumen\Routing\Controller) {
                $instance = null;
                app()->offsetUnset($key);
            }
        }
    }
}

然後在 config/laravels.php 中的 cleaners 中加上該 Cleaner:

'cleaners' => [
    App\Cleaners\ControllerCleaner::class, // 單例控制器清除
],

Dingo 中介軟體處理

新增檔案 App\Cleaners\MiddlewareCleaner.php

<?php

namespace App\Cleaners;

use Dingo\Api\Http\Middleware\Request;
use Hhxsv5\LaravelS\Illuminate\Cleaners\CleanerInterface;
use Illuminate\Container\Container;

class MiddlewareCleaner implements CleanerInterface
{
    public function clean(Container $app, Container $snapshot)
    {
        $reflect = new \ReflectionObject($snapshot);
        $property = $reflect->getProperty('middleware');
        $property->setAccessible(true);

        $middleware = $property->getValue($snapshot);

        // 移除 Dingo\Api\Http\Middleware\Request,防止死迴圈
        $middleware = array_values(array_diff($middleware, [Request::class]));

        app(Request::class)->setMiddlewares($middleware);
    }
}

然後在 config/laravels.php 的 cleaners 中加上該 cleaner:

'cleaners' => [
    App\Cleaners\MiddlewareCleaner::class, // 單例控制器清除
],

目的: 這個命名不是很準確地說明它的作用,其實這個檔案的作用是把被 dingo 清除的中介軟體還原回來,這樣後續的請求才會有第一次請求的中介軟體。

(dingo 會在其處理的週期內透過反射把 app 容器內的中介軟體清除,會導致後續請求沒有經過這些中介軟體。)

這裡說的中介軟體是定義在 app() App 容器中的中介軟體:

bootstrap/app.php

$app->middleware([
    XxxMiddleware::class,
]);

php-fpm 環境下,由於每個請求相互之間不會相互影響,因此不會有問題,而在 laravel-s 環境下,請求結束之後,請求 context 不會被完全銷燬,有可能會對後續請求造成影響。

原文地址: Laravel-s Dingo (Lumen) 支援

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章