(本文译者是爱国者)
我们知道Http的PUT请求和POST请求能包含正文(或叫主体). Http正文能携带任何信息,通过Content-Type
标明正文的类型。Play使用正文解析器(Body parser)将Http正文转换成某种Scala类型的数据。
一个Http正文解析器用play.api.mvc.BodyParser[A]
表示,它将接收到的字节流转换成类型为A的目标对象.
然而,http正文可能很大, 如果等所有的正文内容传输完再一次性放到内存进行解析,这样的做法是不高效的。 某些目标对象,比如字符串和文件,完全可以一边接收字节流一边进行构建。
另外,在开始解析正文之前,Http正文解析器可以获取请求头的内容,从而可以对正文先执行一些检查. 比如客户端上传的文件是否过大,正文类型(Content-Type)是否符合要求等等。
注意:你可以将
BodyParser[A]
看成Iteratee[Array[Byte], A]
,但准确来说应该要看成Iteratee[Array[Byte], Either[Result,A]]
。这样设计的目的在于,当无法正确解析Http正文时可以直接返回诸如400 BAD_REQUEST
,412 PRECONDITION_FAILED
或者413 REQUEST_ENTITY_TOO_LARGE
等响应。想了解更多流式处理的内容,请点击 [[这里 | Iteratees]]
完成Http正文的解析之后会返回一个类型为A
的对象并保存在request
对象里,然后执行Action
函数
前面提到,Action
是一个Request => Result
的函数,但不完全正确。我们先来看看Action
特质的定义:
trait Action[A] extends (Request[A] => Result) {
def parser: BodyParser[A]
}
首先我们看到`Request`带有一个泛型`A`,并在`Action`中定义了一个象函数`parser`,`parser`的返回类型是`BodyParser[A]`
我们再来看看`Request`特质的定义:
trait Request[+A] extends RequestHeader {
def body: A
}
这里的`A`类型就是Http正文解析后的类型。我们定义任何我们想要的类型,常见的有`String`,`NodeSeq`,`Array[Byte]`,`JsonValue`和`java.io.File`等,只要提供相应的body parser
总的来说,`Action[A]`使用`BodyParser[A]`将Http正文解析成类型为`A`的对象,然后创建`Request[A]`对象并传递给action代码块
#### 默认的Http正文解析器
如果不指定Http正文解析器,那么Play会使用一个将Http正文解析成`play.api.mvc.AnyContent`的默认解析器(`play.api.mvc.BodyParsers.parser`)。这个解析器会根据`Content-Type`的值决定解析成那种类型的对象;
text/plain: String
application/json: JsValue
text/xml: NodeSeq
application/form-url-encoded: Map[String, Seq[String]]
multipart/form-data: MultipartFormData[TemporaryFile]
any other content type: RawBuffer
例子:
def save = Action { request =>
val body: AnyContent = request.body
val textBody: Option[String] = body.asText
// Expecting text body
textBody.map { text =>
Ok("Got: " + text)
}.getOrElse {
BadRequest("Expecting text/plain request body")
}
}
如果Content-Type
的值是text/plain
,可以使用parser.text
将正文解析成java.lang.String
def save = Action(parse.text) { request =>
Ok(“Got: " + request.body)
}
你是否注意到这段代码的简洁性? 实际上,如果在解析过程中发生错误或正文无法解析,那么parse.text
会直接返回400 BAD_REQUEST
的响应,因此我们无需在action代码块中对解析后的request.body
进行检验。
上面的代码可以改成:
def save = Action(parse.tolerantText) { request =>
Ok(“Got: " + request.body)
}
所不同的是,parser.tolerantText
不会检查Content-Type
的值,它只是简单地把Http正文转换成java.lang.String
提示: 每一种body parser都有相应的
tolerant
版本
下面的例子将Http正文解析成一个文件
def save = Action(parse.file(to = new File(“/tmp/upload”))) { request =>
Ok(“Saved the request content to " + request.body)
}
现在编写一个自定义的正文解析器。这个正文解析器从session中提取用户名, 并为每个用户生成一个唯一的文件。
val storeInUserFile = parse.using { request =>
request.session.get(“username”).map { user =>
file(to = new File("/tmp/" + user + ".upload"))
}.getOrElse {
error(Unauthorized("You don't have the right to upload here"))
}
}
def save = Action(storeInUserFile) { request =>
Ok(“Saved the request content to " + request.body)
}
注意: 这里我们没有真正实现自己的正文解析器,只是组合现有的正文解析器。一般来说,Play提供的正文解析器能满足大部分应用场景。后面的章节会介绍如何编写自己的正文解析器
对于针对文本的正文解析器,比如text,json,xml或者formUrlEncoded, Play会使用一个阀值来限制正文的最大长度。因为这几种解析器会将Http正文全部放到内存进行解析。Play将Http正文的最大长度默认地被限制为100KB,你可以在修改该阀值。请看下面的例子:
// 只接受不大于10KB的文本
def save = Action(parse.text(maxLength = 1024 * 10)) { request =>
Ok(“Got: " + text)
}
提示: 可以在
application.conf
配置文件中指定Http文本的最大长度:`parsers.text.maxLength=128K`
或者使用maxLength
函数;
// Accept only 10KB of data.
def save = Action(maxLength(1024 * 10, parser = storeInUserFile)) { request =>
Ok(“Saved the request content to " + request.body)
}
原文:https://github.com/playframework/Play20/wiki/ScalaBodyParsers