読者です 読者をやめる 読者になる 読者になる

scramble cadenza

技術ネタのガラクタ置き場

respond_to vs routes 指定の format

イントロ

例えばAPI サーバーを作るときに「この URL にアクセスする時は、常に json でアクセスしてほしい」みたいな事を実現したい。

それを実現する具体的な方法としては namespace :api, format: 'json' と routes を書けばいいと思ってました。
(デフォルトの format が json になるので)

ただ

  • それ以外の format を渡した時にどう動くか
  • 似たような事ができる実装とどう違うのか

がよくわかってなかったので、その時にした実験をもう一度やってみたメモ。

パターン1

class Api::AccountsController < ApplicationController
  def index
    @accounts = Account.all

    respond_to do |format|
      format.html { render }
      format.json { render }
    end
  end
end
Rails.application.routes.draw do
  namespace :api, format: 'json' do
    resources :accounts
  end
end
  • controller 側では respond_to複数 format を受けつける準備ができている
  • namespace では json を指定

結果

% curl -I http://localhost:3000/api/accounts
HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Content-Type: application/json; charset=utf-8
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: 01d34bba-60c5-4185-9e96-e343d179c45b
X-Runtime: 0.014599
Server: WEBrick/1.3.1 (Ruby/2.2.3/2015-08-18)
Content-Length: 10
Connection: Keep-Alive
% curl -I http://localhost:3000/api/accounts.html
HTTP/1.1 404 Not Found
Content-Type: text/html; charset=utf-8
Content-Length: 41119
X-Web-Console-Session-Id: 939cd9fb2834ea8353d1332a62d97e15
X-Request-Id: a77ea27e-f5c0-4766-b357-09b964188e55
X-Runtime: 6.597595
Server: WEBrick/1.3.1 (Ruby/2.2.3/2015-08-18)
Connection: Keep-Alive
  • controller で受け取る formatjson になる
  • response の Content-Typeapplication/json
  • json 以外の format の request が投げられたら、404 Not found
    • controller 側では html をの時は html の render をする準備ができているのにも関わらず
    • routes 側で 404 になってしまうので、controller に到達しない
  • controller 側に到達した時には、常に json 形式
    • (書いてしまってるけど) 実質 renspond_to複数 format 対応は 不要

パターン2

class Api::AccountsController < ApplicationController
  def index
    @accounts = Account.all

    respond_to do |format|
      format.html { render }
      format.json { render }
    end
  end
end
Rails.application.routes.draw do
  namespace :api do
    resources :accounts
  end
end
  • format 指定を削除
  • controller 側は変わらず

結果

% curl -I http://localhost:3000/api/accounts
HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Content-Type: text/html; charset=utf-8  # text/html に注目!
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: 4db51f09-53cc-42ca-a6a0-49e4080b5030
X-Runtime: 0.492789
Server: WEBrick/1.3.1 (Ruby/2.2.3/2015-08-18)
Content-Length: 1715
Connection: Keep-Alive
% curl -I http://localhost:3000/api/accounts.html
HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Content-Type: text/html; charset=utf-8
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: 7d446561-2768-4f20-8832-4d6476115d45
X-Runtime: 0.050431
Server: WEBrick/1.3.1 (Ruby/2.2.3/2015-08-18)
Content-Length: 1715
Connection: Keep-Alive
  • json が通るか確かめる
% curl -I -X GET http://localhost:3000/api/accounts.json
HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Content-Type: application/json; charset=utf-8
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: c3c4a0fb-847c-4240-9470-606695c6b935
X-Runtime: 0.023534
Server: WEBrick/1.3.1 (Ruby/2.2.3/2015-08-18)
Content-Length: 10
Connection: Keep-Alive
  • respond_to での指定以外の format
% curl -I http://localhost:3000/api/accounts.xml
HTTP/1.1 406 Not Acceptable
Content-Type: text/html; charset=utf-8
Content-Length: 115323
X-Web-Console-Session-Id: bfb96347bd3ab4058a468ebb5762595f
X-Request-Id: 74ee3306-aa22-4615-a72d-c1a7602ef434
X-Runtime: 0.640820
Server: WEBrick/1.3.1 (Ruby/2.2.3/2015-08-18)
Connection: Keep-Alive
  • respond_to do ~ end で指定する format の内、 最初に書いてある方が default になる
    • これは controller のコードで、優先順位が決定するということ
    • 今回は format.html { render } を先に書いたので、html が default になった
      • json を上にすると、format 指定なしの状態では json が返る
  • respond_to ブロックで指定している format 以外の request が投げられたら 406 Not acceptable

responders

class Api::AccountsController < ApplicationController
  respond_to :json
  def index
    @accounts = Account.all
    respond_with @account
  end
end
Rails.application.routes.draw do
  namespace :api do
    resources :accounts
  end
end

結果

% curl -I http://localhost:3000/api/accounts
HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Content-Type: application/json; charset=utf-8
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: f7a06375-320d-4d37-a93c-12d9d3c17cf0
X-Runtime: 0.299781
Server: WEBrick/1.3.1 (Ruby/2.2.3/2015-08-18)
Content-Length: 0
Connection: Keep-Alive
% curl -I http://localhost:3000/api/accounts.html
HTTP/1.1 406 Not Acceptable
Content-Type: text/html; charset=utf-8
Content-Length: 115331
X-Web-Console-Session-Id: 780f1e5e4050cb0b7e72d36f60841f4c
X-Request-Id: b0f92ee8-c6fa-4dc4-872e-f60adc942104
X-Runtime: 0.379881
Server: WEBrick/1.3.1 (Ruby/2.2.3/2015-08-18)
Connection: Keep-Alive
  • パターン2 とほぼおなじ
    • respond_to複数個 format が渡せるが、format 指定無しでリクエストされた場合、最初に指定されてある format が利用される
      • respond_to :html, :json なら、html がデフォルト値
  • 指定以外の format でのリクエストは、406 Not acceptable
  • responders の利点は、controller 毎に format の指定をしなくて済むことなのかな?
    • 一箇所にまとめられていれば、controller 毎にデフォルトの format が異なるという間違いを避けられる。
    • (一瞬触っただけなので、よくわかってない)

結論

サーバーが指定外の format で request を受けた時、どう返したいかで実装が変わる

  • 406 Not acceptable を返す場合は respond_to (あるいは responders)
    • 複数 format を受け付ける URL なら、こちらのほうが自然
  • 404 Not Found を返す場合は namespace :prefix, format: 'format名'
    • 文字列 で指定すること。(symbol じゃダメ)
    • 例えば「json しか絶対受け付けない」なら、こういうのもありかも。

参考