1.5_Body parsers

Body parsers

What is a body parser?

An HTTP PUT or POST request contains a body. This body can use any format, specified in the Content-Typerequest header. In Play, a body parser transforms this request body into a Scala value.

However the request body for an HTTP request can be very large and a body parser can’t just wait and load the whole data set into memory before parsing it. A BodyParser[A] is basically an Iteratee[Array[Byte],A], meaning that it receives chunks of bytes (as long as the web browser uploads some data) and computes a value of type A as result.

Let’s consider some examples.

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

Additionally a body parser has access to the HTTP request headers before it starts parsing the request body, and has the opportunity to run some precondition checks. For example, a body parser can check that some HTTP headers are properly set, or that the user trying to upload a large file has the permission to do so.

Note: That’s why a body parser is not really an Iteratee[Array[Byte],A] but more precisely aIteratee[Array[Byte],Either[Result,A]], meaning that it has the opportunity to send directly an HTTP result itself (typically400 BAD_REQUEST412 PRECONDITION_FAILED or 413 REQUEST_ENTITY_TOO_LARGE) if it decides that it is not able to compute a correct value for the request body

Once the body parser finishes its job and gives back a value of type A, the corresponding Action function is executed and the computed body value is passed into the request.

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]
}

First we see that there is a generic type A, and then that an action must define a BodyParser[A]. With Request[A]being defined as:

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

The A type is the type of the request body. We can use any Scala type as the request body, for example String,NodeSeqArray[Byte]JsonValue, or java.io.File, as long as we have a body parser able to process it.

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

In our previous examples we never specified a body parser. So how can it work? If you don’t specify your own body parser, Play will use the default, which processes the body as an instance of play.api.mvc.AnyContent.

This body parser checks the Content-Type header and decides what kind of body to process:

  • text/plainString
  • application/jsonJsValue
  • application/xmltext/xml or application/XXX+xmlNodeSeq
  • application/form-url-encodedMap[String, Seq[String]]
  • multipart/form-dataMultipartFormData[TemporaryFile]
  • any other content type: RawBuffer

For example:

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")
  }
}

Specifying a body parser

The body parsers available in Play are defined in play.api.mvc.BodyParsers.parse.

So for example, to define an action expecting a text body (as in the previous example):

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

Do you see how the code is simpler? This is because the parse.text body parser already sent a 400 BAD_REQUESTresponse if something went wrong. We don’t have to check again in our action code, and we can safely assume thatrequest.body contains the valid String body.

Alternatively we can use:

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.

Tip: There is a tolerant fashion provided for all body parsers included in Play.

Here is another example, which will store the request body in a file:

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

Combining body parsers

In the previous example, all request bodies are stored in the same file. This is a bit problematic isn’t it? Let’s write another custom body parser that extracts the user name from the request Session, to give a unique file for each user:

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)
}

Note: Here we are not really writing our own BodyParser, but just combining existing ones. This is often enough and should cover most use cases. Writing a BodyParser from scratch is covered in the advanced topics section.

Max content length

Text based body parsers (such as textjsonxml or formUrlEncoded) use a maximum content length because they have to load all of the content into memory.

There is a default maximum content length (the default is 100KB), but you can also specify it inline:

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

Tip: The default content size can be defined in application.conf:

parsers.text.maxLength=128K

Unit sizes are defined in Size in bytes format section of the Configuration page.

You can also wrap any body parser with maxLength:

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

Next: Action composition


Found an error in this documentation? The source code for this page can be found here. After reading thedocumentation guidelines, please feel free to contribute a pull request.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值