I have a laravel controller that can throw an exception, and a global middleware that catches that exception. In semi pseudo code:
// App\Controllers\...
class Controller {
function store() {
throw new FormException; // via validation etc, but it's thrown here
}
}
// App\Http\Middleware\...
class Middleware {
function handle(Closure $next) {
try {
// Breakpoint 1
return $next(); // $response
// Breakpoint 2
}
catch (FormException $ex) {
// Breakpoint 3
exit('FormException caught!');
}
}
}
The problem is that the exception is never caught. Somwhere in the pipeline, the application catches the exception and prints a pretty error page, but it should be caught by my middleware so it can handle it properly.
Breakpoint 1 should trigger, and it does << good
Breakpoint 2 shouldn't trigger, and it doesn't << good
Breakpoint 3 should trigger, but it doesn't << what??
The only way I can imagine my middleware not catching it, is if it's caught somewhere deeper inside the pipeline, not further up/around, but I can't find any try/catch in other middleware, or in the pipeline execution code.
Where is this exception caught? Why?
This might not be a great pattern, but I don't care about that now. I'm more curious than anything else. Do I completely misunderstand Laravel's middleware?
My own super simple middleware test does what I expected: https://3v4l.org/Udr84 - catch and handle exception inside middleware.
Notes:
The $response object (return value of $next()) is the handled exception page, so it has already been handled. Where and why?
Handling the exception in App\Exceptions\Handler::render() works, but I want all logic to be in a middleware package, not in app code.
Relevant Laravel code:
Kernel::handle() starts the middleware pipeline << this has a catch-all catch(), but my catch() comes first, right?
Pipeline::then() starts the middleware execution
Pipeline::getSlice() handles and creates the $next closures
Apparently this is by design:
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.
I think that's very strange. Pluggable middleware would be perfect for catching exceptions.
Two ways to still do this:
Proper: in App\Exceptions\Handler, which is not good enough, because a package can't touch that
Funky: take the original exception object from the response object:
$response = $next($request);
$exception = $response->exception;
I had the same problem. When reading the thread Rudie mentioned, they give a possible solution there which worked for me:
public function handle(Request $request, Closure $next) {
$response = $next($request);
// 'Catch' our FormValidationException and redirect back.
if (!empty($response->exception) && $response->exception instanceof FormValidationException) {
return redirect()->back()->withErrors($response->exception->form->getErrors())->withInput();
}
return $response;
}
Looking at the source code, you need to catch both \Exception and \Throwable for your try catch to properly work in your middleware. This works on Laravel 5.8
class TryCatchMiddleware
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
try {
if ( somethingThatCouldThrowAnException() ) {
$request->newVariable = true;
}
} catch (\Exception $e) {
// do nothing
} catch (\Throwable $e) {
// do nothing
}
return $next($request);
}
}
How catch errors without touching App\Exceptions\Handler file:
Register your CustomExceptionHandler
/* #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 CustomExceptionHandler
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 think I can see why your code doesn't catch exceptions. Please try using the following code for your handle method:
function handle(Closure $next) {
try {
// Breakpoint 1
$response = $next();
// Breakpoint 2
}
catch (FormException $ex) {
// Breakpoint 3
exit('FormException caught!');
}
return $response;
}
The code above has not been tested but as you look at the Laravel documentation you can see before returning the response, you should perform your code (in this case, your exception handling logic). Please look at: Laravel - Defining Middleware for more information on Before & After Middleware definition.
And by the way, also look at this file: Laravel/app/Exceptions/Handler.php which I believe is a better place to handle your exceptions globally.
Related
In the clean code book is an example about using exceptions rather than return codes:
You either set an error flag or returned an error code.
public class DeviceController {
...
public void sendShutDown() {
DeviceHandle handle = getHandle(DEV1);
// Check the state of the device
if (handle != DeviceHandle.INVALID) {
// Save the device status to the record field
retrieveDeviceRecord(handle);
// If not suspended, shut down
if (record.getStatus() != DEVICE_SUSPENDED) {
pauseDevice(handle);
clearDeviceWorkQueue(handle);
closeDevice(handle);
} else {
logger.log("Device suspended. Unable to shut down");
}
} else {
logger.log("Invalid handle for: " + DEV1.toString());
}
}
...
}
Unfortunately, it's easy to forget. For this reason, it is better to throw an exception when you encounter an error. The calling code is cleaner. Its logic is not obscured by error handling.
public class DeviceController {
...
public void sendShutDown() {
try {
tryToShutDown();
} catch (DeviceShutDownError e) {
logger.log(e);
}
}
private void tryToShutDown() throws DeviceShutDownError {
DeviceHandle handle = getHandle(DEV1);
DeviceRecord record = retrieveDeviceRecord(handle);
pauseDevice(handle);
clearDeviceWorkQueue(handle);
closeDevice(handle);
}
private DeviceHandle getHandle(DeviceID id) {
...
throw new DeviceShutDownError("Invalid handle for: " + id.toString());
...
}
...
}
The code is better because two concerns that were tangled, the algorithm for device shutdown and error handling, are now separated. You can look at each of those concerns and understand them independently.
Now my question is not so much about the whether to use exceptions or return codes but I am wondering about the unused DeviceRecord in the second "good example", which uses exceptions.
Wouldn't it be enough to just call retrieveDeviceRecord(handle); to save the device status to the record field as in the "bad example code" but change the method to also throw an exception in case anything goes wrong during retrieveDeviceRecord(handle);? Or is there a purpose of returning DeviceRecord but not using it?
When I run PHPUnit 6.5.13. and have a test method following this example PHPUnit Testing Exceptions Documentation
public function testSetRowNumberException()
{
$this->expectException(\InvalidArgumentException::class);
$result = $this->tableCell->setRowNumber('text');
}
that tests this method:
public function setRowNumber(int $number) : TableCell
{
if (!is_int($number)) {
throw new \InvalidArgumentException('Input must be an int.');
}
$this->rowNumber = $number;
return $this;
}
I got this failure:
Failed asserting that exception of type "TypeError" matches expected exception "InvalidArgumentException".
the question is why "TypeError" is taken to assertion and how to make assertion use InvalidArgumentException?
Got it. The thing is I used typing set to int that's why the code didn't even reach the thow command.
it works if tested method is without set typing to int:
public function setRowNumber($number) : TableCell
{
if (!is_int($number)) {
throw new \InvalidArgumentException('Input must be an int.');
}
$this->rowNumber = $number;
return $this;
}
or when the test has TypeError
public function testSetRowNumberException()
{
$this->expectException(\TypeError::class);
$result = $this->tableCell->setRowNumber('text');
}
I'll stay with the second example.
Below is the sample code I'm using to understand exception handling in completablefuture in java8.
If we make use of exceptionally method as per doc,
exceptionally method catches even runtime exception as well and proceeds to last block in the pipeline.
if we don't use exceptionally method then, it justs prints running and exits.
Correct me if my understanding isn't correct.
Question is Lets say if i want to throw runtime exception and want application to stop. Basically if i throw Runtime exception , it shouldn't proceed to next block in pipeline. How should i do that. Any pointers are helpful.
public static void main(String[] args) {
final CompletableFuture<String> retrieveName = CompletableFuture.supplyAsync(() -> {
System.out.println("running");
int i = 0;
if(i == 0) {
throw new RuntimeException("ding");
}
return "test";
}).exceptionally(it -> {
System.out.println(it.getMessage());
return "empty";
}).thenApply(it -> {
System.out.println("last block" + it);
return "dummy";
});
}
Try this:
public static void main(String[] args) {
try {
final CompletableFuture<String> retrieveName = CompletableFuture.supplyAsync(() -> {
System.out.println("running");
int i = 0;
if (i == 0) {
throw new RuntimeException("ding");
}
return "test";
}).exceptionally(it -> {
if (it.getMessage().contains("ding")) {
throw (RuntimeException) it;
}
System.out.println(it.getMessage());
return "empty";
}).thenApply(it -> {
System.out.println("last block" + it);
return "dummy";
});
retrieveName.join();
} catch (Exception e) {
System.out.println("main() exception, cause=" + e.getCause());
}
}
This is the output:
running
main() exception, cause=java.lang.RuntimeException: ding
I made 3 small changes to your code:
Wrapped it all in a try-catch
Threw a RuntimeException in exceptionally() for the "ding" exception.
Added a call to retrieveName.join(). From the Javadoc for CompletableFuture.join():
public T join​()
Returns the result value when complete, or throws an (unchecked) exception if completed exceptionally.
Update based on OP feedback ------->
Lets say if i want to throw runtime exception and want application to
stop. Basically if i throw Runtime exception , it shouldn't proceed to
next block in pipeline. How should i do that.
You can achieve what you want with just 2 changes to your code:
[1] Completely remove the exceptionally() callback so the CompletableFuture (CF) terminates with an exception. In exceptionally() in the OP code the exception was being swallowed rather than rethrown, and returning a CF, so the thenApply() method was still performed.
[2] Add a call to retrieveName.join() at the end of main(). This is a blocking call, but since the thread had terminated with an exception that 's not really relevant for the sample code. The join() method will extract the thrown RunTimeException and re-throw it, wrapped in a CompletionException.
Here's your modified code:
public static void main(String[] args) {
final CompletableFuture<String> retrieveName = CompletableFuture.supplyAsync(() -> {
System.out.println("running");
int i = 0;
if(i == 0) {
throw new RuntimeException("ding");
}
return "test";
}).thenApply(it -> {
System.out.println("last block" + it);
return "dummy";
});
retrieveName.join();
}
Notes:
This is not how to do things in Production. The blocking call from join() was not a problem here, but could be for a long running CF. But you obviously can't extract the exception from the CF until it is complete, so it makes sense that the join() call blocks.
Always bear in mind that main() is not running in the same thread(s) as the CF.
An alternative approach (if viable) might be to handle all the necessary post-exception actions (logging, etc,) within exceptionally() and then terminate normally with a suitable return value (e.g. "Exception handled!") rather than propagating the exception.
You can check whether the CF is still running by calling the non-blocking isDone() method. You can also check whether the CF ended with an exception (isCompletedExceptionally()) or was cancelled(isCancelled​()).
I am new to Server Sent Events but not to Spring.
Have made a controller which gets triggered from a button on the UI which initiates SSEEmitter and passed that to another thread which in loop sends message to UI after each 4 seconds.
SO far i am running a loop of 10 which sleeps for 4 seconds each but suddenly around iteration of 6 or 7th loop, I get exception "Exception in thread "Thread-4" java.lang.IllegalStateException: ResponseBodyEmitter is already set complete"..
Hence, event source again re-establishes the connection i.e. calls the controller method again which certainly i do not want.
I am here trying a simple thing.. User subscribes by clicking on the button..
Server send response 10 or 20 whatever times to the browser. And as far as I think this is what SSE created for.
Code below.:
#RequestMapping("/subscribe")
public SseEmitter subscribe() {
SseEmitter sseEmitter = new SseEmitter();
try {
sseEmitter.send("Dapinder");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Runnable r = new AnotherThread(sseEmitter);
new Thread(r).start();
return sseEmitter;
}
public class AnotherThread implements Runnable {
private SseEmitter sseEmitter;
public AnotherThread(SseEmitter sseEmitter) {
super();
this.sseEmitter = sseEmitter;
}
#Override
public void run() {
SseEventBuilder builder = SseEmitter.event();
builder.name("dapEvent");
for (int i = 0; i < 10; i++) {
builder.data("This is the data: " + i +" time.");
try {
//sseEmitter.send(builder);
sseEmitter.send("Data: "+i);
//sseEmitters.get(1L).send("Hello");
} catch (IOException e) {
e.printStackTrace();
}
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
sseEmitter.complete();
}
public SseEmitter getSseEmitter() {
return sseEmitter;
}
public void setSseEmitter(SseEmitter sseEmitter) {
this.sseEmitter = sseEmitter;
}
}
function start() {
var eventSource = new EventSource("http://localhost:8080/HTML5SSE/springSSE/subscribe"); // /springSSE/connect
eventSource.onmessage = function(event) {
document.getElementById('foo').innerHTML = event.data;
};
}
<button onclick="start()">Subscribe</button>
Your builder is not being used; you create and configure a builder, but then you send a plain message with 'sseEmitter.send' directly. Try this:
sseEmitter.send(SseEmitter.event().name("dapEvent").data("This " + i +" time.");
One more thing: Why do you call the send method already in the subscribe method? At this point in time, the SseEmitter has not been returned. Is this message coming through to the client?
Here is an excellent article explaining SSE from the JavaScript perspective (not Spring). You will see here that you can cancel the event stream from the client by calling close on the stream. Combine this with an event listener, and you should have what you need:
var source = new EventSource('...');
source.addEventListener('error', function(e) {
if (e.currentTarget.readyState == EventSource.CLOSED) {
// Connection was closed.
} else {
// Close it yourself
source.close();
}
});
source.addEventListener('message', function(e) {
console.log(e.data);
});
Note: The article says e.readyState, however I think this is wrong. The received object e is an Event. You need to get the EventSource object from it like this: e.currentTarget.
You need to use the second constructor of SseEmitter which takes a Long timeout argument. Please refer the code below -
#RequestMapping("/subscribe")
public SseEmitter subscribe() {
SseEmitter sseEmitter = new SseEmitter(Long.MAX_VALUE) // for maximum timeout
Below is the copy of Java-doc of this constructor -
/**
* Create a SseEmitter with a custom timeout value.
* <p>By default not set in which case the default configured in the MVC
* Java Config or the MVC namespace is used, or if that's not set, then the
* timeout depends on the default of the underlying server.
* #param timeout timeout value in milliseconds
* #since 4.2.2
*/
I think the default timeout of SSE connection in Tomcat is 40 seconds. Not sure though.
Using Play Framework 2.1 with OpenID, if I cancel my authentication from the OpenID Provider, I get this exception :
[RuntimeException: play.api.libs.openid.Errors$AUTH_CANCEL$]
Here's my code :
Promise<UserInfo> userInfoPromise = OpenID.verifiedId();
UserInfo userInfo = userInfoPromise.get(); // Exception thrown here
But since it's a Runtime exception, I can't catch it with a try/catch so I'm stuck on how to avoid exception and returns something nicer than a server error to the client.
How can I do that?
A Promise is success biased, for all its operations, it assumes it actually contains a value and not an error.
You get the exception because you try to call get on a promise which contains an untransformed error.
What you want is to determine if the Promise is a success or an error, you can do that with pattern matching for instance.
try this code:
AsyncResult(
OpenID.verifiedId.extend1( _ match {
case Redeemed(info) => Ok(info.attributes.get("email").getOrElse("no email in valid response"))
case Thrown(throwable) => {
Logger.error("openid callback error",throwable)
Unauthorized
}
}
)
)
You may want to read more on future and promises, I recommend this excellent article :
http://danielwestheide.com/blog/2013/01/09/the-neophytes-guide-to-scala-part-8-welcome-to-the-future.html
edit :
checking the documentation (http://www.playframework.com/documentation/2.1.0/JavaOpenID) in java it seems you are supposed to catch and handle exceptions yourself.
In any case, you should catch exceptions and if one is thrown redirect
back the user to the login page with relevant information.
something like this should work :
public class Application extends Controller {
public static Result index() {
return ok("welcome");
}
public static Result auth() {
Map<String, String> attributes = new HashMap<String, String>();
attributes.put("email", "http://schema.openid.net/contact/email");
final Promise<String> stringPromise = OpenID.redirectURL("https://www.google.com/accounts/o8/id", "http://localhost:9000/auth/callback",attributes);
return redirect(stringPromise.get());
}
public static Result callback() {
try{
Promise<UserInfo> userInfoPromise = OpenID.verifiedId();
final UserInfo userInfo = userInfoPromise.get();
System.out.println("id:"+userInfo.id);
System.out.println("email:"+userInfo.attributes.get("email"));
return ok(userInfo.attributes.toString());
} catch (Throwable e) {
System.out.println(e.getMessage());
e.printStackTrace();
return unauthorized();
}
}
}