目次


Jersey と Apache Tomcat を使って RESTful な Web サービスを作成する

Comments

RESTful なWeb サービスの概要

REST (Representational State Transfer) は 2000年、HTTP 仕様バージョン 1.0 と 1.1 の中心的作成者の 1 人である Roy Fielding の博士論文で初めて紹介され、定義されました。

REST のなかで最も重要な概念は、グローバル ID によって識別されるリソースです (グローバル ID には通常、URI が使用されます)。クライアント・アプリケーションは REST のリソースまたはリソースのコレクションを、HTTP メソッド (GET/ POST/ PUT/ DELETE) を使って操作します。RESTful な Web サービスとは、HTTP と REST の原則を用いて実装された Web サービスのことです。一般に、RESTful な Web サービスでは以下の側面を定義します。

  • Web サービスのベース/ルート URI (http://host/<appcontext>/resources など)
  • サポートされるレスポンス・データの MIME タイプ (JSON、XML、Atom など)
  • サービスでサポートする操作のセット (POST、GET、PUT、または DELETE など)

表 1 に、RESTful な Web サービスで標準的に使用されるリソース URI と HTTP メソッドを記載します (RESTful な Web サービスの概要および設計に関する考慮事項についての詳細は、「参考文献」を参照してください)。

表 1. RESTful な Web サービスの例
メソッド / リソース リソースのコレクション。URI の例:
http://host/<appctx>/resources
メンバー・リソース。URI の例:
http://host/<appctx>/resources/1234
GET リソースのコレクションに含まれるすべてのメンバーをリストアップ 1234 として識別される 1 つのリソースの表現を取得
PUTコレクションを別のコレクションで更新 (置換) 1234 として識別されるメンバー・リソースを更新
POST コレクション内にメンバー・リソースを作成 (メンバー・リソースの ID は自動的に割り当てられます) メンバー・リソースのサブ・リソースを作成
DELETE リソースのコレクション全体を削除 1234 として識別されるメンバー・リソースを削除

JSR 311 (JAX-RS) と Jersey

JAX-RS (Java API for RESTful Web Services) という略称で呼ばれている JSR 311 は 2007年に提案され、2008年10月にはバージョン 1.0 がリリースされました。現在、JSR 311 のバージョン 1.1 がドラフト版の状態にあります。この JSR が目的としているのは、REST スタイルの Web サービスの開発を単純化する一連の API を提供することです。

JAX-RS 仕様が登場する前は、RESTful な Web サービスの実装を支援するフレームワークとして Restlet や RestEasy などのフレームワークがあったものの、これらのフレームワークは直観的な方法で使えるものではありませんでした。Jersey は JAX-RS のリファレンス実装であり、その主なコンポーネントには以下の 3 つがあります。

  • コア・サーバー: JSR 311 で標準化されたアノテーションと API を備えたサーバーで、極めて直観的な方法で RESTful な Web サービスを開発できるようになっています。
  • コア・クライアント: Jersey クライアント API により、REST サービスと容易に通信することができます。
  • 統合: Jersey には、Spring、Guice、Apache Abdera などと簡単に統合できるライブラリーも用意されています。

記事の以降のセクションでは上記のコンポーネントのすべてを紹介しますが、最も重点を置くのはコア・サーバーです。

RESTful な Web サービスの作成

まず始めに取り掛かるのは、Tomcat に統合できる「Hello World」アプリケーションです。このアプリケーションをとおして環境のセットアップ手順を説明し、Jersey と JAX-RS の基本を説明します。

基本を説明した後は、さらに複雑なアプリケーションを紹介するなかで、複数の MIME タイプの表現のサポートや JAXB サポートなど、JAX-RS の基本ならびに機能の詳細を探っていきます。重要な概念については、サンプルのコード・スニペットを引用して説明します。

Hello World: 最初の Jersey Web プロジェクト

開発環境をセットアップするには、以下のソフトウェアが必要です (ダウンロードについては、「参考文献」を参照)。

  • IDE: Eclipse IDE for JEE (バージョン 3.4 以降) または IBM Rational Application Developer 7.5
  • Java SE5 以上
  • Web コンテナー: Apache Tomcat 6.0 (Jetty などでも有効です)
  • Jersey ライブラリー: Jersey 1.0.3 アーカイブ (必要なすべてのライブラリーが組み込まれています)

Jersey 用に環境をセットアップする

最初に、Eclipse で Tomcat 6.0 のサーバー・ランタイムを作成します。これが、作成する RESTful な Web アプリケーションの Web コンテナーになります。次に「Jersey」という名前の動的な Web アプリケーションを作成し、ターゲット・ランタイムに Tomcat 6.0 を指定します。

最後に Jersey アーカイブに含まれる以下のライブラリーを、WEB-INF の下にある lib ディレクトリーにコピーします。

  • コア・サーバー: jersey-core.jar、jersey-server.jar、jsr311-api.jar、asm.jar
  • コア・クライアント: (テスト用) jersey-client.jar
  • JAXB サポート: (高度な例で使用) jaxb-impl.jar、jaxb-api.jar、activation.jar、stax-api.jar、wstx-asl.jar
  • JSON サポート: (高度な例で使用) jersey-json.jar

REST サービスを開発する

環境はセットアップできたので、これから最初の REST サービスを開発します。これは、クライアントに「Hello」と表示するだけの単純なサービスです。

まずは、すべての REST リクエストを Jersey コンテナーに送信するために、アプリケーションの web.xml ファイルにサーブレット・ディスパッチャーを定義する必要があります (リスト 1 を参照)。このファイルでは Jersey サーブレットを宣言するだけでなく、初期化パラメーターも定義して、リソースが含まれる Java パッケージを指定しています。

リスト 1. web.xml ファイルの中で Jersey サーブレット・ディスパッチャーを定義する
<servlet>
  <servlet-name>Jersey REST Service</servlet-name>
<servlet-class>
  com.sun.jersey.spi.container.servlet.ServletContainer
</servlet-class>
  <init-param>
    <param-name>com.sun.jersey.config.property.packages</param-name>
    <param-value>sample.hello.resources</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
  <servlet-name>Jersey REST Service</servlet-name>
  <url-pattern>/rest/*</url-pattern>
</servlet-mapping>

今度は、HTTP GET を受け入れて「Hello Jersey」というありきたりなメッセージで応答する HelloResource という名前のリソースを作成します。

リスト 2. sample.hello.resources パッケージに含まれる HelloResource
@Path("/hello")
public class HelloResource {
	@GET
	@Produces(MediaType.TEXT_PLAIN)
	public String sayHello() {
		return "Hello Jersey";
	}
}

上記のコードには注目すべき点がいくつかあります。

  • リソースのクラス: このリソースのクラスは POJO (Plain Old Java Object) なので、あらゆるインターフェースを制限なく実装することができます。これにより、再利用可能性や単純さなどの多くのメリットが追加でもたらされます。
  • アノテーション: javax.ws.rs.* に定義されたこれらのアノテーションは、JAX-RS (JSR 311) 仕様に含まれています。
  • @Path: リソースのベース URI を定義します。このリソース識別子はコンテキスト・ルートとホスト名で構成され、例えば http://localhost:8080/Jersey/rest/hello のようになります。
  • @GET: これに続くメソッドが HTTP GET メソッドに応答することを意味します。
  • @Produces: レスポンスのコンテンツの MIME タイプをプレーン・テキストとして定義します。

Hello アプリケーションをテストする

このアプリケーションをテストするには、ブラウザーを開き、URI として http://<host>:<port>/<appctx>/rest/hello と入力します。すると、「Hello Jersey」というレスポンスが表示されるはずです。アノテーションがリクエスト、レスポンス、メソッドを処理してくれるため、このように至って単純なアプリケーションとなります。

この後のセクションでは JAX-RS 仕様に不可欠な部分について、Contacts サンプル・アプリケーションのコード・スニペットを引用して説明します。この高度なサンプル・アプリケーションのコードはすべて、ソース・コード・パッケージに含まれています (「ダウンロード」を参照)。

リソース

リソースは、RESTful な Web サービスを構成する重要な部分です。リソースを操作するには、GET、POST、PUT、DELETE などの HTTP メソッドを使用します。アプリケーションに含まれるあらゆるもの (従業員、連絡先、組織などすべて) がリソースになり得ます。JAX-RX では、リソースは POJO によって実装され、@Path アノテーションによってその識別子が構成されます。リソースは、サブ・リソースを持つこともできます。この場合、親リソースがリソース・コレクションとなり、サブ・リソースがメンバー・リソースとなります。

サンプルの Contacts アプリケーションでは、個々の連絡先と連絡先のコレクションを操作します。ContactsResource は /contacts という URI を持つリソース・コレクションです。一方で ContactResource はメンバー・リソースであり、その URI は /contacts/{contactId} となっています。ベースにある JavaBean は、ID、名前、住所をメンバー・フィールドに持つ単純な Contact クラスです。詳細についてはリスト 3 とリスト 4 を見てください。記事の終わりに用意されている完全なソース・コード・パッケージをダウンロードすることもできます (「ダウンロード」を参照)。

リスト 3. ContactsResource
@Path("/contacts")
public class ContactsResource {
	@Context
	UriInfo uriInfo;
	@Context
	Request request;

	@GET
	@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
	public List<Contact> getContacts() {
		List<Contact> contacts = >new ArrayList<Contact>();
		contacts.addAll( ContactStore.getStore().values() );
		return contacts;
	}

@Path("{contact}")
	public ContactResource getContact(
			@PathParam("contact") String contact) {
		return new ContactResource(uriInfo, request, contact);
	}
}

上記のコードには、注意すべき興味深い点がいくつかあります。

  • @Context: このアノテーションは、コンテキストに依存するオブジェクト (Request、Response、UriInfo、ServletContext など) を注入するために使用します。
  • @Path("{contact}"): @Path アノテーションにルート・パス “/contacts” を結合して、サブ・リソースの URI を構成するアノテーションです。
  • @PathParam("contact"): このアノテーションは、メソッド・パラメーターのパス (この例では連絡先 ID) にパラメーターを注入します。他に使用できるアノテーションには @FormParam@QueryParam などがあります。
  • @Produces: レスポンスには、複数の MIME タイプがサポートされます。この例とその後の例でのデフォルト MIME タイプは、application/xml です。

この点についてもお気付きかもしれませんが、GET メソッドは前の Hello World の例では String (プレーン・テキスト) を返している一方、上記ではカスタム Java オブジェクトを返しています。JAX-RS 仕様では、実装は複数の表現形式 (InputStream、byte[]、JAXB 要素、JAXB 要素のコレクションなど) をサポートすると同時に、これらの表現による実装を XML、JSON、またはプレーン・テキストのレスポンスとしてシリアライズできることを要件としています。表現手法については、記事の後半で特に JAXB 要素の表現に重点を置いて詳しく説明します。

リスト 4. ContactResource
public class ContactResource {
	@Context
	UriInfo uriInfo;
	@Context
	Request request;
	String contact;
	
	public ContactResource(UriInfo uriInfo, Request request, 
			String contact) {
		this.uriInfo = uriInfo;
		this.request = request;
		this.contact = contact;
	}
	
	@GET
	@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
	public Contact getContact() {
		Contact cont = ContactStore.getStore().get(contact);
		if(cont==null)
			throw new NotFoundException("No such Contact.");
		return cont;
	}
}

ContactResource のコードは簡単に理解できるので、以下の項目に目を向けてください。

  • Contact 表現形式: Contact は、XML または JSON として表現することを可能にする @XmlRootElement というアノテーションが付けられた単純な JavaBean です。
  • ContactStore: ハッシュ・マップに基づくメモリー内データ・ストアです。その実装は、この記事では重要ではありません。

メソッド

HTTP メソッドは、リソースに対する CRUD (Create (作成)、Read (読み出し)、Update (更新) および Delete (削除)) アクションに対応します。PUT メソッドを作成アクションや更新アクションにするなどの多少の変更を加えることはできますが、基本的なパターンは以下に記載するとおりです。

  • HTTP GET: 個々のリソースまたはリソースのコレクションを取得/一覧表示/検索します。
  • HTTP POST: 1 つまたは複数のリソースを新規に作成します。
  • HTTP PUT: 既存のリソースまたはリソースのコレクションを更新します。
  • HTTP DELETE: リソースまたはリソースのコレクションを削除します。

GET メソッドについてはすでに説明したので、ここからは POST をはじめとするその他のメソッドについて説明することにします。ただし GET 以外のメソッドの説明でも、引き続き Contact の例を使用します。

POST

通常、新しい連絡先を作成するには、フォームに入力するという方法を使用します。つまり、HTML フォームが POST メソッドでサーバーに送信されることによって、サーバーが連絡先を作成し、その新しく作成した連絡先を永続化するということです。リスト 5 に、そのためのサーバー・サイドのロジックを記載します。

リスト 5. フォームの実行依頼 (POST) を受け入れ、新しい連絡先を作成する
@POST
@Produces(MediaType.TEXT_HTML)
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public void newContact(
		@FormParam("id") String id,
		@FormParam("name") String name,
		@Context HttpServletResponse servletResponse
) throws IOException {
	Contact c = new Contact(id,name,new ArrayList<Address>());
	ContactStore.getStore().put(id, c);
		
	URI uri = uriInfo.getAbsolutePathBuilder().path(id).build();
	Response.created(uri).build();
		
	servletResponse.sendRedirect("../pages/new_contact.html");
}

この例を機能させる以下の要素に注目してください。

  • @Consumes: このメソッドが HTML フォームを使用することを宣言します。
  • @FormParam: HTML の name 属性によって識別されたフォームへの入力をこのメソッドに注入します。
  • @Response.created(uri).build(): 新規に作成された連絡先の新しい URI を /contacts/{id} として構成し、レスポンス・コードを 201/createdに設定します。新しい連絡先には、http://localhost:8080/Jersey/rest/contacts/<id> によってアクセスすることができます。

PUT

ここでは PUT メソッドを、既存のリソースを更新するために使用していますが、リスト 6 のコード・スニペットのように、このメソッドを更新操作として実装したり、リソースを作成するという方法で実装したりすることもできます。

リスト 6. PUT リクエストを受け入れて、連絡先を作成または更新する
@PUT
@Consumes(MediaType.APPLICATION_XML)
public Response putContact(JAXBElement<Contact> jaxbContact) {
	Contact c = jaxbContact.getValue();
	return putAndGetResponse(c);
}

private Response putAndGetResponse(Contact c) {
	Response res;
	if(ContactStore.getStore().containsKey(c.getId())) {
		res = Response.noContent().build();
	} else {
		res = Response.created(uriInfo.getAbsolutePath()).build();
	}
	ContactStore.getStore().put(c.getId(), c);
	return res;
}

この例ではさまざまな概念を取り上げることによって、以下の概念を強調しています。

  • XML の使用: putContact() メソッドは、リクエストのコンテンツ・タイプとして APPLICATION/XML を受け入れる一方、この入力 XML がバインドされるのは JAXB を使用する Contact オブジェクトです。クライアント・コードは次のセクションに記載します。
  • 異なるステータス・コードを持つ空のレスポンス: PUT リクエストのレスポンスにはコンテンツがありませんが、状況に応じて異なるステータス・コードが 1 つ設定されます。該当する連絡先がデータ・ストアに存在する場合は、この連絡先を更新し、204/no content を返します。データ・ストアに連絡先がない場合には、その連絡先を作成して 201/created を返します。

DELETE

DELETE メソッドを実装するのは至って簡単です。リスト 7 に一例を記載します。

リスト 7. ID で識別された連絡先を削除する
@DELETE
public void deleteContact() {
	Contact c = ContactStore.getStore().remove(contact);
	if(c==null)
		throw new NotFoundException("No such Contact.");
}

表現形式

これまでのセクションで、いくつかの表現形式を例示しましたが、ここで、これらの表現形式をひと通り簡単に説明した後、JAXB 表現について詳しく見ていきたいと思います。以下に記載する表現形式の他、byte[]、InputStream、File などもサポートされています。

  • String: プレーン・テキストです。
  • Response: 汎用 HTTP レスポンス。状況に応じて異なるレスポンス・コードを設定したカスタム・コンテンツを含めることができます。
  • Void: ステータス・コード 204/no content の空のレスポンスです。
  • リソースのクラス: プロセスをこのリソースのクラスに委譲します。
  • POJO: @XmlRootElement でアノテーションが付けられた JavaBean。このアノテーションが付けられたオブジェクトは JAXB Bean にされ、さらに XML にバインドできるようになります。
  • POJO のコレクション: JAXB Bean のコレクションです。

JAX-RS では、JAXB (Java API for XML Binding) を使用して、JavaBean を XML または JSON にバインドする (またはその逆を実行する) ことができます。その場合、JavaBean には @XmlRootElement でアノテーションを付けなければなりません。リスト 8 は、Contact Bean を用いた例です。明示的な @XmlElement アノテーションが付けられていないフィールドは、そのままの名前の XML 要素になります。リスト 9 に記載しているのは、同じ Contact Bean をシリアライズした XML 表現と JSON 表現です。連絡先のコレクションの場合も表現はほとんど同じで、デフォルトで <Contacts> がラッパー要素として使用されます。

リスト 8. Contact Bean
@XmlRootElement
public class Contact {
	private String id;
	private String name;
	private List<Address> addresses;
	
	public Contact() {}
	
	public Contact(String id, String name, List<Address> addresses) {
		this.id = id;
		this.name = name;
		this.addresses = addresses;
	}

	@XmlElement(name="address")
	public List<Address> getAddresses() {
		return addresses;
	}

	public void setAddresses(List<Address> addresses) {
		this.addresses = addresses;
	}
	// Omit other getters and setters
}
リスト 9. 同じ Contact に対する表現
XML representation:
<contact>
  <address>
    <city>Shanghai</city>
    <street>Long Hua Street</street>
  </address>
  <address>
    <city>Shanghai</city>
    <street>Dong Quan Street</street>
  </address>
  <id>huangyim</id>
    <name>Huang Yi Ming</name>
</contact>


JSON representation:
{"contact":[{"address":[{"city":"Shanghai","street":"Long
            Hua Street"},{"city":"Shanghai","street":"Dong Quan
            Street"}],"id":"huangyim","name":"Huang Yi Ming"}]}

JAXB を使用したさらに高度なトピックについては、「参考文献」で紹介している JAXB リファレンス実装プロジェクトのホーム・ページを参照してください。

REST サービスと通信するクライアント

今までの例では 連絡先の CRUD をサポートする RESTful な Web サービスを開発してきましたが、ここからは、curl および Jersey クライアント API を使用してこの REST サービスと通信する方法を説明します。また、REST サービスとの通信をとおして、サーバー・サイドのコードをテストし、クライアント・サイドの技術に関する詳しい情報を提供します。

curl を使用して REST サービスと通信する

curl は、HTTP や HTTPS などのプロトコルを使ってサーバーにリクエストを送信できるコマンドライン・ツールとしてよく使われています。あらゆる HTTP メソッドでコンテンツを送信することができる curl は、RESTful なWeb サービスと通信するにはうってつけのツールです。Linux® と Mac には curl がすでに付属しています。Windows® プラットフォームの場合は、ユーティリティーをインストールしてください (「参考文献」を参照)。

まず以下の基本的な curl コマンドを実行して、すべての連絡先を取得してみましょう。サーバー・サイドのコードについては、リスト 3 を参照することができます。

curl http://localhost:8080/Jersey/rest/contacts

レスポンスは XML 形式で、そこにすべての連絡先が含まれているはずです。

getContacts() メソッドは application/json MIME タイプのレスポンスも生成します。以下のコマンドを使用すれば、このタイプのコンテンツをリクエストすることもできます。

curl –HAccept:application/json http://localhost:8080/Jersey/rest/contacts

この場合のレスポンスは、すべての連絡先が含まれる JSON ストリングとなります。

今度は PUT メソッドで新規の連絡先を作成します。リスト 6putContact() メソッドは XML を受け入れ、JAXB によって XML を Contact オブジェクトにバインドすることを思い出してください。

curl -X PUT -HContent-type:application/xml --data "<contact><id>foo</id>
                <name>bar</name></contact>" http://localhost:8080/Jersey/rest/contacts/foo

これで、「foo」で識別された新規の連絡先が、連絡先ストアに追加されます。連絡先のコレクション、または個別に連絡先を確認するには、URI にそれぞれ /contacts、/contacts/foo を指定します。

Jersey クライアントを使用して REST サービスと通信する

Jersey には、サーバーとの通信、そして RESTful なサービスのユニット・テストを行うためのクライアント・ライブラリーも用意されています。このライブラリーは、あらゆる HTTP/HTTPS ベースの Web サービスと連携することができる汎用実装です。

クライアントのコア・クラスは WebResource クラスです。このクラスを使用してルート URI をベースにしたリクエスト URL を構成してから、リクエストを送信してレスポンスを取得します。リスト 10 に、WebResource インスタンスの作成方法を示します。注意する点として、WebResource は重たいオブジェクトなので一度しか作成しないようにします。

リスト 10. WebResource インスタンスを作成する
Client c = Client.create();
WebResource r=c.resource("http://localhost:8080/Jersey/rest/contacts");

Jersey クライアントの最初の例では、GET リクエストを送信してすべての連絡先を取得し、レスポンスのステータス・コードとレスポンスのコンテンツを出力します。その方法はリスト 11 のとおりです。

リスト 11. GET を使用してすべての連絡先を取得し、レスポンスを出力する
ClientResponse response = r.get(ClientResponse.class);
System.out.println( response.getStatus() );
System.out.println( response.getHeaders().get("Content-Type") );
String entity = response.getEntity(String.class);
System.out.println(entity);

リスト 12 に示す 2 番目の例では、「foo」で識別される連絡先を新しく作成します。

リスト 12. 新しい連絡先を作成する
Address[] addrs = {
	new Address("Shanghai", "Ke Yuan Street")
};
Contact c = new Contact("foo", "Foo Bar", Arrays.asList(addrs));

ClientResponse response = r
	.path(c.getId())
	.accept(MediaType.APPLICATION_XML)
	.put(ClientResponse.class, c);
System.out.println(response.getStatus());

WebResource インスタンスの API に注目してください。URI の構成、リクエスト・ヘッダーの設定、そしてリクエストの呼び出しがすべて 1 行のコードで行われています。コンテンツ (Contact オブジェクト) は、自動的に XML にバインドされます。

リスト 13 に記載する最後の例では、「foo」で識別される連絡先 (前の例で作成した連絡先) を取得してから、この連絡先を削除します。

リスト 13. 「foo」連絡先を取得して削除する
GenericType<JAXBElement<Contact>> generic = new GenericType<JAXBElement<Contact>>() {};
JAXBElement<Contact> jaxbContact = r
	.path("foo")
	.type(MediaType.APPLICATION_XML)
	.get(generic);
Contact contact = jaxbContact.getValue();
System.out.println(contact.getId() + ": " + contact.getName());

ClientResponse response = r.path("foo").delete(ClientResponse.class);
System.out.println(response.getStatus());

ここで注意する点として、取得したいレスポンスが JAXB Bean の場合には、J2SE (Java 2 Platform, Standard Edition) で導入された Generics 型を使用する必要があります。

Jersey クライアントを使用して以上の例を試してみてください。ソース・パッケージには、これ以外のサンプル・コードも含まれています (「ダウンロード」を参照)。また、詳細については Jersey Web サイトも参照してください (「参考文献」を参照)。

まとめ

Jersey 統合ライブラリーを使用すれば、他のフレームワークやユーティリティー・ライブラリーにも Jersey を統合することができます。現在、Jersey は Spring と Guice の両フレームワークと統合することができます。また、apache-abdera と統合することで Atom 表現をサポートすることができます。API と入門ガイドについては、Jersey プロジェクトのホーム・ページを参照してください。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Web development
ArticleID=438976
ArticleTitle=Jersey と Apache Tomcat を使って RESTful な Web サービスを作成する
publish-date=09242009