メインメニュー
XAMPP アレンジ
IED
WSL2
-
道具箱
リポジトリ編
フレームワーク編
公開ソフトウェア
メタ
リンク
- PHP ライブラリ
- PHP 言語
apricot:ext:middleware目次
文書の過去の版を表示しています。
Apricot ミドルウェア
— y2sunlight 2020-05-06
関連記事
- Apricot 拡張
- 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(); } }
- Middleware インターフェースの process() メソッドを実装します。
- ロギング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
apricot/ext/middleware.1590114293.txt.gz · 最終更新: 2020/05/22 11:24 by y2sunlight
コメント