Laravel 5:当请求需要 JSON 时处理异常


Laravel 5: Handle exceptions when request wants JSON

我正在Laravel 5上通过AJAX进行文件上传。除了一件事,我几乎什么都做

当我尝试上传一个太大(大于upload_max_filesizepost_max_size的文件时,我得到一个令牌不匹配异常。

但是,这是

意料之中的,因为我知道如果超过这些限制,我的输入将为空。空输入意味着没有收到_token,因此负责验证CSRF令牌的中间件大惊小怪。

然而,我的问题不是抛出这个异常,而是它的呈现方式。当Laravel捕获此异常时,它会吐出通用Whoops页面的HTML(由于我处于调试模式,因此具有堆栈跟踪负载)。

处理此异常的最佳方法是什么,以便通过 AJAX 返回 JSON(或者在请求 JSON 时),同时保持默认行为?


编辑:无论抛出的异常如何,这似乎都会发生。我刚刚尝试通过 AJAX(数据类型:JSON)向不存在的"页面"发出请求,试图获取 404 并发生同样的事情 - 返回 HTML,没有任何 JSON 友好。

考虑到@Wader给出的答案和@Tyler Crompton的评论,我将自己尝试一下:

应用/异常/处理程序.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);
}

在您的应用程序中,您应该有 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);
}

基于@Jonathon的处理程序渲染函数,我只会修改条件以排除 ValidationException 实例。

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

如果适用,Laravel 5 已经在 JSON 中返回验证错误。

应用/异常/处理程序.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);
}

我已经修改了这里找到的几个实现,以在Laravel 5.3上工作。主要区别在于我的将返回正确的HTTP状态文本

在 app''Exceptions''Handler 的 render() 函数中.php将此代码片段添加到顶部:

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

渲染异常作为Json的内容:

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

在 Laravel 8.x 中,你可以做

app/Http/Exceptions/Handler.php

public function render($request, Throwable $exception)
{
    if ($request->wantsJson()) {
        return parent::prepareJsonResponse($request, $exception);
    }
    return parent::render($request, $exception);
}

如果您想始终为所有异常返回 JSON,只需始终调用 parent::prepareJsonResponse 并删除parent::render 即可。

当 JSON 使用 APP_DEBUG=true 呈现时,您将获得完整的错误报告和堆栈跟踪。APP_DEBUG=false 时,您将收到一条通用消息,以便您不会意外暴露应用程序详细信息。

使用@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);
}

我的方式:


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

你可以很容易地像这样捕捉到err.response:

axios.post().then().catch(function(err){

 console.log(err.response);  //is what you want
};