Middleware ========== Okay, so what exactly **is** middleware? It's a nebulous term, and it's a bit reminiscent of the Underpants gnomes. - Phase 1: Request - Phase 2: ??? - Phase 3: Response Middleware is indeed Phase 2. It's something (a callable or object) that takes a request and a response as inputs, does something with the response, and sends the altered response back out. A Web service can built from many, many pieces of middleware, with each piece managing a specific task such as authentication or parsing representations. When each middleware runs, it is responsible for propagating the request through to the next middleware in the sequence—or deciding not to. So what's it look like? In essence, a single piece of middleware looks something like this: .. code-block:: php function ($request, $response, $next) { // Update the response. /* $response = ... */ // Determine if any other middleware should be called after this. if (/* Stop now without calling more middleware? */) { // Return the response without calling any other middleware. return $response; } // Let the next middleware work on the response. This propagates "up" // the chain of middleware, and will eventually return a response. $response = $next($request, $response); // Possibly update the response some more. /* $response = ... */ // Return the response. return $response; } Defining Middleware ^^^^^^^^^^^^^^^^^^^ Middleware can be a callable (as in the `Getting Started`_) or an implementation of the ``WellRESTed\MiddlewareInterface`` (which implements ``__invoke`` so is technically a callable, too). .. rubric:: Callable .. code-block:: php /** * @param Psr\Http\Message\ServerRequestInterface $request * @param Psr\Http\Message\ResponseInterface $response * @param callable $next * @return Psr\Http\Message\ResponseInterface */ function ($request, $response, $next) { } .. rubric:: MiddlewareInterface .. code-block:: php register("GET,PUT,DELETE", "/widgets/{id}", 'Webservice\Widgets\WidgetHandler'); The class is not loaded, and no instances are created, until the route is matched and dispatched. Even for a router with 100 routes, no middleware registered by string name is loaded, except for the one that matches the request. Callable Provider ----------------- You can also use a callable to instantiate and return a ``MiddlewareInterface`` instance or middleware callable. .. code-block:: php $router->add("GET,PUT,DELETE", "/widgets/{id}", function () { return new \Webservice\Widgets\WidgetHandler(); }); This still delays instantiation, but gives you some added flexibility. For example, you could define middleware that receives some configuration upon construction. .. code-block:: php $container = new MySuperCoolDependencyContainer(); $router->add("GET,PUT,DELETE", "/widgets/{id}", function () use ($container) { return new \Webservice\Widgets\WidgetHandler($container); }); This is one approach to `dependency injection`_. Middleware Callable ------------------- Use a middleware callable directly. .. code-block:: php $router->add("GET,PUT,DELETE", "/widgets/{id}", function ($request, $response, $next) { $response = $response->withStatus(200) ->withHeader("Content-type", "text/plain") ->withBody(new \WellRESTed\Message\Stream("It's a bunch of widgets!")); return $next($request, $response); }); Instance -------- You can also provide pass an instance directly as middleware. .. code-block:: php $router->add("GET,PUT,DELETE", "/widgets/{id}", new \Webservice\Widgets\WidgetHandler()); .. warning:: This is simple, but has a significant disadvantage over the other options because each middleware used this way will be loaded and instantiated, even though only one middleware will actually be used for a given request-response cycle. You may find this approach useful for testing, but avoid if for production code. Array ----- Why use one middleware when you can use more? Provide a sequence of middleware as an array. Each component of the array can be any of the varieties listed in this section. When dispatched, the middleware in the array will run in order, with each calling the one following via the ``$next`` parameter. .. code-block:: php $router->add("GET", "/widgets/{id}", ['Webservice\Auth', $jsonParser, $widgetHandler]); Chaining Middleware ^^^^^^^^^^^^^^^^^^^ Chaining middleware together allows you to build your Web service in a discrete, modular pieces. Each middleware in the chain makes the decision to either move the request up the chain by calling ``$next``, or stop propagation by returning a response without calling ``$next``. Propagating Up the Chain ------------------------ Imagine we want to add authorization to the ``/widgets/{id}`` endpoint. We can do this without altering the existing middleware that deals with the widget itself. What we do is create an additional middleware that performs just the authorization task. This middleware will inspect the incoming request for authorization headers, and either move the request on up the chain to the next middleware if all looks good, or send a request back out with an appropriate status code. Here's an example authorization middleware using pseudocode. .. code-block:: php namespace Webservice; class Authorization implements \WellRESTed\MiddlewareInterface { public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next) { // Validate the headers in the request. try { $validateUser($request); } catch (InvalidHeaderException $e) { // User did not supply the right headers. // Respond with a 401 Unauthorized status. return $response->withStatus(401); } catch (BadUserException $e) { // User is not permitted to access this resource. // Respond with a 403 Forbidden status. return $response->withStatus(403); } // No exception was thrown, so propagate to the next middleware. return $next($request, $response); } } We can add authorization for just the ``/widgets/{id}`` endpoint like this: .. code-block:: php $router->register("GET,PUT,DELETE", "/widgets/{id}", [ 'Webservice\Authorization', 'Webservice\Widgets\WidgetHandler' ]); Or, if you wanted to use the authorization for the entire service, you can add it to the ``Server`` in front of the ``Router``. .. code-block:: php $server = new \WellRESTed\Server(); $server ->add('Webservice\Authorization') ->add($server->createRouter() ->register("GET,PUT,DELETE", "/widgets/{id}", 'Webservice\Widgets\WidgetHandler') ) ->respond(); Moving Back Down the Chain -------------------------- The authorization example returned ``$next($request, $response)`` immediately, but you can do some interesting things by working with the response that comes back from ``$next``. Think of the request as taking a round trip on the subway with each middleware being a stop along the way. Each of the stops you go through going up the chain, you also go through on the way back down. We could add a caching middleware in front of ``GET`` requests for a specific widget. This middleware will check if a cached representation exists for the resource the client requested. If it exists, it will send it out to the client without ever bothering the ``WidgetHandler``. If there's no representation cached, it will call ``$next`` to propagate the request up the chain. On the return trip (when the call to ``$next`` finishes), the caching middleware will inspect the response and store the body to the cache for next time. Here's a pseudocode example: .. code-block:: php namespace Webservice; class Cache implements \WellRESTed\MiddlewareInterface { public function dispatch(ServerRequestInterface $request, ResponseInterface $response, $next) { // Inspect the request path to see if there is a representation on // hand for this resource. $representation = $this->getCachedRepresentation($request); if ($representation !== null) { // There is already a cached representation. Send it out // without propagating. return $response ->withStatus(200) ->withBody($representation); } // No representation exists. Propagate to the next middleware. $response = $next($request, $response); // Attempt to store the response to the cache. $this->storeRepresentationToCache($response); return $response; } private function getCachedRepresentation(ServerRequestInterface $request) { // Look for a cached representation. Return null if not found. // ... } private function storeRepresentationToCache(ResponseInterface $response) { // Ensure the response contains a success code, a valid body, // headers that allow caching, etc. and store the representation. // ... } } We can add this caching middleware in the chain between the authorization middleware and the Widget. .. code-block:: php $router->register("GET,PUT,DELETE", "/widgets/{id}", [ 'Webservice\Authorization', 'Webservice\Cache', 'Webservice\Widgets\WidgetHandler' ]); Or, if you wanted to use the authorization and caching middleware for the entire service, you can add them to the ``Server`` in front of the ``Router``. .. code-block:: php $server = new \WellRESTed\Server(); $server ->add('Webservice\Authorization') ->add('Webservice\Cache') ->add($server->createRouter() ->register("GET,PUT,DELETE", "/widgets/{id}", 'Webservice\Widgets\WidgetHandler') ) ->respond(); .. _Dependency Injection: dependency-injection.html .. _Getting Started: getting-started.html