Play 2.0 中文资料 - Body 解析器

什么是 Body 解析器?

HTTP PUT 或 POST 请求含有一个请求体(Body). 请求体可以使用任何格式, 只要在请求头中指定相应的 Content-Type 即可. 在 Play 中, 一个 Body 解析器 转换请求体为对应的 Scala 值.

然而,HTTP 请求体可能非常的大,这时候 Body 解析器 不可能在解析之前光等着把数据全部加载到内存. BodyParser[A] 是个基本的 Iteratee[Array[Byte],A], 这就是说它一块一块的接收数据 (只要 Web 浏览器在上传数据) 并计算出类型为 A 的值作为结果.

让我们考虑一下几个例子.

  • 一个文本型 Body 解析器能够把逐块的字节数据连缀成一个字符串, 并把计算得到的字符串作为结果 (Iteratee[Array[Byte],String]).
  • 一个文件型 Body 解析器能够把逐块的字节数据存为一个本地文件, 并以 java.io.File 引用作为结果 (Iteratee[Array[Byte],File]).
  • 一个 s3 型 Body 解析器能够把逐块的字节数据推送给 Amazon S3 并以 S3 对象 ID 作为结果 (Iteratee[Array[Byte],S3ObjectId]).

此外,Body解析器在开始解析数据之前已经访问了 HTTP 请求头, 因而有了机会运行一些先决条件的检查. 例如, Body 解析器能检查一些 HTTP 头是否正确设置了, 或者用户试图上传一个大文件时要检查是否有权限这么做.

Unmi 注:这上面又提了一通 Iteratee,那什么是 Iteratee 呢? 关于 Iteratee IO, 可参考:Iteratee I/OScalaz Tutorial: Enumeration-Based I/O with Iteratees,Iteratee 是什么意思呢,查不到这个单词,只记得原来看适配器模式时,Adapter 是适配器,Adaptee 是适适配者,同理 Iterator 是迭代器,Iteratee 应该是被迭器者了。

: 那就是为什么 Body 解析器不是一个真正的 Iteratee[Array[Byte],A],确切的说是一个 Iteratee[Array[Byte],Either[Result,A]], 这就是说它在无法为请求体计算出正确的值时,有能力亲自发出 HTTP 状态码 (像 400 BAD_REQUEST, 412 PRECONDITION_FAILED 或者 413 REQUEST_ENTITY_TOO_LARGE)

一旦 Body 解析器完成了它的工作且返回了类型为 A 的值, 相应的 Action 函数就会被执行并计算出请求体中的数据值.

更多 Action 的内容

前面我们说  Action 是一个 Request => Result 函数. 这不完全是对的. 让我们更深入地看下 Action 特质:

首先我们看到有一个类型 A, 然后这个 Action 必须定义一个 BodyParser[A] 类型. Request[A] 的定义如下:

A 是请求体的类型. 我们可以使用任何 Scala 类型作为请求体的类型, 例如 String, NodeSeq, Array[Byte], JsonValue, 或是 java.io.File, 只要我们有一个 Body 解析器能够处理它.

概括一下就是, Action[A] 用返回类型为 BodyParser[A] 的方法去从 HTTP 请求中获取类型为 A 的值, 并构建出 Request[A] 类型的对象传递给 Action 代码.

默认的 Body 解析器: AnyContent

在我们前面的例子中还从未指定过 Body 解析器. 所以它是怎么工作的呢? 如果你不指定自己的 Body 解析器, Play 就会使用默认的, 它把请求体处理成一个 play.api.mvc.AnyContent 实例.

Body 解析通过检查 Content-Type 头来决定要处理的 Body 类型:

  • text/plain: String
  • application/json: JsValue
  • text/xml: NodeSeq
  • application/form-url-encoded: Map[String, Seq[String]]
  • multipart/form-data: MultipartFormData[TemporaryFile]
  • 任何其他的 content type 类型: RawBuffer

例如:

Unmi 注: Body 解析器的大致工作过程是,解析请求体中的数据为相应的类型,然后赋值给 request.body,透明化掉 Body 解析器就是你只要正确去使用 request.body 就行,所以你最终要关注的也就是 request.body。request.body 是 AnyContent 类型,它有 asText, asXml, asJson, asRaw, asFormUrlEncoded 和 asMultipartFormData 方法。

指定一个 Body 解析器

Play 中可用的 Body 解析器定义在 play.api.mvc.BodyParsers.parse 中. Unmi 注:    即定义在 ContentTypes.scala 文件中。

所以像本例那样, 定义了一个期望 Text Body 类型的 Action (类似前面示例中那样):

Unmi 注: 上面方法的原型是来自于 Action.scala 中的 ActionBuilder 里定义的:

上面括号里的 parse.text 位置能使用什么还是看看 play.api.mvc.BodyParsers.parse 中定义的方法,见:http://www.playframework.org/documentation/api/2.0/scala/index.html#play.api.mvc.BodyParsers$parse$,其中有

parse.xml, parse.json, parse.temporaryFile, parse.toLerantText, 或者那些带参数的  parse.file (to: File) 等等

前面 parse.text 方法的原型是:

是不是觉得为什么代码这么简单呢? 这是因为 parse.text Body 解析在出问题时会发送 400 BAD_REQUEST 响应. 我们不必在自己的 Action 代码中再次检查, 我们可以安全的假定 request.body 中包含的是有效的 String 数据.

Unmi 注: 也就是如果用 parse.text 时,如果发送的请求未指定 Content-Type 为 "text/plain" 时,就会报 400 错,不管实际上发送的是字符串,而用 parse.toLerantText 就不会报错,强行按照 text/plain 来解析。

另外,我们也可以用:

Unmi 注: parse.toLerantText 方法的代码是:

这个方法不会检查 Content-Type 头,且总是把请求体加载为字符串.

小贴士: 在 Play 中所有的 Body 解析都提供有 tolerant 样式的方法.

这是另一个例子, 它将把请求存为一个文件:

组合 Body 解析器

在上一个例子中, 所有的请求体都会存到同一个文件去. 这会产生难以预料的问题,不是吗? 我们来写一个定制的 Body 解析器,它会从请求会话中抽取到用户名, 并以此作为种个用户的唯一文件名:

注: 这里我们并没有真正的书写自己的 BodyParser, 只不过是组合了现有的. 这样做通常足够了,能应付多数情况. 从头而写一个 BodyParser 那要在高级话题部份才会涉及到.

最大内容长度

基于文本的 Body 解析器 (如 text, json, xmlformUrlEncoded) 可使用最大内容长度,因为它们要加载所有内容到内存中.

有一个默认的内容长度 (默认为 100KB), 但是你也可以内联的指定它:

小贴士: 默认的内容大小可在 application.conf 中定义:
parsers.text.maxLength=128K

你还可以在任何的 Body 解析器中用 maxLength 来指定:

本文链接 https://yanbin.blog/play2-0-tutorials-cn-body-parsers/, 来自 隔叶黄莺 Yanbin Blog

[版权声明] Creative Commons License 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。

Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments