`

play2.x笔记 官网内容翻译+截取 第一章(完)

阅读更多

本文来自: http://fair-jm.iteye.com/ 转截请注明出处

 

又做了一些笔记 因为长时间没做play的应用现在都已经生疏了 

play官网的doc非常详细是非常好的学习教程 我想有了这个其他书都是浮云了

这是最近看的时候摘录的一些内容 翻译了一下 如果错误麻烦指正

第一章的内容还差两节 等有空 翻译之后会放上来 

 

1HTTP编程(HTTP programming)

 

1.1 Actions, Controllers and Results

SimpleResult

def index =Action{
 
SimpleResult(
    header
=ResponseHeader(200,Map(CONTENT_TYPE ->"text/plain")),
    body
=Enumerator("Hello world!".getBytes())
 
)
}

来自 <https://www.playframework.com/documentation/2.2.x/ScalaActions>

 

Status(状态码)(内容)

 

Action可以只使用TODO来表明还没被实现

def index(name:String)= TODO

 

来自 <https://www.playframework.com/documentation/2.2.x/ScalaActions>

 

 

1.2 HTTP routing

动态参数

:name的形式

对于要包含/的形式

*name

GET   /files/*name          controllers.Application.download(name)

 GET /files/images/logo.png,  name 会为images/logo.png

来自 <https://www.playframework.com/documentation/2.2.x/ScalaRouting>

对于正则用:

$id<[0-9]+>

 

对于后面的值

如果是固定的用 =

不是固定的而是一个默认值的话用 ?=

参数的类型可以不写不写当作String

或者写作Option表示可有可无

# Pagination links, like /clients?page=3
GET  
/clients              controllers.Clients.list(page:Int?=1)

来自 <https://www.playframework.com/documentation/2.2.x/ScalaRouting>

 

# Extract the page parameter from the path, or fix the value for /
GET  
/                     controllers.Application.show(page ="home")
GET  
/:page                controllers.Application.show(page)

 

来自 <https://www.playframework.com/documentation/2.2.x/ScalaRouting>

 

GET   /clients/:id          controllers.Clients.show(id:Long)

 

来自 <https://www.playframework.com/documentation/2.2.x/ScalaRouting>

 

# The version parameter is optional. E.g. /api/list-all?version=3.0
GET  
/api/list-all         controllers.Api.list(version:Option[String])

 

来自 <https://www.playframework.com/documentation/2.2.x/ScalaRouting>

 

优先级:

写在前面的会被最先匹配

(请把越具体的写在越前面)

 

路由反转:

routes包下

和实际的Action有相同的签名返回的是play.api.mvc.Call

 

 

1.3 Manipulating results

 

返回的结果会自动从指定的返回体的scala值中判断出来:

  • val textResult =Ok("Hello World!")

content-type会是 text/plain

  • val xmlResult = Ok(<message>Hello World!</message>)

content-type会是 application/xml

 

(通过 play.api.http.ContentTypeOf完成的)

可以使用 Resultas方法来指定content-type

val htmlResult = Ok(<h1>Hello World!</h1>).as("text/html")

或者:

val htmlResult2 = Ok(<h1>Hello World!</h1>).as(HTML)

 

 

操作HTTP头部

val result = Ok("Hello World!").withHeaders(
  CACHE_CONTROL -> "max-age=3600",
  ETAG -> "xx")

 

来自 <https://www.playframework.com/documentation/2.2.x/ScalaResults>

会自动把原先设置以及存在的值给去掉

 

 

设置和取消Cookie

val result3 = result.withCookies(Cookie("theme", "blue")).discardingCookies(DiscardingCookie("skin"))

 

 

改变基于文字的响应的字符集

默认是UTF-8

 

使用HTML的时候可以通过传入一个隐式的Codec来更改:

objectApplicationextendsController{
implicitval myCustomCharset =Codec.javaSupported("iso-8859-1")
def index =Action{
   
Ok(<h1>HelloWorld!</h1>).as(HTML)
 
}
}

HTML的实现:

def HTML(implicit codec: Codec) = {
  "text/html; charset=" + codec.charset
}

自己也可以实现

 

 

1.4 SessionFlash范围Session and Flash scopes

Session范围的数据对整个会话都是有效的

Flash的数据只对下一个请求有效

 

PlaySessionFlash都不是放在服务器端的他们是通过cookie机制加到每一个后续的Http请求中

只能存string的值只有4KB

默认的cookie名是PLAY_SESSION 可以在application.conf中配置 session.cookieName 改变名字

(也可以在上面的设置和取消Cookie里修改)

 

要存一些数据可以用Cache

 

session存储(Tuple2):

Ok("Welcome!").withSession(
 
"connected"->"user@gmail.com")

他会清空其他所有的值只会有connected这一个key

 

要避免清空用 + 增加 - 取消

Ok("Hello World!").withSession(
  request
.session +("saidHello"->"yes"))

Ok("Theme reset!").withSession(
  request
.session -"theme")

 

session读取:

request参数读取:

def index =Action{ request =>
  request
.session.get("connected").map { user =>
   
Ok("Hello "+ user)
 
}.getOrElse {
   
Unauthorized("Oops, you are not connected")
 
}

 

取消整个session:

Ok("Bye").withNewSession

 

 

1.5 Body解析

什么是Body解析

HTTPPUTPOST请求包含请求体。可以是任何的Content-Type类型

play中,一个body解析器将请求体转换到Scala类型。

 

一个请求体可能非常大并且一个body解析器不能一直等到全部数据载入内存中再解析

一个Body解析器本质是一个  Iteratee[Array[Byte],A]

接受字节块(chunks of bytes)

 

例子:

    • text body parser could accumulate chunks of bytes into a String, and give the computed String as result (Iteratee[Array[Byte],String]).
    • file body parser could store each chunk of bytes into a local file, and give a reference to the java.io.File as result (Iteratee[Array[Byte],File]).
    • s3 body parser could push each chunk of bytes to Amazon S3 and give a the S3 object id as result (Iteratee[Array[Byte],S3ObjectId]).

 

body解析器可以在解析请求体之前访问HTTP请求头,并可以运行一些前置检查。

 

所以 body解析不是真正的Iteratee[Array[Byte],A] 但更准确地是一个Iteratee[Array[Byte],Either[Result,A]]

当解析结束后会返回A,此时对应的Action会被执行。

 

 

更多关于Action的内容

traitAction[A]extends(Request[A]=>Result){
 
def parser:BodyParser[A]
}

 

Request的定义:

traitRequest[+A]extendsRequestHeader{
 
def body: A
}

 

A就是请求体的返回类型

总结一下,Action[A] 使用一个BodyParser[A]HTTP请求中得到类型为A的内容,并构建一个Request来执行Action的代码

 

默认的Body解析:AnyContent

如果我们不指定自己的Body解析器默认的是play.api.mvc.AnyContent的一个实例

这个Body解析器检查Content-Type头并且决定使用解析那种body:

      • text/plainString
      • application/jsonJsValue
      • application/xmltext/xml or application/XXX+xmlNodeSeq
      • application/form-url-encodedMap[String, Seq[String]]
      • multipart/form-dataMultipartFormData[TemporaryFile]
      • any other content type: RawBuffer

 

来自 <https://www.playframework.com/documentation/2.3.x/ScalaBodyParsers>

 

 

比如:

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")
 
}
}

 

 

指定一个body解析器:

可用的body解析器定义在play.api.mvc.BodyParsers.parse中

 

例如:

def save =Action(parse.text){ request =>
 
Ok("Got: "+ request.body)
}

 

如果内容不是text的那么就会返回400

或者我们可以使用:

 

def save =Action(parse.tolerantText){ request =>
 
Ok("Got: "+ request.body)
}

 

与上面不同他不会检查Content-Type并总是把请求体作为String载入

(所有的body解析器都有其对应的tolerant类型)

 

另一个将请求体存入文件的例子:

def save =Action(parse.file(to =newFile("/tmp/upload"))){ request =>
 
Ok("Saved the request content to "+ request.body)
}

 

 

组合body解析器

上面的例子中会将所有请求都放在一个相同的文件中不太合理这里有另一个按照用户名存文件的方式:

val storeInUserFile = parse.using { request =>
  request
.session.get("username").map { user =>
    file
(to =newFile("/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)
}

 

我们并没写自己的Body解析器只是单纯结合了现有的。从脚手架开始书写一个自定义解析器在高级章节中介绍。

 

 

最大内容长度

基于文本的body解析器(text,json,xmlformUrlEncoded)使用了一个最大内容长度因为他们必须将所有的内容载入内存

默认的大小是100KB你也可以指定:

// Accept only 10KB of data.
def save =Action(parse.text(maxLength =1024*10)){ request =>
 
Ok("Got: "+ text)
}

 

默认的内容大小定义在application.conf

parsers.text.maxLength=128K

更多的内容可以在Configuration找到

 

你也可以将任何的body解析器用maxLength包裹:

 

// Accept only 10KB of data.
def save =Action(parse.maxLength(1024*10, storeInUserFile)){ request =>
 
Ok("Saved the request content to "+ request.body)
}

 

 

来自 <https://www.playframework.com/documentation/2.3.x/ScalaBodyParsers

 

 

1.6 Action组合

这个章节介绍了几种定义通用action功能的方法.

 

自定义Action构造器

构建Action的方法实际上都定义在ActionBuilder这个特质里。

我们用来声明的actionAction对象都是这个特质的实例。

通过实现自己的ActionBuilder可以定义可复用的action栈用来构建actions

 

首先是实现invokeBlock方法:

import play.api.mvc._

objectLoggingActionextendsActionBuilder[Request]{
 
def invokeBlock[A](request:Request[A], block:(Request[A])=>Future[Result])={
   
Logger.info("Calling action")
    block
(request)
 
}
}

 

来自 <https://playframework.com/documentation/2.3.x/ScalaActionsComposition>

 

然后我们可以用和使用Action同样的方式来使用了:

def index =LoggingAction{
 
Ok("Hello World")
}

 

def submit =LoggingAction(parse.text){ request =>
 
Ok("Got a body "+ request.body.length +" bytes long")
}

 

组合Actions

在大多数的应用中,我们需要多个action构造器,一些用来做验证,一些用来提供不同类型的通用功能。

可重用的action可以用包裹actions来实现:

 

import play.api.mvc._

caseclassLogging[A](action:Action[A])extendsAction[A]{

def apply(request:Request[A]):Future[Result]={
   
Logger.info("Calling action")
    action
(request)
 
}

lazyval parser = action.parser
}

或者可以这样避免定义一个类:

import play.api.mvc._

def logging[A](action:Action[A])=Action.async(action.parser){ request =>
 
Logger.info("Calling action")
  action
(request)
}

Actions可以通过composeAction方法混入action构造器:

objectLoggingActionextendsActionBuilder[Request]{
 
def invokeBlock[A](request:Request[A], block:(Request[A])=>Future[Result])={
    block
(request)
 
}
 
overridedef composeAction[A](action:Action[A])=newLogging(action)
}

调用和原来一样:

 

def index =LoggingAction{
 
Ok("Hello World")
}

 

或者不用Action构造器的方式:

def index =Logging{
 
Action{
   
Ok("Hello World")
 
}
}

更复杂的action

上面的action并不影响action,当然我们也可以修改传入的request对象

import play.api.mvc._

def xForwardedFor[A](action:Action[A])=Action.async(action.parser){ request =>
 
val newRequest = request.headers.get("X-Forwarded-For").map { xff =>
   
newWrappedRequest[A](request){
     
overridedef remoteAddress = xff
   
}
 
} getOrElse request
  action
(newRequest)
}

:play已经有内置的对于X-Forwarded-For头的支持

 

我们可以截获这个请求:

import play.api.mvc._
def onlyHttps[A](action:Action[A])=Action.async(action.parser){ request =>
  request
.headers.get("X-Forwarded-Proto").collect {
   
case"https"=> action(request)
 
} getOrElse {
   
Future.successful(Forbidden("Only HTTPS requests allowed"))
 
}
}

 

或者修改返回的结果:

import play.api.mvc._
import play.api.libs.concurrent.Execution.Implicits._

def addUaHeader[A](action:Action[A])=Action.async(action.parser){ request =>
  action
(request).map(_.withHeaders("X-UA-Compatible"->"Chrome=1"))
}

 

不同的请求类型

action组合允许执行一些在HTTP请求和回应等级的额外处理

常常也会需要构建一些对请求本身的例如添加上下文或进行验证的数据转换管道

ActionFunction 

每一个function都表示一个模块化的处理

比如验证权限检查等你希望在action间组合和重用的功能

 

几个预定义的ActionFunction:

  • ActionTransformer can change the request, for example by adding additional information.
  • ActionFilter can selectively intercept requests, for example to produce errors, without changing the request value.
  • ActionRefiner is the general case of both of the above.
  • ActionBuilder is the special case of functions that take Request as input, and thus can build actions.

 

也可以实现ActionFunction定义自己的抽象。

 

验证

import play.api.mvc._

classUserRequest[A](val username:Option[String], request:Request[A])extendsWrappedRequest[A](request)

objectUserActionextends
   
ActionBuilder[UserRequest]withActionTransformer[Request,UserRequest]{
 
def transform[A](request:Request[A])=Future.successful {
   
newUserRequest(request.session.get("username"), request)
 
}
}

 

增加额外信息

比如/item/:itemId每一个在这个路径下的routes

都需要先查询拿到Item

先定义一个新的类似用来存放ItemUserRequest(上面的)

import play.api.mvc._

classItemRequest[A](val item:Item, request:UserRequest[A])extendsWrappedRequest[A](request){
 
def username = request.username
}

返回一个Either返回错误是Left 一个新的ItemRequestRight

defItemAction(itemId:String)=newActionRefiner[UserRequest,ItemRequest]{
 
def refine[A](input:UserRequest[A])=Future.successful {
   
ItemDao.findById(itemId)
     
.map(newItemRequest(_, input))
     
.toRight(NotFound)
 
}
}

OptiontoRight 是如果Option不为空就返回Right 为空就以Left返回NotFound

 

验证请求

objectPermissionCheckActionextendsActionFilter[ItemRequest]{
 
def filter[A](input:ItemRequest[A])=Future.successful {
   
if(!input.item.accessibleByUser(input.username))
     
Some(Forbidden)
   
else
     
None
 
}
}

 

结合起来

可以将这些功能(ActionBuilder开头)使用andThen组合起来创建一个Action:

 

def tagItem(itemId:String, tag:String)=
 
(UserAction andThen ItemAction(itemId) andThen PermissionCheckAction){ request =>
    request
.item.addTag(tag)
   
Ok("User "+ request.username +" tagged "+ request.item.id)
 
}

 

1.7 内容协商Content negotiation

内容协商是对同一个资源(URI)返回不同表示方式的机制。

比如,在web service中,同一个资源可以输出多种格式(XMLJSON等)。

服务端驱动的协商通过Accept*头来执行。(http://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html)

 

语言

可接受的语言使用

play.api.mvc.RequestHeader#acceptLanguages

来从

Accept-Language

头中获取值

 

排序按照quality factor(q参数)

 

 

play

play.api.mvc.Controller#lang

中使用这个头并提供一个隐式的

 play.api.i18n.Lang

到你的Action

自动地使用最合适的语言(如果你的应用支持,不然使用默认的语言)

 

内容

play.api.mvc.RequestHeader#acceptedTypes

返回请求的MIME类型列表从请求的Accept头中获取

 

排序按照quality factor(q参数)

 

Accept头可能不包含具体的MIME类型而是一个媒体范围(比如 text/* */*)

controller提供了高阶的render方法来帮助处理:

 

val list =Action{implicit request =>
 
val items =Item.findAll
  render
{
   
caseAccepts.Html()=>Ok(views.html.list(items))
   
caseAccepts.Json()=>Ok(Json.toJson(items))
 
}
}

Render方法的参数是一个偏函数

play.api.http.MediaRange =>play.api.mvc.Result

 

 

如果没有符合的会返回

NotAcceptable 

 

请求抽取器(Request extractors

可以查看

play.api.mvc.AcceptExtractors.Accepts 

API文档去查看在render方法中play内置支持的MIME类型

 

也可以用

play.api.mvc.Accepting样本类来创建自定义MIME类型抽取器

 

以下的代码就是创建一个自定义的audio/MP3 MIME类型的抽取器:

 

  valAcceptsMp3=Accepting("audio/mp3")
  render
{
   
caseAcceptsMp3()=>???
 
}
}

 

本文来自: http://fair-jm.iteye.com/ 转截请注明出处

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics