53
49

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Posted at

なにがしたい?

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と仲良くなれた気がしました。

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

53
49
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
53
49

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?