-
Notifications
You must be signed in to change notification settings - Fork 11.4k
Can't catch exception in middleware #14573
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Yes, this is the beavhiour starting from L5.2. Throwing an exception causes the response to be set as that returned from the exception handler, and then the middleware is allowed to backout from that point. |
Back out from that point how? Where is my exception? How can non-app code do something with specific exceptions? Do I really extract it from the response object? That seems silly... |
We don't recommend you write try catch blocks in middleware. Instead, handle exceptions inside the exception handler. Perhaps https://github.com/GrahamCampbell/Laravel-Exceptions would be of use to you? |
Yeah, I don't like that, because only 1 party can extend a base class. What if 4 packages want specific exception handling? That's why middleware is perfect. This does the trick, but it feels really funky:
Is that what you recommend? It can't be in Thanks for super fast replies! I'm still curious how this handling works though. I was trying to debug the middleware pipeline, but I couldn't find any exception catching. I really thought my middleware was the first to catch it. Where does that happen? |
I guess it would be conventional for the application to still handle package exceptions. Feel free to ask around on the forums for solutions other people might have. In terms of where does this happen, it happens in our pipeline. Starting with L5.2, there's a custom http pipeline: https://github.com/laravel/framework/blob/5.2/src/Illuminate/Routing/Pipeline.php. |
Your observation about accessing the exception on the response could be useful for package exception handling, but I can't say it's something I've had to do before (hence why I'd recommend asking on the forums to find someone who might have tried to do this). |
What a crappy framework!!! |
Hi! The correct way is don't catch errors directly on middleware. You need add a custom ExceptionHandler. If you like register handler on your middleware, you can do it, but forget try/catch. How catch errors without touching Register your /* @var ExceptionHandler Illuminate\Contracts\Debug\ExceptionHandler */
$previousHandler = null;
if (app()->bound(ExceptionHandler::class) === true) {
$previousHandler = app()->make(ExceptionHandler::class);
}
app()->singleton(ExceptionHandler::class, function () use ($previousHandler) {
return new CustomExceptionHandler($previousHandler);
}); And your basic class CustomExceptionHandler implements ExceptionHandlerInterface
{
/**
* @var ExceptionHandlerInterface|null
*/
private $previous;
public function __construct(ExceptionHandlerInterface $previous = null)
{
$this->previous = $previous;
}
public function report(Exception $exception)
{
$this->previous === null ?: $this->previous->report($exception);
}
public function render($request, Exception $exception)
{
if ($exception instanceof CustomExceptionHandler) {
echo 'This is my particular way to show my errors';
} else {
$response = $this->previous === null ? null : $this->previous->render($request, $exception);
}
return $response;
}
/**
* {@inheritdoc}
*/
public function renderForConsole($output, Exception $exception)
{
/* @var OutputInterface $output */
$this->previous === null ?: $this->previous->renderForConsole($output, $exception);
}
} |
I'm pretty sure Barryvdh\Debugbar does something like this. I just encountered a scenario where a middleware was bombing out, and failing quietly. No errors in the log, and only a vague 500 error response. Then I noticed that only Debugbar was detecting what was actually wrong. It looks like the package includes its own Middleware\Debugbar class designed to handle middleware exceptions, a similar tactic to what @pablorsk was describing. I'm dissecting it at the moment to see if it can offer any clues to how I might write my own handler that can exist outside of debug-mode. I've gotten as far as identifying the try/catch in Debugbar::handle as a red herring. That middleware exceptions end up in the $response->exceptions, as stated earlier, though I'm not certain how yet. They begin getting handled in Barryvdh\Debugbar\LaravelDebugbar::modifyResponse(). Beyond that, tracing gets a little tricky, since the data gets buried in associative arrays and inherited class properties, rather than method responses. And pablorsk's answer may spare me having to do that. But @rudiedirkx, you may want to have a look. Or, if you only need this for development, just install that package and don't worry about it. |
What about the situation, when some small part of application goes through this middleware and I don't wan't to handle it global handler? Is it possible to disable this pipeline? |
@warlof - Well you don't have to register all middleware in the Kernel. You could apply it with the Illuminate\Routing\Controller::middleware() method, meaning you could wrap some logic around when to apply it. You could also manage the behavior therein with some configuration variables, maybe derived from environment ones, and even alter those config values within your app (though that feels a little sloppy). There's also a 'middleware.disable' flag that you can leverage, but that's kind of all-or-nothing thing, usually used for unit tests. I'm not entirely certain how to use it outside of the PhpUnit WithoutMiddleware trait, but you could take a look at how that works, and the Illuminate\Routing\Router::runRouteWithinStack() method that actually looks at the flag. You might also consider writing an override for the Illuminate\Routing\Router::gatherRouteMiddleware() method. Maybe something that could look to a config array of 'allowable' middlewares, as it compiles what ones are assigned to the given route. |
I'm developing a package for user tracking. Its called from a middleware, but if anything fails, I want to just log the error and go further, that the user doesn't recognize anything about that, because it's not app critical. I want to do that in the package itself, not in the application. Isn't that possible to achieve? |
The trouble is, I think Middleware follows the Chain of Responsibility pattern, similar to a Handler-Stack, by way of a series of nested Closures. I'm not really sure how to interrupt that flow, given that you can't exactly write an override class for a Closure that could try-catch the error, and more importantly, have an awareness of what the next link in the chain is. Middleware classes don't extend a common base-class for you to leverage either. So the only thing I could think of would be to take a step further back from that, to how Middleware gets stored and executed in the first place. Have something that wraps each closure in a closure of your own that's designed to handle the errors and then move on to the next one. Current flow:
Your flow:
I'm not really sure where what class you'd have to override to do this. I think I'd start by dissecting the Kernel, or using debug_backtrace inside of one of the Middleware classes to see what fires it. Best of luck. And please let me know how it turns out. |
@ivorobioff I will be happy to work with your "not crappy" framework anytime soon |
Developers should not be expected to work outside standards to handle catch issues in when using a standard try catch... I spent hours looking for why this didn't work as expected. If middleware needs to call an external API to validate a user's authentication the BEST way to handle exceptions is within the the middleware is not external to middleware. Not supporting basic programming constructs is not a good method. (Granted I'm new to laravel... but still...) In addition middleware should have the capability to dictate the response. Older versions of Laravel (pre 6.x) seemed to handle this based on research but based on my current app no matter what I return from the middleware it comes back as 200 (with empty content) even when I dictate a 401 or 500. |
@Cleanshooter While it's not possible to use try / catch in a middleware, it's indeed possible to dictate the response with throwing an exception or using the abort() helper: public function handle($request, Closure $next)
{
if(! $notAuthenticated){
throw new AuthenticationException();
// or something like:
abort('401', 'Expired Token');
}
} |
@ndberg Thank you for this, I will give it a try. Beyond the documentation here: https://laravel.com/docs/6.x/middleware where would I have found this helper function? I looked for quite a while for this yesterday but couldn't find what interfaces/methods are available for middleware. |
Here you go https://laravel.com/docs/6.x/helpers |
Thanks for this but this just begs the question, "Why when I used the response() helper in the middleware did it not work." Using 6.9, I tried to use (multiple variations of) response('UNAUTHORIZED', 401);. No matter what I did, however my ngrok reported returning an OK, 200. How are we as developers using the framework to discern which helpers work for which implementation (controler, middleware, model, view, ect.) if they are global and the documentation doesn't indicate what works where. I believe the current options are: trial and error, ask on githib 😉... Thank again for the responses! I'm still learning Laravel and comming from a nodeJS background. Appreciate the patience. |
The thing is you can't return from the middleware what you want, you must call and return the next layer of the middleware stack: return $next($request); The only other option is to raise an exception. |
This is all very old I know, but readers might not know this.
That's not true, any middleware can just return any response and ignore
|
Uh oh!
There was an error while loading. Please reload this page.
I want a middleware package to catch an exception in its middleware.
But the exception can't be caught by the middleware, because it's already been caught and handled somewhere else. The
$response
object ($next()
return value) is an exception error page.Who catches my exception, and where, and why so early? Aren't exceptions allowed in the middleware pipeline?
The app can extend its
App\Exceptions\Handler
to 'render' that exception into a redirect, but I want all logic to be in the package, not just half.Middleware looks like this:
The text was updated successfully, but these errors were encountered: