Rack::MethodOverrideを少しいじった話
よくある背景
よくある理由でIE対応を迫られて、ありがちな理由でPOSTなのにURLにパラメータがくっついてくるという
ありふれた問題にぶつかったまでは良かったのだが(悪かったのだが)
RESTfulなAPIに対応するという点で少しはまったのでメモ。
試したこと
最初はよくある解決策として、PUTで送りたいときに
POST http://hostname/api/member/1?_method=put
と、POSTと_methodによるディスパッチを試みた。
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のパラメータが合体されているようである。
(今回はそれに置き換えた)