GuzzleHttp Asynchronous Request Exception - php

I can't figure out how I can throw an exception from Guzzle future response handler.
Here's my code:
<?php
require 'vendor/autoload.php';
$client = new \GuzzleHttp\Client();
$req = $client->createRequest('GET', 'http://www.google.com', array(
'future' => true,
));
echo "Sending request\n";
$response = $client->send($req);
try {
$response->then(function ($data) {
echo "Response is received\n";
throw new Exception('Test');
})->then(function () {
// success handler
}, function (Exception $exception) {
echo "Error handler invoked\n";
throw $exception;
});
} catch (Exception $e) {
echo "Exception catched\n";
}
echo "Finish\n";
The catch block is never reached in this case.

You are working with promises when using asynchronous Guzzle requests. Using the then() function off of a FutureResponse will create a promise that is fulfilled or rejected when the request completes. If an error occurs while sending, the promise is rejected, which means the second callback provided to the then function is invoked. When a request completes successfully, it is resolved, and the first callback provided to the then function is invoked. When an exception is thrown in any of the promise functions, the exception is caught inside of the promise and forwarded to the next error handler in the chain. In your example, if the request succeeds, then you throw an exception which will trigger the error callback. Throwing an exception in the error callback will either forward the exception to the next error callback in the promise chain, or silently eat the error (in your case, there are no further error callbacks to trigger).
The React Promises library that is used by Guzzle has more documentation on resolution and rejection forwarding of promises: https://github.com/reactphp/promise#how-promise-forwarding-works. The author of this library is looking into adding a done() function that can be used as a terminal promise handler that actually throws unhandled exceptions.

Asynchronous means that your script will not wait for the response to come back from the server, rather will just send the request and continue executing. In this case, the script reaches its end of life before the response returns, so none of the callbacks are ever executed.
Add this line after the catch to block the script's execution until the response comes back.
$response->getStatusCode();
If you provide more info on what you want to achieve, we might be able to help you further.

Related

Catch "Missing or incorrect CSRF cookie type." exception

I know why this exception is thrown, this is not the problem, but I am not capable to catch this exception.
This exception is thrown in CORE/src/Http/Middleware/CsrfProtectionMiddleware.php line #286:
if (!$cookie || !is_string($cookie)) {
throw new InvalidCsrfTokenException(__d('cake', 'Missing or incorrect CSRF cookie type.'));
}
When I check the long list of the stack in the CakePHP error window it's clear to me I cannot start to modify the CORE files as with the next CakePHP update/upgrade my modifications are lost.
The only script I can modify and should be easily handled is webroot/index.php. It's also mentioned in the call stack in the first position:
Cake\Http\Server->run ROOT/webroot/index.php:50
And here I am stuck. What ever I tried:
try/catch ( \Exception )
try/catch ( \Cake\Http\Exception\InvalidCsrfTokenException )
Using set_exception_handler()
nothing helps, this means, I always get the below error window. In this window you can see on the left the long call stack of scripts which are called until the exception is thrown. And this are even not all scripts. So it's really nested.
My question:
How can I catch this exception in the most top PHP script webroot/index.php - below this script are another 16 scripts called until the exception is thrown. I don't want to modify the CakePHP core files.
I am running CakePHP 4.1.4
You cannot catch that exception in index.php, as it is already being catched by the error handler middleware, which presents you that nice error screen, which however you'd only see in debug mode, in case that is your concern.
Your first chance to catch that exception would be a custom middleware, which you'd have to place between the error handler middleware and the CSRF protection middleware, something along the lines of this:
// in src/Application.php
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
$middlewareQueue
->add(new ErrorHandlerMiddleware(Configure::read('Error')))
// ...
->add(function (
\Psr\Http\Message\ServerRequestInterface $request,
\Psr\Http\Server\RequestHandlerInterface $handler
) {
try {
// continue with the next middleware
return $handler->handle($request);
} catch (\Cake\Http\Exception\InvalidCsrfTokenException $exception) {
// handle the catched exception
$response = new \Cake\Http\Response();
return $response->withStringBody('Oh noes, CSRF error!');
}
})
// ...
->add(new CsrfProtectionMiddleware([
'httponly' => true,
]));
return $middlewareQueue;
}

Laravel Queue - Prevent job retry on certain condition

I call an API to send SMS and save it's response using Redis::throttle to limit the rate of the call to 5 calls every 60s with :
Redis::throttle('throttle:sms')->allow(5)->every(60)->then(function(){
//->API call
//->Save response
},function($error){//could not obtain lock
return $this->release(60);//Put back in queue in 60s
});
I didn't specify any $tries because if the lock cannot be obtain, it count as a try and if I process a long queue and the lock cannot be obtain many time the job will fail without any "real" errors.
But I dont want the job to be processed forever, if there is a real error (like if the response cannot be saved) it should fail without retry especially if the error appends after the API call as a retry will send another SMS (which I definitely don't want).
What I've tried :
Redis::throttle('throttle')->allow(5)->every(60)->then(function(){
try{
$response = MyAPICall();
$test = 8/0;
saveResponse($response);
} catch(\LimiterTimeoutException $e){
throw $e;
} catch(Exception $e){
Log::error($e);
$this->fail($exception = null);
//$this->delete();
}
},function($error){//could not obtain lock
Log::info($error);
return $this->release(60);//Put back in queue in 60s
});
If there is an exception because the lock cannot be obtain, I throw it back to let the queue handle it but if it's another exception, I log the error and fail or delete the job.
But it's not working with either delete() or fail(), the job is always retried.
How can I remove the job if there is an exception other than the LimiterTimeoutException ?
I was missing a "\" before Exception in my catch. Here is the fix code :
Redis::throttle('throttle:sms')->allow(5)->every(60)->then(function(){
$response = myAPICall();
try{
$test = 8/0;
SaveResponse($response);
}
catch (LimiterTimeoutException $exception){
throw $exception;//throw back exception to let redis handle it below
}
catch (\Exception $exception) {
Log::error($exception);
$this->fail($exception);
}
},function($error){//could not obtain lock
return $this->release(60);//Put back in queue in 60s
});
I added $this->fail($exception) to make the job to show up as "failed" in Horizon.

Syntax error in a promise before the promise is returned

Here's a simple event loop with a ReactPHP promise:
new React\Http\Server([
function(ServerRequestInterface $request) {
$deferred = new React\Promise\Deferred();
$promise = $deferred->promise();
$deferred->reject(new Response(500));
return $promise;
}
]);
In this case everything works fine and the server returns 500, because the promise was returned and it was rejected.
But how to handle cases like this:
new React\Http\Server([
function(ServerRequestInterface $request) {
$deferred = new React\Promise\Deferred();
$promise = $deferred->promise();
SynTaxErrO..2!((r();
$deferred->reject(new Response(500));
return $promise;
}
]);
The server/loop will still be running, but the connection will be hanging, since a syntax error happened before the promise was returned.
My first assumption was to use try-catch, but it doesn't seem to work in PHP.
try {
SynTaxErrO..2!((r();
} catch($e) {
$deferred->reject(new Response(500));
return $promise;
}
Is there a way to deal with cases like this and still return 500 instead of just hanging and waiting for a promise that was never returned? In real code I have a router function that returns a promise. The promise is never retuned if one of the routes have a syntax error, and thus the connection just hangs. There are no error messages as well.
You cannot catch syntax errors. If there is a syntax error before your catch statement, then execution never reaches your catch and therefore is like it didn't exist. To detect syntax error use a linter (for instance, php -l) before executing your code.
For other kinds of errors, provided you are using PHP 7, then you can use
catch (Error $e) { ... }
or a set_exception_handler() handler to catch errors.
If you want to catch both errors and exceptions, then you can use a block like
catch (Throwable $e) { ... }
If you only want to catch exceptions, use
catch (Exception $e) { ... }
See http://php.net/manual/en/language.errors.php7.php for more info
Hey ReactPHP team member here. Looks like the culput of you issue is SynTaxErrO..2!((r();, PHP can't parse that: https://3v4l.org/02cli
The best solution is not to have any syntax errors. A package that you could use to lint all your files before committing/deploying is: https://github.com/JakubOnderka/PHP-Parallel-Lint

Laravel: Where to throw HTTP Exceptions

Background
Within PHP / Laravel MVC applications response codes and bodies are often dictated by the Exception that is thrown. If a HTTP exception is thrown (inheriting from Symfony\Component\HttpKernel\Exception\HttpException) the correct response codes are thrown (and in certain cases JSON responses). There are other types of exceptions that are non-http related as well that can be thrown.
Question
Where should HTTP exceptions be thrown?
A Only the controller
B Anywhere. Deep or shallow in the applications stack.
Should I be catching my exceptions in the controller and throwing HTTP versions of those exceptions? Or should I just throw a HTTP exception anywhere deep within a service class, repository or utility considering 99% of MVC framework apps are based around a HTTP request >> response lifecycle anyway?
My answer is not targeted at Laravel as I feel working with a framework mindset actually goes against your initial question.
Always throw a tailored exception and then handle the conversion within the controller. In this case wrap it in a HttpException. There are a few good reasons for this:
The decision on which status code and message is delegated to the implementation (in this case the integration with your framework). This means that you could drop your code in any framework and handle its errors separately.
You decide you need a CLI command/worker and now your HttpException throws in your service make no sense to your CLI command.
Essentially thinking about a calculator, it would throw a DivisionByZeroException. For a controller you would wrap this in a HttpException 400 BAD REQUEST and re-throw. For the CLI your command could just let the exception render on screen Division By Zero. Either way this decision is not made by your service.
Where should HTTP exceptions be thrown?
While this is generally up to preference, the framework itself seems to have taken an opinionated stance on this and it is that you should be throwing them anywhere. In fact Laravel has a few useful helpers to make throwing exceptions with associated response codes easier:
abort(403, "Exception message"); //Will throw an HTTP exception with code 403
abort_if(true, 400, "Condition failed"); //Will throw a 400 error if the first parameter is true
abort_unless(false, 422, "Condition failed"); //Will throw a 422 error if the first parameter is false
Practical example:
public function getById($id) {
$model = Model::find($id);
//These are equivalent
if ($model == null) {
abort(404, "$id not found");
}
abort_if($model == null, 404, "$id not found");
abort_unless($model != null, 404, "$id not found");
}
This is touched upon in the Error handling section of the manual
Note that abort does raise HTTP exceptions so you can still catch them and handle them if you need to.
There seems to be a general misunderstanding regarding this question. It was my understanding that the question was where HTTP exceptions should be thrown but it's evolving to a more generic exception handling in the context of HTTP.
First of all, if you have an HTTP exception, meaning an exception that only makes sense in the context of an HTTP request/response cycle, then you should be able to throw it where it occurs and not throw something else with the purpose of converting it when it reaches the controller, this is what the abort helpers are there to do.
However if you have an exception (any kind of exception) that should be interpreted with a specific http response code when left unhandled you have options to deal with that:
Make that exception inherit from the Symfony HttpException (This might feel a bit strange that a perfectly normal exception inherits from a class that doesn't make sense outside the request/response lifecycle).
Implement the render method within your exception e.g.:
class SpecialException extends Exception {
public function render() {
return response()->view('errors.403', [], 403);
}
}
Have a specific handling behaviour within your \App\Exceptions\Handler for example:
class Handler {
// ....
public function render($request, $exception) {
if ($exception instanceof SpecialException) {
return response()->view('errors.403', [], 403);
}
return parent::render()
}
}

How to get information when 500 error

<?php
$this->client = new \GuzzleHttp\Client;
try {
$response = $this->client->request('get', $url);
$result = json_decode($response->getBody());
Log::save($result);
} catch (RequestException $e) {
Log::save($e->getMessage());
}
If the target page has internal server error 500 situation, I will get the error message Client error: 404, actually the target page has show what the bug is if I use the browser to open it, I want to save those PHP error message to log, but I don't know how to got when using Guzzle.
There are a couple of different ways you can proceed:
If you are using Guzzle 6+ and you are using the default
HandlerStack (creating the client with no handler new \GuzzleHttp\Client(); the default behaviour will be for the request
to throw a GuzzleHttp\Exception\ServerException exception. In this case, all you have to do is wrap your request in a try / catch block (as shown above).
If you are manually creating your
HandlerStack $stack = new \GuzzleHttp\HandlerStack(); $client = new \GuzzleHttp\Client(['handler' => > $stack,]); then the \GuzzleHttp\Middleware::httpErrors middleware has not been loaded onto the stack, and you will have to trap it within your own middleware or by passing a callable to the 'on_headers' request option.
References:
Guzzle Request Options - http_errors
Guzzle Handlers and Middleware
Guzzle Request Options - on_headers

Categories