Catching exceptions in middleware

expressive

#1

Good day,
I’ve stumbled upon one problem I don’t think I understand.

I have middleware called ErrorHandler:

class ErrorHandler implements \Zend\Stratigility\MiddlewareInterface
{
    public function __invoke(
        \Psr\Http\Message\ServerRequestInterface $request,
        \Psr\Http\Message\ResponseInterface $response,
        callable $next = null
    ) {
        try {
            if ($next) {
                return $next($request, $response);
            }
        } catch (\FW\Exception\NotFound $e) {
            return new \Zend\Diactoros\Response\TextResponse('404 Not found :(');
        }
        return $response;
    }
}

And I have a simple route that should fire up this middleware, that throws the exception:

$api->get('/404', [
    \App\Middleware\ErrorHandler::class,
    function ($request, \Interop\Http\ServerMiddleware\DelegateInterface $delegate) {
        throw new \FW\Exception\NotFound();
        return new \Zend\Diactoros\Response\TextResponse('Hello, world B!');
    }
]);

For some reason my exception is being uncaught and results in a fatal error. What do I miss or do wrong?
My use case should be pretty obvious, in any of the routes I want to throw different exceptions and then handle them in the error handler returning the corresponding response.

Don’t think of unexisting routes, but more about resolved routes that have no content to display: /user/125 where user with ID 125 doesn’t exist in database.

Thanks in advance.


#2

@Sergey_Telshevsky You may be thinking of error middleware in perhaps the wrong context. In your code you are trying to call the error handler via a dispatched route. Normally you want to pipe the error handler - not dispatch it. A \FW\Exception\NotFound() would be thrown inside something like a \FW\UserAction class that is then caught by the error middleware. Let’s see if I can more graphically demonstrate this. Here is a section of my index.php. Note how I’m piping my ErrorHandler class at the beginning of the pipeline:

Pipeline

// Piped before we identify a route
$app->pipe([
    ErrorHandler::class,
    OriginalMessages::class,
    Helper\ServerUrlMiddleware::class,
    Helper\BodyParams\BodyParamsMiddleware::class
]);

$app->pipeRoutingMiddleware();

//Piped when we know the route, but before dispatching.
$app->pipe(Helper\UrlHelperMiddleware::class);

$app->pipeDispatchMiddleware();

//Routed Actions
$app->route('/user', \FW\RoutableAction\UserAction::class, ['GET', 'POST', 'PATCH', 'DELETE'], 'USER');

In this case, I’m using the default ErrorHandler class that comes with Stratigility 2.x. This uses a generator class to create the actual error response, and it’s there that you can return something different for 400, 401, 403, 404, 500, etc errors.

What the ErrorHandler class is basically doing is akin to:

public function __invoke(
        \Psr\Http\Message\ServerRequestInterface $request,
        \Psr\Http\Message\ResponseInterface $response,
        callable $next = null
    ) {
        try {
           $response = $next($request, $response);
        } catch Throwable $t) {
            return $this->handleViaGenerator($t);
        }
        return $response;
    }

In your routed action UserAction you might have:

public function __invoke(
        \Psr\Http\Message\ServerRequestInterface $request,
        \Psr\Http\Message\ResponseInterface $response,
        callable $next = null
    ) {
        $user = $this->getUserFromDb($request);
        if (!$user) {
          throw new \FW\Exception\NotFound();
        }
        $response = $this->formatUser($user);

        return $response;
    }

The execution chain is along the lines of ErrorHandler->router->dispatcher->routedAction->ErrorHandler->browser. If there is no exception, the ErrorHandler just passes the response along up the chain to the be displayed. If it catches an exception thrown by a routed action, it will then generate its own response to pass up the chain.

On a side note, if you haven’t created dozens of middleware classes yet, you may want to look into utilizing the newer PSR-7 style middleware. It is a bit more simplified and is the style Expressive 2 and Stratigility 2 prefer.

–MDG


#3

The way this was routed should still work, as @Sergey_Telshevsky is creating a routed middleware pipeline (by providing an array of middleware/middleware service names to get()).

I’ve personally just tried the following:

  • Created a new Expressive application (from v2.0.3) using all the defaults.
  • Added zendframework/zend-expressive-tooling as a development requirement.
  • Used vendor/bin/expressive middleware:create to create two middleware, App\ErrorHandler and App\ThrowAction.
  • Created App\NotFoundException.
  • Edited App\ErrorHandler to do a try/catch block that catches App\NotFoundException and return a TextResponse with the contents you provided above.
  • Edited App\ThrowAction to throw an App\NotFoundException.
  • Added the two new middleware as invokables in my application.
  • Created a route just like your above one, only using my named services.

And… visiting the route I created, I get back a 200 OK response with the text contents from the generated response. In other words, it worked.

Is it possible you’re hitting the wrong URI? Have you tried using an actual middleware class instead of the callable?


#4

@matthew You’re right (of course!), I didn’t notice that. I do notice that he’s using double pass for the ErrorHandler and single pass for the callable, but that shouldn’t cause a problem?


#5

That’s why I asked if they could try an actual middleware class instead of a callable. :smiley:


#6

Sorry for all the hassle if I didn’t mention some of the important parts. I’m trying to get into the PSR-7 thing, but for some reason having trouble with that.

What helped as you mentioned, is to change the double pass closure to a single pass with ($request, $response, $next) arguments :slight_smile:

Is it because they’re processed in different flows?
Some more questions because I didn’t find a definite answer, is double-pass deprecated?

What does actually single pass mean? I’ve seen only two signatures: ($request, $response, $next), ($request, $delegate), but there has been something in the docs about single-pass passing only the $request? So what are the differences between them, as in pros & cons.

What is the ->process() method? I’ve only been using __invoke(), is it really an alias? If so, does it work only with expressive? What’s the preferred one, so I will not be locked in expressive?

Also there’s some talks about programmatic pipeline, what is this and what’s the oppopsite?

P.S. Please, don’t think I didn’t google this, I did, but I have read so many controversial information, probably because it gets outdated quickly for new standards.

Thank you both @matthew @moderndeveloperllc for your replies!


#7

Double-pass is a term coined to describe the signature function ($request, $response, $next), and refers to the fact that you pass both the request and response when calling the function that delegates to another middleware: $next($request, $response). Single-pass, or lambda, refers to the signature that passes only the request: function ($request, $delegate), which then resolves to $delegate->process($request).

Double-pass was an idea borrowed from Connect and ExpressJS, two node.js projects, which used that same signature. (They also had an additional signature, which Stratigility and Expressive v1 versions used, which allowed passing an optional additional $err argument to each for creating error middleware chains; we’ve since dropped those.) The reason that these projects passed the response is because of the way node.js’s http module works: it creates a single response, which you then manipulate; the server always works with that instance.

With PHP and PSR-7, this is not the case. All HTTP message instances are instead immutable. In Expressive, the Application dispatches its pipeline, and then emits the response returned by the pipeline. As such, it’s the return value from middleware that’s more important.

Further, passing the response in has potential problems: if you pass in one with values (such as default headers, or default content), there’s no guarantee that those values will be present in a response returned from deeper in the middleware chain. As such, passing it around can lead to a false sense of security, and potentially cause some hard to discover bugs.

As such, the proposed PSR-15 (HTTP server middleware) standard promotes a single-pass or lambda style, and Stratigility and Expressive v2 have adopted this as the default style to use. While you can continue to use double-pass, these actually get decorated internally as http-interop middleware (http-interop is the interim project until PSR-15 is approved).

The signatures for http-interop are as follows:

namespace Interop\Http\ServerMiddleware;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

interface DelegateInterface
{
    /**
     * @return ResponseInterface
     */
    public function process(ServerRequestInterface $request);
}

interface MiddlewareInterface
{
    /**
     * @return ResponseInterface
     */
    public function process(ServerRequestInterface $request, DelegateInterface $delegate);
}

Note that these do not use __invoke(); this was a decision made to allow existing libraries and middleware to gradually adopt the new standard, while remaining backwards compatible with existing code.

If you use zend-expressive-tooling, you can use the ./vendor/bin/expressive middleware:create CLI tooling to create middleware that conforms to this signature, which should be forwards compatible.

In Expressive v1, we used a configuration-driven approach, where you would define your middleware pipeline and routing in configuration files. Users, however, reported that it was far easier to understand how it all worked when working directly with the $app instance than with configuration files, so for v2 we largely deprecated configuration-driven approaches. The manual contains some examples if you’re interested.


#8

@Sergey_Telshevsky The spot you want to look at for the whole ($request, $response, $next) [double pass] vs ($request, $delegate) [single, or lambda, pass] is the proposed PSR-15 standard.

@matthew can most concisely describe the change, but the main difference between the two philosophies is: When do you “decorate” the $response object? In double pass, the standard way to call the next middleware in the pipeline (assuming you aren’t returning an actual response) is with

//Decorate the $response object before passing along to next stage in the pipeline
return $next($request, $response);

//We can't check the $response for changes!

This means that you are modifying the $response as you go into the pipeline. This can lead to unexpected consequences if one of the middleware further into the pipeline also modifies the $response object. The idea behind single pass is that you modify the $response object on the way out of the pipeline. Remember that a middleware pipeline is kinda like a recursive function in that it calls the first middleware class that then calls the next “deeper” middleware and so on until we exhaust the pipeline with a middleware that returns a $response object. At this point that object is returned back “up” or “out” of the pipeline stack. In the traditional double pass of $next($request, $response) the middleware never gets to see that response object as its just immediately passed up and out. Yes, you could very well do

//Decorate the $response object before passing along to next stage in the pipeline
$response = $next($request, $response);

//Also decorate here?

return $response;

but then what was the purpose of passing a $response down into the pipeline in the first place? Single pass methodology forces you to only deal with the $response on the way up and out the pipeline:

//We can modify the request, but not the response
$response = $delegate->process($middlewareModifiedRequest);

//Decorate the $response object here

return $response;

Decorating the $response on the way out of the pipeline makes more sense as you actually know the final disposition of the request - Did I succeed (200)? Did I fail? (400-500)? Is there a body to the response?

The linked PSR proposal goes into why they aren’t doing __invoke() anymore. Sorry if you knew all this already, but if nothing else, it helps firm it up in my mind a bit more!

How to handle errors in a single pass middleware stack?
There are a few perfectly legitimate ways of doing this.

  1. Create the error response at the point of failure. An ErrorHandler class is only to decorate that on the way up/out of the pipeline and to catch programatic problems that throw something (can’t connect to DB, etc). To use your “user not found” example:
//Abbreviated pipeline
$api->get('/user', [
    \App\Middleware\ErrorHandler::class,
    \App\Action\User::class
]);

//User.php
public function process(ServerRequestInterface $request, DelegateInterface $delegate): ResponseInterface
{
  $user = $this->getFromDb($request);
  if (!$user) {
    return new TextResponse('No User Found', 401);
  }
  $response = $this->formatUser($user);

  return $response;
}

I know @matthew prefers this way as a routed action will always return a $response object in normal use and will always pass back up the entire pipeline.

  1. throw everything and have it caught by the ErrorHandler. In this philosophy, every error or exception (400-500 response codes) is a Throwable and the $response object creation is handled by the ErrorHandler.
//Abbreviated pipeline
$api->get('/user', [
    \App\Middleware\ErrorHandler::class,
    \App\Action\User::class
]);

//User.php
public function process(ServerRequestInterface $request, DelegateInterface $delegate): ResponseInterface
{
  $user = $this->getFromDb($request);
  if (!$user) {
    throw new InvalidArgumentException('No User Found', 401);
  }
  $response = $this->formatUser($user);

  return $response;
}

I like doing this for a few reasons. a) All my error response object generation is in one spot regardless of if its a user or programmatic error. b) Some of my middleware has lots of calls to various external service classes and methods. I can throw the problem from anywhere, and it will “short-circuit” the processing and immediately get caught by the ErrorHandler class. I don’t have to check each return from every method to see if I’m valid to move on to the next piece of logic. The downside is that it does skip the pipeline al the way back up to the ErrorHandler class. If you have $response decorators in that pipeline they will not be called. For my programming style, that’s not an issue, but it may be in yours.

I hope this isn’t too redundant!

–Mark

EDIT: Matthew is a faster typer than I, or got to this first. I’ll leave this up anyways as it may help. -M