Laravel 5: обрабатывать исключения, когда запрос хочет JSON

Я делаю загрузку файлов через AJAX на Laravel 5. У меня почти все работает, кроме одного.

Когда я пытаюсь загрузить файл, который слишком велик (больше, чем upload_max_filesize и post_max_size я получаю брошенное TokenMismatchException.

Этого следует ожидать, потому что я знаю, что мой ввод будет пустым, если эти лимиты будут превышены. Пустой вход, означает, что нет _token поэтому почему промежуточное программное обеспечение, ответственное за проверку токенов CSRF, поднимает суету.

Однако моя проблема заключается не в том, что это исключение выбрасывается, а в том, как это делается. Когда это исключение попадает на Laravel, он выплескивает HTML для общей страницы Whoops (с загрузкой трассировки стека, так как я в режиме отладки).

Какой лучший способ справиться с этим исключением, чтобы JSON возвращался через AJAX (или когда запрашивался JSON), в то же время сохраняя поведение по умолчанию?


Изменить: похоже, это происходит независимо от того, что было сделано. Я только что попробовал сделать запрос через AJAX (Datatype: JSON) на "страницу", которая не существует в попытке получить 404, и происходит то же самое - возвращается HTML, нет JSON.

Ответы

Ответ 1

Я собираюсь сделать это самостоятельно, принимая во внимание ответ @Wader и комментарии от @Tyler Crompton:

приложение/исключения /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

В вашем приложении у вас должно быть app/Http/Middleware/VerifyCsrfToken.php. В этом файле вы можете справиться с тем, как работает промежуточное программное обеспечение. Таким образом, вы можете проверить, является ли запрос ajax и обрабатывать то, что вам нравится.

Альтернативно и, вероятно, лучшее решение, было бы отредактировать обработчик исключений, чтобы вернуть json. См. app/exceptions/Handler.php, что-то вроде ниже будет начальным местом

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

Основываясь на функции рендеринга обработчика @Jonathon, я бы просто изменил условия, чтобы исключить экземпляры ValidationException.

// If the request wants JSON + exception is not ValidationException
if ($request->wantsJson() && ( ! $exception instanceof ValidationException))

Laravel 5 возвращает ошибки проверки в JSON, если это необходимо.

Полный метод в 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

Я изменил несколько реализаций, найденных здесь для работы над Laravel 5.3. Основное различие заключается в том, что my вернет правильные тексты HTTP-статуса

В вашей функции render() в приложении \Exceptions\Handler.php добавьте этот фрагмент вверху:

    if ($request->wantsJson()) {
        return $this->renderExceptionAsJson($request, $exception);
    }

Содержание 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

Используя код @Jonathon, здесь быстрое исправление для 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

Быстрая заметка об этом... В Laravel 5.5 исключение называется "$ exception", а не "$ e" в методе.