Play 2 WEB开发

1、HTTP编程(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)

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

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

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

优先级:
写在前面的会被最先匹配(请把越具体的写在越前面)

路由反转:
在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")

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

设置和取消Cookie

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

改变基于文字的响应的字符集
默认是UTF-8

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

    object Application extends Controller {
    implicit val myCustomCharset = Codec.javaSupported("iso-8859-1")
    def index = Action {
        Ok(<h1>Hello World!</h1>).as(HTML)
      }
    }

HTML的实现:

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

自己也可以实现

1.4 Session和Flash范围 Session and Flash scopes

Session范围的数据对整个会话都是有效的
而Flash的数据只对下一个请求有效

Play中Session和Flash 都不是放在服务器端的 他们是通过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解析

HTTP的PUT和POST请求包含请求体。可以是任何的Content-Type类型
在play中,一个body解析器将请求体转换到Scala类型。

一个请求体可能非常大并且一个body解析器不能一直等到全部数据载入内存中再解析
一个Body解析器本质是一个  Iteratee[Array[Byte],A]接受字节块(chunks of bytes)

例子:
• A text body parser could accumulate chunks of bytes into a String, and give the computed String as result (Iteratee[Array[Byte],String]).
• A 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]).
• A 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的内容

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

Request的定义:

    trait Request[+A] extends RequestHeader {
      def body: A
    }

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

默认的Body解析:AnyContent

如果我们不指定自己的Body解析器 默认的是play.api.mvc.AnyContent的一个实例
这个Body解析器检查Content-Type头并且决定怎么解析body:

  • 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 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 = new File("/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 = 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)
    }

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

最大内容长度

基于文本的body解析器(text,json,xml或formUrlEncoded)使用了一个最大内容长度 因为他们必须将所有的内容载入内存默认的大小是100KB 你也可以指定:

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

默认的内容大小定义在application.confparsers.text.maxLength=128K

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

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

1.6 Action组合

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

自定义Action构造器

构建Action的方法实际上都定义在 ActionBuilder 这个特质里。我们用来声明的action的Action对象都是这个特质的实例。通过实现自己的ActionBuilder 可以定义可复用的action栈用来构建actions。

首先是实现invokeBlock方法:

    import play.api.mvc._
    object LoggingAction extends ActionBuilder[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._
case class Logging[A](action: Action[A]) extends Action[A] {
def apply(request: Request[A]): Future[Result] = {
    Logger.info("Calling action")
    action(request)
  }
lazy val 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构造器:

object LoggingAction extends ActionBuilder[Request] {
  def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = {
    block(request)
  }
  override def composeAction[A](action: Action[A]) = new Logging(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 =>
    new WrappedRequest[A](request) {
      override def 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._
class UserRequest[A](val username: Option[String], request: Request[A]) extends WrappedRequest[A](request)
object UserAction extends
    ActionBuilder[UserRequest] with ActionTransformer[Request, UserRequest] {
  def transform[A](request: Request[A]) = Future.successful {
    new UserRequest(request.session.get("username"), request)
  }
}
增加额外信息

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

都需要先查询拿到Item

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

import play.api.mvc._
class ItemRequest[A](val item: Item, request: UserRequest[A]) extends WrappedRequest[A](request) {
  def username = request.username
}

返回一个Either Left表示返回错误(这里就是一个result) Right标识返回ItemRequest

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

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

验证请求
object PermissionCheckAction extends ActionFilter[ItemRequest] {
  def filter[A](input: ItemRequest[A]) = Future.successful {
    if (!input.item.accessibleByUser(input.username))
      Some(Forbidden)
    else
      None
  }
}

None表示继续,Some表示立即返回

结合起来

可以将这些功能(以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中,同一个资源可以输出多种格式(XML,JSON等)。服务端驱动的协商通过Accept*头来执行。(http://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html)

语言

可接受的语言使用 play.api.mvc.RequestHeader#acceptLanguagesAccept-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 {
    case Accepts.Html() => Ok(views.html.list(items))
    case Accepts.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类型的抽取器:

  val AcceptsMp3 = Accepting("audio/mp3")
  render {
    case AcceptsMp3() => ???
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值