Dockerを用いてSwaggerを試す 〜使ってみる〜

ども、iOSエンジニアの徳光です。 前回の続きで、いよいよSwaggerを試してみます。 ◆Swagger 『…

ども、iOSエンジニアの徳光です。
前回の続きで、いよいよSwaggerを試してみます。

◆Swagger

『RESTful APIの記述標準化を目指す「Open API Initiative」をマイクロソフト、Google、IBMらが立ち上げ。Swaggerをベースに』ってなことらしいですね。(2015年11月)
なので、使えるようになっておいて損はないかなと。
「The World’s Most Popular API Tooling.」ですから。

▽The swagger-editor web service

 swaggerapi/swagger-editorの指示にしたがって、dockerでSwagger-Editorを動かしてみる。
この環境では、80ポートで別のサーバ動かしてるし、8080ポートも前回の確認でのNginxサーバを動かしてみたりしているので、10080ポートで動かすことにしたよ。
(てか、Swagger-Editorは基本的に8080ポートで動くことになってるんすかね)

$ docker pull swaggerapi/swagger-editor
$ docker run -d -p 10080:8080 swaggerapi/swagger-editor

◆YAML

「YAML Ain’t a Markup Language」の略で、「Yet Another Markup Language」は後付けであると。
ん?
前者は「YAMLはマークアップ言語ではありません」なのに、後者は「さらに別のマークアップ言語」なのか。
よくわからん……
とりあえずYAMLは、構造化データを記述するためのフォーマット仕様であるってことで良いのかな。
で、Rubyに変換して利用されることが多いらしいってことなのかな。
Swaggerでの記述は、JSONでもかけるらしいが、もろもろ面倒なのでYAMLで書いた方が楽だし見やすくかけると。
わかりました、ちょっと勉強しますね。

◆Swagger-Editor

適当なブラウザで「http://localhost:10080/」を叩いてみると、、、さくっと表示されました。
サンプルとして『Swagger Petstore』というのが設定されているので、まずはこれをいじってみることに。

▽Editor上で試す

定義されてるのは、こんな感じ。

pet
Everything about your Pets
store
Access to Petstore orders
user
Operations about user
  1. 「[POST] /pet」の〔Try it out〕ボタンを押すと、body部分の編集ができるようになるので、適当に値をいじってみる。
    {
      "id": 0,
      "category": {
        "id": 0,
        "name": "室内犬"
      },
      "name": "豆助",
      "photoUrls": [
        "string"
      ],
      "tags": [
        {
          "id": 1,
          "name": "可愛い"
        }
      ],
      "status": "sold"
    }
    
  2. 〔Execute〕ボタンを押すと、それ相当のCurlコマンドやら、レスポンスなどが表示される。
  3. 「[GET] /pet/findByStatus」の〔Try it out〕ボタンを押すと、statusとして「available」「pending」「sold」が選べるようになるので、「sold」を選んで〔Execute〕ボタンを押す
  4. レスポンスとして表示されているなかに、さきほど「[POST] /pet」で投げた情報が表示される
    [
      {
        "id": 101,
        "category": {
          "id": 1,
          "name": "屋外犬"
        },
        "name": "アイン",
        "photoUrls": [
          "string"
        ],
        "tags": [
          {
            "id": 0,
            "name": "賢い"
          },
          {
            "id": 1,
            "name": "可愛い"
          }
        ],
        "status": "sold"
      },
      {
        "id": 103,
        "category": {
          "id": 0,
          "name": "室内犬"
        },
        "name": "豆助",
        "photoUrls": [
          "string"
        ],
        "tags": [
          {
            "id": 1,
            "name": "可愛い"
          }
        ],
        "status": "sold"
      }
    ]
    

    はい、実際にはブラウザからもcurlからも何度も「[POST] /pet」を叩いていたので、めっちゃ登録されてました。

    ▽サーバのコードを生成する

    Swagger-Editor上部のメニュー帯に「File」「Edit」「Generate Server」「Generate Client」とあるので、中を確認してみましょう。
    「Generate Server」を展開すると「go-server」があるので、それを選ぶとコード生成したzipファイルの保存ダイアログが表示されるので保存して内容を確認してみる。
    【main.go】の内容をみると、8080ポートでWebサーバを立ち上げるようになっているので、必要に応じて変更して「go run main.go」で実行。

    とりあえず、ブラウザで「localhost:8080」を表示させてみると、「404 page not found」が表示される。(とりあえずサーバが動いていることは確認できたわけで)
    それでは、別のターミナルを開いてcurlコマンドで叩いて確認してみる。

    curl -X GET "http://localhost:8080/v2/pet/findByStatus?status=sold" -H  "accept: application/json"
    

    これやると、goで立ち上げたサーバのターミナルにログが表示されて叩かれたんなーってことが確認できるかと。

    2018/04/04 12:34:56 GET /v2/pet/findByStatus?status=sold FindPetsByStatus 3.612µs
    

    【routers.go】の内容を確認すると、こちらで指定URLパスから、実際の処理への振り分けを実施していると。
    【pet_api.go】の内容を確認すると、とりあえずHTTPステータス200を返すだけになっているので、ここで適当なレスポンスを返すようにしてやれば良いことになるわけで。
    さきほどのcurlで叩いてみている「[GET /pet/findByStatus」のレスポンスを返すようにしてみるならば、「FindPetsByStatus」に振り分けられるので、とりあえず下のように変更してみた。

    import (
    	"fmt"
    	"net/http"
    )
    
    func FindPetsByStatus(w http.ResponseWriter, r *http.Request) {
    	w.Header().Set("Content-Type", "text/html; charset=UTF-8")
    	w.WriteHeader(http.StatusOK)
    	fmt.Fprintf(w, "おためしレスポンス [%s]", r.URL.Path)
    }
    

    以下は、もう少しうまいやり方があるのかもしれないけど、ブラウザなんかで確認するときと、「application/json」指定で来た時のレスポンスの切り替えをやってみた。

    func FindPetsByStatus(w http.ResponseWriter, r *http.Request) {
    	bufAccept := r.Header.Get("Accept")
    	if bufAccept == "application/json" || bufAccept == "*/*" { // 
    		w.Header().Set("Content-Type", "application/json; charset=UTF-8")
    		w.WriteHeader(http.StatusOK)
    		fmt.Fprintf(w, "[ {\"id\":101,\"category\":{\"id\":1,\"name\":\"屋外犬\" },\"name\":\"アイン\",\"photoUrls\":[\"string\" ],\"tags\":[ {\"id\":0,\"name\":\"賢い\" }, {\"id\":1,\"name\":\"可愛い\" } ],\"status\":\"sold\" }, {\"id\":103,\"category\":{\"id\":0,\"name\":\"室内犬\" },\"name\":\"豆助\",\"photoUrls\":[\"string\" ],\"tags\":[ {\"id\":1,\"name\":\"可愛い\" } ],\"status\":\"sold\" } ]")
    	} else {
    		w.Header().Set("Content-Type", "text/html; charset=UTF-8")
    		w.WriteHeader(http.StatusOK)
    		fmt.Fprintf(w, "\n%s\n", bufAccept)
    		fmt.Fprintf(w, "おためしレスポンス [%s]", r.URL.Path)
    	}
    }
    

    モックの場合には、適当な値を埋め込んだJSONを生成して返したり、サンプルJSONファイルをまるっと返却するってので良いかもしれない。
    とりあえずこれでブラウザで「http://localhost:8080/v2/pet/findByStatus」を表示させた場合には下記のようなレスポンスが表示され、curlコマンド(で [application/json] 指定)を叩いた場合との振り分けができました。

    おためしレスポンス [/v2/pet/findByStatus] text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 
    
    curl -X GET "http://localhost:8080/v2/pet/findByStatus?status=sold" -H  "accept: application/json"
    
    [ {"id":101,"category":{"id":1,"name":"屋外犬" },"name":"アイン","photoUrls":["string" ],"tags":[ {"id":0,"name":"賢い" }, {"id":1,"name":"可愛い" } ],"status":"sold" }, {"id":103,"category":{"id":0,"name":"室内犬" },"name":"豆助","photoUrls":["string" ],"tags":[ {"id":1,"name":"可愛い" } ],"status":"sold" } ]
    
    curl -X GET "http://localhost:8080/v2/pet/findByStatus?status=sold" -H  "accept: text/html"
    
    おためしレスポンス [/v2/pet/findByStatus]
    text/html