My custom error handler is not working with Slim 3 framework. Instead of getting a 500 error, I get a response with status 200 and the html error details are in the body.
Here is my minimal, verifiable and complete example:
$c = new \Slim\Container();
$c['errorHandler'] = function ($c) {
return function($request, $response, $exception) use ($c) {
return $c['response']->withStatus(500)
->withHeader('Content-Type', 'text/html')
->write('Something went wrong!');
};
};
$app = new \Slim\App($c);
$app->any('/foo', function($request, $response, $args) {
$data = json_encode($request->nonExistingMethod()); // error!
return $response->withJson($data);
});
$app->run();
How do I need to refactor this sample to make it work? I suspect it's related with the Fatal nature of the error. But in this case how to deal with that?
Reference: http://www.slimframework.com/docs/handlers/error.html
Edit 1
For use in an api style web application, the final solution I'm using with minor changes from the response of this question:
function checkForError() {
$last = error_get_last();
if ($last) {
#header("HTTP/1.0 500 Internal Server Error");
echo json_encode($last); // optional, includes error details in json format
}
}
error_reporting(0);
register_shutdown_function('checkForError');
you cannot catch all errors like this
there is however one way to catch all except memory errors (or only those that tried to allocate more than your error handler needs)
function checkForError() {
$last = error_get_last();
if ($last) {
#header("HTTP/1.0 500 Internal Server Error");
echo 'we failed... sry';
}
}
register_shutdown_function('checkForError');
updated with 500 status header
Related
I'm working on an asynchronous process on a PHP project. I'm using a library named spatie/async. The code snippet is like below :
foreach (range(1, 2) as $i) {
$pool->add(function () use ($i) {
// Do a thing
try {
$result = $i / 0; // This will cause an error
return "Works";
} catch (\Exception $e) {
return -1;
}
})->then(function ($output) {
// Handle success
echo (output . "\n");
})->catch(function ($exception) {
// When an exception is thrown, it's caught and passed here.
echo "Sounds good, but don't work\n";
})
}
$pool->wait();
All I want is when the $result got an error, it will go into the inner catch, but instead, it goes down to the bottom catch which causing a different result from what I want.
The result that I want is :
-1
-1
But instead, the result is :
Sounds good, but don't work
Sounds good, but don't work
Can anyone help me to achieve the result as I want?
The problem of your code is, that it does not throw an Exception in the add method call. A division by 0 is just causing an error, but not ein exception. Instead of changing the whole php error handler, I 'd suggest to extend your logic a little bit in your add method call.
$divisor = 0;
$pool->add(function() use ($i, $divisor) {
try {
if ($divisor === 0) {
throw new \LogicException('Division by zero!');
}
return $i / $divisor;
} catch (\LogicException $e) {
return -1;
}
});
Another solution could be changing the error handling for the pool method call.
set_error_handler(function () {
throw new \LogicException('Ouch!');
});
$pool->add(function() use ($i) {
try {
$result = $i / 0;
} catch (\LogicException $e) {
return -1;
}
});
restore_error_handler();
Caution! Changing the error handler affects all upcoming errors. Even the errors thrown in your used library. Keep in mind, that these are code snippets. This is not tested or thougt to be used in production. Hope that helps out a little bit.
i have this entry in my laravel 5.3 log
2016-12-22 17:23:37] local.ERROR:
GuzzleHttp\Exception\ClientException: Client error: POST
https://api.sparkpost.com/api/v1/transmissions resulted in a 400 Bad
Request response: { "errors": [ { "message": "Message generation
rejected", "description": "recipient address suppressed due to
customer p (truncated...)
why does it truncate an important error message? now i cannot figure out what is going wrong...
Because your request throws a Guzzle Exception, as a workaround, instead of calling $e->getMessage(), You can simply try:
$e->getResponse()->getBody()->getContents();
If you don't want to modify the report() method.
Worked nice for me.
The truncating is done by the Guzzle library. It only shows the first 120 characters of the response. I am assuming this is because responses could potentially be very long.
If you would like to see the full message, you should be able to customize how guzzle exceptions are handled.
Update the report() method in your app/Exceptions/Handler.php to something like:
public function report(Exception $exception)
{
// this is from the parent method
if ($this->shouldntReport($exception)) {
return;
}
// this is from the parent method
try {
$logger = $this->container->make(\Psr\Log\LoggerInterface::class);
} catch (Exception $ex) {
throw $exception; // throw the original exception
}
// this is the new custom handling of guzzle exceptions
if ($exception instanceof \GuzzleHttp\Exception\RequestException) {
// get the full text of the exception (including stack trace),
// and replace the original message (possibly truncated),
// with the full text of the entire response body.
$message = str_replace(
rtrim($exception->getMessage()),
(string) $exception->getResponse()->getBody(),
(string) $exception
);
// log your new custom guzzle error message
return $logger->error($message);
}
// make sure to still log non-guzzle exceptions
$logger->error($exception);
}
Note: this is done in the report method, so it only affects what is written to the log. If the exception is dumped to the terminal or to the browser, it will still show the truncated message.
as alternative solution:
hotfix RequestException.php
ta_integration/vendor/guzzlehttp/guzzle/src/Exception/RequestException.php
replace
$size = $body->getSize();
$summary = $body->read(120);
$body->rewind();
if ($size > 120) {
with for example:
$size = $body->getSize();
$summary = $body->read(999);
$body->rewind();
if ($size > 999) {
function
getResponseBodySummary
Edit vendor/guzzlehttp/psr7/src/Message.php
public static function bodySummary(MessageInterface $message, $truncateAt = 999)
Edit vendor/guzzlehttp/psr7/src/functions.php
function get_message_body_summary(MessageInterface $message, $truncateAt = 999)
None of these solutions here helped me. I found a solution here that helped. By the user sidk2020. Here is his solution in case the link breaks:
I did something very adventurous. I modified guzzel's exception handler
guzzel on purpose only reads up 120 bytes of info and prints truncated next to it.
The file is located at : /vendor/guzzlehttp/guzzle/src/Exception/RequestException.php
So I modified that function and below is what my function looks like:
public static function getResponseBodySummary(ResponseInterface $response) {
$body = $response->getBody();
if (!$body->isSeekable() || !$body->isReadable()) {
return null;
}
$size = $body->getSize();
if ($size === 0) {
return null;
}
// Matches any printable character, including unicode characters:
// letters, marks, numbers, punctuation, spacing, and separators.
if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/', $body)) {
return null;
}
return $body;
}
In vendor/guzzlehttp/psr7/src/functions.php
there's this function:
function get_message_body_summary(MessageInterface $message, $truncateAt = 120)
{
return Message::bodySummary($message, $truncateAt);
}
just change the $truncateAt = 120 to whatever you are confortable with
I followed the Slim v3 documentation to a tee on how to redefine the framework's built in error handler so as to prevent errors from being outputted to the response with the following code:
$settings = require __DIR__ . '/../src/settings.php';
$app = new \Slim\App($settings);
// Errors to log rather than to end user
$c = $app->getContainer();
$c['errorHandler'] = function ($c) {
return function ($request, $response, $exception) use ($c) {
$c['logger']->error("Error 500 diverted: ".$exception->getMessage());
return $c['response']->withStatus(500)
->withHeader('Content-Type', 'text/html')
->write('Something went wrong on our side!');
};
};
And yet my API is still spitting out Slim's default handler with full stack traces and the telltale Slim Application Error string... albeit helpful, I'd much rather have this information going to Slim's log (monolog) and something less revealing facing the client. Is there any reason that this service redefinition is effectively being ignored?
This code works:
<?php
$app = new \Slim\App();
$container = $app->getContainer();
$container['errorHandler'] = function ($c) {
return function ($request, $response, $exception) use ($c) {
// log error here
return $c['response']->withStatus(500)
->withHeader('Content-Type', 'text/html')
->write('Something went wrong on our side!');
};
};
$container['phpErrorHandler'] = function ($c) {
return function ($request, $response, $error) use ($c) {
// log error here
return $c['response']->withStatus(500)
->withHeader('Content-Type', 'text/html')
->write('Something went wrong on our side (again)!');
};
};
$app->get('/exception', function ($req, $res, $args) {
// errorHandler will trap this Exception
throw new Exception("An error happened here");
});
$app->get('/php7', function ($req, $res, $args) {
$x = function (int $x) {
return $x;
};
// phpErrorHandler wil trap this Error
$x('test');
});
$app->get('/warning', function ($req, $res, $args) {
$x = UNDEFINED_CONSTANT;
});
$app->run();
As you can see, you need to register an errorHandler to catch Exceptions and a phpErrorHandler to catch PHP 7 Errors.
Note that neither will catch a PHP Notice as shown by the /warning route. To catch that too, you need to register your own error handler:
set_error_handler(function ($severity, $message, $file, $line) {
if (!(error_reporting() & $severity)) {
// This error code is not included in error_reporting, so ignore it
return;
}
throw new \ErrorException($message, 0, $severity, $file, $line);
});
I've written some code in Laravel 5.2 to retrieve results from an unrelible API source. However, it needs to be able to automatically retry the request on failed attempts, as the API call results in a 503 about a third of the time.
I'm use Guzzle to do this, and I think I know where to put the code that will intercept the 503 responses before they are processed; but I'm not sure what to actually write there.
The guzzle documentation doesn't offer much as far as retries go, and all of the examples I've come across of Guzzle 6 only show how to retrieve results (which I can already do), but not how to get it to repeat the request if needed.
I'm by no means asking anyone to do the work for me - but I think I'm approaching the limits of my understanding on this. If anybody can point me in the right direction, it'd be much appreciated :)
EDIT:
I will try and revise. Please consider the following code. In it, I want to send a GET request which should normally yield a JSON response.
DataController.php
$client = new \GuzzleHttp\Client();
$request = $client->request('GET', 'https://httpbin.org/status/503'); // URI is for testing purposes
When the response from this request is a 503, I can intercept it here:
Handler.php
public function render($request, Exception $e)
{
if ($e->getCode() == 503)
{
// Code that would tell Guzzle to retry the request 5 times with a 10s delay before failing completely
}
return parent::render($request, $e);
}
I don't know that that is the best place to put it, but the real problem is I don't know is what to write inside the if ($e->getCode() == 503)
Guzzle by default throws exceptions when a non 2** response is returned. In your case you're seeing a 503 response. Exceptions can be thought of as errors that the application can recover from. The way this works is with try catch blocks.
try {
// The code that can throw an exception will go here
throw new \Exception('A generic error');
// code from here down won't be executed, because the exception was thrown.
} catch (\Exception $e) {
// Handle the exception in the best manner possible.
}
You wrap the code that could throw an exception in the try portion of the block. Then you add your error handling code in the catch portion of the block. You can read the above link for more information on how php handles exceptions.
For your case, lets move the Guzzle call to it's own method in your controller:
public function performLookUp($retryOnError = false)
{
try {
$client = new \GuzzleHttp\Client();
$request = $client->request('GET', 'https://httpbin.org/status/503');
return $request->send();
} catch (\GuzzleHttp\Exception\BadResponseException $e) {
if ($retryOnError) {
return $this->performLookUp();
}
abort(503);
}
}
Now in your controller you can execute, $this->performLookUp(true);.
Just to add some information to clarify a few points that Logan made.
Guzzle "can" throw exceptions on a Response other then 2**/3**. It all depends upon how the GuzzleHttp\HandlerStack is created.
$stack = GuzzleHttp\HandlerStack::create();
$client = new Client(['handler'=> $stack]);
$client = new Client();
// These two methods of generating a client are functionally the same.
$stack = New GuzzleHttp\HandlerStack(new GuzzleHttp\Handler\CurlHandler());
$client = new Client(['handler'=> $stack]);
// This client will not throw exceptions, or perform any of the functions mentioned below.
The create method adds default handlers to the HandlerStack. When the HandlerStack is resolved, the handlers will execute in the following order:
Sending request:
http_errors - No op when sending a request. The response status code is checked in the response processing when returning a response promise up the stack.
allow_redirects - No op when sending a request. Following redirects occurs when a response promise is being returned up the stack.
cookies - Adds cookies to requests.
prepare_body - The body of an HTTP request will be prepared (e.g., add default headers like Content-Length, Content-Type, etc.).
send request with handler
Processing response:
prepare_body - no op on response processing.
cookies - extracts response cookies into the cookie jar.
allow_redirects - Follows redirects.
4.http_errors - throws exceptions when the response status code >= 300.
When provided no $handler argument, GuzzleHttp\HandlerStack::create() will choose the most appropriate handler based on the extensions available on your system. As indicated within the Handler Documentation
By manually creating your GuzzleHttp\HandlerStack, you can add middleware to the application. Given the context of your original question "how do i repeat the request" I believe you are most interested in the Retry Middleware that is provided within Guzzle 6.1. This is a middleware that retries requests based on the result of the provided decision function.
Documentation has yet to catch up with this class.
final class HttpClient extends \GuzzleHttp\Client
{
public const SUCCESS_CODE = 200;
private int $attemptsCount = 3;
private function __construct(array $config = [])
{
parent::__construct($config);
}
public static function new(array $config = []): self
{
return new self($config);
}
public function postWithRetry(string $uri, array $options = []): Response
{
$attemptsCount = 0;
$result = null;
do {
$attemptsCount++;
$isEnd = $attemptsCount === $this->attemptsCount;
try {
$result = $this->post(
$uri,
$options,
);
} catch (ClientException $e) {
$result = $e->getResponse();
} catch (GuzzleException $e) {
if ($isEnd) {
Logger::error($e->getMessage());
$result = $e->getResponse();
}
}
} while ($this->isNeedRetry($result, $attemptsCount));
return $result;
}
private function isNeedRetry(?Response $response, int $attemptsCount): bool
{
return $response === null && $attemptsCount < $this->attemptsCount;
}
From the documentation it says we can catch all 404 like so:
App::missing(function($exception)
{
return Response::view('errors.missing', array(), 404);
});
And we can also do things like:
App::abort(404);
App::abort(403);
All 404 gets handled by the App::missing
All other errors gets handled by:
App::error(function( HttpException $e)
{
//handle the error
});
But the question is How do i handle each of the error like if its a 403 I will display this if its a 400 I will display another error
Short answer: if your custom App::error function does not return a value, Laravel will handle it. Check the docs
Code sample for custom error views and/or logic:
App::error(function(Exception $exception, $code){
// Careful here, any codes which are not specified
// will be treated as 500
if ( ! in_array($code,array(401,403,404,500))){
return;
}
// assumes you have app/views/errors/401.blade.php, etc
$view = "errors/$code";
// add data that you want to pass to the view
$data = array('code'=>$code);
// switch statements provided in case you need to add
// additional logic for specific error code.
switch ($code) {
case 401:
return Response::view($view, $data, $code);
case 403:
return Response::view($view, $data, $code);
case 404:
return Response::view($view, $data, $code);
case 500:
return Response::view($view, $data, $code);
}
});
The above snippet could be inserted in app/start/global.php after the default Log::error handler, or better yet in the boot method of a custom service provider.
EDIT: Updated so the handler only processes codes you specify.