Java開発者のためのREST、パート2:疲れた人のためのレストレット

オープンソースのRestletAPIは、JavaでのRESTfulAPIの構築と使用に伴う作業負荷を軽減します。REST for Java開発者シリーズのこの2番目の記事では、Brian SlettenがRestletを紹介し、現在使用しているサーブレットコンテナにインターフェイスをデプロイする際のサンプルアプリケーションについて説明し、将来のシステムの準備も行います。ブライアンはまた、JSR 311:JAX-RS、RESTfulAPIをJavaEEスタックと統合するためのSunの取り組みを簡単に紹介します。

Java開発者は長い間RESTアーキテクチャスタイルに興味を持っていましたが、慣れ親しんだオブジェクトの世界とRESTfulなリソースの世界との間の距離を移動したことはまだほとんどありません。 RESTfulサービスは他の言語で生成または利用できるという事実は気に入っているかもしれませんが、バイトストリームとの間でデータを変換する必要はありません。 Apache HTTPクライアントなどのツールを使用するときに、HTTPについて考える必要はありません。wsdl2javaコマンドによって作成されたオブジェクトを待ち望んでいます。これにより、他のメソッド呼び出しと同じくらい簡単に引数をSOAPサービスに渡すことができ、敷物の下でリモートサービスを呼び出す詳細を一掃できます。そして、サーブレットモデルが、生成されているリソースからわずかに切り離されすぎていることがわかりました。私たちができている間、それを言うだけで十分です RESTfulサービスをゼロから構築することは、楽しい経験ではありませんでした。

Java開発者向けのREST

シリーズを読む:

  • パート1:それは情報についてです
  • パート2:疲れた人のためのレストレット
  • パート3:NetKernel

政治的な問題が技術的なハードルを悪化させることがあります。多くの管理者は、SOAPベースのWebサービスが、Java EEでサービス指向アーキテクチャー(SOA)を構築するための規定された方法であると感じています。これは、この記事で学習するJSR 311、JAX-RS:RESTfulWebサービス用のJavaAPIなどの重要なアクティビティの出現によって変化しています。他に何もないとしても、この取り組みはJEEスペースでのRESTful開発を正当化しています。

その間、助けが到着しました。エレガントな方法で、オープンソースのRestletフレームワークにより、従来のJEEテクノロジーを使用してRESTfulサービスを構築および利用することから発生する可能性のある厄介な問題を簡単に回避できます。

レストレットのルーツ

JavaでRESTを実行することに伴う技術的な問題のいくつかに対処するために、フランスのソフトウェアコンサルタントであるJéromeLouvelは、より自然な適合を提供するフレームワークを作成しようとしました。彼は最初にNetKernel環境を出発点として見ました。彼が気に入ったのと同じくらい、それは彼が利用可能にしようとしたAPIに焦点を合わせたフレームワークに完全には適合しませんでした。しかし、この経験は、REST指向の環境が提供できるものの種類についての彼の考えに影響を与えるのに役立ちました。(このシリーズの次の記事では、NetKernelについてさらに詳しく説明します。)

Louvelがフレームワークに取り組んだとき、彼は3つの目標を立てました。

  • 単純なアクションは、基本的な使用法では単純でなければなりません。デフォルトは最小限の労力で機能するはずですが、より複雑な構成も可能です。
  • このAPIに記述されたコードは、コンテナー間で移植可能である必要があります。サーブレットベースのシステムは、Tomcat、Jetty、IBM WebSphereなどのコンテナー間で移動できますが、Louvelは全体像を念頭に置いていました。サーブレット仕様は、HTTPおよびブロッキングI / Oモデルに関連付けられています。彼は、APIをこれらの両方から分離可能にし、現在使用されているコンテナーにデプロイできるようにしたいと考えていました。彼はまた、Grizzly、AsyncWeb、SimpleFrameworkなどの代替の新しいコンテナーでほとんど労力をかけずに使用できるようにしたいと考えていました。
  • JavaでRESTfulインターフェイスを作成するサーバー側だけでなく、クライアント側も充実させる必要があります。HttpURLConnectionクラスとApache HTTPクライアントは、あまりにも、ほとんどのアプリケーションに直接きれいに統合するための低レベルです。

これらの目標を念頭に置いて、彼はRestletAPIの作成に着手しました。数年の流動の後、APIは安定し、コミュニティはその周りで成長しました。現在、コアAPIには活気のあるユーザーベースがあり、JAX-RSなどの他のツールキットやイニシアチブとの統合をサポートするための重要な活動が進行中です。(Louvelは現在JAX-RSエキスパートグループに属しています。)

レストレットの基本

リスト1に示すように、RestletAPIを備えた基本的なサーバーはおそらくこれ以上簡単ではありません。

リスト1.Restletを備えた基本的なサーバー

package net.bosatsu.restlet.basic; import org.restlet.Restlet; import org.restlet.Server; import org.restlet.data.MediaType; import org.restlet.data.Protocol; import org.restlet.data.Request; import org.restlet.data.Response; public class SimpleServer { public static void main(String[]args) throws Exception { Restlet restlet = new Restlet() { @Override public void handle(Request request, Response response) { response.setEntity("Hello, Java RESTafarians!", MediaType.TEXT_PLAIN); } }; // Avoid conflicts with other Java containers listening on 8080! new Server(Protocol.HTTP, 8182, restlet).start(); } }

このアプリケーションは(良い歓声を広めることを除いて)あまり効果がありませんが、Restletの2つの基本原則を示しています。まず、単純なことは単純です。より複雑な活動は確かに可能ですが、あなたは必要なときにだけそれらについて心配します。 RESTには、セキュリティ、制約、コンテンツネゴシエーション、またはその他の重要なタスクを実施する機能があります。これらは、RESTful APIを満たすプロセスとはまったく異なり、ほぼ直交するアクティビティのままです。必要に応じて複雑さを重ねます。

次に、リスト1のコードは、コンテナー・タイプ間で移植できるように設計されています。コンテナを指定していないことに注意してください。Restletsは、最終的に要求に応答する実際のリソースです。サーブレットモデルにある可能性があるため、リクエストを処理するコンテナと情報リソースレスポンダーの間に違いはありません。IDEにコードを入力し、org.restlet.jarおよびcom.noelios.restlet.jarアーカイブへの依存関係を追加すると、アプリケーションを実行でき、次のようなログメッセージが表示されます。

Dec 7, 2008 11:37:32 PM com.noelios.restlet.http.StreamServerHelper start INFO: Starting the internal HTTP server

ブラウザを//localhost:8182に向けると、フレンドリーな挨拶が表示されます。

舞台裏では、にorg.restlet.jarはこのAPIの主要なインターフェースがすべて含まれています。にcom.noelios.restlet.jarは、これらのインターフェイスの基本的な実装が含まれており、デフォルトのHTTP処理機能を提供します。このHTTPエンジンを使用して本番環境に移行することは望ましくありませんが、開発およびテストの目的には非常に便利です。RESTfulコードをテストするために主要なコンテナーを起動する必要はありません。その結果、ユニットテストと統合テストがはるかに簡単になります。

リスト1のサンプルでは、​​多くのデフォルト動作を使用してデフォルトApplicationインスタンスを作成し(Application次の例で説明します)、ポート8182でHTTPプロトコルリクエストをリッスンします。StreamServerHelperクラスはこのポートでリッスンを開始し、Restletインスタンスにリクエストをディスパッチします。彼らは入ってくる。

リスト2に示すように、クライアント側のRESTfulJavaをサポートするというLouvelの目標も簡単に達成できます。

リスト2.Restletクライアント

package net.bosatsu.restlet.basic; import java.io.IOException; import org.restlet.Client; import org.restlet.data.Protocol; public class SimpleClient { public static void main(String [] args) throws IOException { String uri = (args.length > 0) ? args[0] : "//localhost:8182" ; Client client = new Client(Protocol.HTTP); client.get(uri).getEntity().write(System.out); } }

SimpleServerまだコンソールにやさしい挨拶をプリントアウトする必要があり、同じJARの依存関係を持つこの新しいクライアントコードを起動し、実行しています。このスタイルで出力を印刷することは、バイナリ指向のMIMEタイプでは明らかに機能しませんが、繰り返しになりますが、これは便利な出発点です。

非CRUDの例

ほとんどの教育的なRESTの例は、単純なオブジェクトの周りのCRUDishサービス(作成、取得、更新、削除)を示しています。そのスタイルは確かにRESTでうまく機能しますが、それが理にかなっている唯一のアプローチではありません。とにかく、私たちのほとんどはCRUDの例にうんざりしています。次の例は、Jazzyオープンソースのスペルチェッカーをラップすることにより、Restletアプリケーションの基本を示しています。

REST is about managing information, not invoking arbitrary behavior, so you need to exercise care when considering a behavior-oriented API like Jazzy. The trick is to treat the RESTful API as an information space for words that do and do not exist within the dictionaries in use. The problem could be solved in a variety of ways, but this article will define two information spaces. /dictionary is used to manage words in the dictionary. /spellchecker is used to find suggestions for words similar to misspelled words. Both focus on information by considering the absence or presence of words in the information spaces.

In a RESTful architecture, this HTTP command could return a definition of a word in the dictionary:

GET //localhost:8182/dictionary/word

It would probably return the HTTP response code "Not Found" for words that are not in the dictionary. In this information space, it is fine to indicate that words do not exist. Jazzy does not provide definitions for words, so I'll leave returning some content as an exercise for the reader.

This next HTTP command should add a word to the dictionary:

PUT //localhost:8182/dictionary/word

This example uses PUT because you can figure out what the URI in the /dictionary information space should be beforehand, and issuing multiple PUTs should not make a difference. (PUT is an idempotent request, like GET. Issuing the same command multiple times should not make a difference.) If you want to add definitions, you could pass them in as bodies to the PUT handler. If you want to accept multiple definitions over time, you may wish to POST those definitions in, because PUT is an overwrite operation.

Don't overlook synchronization

In the interest of keeping the examples focused, this article pays no special attention to synchronization issues. Do not treat your production code so nonchalantly! Consult a resource such as Java Concurrency in Practice for more information.

The Restlet instances that I'll create need to be bound to the appropriate information spaces, as shown in Listing 3.

Listing 3. A simple RESTful spell checker

package net.bosatsu.restlet.spell; import com.swabunga.spell.event.SpellChecker; import com.swabunga.spell.engine.GenericSpellDictionary; import com.swabunga.spell.engine.SpellDictionary; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import org.restlet.data.Protocol; import org.restlet.*; public class SpellCheckingServer extends Application { public static String dictionary = "Restlet/dict/english.0"; public static SpellDictionary spellingDict; public static SpellChecker spellChecker; public static Restlet spellCheckerRestlet; public static Restlet dictionaryRestlet; static { try { spellingDict = new GenericSpellDictionary(new File(dictionary)); spellChecker = new SpellChecker(spellingDict); spellCheckerRestlet = new SpellCheckerRestlet(spellChecker); dictionaryRestlet = new DictionaryRestlet(spellChecker); } catch (Exception e) { e.printStackTrace(); } } public static void main(String [] args) throws Exception { Component component = new Component(); component.getServers().add(Protocol.HTTP, 8182); SpellCheckingServer spellingService = new SpellCheckingServer(); component.getDefaultHost().attach("", spellingService); component.start(); } public Restlet createRoot() { Router router = new Router(getContext()); router.attach("/spellchecker/{word}", spellCheckerRestlet); router.attach("/dictionary/{word}", dictionaryRestlet); return router; } }

After it builds up the dictionary instance and the spell checker, the Restlet setup in Listing 3 is slightly more complicated than in the earlier basic example (but not much!). The SpellCheckingServer is an instance of a Restlet Application. An Application is an organizational class that coordinates deployment of functionally connected Restlet instances. The surrounding Component asks an Application for its root Restlet by calling the createRoot() method. The root Restlet returned indicates who should respond to the external requests. In this example, a class called Router従属情報スペースにディスパッチするために使用されます。このコンテキストバインディングの実行に加えて、URLの「単語」部分をリクエストの属性として使用できるようにするURLパターンを設定します。これはRestlet、リスト4および5で作成されたで活用されます。

DictionaryRestlet、リスト4に示され、操作するための要求を処理するための責任がある/dictionary情報空間を。