【问题标题】:Laravel 5: Handle exceptions when request wants JSONLaravel 5:当请求需要 JSON 时处理异常
【发布时间】:2015-05-10 17:35:03
【问题描述】:

我正在通过 Laravel 5 上的 AJAX 上传文件。除了一件事,我几乎可以完成所有工作。

当我尝试上传太大的文件时(大于 upload_max_filesizepost_max_size,我会抛出 TokenMismatchException。

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

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

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


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

【问题讨论】:

  • 所以澄清一下,调试模式和生产模式应该产生相同的结果?
  • 通过 AJAX,生产应产生一个响应,指示存在令牌不匹配异常,而无需任何更多信息。调试模式,理想情况下会返回一堆关于异常的额外细节,但我可以忍受它是一样的。

标签: php ajax exception laravel csrf


【解决方案1】:

考虑到@Wader 和@Tyler Crompton 的cmets 给出的答案,我将亲自试一试:

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

【讨论】:

  • 您可以将设置状态码的行缩短为:$status = method_exists($e, 'getStatusCode') ? $e->getStatusCode() : 400;
  • 这很好用,除非它是一个验证异常,它不返回验证错误。
  • @YoussefLourayad 在使用 Laravel 的验证功能时,无论如何都会通过 AJAX 以 JSON 形式返回验证错误(带有 422 HTTP 状态代码)。但是,如果您真的想要,您可以调整上面的内容以检查异常的类型并将验证错误添加到响应中。 if ($e instanceof ValidationException) {
  • 我记得试过没有成功,我会再试一次。谢谢
  • 没问题,Laravel 通常会自己处理验证错误。看看ValidatesRequest trait,尤其是buildFailedValidationResponse 方法。
【解决方案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);
}

【讨论】:

  • 为什么假设如果请求是 JSON,响应应该是 JSON?
  • 为您的回复干杯。我设法在Handler.php 文件中找到了类似的东西。我还通过 if ($e instanceof TokenMismatchException ....) 添加了对异常类型的检查
  • 这可能应该返回 500,而不是 400。如果输入不合理,您的控制器应该验证输入并抛出 400,但异常处理程序适用于某种异常情况(开发人员? ) 发生错误。
【解决方案3】:

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

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

如果合适,Laravel 5 已经在 J​​SON 中返回验证错误。

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

【讨论】:

  • 我认为倒数第二行应该是 $status = $exception->getStatusCode(),getCode() 方法返回 0,它不被接受为 HTTP 返回码。也许这只是在较新的 Laravel 版本中才会发生的事情。我正在使用 5.6。
【解决方案4】:

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

在 app\Exceptions\Handler.php 中的 render() 函数中,将此 sn-p 添加到顶部:

    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】:

    在 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

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

    【讨论】:

      【解决方案6】:

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

      【讨论】:

        【解决方案7】:

        我的方式:

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

        【讨论】:

          【解决方案8】:

          您可以像这样轻松捕获 err.response:

          axios.post().then().catch(function(err){
          
          
           console.log(err.response);  //is what you want
          
          };
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-04-15
            • 1970-01-01
            • 1970-01-01
            • 2020-03-27
            • 2014-08-05
            相关资源
            最近更新 更多