HTTP
Overview
Result
play.api.mvc.Result
は、HTTP レスポンスを表す。ボディ部は Enumerator[Array[Byte]]
であり、すべてのボディ部をメモリに格納することなく、ストリームとして出力エンジンに受け渡せる。
case class Result(header: ResponseHeader, body: Enumerator[Array[Byte]],
connection: HttpConnection.Connection = HttpConnection.KeepAlive)
Content-Type: text/plain
を返す HTTP レスポンスは以下のようになる。
import play.api.http.HeaderNames._
import play.api.libs.iteratee._
import play.api.mvc._
...
val response: Result = Result(
header = ResponseHeader(200, Map(CONTENT_TYPE -> "text/plain")),
body = Enumerator("Hello World".getBytes())
)
HttpConnection.Connection
は接続リソースではなく、Connection: (keep-alive|close)
ヘッダの Enum である。明示的に Connection: close
を送信したい場合を除いて、デフォルト値の HttpConnection.KeepAlive
のままでよい。
Writeable
ボディ部の Array[Byte]
への変換のために、play.api.http.Writeable[E]
に各タイプ毎の暗黙関数が定義されている。
import play.api.http.Writeable
def enumerator[E](in: E)(implicit w: Writeable[E]): Enumerator[Array[Byte]] = {
Enumerator(w.transform(in))
}
val enumratorFromStr: Enumerator[Array[Byte]] = enumerator("Hello")
val enumratorFromXml: Enumerator[Array[Byte]] = enumerator(<foo>bar</foo>)
Status
Result
インスタンスの生成には、通常は play.api.mvc.Results.Status
のヘルパーメソッドを使えばよい。ボディ部は Writeable[E]
により、暗黙的に Array[Byte]
に変換される。
// Result(123, Map(Content-Type -> text/plain; charset=utf-8))
val status = Status(123)("One-Two-Three")
// Result(200, Map(Content-Type -> text/html; charset=utf-8))
val ok = Ok("<html><body><p>Hello World!</p></body></html>").as(HTML)
// Result(200, Map(Content-Type -> application/xml; charset=utf-8))
val notFound = NotFound(<message>Page not found</message>)
// Result(503, Map())
val serverError = ServiceUnavailable
// Result(303, Map(Location -> /path/to/url))
val seeOther = SeeOther("/path/to/url")
// Result(301, Map(Location -> /path/to/url))
val movedPermanetly = Redirect("/path/to/url", MOVED_PERMANENTLY)
// Result(303, Map(Location -> /path/to/url?foo=bar&foo=baz))
val withQueryString = Redirect("/path/to/url", Map("foo" -> Seq("bar", "baz")))
Chunked Transfer Encoding
ファイル送信を行なうには、ヘルパーメソッドの Status.sendFile
を使えばよい。
val file = new java.io.File(getClass.getResource("/a.txt").getPath())
Ok.sendFile(
content = file,
inline = false,
fileName = "download-a.txt"
)
inline
:Content-Disposition
ヘッダを付与しない(ダウンロードファイルとして送信しない)fileName
:Content-Disposition
ヘッダのfilename
属性(ダウンロードファイル名)
ファイルのように、ボディ部のサイズが大きい場合は注意が必要である。Content-Length
ヘッダ値の算出のために、全てのボディ部をメモリに入れる必要があり、Enumerator
を利用している意味がない。
独自に Result
を組み立てる場合は、ボディ部のサイズを予め取得して Content-Length
値に指定しておく必要がある。
val file = new java.io.File(getClass.getResource("/a.txt").getPath())
Result(
header = ResponseHeader(OK, Map(
CONTENT_DISPOSITION -> "attachment; filename=a.txt",
CONTENT_TYPE -> "application/octet-stream",
CONTENT_LENGTH -> file.length().toString())),
body = Enumerator.fromFile(file)
)
入力ソースが java.io.InputStream
のように、あらかじめボディ部のサイズを得る事ができない場合は Transfer-Encoding: chunked
で送信する。
ヘルパーメソッドの Status.chunked
により、Chunked 形式で送信できる。
val input = getClass.getResourceAsStream("/a.txt")
Ok.chunked(Enumerator.fromStream(input)).withHeaders(
CONTENT_DISPOSITION -> "attachment; filename=a.txt",
CONTENT_TYPE -> "application/octet-stream"
)
}
独自に Result
を組み立てたい場合は、Results.chunked
により得られる Enumeratee[Array[Byte], Array[Byte]]
を利用して Chunked 形式に変換する。Status.chunked
のソースを参考にするとよい。
def chunked[C](content: Enumerator[C])(implicit writeable: Writeable[C]): Result = {
Result(header = ResponseHeader(status,
writeable.contentType.map(ct => Map(
CONTENT_TYPE -> ct,
TRANSFER_ENCODING -> CHUNKED
)).getOrElse(Map(
TRANSFER_ENCODING -> CHUNKED
))
),
body = content &> writeable.toEnumeratee &> chunk,
connection = HttpConnection.KeepAlive)
}
Request
play.api.mvc.Request
は、HTTP リクエストを表す。 Action を通じて引数として得るのみで、アプリケーション側で組み立てることはない。
object Request {
def apply[A](rh: RequestHeader, a: A): Request[A]
...
}
テスト時にモックを作りたい場合は、play.api.test.FakeRequest
を使う。テストがしやすいように withHeaders
などのメソッドが追加されている。
val rh: Request[NodeSeq] = FakeRequest("POST", "/create")
.withHeaders("X-Requested-With" -> "0123456789abcdef")
.withBody(<foo>bar</foo>)
FakeRequest
ではボディ部も指定できるが BodyParser で変換されたと仮定される Request#body
である。HTTP リクエストのボディ部のバイト列や文字列ではない。 BodyParser をテストするときは Enumerator でボディ部を送る。
val it = parse.xml(FakeRequest().withHeaders(CONTENT_TYPE -> "application/xml"))
(Enumerator("<foo>bar</foo>".getBytes) |>>> it)
.onComplete(println) // Success(Right(<foo>bar</foo>))