`
sillycat
  • 浏览: 2486324 次
  • 性别: Icon_minigender_1
  • 来自: 成都
社区版块
存档分类
最新评论

Playframework(8)Scala Project and Asynchronous HTTP Programming

 
阅读更多
Playframework(8)Scala Project and Asynchronous HTTP Programming
1.5 Body Parsers
An HTTP PUT or POST request contains a body. This body can use any format, specified in the Cotnent-Type request header.
In Play, a body parser transforms this request body into a Scala value.

More about Actions
Previously we said that an Action was a Request => Result function. This is not entirely true. Let's have a more precise look at the Action trait.

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

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

To summarize, an Action[A] uses a BodyParser[A] to retrieve a value of type A from the HTTP request, and to build a Request[A] object that is passed to the action code.

Default body parser: AnyContent
If we did not specify our own body parser, Play will use the default, play.api.mvc.AnyContent. The body parser checks the Content-Type header and decides what kind of body to process:

text/plain: String
application/json: JsValue
text/xml: NodeSeq
application/form-url-encoded: Map[String, Seq[String]]
multipart/form-data: MultipartFormData[TemporaryFile]
any other content type: RawBuffer

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

parse.text body parser will send a 400 BAD_REQUEST response if something goes wrong. We don't have to check again in our action code, and we can safely assume that request.body contains the valid String body.

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

This one doesn't check the Content-Type header and always loads the request body as a String.

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

Combining body parsers
In the previous example, all request bodies are stored in the same file, that is a bit problematic.

val storeInUserFile = parse.using { request =>
     request.session.get("username").map{     user =>
          file( to = new File("/tmp/" + user + ".upload"))
     }.getOrElse{
          error(Unauthorized("You don't have the right to upload here"))
     }
}

def save = Action(storeInUserFile) { request =>
     Ok("Saved the request content to " + request.body)
}

Max content length
There is a default content length (the default is 100KB), but you can also specify it inline:
def save = Action(parse.text(maxLength = 1024 * 10)) { request =>
     Ok("Got: " + text)
}

The default content size can be defined in application.conf
parsers.text.maxLength=128K

1.6 Action Composition
Basic action composition
Provide a helper method building a standard action:
def LoggingAction( f: Request[AnyContent] => Result) : Action[AnyContent] = {
     Action { request =>
          Logger.info("Calling action")
          f(request)    
     }
}

Then we can use this function like this:
def index = LoggingAction{ request =>
     Ok("Hello World!")
}

This will work with the default parse.anyContent body parser.

def LoggingAction[A](bp: BodyParser[A]) (f:Request[A] => Result) : Action[A] = {
     Action(bp) { request =>
          Logge.info("Calling action")
          f(request)
     }
}

And then we can pass the body parser to that function
def index = LoggingAction(parse.text) { request =>
     Ok("Hello World");
}

Wrapping existing actions
Another way to define our own LoggingAction that would be a wrapper over another Action:

case class Logging[A] (action: Action[A]) extends Action[A] {
     def apply(request: Request[A]): Result = {
          Logger.info("Calling action")
          action(request)
     }
     lazy val parser = action.parser
}

Wrap any other action:
def index = Logging {
     Action{
          Ok("Hello World")
     }
}

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

A more complicated example
I will choose directly use play.api.mvc.Security.authenticated

Another way to create the Authenticated action
…snip…

2. Asynchronous HTTP Programming
2.1 Handing asynchronous results
The same as Java, we need to response the client a promise of result.
…snip…

2.2 Streaming HTTP responses
…snip…

2.3 Comet Sockets
…snip...

3. The template engine
3.1 Template syntax
A type safe template engine based on Scala
Overview
A Play Scala template is a simple text file, that contains small block of Scala code. They can generate any text-based format, such as HTML, XML or CSV.

Naming convention
views/Application/index.scala.html   
views.html.Application.index

@(customer: Customer, orders: Seq[Order])
<h1>Welcome @customer.name!</h1>
<ul>
@orders.map { order =>
     <li>@order.title</li>
}
</ul>

We can then call this from any Scala code as we would call a function:
val html = views.html.Application.index(customer, orders)

The same as in Java Play project.
@**************************
* Comments
****************************@

3.2 Common Usecase
…snip…

4. HTTP form submission and validation
4.1 Form definitions
Defining a form in Project
val loginForm = Form(
     tuple(
          "email" -> text,
          "password" -> text
     }
)

This form can generate a (String, String) result value from Map[String, String] data:
val anyData = Map("email" -> "luohuazju@gmail.com", "password" -> "111111")
val (user, password) = loginForm.bind(anyData).get

Put the value into a tuple. We can do that to request.
val (use, password) = loginForm.bindFromRequest.get

Constructing complex objects
case class User(name: String, age: Int)

val userForm = Form(
     mapping(
          "name" -> text,
          "age" -> number
     )(User.apply)(User.unapply)
)

val anyData = Map("name" ->"sillycat", "age" ->"30")
val user: User = user form.bind(anyData).get

If we use class, we need to define apply and unapply, if we use tuple, we do not need that.

Checkbox for terms of service, we don't need to add this data to our User value. It is just a dummy field that is used for form validation but which doesn't carry any useful information once validated.

val userForm = Form(
     mapping(
          "name" -> text,
          "age" -> number,
          "accept" -> checked("Please accept the terms and conditions")
     )((name, age, _) => User(name, age))
      ((user: User) => Some((user.name, user,age,false))
)

Defining constraints
For each mapping, you can also define additional validation constraints that will be checked during the binding phase.

case class User(name: String, age: Int)
val userForm = Form(
     mapping(
          "name" -> text.verifying(required),
          "age" -> number.verifying(min(0), max(100))
     )(User.apply)(User.unapply)
)

alternatively, we can write like this
mapping(
     "name" -> nonEmptyText,
     "age" -> number(min=0, max=100)
)

ad-hoc constraints

val loginForm = Form(
     tuple(
          "email" -> nonEmptyText,
          "password" -> text
     ) verifying("Invalid user name or password", fields => fields match{
          case(e,p) => User.authenticate(e,p).isDefined
     })
)

Handling binding failure
use fold operation for binding errors.
loginForm.bindFromRequest.fold(
     formWithErrors => // binding failure, you retrieve the form containing errors
     value => //binding success, you get the actual value
)

Fill a form with initial default values
val filledForm = userForm.fill(User("Carl", 18))

Nested values
case class User(name: String, address: Address)
case class Address(street: String, city: String)

val userForm = Form(
     mapping(
          "name" -> text,
          "address" -> mapping(
               "street" -> text,
               "city" -> text
          )(Address.apply)(Address.unapply)
     )(User.apply, User.unapply)
)

The form values sent by the browser must be named like this:
address,street, address.city

Repeated values
case class User(name: String, emails: List[String])

val userForm = Form(
     mapping(
          "name" -> text,
          "emails" -> list(text)
     )(User.apply, User.unapply)
)

The form values sent by the browser must be named
emails[0], emails[1], emails[2], etc.

Optional values
case class User(name: String, email: Option[String])

val userForm = Form(
     mapping(
          "name" -> text,
          "email" -> optiional(text)
     )(User.apply, User.unapply)
)

Ignored values
case class User(id: Long, name: String, email: Option[String])

val userForm = Form(
     mapping(
          "id" -> ignored(1234),
          "name" -> text,
          "email" -> optional(text)
     )(User.apply, User.unapply)
)

4.2 Using the form template helpers
@helper.form(action = routes.Application.submit, 'id->"myForm") {

}

Mostly the same as Play Java Project.

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


分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics