これはG* Advent Calendar 2015の5日目の記事です。
おさらい
さて、昨日の投稿で、Ratpackでの基本的なHTTPルーティングの方法がわかりました。
コレで簡単なAPIサーバを構築する準備ができました!
今回は、さらにクライアントとRatpack間でJSONでデータをやりとりする方法を見て行きましょう。
コード
今回のコードの全体は以下のようになります。
import static ratpack.groovy.Groovy.ratpack
import static ratpack.jackson.Jackson.json
import static ratpack.jackson.Jackson.jsonNode
import groovy.json.JsonSlurper
import groovy.json.JsonBuilder
ratpack {
handlers {
path("getjson"){
response.headers.set 'Content-Type', 'application/json'
// 注意!jQueryなどでJSONPで値が渡ってきた時は、そもそもそれってJSONではないのでここでは例外が発生する
//ratpack.parse.NoSuchParserException: Could not find parser able to produce object of type 'com.fasterxml.jackson.databind.JsonNode' from request of content type 'null' and parse options 'JsonParseOpts{objectMapper=null}'. The following parsers were tried:
render it.parse(jsonNode()).map{
// ... promiseなんとか。。。
def name = it.get("name").asText()
def title = it.get("title").asText()
json( ["postedName": name, "postedTitle":title] )
}
}
// JSONPの場合はGETで投げられる。(jQueryでPOSTとか指定していても無意味)
get("getjsonp"){
//http://tsujimotter.info/2013/01/03/jsonp/
String requestedCollbackMethodName = request.queryParams.callbackName
response.headers.set 'Content-Type', 'application/javascript'
JsonBuilder jsonResponse = new JsonBuilder()
jsonResponse(
'postedName' : request.queryParams.name,
'postedTitle' : request.queryParams.title
)
render """${requestedCollbackMethodName}(${jsonResponse})"""
}
}
}
JSONを受け取り、JSONを返す
まずJSON( path("getjson")
の方)です。
該当コードを抜き出すと以下の部分になります。
path("getjson"){
response.headers.set 'Content-Type', 'application/json'
// 注意!jQueryなどでJSONPで値が渡ってきた時は、そもそもそれってJSONではないのでここでは例外が発生する
//ratpack.parse.NoSuchParserException: Could not find parser able to produce object of type 'com.fasterxml.jackson.databind.JsonNode' from request of content type 'null' and parse options 'JsonParseOpts{objectMapper=null}'. The following parsers were tried:
render it.parse(jsonNode()).map{
// ... promiseなんとか。。。
def name = it.get("name").asText()
def title = it.get("title").asText()
json( ["postedName": name, "postedTitle": title] )
}
}
コメントをちらっと見ればなんとなく解ると思いますが、前回POSTされたデータを取得する方法のところでも出てきたPromiseがここにも。。。
PromiseはRatpackにおいてとても重要な概念のようです。
基本的にはまぁコードを見てください。。。という感じなのですが、この注目する点は、JSONを扱うのにJackson
というクラスを利用している点です。
Ratpackが提供してくれているので、単純にimport文を書くだけで利用できます。
Jacksonの機能を利用して、jsonNode()
をパースして、結果プロミスが返されるので、mapメソッドの中で実際にJSONでクライアントから投げられたデータを取得することが出来ます。
そして、クライアントにJSONデータを返す際にも、Jackson.json()
というメソッドにMapを渡してあげれば自動的にJSONフォーマットに変換してくれてとても簡単にJSONを生成できます。import static
しているので、Jackson.json()
と書かずにjson()
と書くだけで利用できています。
Groovyからテストしてみる
前回同様、curlコマンド
でテストも当然できるのですが、ついでなのでGroovyからAPIを叩く為の方法も同時に試してみます。
Groovyには、HTTPBuilderというイケイケな通信用ライブラリがあります。
コレを使えばGroovyからお手軽にPOSTやGETで通信できます。
以下のソースを見ていただければなんとなく解ると思いますが、JSONを送って、レスポンスもJSONとして取得するサンプルです。
@Grab('org.codehaus.groovy.modules.http-builder:http-builder:0.7.1')
import groovyx.net.http.HTTPBuilder
import static groovyx.net.http.ContentType.*
import static groovyx.net.http.Method.*
def http = new HTTPBuilder( 'http://localhost:5050/getjson' )
http.request(POST, JSON) {
uri.path = '/getjson'
body = [name: 'こうじ', title: 'hogehoge']
response.success = { resp, json ->
println "Success! ${resp.status}"
println "$json"
}
response.failure = { resp ->
println "Request failed with status ${resp.status}"
}
}
Groovyの必殺技Grab
を使っているのでただこのgroovyスクリプトを保存して、groovyコマンドで実行するだけです!
RatpackがちゃんとJSONを扱えて、Groovyからもこんなにお手軽にJSONが送信できることが解ると思います。
jQueryからJSONPでデータを送信する
さて、JSONを送信といえばAjaxでjQueryから送信するというのはもう非常によくあるパターンですね。
基本的に上記のGroovyバージョンでJSONが正しく扱えることは分かったので、ここではJSONPを試してみたいと思います。
該当部分を抜き出すと以下になります。
// JSONPの場合はGETで投げられる。(jQueryでPOSTとか指定していても無意味)
get("getjsonp"){
//http://tsujimotter.info/2013/01/03/jsonp/
String requestedCollbackMethodName = request.queryParams.callbackName
response.headers.set 'Content-Type', 'application/javascript'
JsonBuilder jsonResponse = new JsonBuilder()
jsonResponse(
'postedName' : request.queryParams.name,
'postedTitle' : request.queryParams.title
)
render """${requestedCollbackMethodName}(${jsonResponse})"""
}
大雑把に言うとJSONPは、JavaScriptが動いているドメインとは別のドメインに対して、擬似的にAjaxでJSONを送受信する方法です。
あくまで擬似的です。なので、実際の所クエリストリングとしてRatpackにJSONデータが送信されてきますので、Ratpack側では当然クエリストリングとして処理をして、JavaScriptから指定されたコールバック関数名で囲んで、JavaScriptコードとして返してあげます。
テスト用のHTMLを以下に用意しました。
デスクトップとか適当なディレクトリに保存して、ブラウザで開いてみてください。
Get JSON
とうボタンをクリックすると、結果をconsole.logで出力しています。(ブラウザのJavaScriptコンソールなどで結果を確認してください)
<!DOCTYPE html>
<html>
<head>
<meta name="robots" content="noindex,nofollow" />
<meta charset="UTF-8">
<title>This is Template</title>
<script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.11.1.min.js"></script>
<script type="text/javascript">
$(document).ready(function(){
// Initial Code
$('#button').click(function(){
var data = {
'name' : 'こうじ',
'title' : 'programmer',
'callbackName': 'callbackMethod'
}
$.ajax({
type: "get",
url: 'http://localhost:5050/getjsonp',
//data: JSON.stringify(data),
data: data, // JSONPはJSONにあらず!stringgifyすると、このJSONの値自体がサーバ側でキーになってしまうので、生で送る。
contentType: 'application/json',
dataType: 'jsonp',
jsonpCallback: data.callbackName,
success: function(json){
console.log(json);
},
error:function(a, b, c){
console.log(a);
console.log(b);
console.log(c);
}
});
});
});
</script>
</head>
<body>
<p>Hello World!</p>
<button id='button'>Get JSON</button>
</body>
</html>
ちゃんと動くことが確認できると思います。
「送信されてくるJSONPが入れ子の場合どうなんの?」と思われた方は鋭い。
例えばJavaScriptからRatpackに送信されるJSONが以下のような場合、
var data = {
'name' : 'こうじ',
'title' : 'programmer',
'callbackName': 'callbackMethod',
'records': {
'a':123,
'b':456
}
}
Ratpack側では以下のように取得できます。
request.queryParams.get("records[b]")
添字自体も含めてパラメタ名になるんですね。
まとめ
いかがだったでしょうか。
これでRatpackを使って、JSONでデータを送受信するような簡単なAPIサーバが構築できるようになりました。
Ratpackの特徴であるノンブロッキングな部分や、プロミスなど、まだまだ興味はつきませんが、私の理解は今の所このあたり止まりですので、ここで一旦終了となります。
それでは、2016年も素敵なG*ライフを送りましょう!
参考
Ratpack
Ratpackについて(前編)
Ratpackについて(後編)
Ratpackについて(延長戦)
HTTPBuilder
HTTPBUilder Wiki