Ground Sunlight

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

ユーザ用ツール

サイト用ツール


apricot:usage:ja:middleware

差分

このページの2つのバージョン間の差分を表示します。

この比較画面にリンクする

両方とも前のリビジョン 前のリビジョン
次のリビジョン
前のリビジョン
apricot:usage:ja:middleware [2020/08/03 11:51]
tanaka [ミドルウェアの構成]
apricot:usage:ja:middleware [2020/09/03 13:46]
y2sunlight [Apricot ミドルウェア]
行 1: 行 1:
-> 編集中 
- 
----- 
- 
 ====== Apricot ミドルウェア ====== ====== Apricot ミドルウェア ======
  --- //[[http://www.y2sunlight.com|y2sunlight]] 2020-07-29//  --- //[[http://www.y2sunlight.com|y2sunlight]] 2020-07-29//
  
-[[apricot:usage:ja|Apricotの使用法 に戻る]]+[[apricot:usage:ja|Apricot ドキュメント に戻る]]
  
 目次 目次
  
 +  * [[apricot:usage:ja:features|Apricot 特徴と概要]]
   * [[apricot:usage:ja:config|Apricot 配置と構成]]   * [[apricot:usage:ja:config|Apricot 配置と構成]]
 +  * [[apricot:usage:ja:errors-logging|Apricot ログとエラー処理]]
   * [[apricot:usage:ja:http|Apricot リクエストとレスポンス]]   * [[apricot:usage:ja:http|Apricot リクエストとレスポンス]]
   * [[apricot:usage:ja:frontend|Apricot フロントエンド]]   * [[apricot:usage:ja:frontend|Apricot フロントエンド]]
-  * [[apricot:usage:ja:model|Apricot データベースモデル]]+  * [[apricot:usage:ja:database|Apricot データベース]] 
 +  * [[apricot:usage:ja:model|Apricot モデルとサービス]]
   * Apricot ミドルウェア   * Apricot ミドルウェア
   * [[apricot:usage:ja:controller|Apricot コントローラ]]   * [[apricot:usage:ja:controller|Apricot コントローラ]]
-  * [[apricot:usage:ja:errors-logging|Apricot ログとエラ処理]]+  * [[apricot:usage:ja:validation|Apricot バリデーション]] 
 +  * [[apricot:usage:ja:provider|Apricot サービスプバイダー]] 
 +  * [[apricot:usage:ja:authentication|Apricot ユザ認証]]
   * [[apricot:usage:ja:utility|Apricot ユーティリティ]]   * [[apricot:usage:ja:utility|Apricot ユーティリティ]]
  
 ---- ----
  
-===== ミドルウェアの構成 =====+===== ミドルウェアの構造 =====
  
-本章ではミドルウェア基盤を作ります。ミドルウェアとはアクションを囲んでいる層のような存在で、ユーザからのリクエストは何層もあるミドルウェアを通って最終的にアクションにたどり着きそこでレスポンスが生成されますが、途中でリクエストが中断され、ミドルウェアがレスポンスを生成することもあります。これ図示すると以下のようになります。+ミドルウェアとはアクションを囲んでいる層のような存在で、利用者からのリクエストは何層もあるミドルウェアを通って最終的にアクションにたどり着きそこでレスポンスが生成されますが、途中でリクエストが中断され、ミドルウェアがレスポンスを生成することもあります。また、ミドルウェアとは途中でリクエストやレスポンスに介入してデータモニタリング、フィルタリングまたは変換したりするもできます。
  
-=== ミドルウェア構造 === +=== ミドルウェアパイプライン === 
-{{:apricot:ext:ext-fig01.svg?nolink&800}}+ 
 +{{:apricot:usage:ja:middleware:ext-fig01.svg?nolink&800}}
  
 上図のような処理のネスト構造を ''パイプライン'' (pipeline) と呼び、特に多層になったミドルウェア構造を ''ミドルウェアパイプライン'' と呼ぶ事にします。 上図のような処理のネスト構造を ''パイプライン'' (pipeline) と呼び、特に多層になったミドルウェア構造を ''ミドルウェアパイプライン'' と呼ぶ事にします。
  
-Apricotに以下のミドルウェア実装されています。+ミドルウェアパイプラインを含めたミドルウェアの仕組みApricotコアが提供しますが、具体的なミドルウェア実装はアプリ側に任されています。
  
-  * [[apricot:ext:access-log|アクセスログ]] +\\
-  * [[apricot:ext:csrf|CSRF対策]] +
-  * [[apricot:ext:user-auth|ユーザ認証]]+
  
-ミドルウェアパイプラインを含めたミドルウェアの仕組みはApricotのコアの機能として実装しますが、上記のような具体的なミドルウアの実装はアプリ側で行います。+===== Middleware インターフース =====
  
-冒頭[[#ミドルウェア構造|ミドルウェア構造]]の図から分かるように、ミドルウェアの役割自分を処理の後に次のプロセッサーに制御を渡すことです。この時、プロセッサーにはミドルウェアとアクションの両方があるので、前出の [[#Invokerインタフェース|Invoker インターフェース]]を使います。ミドルウェアは任意タイミングで Invokerを 使うことができるので、前処理、後処理またはその両方ができます。また、クライアントの要求自分だけで消費して Invoker を使うことなく自分のレスポンスを返すことも可能です。+全てのミドルウェアは以下の ''Middleware'' インタフェースを実装して作ります。
  
-{{fa>folder-open-o}} ** /apricot/core/Foundation/Middleware ** 
 <code php Middleware.php> <code php Middleware.php>
 <?php <?php
-namespace Core\Foundation\Middleware;+namespace Apricot\Foundation\Middleware;
  
-use Core\Foundation\Invoker; +use Apricot\Foundation\Invoker; 
-use Core\Foundation\Response;+use Apricot\Foundation\Response;
  
 /** /**
行 54: 行 53:
 { {
     /**     /**
-     Process incoming requests and produces a response +     Processes an incoming request and produces a response
-     * @param Invoker $next Next invoker +     * 
-     * @return \Core\Foundation\Response if return response, then don'true call next action+     * @param Invoker $next Next invoker. 
 +     * @return \Apricot\Foundation\Response if return response within this method, then don'call the next action.
      */      */
     public function process(Invoker $next) :Response;     public function process(Invoker $next) :Response;
 } }
 </code> </code>
 +
 +前項のミドルウェア構造の図から分かるように、ミドルウェアのインターフェースの役割は自分を処理の後に次のプロセッサーに制御を渡すことです。この為に、ミドルウェアパイプラインは、''process()'' のパラメータとして ''Invoker'' インターフェースを渡します。
 +
 +<code php Invoker.php>
 +<?php
 +namespace Apricot\Foundation;
 +
 +/**
 + * Invoker Interface
 + */
 +interface Invoker
 +{
 +    /**
 +     * Invokes an incoming request processor
 +     *
 +     * @return \Apricot\Foundation\Response
 +     */
 +    public function invoke() : Response;
 +}
 +</code>
 +
 +''Invoker'' の ''invoke()'' メソッドを呼び出すことによって次のプロセッサーに制御を渡します。ミドルウェアは任意タイミングで ''invoke()'' を 使うことができるので、前処理、後処理またはその両方ができます。また、クライアントの要求を自分だけで消費して Invoker を使うことなく自分のレスポンスを返すことも可能です。
  
 \\ \\
  
-===== アクセログ ===== +===== Middlewareの実装 ===== 
->TODO+ 
 +Apricotのケルトンで提供されているミドルウェアは、以下場所にの配置してあります。この配置は必須ではありません。適宜アプリケーションのルールで変更して下さい。 
 + 
 +<code> 
 +/your-project/app/Middleware; 
 +</code> 
 + 
 +以下は、ミドルウェアの典型的な実装例です。 
 + 
 +{{fa>folder-open-o}} ** /your-project/app/Middleware ** 
 +<code php 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  
 +    } 
 +
 +</code> 
 + 
 +=== Middlewareの設定 === 
 + 
 +新しく作成したミドルウェアは、アプリケーションの設定ファイル ''/your-project/config/app.php'' に登録します。 
 + 
 +{{fa>folder-open-o}} ** /your-project/config ** 
 +<code php 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 */ 
 +    ], 
 +]; 
 +</code> 
 + 
 +これらのミドルウェアは、パイプラインによって登録した順に発動されます。
  
 \\ \\
  
-===== 認証 ===== +===== ミドルウエアの実例 ===== 
->TODO+ 
 +Apricotのスケルトンでは以下のミドルウェアが初期実装されています。 
 + 
 +  * アクセスログ 
 +  * フォーム入力変換 
 +  * CSRF対策 
 +  * ユーザ認証(基本認証 及び セッション認証) 
 + 
 +ここでは、ユーザ認証以外のミドルウェアについて説明します。ユーザ認証については後続の章をご覧ください。これらのミドルウェアを適用したくない場合は、アプリケーションの設定ファイル( config/app.php )を修正して下さい。
  
 \\ \\
  
-===== CSRF ===== +==== アクセスログ ==== 
->TODO+ 
 +アクセスログのミドルウェアは、リクエストをモニタして次のプロセスを発動するだけの簡単な構造をしています。アクセスログをカスタマイズするには、このミドルウェアを修正して下さい。 
 + 
 +{{fa>folder-open-o}} ** /your-project/app/Middleware ** 
 +<code php 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(); 
 +    } 
 +
 +</code> 
 + 
 +収集したログデータはinfoレベルで出力します。ログインユーザのアカウントを取得する為に、''AuthUser'' シングルトンを使用しています。このシングルトンについてはユーザ認証を参照して下さい。
  
 \\ \\
  
 +==== フォーム入力変換 ====
 +
 +このミドルウェアの目的は、フォームの入力変数を変換することです。
 +
 +  * 入力値をトリミングします。
 +  * 入力値が空の場合、その値をnullにします。
 +
 +これらの変換を望まない変数については、そのキーを ''$exclude'' に登録して下さい。入力変数の変換を追加または変更するには、このミドルウェアを修正して下さい。
 +
 +{{fa>folder-open-o}} ** /your-project/app/Middleware **
 +<code php 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();
 +    }
 +}
 +</code>
 +
 +==== CSRF対策 ====
 +
 +このミドルウェアの目的は、CSRF対策です。発行しているCSRFトークンの検証に失敗した場合は、''TokenMismatchException'' をスローして[[apricot:usage:ja:errors-logging#集約例外ハンドラー]]に処理を委ねます。
 +
 +CSR対策を望まないコントローラーについては、そのクラス名を ''$exclude'' に登録して下さい。
 +
 +{{fa>folder-open-o}} ** /your-project/app/Middleware **
 +<code php 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();
 +    }
 +}
 +</code>
 +
 +''CsrfToken'' はApricotコアのクラスで次の静的メソッドを持ちます。
 +
 +^メソッド^機能^
 +|generate()|セッション内のCSRFトークンが未生成の場合、それを生成してセッションに格納します。|
 +|verify():bool|フォームの入力変数とセッションに格納されているCSRFトークンを比較して同じならtrueを返します。これはHTTPメソッドがPOSTの場合のみ有効で、GETの場合は常にtrueを返します。|
 +
 +フォーム内にCSRFトークンを入力変数として埋め込む方法については、「[[apricot:usage:ja:frontend#テンプレートの継承|フロントエンドのテンプレートの継承]]」のCSRF対策の項を参照して下さい。
 +
 +アプリケーション設定( [[apricot:usage:ja:config#appphp_ファイル|config/app.php]] )の ''csrftrue.disposable'' が true の場合、CSRFトークンは使い捨てで、''verify()'' の後で削除され次の ''generate()'' で再生成されます。これが false の場合は、セッション中の間CSRFトークンの値は不変です。
 +
 +\\
  
apricot/usage/ja/middleware.txt · 最終更新: 2020/09/03 13:46 by y2sunlight