什么是Body Parsers
一個(gè)HTTP請求是一個(gè)頭部后面緊隨著一個(gè)body,頭部很小,可以在內(nèi)存中緩存,因此Play的模型中使用了RequestHeader
這個(gè)類。Body有時(shí)候也可能很長,以致于不能緩存,反而作為一種流而被建模。但是,許多請求體的有效載荷是小的,可以在內(nèi)存中建模。因此描繪body流對于一個(gè)內(nèi)存中的對象,Play提供 BodyParser
。
由于Play是一個(gè)異步框架,傳統(tǒng)的InputStream方法不能用來讀請求體,當(dāng)你調(diào)用read方法時(shí),輸入流被阻塞,調(diào)用它的線程必須等到數(shù)據(jù)可用為止。Play使用一個(gè)異步流的庫 Akka Streams ,它是 Reactive Streams 的一個(gè)實(shí)現(xiàn),是一個(gè)允許許多異步流API無縫協(xié)同工作的SPI。因此雖然傳統(tǒng)的InputStream不適合用在Play上,但是Akka Streams以及以Reactive Streams為核心的整個(gè)異步庫的整個(gè)生態(tài)系統(tǒng)能提供你一切所需。
更多關(guān)于Actions
之前我們說過,Action是一個(gè)Request => Result類型的函數(shù), 這并不完全正確,讓我們更仔細(xì)地看一下Action這個(gè)特質(zhì):
trait Action[A] extends (Request[A] => Result) { def parser: BodyParser[A]}
BodyParser[A] , 另外Request[A]可以被定義如下:
trait Request[+A] extends RequestHeader { def body: A}
A類型是請求體的類型,我們可以用任何Scala的類型來作為請求體的類型,例如 String
, NodeSeq
, Array[Byte]
, JsonValue
或者java.io.File,只要有一個(gè)body parser能夠處理它就行。
總而言之,一個(gè)Action[A]會使用一個(gè)BodyParser[A]來從HTTP請求中檢索A類型的值,來建立傳遞給Action代碼的request[A]類型的對象。
使用內(nèi)置的body parsers
許多典型的web apps都不需要使用客戶端的body parsers,它們能使用Play內(nèi)置的body parsers正常工作。包括JSON、XML、表單的解析器,還包括把plain text當(dāng)做String來處理,把byte當(dāng)做byteString來處理。
默認(rèn)的body parser
當(dāng)沒有明確指定一個(gè)body parser的時(shí)候,默認(rèn)的body parser會根據(jù)頭部的content-type來解析body。舉例來說,content-type是Application/json類型的話,會被解析成JSValue,content-type為application/x-www-form-urlencoded類型的會被解析成Map[String, Seq[String]]。
默認(rèn)解析器產(chǎn)生的AnyContent類型的body,AnyContent能通過as類方法來支持各種類型,譬如asJson,返回body類型的一個(gè)Option類型:
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") }}
默認(rèn)解析器支持以下類型之間的映射:
text/plain:通過asText轉(zhuǎn)換成String。
application/json:通過asJson轉(zhuǎn)換成JSValue。
application/xml,text/xml或者application/XXX+xml:通過asXML轉(zhuǎn)換成scala.xml.NodeSeq
application/x-www-form-urlencoded:通過asFormUrlEncoded轉(zhuǎn)換成Map[String, Seq[String]]
multipart/form-data:通過asMultipartFormData轉(zhuǎn)換成MultipartFormData
任何其他的類型:通過asRaw轉(zhuǎn)換成rawBuffer。
默認(rèn)的body parser,出于性能的考慮,如果請求方法中沒有定義一個(gè)有意義的body,就不會解析該請求方法的body,默認(rèn)body parser只解析post、put、patch請求,而不會解析get、head、delete請求,如果要為這些方法解析請求體,就需要使用Anycontent Body Parser。
選擇顯式的body parser
如果需要顯式地指定body parser,就需要向Action的apply或async方法傳遞一個(gè)body parser。
Play提供了許多框架之外的body parser,通過用Controllers引入 BodyParsers.parse
對象來實(shí)現(xiàn)。舉例說明,定義一個(gè)期望得到j(luò)son body的Action如下:
def save = Action(parse.json) { request => Ok("Got: " + (request.body \ "name").as[String])}
注意到現(xiàn)在body的類型是JSValue,當(dāng)它不再是Option類型時(shí),工作變得相對簡單。沒有Option類型的原因是json body parser要驗(yàn)證一個(gè)請求有一個(gè)application/json的content-type,如果請求沒達(dá)到期望,然后回送415 Unsupported Media Type應(yīng)答。因此我們在Action代碼中不用再次校驗(yàn)。
客戶端必須發(fā)送正確的content-type頭部,同時(shí)附上他們的請求。如果你想更輕松點(diǎn),可以使用tolerantJson,這將會忽略content-type,嘗試把body解析成json格式:
def save = Action(parse.tolerantJson) { request => Ok("Got: " + (request.body \ "name").as[String])}
另一個(gè)例子是把請求體放在文件里:
def save = Action(parse.file(to = new File("/tmp/upload"))) { request => Ok("Saved the request content to " + request.body)}
抽取用戶名,給每一個(gè)用戶一個(gè)獨(dú)有的文件:
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)}
我們不是真正寫一個(gè)自己的body parser,而是結(jié)合已有的body parser而已, 這已經(jīng)足夠了,能涵蓋大多數(shù)的實(shí)例。
大內(nèi)容長度
給予文本的body parser,譬如 text, json, xml或者formUrlEncoded這些,使用大內(nèi)容長度限制,因?yàn)樗麄円獙⑺袃?nèi)容加載到內(nèi)存,默認(rèn)的能解析的大內(nèi)容長度是100KB,通過指定application.conf中的play.http.parser.maxMemoryBuffer就可以實(shí)現(xiàn):
play.http.parser.maxMemoryBuffer=128K
對于一個(gè)解析器而言,在磁盤上的緩沖內(nèi)容,譬如raw parser或者multipart/form-data,大內(nèi)容長度通過play.http.parser.maxDiskBuffer這一屬性指定,默認(rèn)10MB。為了數(shù)據(jù)域的統(tǒng)計(jì),multipart/form-data解析器強(qiáng)制指定了文本大長度這一屬性。
在Action中也可以修改默認(rèn)大長度:
// Accept only 10KB of data.def save = Action(parse.text(maxLength = 1024 * 10)) { request => Ok("Got: " + text)}
寫一個(gè)自定義的body parser:
通過實(shí)現(xiàn)body parser特質(zhì),可以實(shí)現(xiàn)一個(gè)自定義的body parser,body parser特質(zhì)定義如下:
trait BodyParser[+A] extends (RequestHeader => Accumulator[ByteString, Either[Result, A]])
這個(gè)特質(zhì)傳入的是一個(gè)RequestHeader
對象,用來驗(yàn)證請求的合法性,只有得到content-type,請求才能被正確解析。特質(zhì)的返回類型是Accumulator
,一個(gè)accumulator在 Akka Streams Sink
中是輕量級的。一個(gè)accumulator會異步地將元素流匯集到result中,這可以通過在 Akka Streams Source
中傳遞來執(zhí)行。當(dāng)accumulator結(jié)束工作的時(shí)候,會返回一個(gè)Future對象,這就相當(dāng)于Sink[E, Future[A]],一個(gè)類的封裝類,不過有一個(gè)大的區(qū)別是,Accumulator提供便利的方法,如map
, mapFuture
, recover
等。處理的是Result類型,因此好像是一個(gè)promise,可是Sink實(shí)際上所有類似的操作都被封裝在mapMaterializedValue回調(diào)里。
Apply方法返回的accumulator產(chǎn)生ByteString類型的元素。這些實(shí)際上是Bytes數(shù)組,但和byte[]又有所區(qū)別, ByteString是不可變的,譬如切分和追加等操作都是在常量時(shí)間內(nèi)完成的。
如果accumulator的返回類型是
Either[Result, A]
,那么它會返回一個(gè)Result類型或A類型。A一般是拋出異常時(shí)返回的錯(cuò)誤類型,這些錯(cuò)誤包括解析失敗、content-type和body parser接受的類型不匹配,或者緩沖區(qū)溢出。如果body parser 返回Result類型,它會縮短Action的過程,body parsers的Result會馬上返回,Action永遠(yuǎn)不會被調(diào)用。
定位另一處的body
一個(gè)普通的用例是,當(dāng)你向解析一個(gè)body,并且你希望在另一個(gè)地方流式化,此時(shí)需要自定義一個(gè)body parser:
import javax.inject._import play.api.mvc._import play.api.libs.streams._import play.api.libs.ws._import scala.concurrent.ExecutionContextimport akka.util.ByteStringclass 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自定義解析
在極少數(shù)情況下會通過Akka Streams來寫一個(gè)自定義解析器。通常先在ByteString中緩存body是沒問題的,另一種更簡易的途徑在body上是使用必要的方法和隨機(jī)存取。
當(dāng)然也有不適合的時(shí)候,如果你的body需要解析的內(nèi)容太長以致于內(nèi)存中不能匹配合適的空間,這時(shí)候你需要寫一個(gè)自定義解析器。
在來自ByteStrings的流的Parsing Lines下建立起來的CSV Parser,具體使用demo如下,文檔來自于Akka Streams cookbook:
import play.api.mvc._import play.api.libs.streams._import play.api.libs.concurrent.Execution.Implicits.defaultContextimport akka.util.ByteStringimport 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)}
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。
本文題目:Scala下Play框架學(xué)習(xí)筆記(Bodyparsers)-創(chuàng)新互聯(lián)
標(biāo)題鏈接:http://bm7419.com/article28/cdgdcp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供全網(wǎng)營銷推廣、網(wǎng)站改版、網(wǎng)站維護(hù)、微信公眾號、建站公司、網(wǎng)站內(nèi)鏈
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容