Ground Sunlight

Windowsで作る - PHPプログラミングの開発環境

ユーザ用ツール

サイト用ツール


サイドバー

メインメニュー

XAMPP アレンジ

IED

WSL2

道具箱

リポジトリ編

フレームワーク編

公開ソフトウェア

メタ
リンク


このページへのアクセス
今日: 1 / 昨日: 0
総計: 1167

apricot:ext:middleware

文書の過去の版を表示しています。


Apricot ミドルウェア

y2sunlight 2020-05-06

Apricot に戻る

関連記事

本章ではミドルウェアを作ります。ミドルウェアとはアクションを囲んでいる層のような存在で、ユーザからのリクエストは何層もあるミドルウェアを通って最終的にアクションにたどり着きそこでレスポンスが生成されますが、途中でリクエストが中断され、ミドルウェアがレスポンスを生成することもあります。これを図示すると以下のようになります。

ミドルウェア構造

                   Middleware(A)       Middleware(B)        Action
                  ┌────────────┐      ┌────────────┐      ┌────────┐
  [Request ] ---> | ---------> | ---> | ---------> | ---> | ───┐   | 
                  |     ↓      |      |     ↓      |      |    |   | 
  [Response] <--- | <--------  | <--- | <--------- | <--- | <──┘   | 
                  └────────────┘      └────────────┘      └────────┘

上図のような処理のネスト構造を パイプライン (pipeline) と呼び、特に多層になったミドルウェア構造を ミドルウェアパイプライン と呼ぶ事にします。

本章では、Apricotにミドルウェアの仕組みを作り、その例としてアクセスログを実装します。Apricotには以下のミドルウェアが実装されています。

ミドルウェアパイプラインを含めたミドルウェアの仕組みはApricotのコアの機能として実装しますが、上記のような具体的なミドルウェアの実装はアプリ側で行います。


Invoker と アクション

Invokerインターフェース

ミドルウェアを作る準備として、まずInvoker(呼び出し人)を作ります。冒頭のミドルウェア構造の図から分かるように、ミドルウェアが次に呼び出すのはミドルウェアまたはアクションです。この2つを同一視する為にInvokerインターフェースを作ります。

以下にInvokerインターフェースを示します。Invokerはレスポンスオブジェクトを返します。

/apricot/core/Foundation

Invoker.php
<?php
namespace Core\Foundation;
 
/**
 * Invoker Interface
 */
interface Invoker
{
    /**
     * Invoke incoming request processor
     * @return \Core\Foundation\Response
     */
    public function invoke() : Response;
}

ActionInvoker クラス

アクションを呼び出すクラスとして Invokerを実装した ActionInvoker クラスを作ります。このクラスはミドルウェアパイプラインによって使用されます。

このクラスの使い方は、コンストラクタでアクションの情報(コントローラ名、アクション名、パラメータ)を設定し invoke() メソッドを呼び出してレスポンスを取得します。

/apricot/core/Foundation

ActionInvoker.php
<?php
namespace Core\Foundation;
 
/**
 * Request ActionInvoker Class
 */
class ActionInvoker implements Invoker
{
    /**
     * Controller name
     * @var string;
     */
    private $controller;
 
    /**
     * method name
     * @var string
     */
    private $action;
 
    /**
     * parameters
     * @var array
     */
    private $params;
 
    /**
     * Create ActionInvoker
     * @param string $controller
     * @param string $action
     * @param array $params
     */
    public function __construct(string $controller, string $action, array $params=[])
    {
        $this->controller = $controller;
        $this->action = $action;
        $this->params = $params;
    }
 
    /**
     * Invoke action
     * {@inheritDoc}
     * @see \Core\Foundation\Invoker::invoke()
     */
    public function invoke() : Response
    {
        // Create Controller
        $controller = "\\App\\Controllers\\{$this->controller}";
        $instance = new $controller();
 
        return call_user_func_array(array($instance, 'invokeAction'), [$this->action, $this->params]);
    }
}


ミドルウェアパイプライン

Middleware インターフェース

冒頭のミドルウェア構造の図から分かるように、ミドルウェアの役割は自分を処理の後に次のプロセッサーに制御を渡すことです。この時、プロセッサーにはミドルウェアとアクションの両方があるので、前出の Invoker インターフェースを使います。ミドルウェアは任意タイミングで Invokerを 使うことができるので、前処理、後処理またはその両方ができます。また、クライアントの要求を自分だけで消費して Invoker を使うことなく自分のレスポンスを返すことも可能です。

/apricot/core/Foundation/Middleware

Middleware.php
<?php
namespace Core\Foundation\Middleware;
 
use Core\Foundation\Invoker;
use Core\Foundation\Response;
 
/**
 * Middleware Interface
 */
interface Middleware
{
    /**
     * Process incoming requests and produces a response
     * @param Invoker $next Next invoker
     * @return \Core\Foundation\Response if return response, then don'true call next action
     */
    public function process(Invoker $next) :Response;
}

MiddlewareInvoker クラス

ミドルウェアパイプラインを作る為にミドルウェアとアクションを同一視することはに述べました。ここでは、ActionInvoker クラスと同様に、Invokerを実装した MiddlewareInvoker クラスを作ります。

このクラスの使い方は、コンストラクタでアクションのミドルウェアを設定し invoke() メソッドを呼び出してレスポンスを取得します。

/apricot/core/Foundation/Middleware

MiddlewareInvoker.php
<?php
namespace Core\Foundation\Middleware;
 
use Core\Foundation\Invoker;
use Core\Foundation\Response;
 
/**
 * Middleware Invoker
 */
class MiddlewareInvoker implements Invoker
{
    /**
     * Middleware Instance
     * @var Middleware
     */
    private $middleware;
 
    /**
     * Next Invoker Instance
     * @var Invoker
     */
    private $nextInvoker;
 
    /**
     * Create Middleware Handler
     * @param Middleware $middleware
     * @param Invoker $nextInvoker
     */
    public function __construct(Middleware $middleware, Invoker $nextInvoker)
    {
        $this->middleware = $middleware;
        $this->nextInvoker = $nextInvoker;
    }
 
    /**
     * Invoke middleware
     * @return \Core\Foundation\Response
     * {@inheritDoc}
     * @see \Core\Foundation\Invoker::invoke()
     */
    public function invoke(): Response
    {
        return $this->middleware->process($this->nextInvoker);
    }
}

MiddlewarePipeline クラス

全ての準備ができたので、ミドルウェアパイプラインを実装します。

このクラスの使い方は、addMiddleware() メソッドでミドルウェアをパイプラインに登録し、executeAction() でアクションを実行します。このとき、登録されているミドルウェアが順に実行され、最終的にアクションが実行されます。尚、ミドルウェアの登録はコンストラクタで一括してすることもできます。

/apricot/core/Foundation/Middleware

MiddlewareInvoker.php
<?php
namespace Core\Foundation\Middleware;
 
use Core\Foundation\ActionInvoker;
 
class MiddlewarePipeline
{
    /**
     * Middleware
     * @var Middleware[]
     */
    private $middleware = [];
 
    /**
     * Create
     * @param Middleware[] $middleware
     */
    public function __construct(array $middleware=[])
    {
        foreach($middleware as $item)
        {
            $this->addMiddleware(new $item);
        }
    }
 
    /**
     * Add middleware
     * @param Middleware $middleware
     */
    public function addMiddleware(Middleware $middleware)
    {
        array_unshift($this->middleware, $middleware);
        return $this;
    }
 
    /**
     * Execute action
     * @param ActionInvoker $action
     * @return \Core\Foundation\Response
     */
    public function executeAction(ActionInvoker $invoker)
    {
        // Create Pipeline
        foreach($this->middleware as $middleware)
        {
            $invoker = new MiddlewareInvoker($middleware, $invoker);
        }
 
        return $invoker->invoke();
    }
}


Applicationクラスの変更

ミドルウェアパイプラインができたので、ApplicationクラスのexecuteAction()メソッドを変更して、ミドルウェアパイプライン経由でアクションを実行するようにします。

/apricot/core

Application.php
<?php
namespace Core;
 
/**
 * Application Class
 */
class Application
{
 
...
 
    /**
     * Ecexute action
     * @param string $controllerName
     * @param string $actionName
     * @param array $params
     */
    private function executeAction(string $controllerName, string $actionName, array $params=[])
    {
        // Create ActionInvoker
        $action = new \Core\Foundation\ActionInvoker($controllerName, $actionName, $params);
 
        // Create Middleware pipeline
        $pipeline = new \Core\Foundation\Middleware\MiddlewarePipeline($this->app['middleware']);
 
        // Ecexute action
        $response = $pipeline->executeAction($action);
        if ($response instanceof \Core\Foundation\Response)
        {
            $response->commit();
        }
        else
        {
            abort(500,'No Response');
        }
    }
}
  • ActionInvoker と MiddlewarePipeline を生成します。
    • ミドルウエアの登録はApplicationクラスの設定ファイル(app.php)を使って一括設定して行います。app['middleware']はミドルウェアクラスの登録された配列です。
  • MiddlewarePipeline クラスの executeAction() メソッドの戻り値が Responseインスタンスだった時は、commit()メソッドを実行してクライアントにレスポンスを返します。

テスト

ミドルウェアの仕組みはこれで作成出来ました。まだ、具体的なミドルウエアは実装されていませんが、この時点で一度実行してみましょう。

http://localhost/ws2019/apricot/public/

Apricotのホームが画面が正常に表示されたらOKです。


アクセスログ

ミドルウエアの実装例としてアクセスログを作ってみましょう。アクセスログは一番外側のミドルウェアとして機能させます。パプラインの次の処理の前後でログを取ることもできますが、ここでは、前処理としてログ出力を行います。

Apricotでは具体的なミドルウェアの実装はアプリ側の以下のフォルダに保存します。

apricot [プロジェクト]
 |
 ├── app [アプリケーション]
 |    |
 |    └── Middleware [ルミドルウェア]

AccessLog ルミドルウェア

以下に、アクセスログの実装を示します。

/apricot/app/Middleware

AccessLog.php
namespace App\Middleware;
 
use Core\Log;
use Core\Input;
use Core\Foundation\Response;
use Core\Foundation\Invoker;
use Core\Foundation\Middleware\Middleware;
 
/**
 * アクセスログ - Middleware
 */
class AccessLog implements Middleware
{
    /**
     * Process incoming requests and produces a response
     * {@inheritDoc}
     * @see \Core\Foundation\Middleware\Middleware::invoke()
     */
    public function process(Invoker $next): Response
    {
        $message = session_id().' '.$_SERVER['REQUEST_METHOD'].' '.$_SERVER['REQUEST_URI'];
 
        // 前処理
        $data = [
            'remote_addr' => $_SERVER['REMOTE_ADDR'],
            'remote_user' => array_key_exists('REMOTE_USER', $_SERVER) ?$_SERVER['REMOTE_USER'] : 'Anonymous',
            'user_agent' =>  $_SERVER['HTTP_USER_AGENT'],
            'input' => json_encode(Input::all()),
        ];
        Log::info("$message",$data);
 
        // 次のInvokerを呼び出す
        return $next->invoke();
    }
}
  • ロギングLog::info() メソッドでアクセスログを出力します。
    • ログメッセージ
      • セッションID ( session_id() )
      • リクエストメソッド ( $_SERVER['REQUEST_METHOD'] )
      • リクエストURI ( $_SERVER['REQUEST_URI'] )
    • コンテキスト
      • リモートIPアドレス ( $_SERVER['REMOTE_ADDR'] )
      • リモートユーザ ( $_SERVER['REMOTE_USER'] )
      • ユーザエージェント ( $_SERVER['HTTP_USER_AGENT'] )
      • フォームデータ ( Input::all() )
  • 前処理の後、次の Invoker の invoke() メソッドを呼び出します。


アプリケーション設定の変更

上で作ったミドルウェア(AccessLog)をアプリケーションの設定ファイル(app.php)に追加します。

/apricot/config

app.php
<?php
return
[
    'setup' =>[],
    'middleware' =>[
        \App\Middleware\AccessLog::class,        /* Access log */
    ],
    'auth' =>[],
    'csrf' =>[],
];
  • middlewareにクラス( \App\Middleware\AccessLog::class )を追加します


テスト実行

アクセスログのテストをしてみましょう。ブラウザ上で以下のURLにアクセスしてみて下さい。

http://localhost/ws2019/apricot/public/

以下のアクセスログが出力されていることを確認して下さい。

/apricot/var/logs

apricot-2020-05-21.log
<code>
[2020-05-21T15:29:20.941358+09:00] apricot.INFO: 2qqabsldseqabgv9sksejslaic GET /ws2019/apricot/public/home {"remote_addr":"::1","remote_user":"Anonymous","user_agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36","input":"[]"} 

■ その他の画面を表示してアクセスログを確認して下さい。
■ 尚、ロギングの設定は以下の設定ファイルで行います。

/apricot/config/setting/monolog.setting.php


コメント

コメントを入力. Wiki文法が有効です:
 
apricot/ext/middleware.1590114293.txt.gz · 最終更新: 2020/05/22 11:24 by y2sunlight