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

PerlerのRuby日記

Rubyとか

Rack::MethodOverrideを少しいじった話

よくある背景

よくある理由でIE対応を迫られて、ありがちな理由でPOSTなのにURLにパラメータがくっついてくるという

ありふれた問題にぶつかったまでは良かったのだが(悪かったのだが)

RESTfulなAPIに対応するという点で少しはまったのでメモ。


試したこと

最初はよくある解決策として、PUTで送りたいときに

POST
http://hostname/api/member/1?_method=put

と、POST_methodによるディスパッチを試みた。

とりあえすSinatraを使っていたので、公式サイトを見て

require "sinatra"

configure do
  enable :method_override
end

put "/" do
  "koreha put desu."
end

post "/" do
  "post desu. shippai desu."
end

と設定し、わくわくしながら試したけど、POST扱いになってしまうようだった。

$ curl -i -X POST http://localhost:4567/?_method=PUT
post desu. shippai desu.

調べた

実際に実装を覗いてみると、中でRack::MethodOverrideが使われていて、

#rack-1.5.2/lib/rack/methodoverride.rb

  method = req.POST[METHOD_OVERRIDE_PARAM_KEY] ||
    env[HTTP_METHOD_OVERRIDE_HEADER]

としてパラメータを取り出していた。

reqというのはRack::Requestオブジェクトだから、

#rack-1.5.2/lib/rack/request.rb

# Returns the data received in the request body.
#
# This method support both application/x-www-form-urlencoded and
# multipart/form-data.
def POST

というわけで、URLにくっついているクエリパラメータからは判断できないようになっていた。

解決策を考える

API内の一部の問題だったら、もう少し対応を考えたけど、

API全部が全部こんな感じだったので、素直に(?)Rack::MethodOverrideの方を再オープンして書き換えた。

require "sinatra"

configure do
  class Rack::MethodOverride
    def method_override(env)
#     req = Request.new(env)
      req = Rack::Request.new(env)
#     method = req.POST[METHOD_OVERRIDE_PARAM_KEY] ||
      method = req.params[METHOD_OVERRIDE_PARAM_KEY] ||
        env[HTTP_METHOD_OVERRIDE_HEADER]
      method.to_s.upcase
    end
  end
  enable :method_override
end

put "/" do
  "koreha put desu"
end

post "/" do
  "post desu. shippai desu."
end
$ curl -X POST http://localhost:4567/?_method=PUT
koreha put desu

動いた。

ちなみに

あと、Rack::Requestクラスを見て気がついたけど

paramsって

@params ||= self.GET.merge(self.POST)

ってURLのクエリパラメータとrequest bodyのパラメータが合体されているようである。

(今回はそれに置き換えた)