Help us understand the problem. What is going on with this article?

Laravel APIで常にJSONをリクエストするミドルウェア

More than 1 year has passed since last update.

なにがしたい?

APIなのにHTMLエラーが出る
image.png

APIは、ブラウザで開いてもJSONで返して欲しい
image.png

WEBとしても、APIとしても、さらっと動くLaravelさん。コントローラでは、WEBはHTMLで、APIはJSONで返すように、コードを分けていることが多いと思いますが、例えばエラーが起きたらどうしていますか? JSONなのに、HTMLのエラー画面が返ってきたりしていませんか?

実は、Laravelは、特に何もしなくてもうまいことやってくれます。
リクエストされているのがHTMLなのか、JSONなのかを自動的に判断して、WEBならHTML、APIならJSONでエラーを返します。

でも、例えば、APIのエンドポイントをWEBブラウザから試験的に開こうとすると、Laravelは WEBモードだと判断して、エラーハンドリングの挙動が変わります

そうじゃなくて、APIは何が起ころうと、JSONで返して欲しい。
それをさらっと実現する方法です。

APIを作る際にはデフォルトで入れておいたほうが良いかなーと思います。

結論

app/Http/Middleware/RequireJson.php
<?php namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Response;

/**
 * 強制的にJSONをリクエスト
 * リクエストヘッダに Accept: application/json を付与する
 * 
 * @link https://stackoverflow.com/questions/46035072/enforce-json-middleware
 */
class RequireJson
{
    public function handle($request, Closure $next)
    {
        // リクエストヘッダに Accept:application/json を加える
        $request->headers->set('Accept','application/json');

        $response = $next($request);

        return $response;
    }
}

APIの呼び出しには常にこれを適用します。

app/Http/Kernel.php
        //....
        'api' => [
            'throttle:60,1',
            'bindings',
            \App\Http\Middleware\RequireJson::class, // 追加
        ],
        //....

結論2

ミドルウェアじゃいやだ!という人は、Routeに書く方法があります。

app/Providers/RouteServiceProvider.php
    //....
    protected function mapApiRoutes()
    {
        Route::prefix('api')
             ->middleware('api')
             ->header('Accept', 'application/json') // 追加
             ->namespace($this->namespace)
             ->group(base_path('routes/api.php'));
    }
    //....

Routeのほうがカンタンですね。ただ、Routeは route/api.php だとグループを追加する必要があったり、となると直感に反して RouteServiceProvider.php に書く必要があったり、と不確定要素が多いのでどちらかというとミドルウェアをおすすめします。このあたりは好みの問題かもしれません。

解説

仕組みはコチラ。

Illuminate\Http\Request.php
    /**
     * Jsonをリクエストされているか?
     */
    public function expectsJson()
    {
        return ($this->ajax() && ! $this->pjax()) || $this->wantsJson();
    }

    public function wantsJson()
    {
        $acceptable = $this->getAcceptableContentTypes();

        return isset($acceptable[0]) && Str::contains($acceptable[0], ['/json', '+json']);
    }

    public function ajax()
    {
        return $this->isXmlHttpRequest();
    }

    public function pjax()
    {
        return $this->headers->get('X-PJAX') == true;
    }

    // これはベースクラス Symphony のコード
    public function isXmlHttpRequest()
    {
        return 'XMLHttpRequest' == $this->headers->get('X-Requested-With');
    }

つまり、XMLHttpRequest であるか、リクエストヘッダに、Accept:...../json' とかAccept:.....+json' と書いてあるかどうか、といったところです。

ふつうのAjaxリクエストは XMLHttpRequest がセットされるので、自動的にすべてJSONで返してくれますが、ブラウザからのリクエストにはこれが含まれません。そこで、Accept:...../json というヘッダを書き込むことで、常にJSONとして可動させることができます。

感想

APIを作ろう!というと、まずエラーの返し方を考えると思いますが、そこもフレームワークの標準にどかっと乗っかると、余計なことを考えることもなく、余計な手を動かすこともないので早いです。ちょっとイメージと違う動きをするぞ?と思っても、無理に各個撃破するんじゃなくて、めんどくさくてもフレームワークの仕組みをたどると、あっさりとした解決策が見つかったりして、またLaravelと仲良くなれた気がしました。

できるだけ、フレームワークには依存したいお年頃です。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした