Play ライブラリ
play.libs
パッケージには、一般的なプログラミングタスクを達成する手助けとなる便利ないくつかのライブラリが含まれています。
これらライブラリのほとんどは本当に直感的に使うことのできるシンプルなヘルパです:
- Codec: データをエンコードまたはデコードするユーティリティ
- Crypto: 暗号化に関するユーティリティ
- Expression: 動的な式の評価
- F: Java による関数プログラミング
- ファイル: ファイルシステム操作ヘルパ
- I18N: 国際化ヘルパ
- IO: ストリーム操作ヘルパ
- Images: イメージ操作ヘルパ
- Mail: e メール関数
- MimeTypes: MIME タイプの取扱い
- OAuth: OAuth クライアントプロトコル
- OAuth2: OAuth2 クライアントプロトコル
- OpenID: OpenID クライアントプロトコル
- Time: 時間と期間のユーティリティ
- WS: 強力な Web サービスクライアント
- XML: XML 構造のロード
- XPath: XPath による XML の解析
以下の節では、特に重要なライブラリについて、より詳細な情報を提供します。
XPath による XML の解析
XPath は、コード生成ツールを使わずに XML ドキュメントを解析する、おそらくもっとも簡単な方法です。 play.libs.XPath
ライブラリは、このタスクを効率的に達成するために必要な基本機能を提供します。
XPath
演算子はすべての org.w3.dom.Node
型を操作します:
org.w3.dom.Document xmlDoc = … // retrieve a Document somewhere
for(Node event: XPath.selectNodes("events//event", xmlDoc)) {
String name = XPath.selectText("name", event);
String data = XPath.selectText("@date", event);
for(Node place: XPath.selectNodes("//place", event)) {
String place = XPath.selectText("@city", place);
…
}
…
}
Web サービスクライアント
play.libs.WS
は、強力な HTTP クライアントを提供します。内部では Async HTTP client を使用しています。
リクエストの作成は簡単です:
HttpResponse res = WS.url("http://www.google.com").get();
いったん HttpResponse
オブジェクトを手に入れてしまえば、すべてのレスポンス属性にアクセスすることができます。
int status = res.getStatus();
String type = res.getContentType();
複数の content-type においてボディの内容を検索することもできます:
String content = res.getString();
Document xml = res.getXml();
JsonElement json = res.getJson();
InputStream is = res.getStream();
async API を使ってノンブロッキングな HTTP リクエストを作成することもできます。この場合は Promise<HttpResponse>
を受け取ります。いったん非同期状態から回復すれば、通常通り HttpResponse
を使うことができます:
Promise<HttpResponse> futureResponse = WS.url(
"http://www.google.com"
).getAsync();
Java による関数プログラミング
play.libs.F
ライブラリは関数プログラミング由来の便利な構成物を提供します。これらの構成物は複雑に抽象化された事象を取り扱うために使用されます。関数プログラミングに慣れている人のために、以下が提供されています:
Option<T>
(設定できる、または設定できない T 値)Either<A,B>
(どちらかが含まれる A 値または B 値)Tuple<A,B>
(どちらとも含まれる A 値と B 値)
Option<T>
, Some<T>
と None<T>
いくつかのケースでは結果を返さないかもしれない関数 (例えば find) を記述する場合において、一般的な(悪い)Java のパターンは結果がない場合に null
を返すことです。この方法は、関数の戻り値の型がオブジェクトを返さない可能性があることを明確に示していないし、null 参照の発明者によって “billion-dollar mistake” として認識されているおり、危険です。 Option<T>
はこの問題に対するエレガントなソリューションです: T
型のオブジェクトを返す代わりに、この関数は Option<T>
を返します。この関数は成功した場合、(実際の結果をラップする) Some<T>
型、そうでない場合は None<T>
型、いずれも Option<T>
のサブタイプであるオブジェクトを返します。
以下に例を示します:
/* Safe division (will never throw a runtime ArithmeticException) */
public Option<Double> div(double a, double b) {
if (b == 0)
return None();
else
return Some(a / b);
}
この関数のこのように使います:
Option<Double> q = div(42, 5);
if (q.isDefined()) {
Logger.info("q = %s", q.get()); // "q = 8.4"
}
しかし、これを使うために Option<T>
が Iterable<T>
を実装していることを利用したもっと便利な構文があります:
for (double q : div(42, 5)) {
Logger.info("q = %s", q); // "q = 8.4"
}
この for ループの中身は div
関数が成功した場合にのみ、一回だけ実行されます。
Tuple<A, B>
便利な Tuple<A, B>
は、 A
および B
型の二つのオブジェクトをラップします。これらのオブジェクトは、それぞれ _1
および _2
フィールドを使って取得することができます。例えば:
public Option<Tuple<String, String>> parseEmail(String email) {
final Matcher matcher = Pattern.compile("(\\w+)@(\\w+)").matcher(email);
if (matcher.matches()) {
return Some(Tuple(matcher.group(1), matcher.group(2)));
}
return None();
}
そして:
for (Tuple<String, String> email : parseEmail("[email protected]")) {
Logger.info("name = %s", email._1); // "name = foo"
Logger.info("server = %s", email._2); // "server = bar.com"
}
T2<A, B>
クラスは Tuple<A, B>
のエイリアスです。三要素のタプルを扱う場合は T3<A, B, C>
クラスを使い、 T5<A, B, C, D, E>
まで同様です。
パターンマッチング
Java にはパターンマッチングが必要だと感じることがありました。残念ながら Java は組み込みのパターンマッチングを持ち合わせておらず、関数構造の欠如により、パターンマッチングをライブラリとして追加することも困難でした。とにかく、私たちはそれほど悪くない解決策に取り組みました。
私たちのアイディアとは、Java において基礎的なパターンマッチングを達成するために最新の‘for ループ’文法を使うことでした。パターンマッチングは、オブジェクトが求められる条件に合致することを確認し、関心のある値を抽出しなければなりません。Play のパターンマッチングライブラリは play.libs.F
ライブラリの一部です。
簡単な例を見てみましょう; Object 型の参照があり、これが “command:” から始まる文字列であることを確認したいとします。
標準的な方法は以下のようになるでしょう:
Object o = anything();
if(o instanceof String && ((String)o).startsWith("command:")) {
String s = (String)o;
System.out.println(s.toUpperCase());
}
Play のパターンマッチングを使うと、これを次のように書くことができます:
for(String s: String.and(StartsWith("command:")).match(o)) {
System.out.println(s.toUpperCase());
}
この for ループは条件に合致する場合に一度だけ実行され、キャストをする必要なく自動的に String の値を抽出します。明示的なキャストが存在しないので、すべてがタイプセーフであり、コンパイラによって型チェックが行われます。
Promises
Promise
は Play による Future
型のカスタマイズです。実際のところ、 Promise<T>
は Future<T>
でもあるので、標準的な Future
として使用することもできます。しかし、 Promise
にはとても興味深い属性: onRedeem(…)
を使って、期待する値が利用可能になるとできるだけ早く呼び出されるコールバックを登録する機能が備わっています。
Promise
インスタンスは Play のいたるところ (Jobs や WS.async
, その他…) で Future
の代わりに使用されています。
Promise はいくつかの方法で紐付けることができます。例えば:
Promise p = Promise.waitAll(p1, p2, p3)
Promise p = Promise.waitAny(p1, p2, p3)
Promise p = Promise.waitEither(p1, p2, p3)
OAuth
OAuth は、デスクトップアプリケーションや web アプリケーションに向けた、シンプルで標準的なアプローチを使ったセキュアな認証 API 用オープンプロトコルです。
二つの異なる仕様が存在します: OAuth 1.0 と OAuth 2.0 です。Play は、これらのうちどちらかの仕様を提案するサービスにコンシューマとして接続するライブラリを提供します。
一般的な手順は次のようになります:
- ユーザをプロバイダの認証ページへリダイレクトします
- ユーザが認証されると、プロバイダはユーザを未認証トークンと共にあなたのサーバへ再度リダイレクトします
- あなたのサーバは、サービスに対するリクエストを処理するために保存されなければならない、現在のユーザ向けに特化したアクセストークンと、この未認証トークンを交換します。この手順はサーバ間のコミュニケーションとして実行されます
Play フレームワークは、この手順の大部分を取り計らいます。
OAuth 1.0
OAuth 1.0 の機能は oauth-signpost に基づく play.libs.OAuth
クラスによって提供されます。OAuth 1.0 は Twitter や Google のようなサービスで使用されています。
サーバへ接続するためには、サービスプロバイダから得られる以下の情報を使ってOAuth.ServiceInfo インスタンスを作成する必要があります:
- リクエストトークン URL
- アクセストークン URL
- 認証 URL
- コンシューマ・キー
- コンシューマ・シークレット
アクセストークンはこのようにして読み出すことができます:
public static void authenticate() {
// TWITTER is a OAuth.ServiceInfo object
// getUser() is a method returning the current user
if (OAuth.isVerifierResponse()) {
// We got the verifier;
// now get the access tokens using the request tokens
OAuth.Response resp = OAuth.service(TWITTER).retrieveAccessToken(
getUser().token, getUser().secret
);
// let's store them and go back to index
getUser().token = resp.token; getUser().secret = resp.secret;
getUser().save()
index();
}
OAuth twitt = OAuth.service(TWITTER);
Response resp = twitt.retrieveRequestToken();
// We received the unauthorized tokens
// we need to store them before continuing
getUser().token = resp.token; getUser().secret = resp.secret;
getUser().save()
// Redirect the user to the authorization page
redirect(twitt.redirectUrl(resp.token));
}
これで、トークンペアを使って署名したリクエストによって API を呼び出すことができるようになります:
mentions = WS.url(url).oauth(TWITTER, getUser().getTokenPair()).get().getString();
この例ではエラーをチェックしていませんが、本番環境ではチェックすべきです。 OAuth.Response オブジェクトは、エラーが起こった場合には null でないエラーフィールドを保持します。それらの多くは、ユーザがあなたにアクセス権を与えていないか、プロバイダがダウンしているか、あるいはプロバイダにバグがある場合です。
完全な使用例が samples-and-tests/twitter-oauth
にあります。
OAuth 2.0
OAuth 2.0 は、リクエストの署名を伴わないので、OAuth 1.0 よりもかなりシンプルです。OAuth 2.0 は Facebook と 37signals で使用されています。
OAuth 2.0 の機能は play.libs.OAuth2
として提供されています。
サーバへ接続するためには、サービスプロバイダから得られる以下の情報を使って OAuth2 インスタンスを作成する必要があります:
- アクセストークン URL
- 認証 URL
- クライアント ID
- シークレット
public static void auth() {
// FACEBOOK is a OAuth2 object
if (OAuth2.isCodeResponse()) {
// authUrl must be the same as the retrieveVerificationCode call
OAuth2.Response response = FACEBOOK.retrieveAccessToken(authUrl);
// null if an error occurred
String accessToken = response.accessToken;
// null if the call was a success
OAuth2.Error = response.error;
// Save accessToken, you will need it to request the service
index();
}
// authUrl is a String containing an absolute URL where the service
// should redirect the user back
// This will trigger a redirect
FACEBOOK.requestVerificationCode(authUrl);
}
一旦このアクセストークンをユーザと関連づけたあとは、ユーザに代わってサービスへ問い合わせを行うためにこのトークンを使うことができます:
WS.url(
"https://graph.facebook.com/me?access_token=%s", access_token
).get().getJson();
完全な使用例が samples-and-tests/facebook-oauth2
にあります。
OpenID
OpenID オープンで分散した識別システムです。アプリケーションに特化したユーザ情報を保持することなしに、容易に新しいユーザを受け入れることができます。ユーザの OpenID を通して認証されたユーザを追跡し続けなければならないだけです。
この例では、Play アプリケーションにおいて OpenID 認証がどのように使用されるかを高いレベルで見ることができます:
- あらゆるリクエストにおいて、ユーザが接続されているか確認する
- もしユーザが接続されていなければ、ユーザが OpenID を投稿することのできるページを表示する
- ユーザを OpenID プロバイダにリダイレクトする
- ユーザが戻ってきたら、検証された OpenID を取得して HTTP セッションに保存する
OpenID の機能は play.libs.OpenID
クラスとして提供されています。
@Before(unless={"login", "authenticate"})
static void checkAuthenticated() {
if(!session.contains("user")) {
login();
}
}
public static void index() {
render("Hello %s!", session.get("user"));
}
public static void login() {
render();
}
public static void authenticate(String user) {
if(OpenID.isAuthenticationResponse()) {
UserInfo verifiedUser = OpenID.getVerifiedID();
if(verifiedUser == null) {
flash.error("Oops. Authentication has failed");
login();
}
session.put("user", verifiedUser.id);
index();
} else {
if(!OpenID.id(user).verify()) { // will redirect the user
flash.error("Cannot verify your OpenID");
login();
}
}
}
そして、以下が login.html
テンプレートです:
#{if flash.error}
<h1>${flash.error}</h1>
#{/if}
<form action="@{Application.authenticate()}" method="POST">
<label for="user">What’s your OpenID?</label>
<input type="text" name="user" id="user" />
<input type="submit" value="login..." />
</form>
</code>
最後に、 routes
の定義は以下のようになります:
GET / Application.index
GET /login Application.login
* /authenticate Application.authenticate
考察を続けます
それでは 非同期ジョブ を使って、あらゆる HTTP リクエストの外側で操作を実行する方法を確認しましょう。