Browse Source

Implement the middleware runner.

This runner class implements the `$next` callable that is used by
middleware objects to signal that the next middleware object should take
control of the request/response.
Mark Story 10 years ago
parent
commit
b89a84565a
3 changed files with 224 additions and 2 deletions
  1. 3 2
      composer.json
  2. 69 0
      src/Http/Runner.php
  3. 152 0
      tests/TestCase/Http/RunnerTest.php

+ 3 - 2
composer.json

@@ -23,10 +23,11 @@
         "ext-mbstring": "*",
         "cakephp/chronos": "*",
         "aura/intl": "1.1.*",
-        "psr/log": "1.0"
+        "psr/log": "1.0",
+        "zendframework/zend-diactoros": "~1.0"
     },
     "suggest": {
-      "ext-openssl": "To use Security::encrypt() or have secure CSRF token generation."
+        "ext-openssl": "To use Security::encrypt() or have secure CSRF token generation."
     },
     "require-dev": {
         "phpunit/phpunit": "*",

+ 69 - 0
src/Http/Runner.php

@@ -0,0 +1,69 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         3.3.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\Http;
+
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * Executes the middleware stack and provides the `next` callable
+ * that allows the stack to be iterated.
+ */
+class Runner
+{
+    /**
+     * The current index in the middleware stack.
+     *
+     * @var int
+     */
+    protected $index;
+
+    /**
+     * The middleware stack being run.
+     *
+     * @var MiddlewareStack
+     */
+    protected $middleware;
+
+    /**
+     * @param \Cake\Http\MiddlewareStack $middleware The middleware stack
+     * @param \Psr\Http\Message\ServerRequestInterface $request The Server Request
+     * @param \Psr\Http\Message\ResponseInterface $response The response
+     * @return \Psr\Http\Message\ResponseInterface A response object
+     */
+    public function run($middleware, ServerRequestInterface $request, ResponseInterface $response)
+    {
+        $this->middleware = $middleware;
+        $this->index = 0;
+        return $this->__invoke($request, $response);
+    }
+
+    /**
+     * @param \Psr\Http\Message\ServerRequestInterface $request  The server request
+     * @param \Psr\Http\Message\ResponseInterface $response The response object
+     * @return \Psr\Http\Message\ResponseInterface An updated response
+     */
+    public function __invoke(ServerRequestInterface $request, ResponseInterface $response)
+    {
+        $next = $this->middleware->get($this->index);
+        if ($next) {
+            $this->index++;
+            return $next($request, $response, $this);
+        }
+
+        // End of the stack
+        return $response;
+    }
+}

+ 152 - 0
tests/TestCase/Http/RunnerTest.php

@@ -0,0 +1,152 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         3.3.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\Test\TestCase;
+
+use Cake\Http\MiddlewareStack;
+use Cake\Http\Runner;
+use Cake\TestSuite\TestCase;
+use RuntimeException;
+
+/**
+ * Test case for runner.
+ */
+class RunnerTest extends TestCase
+{
+    /**
+     * setup
+     *
+     * @return void
+     */
+    public function setUp()
+    {
+        parent::setUp();
+        $this->stack = new MiddlewareStack();
+
+        $this->ok = function ($req, $res, $next) {
+            return $next($req, $res);
+        };
+        $this->pass = function ($req, $res, $next) {
+            return $next($req, $res);
+        };
+        $this->noNext = function ($req, $res, $next) {
+        };
+        $this->fail = function ($req, $res, $next) {
+            throw new RuntimeException('A bad thing');
+        };
+    }
+
+    /**
+     * Test running a single middleware object.
+     *
+     * @return void
+     */
+    public function testRunSingle()
+    {
+        $this->stack->push($this->ok);
+        $req = $this->getMock('Psr\Http\Message\ServerRequestInterface');
+        $res = $this->getMock('Psr\Http\Message\ResponseInterface');
+
+        $runner = new Runner();
+        $result = $runner->run($this->stack, $req, $res);
+        $this->assertSame($res, $result);
+    }
+
+    /**
+     * Test replacing a response in a middleware.
+     *
+     * @return void
+     */
+    public function testRunResponseReplace()
+    {
+        $one = function ($req, $res, $next) {
+            $res = $this->getMock('Psr\Http\Message\ResponseInterface');
+            return $next($req, $res);
+        };
+        $this->stack->push($one);
+        $runner = new Runner();
+
+        $req = $this->getMock('Psr\Http\Message\ServerRequestInterface');
+        $res = $this->getMock('Psr\Http\Message\ResponseInterface');
+        $result = $runner->run($this->stack, $req, $res);
+
+        $this->assertNotSame($res, $result, 'Response was not replaced');
+        $this->assertInstanceOf('Psr\Http\Message\ResponseInterface', $result);
+    }
+
+    /**
+     * Test that middleware is run in sequence
+     *
+     * @return void
+     */
+    public function testRunSequencing()
+    {
+        $log = [];
+        $one = function ($req, $res, $next) use (&$log) {
+            $log[] = 'one';
+            return $next($req, $res);
+        };
+        $two = function ($req, $res, $next) use (&$log) {
+            $log[] = 'two';
+            return $next($req, $res);
+        };
+        $three = function ($req, $res, $next) use (&$log) {
+            $log[] = 'three';
+            return $next($req, $res);
+        };
+        $this->stack->push($one)->push($two)->push($three);
+        $runner = new Runner();
+
+        $req = $this->getMock('Psr\Http\Message\ServerRequestInterface');
+        $res = $this->getMock('Psr\Http\Message\ResponseInterface');
+        $result = $runner->run($this->stack, $req, $res);
+
+        $this->assertSame($res, $result, 'Response is not correct');
+
+        $expected = ['one', 'two', 'three'];
+        $this->assertEquals($expected, $log);
+    }
+
+    /**
+     * Test that exceptions bubble up.
+     *
+     * @expectedException RuntimeException
+     * @expectedExceptionMessage A bad thing
+     */
+    public function testRunExceptionInMiddleware()
+    {
+        $this->stack->push($this->ok)->push($this->fail);
+        $req = $this->getMock('Psr\Http\Message\ServerRequestInterface');
+        $res = $this->getMock('Psr\Http\Message\ResponseInterface');
+
+        $runner = new Runner();
+        $runner->run($this->stack, $req, $res);
+    }
+
+    /**
+     * Test that 'bad' middleware returns null.
+     *
+     * @return void
+     */
+    public function testRunNextNotCalled()
+    {
+        $this->stack->push($this->noNext);
+        $req = $this->getMock('Psr\Http\Message\ServerRequestInterface');
+        $res = $this->getMock('Psr\Http\Message\ResponseInterface');
+
+        $runner = new Runner();
+        $result = $runner->run($this->stack, $req, $res);
+        $this->assertNull($result);
+    }
+}