メインメニュー
XAMPP アレンジ
IED
WSL2
-
道具箱
リポジトリ編
フレームワーク編
公開ソフトウェア
メタ
リンク
- PHP ライブラリ
- PHP 言語
apricot:usage:ja:middlewareApricot ミドルウェア
ミドルウェアの構造
ミドルウェアとはアクションを囲んでいる層のような存在で、利用者からのリクエストは何層もあるミドルウェアを通って最終的にアクションにたどり着きそこでレスポンスが生成されますが、途中でリクエストが中断され、ミドルウェアがレスポンスを生成することもあります。また、ミドルウェアとは途中でリクエストやレスポンスに介入してデータをモニタリング、フィルタリングまたは変換したりすることもできます。
ミドルウェアパイプライン
上図のような処理のネスト構造を
パイプライン
(pipeline) と呼び、特に多層になったミドルウェア構造をミドルウェアパイプライン
と呼ぶ事にします。ミドルウェアパイプラインを含めたミドルウェアの仕組みはApricotのコアが提供しますが、具体的なミドルウェアの実装はアプリ側に任されています。
Middleware インターフェース
全てのミドルウェアは以下の
Middleware
インタフェースを実装して作ります。- Middleware.php
<?php namespace Apricot\Foundation\Middleware; use Apricot\Foundation\Invoker; use Apricot\Foundation\Response; /** * Middleware Interface */ interface Middleware { /** * Processes an incoming request and produces a response. * * @param Invoker $next Next invoker. * @return \Apricot\Foundation\Response if return a response within this method, then don't call the next action. */ public function process(Invoker $next) :Response; }
前項のミドルウェア構造の図から分かるように、ミドルウェアのインターフェースの役割は自分を処理の後に次のプロセッサーに制御を渡すことです。この為に、ミドルウェアパイプラインは、
process()
のパラメータとしてInvoker
インターフェースを渡します。- Invoker.php
<?php namespace Apricot\Foundation; /** * Invoker Interface */ interface Invoker { /** * Invokes an incoming request processor * * @return \Apricot\Foundation\Response */ public function invoke() : Response; }
Invoker
のinvoke()
メソッドを呼び出すことによって次のプロセッサーに制御を渡します。ミドルウェアは任意タイミングでinvoke()
を 使うことができるので、前処理、後処理またはその両方ができます。また、クライアントの要求を自分だけで消費して Invoker を使うことなく自分のレスポンスを返すことも可能です。
Middlewareの実装
Apricotのスケルトンで提供されているミドルウェアは、以下場所にの配置してあります。この配置は必須ではありません。適宜アプリケーションのルールで変更して下さい。
/your-project/app/Middleware;
以下は、ミドルウェアの典型的な実装例です。
/your-project/app/Middleware
- Invoker.php
<?php namespace App\Middleware; use Apricot\Foundation\Response; use Apricot\Foundation\Invoker; use Apricot\Foundation\Middleware\Middleware; /** * Middleware */ class MyMiddleware implements Middleware { /** * {@inheritDoc} * @see \Apricot\Foundation\Middleware\Middleware::invoke() */ public function process(Invoker $next): Response { // Pre-processing // ... // Calls the next Invoker. $response = $next->invoke(); // Post-processing // ... return response } }
Middlewareの設定
新しく作成したミドルウェアは、アプリケーションの設定ファイル
/your-project/config/app.php
に登録します。/your-project/config
- app.php
<?php /** * This file contains application settings. */ return [ 'middleware' =>[ \App\Middleware\AccessLog::class, /* Access log */ \App\Middleware\VerifyCsrfToken::class, /* Verify CSRF Token */ \App\Middleware\Auth\SessionAuth::class, /* Session authentication */ \App\Middleware\InputConverter::class, /* Input Converter */ \App\Middleware\MyMiddleware ::class, /* New MyMiddleware */ ], ];
これらのミドルウェアは、パイプラインによって登録した順に発動されます。
ミドルウエアの実例
Apricotのスケルトンでは以下のミドルウェアが初期実装されています。
- アクセスログ
- フォーム入力変換
- CSRF対策
- ユーザ認証(基本認証 及び セッション認証)
ここでは、ユーザ認証以外のミドルウェアについて説明します。ユーザ認証については後続の章をご覧ください。これらのミドルウェアを適用したくない場合は、アプリケーションの設定ファイル( config/app.php )を修正して下さい。
アクセスログ
アクセスログのミドルウェアは、リクエストをモニタして次のプロセスを発動するだけの簡単な構造をしています。アクセスログをカスタマイズするには、このミドルウェアを修正して下さい。
/your-project/app/Middleware
- AccessLog.php
/** * Access Log - Middleware */ class AccessLog implements Middleware { /** * {@inheritDoc} * @see \Apricot\Foundation\Middleware\Middleware::invoke() */ public function process(Invoker $next): Response { // Logs a message. $message = session_id().' '.$_SERVER['REQUEST_METHOD'].' '.$_SERVER['REQUEST_URI']; // Logs context data. $data = [ 'remote_addr' => $_SERVER['REMOTE_ADDR'], 'remote_user' => AuthUser::check() ? AuthUser::getUser()->account : null, 'user_agent' => $_SERVER['HTTP_USER_AGENT'], 'input' => json_encode(Input::all()), ]; Log::info($message,$data); // Calls the next Invoker. return $next->invoke(); } }
収集したログデータはinfoレベルで出力します。ログインユーザのアカウントを取得する為に、
AuthUser
シングルトンを使用しています。このシングルトンについてはユーザ認証を参照して下さい。
フォーム入力変換
このミドルウェアの目的は、フォームの入力変数を変換することです。
- 入力値をトリミングします。
- 入力値が空の場合、その値をnullにします。
これらの変換を望まない変数については、そのキーを
$exclude
に登録して下さい。入力変数の変換を追加または変更するには、このミドルウェアを修正して下さい。/your-project/app/Middleware
- InputConverter.php
/** * Input Converter - Middleware */ class InputConverter implements Middleware { /** * @var array List of input variables to exclude */ private $exclude = [ 'password', 'password_confirmation', ]; /** * {@inheritDoc} * @see \Apricot\Foundation\Middleware\Middleware::invoke() */ public function process(Invoker $next): Response { $inputs = Input::all(); foreach($inputs as $key=>$value) { if (in_array($key, $this->exclude)) continue; if (is_string($value)) { $value = trim($value); // Trims a string value. if($value === '') $value = null; // Converts an empty string value to null. Input::set($key, $value); } } return $next->invoke(); } }
CSRF対策
このミドルウェアの目的は、CSRF対策です。発行しているCSRFトークンの検証に失敗した場合は、
TokenMismatchException
をスローして集約例外ハンドラーに処理を委ねます。CSR対策を望まないコントローラーについては、そのクラス名を
$exclude
に登録して下さい。/your-project/app/Middleware
- VerifyCsrfToken.php
/** * CSRF token verification - Middleware */ class VerifyCsrfToken implements Middleware { /** * @var array List of controllers to exclude */ private $exclude = [ 'HogeHogeController', // For example: Web API controller etc. ]; /** * {@inheritDoc} * @see \Apricot\Foundation\Middleware\Middleware::invoke() */ public function process(Invoker $next): Response { if (!in_array(controllerName(), $this->exclude)) { // Verifies CSRF tokens. if (!CsrfToken::verify()) { throw new \Apricot\Exceptions\TokenMismatchException('VerifyCsrfToken Error'); } } // Generates a CSRF token. CsrfToken::generate(); return $next->invoke(); } }
CsrfToken
はApricotコアのクラスで次の静的メソッドを持ちます。メソッド 機能 generate() セッション内のCSRFトークンが未生成の場合、それを生成してセッションに格納します。 verify():bool フォームの入力変数とセッションに格納されているCSRFトークンを比較して同じならtrueを返します。これはHTTPメソッドがPOSTの場合のみ有効で、GETの場合は常にtrueを返します。 フォーム内にCSRFトークンを入力変数として埋め込む方法については、「フロントエンドのテンプレートの継承」のCSRF対策の項を参照して下さい。
アプリケーション設定( config/app.php )の
csrftrue.disposable
が true の場合、CSRFトークンは使い捨てで、verify()
の後で削除され次のgenerate()
で再生成されます。これが false の場合は、セッション中の間CSRFトークンの値は不変です。
apricot/usage/ja/middleware.txt · 最終更新: 2020/09/03 13:46 by y2sunlight
コメント