Documentation

You are viewing the documentation for the 2.3.x release series. 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 を使うには、まず最初に wsbuild.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 で送信するには、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 オブジェクト として処理するには、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 によって扱うことができます。WSRequestHolderstream()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 シングルトンから呼び出すことができます。

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 の設定

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

§SSL を用いた WS の設定

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

§タイムアウトの設定

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

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

Next: Play の OpenID サポート


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