はい!今やってます!

Work Pertly, Live Idly

LaravelでAjaxリクエストかFormリクエストかどうかは誰が判定しているか

LaravelでAPIを書いていて、適当なRestClientを使ってリクエストを送った際に、 Validationエラーで引っかかると、HTTPステータスコード302でリダイレクトされるということがあった。 APIなので、単純にValidationのエラー結果をjsonなりで返してくれればいいのだけどHTTPヘッダーが不足していた。

FormリクエストだとValidationに引っかかった時に、もとのページにリダイレクトして、 Ajax通信の場合はjsonを返すという仕様は公式ドキュメントにも書かれている。

AJAXリクエストとバリデーション この例ではアプリケーションにデータを送るために伝統的なフォームを使いました。しかし、多くのアプリケーションでAJAXリクエストが使用されています。AJAXリクエストにvalidateメソッドを使う場合、Laravelはリダイレクトレスポンスを生成しません。代わりにバリデーションエラーを全部含んだJSONレスポンスを生成します。このJSONレスポンスは422 HTTPステータスコードで送られます。

結論から言うと、 X-Requested-WithヘッダーにXMLHttpRequestを渡せば、Ajaxリクエストとして判定してくれてValidationエラーの内容がjsonで返ってくるのだけど、

f:id:yuji_ueda:20190425094349p:plain
X-Requested-With:XMLHttpRequest

少し気になったのでコードを読んでみた。

vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Handler.php

    /**
     * Create a response object from the given validation exception.
     *
     * @param  \Illuminate\Validation\ValidationException  $e
     * @param  \Illuminate\Http\Request  $request
     * @return \Symfony\Component\HttpFoundation\Response
     */
    protected function convertValidationExceptionToResponse(ValidationException $e, $request)
    {
        if ($e->response) {
            return $e->response;
        }

        return $request->expectsJson()
                    ? $this->invalidJson($request, $e)
                    : $this->invalid($request, $e);
    }

vendor/laravel/framework/src/Illuminate/Http/Concerns/InteractsWithContentTypes.php

    /**
     * Determine if the current request probably expects a JSON response.
     *
     * @return bool
     */
    public function expectsJson()
    {
        return ($this->ajax() && ! $this->pjax() && $this->acceptsAnyContentType()) || $this->wantsJson();
    }

vendor/laravel/framework/src/Illuminate/Http/Request.php

    /**
     * Determine if the request is the result of an AJAX call.
     *
     * @return bool
     */
    public function ajax()
    {
        return $this->isXmlHttpRequest();
    }

vendor/symfony/http-foundation/Request.php

    /**
     * Returns true if the request is a XMLHttpRequest.
     *
     * It works if your JavaScript library sets an X-Requested-With HTTP header.
     * It is known to work with common JavaScript frameworks:
     *
     * @see http://en.wikipedia.org/wiki/List_of_Ajax_frameworks#JavaScript
     *
     * @return bool true if the request is an XMLHttpRequest, false otherwise
     */
    public function isXmlHttpRequest()
    {
        return 'XMLHttpRequest' == $this->headers->get('X-Requested-With');
    }

こんな感じ。