§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 のオプションは、 BASIC
、 DIGEST
、 KERBEROS
、 NONE
、NTLM
、 SPNEGO
があります。
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 で送信するには、 post
に Map[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: Future[Unit] = WS.url(url).withTimeout(3000).get {
headers =>
fromStream(outputStream)
}.flatMap(_.run)
Iteratee は ファイルの一部をバイト配列として受け取り、それを OutputStream に書き込みます。 また、 EOF
シグナルを受け取ると ストリームを閉じます。 EOF
シグナルを受け取るまで、 Iteratee は実行され続けます。
WS
は終了時に EOF
を Iteratee に送信しません。 その代わりに、 Future を返すようにしています。
実際、 WS
は入力の制御を行わないため、 EOF
を送信するべきではありません。 複数の WS の呼び出し (その場で tar ファイルを構築するような) を Iteratee に送信したくなるかもしれません。 そして、ここでもし WS
が EOF
を送信するなら、 ストリームは期待したとおりに閉じられません。 EOF
の送信は呼び出し側の責任です。
Iteratee.run を呼び出す事で Future が実行されるときに、 Iteratee に EOF
が送信されます。
POST
と PUT
の呼び出しでは、 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
はマルチパートのフォームアップロードを直接サポートしません。 基礎的なクライアントの RequestBuilder.addBodyPart を使う事ができます。WS
は (相互 TLS / MTLS / クライアント認証として知られている) クライアント証明書をサポートしません。 AsyncHttpClientConfig のインスタンスに、SSLContext
を直接設定し、 適切なキーストアとトラストストアを設定します。
§WS の設定
WS クライアントの設定には、以下のプロパティを使います。
ws.timeout
は接続、および、リクエストタイムアウトの両方をミリ秒で設定します。ws.followRedirects
は301、および、302 でのリダイレクトにクライアントが従うかを設定します。ws.useProxyProperties
はシステムhttpプロキシ設定(http.proxyHost、http.proxyPort)を使用するかを設定します。ws.useragent
は User-Agent ヘッダーフィールドを設定します。ws.acceptAnyCertificate
はデフォルトの SSLContext を使用するため false を設定します。
§タイムアウト
WS には3種類のタイムアウトがあります。 タイムアウトになると、 WS のリクエストに割り込みが発生します。
- コネクションタイムアウト: リモートホストとの接続を行う最大の時間です。 ( デフォルトは 120秒 )
- コネクションアイドルタイムアウト: アイドル状態(コネクションは確立したが、データを待っている状態)を保持する最大の時間です。 ( デフォルトは 120秒 )
- リクエストタイムアウト: リクエストにかかる全ての時間(リモートホストがデータを送信中であっても、割り込みが発生します)です。 (ストリームによる処理を許容するため、デフォルトは なし )
各タイムアウトは application.conf
の中の、それぞれ、 ws.timeout.connection
、 ws.timeout.idle
、 ws.timeout.request
に設定します。
あるいは、 ws.timeout
で、 コネクションタイムアウト と コネクションアイドルタイムアウト の両方を設定できます。
リクエストタイムアウトは、 withRequestTimeout
を使用してコネクションに設定する事ができます。
例:
WS.url("http://playframework.org/").withRequestTimeout(10000 /* ミリ秒で */)
Next: Play の OpenID サポート
このドキュメントの翻訳は Play チームによってメンテナンスされているものではありません。 間違いを見つけた場合、このページのソースコードを ここ で確認することができます。 ドキュメントガイドライン を読んで、お気軽にプルリクエストを送ってください。