Help us understand the problem. What is going on with this article?

[Groovy]RatpackでシンプルなWEBアプリケーションを開発する -JSONとJSONPを扱う-

More than 5 years have passed since last update.

これはG* Advent Calendar 2015の5日目の記事です。

おさらい

さて、昨日の投稿で、Ratpackでの基本的なHTTPルーティングの方法がわかりました。
コレで簡単なAPIサーバを構築する準備ができました!
今回は、さらにクライアントとRatpack間でJSONでデータをやりとりする方法を見て行きましょう。

コード

今回のコードの全体は以下のようになります。

ratpack.groovy
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として取得するサンプルです。

json-client.groovy
@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コンソールなどで結果を確認してください)

jsonp-client.html
<!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

saba1024
ドイツでWeb系エンジニアとして働いている日本人です。 基本プログラマですがシスアドっぽいこともしています。 大体Groovy、Grails関連かサーバ関連の内容を書いています。 _(:3」∠)_ =3 ブーッ
https://doitu.info/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away