: O. Yuanying

RailsにJSONデータをPostする

概要

クライアント側からポストされたJSON形式のデータを、サーバ側でparamsから利用する。

こんな感じ。

クライアント側

$ telnet localhost 3000
POST /projects HTTP/1.1
Accept: application/json
Content-Type: application/json
Content-Length: *

{ project : { name : "Hello!", desc : "Hello World Project!" } }

サーバ側

def create
  project = Project.new
  project.name = params['project']['name']
  project.desc = params['project']['desc']
  project.save
  
  ...
end

通常の場合

通常の場合、クライアント側からポストされるデータは、例えばブラウザのフォームからのデータだった場合、"application/x-www-form-urlencoded"形式である。

HTMLに以下のように書いてあれば、

<form method='post'>
<input type='text' name='project[name]' value='Hello!' />
<input type='text' name='project[desc]' value='Hello World Project!' />
...

送信時にブラウザによって"application/x-www-form-urlencoded"形式のデータに変換されてサーバに送られる。

ActiveResourceの場合

RESTfulなRailsサーバのクライアントとして、ActiveResourceが挙げられる。ActiveResourceクライアントはRailsとの通信にXMLを利用する。それはクライアントからサーバへデータを送信するときも同様である。

例えば上記と同じデータをActiveResouceクライアントは以下のようなフォーマットでサーバに送信する。

<project>
  <name>Hello!</name>
  <desc>Hello World Project!</desc>
</project>

サーバ側からは、データ形式が"application/x-www-form-urlencoded"であった場合と同じように、paramsメソッドを利用してこのデータを取得できる。

def create
  project = Project.new
  project.name = params['project']['name']
  project.desc = params['project']['desc']
  project.save
  
  ...
end

サーバ側の挙動

Raisサーバ側はクライアントからポストされたデータが"application/x-www-form-urlencoded"であるか"application/xml"であるかをクライアント側のContent-Typeを識別して、それぞれのデータをハッシュに変換する。コントローラからはこのハッシュ化されたデータをparamsメソッドを利用して取得することができる。

このクライアント側からのデータをハッシュに変換するパーサはActionController::Base.param_parsersに登録されている。

例えば"application/xml"に対応するparserは、以下の書式で取得することができる。

ActionController::Base.param_parsers[Mime::Type::lookup("application/xml")]

逆に言えばparam_parsersにparserが登録されているデータ形式ならば何でも、サーバ側でparamsメソッドからハッシュとして利用できると言うことである。

JSONデータのparserを登録する

ActionController::Base.param_parsersに登録されているparserは、Raw Dataを受け取り、ハッシュに変換されたデータを返すProcオブジェクトである。

従って、JSON形式、content-typeが"application/json"であるRaw Dataをハッシュに変換するparserを登録するには、config/environment.rbに以下のように設定してやればよい。

ActionController::Base.param_parsers[Mime::Type::lookup('application/json')] = Proc.new { |data|
  ActiveSupport::JSON.decode(data)
}

なお、ActiveSupport::JSON.decodeメソッドはRails 1.2.3をインストールしたときについてくるActiveSupportにはまだ無いAPIなので、最新のActiveSupportをレポジトリから取得しておく必要がある。

Jakarta Commons HttpClientを利用してはまったこと

上記の設定をすれば、JSONオブジェクトをRailsサーバにポストできるはずだが、HttpClientを利用して以下のコードでデータをポストしてみたところ、見事にはまった。

Project project = ...;
		
PostMethod post = new PostMethod("http://localhost:3000/projects.json");
StringRequestEntity entity = new StringRequestEntity(project.toJSON().toString(), "application/json", "utf-8");
post.setRequestEntity(entity);
		
this.getHttpClient().executeMethod(post);

原因は、StringRequestEntityを生成するときにcontent-typeとcharsetを指定するのだが、これをそのままPostすると、Content-Type="application/json; charset=utf-8"というヘッダが生成されてしまうからだった。

べつに良いじゃんと思わないでもないが、Railsに登録されているのは"application/json"であって、"application/json; charset=utf-8"で無かったため、異なるMime-Typeであると判断されてしまった模様。

結局、

StringRequestEntity entity = new StringRequestEntity(project.toJSON().toString(), "application/json", null);

とし、charsetは別ヘッダーとすることで解決した。