Controllers
1 |
|
在 conf
目录下配置 routes
文件,对请求url 进行映射到 controllers
,和 springmvc
的 @RequestMapping
很类似。
1 | # Routes |
通过上述配置之后,我们就可以通过 url 访问到具体的 请求方法了。
Action
每一个请求是被一个 Action 进行处理了,处理之后返回 Results
Results
常见的 results
1 | Ok("Got request [" + request + "]") |
重定向到另一个 url
对应的 Action
1 | Redirect("/echo") |
mark
方法还没有完成
1 | def todo() = TODO |
自定义 Result
1 | Result( |
Http Routing
所有路由信息将定义在
conf/routes
文件下,前文有提及到。router
是负责将每个传入HTTP
请求转换为Action
的组件。
每一个 Http
请求被 Play MVC Framework 认为是一个事件。每个请求包含两条主要信息:
- 请求路径,restful 风格
- http 请求方法,类似 Get、Post、Delete、Put 等
语法
conf/routes
是 router
使用的配置文件。该文件列出了应用程序所需的所有 routes
。每个路由由一个 HTTP
方法和 URI
模式组成,它们都与 Action
的调用相关联。
让我们看看路由定义是什么样的:
1 | GET /clients/:id controllers.Clients.show(id: Long) |
通过 ->
来使用不同的路由规则
1 | /api api.MyRouter |
当与字符串插值路由DSL(也称为SIRD路由)结合使用时,或者在处理使用多个路由文件路由的子项目时,这一点尤其有用。
通过 nocsrf
来禁用 CSRF filter
1 | nocsrf |
URL规则:
1 | #静态 path |
逆向、反转 routing
1 | Redirect(routes.HelloController.echo()) |
操作处理结果返回
结果内容类型自动从您指定作为响应体的Scala值推断出来。
通过 play.api.http.ContentTypeOf
来实现
1 | //Will automatically set the Content-Type header to text/plain, while: |
Manipulating Http headers (操纵 Http 头)
1 | //添加返回头信息 |
Session and Flash
存储在 session 中的数据在整个会话期间都是可用的,存储在 flash 作用域的数据只对下一个请求可用。
需要注意,session 和 flash 的数据不是由服务器存储的,而是使用 cookie 机制添加到每个后续 http 请求中的。这意味着数据大小将非常有限(up to 4kb),并且只能存储字符串值。 cookie 的默认名称是
PLAY_SESSION
。这可以在application.conf
通过配置 keyplay.http.session
来更改。
Session 存储
1 | //这会将 session 完全替换掉 |
读取 Session
内容
1 | def index = Action { request => |
丢弃整个 Session
内容
1 | Ok("Bye").withNewSession |
Flash Scope (Flash
作用域)
flash scope 和 session 作用很像,但是有两个区别
- data are kept for only one request
- the Flash cookie is not signed, making it possible for the user to modify it.
session 的 cookie 会进行加密,而 flash 的 cookie 不会进行加密。
Flash作用域
应该只用于在简单的 非ajax
应用程序上传输 成功/错误消息。由于数据只是为下一个请求保存的,而且在复杂的 Web
应用程序中不能保证请求顺序,所以 Flash作用域
受竞态条件的限制。(the Flash scope is subject to race conditions.)
code using flash scope
1 | def index = Action { implicit request => |
To retrieve the Flash scope value in your view, add an implicit Flash parameter
要在视图中检索Flash作用域值,请添加一个隐式Flash参数:
1 | @()(implicit flash: Flash) |
请求体解析 Body Parsers
什么是 body parsers
HTTP请求
是header 后面跟着 body。header 通常很小——它可以安全地缓冲在内存中,因此在 Play
中它是使用RequestHeader
类建模的。
然而,body
可能非常长,因此不在内存中缓冲,而是建模为流。然而,许多请求体有效负载 (payloads
) 都很小,并且可以在内存中建模,因此为了将 body流
映射到内存中的对象,Play
提供了一个BodyParser
抽象。
由于 Play
是一个异步框架,传统的 InputStream
不能用于读取请求体——输入流阻塞了,当您调用 read
时,调用它的线程必须等待数据可用。
相反,Play
使用一个名为 Akka Streams
的异步流库。Akka Streams 是 Reactive Streams 的一个实现。
允许许多异步流api 无缝地协同工作,所以尽管传统 InputStream
的基础技术不适合使用, 但是Akka Streams
和 Reactive Streams
的整个生态系统的异步库将为你提供你需要的一切。
使用 Body Parsers
如果没有显式选择
body parser
,Play
将使用的缺省的body parser
将查看传入的Content-Type
,并相应地解析body。例如,类型
application/json
的内容类型将被解析为JsValue
,而类型application/x-www-form- urlencoding
的内容类型将被解析为Map[String, Seq[String]]
默认的 Body Parser
生成 AnyContent
类型的 Body
。AnyContent
支持的各种类型, 可以通过 as方法
访问,例如 asJson
,它返回一个 Option[body类型]
1 | def save = Action { request: Request[AnyContent] => |
下面是默认 body parser
支持的类型映射 (The following is a mapping of types supported by the default body parser
)
- text/plain:
String
, accessible viaasText
. - application/json:
JsValue
, accessible viaasJson
. - application/xml, text/xml or application/XXX+xml:
scala.xml.NodeSeq
, accessible viaasXml
. - application/x-www-form-urlencoded:
Map[String, Seq[String]]
, accessible viaasFormUrlEncoded
. - multipart/form-data:
MultipartFormData
, accessible viaasMultipartFormData
. - Any other content type:
RawBuffer
, accessible viaasRaw
.
默认的 body parser 在解析之前会 try to determine
请求是否具有 body
。
根据 HTTP
规范(spec),内容长度(Content-Length) 或 传输编码标头(Transfer-Encoding) 的出现都表示主体的存在,因此解析器只在出现其中一个标头时进行解析,或者在显式设置非空主体时在 FakeRequest 上进行解析。
如果希望在所有情况下解析主体,可以使用下面描述的anyContent主体解析器。
显示的选择一个 Body Parser
如果希望显式地选择主体解析器,可以将 body parser 传递给 Action 的 apply或 async 方法。
Play 提供了许多开箱即用的 body parser (Play provides a number of body parsers out of the box
),
这是通过 PlayBodyParsers
trait 提供的,它可以注入到您的控制器中。
例子,如果要定义一个 request body 期望是 Json Body 的 Action
1 | //如果不是 Json类型会返回 415 Unsupported Media Type , 类似 Springmvc 在参数前面加 @RequestBody |
注意,上述 body 的类型为 JsValue
, 它不是 Option
类型的了。原因是 json body parser 会验证请求是否具有 application/json
的 Content-Type
,如果不是,则会返回 415 的错误,即 415 Unsupported Media Type
,这样我们就不用再次检查了。
这样依赖,这个方法将要对请求的type 有严格的限制了,客户端要清楚这一点。如果希望有更宽松的做法,即不是 ``application/json类型的
Content-Type` 也能够进行解析,可以使用如下方法:
1 | //不会返回 415,会尝试进行解析 |
另一个例子,保存文件
1 | def save = Action(parse.file(to = new File("/tmp/upload"))) { request: Request[File] => |
组合 Body Parsers (Combining body parsers)
在上一个保存文件的示例中,所有请求 bodies 都存储在同一个文件中 (/tmp/upload
),这是存在问题的。
我们可以编写另一个自定义主体解析器,它从请求会话中提取用户名,为每个用户提供一个惟一的文件
1 | val storeInUserFile: BodyParser[File] = parse.using { request => |
上面我们并没有真正编写自己的 BodyParser
,而只是组合现有的 BodyParser
。这通常就足够了,应该涵盖大多数用例。高级主题部分将介绍从头编写 BodyParser
Max content length
基于文本的 body parsers (例如 text、json、xml 或 formurlencoding) 需要有一个最大内容长度。因为它们必须将所有内容加载到内存中。默认情况下,它们将解析的最大内容长度是 100KB
。可以通过指定 play.http.parser
来覆盖它。可以通过在 application.conf
指定 maxMemoryBuffer
属性来改变这个大小。
1 | # application.conf |
对于将内容缓冲 (buffer
) 到磁盘上的 body parser,例如 raw parser
或 multipart/form-data
等,它们的最大内容长度使用 play.http.parser
的 maxDiskBuffer
来进行指定。
maxDiskBuffer
属性,默认值为10MB
。multipart/form-data
解析器还强制为数据字段的聚合设置文本最大长度属性。
您还可以通过在 Action
中显示指定来覆盖默认的最大长度。
1 | // Accept only 10KB of data. |
You can also wrap any body parser with maxLength
1 | // Accept only 10KB of data. |
Writing a custom body parser
我们可以实现
BodyParser
特质来自定义 body parser,这个 trait 是一个很简单的函数
1 | trait BodyParser[+A] extends (RequestHeader => Accumulator[ByteString, Either[Result, A]]) |
接收一个
RequestHeader
,这样我们可以检查关于请求的信息,它通常用于获取 Content-Type,这样 body 就会被正确的解析。函数的返回类型是
Accumulator
,它是一个累加器。Accumulator
是 围绕Akka Streams Sink
的一层薄层。Accumulator
异步地将streams of elements
加到结果中,它可以通过传入Akka Streams Source
来运行。这将返回一个Future
,并且在累加器完成时,future
得到完成。Accumulator
本质上和 Sink[E,Future[A]] 一样,事实上,Accumulator 是 Sink 的一个包装器。但是最大的区别是 Accumulator 提供了方便的方法,比如 map、mapFuture、recover 等等。Accumulator
用于将结果作为一个promise
来处理,其中 Sink 要求将所有的操作包装在 mapMaterializedValue 的调用中。Accumulator 的 apply 返回 ByteString 类型的元素,这些元素本质上是字节数组,但与 byte[] 不同的是,
ByteString
是不可变的,它的许多操作(如 slicing 或者 appending) 都是在固定的时间内完成的。
Accumulator
的返回类型是 Either[Result, A]
,它要么返回一个 Result
,要么返回一个类型 A
。通常在发生错误的情况下返回 Result
。例如,如果 body 解析失败,比如 Content-Type 与 Body Parser 接受的类型不匹配,或者内存缓冲区溢出了。当 body parser
返回一个 result
时,这将缩短 action
的处理时间,action
的结果将里脊返回,并且永远不会调用该操作。
将 Body 引向别处 Directing the body elsewhere
编写 body parser 的一个常见用例是,当您实际上不想解析主体时,而是想将它以流的形式引入到其他地方(
stream it elsewhere
)。为此,您可以定义一个自定义 body parser
1 | import javax.inject._ |
使用 Akka Streams 进行自定义的解析
Custom parsing using Akka Streams
在很少的情况下,可能需要使用 Akka Streams
编写自定义解析器。在大多数情况下,先用 ByteString
缓冲 body 就足够了,这通常提供一种简单得多的解析方法,因为您可以对主体使用强制方法和随机访问。
但是,当这不可行时,例如需要解析的主体太长而无法装入内存时,则可能需要编写自定义主体解析器。
关于如何使用 Akka Streams
的完整描述超出了本文档的范围——最好从阅读 Akka Streams
文档开始。但是,下面显示了一个 CSV
解析器,它基于Akka Streams
烹饪书中 ByteStrings
文档流的解析行。
1 | import play.api.mvc.BodyParser |
原文链接: https://hzways.gitee.io/p/468029a2.html
版权声明: 转载请注明出处.