====== Apricot ミドルウェア ====== --- //[[http://www.y2sunlight.com|y2sunlight]] 2020-05-06// [[apricot:top|Apricot に戻る]] 関連記事 * [[apricot:configuration|Apricot プロジェクトの作成]] * [[apricot:public|Apricot 公開フォルダ]] * [[apricot:core:top|Apricot コア]] * [[apricot:app:top|Apricot アプリ]] * Apricot 拡張 * Apricot ミドルウェア * [[apricot:ext:access-log|Apricot アクセスログ]] * [[apricot:ext:csrf|Apricot CSRF対策]] * [[apricot:ext:user-auth|Apricot ユーザ認証]] * [[apricot:ext:basic-auth|Apricot 基本認証]] * [[apricot:ext:session-auth|Apricot セッション認証]] * [[apricot:ext:interceptor|Apricot インターセプター]] * [[apricot:ext:di-container|Apricot DIコンテナー]] 本章ではミドルウェア基盤を作ります。ミドルウェアとはアクションを囲んでいる層のような存在で、ユーザからのリクエストは何層もあるミドルウェアを通って最終的にアクションにたどり着きそこでレスポンスが生成されますが、途中でリクエストが中断され、ミドルウェアがレスポンスを生成することもあります。これを図示すると以下のようになります。 === ミドルウェア構造 === {{:apricot:ext:ext-fig01.svg?nolink&800}} 上図のような処理のネスト構造を ''パイプライン'' (pipeline) と呼び、特に多層になったミドルウェア構造を ''ミドルウェアパイプライン'' と呼ぶ事にします。 Apricotには以下のミドルウェアが実装されています。 * [[apricot:ext:access-log|アクセスログ]] * [[apricot:ext:csrf|CSRF対策]] * [[apricot:ext:user-auth|ユーザ認証]] ミドルウェアパイプラインを含めたミドルウェアの仕組みはApricotのコアの機能として実装しますが、上記のような具体的なミドルウェアの実装はアプリ側で行います。 ---- ===== Invoker と アクション ===== ==== Invokerインターフェース ==== ミドルウェアを作る準備として、まずInvoker(呼び出し人)を作ります。冒頭の[[#ミドルウェア構造|ミドルウェア構造]]の図から分かるように、ミドルウェアが次に呼び出すのはミドルウェアまたはアクションです。この2つを同一視する為にInvokerインターフェースを作ります。 以下にInvokerインターフェースを示します。Invokerは[[apricot:core:response-class|レスポンスオブジェクト]]を返します。 {{fa>folder-open-o}} ** /apricot/core/Foundation ** ==== ActionInvoker クラス ==== アクションを呼び出すクラスとして Invokerを実装した ActionInvoker クラスを作ります。このクラスはミドルウェアパイプラインによって使用されます。 このクラスの使い方は、コンストラクタでアクションの情報(コントローラ名、アクション名、パラメータ)を設定し invoke() メソッドを呼び出してレスポンスを取得します。 {{fa>folder-open-o}} ** /apricot/core/Foundation ** 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を 使うことができるので、前処理、後処理またはその両方ができます。また、クライアントの要求を自分だけで消費して Invoker を使うことなく自分のレスポンスを返すことも可能です。 {{fa>folder-open-o}} ** /apricot/core/Foundation/Middleware ** ==== MiddlewareInvoker クラス ==== ミドルウェアパイプラインを作る為にミドルウェアとアクションを同一視することは[[#Invokerインターフェース|前]]に述べました。ここでは、[[#ActionInvoker クラス|ActionInvoker クラス]]と同様に、Invokerを実装した MiddlewareInvoker クラスを作ります。 このクラスの使い方は、コンストラクタでアクションのミドルウェアを設定し invoke() メソッドを呼び出してレスポンスを取得します。 {{fa>folder-open-o}} ** /apricot/core/Foundation/Middleware ** 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() でアクションを実行します。このとき、登録されているミドルウェアが順に実行され、最終的にアクションが実行されます。尚、ミドルウェアの登録はコンストラクタで一括してすることもできます。 {{fa>folder-open-o}} ** /apricot/core/Foundation/Middleware ** 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()メソッドを変更して、ミドルウェアパイプライン経由でアクションを実行するようにします。 {{fa>folder-open-o}} ** /apricot/core ** app['middleware']); // Ecexute action $response = $pipeline->executeAction($action); if ($response instanceof \Core\Foundation\Response) { $response->commit(); } else { abort(500,'No Response'); } } } * ActionInvoker と MiddlewarePipeline を生成します。 * ミドルウエアの登録は[[apricot:core:application-class#applicationクラスの設定ファイル|Applicationクラスの設定ファイル]](app.php)を使って一括設定して行います。app['middleware']はミドルウェアクラスの登録された配列です。 * MiddlewarePipeline クラスの executeAction() メソッドの戻り値が Responseインスタンスだった時は、commit()メソッドを実行してクライアントにレスポンスを返します。 \\ ===== テスト実行 ===== ミドルウェアの仕組みはこれで作成出来ました。まだ、具体的なミドルウエアは実装されていませんが、この時点で一度実行してみましょう。 http://localhost/ws2019/apricot/public/ Apricotのホームが画面が正常に表示されたらOKです。 \\