内容简介:Body parsers[翻译]
什么是Body解析器?
一个HTTP请求是一个HTTP头后面跟着Body,这个头通常比较小——它可以安全的缓存在内存中,因此在Play中使用RequestHeader 类模仿头。而Body有可能很长,因此不能在内存中缓存,而是使用一个流来模仿。
但是,许多请求Body的有效负载比较小,可以缓存到内存中,因此在缓存中映射Body流到一个对象,Play提供了一个 BodyParser 抽象。
由于Paly是一个异步框架,因此传统的 InputStream不能被用来读Body请求——当你调用read时,输入流会被阻塞掉,线程要调用它就必须等到数据可用时。作为替代,Play使用了一个叫做 Akka Streams的异步流库。
Akka Streams是 Reactive Streams的实现,Reactive Streams是一个允许许多异步流API无缝地一起工作的SPI,因此尽管一般的基于基础技术的 InputStream不合适在Play中使用,但是Akka Streams和Reactive Streams相关的整个异步库生态系统将提供给你任何你想要的。
更多关于Actions
前面我们说过,一个Action是一个Request => Result函数。这不完全是对的。让我们更精确的看一看Action的特质:
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类型是请求Body的类型。只要我们有一个Body解析器能处理这个类型 ,我们就可以使用任何Scala的类型做为请求Body,例如,String, NodeSeq, Array[Byte], JsonValue,或者java.io.File。
总结,Action[A] 使用 BodyParser[A] 从HTTP请求中获取类型A的值,构建一个可以传入到Action代码的Request[A]对象。
使用内置的Body解析器
大多数通常的网络App不需要自定义Body解析器,他们可以简单的使用Play的内置Body解析器。这些解析器包括JSON,XML,表单,以及处理纯文本字符串和byteBody的ByteString.
如果你没有明确的选择Boay解析器它会使用默认的Body解析器,这个默认解析器将在传入的Content-Type头中看到,并依此解析Body。所以例如, application/json 类型的 Content-Type将被做为 JsValue解析,而 application/x-www-form-urlencoded类型的Content-Type将被做为 Map[String, Seq[String]]解析。默认的Body解析器产生一个AnyContent类型的Body。 AnyContent支持的各种类型可以通过as方法返回一个Option类型的Body,如asJson。
def save = Action { request => val body: AnyContent = request.body val jsonBody: Option[JsValue] = body.asJson // Expecting json body jsonBody.map { json => Ok("Got: " + (json \ "name").as[String]) }.getOrElse { BadRequest("Expecting application/json request body") } }
下来是默认的Body解析器支持的映射类型:
- text/plain: String,通过asText获得。
- application/json:JsValue,通过asJson.获得。
- application/xml, text/xml or application/XXX+xml: scala.xml.NodeSeq, 通过asXml获得。
- application/x-www-form-urlencoded: Map[String, Seq[String]],通过 asFormUrlEncoded得到。
- multipart/form-data: MultipartFormData, 通过asMultipartFormData得到
- 其他的content type RawBuffer, 通过asRaw得到。
由于性能原因,如果请求方法没有被定义为一个由HTTP规范定义的有意义的Body,那么默认的Body解析器就不会尝试去解析。也就是说它仅解析POST, PUT 和PATCH请求的Body,而不是GET, HEAD 或者DELETE。如果你想要解析这些方法的请求Body,你可以使用下面将讲的anyContentBody解析器。
选择一个明确的Body解析器
如果你想明确的选择一个Body解析器,这可以通过传递Body解析器到Action 的apply 或者 async 方法。 Play提供了一些可通过BodyParsers.parse对象获得的现成的Body解析器, 这个对象可以通过Controller特质方便的获得.因此,例如,要定义一个想要JSON Body的Action(就像前面的例子):
def save = Action(parse.json) { request => Ok("Got: " + (request.body \ "name").as[String]) }
注意这次Body的类型是JsValue, 由于它不是Option类型,因此这就让Body更容易的被处理。它不是 Option 类型的原因是因为请求有application/json的 Content-Type因此Json Body解析器会生效,如果请求没有匹配到期望的类型,就会返回415 Unsupported Media Type的应答。因此我就不用再次检查我们的Action代码。
当然,也就是说,客户端必须规范,在他们的请求中发送正确的Content-Type头。如果你想轻松一点,你可以使用忽略Content-Type的tolerantJson,并将其强制解析为Json:
def save = Action(parse.tolerantJson) { request => Ok("Got: " + (request.body \ "name").as[String]) }
这里是另一个将请求Body存储进文件的例子:
def save = Action(parse.file(to = new File("/tmp/upload"))) { request => Ok("Saved the request content to " + request.body) }
合成Body解析器
在前面的例子中:所有的请求Body都被存入相同的文件中,这是不是有点小问题?让我们再写一个可以从请求的Session中提取用户名的自定义Body解析器:
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) }
注意:这里不是真正的写一个属于我们自己的BodyParser,而是和已经存在的合成一个。这在大多数情况下是满足使用的。在高级专题部分讲到从零开始写BodyParser.
最大的内容长度
由于他们必须把所有的内容加载进内存,所以基于文本的Body分析器(如text, json, xml 或 formUrlEncoded)有最大的内容长度限制。默认情况下,被解析的最大的内容长度是 100KB。它可以通过在 pplication.conf文件的
play.http.parser.maxMemoryBuffer=128K
对于那些在磁盘上缓存内容的解析器,如用属性play.http.parser.maxDiskBuffer指定原生解析器或者 multipart/form-data的最大内容长度,默认是10MB。 multipart/form-data解析器也强迫文本的最大长度属性为所有数据字段的和.
你也可以使用 maxLength设置任何Body解析器:
// Accept only 10KB of data. def save = Action(parse.maxLength(1024 * 10, storeInUserFile)) { request => Ok("Saved the request content to " + request.body) }
写一个自定义的Body解析器
一个自定义的Body解析器可以通过实现 BodyParser 特质完成。这个特质是一个简单的函数:
trait BodyParser[+A] extends (RequestHeader => Accumulator[ByteString, Either[Result, A]])
这个函数的签名开始时可能有点令人望而生畏,因此让我们来打破这点。函数接受一个 RequestHeader。这可以用来检查相关最常见的请求信息,也可以用来获取Content-Type,因此Body可以被正确的解析。函数的返回类型是一个 Accumulator。 Accumulator是 Akka Streams Sink的薄层。Accumulator 异步的把元素流累积到结果中,它可以通过传入Akka Streams Source运行,当收集器完成时这将返回一个赎回的Future 。它和 Sink[E, Future[A]]本质上是相同的东西,事实上,它只是包装这个类型的包装器,但是最大的不同是Accumulator 提供了如map, mapFuture, recover 等等便利的方法。为了使用结果,Sink要求所有的操作被封装在 mapMaterializedValue里调用。
收集器的 apply 方法返回一个ByteString 类型的consumes(consumes: 指定处理请求的提交内容类型(Content-Type),例如application/json, text/html;)元素——这本质上是Byte数组,但是和 byte[] 的不同之处是 ByteString是不可改变的,并且许多操作如切片和附加在不间断的时间内执行。收集器的返回类型是 Either[Result, A] ,他要么返回Result,要么返回方法A类型的Body。一般在错误的情况下返回A结果,例如,如果Body解析失败,如果 Content-Type不匹配Body解析器接受的类型,又或者如果超出内存缓冲区。当Body解析器返回一个结果时,这将短路Action的执行——Body解析器结果会被立即返回,并Action将永远不会被触发。
把Body跳转到别处
写一个Body解析器的常见情况是当你不想解析Body时,相反的,你想把它分流到别处。为了做的这个,你可以定义一个自定义的Body解析器。
import javax.inject._ import play.api.mvc._ import play.api.libs.streams._ import play.api.libs.ws._ import scala.concurrent.ExecutionContext import akka.util.ByteString class MyController @Inject() (ws: WSClient)(implicit ec: ExecutionContext) { def forward(request: WSRequest): BodyParser[WSResponse] = BodyParser { req => Accumulator.source[ByteString].mapFuture { source => request // TODO: stream body when support is implemented // .withBody(source) .execute() .map(Right.apply) } } def myAction = Action(forward(ws.url("https://example.com"))) { req => Ok("Uploaded") } }
使用Akka Streams自定义解析器
在极少的情况下,需要使用 Akka Streams写一个自定义的解析器。在大多数情况下,先满足把Body缓存到 ByteString,由于你对Body使用了命令方法和随机访问 , 这通常会提供一种简单的解析方式。然而,当那不可行时,例如,当你需要解析的Body太长而不能放入缓存时,那么你需要写一个自定义的Body解析器。
怎么使用 Akka Streams 的完整描述超出了本文档的范围——最好是从阅读 Akka Streams 文档 开始。不管怎样,下面介绍CSV解析器,它基于 Akka Streams cookbook中的文档 Parsing lines from a stream of ByteStrings
import play.api.mvc._ import play.api.libs.streams._ import play.api.libs.concurrent.Execution.Implicits.defaultContext import akka.util.ByteString import akka.stream.scaladsl._ val csv: BodyParser[Seq[Seq[String]]] = BodyParser { req => // A flow that splits the stream into CSV lines val sink: Sink[ByteString, Future[Seq[Seq[String]]]] = Flow[ByteString] // We split by the new line character, allowing a maximum of 1000 characters per line .via(Framing.delimiter(ByteString("\n"), 1000, allowTruncation = true)) // Turn each line to a String and split it by commas .map(_.utf8String.trim.split(",").toSeq) // Now we fold it into a list .toMat(Sink.fold(Seq.empty[Seq[String]])(_ :+ _))(Keep.right) // Convert the body to a Right either Accumulator(sink).map(Right.apply) }
原文: Body parsers
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 基于 Laravel、Lumen 框架集成百度翻译、有道翻译、Google 翻译扩展包
- 腾讯发布人工智能辅助翻译 致敬人工翻译
- golang调用baidu翻译api实现自动翻译
- 监管机器翻译质量?且看阿里如何搭建翻译质量评估模型
- 机器翻译新突破:谷歌实现完全基于attention的翻译架构
- PendingIntent 是个啥?官方文档描述的很到位。我给翻译翻译
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
网络、群体与市场
大卫·伊斯利(David Esley)、乔恩·克莱因伯格(Jon Kleinberg) / 李晓明、王卫红、杨韫利 / 清华大学出版社 / 2011-10-1 / CNY 69.00
过去十年来,现代社会中复杂的连通性向公众展现出与日俱增的魅力。这种连通性在许多方面都有体现并发挥着强大的作用,包括互联网的快速成长、全球通信的便捷,以及新闻与信息(及传染病与金融危机)以惊人的速度与强度传播的能力。这种现象涉及网络、动机和人们的聚合行为。网络将人们的行为联系起来,使得每个人的决定可能对他人产生微妙的后果。 本书是本科生的入门教材,同时也适合希望进入相关领域的高层次读者。它从交......一起来看看 《网络、群体与市场》 这本书的介绍吧!
XML、JSON 在线转换
在线XML、JSON转换工具
Markdown 在线编辑器
Markdown 在线编辑器