JAX-RSのリファレンス実装であるJerseyには、RESTサービスを提供するサーバ側の実装(jersey-server)と、RESTサービスを利用するクライアント側の実装(jersey-client)とがあります。
RESTサービスではHTTPを介してサーバとクライアントとがやりとりしますから、当然jersey-clientはHTTPクライアントの機能を備えています。今回はJerseyの使い方の番外編として、RESTサービスを抜きにHTTPクライアントとしても、jersey-clientは使い勝手が良いという話をします。
ちなみに、JAX-RS 1.1にはクライアント側のAPIは定義されておらず、今年(2012年)のQ2にリリース予定のJAX-RS 2.0から定義されることになっています。(本稿執筆時点ではEarly Draft Reviewの段階)
何はともあれ、使ってみましょう。
Mavenを使用する場合は、以下のdependencyを追加します。
<dependency> <groupId>com.sun.jersey</groupId> <artifactId>jersey-client</artifactId> <version>1.11</version> </dependency>
あるいは、http://jersey.java.net/からjarをダウンロードしてクラスパスに含めます。
まずは簡単なところで、あるサイトのHTMLを取得したいとしましょう。その場合以下のようなコードになります。
Client c = Client.create(); WebResource r = c.resource("http://www.example.com/"); String html = r.get(String.class);
これだけです。説明も不要なくらいごく簡潔ですね。
基本的にはこの3ステップで進んでいきます。
先のコードのように、これはこれで手軽に書けて良いのですが、実際的にはクライアントにいくつかの設定をしたいことが多いでしょう。
タイムアウトとリダイレクトの追跡を設定するには、次のようにします。
Client c = Client.create(); c.setConnectTimeout(3*1000); c.setReadTimeout(5*1000); c.setFollowRedirects(Boolean.TRUE);
Basic認証を設定するには、次のようにします。
c.addFilter(new HTTPBasicAuthFilter("user", "passwd"));
個々のリクエストに関する変更はWebResourceを使って行います。以下のコードがどのような結果になるか見てみましょう。
WebResource r = c.resource("http://www.example.com/"); WebResource r2 = r .path("search") .queryParam("key", "value"); System.out.printf("r : %s%n", r); System.out.printf("r2: %s%n", r2);
r : http://www.example.com/ r2: http://www.example.com/search?key=value
このように、WebResourceの持つメソッドの多くはそのインスタンス自身を変更せず、変更を加えた新しいWebResourceインスタンス・あるいはWebResource.Builderインスタンスを生成して返します。
いくつかの変更・設定を行う例です。
WebResource.Builder b = c.resource("http://www.example.com/") .path("search") .queryParam("key", "value") .cookie(new Cookie("name", "value")) .accept(MediaType.TEXT_HTML_TYPE) .header("name","value");
HTTPリクエストを行ったら、レスポンスを受け取ります。get()メソッドに渡すクラスによって、レスポンスをどのクラスのオブジェクトに変換して取得するかを指定します。とは言え、さすがに任意のクラスに変換できるというわけではなく、JAX-RSで変換が定義されているクラスと、Jerseyで独自に変換が提供されているクラスに限られます。
以下のクラスが使用可能です。
またget()メソッドはHTTPのGETメソッドに対応しており、同様にPOST・PUT・DELETE・HEAD・OPTIONSメソッドそれぞれにも対応するメソッドが用意されています。
ここまでをまとめると、以下のようなコードになります。
Client c = Client.create(); c.setConnectTimeout(3*1000); c.setReadTimeout(5*1000); c.setFollowRedirects(Boolean.TRUE); c.addFilter(new HTTPBasicAuthFilter("user", "passwd")); String html = c.resource("http://www.example.com/") .path("search") .queryParam("key", "value") .cookie(new Cookie("name", "value")) .accept(MediaType.TEXT_HTML_TYPE) .header("name","value") .get(String.class);
jersey-clientの特徴として、フィルターによるカスタマイズがあります。プログラミングモデルとしてはServletFilterに近いイメージですね。この仕組みがコンパクトなAPIに柔軟性を与えていて、個人的に気に入っているポイントでもあります。
Clientクラス・WebResourceクラスともにFilterableインターフェイスを実装しており、各々にフィルタを適用できます。具体的にはClientFilterを拡張したクラスを作成し、handle()メソッドを実装します。
※先ほどBasic認証の設定で使ったHTTPBasicAuthFilterは、予め用意されているClientFilterの拡張クラスです。
Filterable#addFilter(new ClientFilter() { @Override public ClientResponse handle(ClientRequest cr) throws ClientHandlerException { doSome(cr); // リクエストに対して何かをする // 次のフィルタへ処理を渡す ClientResponse res = getNext().handle(cr); doOther(res); // レスポンスに対して何かをする return res; } });
例えばClientにユーザエージェントを設定する場合には、次のようにします。
Client c = Client.create(); c.addFilter(new ClientFilter() { @Override public ClientResponse handle(ClientRequest cr) throws ClientHandlerException { if (!cr.getHeaders().containsKey(HttpHeaders.USER_AGENT)) { cr.getHeaders().add( HttpHeaders.USER_AGENT, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:9.0.1)" + " Gecko/20100101 Firefox/9.0.1"); } return getNext().handle(cr); } });
共通的なHTTPヘッダを設定するというシーンは割とよくあると思うのですが、それ用の拡張クラスは用意されていません。
元はRESTサービスのクライアントとして開発されたjersey-clientではありますが、HTTPクライアントとして見ても実用に耐えるものです。そして最近のシステムでは、"Web上にある何かを取ってくる"という機能も当たり前になりました。それを速く・分かりやすく・かつ柔軟に実装したいとき、jersey-clientはとても有用だと思います。