web-dev-qa-db-ja.com

Laravel 5:要求にJSONが必要な場合に例外を処理する

AJAX on Laravel 5.を介してファイルのアップロードを行っています。

大きすぎるファイルをアップロードしようとすると(upload_max_filesizeおよびpost_max_size TokenMismatchExceptionがスローされます。

ただし、これらの制限を超えると入力が空になることがわかっているため、これは予想されることです。入力が空の場合、_tokenが受信されるため、CSRFトークンの検証を担当するミドルウェアが大騒ぎしている理由。

ただし、私の問題は、この例外がスローされることではなく、レンダリングされる方法です。この例外がLaravel=によってキャッチされている場合、一般的なWhoopsページのHTMLを吐き出します(デバッグモードになっているため、スタックトレースが大量に発生します)。

JSONがAJAX(またはJSONが要求されたとき)で返され、それ以外の場合はデフォルトの動作を維持するように、この例外を処理する最良の方法は何ですか?


編集:これは、スローされた例外に関係なく発生するようです。 404を取得しようとして存在しない「ページ」に対してAJAX(データ型:JSON)を介してリクエストを作成しようとしましたが、同じことが起こります-HTMLが返されます、JSONフレンドリーなものはありません。

35
Jonathon

@Waderからの回答と@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);
}
84
Jonathon

アプリケーションには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);
}
11
Wader

@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);
}
8
Liore Shai

ここで見つかったいくつかの実装を変更して、Laravel 5.3。

App\Exceptions\Handler.phpのrender()関数で、このスニペットを先頭に追加します。

    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);
}
6
Joe Alamo

@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);
}
0
juliochina