Playframework(8)Scala Project and Asynchronous HTTP Programming

Playframework(8)ScalaProjectandAsynchronousHTTPProgramming

1.5BodyParsers

AnHTTPPUTorPOSTrequestcontainsabody.Thisbodycanuseanyformat,specifiedintheCotnent-Typerequestheader.

InPlay,abodyparsertransformsthisrequestbodyintoaScalavalue.

MoreaboutActions

PreviouslywesaidthatanActionwasaRequest=>Resultfunction.Thisisnotentirelytrue.Let'shaveamorepreciselookattheActiontrait.

traitAction[A]extends(Request[A]=>Result){

defparser:BodyParser[A]

}

traitRequest[+A]extendsRequestHeader{

defbody:A

}

Tosummarize,anAction[A]usesaBodyParser[A]toretrieveavalueoftypeAfromtheHTTPrequest,andtobuildaRequest[A]objectthatispassedtotheactioncode.

Defaultbodyparser:AnyContent

Ifwedidnotspecifyourownbodyparser,Playwillusethedefault,play.api.mvc.AnyContent.ThebodyparsercheckstheContent-Typeheaderanddecideswhatkindofbodytoprocess:

text/plain:String

application/json:JsValue

text/xml:NodeSeq

application/form-url-encoded:Map[String,Seq[String]]

multipart/form-data:MultipartFormData[TemporaryFile]

anyothercontenttype:RawBuffer

Specifyingabodyparser

defsave=Action(parse.text){request=>

Ok("Got:"+request.body)

}

parse.textbodyparserwillsenda400BAD_REQUESTresponseifsomethinggoeswrong.Wedon'thavetocheckagaininouractioncode,andwecansafelyassumethatrequest.bodycontainsthevalidStringbody.

Alternatively

defsave=Action(parse.tolerantText){request=>

Ok("Got:"+request.body)

}

Thisonedoesn'tchecktheContent-TypeheaderandalwaysloadstherequestbodyasaString.

defsave=Action(parse.file(to=newFile("/tmp/upload"))){request=>

Ok("Savetherequestcontentto"+request.body)

}

Combiningbodyparsers

Inthepreviousexample,allrequestbodiesarestoredinthesamefile,thatisabitproblematic.

valstoreInUserFile=parse.using{request=>

request.session.get("username").map{user=>

file(to=newFile("/tmp/"+user+".upload"))

}.getOrElse{

error(Unauthorized("Youdon'thavetherighttouploadhere"))

}

}

defsave=Action(storeInUserFile){request=>

Ok("Savedtherequestcontentto"+request.body)

}

Maxcontentlength

Thereisadefaultcontentlength(thedefaultis100KB),butyoucanalsospecifyitinline:

defsave=Action(parse.text(maxLength=1024*10)){request=>

Ok("Got:"+text)

}

Thedefaultcontentsizecanbedefinedinapplication.conf

parsers.text.maxLength=128K

1.6ActionComposition

Basicactioncomposition

Provideahelpermethodbuildingastandardaction:

defLoggingAction(f:Request[AnyContent]=>Result):Action[AnyContent]={

Action{request=>

Logger.info("Callingaction")

f(request)

}

}

Thenwecanusethisfunctionlikethis:

defindex=LoggingAction{request=>

Ok("HelloWorld!")

}

Thiswillworkwiththedefaultparse.anyContentbodyparser.

defLoggingAction[A](bp:BodyParser[A])(f:Request[A]=>Result):Action[A]={

Action(bp){request=>

Logge.info("Callingaction")

f(request)

}

}

Andthenwecanpassthebodyparsertothatfunction

defindex=LoggingAction(parse.text){request=>

Ok("HelloWorld");

}

Wrappingexistingactions

AnotherwaytodefineourownLoggingActionthatwouldbeawrapperoveranotherAction:

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

defapply(request:Request[A]):Result={

Logger.info("Callingaction")

action(request)

}

lazyvalparser=action.parser

}

Wrapanyotheraction:

defindex=Logging{

Action{

Ok("HelloWorld")

}

}

defindex=Logging{

Action(parse.text){

Ok("HelloWorld")

}

}

Amorecomplicatedexample

Iwillchoosedirectlyuseplay.api.mvc.Security.authenticated

AnotherwaytocreatetheAuthenticatedaction

…snip…

2.AsynchronousHTTPProgramming

2.1Handingasynchronousresults

ThesameasJava,weneedtoresponsetheclientapromiseofresult.

…snip…

2.2StreamingHTTPresponses

…snip…

2.3CometSockets

…snip...

3.Thetemplateengine

3.1Templatesyntax

AtypesafetemplateenginebasedonScala

Overview

APlayScalatemplateisasimpletextfile,thatcontainssmallblockofScalacode.Theycangenerateanytext-basedformat,suchasHTML,XMLorCSV.

Namingconvention

views/Application/index.scala.html

views.html.Application.index

@(customer:Customer,orders:Seq[Order])

<h1>[email protected]!</h1>

<ul>

@orders.map{order=>

<li>@order.title</li>

}

</ul>

WecanthencallthisfromanyScalacodeaswewouldcallafunction:

valhtml=views.html.Application.index(customer,orders)

ThesameasinJavaPlayproject.

@**************************

*Comments

****************************@

3.2CommonUsecase

…snip…

4.HTTPformsubmissionandvalidation

4.1Formdefinitions

DefiningaforminProject

valloginForm=Form(

tuple(

"email"->text,

"password"->text

}

)

Thisformcangeneratea(String,String)resultvaluefromMap[String,String]data:

valanyData=Map("email"->"[email protected]","password"->"111111")

val(user,password)=loginForm.bind(anyData).get

Putthevalueintoatuple.Wecandothattorequest.

val(use,password)=loginForm.bindFromRequest.get

Constructingcomplexobjects

caseclassUser(name:String,age:Int)

valuserForm=Form(

mapping(

"name"->text,

"age"->number

)(User.apply)(User.unapply)

)

valanyData=Map("name"->"sillycat","age"->"30")

valuser:User=userform.bind(anyData).get

Ifweuseclass,weneedtodefineapplyandunapply,ifweusetuple,wedonotneedthat.

Checkboxfortermsofservice,wedon'tneedtoaddthisdatatoourUservalue.Itisjustadummyfieldthatisusedforformvalidationbutwhichdoesn'tcarryanyusefulinformationoncevalidated.

valuserForm=Form(

mapping(

"name"->text,

"age"->number,

"accept"->checked("Pleaseacceptthetermsandconditions")

)((name,age,_)=>User(name,age))

((user:User)=>Some((user.name,user,age,false))

)

Definingconstraints

Foreachmapping,youcanalsodefineadditionalvalidationconstraintsthatwillbecheckedduringthebindingphase.

caseclassUser(name:String,age:Int)

valuserForm=Form(

mapping(

"name"->text.verifying(required),

"age"->number.verifying(min(0),max(100))

)(User.apply)(User.unapply)

)

alternatively,wecanwritelikethis

mapping(

"name"->nonEmptyText,

"age"->number(min=0,max=100)

)

ad-hocconstraints

valloginForm=Form(

tuple(

"email"->nonEmptyText,

"password"->text

)verifying("Invalidusernameorpassword",fields=>fieldsmatch{

case(e,p)=>User.authenticate(e,p).isDefined

})

)

Handlingbindingfailure

usefoldoperationforbindingerrors.

loginForm.bindFromRequest.fold(

formWithErrors=>//bindingfailure,youretrievetheformcontainingerrors

value=>//bindingsuccess,yougettheactualvalue

)

Fillaformwithinitialdefaultvalues

valfilledForm=userForm.fill(User("Carl",18))

Nestedvalues

caseclassUser(name:String,address:Address)

caseclassAddress(street:String,city:String)

valuserForm=Form(

mapping(

"name"->text,

"address"->mapping(

"street"->text,

"city"->text

)(Address.apply)(Address.unapply)

)(User.apply,User.unapply)

)

Theformvaluessentbythebrowsermustbenamedlikethis:

address,street,address.city

Repeatedvalues

caseclassUser(name:String,emails:List[String])

valuserForm=Form(

mapping(

"name"->text,

"emails"->list(text)

)(User.apply,User.unapply)

)

Theformvaluessentbythebrowsermustbenamed

emails[0],emails[1],emails[2],etc.

Optionalvalues

caseclassUser(name:String,email:Option[String])

valuserForm=Form(

mapping(

"name"->text,

"email"->optiional(text)

)(User.apply,User.unapply)

)

Ignoredvalues

caseclassUser(id:Long,name:String,email:Option[String])

valuserForm=Form(

mapping(

"id"->ignored(1234),

"name"->text,

"email"->optional(text)

)(User.apply,User.unapply)

)

4.2Usingtheformtemplatehelpers

@helper.form(action=routes.Application.submit,'id->"myForm"){

}

MostlythesameasPlayJavaProject.

References:

http://www.playframework.org/documentation/2.0.4/ScalaHome

http://www.playframework.org/documentation/2.0.4/ScalaBodyParsers

相关推荐