什么是请求主体解析器
通常HTTP的PUT和POST请求都包含一个主体(body)。该主体可以是任意格式的,并且通过‘Content-Type‘定义其具体格式。在Play中,请求主体解析器将这些请求的主题内容转换为对应的Scala可以直接读取/操作的值。
虽然,HTTP请求的主体可以是非常庞大的,并且请求主体解析器通常也不能一直等待并将其载入到系统内存中。BodyParser[A]实际上是基于Iteratee[Array[Byte],A]的,这就意味着可以获取大量字节码并计算其对应的A类型的Scala值。
考虑以下几个例子:
- 文本的请求主体解析器可以将字节码转换成为String,并且将该值作为Iteratee[Array[Byte],A]结果返回;
- 文件的请求主体解析器可以存储文本的各个部分至本地,并且返回一个作为Iteratee[Array[Byte],File]结果的java.io.File类型的文本引用;
- 一个S3的请求主体解析器可以将请求主体的内容推送至Amazon S3服务器,并且返回一个作为Iteratee[Array[Byte],S3ObjectId]结果的S3服务器Id对象。
作为请求主体解析器的附加功能,它也可以用来在解析请求实体之前访问HTTP请求头,并且适时的进行一些预检查。例如:实体解析器能够检查某些HTTP头并进行适当的集合操作。
其实请求主体解析器并不是真正意义上的Iteratee[Array[Byte],A],而是Iteratee[Array[Byte],Either[Result,A]],因此,请求主体解析器在它无法正确的计算出正确的请求值时,它具有直接传递HTTP请求结果的职责(例如,返回400 BAD_REQUEST,412 PRECONDITION_FAILED或者413 REQUEST_ENTITY_TOO_LARGE)。
一旦请求主体解析器完成了它的工作并且返回了对应A类型的值,对应的Action方法就根据解析出的HTTP请求中的值计算出相应的结果。
更多Action的内容
在之前提到Action是一个Request=>Result的方法。这样说其实并不完全正确。我们来仔细审视以下Action trait:
trait Action[A] extends (Request[A] => Result) {
def parser: BodyParser[A]
}
A为泛化类型,Action特质中必须定义一个BodyParser[A]。对于Request[A]是这样定义的:
trait Request[+A] extends RequestHeader {
def body: A
}
A为请求主体的类型。请求主体的类型可以是任意的Scala类型,例如:String,NodeSeq,Array[Byte],JsonValue或者java.io.Fil,只要是请求主体解析器能够处理的类型都可以。
综上所述,Action[A]使用了BodyParser[A]来从HTTP请求中获取类型A的值,并且构建一个Request[A]对象有Action方法进行处理。
定义请求实体解析器 parser: AnyContent
在以上的例子中,没有特例化请求实体解析器。那么默认情况下它是如何工作的?默认情况下Play会获取一个play.api.mvc.AnyContent的实例。
该请求主体解析器会检查Content-Type请求头并且确定所处理的请求主体的类型:
- Text/plain——String
- Application/json——JsValue
- application/xml, text/xml or application/XXX+xml——NodeSeq
- application/form-url-encoded——Map[String, Seq[String]]
- multipart/form-data——MultipartFormData[TemporaryFile]
- any other content typ——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")
}
}
特别指定请求主体解析器
Play定义的请求主体解析器在play.api.mvc.BodyParsers.parse中。例如,定义某个Action来处理某个文本主体:
def save = Action(parse.text) { request =>
Ok("Got: " + request.body)
}
这段代码看似简单。其实一旦出现了某些错误,通常会得到一个400 BAD_REQUEST。我们没有必要在Action里定义检查的代码,我们可以假定request.body包含了String的内容实体。
我们也可以通过以下方式处理:
def save = Action(parse.tolerantText) { request =>
Ok("Got: " + request.body)
}
以上代码不会检测Content-Type中的内容,并直接使用Request的主体内容为String类型。
> tolerant就是提供对应各种实体解析方式的方法
以下是用于文件存储的实例:
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 {
sys.error("You don't have the right to upload here")
}
}
def save = Action(storeInUserFile) { request =>
Ok("Saved the request content to " + request.body)
}
此处我们并没有编写特定的主体解析器,仅仅是结合了已有的解析器,让它们协同完成工作。通常情况下,这种做法是足以应付绝大多数的用例的。
实体内容长度限制
基于文本的请求主体解析器(例如 text,json,xml或者formUrlEncoded)通常会对文本长度有最长长度的限定,这样做的目的在于使得该内容可以全部载入到内存中。
文本内容的最大长度在Play中默认为100KB,这个值用户可以通过以下方式修改:
// Accept only 10KB of data.
def save = Action(parse.text(maxLength = 1024 * 10)) { request =>
Ok("Got: " + text)
}