Documentation

§The Play WS API

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

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

§リクエストの作成

WS を使用するには、まず wsbuild.sbt ファイルに追加してください。

libraryDependencies ++= Seq(
  ws
)

そして、WS を利用したいコントローラやコンポーネントで WSClient への依存を宣言します。

import javax.inject.Inject
import scala.concurrent.Future

import play.api.mvc._
import play.api.libs.ws._

class Application @Inject() (ws: WSClient) extends Controller {

}

WSClient のインスタンスを ws と名付けたので、以下の例ではこの名前を用いることとします。

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

val request: WSRequest = ws.url(url)

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

val complexRequest: WSRequest =
  request.withHeaders("Accept" -> "application/json")
    .withRequestTimeout(10000)
    .withQueryString("search" -> "play")

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

val futureResponse: Future[WSResponse] = complexRequest.get()

すると、 Future[WSResponse] が返されます。ここで Response がサーバから返されたデータを保持しています。

§認証の設定

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 を使用しミリ秒で値を設定します。 -1 を設定すると無期限になります。

ws.url(url).withRequestTimeout(5000).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[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 object として処理するには、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 によって扱うことができます。WSRequeststream()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
      }
  }
}

POSTPUT は、 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による注入を受けることなく、コードから直接 WS クライアントを定義することができます。そして、 WS.clientUrl() の際にはクライアントを暗黙的に使用することができます。

import play.api.libs.ws.ning._

implicit val sslClient = NingWSClient()
// close with sslClient.close() when finished with client
val response = WS.clientUrl(url).get()

NOTE: NingWSClient オブジェクトをインスタンス化した場合には、それは WS モジュールのライフサイクルに乗らないので、Application.onStop の際に自動的にはクローズされません。そのかわりに、処理が終了した際に client.close() することによりクライアントを手動でシャットダウンしなければなりません。これによって、AsyncHttpClient が内部で使用している ThreadPoolExecutor を開放します。クライアントを閉じることに失敗すると、アウトオブメモリー例外が生じるおそれがあります (特に、開発モードでアプリケーションを頻繁にリロードする場合)。

または直接、

val response = sslClient.url(url).get()

あるいは、マグネットパターンを利用して、特定のクライアントを自動的に紐付けることもできます。

object PairMagnet {
  implicit def fromPair(pair: (WSClient, java.net.URL)) =
    new WSRequestMagnet {
      def apply(): WSRequest = {
        val (client, netUrl) = pair
        client.url(netUrl.toString)
      }
    }
}

import scala.language.implicitConversions
import PairMagnet._

val exampleURL = new java.net.URL(url)
val response = WS.url(ws -> exampleURL).get()

デフォルトでは、設定は application.conf で行われますが、設定から直接ビルダーを構築することも可能です。

import com.typesafe.config.ConfigFactory
import play.api._
import play.api.libs.ws._
import play.api.libs.ws.ning._

val configuration = Configuration.reference ++ Configuration(ConfigFactory.parseString(
  """
    |ws.followRedirects = true
  """.stripMargin))

// If running in Play, environment should be injected
val environment = Environment(new File("."), this.getClass.getClassLoader, Mode.Prod)

val parser = new WSConfigParser(configuration, environment)
val config = new NingWSClientConfig(wsClientConfig = parser.parse())
val builder = new NingAsyncHttpClientConfigBuilder(config)

内部の 非同期クライアント を使用することも可能です。

import com.ning.http.client.AsyncHttpClient

val client: AsyncHttpClient = ws.underlying

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

§WS の設定

WS クライアントの設定は、 application.conf にある以下のプロパティで行います。

§SSL を用いた WS の設定

HTTP over SSL/TLS (HTTPS) を使用するための WS の設定については、 WS SSLの設定 を参照してください。

§タイムアウトの設定

WS には3種類のタイムアウト設定があります。タイムアウトになると、WS リクエストが中断します。

リクエストのタイムアウトは withRequestTimeout() を使用した接続において上書き可能です。 (“リクエストの作成”の節を参照してください。)

§AsyncHttpClientConfig の設定

内部のAsyncHttpClientConfig にて、以下のような高度な設定を行うことができます。

詳しくはAsyncHttpClientConfig のドキュメントを参照してください。

Next: OpenID サーバへの接続


このドキュメントの翻訳は Play チームによってメンテナンスされているものではありません。 間違いを見つけた場合、このページのソースコードを ここ で確認することができます。 ドキュメントガイドライン を読んで、お気軽にプルリクエストを送ってください。