おつかれさまです。藤澤です。
みなさん個人的なアプリを作っていて、ちょっとした WEB サービスを用意したくなったことはないでしょうか。ですがクライアントサイドの開発経験しかないとサーバ立てたりとかハードル高そうに感じますよね。そんなとき JAX-RS を使えば慣れ親しんだ Java の知識で簡単に WEB サービスを作成できます。また、プラットフォームに Google App Engine(以下 GAE)を利用すれば Java も使えますし(なにより無料で使えますし)サーバを立てたりといったことを考える必要もありません。今回は、そんな GAE と JAX-RS を使った WEB サービスの作り方をご紹介したいと思います。
プロジェクトの作成
eclipse で GAE 用のプロジェクトを作成するにはプラグインが必要です。
Help → Install New Software を選択し、Work with に以下の URL を入力します。
https://dl.google.com/eclipse/plugin/4.4
4.4 の部分は eclipse のバージョン番号になります。
いくつか候補が出てきますが Google Plugin for Eclipse と SDKs だけインストールすれば OK です。
指示にしたがってインストール、および eclipse を再起動するとツールバーに Google のアイコンが追加され、Web Application Project を作成できるようになります。
Jersey の入手
JAX-RS というのは Java で RESTful な WEB サービスを実装するための API 仕様だそうです。JAX-RS を実装するプロダクトとしてはいろいろあるようですが、ここでは Jersey を利用します。
Jersey のサイトより最新の bundle をダウンロードしたら、api フォルダ内の jar をプロジェクトに追加、api, ext, lib の各フォルダ内の jar ファイルをすべて war/WEB-INF/lib にコピーします。
web.xml の設定
Jersey を利用するために web.xml の servlet 要素と servlet-mapping 要素を以下のように書き換えます。
<servlet> <servlet-name>JaxSample</servlet-name> <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class> <init-param> <param-name>jersey.config.server.provider.classnames</param-name> <param-value>my.sample.JaxSampleService</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>JaxSample</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping>
JaxSample の部分は任意、my.sample.JaxSampleService の部分は後で作成するサービスクラスの名前になります。
サービスクラスの作成
実際に WEB サービスの本体となるクラスを実装します。
まずは定番の "Hello World" を返すサービスを実装してみたいと思います。
package my.sample; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; @Path("sample_service") // URL public class JaxSampleService { @GET // GET メソッド @Produces(MediaType.TEXT_PLAIN) // Content-Type: text/plain @Path("hello") // URL public String sayHello() { return "Hello World"; } }
これだけです。アノテーションがついている以外は POJO なクラスと変わりません。
ではこのサービスにアクセスしてみましょう。
実行するとコンソール上に localhost:8888 でサーバが起動されたことが表示されますので こちらにアクセスしてみます。@Path アノテーションで指定した文字列を連結したものが URL になります。
$ curl -X GET http://localhost:8888/sample_service/hello/ Hello World
Hello World が返ってきました。
上記では GET メソッドを指定しましたが、試しに POST メソッドでアクセスしてみます。
$ curl -X POST http://localhost:8888/sample_service/hello/ <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/> <title>Error 405 METHOD_NOT_ALLOWED</title> </head> <body><h2>HTTP ERROR 405</h2> <p>Problem accessing /sample_service/hello/. Reason: <pre> METHOD_NOT_ALLOWED</pre></p><hr /><i><small>Powered by Jetty://</small></i><br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> </body> </html>
ちゃんとエラーになりました。
パラメータを指定することもできます。
package my.sample; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; @Path("sample_service") // URL public class JaxSampleService { @GET // GET メソッド @Produces(MediaType.TEXT_PLAIN) // Content-Type: text/plain @Path("hello") // URL public String sayHello(@QueryParam("who") String s) { return "Hello " + s; } }
変更点としては sayHello メソッドに @QueryParam アノテーションつきで引数を追加しただけです。
ではアクセスしてみます。
$ curl -X GET http://localhost:8888/sample_service/hello/?who=foo Hello foo
クエリ文字列で指定した文字列がちゃんと表示されました。
POST のパラメータで渡すこともできます。
package my.sample; import javax.ws.rs.FormParam; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; @Path("sample_service") // URL public class JaxSampleService { @POST // POST メソッド @Produces(MediaType.TEXT_PLAIN) // Content-Type: text/plain @Path("hello") // URL public String sayHello(@FormParam("who") String s) { return "Hello " + s; } }
$ curl -X POST -d "who=bar" http://localhost:8888/sample_service/hello/ Hello bar
いいですね!
Content-Type を XML や JSON にして任意のオブジェクトを返すこともできます。
その場合、データクラスに XmlRootElement 属性を指定します。
package my.sample; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement public class Data { public String s; public int i; }
package my.sample; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; @Path("sample_service") // URL public class JaxSampleService { @GET // GET メソッド @Produces(MediaType.APPLICATION_XML) // Content-Type: application/xml @Path("hello") // URL public Data sayHello() { Data d = new Data(); d.s = "hello"; d.i = 5; return d; } }
$ curl -X GET http://localhost:8888/sample_service/hello/ <?xml version="1.0" encoding="UTF-8" standalone="yes"?><data><s>hello</s><i>5</i></data>
とくに XML に変換するコードを書かなくても Data クラスを XML として返却してくれました。
レスポンスを JSON にする場合は変換用のライブラリが別途必要なようです。ライブラリはいろいろあるようですが、ここでは Genson を使用してみます。サイトから jar をダウンロードして war/WEB-INF/lib にコピーします。
// ……(略)…… @GET // GET メソッド @Produces(MediaType.APPLICATION_JSON) // Content-Type: application/json @Path("hello") // URL public Data sayHello() { // ……(略)……
$ curl -X GET http://localhost:8888/sample_service/hello/ {"i":5,"s":"hello"}
いいですね!
また、MediaType を複数指定しておいて、実際のレスポンスの形式をクライアントに選択させることも可能です。
……(略)…… @GET // GET メソッド @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) @Path("hello") // URL public Data sayHello() { ……(略)……
$ curl -X GET -H "Accept: application/xml" http://localhost:8888/sample_service/hello/ <?xml version="1.0" encoding="UTF-8" standalone="yes"?><data><s>hello</s><i>5</i></data> $ curl -X GET -H "Accept: application/json" http://localhost:8888/sample_service/hello/ {"i":5,"s":"hello"}
便利ですね!
XmlElement 属性で要素名を指定することもできますし、クラスを入れ子にすることも可能です。
package my.sample; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement(name="parent") public class Data { public Child[] children; }
package my.sample; import javax.xml.bind.annotation.XmlElement; public class Child { @XmlElement(name="text") public String s; @XmlElement(name="number") public int i; }
package my.sample; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; @Path("sample_service") // URL public class JaxSampleService { @GET // GET メソッド @Produces(MediaType.APPLICATION_JSON) // Content-Type: application/json @Path("hello") // URL public Data sayHello() { Child c1 = new Child(); c1.s = "a"; c1.i = 1; Child c2 = new Child(); c2.s = "b"; c2.i = 2; Child[] c = new Child[] { c1, c2 }; Data d = new Data(); d.children = c; return d; } }
$ curl -X GET http://localhost:8888/sample_service/hello/ {"children":[{"number":1,"text":"a"},{"number":2,"text":"b"}]}
これで より複雑なデータも簡単に扱うことができますね。
GAE へのデプロイ
(GAE のアカウントまで作成できているものとします)
まずは GAE のコンソールでプロジェクトを作成します。
次に appengine-web.xml の applicatoin タグに先ほど作成したプロジェクトのプロジェクト ID を指定します。
<application>silent-rune-847</application> <version>1</version>
これだけ設定したら、あとはツールバーの g アイコンより Deploy to App Engine を選択するだけです。
それでは実際にデプロイ先にアクセスしてみます。
$ curl -X GET http://<GAEのURL>/sample_service/hello/ {"children":[{"number":1,"text":"a"},{"number":2,"text":"b"}]}
簡単ですね!
今回は以上です。