Slim研讀筆記六之應用主體(中)

你可以在你的 Slim 應用之前(before) 和 之後(after) 運行代碼來處理你認爲合適的請求和響應對象。這就叫做中間件。爲什麼要這麼做呢?比如你想保護你的應用不遭受跨站請求僞造。也許你想在應用程序運行前驗證請求。中間件對這些場景的處理簡直完美。


什麼是中間件?

從技術上來講,中間件是一個接收三個參數的可回調(callable)對象:

  1. \Psr\Http\Message\ServerRequestInterface - PSR7 請求對象
  1. \Psr\Http\Message\ResponseInterface - PSR7 響應對象
  1. callable - 下一層中間件的回調對象

它可以做任何與這些對象相應的事情。唯一硬性要求就是中間件必須返回一個 \Psr\Http\Message\ResponseInterface 的實例。 每個中間件都 應當調用下一層中間件,並講請求和響應對象作爲參數傳遞給它(下一層中間件)。


中間件是如何工作的?

不同的框架使用中間件的方式不同。在 Slim 框架中,中間件層以同心圓的方式包裹着核心應用。由於新增的中間件層總會包裹所有已經存在的中間件層。當添加更多的中間件層時同心圓結構會不斷的向外擴展。

當 Slim 應用運行時,請求對象和響應對象從外到內穿過中間件結構。它們首先進入最外層的中間件,然後然後進入下一層,(以此類推)。直到最後它們到達了 Slim 應用程序自身。在 Slim 應用分派了對應的路由後,作爲結果的響應對象離開 Slim 應用,然後從內到外穿過中間件結構。最終,最後出來的響應對象離開了最外層的中間件,被序列化爲一個原始的 HTTP 響應消息,並返回給 HTTP 客戶端。下圖清楚的說明了中間件的工作流程


一起看下Slim是如何處理中間件的,在應用主體類App中

class App
{
    use MiddlewareAwareTrait;
......

繼續研讀該特製的實現過程

/**
 * Middleware
 * 這是一個啓用同心中間件的內部類。
 * This is an internal class that enables concentric middleware layers. This
 * class is an implementation detail and is used only inside of the Slim
 * application; it is not visible to—and should not be used by—end users.
 */
trait MiddlewareAwareTrait
{
    /**
     * 中間件調用堆棧的棧頂
     * Tip of the middleware call stack
     *
     * @var callable
     */
    protected $tip;

    /**
     * 中間件棧鎖
     * Middleware stack lock
     *
     * @var bool
     */
    protected $middlewareLock = false;

    /**
     * 增加中間件
     * Add middleware
     * 這個方法增加一個新的中間件到應用中間件棧
     * This method prepends new middleware to the application middleware stack.
     * 中間件以回調形式存在:包括:1.一個請求對象 2.一個響應對象 3.next中間件回調函數
     * @param callable $callable Any callable that accepts three arguments:
     *                           1. A Request object
     *                           2. A Response object
     *                           3. A "next" middleware callable
     * @return static
     *
     * @throws RuntimeException         If middleware is added while the stack is dequeuing
     * @throws UnexpectedValueException If the middleware doesn't return a Psr\Http\Message\ResponseInterface
     */
    protected function addMiddleware(callable $callable)
    {
        // 若中間件鎖住,拋出異常
        if ($this->middlewareLock) {
            throw new RuntimeException('Middleware can’t be added once the stack is dequeuing');
        }

        // 棧頂元素爲空,則初始化棧(實質先存儲一個元素),該步驟之後
        // $this->tip保存當前應用主體類$app實例
        if (is_null($this->tip)) {
            $this->seedMiddlewareStack();
        }
        // 這一步是關鍵,不斷從棧中取出元素(中間件)
        // call_user_func的$next參數爲上一中間件(閉包)的返回值
        $next = $this->tip;
        $this->tip = function (
            ServerRequestInterface $request,
            ResponseInterface $response
        ) use (
            $callable,
            $next
        ) {
            $result = call_user_func($callable, $request, $response, $next);
            if ($result instanceof ResponseInterface === false) {
                throw new UnexpectedValueException(
                    'Middleware must return instance of \Psr\Http\Message\ResponseInterface'
                );
            }

            return $result;
        };

        return $this;
    }

    /**
     * 使用一個回調填充中間件棧
     * Seed middleware stack with first callable
     *
     * @param callable $kernel The last item to run as middleware
     *
     * @throws RuntimeException if the stack is seeded more than once
     */
    protected function seedMiddlewareStack(callable $kernel = null)
    {   // 填充前提是棧頂元素爲空
        if (!is_null($this->tip)) {
            throw new RuntimeException('MiddlewareStack can only be seeded once.');
        }

        // 將當前類(App)實例賦值給$kernel
        if ($kernel === null) {
            $kernel = $this;
        }

        // $this->tip元素即爲當前應用主體app實例
        $this->tip = $kernel;
    }

    /**
     * 調用中間件
     * Call middleware stack
     *
     * @param  ServerRequestInterface $request A request object
     * @param  ResponseInterface      $response A response object
     *
     * @return ResponseInterface
     */
    public function callMiddlewareStack(ServerRequestInterface $request, ResponseInterface $response)
    {
        if (is_null($this->tip)) {
            $this->seedMiddlewareStack();
        }
        /** @var callable $start */
        // 取出棧頂元素(閉包),取的過程要鎖棧,取出之後再打開
        // 鎖棧的過程是不可增加中間件的
        $start = $this->tip;
        $this->middlewareLock = true;
        $response = $start($request, $response);
        $this->middlewareLock = false;
        return $response;
    }
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章