Documentation

You are viewing the documentation for the 2.2.0 release in the 2.2.x series of releases. The latest stable release series is 2.4.x.

§Play WS API

ときどき、Play アプリケーションから他の HTTP サービスを呼び出したくなることがあります。Play は非同期の HTTP 呼び出しを実現する WS ライブラリ でこれをサポートしています。

WS API には、リクエストの作成とレスポンスの処理という2つの重要な部品があります。 まず、 GET および POST の HTTP リクエストを作成する方法について紹介し、 次に WS からレスポンスを処理する方法について紹介します。 最後に、よくあるユースケースを紹介します。

§リクエストの作成

WS を使うには、まず以下のインポートを行います。

import play.api.libs.ws._
import scala.concurrent.Future

HTTP リクエストを構築するために、 WS.url() を URL を設定して呼び出します。

val holder : WSRequestHolder = WS.url(url)

これは WSRequestHolder を返し、 ヘッダの設定のような様々な HTTP のオプションを設定するために使用します。 メソッド呼び出しを連鎖して、複雑なリクエストの構築をまとめることができます。

val complexHolder : WSRequestHolder = holder.withHeaders(...)
                                            .withTimeout(...)
                                            .withQueryString(...)

使用したい HTTP メソッドに対応するメソッドを最後に呼びだします。 これで連鎖が終了し、 WSRequestHolder のリクエストに設定した全てのオプションが使用されます。

val futureResponse : Future[Response] = complexHolder.get()

これは、 Future[Response] を返し、 Response にはサーバーから返されるデータが含まれます。

§認証の設定

もしHTTP認証が必要なら、 ユーザー名、パスワード、および、 AuthScheme をビルダーに設定します。 AuthScheme のオプションは、 BASICDIGESTKERBEROSNONENTLMSPNEGO があります。

import com.ning.http.client.Realm.AuthScheme

WS.url(url).withAuth(user, password, AuthScheme.BASIC).get()

§リダイレクトの設定

もし、 HTTP 呼び出しの結果が、 302 や 301 のようなリダイレクトであるなら、 他のメソッド呼び出しをしなくとも自動的にリダイレクトされます。

WS.url(url).withFollowRedirects(true).get()

§クエリストリングの設定

パラメーターは、 キー/値のタプルをつなげて設定することもできます

WS.url(url).withQueryString("paramKey" -> "paramValue").get()

§ヘッダの設定

ヘッダは、キー/値のタプルをつなげて設定します。

WS.url(url).withHeaders("headerKey" -> "headerValue").get()

もし、 プレーンなテキストを特定のフォーマットで送信したいのなら、 コンテントタイプを明示的に設定する必要があります。

WS.url(url).withHeaders("Content-Type" -> "text-xml").post(xmlString)

§バーチャルホストの設定

バーチャルホストは文字列で設定します。

WS.url(url).withVirtualHost("192.168.1.1").get()

§タイムアウトの設定

もし、サーバーでの処理に要する時間が多くなるなら、 withTimeout をミリ秒で設定することができます。 巨大なファイルを扱う場合などに使うことができます。

WS.url(url).withTimeout(1000).get()

§フォームデータの送信

フォームエンコードされたデータを POST で送信するには、 postMap[String, Seq[String]] を渡す必要があります。

WS.url(url).post(Map("key" -> Seq("value")))

§JSON データの送信

JSON データを送信する最も簡単な方法は、 JSON ライブラリを使うことです。

import play.api.libs.json._
val data = Json.obj(
  "key1" -> "value1",
  "key2" -> "value2"
)
val futureResponse: Future[Response] = WS.url(url).post(data)

§XML データの送信

XML データを送信する最も簡単な方法は、 XML リテラルを使う事です。 XML リテラルは便利ですが、 それほど速くはありません。 効率を重視するなら、 XML ビューテンプレート や JAXB ライブラリを使う事を検討してください。

val data = <person>
  <name>Steve</name>
  <age>23</age>
</person>
val futureResponse: Future[Response] = WS.url(url).post(data)

§レスポンスの処理

Response に対する操作は Future の中でマッピングをすることで簡単に行えます。

以降の例には、いくつか共通の依存コードがあります。簡潔にするため、ここで1度だけ掲載します。

Future.map が必要とする実行時コンテキストです。

implicit val context = scala.concurrent.ExecutionContext.Implicits.global

シリアライゼーション/デシリアラーゼーションで使用するケースクラスです。

case class Person(name: String, age: Int)

§JSON レスポンスの処理

レスポンスを JSON オブジェクト として処理するには、 response.json を呼び出します。

val futureResult: Future[String] = WS.url(url).get().map {
  response =>
    (response.json \ "person" \ "name").as[String]
}

JSON ライブラリには、暗黙の Reads[T] をクラスに直接マッピングする 便利な機能 があります。

import play.api.libs.json._
import play.api.libs.functional.syntax._

implicit val personReads: Reads[Person] = (
  (__ \ "name").read[String]
    and (__ \ "age").read[Int]
  )(Person)

val futureResult: Future[JsResult[Person]] = WS.url(url).get().map {
  response => (response.json \ "person").validate[Person]
}

§XML レスポンスの処理

レスポンスを XML リテラル として処理するには、 response.xml を呼び出します。

val futureResult: Future[scala.xml.NodeSeq] = WS.url(url).get().map {
  response =>
    response.xml \ "message"
}

§巨大なレスポンスの処理

get()post() を実行すると、レスポンスが使用可能になる前に、リクエストの本体をメモリに読込みます。 数ギガバイトのファイルのような大量のダウンロードを行うと、 不愉快なガベージコレクションや、アウトオブメモリーエラーを招くかもしれません。

WS では、 Iteratee を使うことにより、レスポンスをインクリメンタルに扱うことができます。

import play.api.libs.iteratee._

def fromStream(stream: OutputStream): Iteratee[Array[Byte], Unit] = Cont {
  case [email protected] =>
    stream.close()
    Done((), e)
  case Input.El(data) =>
    stream.write(data)
    fromStream(stream)
  case Input.Empty =>
    fromStream(stream)
}

val outputStream: OutputStream = new BufferedOutputStream(new FileOutputStream(file))
val futureResponse = WS.url(url).withTimeout(3000).get {
  headers =>
    fromStream(outputStream)
}.map(_.run)

Iteratee は ファイルの一部をバイト配列として受け取り、それを OutputStream に書き込みます。 また、 EOF シグナルを受け取ると ストリームを閉じます。 EOF シグナルを受け取るまで、 Iteratee は実行され続けます。

WS は終了時に EOF を Iteratee に送信しません。 その代わりに、 Future を返すようにしています。
実際、 WS は入力の制御を行わないため、 EOF を送信するべきではありません。 複数の WS の呼び出し (その場で tar ファイルを構築するような) を Iteratee に送信したくなるかもしれません。 そして、ここでもし WSEOF を送信するなら、 ストリームは期待したとおりに閉じられません。 EOF の送信は呼び出し側の責任です。

Iteratee.run を呼び出す事で Future が実行されるときに、 Iteratee に EOF が送信されます。

POSTPUT の呼び出しでは、 GET の方法とは少し違う API を使います。 post() の代わりに、 同じ動作をする postAndRetrieveStream(body) を使用してください。

WS.url(url).postAndRetrieveStream(body) { headers =>
  Iteratee.foreach { bytes => logger.info("Received bytes: " + bytes.length) }
}

§共通パターンとユースケース

§WS 呼び出しの連結

for 内包を使うのは、 信頼できる環境で WS の呼び出しを連結する良い方法です。 起こりうる失敗に対応するために、 for 内包と一緒に Future.recover を使用してください。

val futureResponse: Future[Response] = for {
  responseOne <- WS.url(urlOne).get()
  responseTwo <- WS.url(responseOne.body).get()
  responseThree <- WS.url(responseTwo.body).get()
} yield responseThree

futureResponse.recover {
  case e: Exception =>
    val exceptionData = Map("error" -> Seq(e.getMessage))
    WS.url(exceptionUrl).post(exceptionData)
}

§コントローラーでの使用

非同期レスポンスの処理 で定義されている Async メソッドを使うと、 Promise の構成と Future[Result] の終了を Play サーバーが直接ハンドルします。

def feedTitle(feedUrl: String) = Action {
  Async {
    WS.url(feedUrl).get().map { response =>
      Ok("Feed title: " + (response.json \ "title").as[String])
    }
  }
}

§高度な使用方法

基礎的な 非同期クライアント に触ることも可能です。

import com.ning.http.client.AsyncHttpClient

val client:AsyncHttpClient = WS.client

重要な事柄が2点あります。 WS はクライアントへのアクセス要求時に2つの制限があります。

§WS の設定

WS クライアントの設定には、以下のプロパティを使います。

§タイムアウト

WS には3種類のタイムアウトがあります。 タイムアウトになると、 WS のリクエストに割り込みが発生します。

各タイムアウトは application.conf の中の、それぞれ、 ws.timeout.connectionws.timeout.idlews.timeout.request に設定します。

あるいは、 ws.timeout で、 コネクションタイムアウトコネクションアイドルタイムアウト の両方を設定できます。

リクエストタイムアウトは、 withRequestTimeout を使用してコネクションに設定する事ができます。

例:

WS.url("http://playframework.org/").withRequestTimeout(10000 /* ミリ秒で */)

Next: Play の OpenID サポート