play2.x 官網筆記擷取+翻譯 第一章(完)
本文來自圖靈社群 轉截請註明出處(當然我還放在了自己的部落格裡: http://fair-jm.iteye.com/ )
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完成的)
可以使用 Result
的as
方法來指定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.conf
:
parsers.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)
}
}
Option
的toRight
是
如果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#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 {
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() => ???
}
}
相關文章
- play2.x 官網筆記擷取+翻譯 第二章(完)筆記
- influxdb官網文件翻譯UX
- substr擷取函式 筆記函式筆記
- 【翻譯】CoffeeScript 選譯筆記筆記
- Gremlin-官網介紹翻譯REM
- Hadoop官網翻譯之HDFS ArchitectureHadoop
- spring官網線上學習文件翻譯Spring
- Drill官網文件翻譯四Drill的效能
- android之Fragment(官網資料翻譯)AndroidFragment
- Hadoop官網翻譯之HDFS Users GuideHadoopGUIIDE
- hadoop官網翻譯第一天Hadoop
- Gradle入門(翻譯自Graddle官網)Gradle
- FreeLearning 安全譯文集翻譯完畢
- ARP協議:網路世界的地址翻譯官協議
- Elasticsearch官檔翻譯——2.6 升級Elasticsearch
- Drill官網文件翻譯二:Drill查詢的執行
- 爬取有道翻譯
- Kotlin 官方參考文件翻譯完畢Kotlin
- hadoop官網翻譯第三天Hadoop Cluster SetupHadoop
- Drill官網文件翻譯六:儲存外掛的註冊
- 有道雲詞典--翻譯/螢幕取詞翻譯
- 爬取必應翻譯
- The art of software testing翻譯–第一章薦
- 【工利其器】工具使用之(四)systrace篇(1)官網翻譯
- 【翻譯】蘋果官網的命名規範之 Naming Properties and Data Types蘋果
- 【讀書筆記】代理模式翻譯成C++了筆記模式C++
- php字串擷取函式,支援中文擷取PHP字串函式
- js擷取JS
- 擷取ip
- 字串擷取字串
- 日常筆記一:擷取富文字編輯器中的文字內容筆記
- 【翻譯】使用PowerShell獲取網站執行時資料網站
- [翻譯] .NET 官宣跨平臺 UI 框架 MAUIUI框架
- 翻譯隨筆(隨時更新)
- hadoop官網翻譯之HDFS High Availability Using the Quorum Journal ManagerHadoopAI
- 我自己翻譯的reactjs官網,希望能幫助你學習react!ReactJS
- 【翻譯】蘋果官網的命名規範之 Code Naming Basics-General Principles蘋果
- [翻譯] 理解 ECMASCript 6 第一章: 基礎知識