play.api.http.HttpEntity.scala Maven / Gradle / Ivy
/*
* Copyright (C) 2009-2016 Lightbend Inc.
*/
package play.api.http
import akka.stream.Materializer
import akka.stream.scaladsl.Source
import akka.util.ByteString
import play.api.mvc.Headers
import scala.concurrent.Future
/**
* An HTTP entity.
*
* HTTP entities come in three flavors, [[HttpEntity.Strict]], [[HttpEntity.Streamed]] and [[HttpEntity.Chunked]].
*/
sealed trait HttpEntity {
/**
* The content type of the entity, if known.
*/
def contentType: Option[String]
/**
* Whether it is known if this entity is empty or not.
*
* If this returns true, then the entity is definitely empty. If it returns false, the entity may or may not be empty.
*/
def isKnownEmpty: Boolean
/**
* The content length of the entity, if known.
*/
def contentLength: Option[Long]
/**
* The entity as a data stream.
*/
def dataStream: Source[ByteString, _]
/**
* Consume the data from this entity.
*/
def consumeData(implicit mat: Materializer): Future[ByteString] = {
dataStream.runFold(ByteString.empty)(_ ++ _)
}
/**
* Return this entity as the given content type.
*/
def as(contentType: String): HttpEntity
}
object HttpEntity {
/**
* No entity.
*/
val NoEntity = Strict(ByteString.empty, None)
/**
* A strict entity.
*
* Strict entities are contained entirely in memory.
*
* @param data The data contained within this entity.
* @param contentType The content type, if known.
*/
final case class Strict(data: ByteString, contentType: Option[String]) extends HttpEntity {
def isKnownEmpty = data.isEmpty
def contentLength = Some(data.size)
def dataStream = if (data.isEmpty) Source.empty[ByteString] else Source.single(data)
override def consumeData(implicit mat: Materializer) = Future.successful(data)
def as(contentType: String) = copy(contentType = Some(contentType))
}
/**
* A streamed entity.
*
* @param data The stream of data for this entity.
* @param contentLength The content length, if known. If no content length is set, then this entity will be close
* delimited.
* @param contentType The content type, if known.
*/
final case class Streamed(data: Source[ByteString, _], contentLength: Option[Long], contentType: Option[String]) extends HttpEntity {
def isKnownEmpty = false
def dataStream = data
def as(contentType: String) = copy(contentType = Some(contentType))
}
/**
* A chunked entity.
*
* @param chunks The stream of chunks for this entity. Must be zero or more [[HttpChunk.Chunk]] elements, followed
* by zero or one [[HttpChunk.LastChunk]] elements. Any elements after the [[HttpChunk.LastChunk]]
* element will be ignored. If no [[HttpChunk.LastChunk]] element is sent, then the last chunk will
* contain no trailers.
* @param contentType The content type, if known.
*/
final case class Chunked(chunks: Source[HttpChunk, _], contentType: Option[String]) extends HttpEntity {
def isKnownEmpty = false
def contentLength = None
def dataStream = chunks.collect {
case HttpChunk.Chunk(data) => data
}
def as(contentType: String) = copy(contentType = Some(contentType))
}
}
/**
* An HTTP chunk.
*
* May either be a [[HttpChunk.Chunk]] containing data, or a [[HttpChunk.LastChunk]], signifying the last chunk in
* a stream, optionally with trailing headers.
*/
sealed trait HttpChunk {
}
object HttpChunk {
/**
* A chunk.
*
* @param data The data for the chunk.
*/
final case class Chunk(data: ByteString) extends HttpChunk {
assert(data.nonEmpty, "Http chunks must not be empty")
}
/**
* The last chunk.
*
* @param trailers The trailers.
*/
final case class LastChunk(trailers: Headers) extends HttpChunk
}