Laravel 5: Handle exceptions when request wants JSON

隐身守侯 提交于 2019-12-03 01:20:04

问题


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.


回答1:


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);
}



回答2:


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);
}



回答3:


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);
}



回答4:


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);
}



回答5:


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);
}



回答6:


Quick note on this... In Laravel 5.5 the exception is named "$exception" and not "$e" in the method.



来源:https://stackoverflow.com/questions/28944097/laravel-5-handle-exceptions-when-request-wants-json

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!