I have created a worker class in Laravel.
The worker class communicate to with Lumen. If there is an error in Lumen it will response back in json to Laravel.
The worker class like this:-
class Worker {
public function put($ip, $user, $file)
{
try {
$response = $this->client->post('/put', ['form_params' => ['ip' => $ip,'username' => $user, 'file' => $file]]);
$responseBody = (string)$response->getBody();
// do something
} catch (ClientException | ServerException $e) {
return $this->handleRequestError($e);
}
}
protected function handleRequestError($e)
{
if ($e instanceof ClientException) {
if ($e->getCode() == 422) {
throw new WorkerValidationException((string)$e->getResponse()->getBody());
}
}
if ($e instanceof ServerException) {
$error = json_decode($e->getResponse()->getBody(), true);
if ($error['error_type'] == 'ftp') {
throw new FtpException((string)$e->getResponse()->getBody());
}
if ($error['error_type'] == 'somethingElse') {
throw new SomethingElseException((string)$e->getResponse()->getBody());
}
}
throw new \Exception((string) $e->getResponse()->getBody());
}
}
The handleRequestError() method read the value of $error['error_type'] and throw specific exception.
However, I want 2 or 3 error codes ($error['code']) to response back to the user with json format. What is good approach to do this?
Eg:
if (if ($error['error_type'] == 'ftp' && $error['code'] == 200) {
response(['success' => false, 'message' => 'could not connect']);
}
I don't want to put response logic in the worker class. Do I need to do it in Exception Handler?
You could bind an error type and error code identifier to the app container and have it create the correct exception class. For example:
app()->bind('type1-error1', ExceptionClass1::class);
app()->bind('type2-error2', ExceptionClass2::class);
app()->bind('type2-error3', ExceptionClass3::class);
These could be bound early in the application life cycle such as in AppServiceProvider boot(). Then the exception handler could resolve an instance of the correct exception based on the type-error combination using:
$e = app('type1-error1');
throw $e;
The container is a powerful tool at your disposal!
Related
I'm trying to modify my App\Exceptions\Handler to pass the request (and therefore current URL) through to all exceptions. For this reason I need the lowest-level exception class I can get hold of to type-hint to the ->renderable() method.
Laravel/Symfony's HttpException works but only for HTTP errors, leaving out all non-HTTP exceptions. PHP's Exception class works when using getCode() instead of getStatusCode(), but always returns a "0" for both HTTP errors and exceptions. Is there another low-level exception class that will work for my purposes, or otherwise any other way to accomplish what I'm trying to do here?
public function register()
{
$this->renderable(function (Exception $exception, $request) {
$url = $request->fullUrl();
$status = $exception->getCode();
Log::warning("Error $status when trying to visit $url. Received the following message: " . $exception->getMessage());
return response()->view("errors.$status", [
"exception" => $exception
],
$status
);
});
}
}
For what it's worth, I'm using the following web routes to trigger exceptions and HTTP errors for testing:
if (app()->environment('local')) {
Route::get("/exception", function (){
throw new JsonException; // chosen because it's one of the few Laravel exceptions
// that doesn't seem to automatically resolve to a HTTP error
});
}
if (app()->environment('local')) {
Route::get("/fail/{status}", function ($status){
abort($status);
});
}
As requested, this is what I have in my Handler. I use some custom logging, and I want to make sure I grab the right code when it's an HTTP error.
public function report(Throwable $e)
{
$code = match (get_class($e)) {
'Symfony\Component\HttpKernel\Exception\NotFoundHttpException' => 404,
\HttpException::class => $e->getStatusCode(),
default => 'No Code',
};
// more stuff here
}
You can use $e->getCode() for your default as well
You can throw your JsonException and abort like so with a given code and the handler should grab it from getCode like so
// in your controller
throw new \JsonException('Something went wrong', 500);
// or
abort(500, 'Something went wrong')
// in your handler
$status = $e->getCode(); // 500
$message = $e->getMessage(); // "Something went wrong"
That said it's better to keep them as semantically separate as possible in my opinion, and let the handler do the handling depending on what it receives.
I finally managed to figure this out in the end. It's probably not the cleanest solution, but it works perfectly for my needs.
It works by inspecting each instance of the Exception class and using PHP's instanceof() to check whether it's a HTTP exception or not. If it is, it gets logged with the request URL and returns a view with a status code. If it's a generic non-HTTP exception, it gets logged with the request URL and returns another view with no status code (or you can keep the default exception behaviour by removing the return block, which renders a blank screen in production).
public function register()
{
$this->renderable(function (Exception $exception, $request) {
$url = $request->fullUrl();
if ($exception instanceof HttpException) {
$status = $exception->getStatusCode();
Log::warning("Error $status occurred when trying to visit $url. Received the following message: " . $exception->getMessage());
return response()->view("errors.error", [
"exception" => $exception,
"status" => $status
],
$status
);
} else {
$status = $exception->getCode();
Log::warning("Exception $status occurred when trying to visit $url. Received the following message: " . $exception->getMessage());
return response()->view("errors.exception", [
"exception" => $exception,
"status" => $status
]);
}
});
// Optionally suppress all Laravel's default logging for exceptions, so only your own logs go to the logfile
$this->reportable(function (Exception $e) {
})->stop();
}
I am using symfony2.8 and
We have a KernelExceptionService and I want to skip it if there is any Exception like 500,400 or any and get back to service and continue the work.
The reason
We are hitting multiple url to fetch the data and if there is any exception occurred whole processing get stopped.
public function onKernelException(GetResponseForExceptionEvent $event) {
$exception = $event->getException();
$response = new JsonResponse;
$request = $event->getRequest();
if ($exception instanceof InvalidConfigurationException) {
//500 case
$responseData = return [
'code' => Response::HTTP_NOT_FOUND,
'message' => $exception->getMessage()
];
} else {
// same as aobve if with difference code
}
//Prepare the response
$response->setData($responseData);
$response->setStatusCode($statusCode);
$event->setResponse($response);
}
Just wrap the particular code with a try catch block?
That way your exception listener will never trigger and you can handle the exception differently in that specific part of code.
I just finished creating an API application with slim framework, initially, in my code I use a dependency container to handle all exceptions thrown, the code is below.
//Add container to handle all exceptions/errors, fail safe and return json
$container['errorHandler'] = function ($container) {
return function ($request, $response, $exception) use ($container) {
//Format of exception to return
$data = [
'message' => $exception->getMessage()
];
return $container->get('response')->withStatus(500)
->withHeader('Content-Type', 'application/json')
->write(json_encode($data));
};
};
But instead of throwing a 500 Server Error all the time I would like to add other HTTPS reponse code. I wonder if I could get help on how to go about that.
public static function decodeToken($token)
{
$token = trim($token);
//Check to ensure token is not empty or invalid
if ($token === '' || $token === null || empty($token)) {
throw new JWTException('Invalid Token');
}
//Remove Bearer if present
$token = trim(str_replace('Bearer ', '', $token));
//Decode token
$token = JWT::decode($token, getenv('SECRET_KEY'), array('HS256'));
//Ensure JIT is present
if ($token->jit == null || $token->jit == "") {
throw new JWTException('Invalid Token');
}
//Ensure User Id is present
if ($token->data->uid == null || $token->data->uid == "") {
throw new JWTException("Invalid Token");
}
return $token;
}
The problem is even more from functions like the above one, since slim framework decides to handle all exceptions implicitly, I have no access to use try catch to catch any errors
Not that hard, it is simple. Rewrite the code:
container['errorHandler'] = function ($container) {
return function ($request, $response, $exception) use ($container) {
//Format of exception to return
$data = [
'message' => $exception->getMessage()
];
return $container->get('response')->withStatus($response->getStatus())
->withHeader('Content-Type', 'application/json')
->write(json_encode($data));
};
}
So what does this code do? You basically pass a $response as before, and what this code does is that it gets the status code from the $response object and passes it to the withStatus() method.
Slim Documentation for referring to status.
You could use the withJson() method of Slim\Http\Response Object
class CustomExceptionHandler
{
public function __invoke(Request $request, Response $response, Exception $exception)
{
$errors['errors'] = $exception->getMessage();
$errors['responseCode'] = 500;
return $response
->withStatus(500)
->withJson($errors);
}
}
and if you are using dependency injection you could do
$container = $app->getContainer();
//error handler
$container['errorHandler'] = function (Container $c) {
return new CustomExceptionHandler();
};
function order_confirmationAction($order,$token) {
$client = new \GuzzleHttp\Client();
$answer = $client->post("http://www.fullcommerce.com/rest/public/Qtyresponse",
array('body' => $order)
);
$answer = json_decode($answer);
if ($answer->status=="ACK") {
return $this->render('AcmeDapiBundle:Orders:ack.html.twig', array(
'message' => $answer->message,
));
} else throw new \Symfony\Component\HttpKernel\Exception\HttpException(500, $answer->message);
}
If $client->post() response status code is an "Error 500" Symfony stops the script execution and throw new exception before the json decoding.
How can I force Symfony to ignore $client->post() bad response and execute till the last if statement?
$client = new \GuzzleHttp\Client();
try {
$answer = $client->post("http://www.fullcommerce.com/rest/public/Qtyresponse",
array('body' => $serialized_order)
);
}
catch (\GuzzleHttp\Exception\ServerException $e) {
if ($e->hasResponse()) {
$m = $e->getResponse()->json();
throw new \Symfony\Component\HttpKernel\Exception\HttpException(500, $m['result']['message']);
}
}
I solved like this. In that way I can access to responses of remote server even if it returns an error 500 code.
Per Guzzle documentation:
Guzzle throws exceptions for errors that occur during a transfer.
Specifically, if the API responds with a 500 HTTP error, you shouldn't expect its content to be JSON, and you don't want to parse it, so you're better off re-throwing an exception from there already (or informing the user that something went wrong). I would suggest trying this out:
function order_confirmationAction($order, $token) {
$client = new \GuzzleHttp\Client();
try {
$answer = $client->post("http://www.fullcommerce.com/rest/public/Qtyresponse",
array('body' => $order)
);
}
catch (Exception $e) {
throw new \Symfony\Component\HttpKernel\Exception\HttpException(500, $e->getMessage());
}
$answer = json_decode($answer);
if ($answer->status=="ACK") {
return $this->render('AcmeDapiBundle:Orders:ack.html.twig', array(
'message' => $answer->message,
));
} else {
throw new \Symfony\Component\HttpKernel\Exception\HttpException(500, $answer->message);
}
}
It is probably also a good idea to check for errors when JSON-decoding the response, because there could be surprises in the content you're getting (eg. wrong format, missing or unexpected fields or values, etc.).
I'm trying to catch exceptions from a set of tests I'm running on an API I'm developing and I'm using Guzzle to consume the API methods. I've got the tests wrapped in a try/catch block but it is still throwing unhandled exception errors. Adding an event listener as described in their docs doesn't seem to do anything. I need to be able to retrieve the responses that have HTTP codes of 500, 401, 400, in fact anything that isn't 200 as the system will set the most appropriate code based on the result of the call if it didn't work.
Current code example
foreach($tests as $test){
$client = new Client($api_url);
$client->getEventDispatcher()->addListener('request.error', function(Event $event) {
if ($event['response']->getStatusCode() == 401) {
$newResponse = new Response($event['response']->getStatusCode());
$event['response'] = $newResponse;
$event->stopPropagation();
}
});
try {
$client->setDefaultOption('query', $query_string);
$request = $client->get($api_version . $test['method'], array(), isset($test['query'])?$test['query']:array());
// Do something with Guzzle.
$response = $request->send();
displayTest($request, $response);
}
catch (Guzzle\Http\Exception\ClientErrorResponseException $e) {
$req = $e->getRequest();
$resp =$e->getResponse();
displayTest($req,$resp);
}
catch (Guzzle\Http\Exception\ServerErrorResponseException $e) {
$req = $e->getRequest();
$resp =$e->getResponse();
displayTest($req,$resp);
}
catch (Guzzle\Http\Exception\BadResponseException $e) {
$req = $e->getRequest();
$resp =$e->getResponse();
displayTest($req,$resp);
}
catch( Exception $e){
echo "AGH!";
}
unset($client);
$client=null;
}
Even with the specific catch block for the thrown exception type I am still getting back
Fatal error: Uncaught exception 'Guzzle\Http\Exception\ClientErrorResponseException' with message 'Client error response [status code] 401 [reason phrase] Unauthorized [url]
and all execution on the page stops, as you'd expect. The addition of the BadResponseException catch allowed me to catch 404s correctly, but this doesn't seem to work for 500 or 401 responses. Can anyone suggest where I am going wrong please.
Depending on your project, disabling exceptions for guzzle might be necessary. Sometimes coding rules disallow exceptions for flow control. You can disable exceptions for Guzzle 3 like this:
$client = new \Guzzle\Http\Client($httpBase, array(
'request.options' => array(
'exceptions' => false,
)
));
This does not disable curl exceptions for something like timeouts, but now you can get every status code easily:
$request = $client->get($uri);
$response = $request->send();
$statuscode = $response->getStatusCode();
To check, if you got a valid code, you can use something like this:
if ($statuscode > 300) {
// Do some error handling
}
... or better handle all expected codes:
if (200 === $statuscode) {
// Do something
}
elseif (304 === $statuscode) {
// Nothing to do
}
elseif (404 === $statuscode) {
// Clean up DB or something like this
}
else {
throw new MyException("Invalid response from api...");
}
For Guzzle 5.3
$client = new \GuzzleHttp\Client(['defaults' => [ 'exceptions' => false ]] );
Thanks to #mika
For Guzzle 6
$client = new \GuzzleHttp\Client(['http_errors' => false]);
To catch Guzzle errors you can do something like this:
try {
$response = $client->get('/not_found.xml')->send();
} catch (Guzzle\Http\Exception\BadResponseException $e) {
echo 'Uh oh! ' . $e->getMessage();
}
... but, to be able to "log" or "resend" your request try something like this:
// Add custom error handling to any request created by this client
$client->getEventDispatcher()->addListener(
'request.error',
function(Event $event) {
//write log here ...
if ($event['response']->getStatusCode() == 401) {
// create new token and resend your request...
$newRequest = $event['request']->clone();
$newRequest->setHeader('X-Auth-Header', MyApplication::getNewAuthToken());
$newResponse = $newRequest->send();
// Set the response object of the request without firing more events
$event['response'] = $newResponse;
// You can also change the response and fire the normal chain of
// events by calling $event['request']->setResponse($newResponse);
// Stop other events from firing when you override 401 responses
$event->stopPropagation();
}
});
... or if you want to "stop event propagation" you can overridde event listener (with a higher priority than -255) and simply stop event propagation.
$client->getEventDispatcher()->addListener('request.error', function(Event $event) {
if ($event['response']->getStatusCode() != 200) {
// Stop other events from firing when you get stytus-code != 200
$event->stopPropagation();
}
});
thats a good idea to prevent guzzle errors like:
request.CRITICAL: Uncaught PHP Exception Guzzle\Http\Exception\ClientErrorResponseException: "Client error response
in your application.
In my case I was throwing Exception on a namespaced file, so php tried to catch My\Namespace\Exception therefore not catching any exceptions at all.
Worth checking if catch (Exception $e) is finding the right Exception class.
Just try catch (\Exception $e) (with that \ there) and see if it works.
If the Exception is being thrown in that try block then at worst case scenario Exception should be catching anything uncaught.
Consider that the first part of the test is throwing the Exception and wrap that in the try block as well.
You need to add a extra parameter with http_errors => false
$request = $client->get($url, ['http_errors' => false]);
I want to update the answer for exception handling in Psr-7 Guzzle, Guzzle7 and HTTPClient(expressive, minimal API around the Guzzle HTTP client provided by laravel).
Guzzle7 (same works for Guzzle 6 as well)
Using RequestException, RequestException catches any exception that can be thrown while transferring requests.
try{
$client = new \GuzzleHttp\Client(['headers' => ['Authorization' => 'Bearer ' . $token]]);
$guzzleResponse = $client->get('/foobar');
// or can use
// $guzzleResponse = $client->request('GET', '/foobar')
if ($guzzleResponse->getStatusCode() == 200) {
$response = json_decode($guzzleResponse->getBody(),true);
//perform your action with $response
}
}
catch(\GuzzleHttp\Exception\RequestException $e){
// you can catch here 400 response errors and 500 response errors
// You can either use logs here use Illuminate\Support\Facades\Log;
$error['error'] = $e->getMessage();
$error['request'] = $e->getRequest();
if($e->hasResponse()){
if ($e->getResponse()->getStatusCode() == '400'){
$error['response'] = $e->getResponse();
}
}
Log::error('Error occurred in get request.', ['error' => $error]);
}catch(Exception $e){
//other errors
}
Psr7 Guzzle
use GuzzleHttp\Psr7;
use GuzzleHttp\Exception\RequestException;
try {
$client->request('GET', '/foo');
} catch (RequestException $e) {
$error['error'] = $e->getMessage();
$error['request'] = Psr7\Message::toString($e->getRequest());
if ($e->hasResponse()) {
$error['response'] = Psr7\Message::toString($e->getResponse());
}
Log::error('Error occurred in get request.', ['error' => $error]);
}
For HTTPClient
use Illuminate\Support\Facades\Http;
try{
$response = Http::get('http://api.foo.com');
if($response->successful()){
$reply = $response->json();
}
if($response->failed()){
if($response->clientError()){
//catch all 400 exceptions
Log::debug('client Error occurred in get request.');
$response->throw();
}
if($response->serverError()){
//catch all 500 exceptions
Log::debug('server Error occurred in get request.');
$response->throw();
}
}
}catch(Exception $e){
//catch the exception here
}
Old question, but Guzzle adds the response within the exception object. So a simple try-catch on GuzzleHttp\Exception\ClientException and then using getResponse on that exception to see what 400-level error and continuing from there.
I was catching GuzzleHttp\Exception\BadResponseException as #dado is suggesting. But one day I got GuzzleHttp\Exception\ConnectException when DNS for domain wasn't available.
So my suggestion is - catch GuzzleHttp\Exception\ConnectException to be safe about DNS errors as well.
If you are using the latest version say 6^ and you have a JSON parameter, you can add 'http_errors' => false to the array together with the JSON as seen below
I was looking out for away to do this i.e with my JSON in there but couldn't find a straight answer.