I'm doing file uploads via AJAX on Laravel 5. I've got pretty much everything working except one thing.
When I try to upload a file that is too big (Bigger than upload_max_filesize and post_max_size I get a TokenMismatchException thrown.
This is to be expected however, because I know that my input will be empty if these limits are being exceeded. Empty input, means no _token is received hence why the middleware responsible for verifying CSRF tokens is kicking up a fuss.
My issue however is not that this exception is being thrown, it is how it is being rendered. When this exception is being caught by Laravel it's spitting out the HTML for the generic Whoops page (With a load of stack tracing since I'm in debug mode).
What's the best way to handle this exception so that JSON is returned over AJAX (Or when JSON is requested) while keeping the default behaviour otherwise?
Edit: This seems to happen regardless of the exception thrown. I've just tried making a request via AJAX (Datatype: JSON) to a 'page' that doesn't exist in an attempt to get a 404 and the same thing happens - HTML is returned, nothing JSON friendly.
I'm going to take a shot at this one myself taking into account the answer given by #Wader and the comments from #Tyler Crompton:
app/Exceptions/Handler.php
/**
* Render an exception into an HTTP response.
*
* #param \Illuminate\Http\Request $request
* #param \Exception $e
* #return \Illuminate\Http\Response
*/
public function render($request, Exception $e)
{
// If the request wants JSON (AJAX doesn't always want JSON)
if ($request->wantsJson()) {
// Define the response
$response = [
'errors' => 'Sorry, something went wrong.'
];
// If the app is in debug mode
if (config('app.debug')) {
// Add the exception class name, message and stack trace to response
$response['exception'] = get_class($e); // Reflection might be better here
$response['message'] = $e->getMessage();
$response['trace'] = $e->getTrace();
}
// Default response of 400
$status = 400;
// If this exception is an instance of HttpException
if ($this->isHttpException($e)) {
// Grab the HTTP status code from the Exception
$status = $e->getStatusCode();
}
// Return a JSON response with the response array and status code
return response()->json($response, $status);
}
// Default to the parent class' implementation of handler
return parent::render($request, $e);
}
In your application you should have app/Http/Middleware/VerifyCsrfToken.php. In that file you can handle how the middleware runs. So you could check if the request is ajax and handle that how you like.
Alternativly, and probably a better solution, would be to edit the exception handler to return json. See app/exceptions/Handler.php, something like the below would be a starting place
public function render($request, Exception $e)
{
if ($request->ajax() || $request->wantsJson())
{
$json = [
'success' => false,
'error' => [
'code' => $e->getCode(),
'message' => $e->getMessage(),
],
];
return response()->json($json, 400);
}
return parent::render($request, $e);
}
Building on #Jonathon's handler render function, I would just modify the conditions to exclude ValidationException instances.
// If the request wants JSON + exception is not ValidationException
if ($request->wantsJson() && ( ! $exception instanceof ValidationException))
Laravel 5 returns validation errors in JSON already if appropriate.
The full method in App/Exceptions/Handler.php:
/**
* Render an exception into an HTTP response.
*
* #param \Illuminate\Http\Request $request
* #param \Exception $exception
* #return \Illuminate\Http\Response
*/
public function render($request, Exception $exception)
{
// If the request wants JSON + exception is not ValidationException
if ($request->wantsJson() && ( ! $exception instanceof ValidationException))
{
// Define the response
$response = [
'errors' => 'Sorry, something went wrong.'
];
// If the app is in debug mode
if (config('app.debug'))
{
// Add the exception class name, message and stack trace to response
$response['exception'] = get_class($exception); // Reflection might be better here
$response['message'] = $exception->getMessage();
$response['trace'] = $exception->getTrace();
}
// Default response of 400
$status = 400;
// If this exception is an instance of HttpException
if ($this->isHttpException($exception))
{
// Grab the HTTP status code from the Exception
$status = $exception->getCode();
}
// Return a JSON response with the response array and status code
return response()->json($response, $status);
}
return parent::render($request, $exception);
}
I have altered several implementations found here to work on Laravel 5.3.
The main difference is that mine will return the correct HTTP status texts
In your render() function in app\Exceptions\Handler.php add this snippet to the top:
if ($request->wantsJson()) {
return $this->renderExceptionAsJson($request, $exception);
}
Contents of renderExceptionAsJson:
/**
* Render an exception into a JSON response
*
* #param $request
* #param Exception $exception
* #return SymfonyResponse
*/
protected function renderExceptionAsJson($request, Exception $exception)
{
// Currently converts AuthorizationException to 403 HttpException
// and ModelNotFoundException to 404 NotFoundHttpException
$exception = $this->prepareException($exception);
// Default response
$response = [
'error' => 'Sorry, something went wrong.'
];
// Add debug info if app is in debug mode
if (config('app.debug')) {
// Add the exception class name, message and stack trace to response
$response['exception'] = get_class($exception); // Reflection might be better here
$response['message'] = $exception->getMessage();
$response['trace'] = $exception->getTrace();
}
$status = 400;
// Build correct status codes and status texts
switch ($exception) {
case $exception instanceof ValidationException:
return $this->convertValidationExceptionToResponse($exception, $request);
case $exception instanceof AuthenticationException:
$status = 401;
$response['error'] = Response::$statusTexts[$status];
break;
case $this->isHttpException($exception):
$status = $exception->getStatusCode();
$response['error'] = Response::$statusTexts[$status];
break;
default:
break;
}
return response()->json($response, $status);
}
In Laravel 8.x, you could do
app/Http/Exceptions/Handler.php
public function render($request, Throwable $exception)
{
if ($request->wantsJson()) {
return parent::prepareJsonResponse($request, $exception);
}
return parent::render($request, $exception);
}
and if you want to always return JSON for all exceptions, just always call parent::prepareJsonResponse and remove parent::render.
When the JSON is rendered with APP_DEBUG=true, you will get a full error report and stack trace. When APP_DEBUG=false, you will get a generic message so that you do not accidentally expose application details.
Using #Jonathon's code, here's a quick fix for Laravel/Lumen 5.3 :)
/**
* Render an exception into an HTTP response.
*
* #param \Illuminate\Http\Request $request
* #param \Exception $e
* #return \Illuminate\Http\Response
*/
public function render($request, Exception $e)
{
// If the request wants JSON (AJAX doesn't always want JSON)
if ($request->wantsJson())
{
// Define the response
$response = [
'errors' => 'Sorry, something went wrong.'
];
// If the app is in debug mode
if (config('app.debug'))
{
// Add the exception class name, message and stack trace to response
$response['exception'] = get_class($e); // Reflection might be better here
$response['message'] = $e->getMessage();
$response['trace'] = $e->getTrace();
}
// Default response of 400
$status = 400;
// If this exception is an instance of HttpException
if ($e instanceof HttpException)
{
// Grab the HTTP status code from the Exception
$status = $e->getStatusCode();
}
// Return a JSON response with the response array and status code
return response()->json($response, $status);
}
// Default to the parent class' implementation of handler
return parent::render($request, $e);
}
My way:
// App\Exceptions\Handler.php
public function render($request, Throwable $e) {
if($request->is('api/*')) {
// Setting Accept header to 'application/json', the parent::render
// automatically transform your request to json format.
$request->headers->set('Accept', 'application/json');
}
return parent::render($request, $e);
}
you can easily catch err.response like this:
axios.post().then().catch(function(err){
console.log(err.response); //is what you want
};
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 have made my custom Exception defined in $this->exception($response['error']); .
So in my service method:
$response = $this->request('POST', $endpoint, $options);
if (array_key_exists('error', $response)) {
$this->exception($response['error']);
}
Which is returning error message if key error exists in response.
And in my controler:
$response = $this->serviceMethod();
if (!empty($response)) {
return $this->apiError(['error' => 'VIOLATION']);
}
If service method pass, response will return empty array which is fine.
Problem occurs that if service method catches error in if() statement and I am dumping controller method response it never gets to that part. It finishes with that custom exception.
How can I accomplish to pass response in both ways so I can handle in in my apiError() method which is made to write error from response.
It's like:
protected function apiError(array $errors = [], $message = ''): void
{
throw new ApiException($errors, $message)
}
I need to connect to an API so I write a function:
try {
$res4 = $client3->post('https://api.example.co.uk/Book', [
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ajhsdbjhasdbasdbasd',
],
'json' => [
'custFirstName' => $FirstName,
'custLastName' => $Surname,
'custPhone' => $Mobile,
'custEmail' => $Email,
]
]);
} catch (GuzzleHttp\Exception\ClientException $e) {
$response = $e->getResponse();
$result = json_decode($response->getBody()->getContents());
$item->update(['status' => 'Problems at step3']);
Mail::raw('Problem at STEP 3', function ($message) use ($serial) {
$message->from('asd.asd#gmail.com', 'asd.asd#gmail.com');
$message->subject('We got a problem etc.');
$message->to('john.smith#gmail.com');
});
}
As you can see I need to make a call to API but in the case when API is down I write catch functions.
But now when API is down and API return '500 Internal Error' this function is just crashed ...
My question is why catch dont handle it?
How I can handle errors - when API is down or bad request... WHy catch{} doesn't work?
UPDATE: here is my laravel.log
[2018-10-25 14:51:04] local.ERROR: GuzzleHttp\Exception\ServerException: Server error: `POST https://api.example.co.uk/Book` resulted in a `500 Internal Server Error` response:
{"message":"An error has occured. Please contact support."}
in /home/public_html/vendor/guzzlehttp/guzzle/src/Exception/RequestException.php:107
Stack trace:
#0 /home/public_html/vendor/guzzlehttp/guzzle/src/Middleware.php(65): GuzzleHttp\Exception\RequestException::create(Object(GuzzleHttp\Psr7\Request), Object(GuzzleHttp\Psr7\Response))
#1 /home/public_html/vendor/guzzlehttp/promises/src/Promise.php(203): GuzzleHttp\Middleware::GuzzleHttp\{closure}(Object(GuzzleHttp\Psr7\Response))
The problem are namespaces here, instead of:
} catch (GuzzleHttp\Exception\ClientException $e) {
you should rather use:
} catch (\GuzzleHttp\Exception\ClientException $e) {
Otherwise PHP assumes that class is in current namespacase, so in fact when you used GuzzleHttp\Exception\ClientException in fact you probably used App\Http\Controllers\GuzzleHttp\Exception\ClientException and such exception obviously won't be thrown by Guzzle.
The exception that is fired is a ServerException instance, and catch block tries to catch ClientException.
} catch (GuzzleHttp\Exception\ServerException $e) {
in your app/exceptions/handler.php file, update the render method like this one.
/**
* Render an exception into an HTTP response.
*
* #param \Illuminate\Http\Request $request
* #param \Exception $exception
* #return \Illuminate\Http\Response
*/
public function render($request, Exception $exception) {
if ($exception instanceof \GuzzleHttp\Exception\ClientException) {
return your_response();
}
return parent::render($request, $exception);
}
This approach worked for me.
The issue is you're trying to catch an exception, when you should actually be trying to catch an error. In your catch block try using \Error instead of \GuzzleHttp\Exception\ClientException.
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'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.