§Play WS API
ときどき、Play アプリケーションから他の HTTP サービスを呼び出したくなることがあります。Play は非同期の HTTP 呼び出しを実現する WS ライブラリ でこれをサポートしています。
WS API には、リクエストの作成とレスポンスの処理という2つの重要な部品があります。まず、GET および POST の HTTP リクエストを作成する方法について紹介し、次に WS からレスポンスを処理する方法について紹介します。最後に、よくあるユースケースを紹介します。
§リクエストの作成
WS を使うには、まず最初に ws
を build.sbt
ファイルに追加します。
libraryDependencies ++= Seq(
ws
)
そして、次のようにインポートします。
import play.api.Play.current
import play.api.libs.ws._
import play.api.libs.ws.ning.NingAsyncHttpClientConfigBuilder
import scala.concurrent.Future
HTTP リクエストを構築するために、 WS.url()
を URL を設定して呼び出します。
val holder: WSRequestHolder = WS.url(url)
これは WSRequestHolder を返し、ヘッダの設定のような様々な HTTP のオプションを設定するために使用します。メソッド呼び出しを連鎖して、複雑なリクエストの構築をまとめることができます。
val complexHolder: WSRequestHolder =
holder.withHeaders("Accept" -> "application/json")
.withRequestTimeout(10000)
.withQueryString("search" -> "play")
使用したい HTTP メソッドに対応するメソッドを最後に呼び出します。これで連鎖が終了し、 WSRequestHolder
のリクエストに設定した全てのオプションが使用されます。
val futureResponse: Future[WSResponse] = complexHolder.get()
This returns a Future[WSResponse]
where the Response contains the data returned from the server.
§認証の設定
HTTP 認証を使う必要があるなら、ビルダーにユーザー名、パスワード、 AuthScheme を設定します。AuthScheme に適用可能なケースオブジェクトは、BASIC
, DIGEST
, KERBEROS
, NONE
, NTLM
そして SPNEGO
です。
WS.url(url).withAuth(user, password, WSAuthScheme.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" -> "application/xml").post(xmlString)
§バーチャルホストの設定
バーチャルホストは文字列で設定します。
WS.url(url).withVirtualHost("192.168.1.1").get()
§タイムアウトの設定
リクエストにタイムアウトを設定するなら、 withRequestTimeout
を使用しミリ秒で値を設定します。
WS.url(url).withRequestTimeout(5000).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[WSResponse] = WS.url(url).post(data)
§XML データの送信
XML データを送信する最も簡単な方法は、XML リテラルを使う事です。XML リテラルは便利ですが、それほど速くはありません。効率を重視するなら、XML ビューテンプレート や JAXB ライブラリを使う事を検討してください。
val data = <person>
<name>Steve</name>
<age>23</age>
</person>
val futureResponse: Future[WSResponse] = WS.url(url).post(data)
§レスポンスの処理
Response に対する操作は Future の中でマッピングをすることで簡単に行えます。
以降の例には、いくつか共通の依存コードがあります。簡潔にするため、ここで1度だけ掲載します。
Future
上で処理を実行するときにはいつでも、暗黙的な実行コンテキストが必要となります。実行コンテキストとは future が実行されてコールバックを行うスレッドプールのことです 。Play のデフォルトの実行コンテキストを使えば、通常では充分でしょう。
implicit val context = play.api.libs.concurrent.Execution.Implicits.defaultContext
次の例も、以降のコードで使用するシリアライゼーション/デシリアラーゼーションのためのケースクラスです。
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._
implicit val personReads = Json.reads[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 によって扱うことができます。WSRequestHolder
の stream()
と getStream()
メソッドはFuture[(WSResponseHeaders, Enumerator[Array[Byte]])]
を返します。Enumerator には レスポンスボディが含まれています。
Iteratee を使用して、レスポンスによって返却されたバイト数を数える、ささいな例を示します。
import play.api.libs.iteratee._
// Make the request
val futureResponse: Future[(WSResponseHeaders, Enumerator[Array[Byte]])] =
WS.url(url).getStream()
val bytesReturned: Future[Long] = futureResponse.flatMap {
case (headers, body) =>
// Count the number of bytes returned
body |>>> Iteratee.fold(0l) { (total, bytes) =>
total + bytes.length
}
}
もちろん、通常はこのような方法で大きなボディを消費したくはないでしょう。より一般的な使用方法は、ボディを別の場所にストリームによって出力することです。次の例では、ボディをファイルにストリームで出力します。
import play.api.libs.iteratee._
// Make the request
val futureResponse: Future[(WSResponseHeaders, Enumerator[Array[Byte]])] =
WS.url(url).getStream()
val downloadedFile: Future[File] = futureResponse.flatMap {
case (headers, body) =>
val outputStream = new FileOutputStream(file)
// The iteratee that writes to the output stream
val iteratee = Iteratee.foreach[Array[Byte]] { bytes =>
outputStream.write(bytes)
}
// Feed the body into the iteratee
(body |>>> iteratee).andThen {
case result =>
// Close the output stream whether there was an error or not
outputStream.close()
// Get the result or rethrow the error
result.get
}.map(_ => file)
}
レスポンスボディの行き先として他にありうるのは、このサーバーが現在処理しているレスポンスへのストリームによる出力です。
def downloadFile = Action.async {
// Make the request
WS.url(url).getStream().map {
case (response, body) =>
// Check that the response was successful
if (response.status == 200) {
// Get the content type
val contentType = response.headers.get("Content-Type").flatMap(_.headOption)
.getOrElse("application/octet-stream")
// If there's a content length, send that, otherwise return the body chunked
response.headers.get("Content-Length") match {
case Some(Seq(length)) =>
Ok.feed(body).as(contentType).withHeaders("Content-Length" -> length)
case _ =>
Ok.chunked(body).as(contentType)
}
} else {
BadGateway
}
}
}
POST
と PUT
は、 withMethod
メソッドを明示的に呼び出す必要があります。例を示します。
val futureResponse: Future[(WSResponseHeaders, Enumerator[Array[Byte]])] =
WS.url(url).withMethod("PUT").withBody("some body").stream()
§共通パターンとユースケース
§WS 呼び出しの連結
for 内包を使うのは、信頼できる環境で WS の呼び出しを連結する良い方法です。起こりうる失敗に対応するために、for 内包と一緒に Future.recover を使用してください。
val futureResponse: Future[WSResponse] = 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)
}
§コントローラーでの使用
コントローラーからリクエストを作成するとき、レスポンスを Future[Result]
へマッピングできます。これは Play の Action.async
アクションビルダーと組み合わせることで使用できます。詳細は 非同期レスポンスの処理 にあります。
def wsAction = Action.async {
WS.url(url).get().map { response =>
Ok(response.body)
}
}
status(wsAction(FakeRequest())) must_== OK
§WSClient を使用する
WSClient は内部的な AsyncHttpClient を包むラッパーです。いくつかのプロパティを指定した複数のクライアントを定義したり、モックを使用できたりする点で有用です。
デフォルトのクライアントは WS シングルトンから呼び出すことができます。
val client: WSClient = WS.client
WS クライアントを、WS を介さず直接コードから定義することができます。その場合、暗黙的なクライアントと一緒にWS.clientUrl()
を使用します。
val clientConfig = new DefaultWSClientConfig()
val secureDefaults:com.ning.http.client.AsyncHttpClientConfig = new NingAsyncHttpClientConfigBuilder(clientConfig).build()
// You can directly use the builder for specific options once you have secure TLS defaults...
val builder = new com.ning.http.client.AsyncHttpClientConfig.Builder(secureDefaults)
builder.setCompressionEnabled(true)
val secureDefaultsWithSpecificOptions:com.ning.http.client.AsyncHttpClientConfig = builder.build()
implicit val implicitClient = new play.api.libs.ws.ning.NingWSClient(secureDefaultsWithSpecificOptions)
val response = WS.clientUrl(url).get()
注意: もし NingWSClient オブジェクトをインスタンス化した場合、それは WS のプラグインシステムを使用していません。そのため、
Application.onStop
の実行時に自動で閉じられません。その代わり、クライアントは処理が完了した際に、client.close()
を用いて手動でシャットダウンする必要があります。これは AsyncHttpClient が内部で使用している ThreadPoolExecutor を解放します。クライアントを閉じることを行わないと、 (開発モードで頻繁にアプリケーションをリロードしている場合は、特に) アウトオブメモリー例外を引き起こすかもしれません。
あるいは、直接的に使用します。
val response = client.url(url).get()
また、適切なクライアントを自動的に結びつけるマグネットパターンを使用できます。
object PairMagnet {
implicit def fromPair(pair: (WSClient, java.net.URL)) =
new WSRequestHolderMagnet {
def apply(): WSRequestHolder = {
val (client, netUrl) = pair
client.url(netUrl.toString)
}
}
}
import scala.language.implicitConversions
import PairMagnet._
val client = WS.client
val exampleURL = new java.net.URL(url)
val response = WS.url(client -> exampleURL).get()
デフォルトでは、設定は application.conf
で行われますが、設定から直接ビルダーを構築することも可能です。
import com.typesafe.config.ConfigFactory
import play.api.libs.ws._
import play.api.libs.ws.ning._
val configuration = play.api.Configuration(ConfigFactory.parseString(
"""
|ws.followRedirects = true
""".stripMargin))
val classLoader = app.classloader // Play.current.classloader or other
val parser = new DefaultWSConfigParser(configuration, classLoader)
val builder = new NingAsyncHttpClientConfigBuilder(parser.parse())
内部的な 非同期クライアント に触ることも可能です。
import com.ning.http.client.AsyncHttpClient
val client: AsyncHttpClient = WS.client.underlying
重要な事柄が 2 点あります。WS はクライアントへのアクセス要求時に 2 つの制限があります。
WS
はマルチパートのフォームのアップロードを直接サポートしていません。内部的なクライアントの RequestBuilder.addBodyPart を使用してください。WS
はストリームによるボディのアップロードをサポートしていません。この場合は、AsyncHttpClient によって提供されるFeedableBodyGenerator
を使用してください。
§WS の設定
WS クライアントの設定は、 application.conf
にある以下のプロパティで行います。
ws.followRedirects
: 301、および、302 でのリダイレクトにクライアントが従うかを設定します。 (デフォルトは true)ws.useProxyProperties
: システムのHTTPプロキシ設定(http.proxyHost, http.proxyPort)を使用するか設定します。 (デフォルトは true)ws.useragent
: User-Agent ヘッダーフィールドを設定します。ws.compressionEnabled
: このプロパティが true の場合 gzip/deflater によるエンコーディングを行います。 (デフォルトは false).
§SSL を用いた WS の設定
HTTP オーバー SSL/TLS (HTTPS) を使用するための WS の設定については、 WS SSLの設定 を参照してください。
§タイムアウトの設定
WS には3種類のタイムアウト設定があります。タイムアウトになると、WS リクエストに割り込みが発生します。
ws.timeout.connection
: リモートホストとの接続を行う最大の時間です。 ( デフォルトは 120秒 )ws.timeout.idle
: アイドル状態(コネクションは確立したが、データを待っている状態)を保持する最大の時間です。 ( デフォルトは 120秒 )ws.timeout.request
: リクエストにかかる全ての時間(リモートホストがデータを送信中であっても、割り込みが発生します)です。 (ストリームによる処理を許容するため、デフォルトは none )
リクエストのタイムアウトは withRequestTimeout()
を使用した接続において上書き可能です。 (“リクエストの作成”の節を参照してください。)
Next: Play の OpenID サポート
このドキュメントの翻訳は Play チームによってメンテナンスされているものではありません。 間違いを見つけた場合、このページのソースコードを ここ で確認することができます。 ドキュメントガイドライン を読んで、お気軽にプルリクエストを送ってください。