Rubyのnet/httpで重複するキーをもつパラメータをPostする
net/http を使うと、単純にPostするのは至って簡単だ。
Net::HTTP.post_form
メソッドを叩くだけで事足りる。
require 'net/http' require 'uri' #例1: POSTするだけ res = Net::HTTP.post_form(URI.parse('http://www.example.com/search'), {'q'=>'ruby', 'max'=>'50'}) puts res.body #例2: 認証付きで POST する res = Net::HTTP.post_form(URI.parse('http://jack:pass@www.example.com/todo.cgi'), {'from'=>'2005-01-01', 'to'=>'2005-03-31'}) puts res.body #例3: より細かく制御する url = URI.parse('http://www.example.com/todo.cgi') req = Net::HTTP::Post.new(url.path) req.basic_auth 'jack', 'pass' req.set_form_data({'from'=>'2005-01-01', 'to'=>'2005-03-31'}, ';') res = Net::HTTP.new(url.host, url.port).start {|http| http.request(req) } case res when Net::HTTPSuccess, Net::HTTPRedirection # OK else res.value end
キーが重複するパラメータ問題
フォームで配列を送信するとしよう。
例えば、
somekey[] => "hoge", somekey[] => "fuga", somekey[] => "piyo",
といったパラメータだ。
post_form
メソッドは、uri
とparams
の引数を取る。第一引数のuri
はURI
で、第二引数のparams
はHash
だ。
つまり、こういう呼び出しになるだろう。
Net::HTTP.post_form(URI.parse('http://example.com'), { "somekey[]" => "hoge", "somekey[]" => "fuga", "somekey[]" => "piyo", })
ここで問題が浮上する。Hash
は重複するキーを持つことができない。上記の呼び出しは、このように変換されてしまう。
Net::HTTP.post_form(URI.parse('http://example.com'), { "somekey[]" => "piyo", })
解決法
HTTPリクエストbodyにPostしたいデータを直接セットしてやることで回避した。
その際は、自分でPostするパラメータを文字列化し、URLエンコードする必要がある。具体的には、URI.encode_www_form
を使う。
3行目を参照。
url = URI.parse('http://www.example.com/todo.cgi') req = Net::HTTP::Post.new(url.path) req.body = URI.encode_www_form [ ["somekey[]", "hoge"], ["somekey[]", "fuga"], ["somekey[]", "piyo"] ] res = Net::HTTP.new(url.host, url.port).start {|http| http.request(req) } case res when Net::HTTPSuccess, Net::HTTPRedirection # OK else res.value end
キモは、URI.encode_www_form
でURLエンコードする際に、配列の配列を渡すことだ。Hash
じゃないからキーの重複も問題ない。
http://docs.ruby-lang.org/ja/2.1.0/class/URI.html#S_ENCODE_WWW_FORM
URI.encode_www_form
メソッドの返り値はこうなる。
"somekey%5B%5D=hoge&somekey%5B%5D=fuga&somekey%5B%5D=piyo"
tips
Net::HTTP.post_form
はHashを受け取ると書いたが、それはあくまでドキュメント上のはなしだ。
ソースを追えばわかるが、内部ではURI.encode_www_form
を呼んでいるだけである。
(post_form
の中でNet::HTTPHeader#set_form_data
が呼ばれ、その中でURI.encode_www_form
している。)
だから、post_form
メソッドに(Hash
ではなく)配列の配列を渡しても動く。
Net::HTTP.post_form(URI.parse('http://example.com'), [ ["somekey[]", "hoge"], ["somekey[]", "fuga"], ["somekey[]", "piyo"] ])
不安なら、set_debug_output($stderr)
を使ってリクエストを覗いてみればいい。
ただundocumentedなので、こういう風に使っていいのかアヤシイ。