12
16

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.

swagger-uiでAPIを叩いて結果をjsonで見る

Posted at

ローカルで開発中にUI上からAPIを実行して結果をjson形式で見たいと思ってLaravelプロジェクトで実現した件をまとめておきます。
Laravelは5.5を使っています。

最終的にはこういう事ができます

API実行画面
スクリーンショット 2019-05-26 22.51.13.png

レスポンス
スクリーンショット 2019-05-27 8.52.45.png

必要な作業

必要な対応は下記の通りです。

  • swagger-uiの環境を作る
  • swagger-phpを使ってopenapi.yamlを自動生成できるようにする
  • Cross Originなリクエストをswagger-uiから実行可能にする
  • swagger-uiからのリクエストの時はレスポンスをjsonで返す
  • swagger-uiからのリクエストの時は認証を無効にする
  • swagger-uiからのPOSTリクエスト時のトークンチェックを無効にする

swagger-uiの環境を作る

swager-ui用の階層をプロジェクトのrootに作って下記のファイルを作ります。
ポートは衝突しない何かしらを選んで指定します。

docker-compose.yml
swagger-ui:
  image: swaggerapi/swagger-ui
  container_name: "swagger-ui"
  ports:
    - "8001:8080"
  volumes:
    - ./openapi.yaml:/openapi.yaml
  environment:
    SWAGGER_JSON: /openapi.yaml

SWAGGER_JSON という環境変数で、apiを定義するファイルを指定します。
openapi.yamlは同階層に作成し、マウントしておきます。

swagger-phpを使ってopenapi.yamlを自動生成できるようにする

openapi.yamlを手で書くこともできますが、コントローラに記述したPHPDocから自動生成するツールがあるのでそれを使います。どの道何かしらを追記する必要がありますが、編集対象をphpファイルに統一できるのでこれを使います。

下記の手順でパッケージを追加します。

composer require zircote/swagger-php

これで生成コマンドが使えるようになるので、Makefileに生成コマンドを作っておきます。
実行するとプロジェクト内のPHPDocを見てopenapi.yamlが生成されます。

build: ## openapiの定義ファイルを更新する
    ./vendor/bin/openapi app -o ./swagger-ui/

swagger-phpの記法については下記URLを参考にします。
http://zircote.com/swagger-php

GETリクエストの例

    /**
     * @OA\Get(
     *     path="/sample",
     *     tags={"SampleController"},
     *     @OA\Parameter(name="some_id",
     *         in="query",
     *         description="何かしらのid",
     *         @OA\Schema(type="integer")
     *     ),
     *     @OA\Response(
     *         response="200",
     *         description="レスポンス内容の説明"
     *     )
     * )
     */

POSTリクエストの例

   /**
     * @OA\Post(
     *     path="/sample/register",
     *     tags={"SampleController"},
     *     description="何かしらの登録処理",
     *     @OA\RequestBody(
     *         required=true,
     *         @OA\MediaType(
     *             mediaType="application/x-www-form-urlencoded",
     *             @OA\Schema(
     *                 type="object",
     *                 @OA\Property(
     *                     property="name",
     *                     description="登録する何かの名前",
     *                     type="string"
     *                 ),
     *                 @OA\Property(
     *                     property="user_id",
     *                     description="ユーザid",
     *                     type="integer"
     *                 )
     *             )
     *         )
     *     ),
     *     @OA\Response(
     *         response="200",
     *         description="レスポンス内容の説明",
     *     )
     * )
     */

Cross Originなリクエストをswagger-uiから実行可能にする

異なるhostからのリクエストはデフォルトでは禁じられているため、ローカルでかつswagger-uiからのアクセスのみ許可する設定をします。

下記のページにあるように、許可するhostを指定する必要があります。
https://fetch.spec.whatwg.org/#http-cors-protocol

Middlewareとして下記のクラスを追加します。

Cors.php
<?php

namespace App\Http\Middleware;

use Closure;

class Cors
{
    // from swagger-ui
    const ACCESS_ALLOW_ORIGIN = 'http://localhost:8001';

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $response = $next($request);

        if (app()->isLocal()) {
            $response
                ->header("Access-Control-Allow-Origin" , self::ACCESS_ALLOW_ORIGIN)
                ->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
        }

        return $response;
    }
}

swagger-uiからのリクエストの時はレスポンスをjsonで返す

これでswagger-uiでAPIの一覧を見れるようになりました。
ただし、このままだとレスポンスがhtmlになってしまうので、形式をjsonに変更します。

LaravelはデフォルトでBaseControllerを継承していますが、その間に一つ中間クラスを作ってそこに共通処理を書いていきます。

まずはswagger-uiからのリクエスト判定です。
ローカルからのアクセスであることと、headerの中のアクセスoriginを取得して判定します。

use Illuminate\Routing\Controller as BaseController;
use App\Http\Middleware\Cors;
class Controller extends BaseController
{
    protected function isSwagger() {
        return app()->isLocal() && request()->headers->get('origin') === Cors::ACCESS_ALLOW_ORIGIN;
}

次にこれを使ってresponseの形式を変換します。
実装に手を入れることになり嫌な気持ちがありますが、responseをviewのパラメータを渡す処理をラップする関数を作り、Controllerの記述を書き換えます。

    protected function response($view, $response) {
        if ($this->isSwagger()) {
            return response()->json($response);
        }
        return view($view)->with($response);
    }

これでswagger-uiからのアクセス時にのみresponse形式をjsonにすることができました。
他にうまい方法があればそうしたいところ。

swagger-uiからのリクエストの時は認証を無効にする

何かしらの仕組みで認証を使っている場合は無効にしないとswagger-ui上から突破するのは困難です。
これは認証の使い方次第ですが、標準搭載されているMiddlewareを使っている場合は下記のように対応することで無効化できます。

    public function __construct()
    {
        if (!$this->isSwagger()) {
            $this->middleware('auth')->only(['getUpdate', 'postRegister', 'postUpdate']);
        }
    }

swagger-uiからのPOSTリクエスト時のトークンチェックを無効にする

ここまででGETリクエスウトについては動くようになりますが、まだPOSTリクエスト時にのCSRFトークンのチェックに引っかかります。

これもswagger-ui上から適切なtokenを送るのは困難であるため無効化します。
VerifyCsrfToken.phpというファイルがあるので、handleをオーバーライドし、判定条件を変えます。
UnitTest時に無効にしてるのと挙動としては近いですね。

isSwaggerと同じ条件を書いてしまっているのは良くないですが、雑に作るとこうなります。

VerifyCsrfToken.php
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
class VerifyCsrfToken extends Middleware
{
    /**
     * @return bool
     */
    protected function runningSwaggerUi()
    {
        return app()->isLocal() && request()->headers->get('origin') === Cors::ACCESS_ALLOW_ORIGIN;
    }

    /**
     * @param \Illuminate\Http\Request $request
     * @param Closure $next
     * @return mixed|\Symfony\Component\HttpFoundation\Response
     * @throws TokenMismatchException
     */
    public function handle($request, Closure $next)
    {
        if (
            $this->runningSwaggerUi() ||
            $this->isReading($request) ||
            $this->runningUnitTests() ||
            $this->inExceptArray($request) ||
            $this->tokensMatch($request)
        ) {
            return $this->addCookieToResponse($request, $next($request));
        }

        throw new TokenMismatchException;
    }

最後に

判定処理が共通化できてないのと、Corsクラスの定数に依存してる辺りはなんとかしたいところです。その辺りは次の課題ということで。いい方法知ってる人がいたら教えてほしいです。
とりあえずswagger-uiからAPIを実行してjsonで結果を見る環境はこれでできました。

12
16
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
12
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?