|
Play Framework/Scala example source code file (ContentTypes.scala)
The ContentTypes.scala Play Framework example source code/* * Copyright (C) 2009-2013 Typesafe Inc. <http://www.typesafe.com> */ package play.api.mvc import scala.language.reflectiveCalls import java.io._ import scala.concurrent.Future import scala.xml._ import play.api._ import play.api.libs.json._ import play.api.libs.iteratee._ import play.api.libs.iteratee.Input._ import play.api.libs.iteratee.Parsing._ import play.api.libs.Files.TemporaryFile import MultipartFormData._ import scala.collection.mutable.ListBuffer import java.util.Locale import scala.util.control.NonFatal import play.api.http.HttpVerbs import play.utils.PlayIO /** * A request body that adapts automatically according the request Content-Type. */ sealed trait AnyContent { /** * application/form-url-encoded */ def asFormUrlEncoded: Option[Map[String, Seq[String]]] = this match { case AnyContentAsFormUrlEncoded(data) => Some(data) case _ => None } /** * text/plain */ def asText: Option[String] = this match { case AnyContentAsText(txt) => Some(txt) case _ => None } /** * application/xml */ def asXml: Option[NodeSeq] = this match { case AnyContentAsXml(xml) => Some(xml) case _ => None } /** * text/json or application/json */ def asJson: Option[JsValue] = this match { case AnyContentAsJson(json) => Some(json) case _ => None } /** * multipart/form-data */ def asMultipartFormData: Option[MultipartFormData[TemporaryFile]] = this match { case AnyContentAsMultipartFormData(mfd) => Some(mfd) case _ => None } /** * Used when no Content-Type matches */ def asRaw: Option[RawBuffer] = this match { case AnyContentAsRaw(raw) => Some(raw) case _ => None } } /** * AnyContent - Empty request body */ case object AnyContentAsEmpty extends AnyContent /** * AnyContent - Text body */ case class AnyContentAsText(txt: String) extends AnyContent /** * AnyContent - Form url encoded body */ case class AnyContentAsFormUrlEncoded(data: Map[String, Seq[String]]) extends AnyContent /** * AnyContent - Raw body (give access to the raw data as bytes). */ case class AnyContentAsRaw(raw: RawBuffer) extends AnyContent /** * AnyContent - XML body */ case class AnyContentAsXml(xml: NodeSeq) extends AnyContent /** * AnyContent - Json body */ case class AnyContentAsJson(json: JsValue) extends AnyContent /** * AnyContent - Multipart form data body */ case class AnyContentAsMultipartFormData(mdf: MultipartFormData[TemporaryFile]) extends AnyContent /** * Multipart form data body. */ case class MultipartFormData[A](dataParts: Map[String, Seq[String]], files: Seq[FilePart[A]], badParts: Seq[BadPart], missingFileParts: Seq[MissingFilePart]) { /** * Extract the data parts as Form url encoded. */ def asFormUrlEncoded: Map[String, Seq[String]] = dataParts /** * Access a file part. */ def file(key: String): Option[FilePart[A]] = files.find(_.key == key) } /** * Defines parts handled by Multipart form data. */ object MultipartFormData { /** * A part. */ sealed trait Part /** * A data part. */ case class DataPart(key: String, value: String) extends Part /** * A file part. */ case class FilePart[A](key: String, filename: String, contentType: Option[String], ref: A) extends Part /** * A file part with no content provided. */ case class MissingFilePart(key: String) extends Part /** * A part that has not been properly parsed. */ case class BadPart(headers: Map[String, String]) extends Part /** * A data part that has excedeed the max size allowed. */ case class MaxDataPartSizeExceeded(key: String) extends Part } /** * Handle the request body a raw bytes data. * * @param memoryThreshold If the content size is bigger than this limit, the content is stored as file. */ case class RawBuffer(memoryThreshold: Int, initialData: Array[Byte] = Array.empty[Byte]) { import play.api.libs.Files._ import scala.collection.mutable._ @volatile private var inMemory: List[Array[Byte]] = if (initialData.length == 0) Nil else List(initialData) @volatile private var inMemorySize = initialData.length @volatile private var backedByTemporaryFile: TemporaryFile = _ @volatile private var outStream: OutputStream = _ private[play] def push(chunk: Array[Byte]) { if (inMemory != null) { if (chunk.length + inMemorySize > memoryThreshold) { backToTemporaryFile() outStream.write(chunk) } else { inMemory = chunk :: inMemory inMemorySize += chunk.length } } else { outStream.write(chunk) } } private[play] def close() { if (outStream != null) { outStream.close() } } private[play] def backToTemporaryFile() { backedByTemporaryFile = TemporaryFile("requestBody", "asRaw") outStream = new FileOutputStream(backedByTemporaryFile.file) inMemory.reverse.foreach { chunk => outStream.write(chunk) } inMemory = null } /** * Buffer size. */ def size: Long = { if (inMemory != null) inMemorySize else backedByTemporaryFile.file.length } /** * Returns the buffer content as a bytes array. * * This operation will cause the internal collection of byte arrays to be copied into a new byte array on each * invocation, no caching is done. If the buffer has been written out to a file, it will read the contents of the * file. * * @param maxLength The max length allowed to be stored in memory. If this is smaller than memoryThreshold, and the * buffer is already in memory then None will still be returned. * @return None if the content is greater than maxLength, otherwise, the data as bytes. */ def asBytes(maxLength: Int = memoryThreshold): Option[Array[Byte]] = { if (size <= maxLength) { if (inMemory != null) { val buffer = new Array[Byte](inMemorySize) inMemory.reverse.foldLeft(0) { (position, chunk) => System.arraycopy(chunk, 0, buffer, position, Math.min(chunk.length, buffer.length - position)) chunk.length + position } Some(buffer) } else { Some(PlayIO.readFile(backedByTemporaryFile.file)) } } else { None } } /** * Returns the buffer content as File. */ def asFile: File = { if (inMemory != null) { backToTemporaryFile() close() } backedByTemporaryFile.file } override def toString = { "RawBuffer(inMemory=" + Option(inMemory).map(_.size).orNull + ", backedByTemporaryFile=" + backedByTemporaryFile + ")" } } /** * Default body parsers. */ trait BodyParsers { /** * Default body parsers. */ object parse { /** * Unlimited size. */ val UNLIMITED: Int = Integer.MAX_VALUE private val ApplicationXmlMatcher = """application/.*\+xml.*""".r /** * Default max length allowed for text based body. * * You can configure it in application.conf: * * {{{ * parsers.text.maxLength = 512k * }}} */ def DEFAULT_MAX_TEXT_LENGTH: Int = Play.maybeApplication.flatMap { app => app.configuration.getBytes("parsers.text.maxLength").map(_.toInt) }.getOrElse(1024 * 100) // -- Text parser /** * Parse the body as text without checking the Content-Type. * * @param maxLength Max length allowed or returns EntityTooLarge HTTP response. */ def tolerantText(maxLength: Int): BodyParser[String] = BodyParser("text, maxLength=" + maxLength) { request => // Encoding notes: RFC-2616 section 3.7.1 mandates ISO-8859-1 as the default charset if none is specified. import Execution.Implicits.trampoline Traversable.takeUpTo[Array[Byte]](maxLength) .transform(Iteratee.consume[Array[Byte]]().map(c => new String(c, request.charset.getOrElse("ISO-8859-1")))) .flatMap(Iteratee.eofOrElse(Results.EntityTooLarge)) } /** * Parse the body as text without checking the Content-Type. */ def tolerantText: BodyParser[String] = tolerantText(DEFAULT_MAX_TEXT_LENGTH) /** * Parse the body as text if the Content-Type is text/plain. * * @param maxLength Max length allowed or returns EntityTooLarge HTTP response. */ def text(maxLength: Int): BodyParser[String] = when( _.contentType.exists(_.equalsIgnoreCase("text/plain")), tolerantText(maxLength), createBadResult("Expecting text/plain body") ) /** * Parse the body as text if the Content-Type is text/plain. */ def text: BodyParser[String] = text(DEFAULT_MAX_TEXT_LENGTH) // -- Raw parser /** * Store the body content in a RawBuffer. * * @param memoryThreshold If the content size is bigger than this limit, the content is stored as file. */ def raw(memoryThreshold: Int): BodyParser[RawBuffer] = BodyParser("raw, memoryThreshold=" + memoryThreshold) { request => import play.core.Execution.Implicits.internalContext // Cannot run on same thread as may need to write to a file val buffer = RawBuffer(memoryThreshold) Iteratee.foreach[Array[Byte]](bytes => buffer.push(bytes)).map { _ => buffer.close() Right(buffer) } } /** * Store the body content in a RawBuffer. */ def raw: BodyParser[RawBuffer] = raw(memoryThreshold = 100 * 1024) // -- JSON parser /** * Parse the body as Json without checking the Content-Type. * * @param maxLength Max length allowed or returns EntityTooLarge HTTP response. */ def tolerantJson(maxLength: Int): BodyParser[JsValue] = tolerantBodyParser[JsValue]("json", maxLength, "Invalid Json") { (request, bytes) => // Encoding notes: RFC 4627 requires that JSON be encoded in Unicode, and states that whether that's // UTF-8, UTF-16 or UTF-32 can be auto detected by reading the first two bytes. So we ignore the declared // charset and don't decode, we passing the byte array as is because Jackson supports auto detection. Json.parse(bytes) } /** * Parse the body as Json without checking the Content-Type. */ def tolerantJson: BodyParser[JsValue] = tolerantJson(DEFAULT_MAX_TEXT_LENGTH) /** * Parse the body as Json if the Content-Type is text/json or application/json. * * @param maxLength Max length allowed or returns EntityTooLarge HTTP response. */ def json(maxLength: Int): BodyParser[JsValue] = when( _.contentType.exists(m => m.equalsIgnoreCase("text/json") || m.equalsIgnoreCase("application/json")), tolerantJson(maxLength), createBadResult("Expecting text/json or application/json body") ) /** * Parse the body as Json if the Content-Type is text/json or application/json. */ def json: BodyParser[JsValue] = json(DEFAULT_MAX_TEXT_LENGTH) /** * Parse the body as Json if the Content-Type is text/json or application/json, * validating the result with the Json reader. * * @tparam A the type to read and validate from the body. * @param reader a Json reader for type A. */ def json[A](implicit reader: Reads[A]): BodyParser[A] = BodyParser("json reader") { request => import play.api.libs.iteratee.Execution.Implicits.trampoline json(request) mapM { case Left(simpleResult) => Future.successful(Left(simpleResult)) case Right(jsValue) => jsValue.validate(reader) map { a => Future.successful(Right(a)) } recoverTotal { jsError => val msg = s"Json validation error ${JsError.toFlatForm(jsError)}" createBadResult(msg)(request) map Left.apply } } } // -- Empty parser /** * Don't parse the body content. */ def empty: BodyParser[Unit] = BodyParser("empty") { request => Done(Right(()), Empty) } // -- XML parser /** * Parse the body as Xml without checking the Content-Type. * * @param maxLength Max length allowed or returns EntityTooLarge HTTP response. */ def tolerantXml(maxLength: Int): BodyParser[NodeSeq] = tolerantBodyParser[NodeSeq]("xml", maxLength, "Invalid XML") { (request, bytes) => val inputSource = new InputSource(new ByteArrayInputStream(bytes)) // Encoding notes: RFC 3023 is the RFC for XML content types. Comments below reflect what it says. // An externally declared charset takes precedence request.charset.orElse( // If omitted, maybe select a default charset, based on the media type. request.mediaType.collect { // According to RFC 3023, the default encoding for text/xml is us-ascii. This contradicts RFC 2616, which // states that the default for text/* is ISO-8859-1. An RFC 3023 conforming client will send US-ASCII, // in that case it is safe for us to use US-ASCII or ISO-8859-1. But a client that knows nothing about // XML, and therefore nothing about RFC 3023, but rather conforms to RFC 2616, will send ISO-8859-1. // Since decoding as ISO-8859-1 works for both clients that conform to RFC 3023, and clients that conform // to RFC 2616, we use that. case mt if mt.mediaType == "text" => "iso-8859-1" // Otherwise, there should be no default, it will be detected by the XML parser. } ).foreach { charset => inputSource.setEncoding(charset) } Play.XML.load(inputSource) } /** * Parse the body as Xml without checking the Content-Type. */ def tolerantXml: BodyParser[NodeSeq] = tolerantXml(DEFAULT_MAX_TEXT_LENGTH) /** * Parse the body as Xml if the Content-Type is application/xml, text/xml or application/XXX+xml. * * @param maxLength Max length allowed or returns EntityTooLarge HTTP response. */ def xml(maxLength: Int): BodyParser[NodeSeq] = when( _.contentType.exists { t => val tl = t.toLowerCase(Locale.ENGLISH) tl.startsWith("text/xml") || tl.startsWith("application/xml") || ApplicationXmlMatcher.pattern.matcher(tl).matches() }, tolerantXml(maxLength), createBadResult("Expecting xml body") ) /** * Parse the body as Xml if the Content-Type is application/xml, text/xml or application/XXX+xml. */ def xml: BodyParser[NodeSeq] = xml(DEFAULT_MAX_TEXT_LENGTH) // -- File parsers /** * Store the body content into a file. * * @param to The file used to store the content. */ def file(to: File): BodyParser[File] = BodyParser("file, to=" + to) { request => import play.core.Execution.Implicits.internalContext Iteratee.fold[Array[Byte], FileOutputStream](new FileOutputStream(to)) { (os, data) => os.write(data) os }.map { os => os.close() Right(to) } } /** * Store the body content into a temporary file. */ def temporaryFile: BodyParser[TemporaryFile] = BodyParser("temporaryFile") { request => Iteratee.flatten(Future { val tempFile = TemporaryFile("requestBody", "asTemporaryFile") file(tempFile.file)(request).map(_ => Right(tempFile))(play.api.libs.iteratee.Execution.trampoline) }(play.core.Execution.internalContext)) } // -- FormUrlEncoded /** * Parse the body as Form url encoded without checking the Content-Type. * * @param maxLength Max length allowed or returns EntityTooLarge HTTP response. */ def tolerantFormUrlEncoded(maxLength: Int): BodyParser[Map[String, Seq[String]]] = tolerantBodyParser("urlFormEncoded", maxLength, "Error parsing application/x-www-form-urlencoded") { (request, bytes) => import play.core.parsers._ FormUrlEncodedParser.parse(new String(bytes, request.charset.getOrElse("utf-8")), request.charset.getOrElse("utf-8")) } /** * Parse the body as form url encoded without checking the Content-Type. */ def tolerantFormUrlEncoded: BodyParser[Map[String, Seq[String]]] = tolerantFormUrlEncoded(DEFAULT_MAX_TEXT_LENGTH) /** * Parse the body as form url encoded if the Content-Type is application/x-www-form-urlencoded. * * @param maxLength Max length allowed or returns EntityTooLarge HTTP response. */ def urlFormEncoded(maxLength: Int): BodyParser[Map[String, Seq[String]]] = when( _.contentType.exists(_.equalsIgnoreCase("application/x-www-form-urlencoded")), tolerantFormUrlEncoded(maxLength), createBadResult("Expecting application/x-www-form-urlencoded body") ) /** * Parse the body as form url encoded if the Content-Type is application/x-www-form-urlencoded. */ def urlFormEncoded: BodyParser[Map[String, Seq[String]]] = urlFormEncoded(DEFAULT_MAX_TEXT_LENGTH) // -- Magic any content /** * Guess the body content by checking the Content-Type header. */ def anyContent: BodyParser[AnyContent] = BodyParser("anyContent") { request => import play.api.libs.iteratee.Execution.Implicits.trampoline if (request.method == HttpVerbs.GET || request.method == HttpVerbs.HEAD) { Play.logger.trace("Parsing AnyContent as empty") Done(Right(AnyContentAsEmpty), Empty) } else { val contentType: Option[String] = request.contentType.map(_.toLowerCase(Locale.ENGLISH)) contentType match { case Some("text/plain") => { Play.logger.trace("Parsing AnyContent as text") text(request).map(_.right.map(s => AnyContentAsText(s))) } case Some("text/xml") | Some("application/xml") | Some(ApplicationXmlMatcher()) => { Play.logger.trace("Parsing AnyContent as xml") xml(request).map(_.right.map(x => AnyContentAsXml(x))) } case Some("text/json") | Some("application/json") => { Play.logger.trace("Parsing AnyContent as json") json(request).map(_.right.map(j => AnyContentAsJson(j))) } case Some("application/x-www-form-urlencoded") => { Play.logger.trace("Parsing AnyContent as urlFormEncoded") urlFormEncoded(request).map(_.right.map(d => AnyContentAsFormUrlEncoded(d))) } case Some("multipart/form-data") => { Play.logger.trace("Parsing AnyContent as multipartFormData") multipartFormData(request).map(_.right.map(m => AnyContentAsMultipartFormData(m))) } case _ => { Play.logger.trace("Parsing AnyContent as raw") raw(request).map(_.right.map(r => AnyContentAsRaw(r))) } } } } // -- Multipart /** * Parse the content as multipart/form-data */ def multipartFormData: BodyParser[MultipartFormData[TemporaryFile]] = multipartFormData(Multipart.handleFilePartAsTemporaryFile) /** * Parse the content as multipart/form-data * * @param filePartHandler Handles file parts. */ def multipartFormData[A](filePartHandler: Multipart.PartHandler[FilePart[A]]): BodyParser[MultipartFormData[A]] = BodyParser("multipartFormData") { request => import play.api.libs.iteratee.Execution.Implicits.trampoline val handler: Multipart.PartHandler[Either[Part, FilePart[A]]] = Multipart.handleDataPart.andThen(_.map(Left(_))) .orElse({ case Multipart.FileInfoMatcher(partName, fileName, _) if fileName.trim.isEmpty => Done(Left(MissingFilePart(partName)), Input.Empty) }: Multipart.PartHandler[Either[Part, FilePart[A]]]) .orElse(filePartHandler.andThen(_.map(Right(_)))) .orElse { case headers => Done(Left(BadPart(headers)), Input.Empty) } Multipart.multipartParser(handler)(request).map { errorOrParts => errorOrParts.right.map { parts => val data = parts.collect { case Left(DataPart(key, value)) => (key, value) }.groupBy(_._1).mapValues(_.map(_._2)) val az = parts.collect { case Right(a) => a } val bad = parts.collect { case Left(b @ BadPart(_)) => b } val missing = parts.collect { case Left(missing @ MissingFilePart(_)) => missing } MultipartFormData(data, az, bad, missing) } } } object Multipart { def multipartParser[A](partHandler: Map[String, String] => Iteratee[Array[Byte], A]): BodyParser[Seq[A]] = parse.using { request => val maybeBoundary = for { mt <- request.mediaType (_, value) <- mt.parameters.find(_._1.equalsIgnoreCase("boundary")) boundary <- value } yield ("\r\n--" + boundary).getBytes("utf-8") maybeBoundary.map { boundary => BodyParser { request => import play.api.libs.iteratee.Execution.Implicits.trampoline val CRLF = "\r\n".getBytes val CRLFCRLF = CRLF ++ CRLF val takeUpToBoundary = Enumeratee.takeWhile[MatchInfo[Array[Byte]]](!_.isMatch) val maxHeaderBuffer = Traversable.takeUpTo[Array[Byte]](4 * 1024) transform Iteratee.consume[Array[Byte]]() val collectHeaders = maxHeaderBuffer.map { buffer => val (headerBytes, rest) = Option(buffer).map(b => b.splitAt(b.indexOfSlice(CRLFCRLF))).get val headerString = new String(headerBytes, "utf-8").trim val headers = headerString.lines.map { header => val key :: value = header.trim.split(":").toList (key.trim.toLowerCase, value.mkString.trim) }.toMap val left = rest.drop(CRLFCRLF.length) (headers, left) } val readPart = collectHeaders.flatMap { case (headers, left) => Iteratee.flatten(partHandler(headers).feed(Input.El(left))) } val handlePart = Enumeratee.map[MatchInfo[Array[Byte]]](_.content).transform(readPart) Traversable.take[Array[Byte]](boundary.size - 2).transform(Iteratee.consume()).flatMap { firstBoundary => Parsing.search(boundary) transform Iteratee.repeat { takeUpToBoundary.transform(handlePart).flatMap { part => Enumeratee.take(1)(Iteratee.ignore[MatchInfo[Array[Byte]]]).map(_ => part) } }.map(parts => Right(parts.dropRight(1))) } } }.getOrElse(parse.error(createBadResult("Missing boundary header")(request))) } type PartHandler[A] = PartialFunction[Map[String, String], Iteratee[Array[Byte], A]] def handleFilePartAsTemporaryFile: PartHandler[FilePart[TemporaryFile]] = { handleFilePart { case FileInfo(partName, filename, contentType) => val tempFile = TemporaryFile("multipartBody", "asTemporaryFile") import play.core.Execution.Implicits.internalContext Iteratee.fold[Array[Byte], FileOutputStream](new java.io.FileOutputStream(tempFile.file)) { (os, data) => os.write(data) os }.map { os => os.close() tempFile } } } case class FileInfo(partName: String, fileName: String, contentType: Option[String]) object FileInfoMatcher { private def split(str: String) = { var buffer = new StringBuffer var escape: Boolean = false var quote: Boolean = false val result = new ListBuffer[String] def addPart() = { result += buffer.toString().trim buffer = new StringBuffer } str foreach { c => c match { case '\\' => buffer.append(c) escape = true case '"' => buffer.append(c) if (!escape) quote = !quote escape = false case ';' => if (!quote) { addPart } else { buffer.append(c) } escape = false case _ => buffer.append(c) escape = false } } addPart result.toList } def unapply(headers: Map[String, String]): Option[(String, String, Option[String])] = { val keyValue = """^([a-zA-Z_0-9]+)="(.*)"$""".r for { value <- headers.get("content-disposition") values = split(value).map(_.trim).map { // unescape escaped quotes case keyValue(key, value) => (key.trim, value.trim.replaceAll("""\\"""", "\"")) case key => (key.trim, "") }.toMap _ <- values.get("form-data"); partName <- values.get("name"); fileName <- values.get("filename"); contentType = headers.get("content-type") } yield ((partName, fileName, contentType)) } } def handleFilePart[A](handler: FileInfo => Iteratee[Array[Byte], A]): PartHandler[FilePart[A]] = { case FileInfoMatcher(partName, fileName, contentType) => val safeFileName = fileName.split('\\').takeRight(1).mkString import play.api.libs.iteratee.Execution.Implicits.trampoline handler(FileInfo(partName, safeFileName, contentType)).map(a => FilePart(partName, safeFileName, contentType, a)) } object PartInfoMatcher { def unapply(headers: Map[String, String]): Option[String] = { val keyValue = """^([a-zA-Z_0-9]+)="(.*)"$""".r for { value <- headers.get("content-disposition") values = value.split(";").map(_.trim).map { case keyValue(key, value) => (key.trim, value.trim) case key => (key.trim, "") }.toMap _ <- values.get("form-data"); partName <- values.get("name") } yield (partName) } } def handleDataPart: PartHandler[Part] = { case headers @ PartInfoMatcher(partName) if !FileInfoMatcher.unapply(headers).isDefined => import play.api.libs.iteratee.Execution.Implicits.trampoline Traversable.takeUpTo[Array[Byte]](DEFAULT_MAX_TEXT_LENGTH) .transform(Iteratee.consume[Array[Byte]]().map(bytes => DataPart(partName, new String(bytes, "utf-8")))) .flatMap { data => Cont({ case Input.El(_) => Done(MaxDataPartSizeExceeded(partName), Input.Empty) case in => Done(data, in) }) } } def handlePart(fileHandler: PartHandler[FilePart[File]]): PartHandler[Part] = { handleDataPart .orElse({ case FileInfoMatcher(partName, fileName, _) if fileName.trim.isEmpty => Done(MissingFilePart(partName), Input.Empty) }: PartHandler[Part]) .orElse(fileHandler) .orElse({ case headers => Done(BadPart(headers), Input.Empty) }) } } // -- Parsing utilities /** * Wrap an existing BodyParser with a maxLength constraints. * * @param maxLength The max length allowed * @param parser The BodyParser to wrap */ def maxLength[A](maxLength: Int, parser: BodyParser[A]): BodyParser[Either[MaxSizeExceeded, A]] = BodyParser("maxLength=" + maxLength + ", wrapping=" + parser.toString) { request => import play.api.libs.iteratee.Execution.Implicits.trampoline Traversable.takeUpTo[Array[Byte]](maxLength).transform(parser(request)).flatMap(Iteratee.eofOrElse(MaxSizeExceeded(maxLength))).map { case Right(Right(result)) => Right(Right(result)) case Right(Left(badRequest)) => Left(badRequest) case Left(maxSizeExceeded) => Right(Left(maxSizeExceeded)) } } /** * A body parser that always returns an error. */ def error[A](result: Future[Result]): BodyParser[A] = BodyParser("error, result=" + result) { request => import play.api.libs.iteratee.Execution.Implicits.trampoline Iteratee.flatten(result.map(r => Done(Left(r), Empty))) } /** * Allow to choose the right BodyParser parser to use by examining the request headers. */ def using[A](f: RequestHeader => BodyParser[A]) = BodyParser { request => f(request)(request) } /** * Create a conditional BodyParser. */ def when[A](predicate: RequestHeader => Boolean, parser: BodyParser[A], badResult: RequestHeader => Future[Result]): BodyParser[A] = { BodyParser("conditional, wrapping=" + parser.toString) { request => if (predicate(request)) { parser(request) } else { import play.api.libs.iteratee.Execution.Implicits.trampoline Iteratee.flatten(badResult(request).map(result => Done(Left(result), Empty))) } } } private def createBadResult(msg: String): RequestHeader => Future[Result] = { request => Play.maybeApplication.map(_.global.onBadRequest(request, msg)) .getOrElse(Future.successful(Results.BadRequest)) } private def tolerantBodyParser[A](name: String, maxLength: Int, errorMessage: String)(parser: (RequestHeader, Array[Byte]) => A): BodyParser[A] = BodyParser(name + ", maxLength=" + maxLength) { request => import play.api.libs.iteratee.Execution.Implicits.trampoline import scala.util.control.Exception._ val bodyParser: Iteratee[Array[Byte], Either[Result, Either[Future[Result], A]]] = Traversable.takeUpTo[Array[Byte]](maxLength).transform( Iteratee.consume[Array[Byte]]().map { bytes => allCatch[A].either { parser(request, bytes) }.left.map { case NonFatal(e) => Play.logger.debug(errorMessage, e) createBadResult(errorMessage)(request) case t => throw t } } ).flatMap(Iteratee.eofOrElse(Results.EntityTooLarge)) bodyParser.mapM { case Left(tooLarge) => Future.successful(Left(tooLarge)) case Right(Left(badResult)) => badResult.map(Left.apply) case Right(Right(body)) => Future.successful(Right(body)) } } } } /** * Defaults BodyParsers. */ object BodyParsers extends BodyParsers /** * Signal a max content size exceeded */ case class MaxSizeExceeded(length: Int) Other Play Framework source code examplesHere is a short list of links related to this Play Framework ContentTypes.scala source code file: |
... this post is sponsored by my books ... | |
#1 New Release! |
FP Best Seller |
Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.
A percentage of advertising revenue from
pages under the /java/jwarehouse
URI on this website is
paid back to open source projects.