Merge pull request #16195 from spray/w/15930-RangeDirectives

+htp #15930 import RangeDirectives from spray
This commit is contained in:
Björn Antonsson 2014-11-05 10:54:33 +01:00
commit 04385f4d91
8 changed files with 341 additions and 6 deletions

View file

@ -106,6 +106,13 @@ sealed trait ResponseEntity extends HttpEntity with japi.ResponseEntity {
/* An entity that can be used for requests, responses, and body parts */
sealed trait UniversalEntity extends japi.UniversalEntity with MessageEntity with BodyPartEntity {
def withContentType(contentType: ContentType): UniversalEntity
def contentLength: Long
/**
* Transforms this' entities data bytes with a transformer that will produce exactly the number of bytes given as
* ``newContentLength``.
*/
def transformDataBytes(newContentLength: Long, transformer: () Transformer[ByteString, ByteString]): UniversalEntity
}
object HttpEntity {
@ -141,6 +148,7 @@ object HttpEntity {
*/
final case class Strict(contentType: ContentType, data: ByteString)
extends japi.HttpEntityStrict with UniversalEntity {
def contentLength: Long = data.length
def isKnownEmpty: Boolean = data.isEmpty
@ -159,6 +167,12 @@ object HttpEntity {
Chunked(contentType, Source.failed(ex))
}
}
override def transformDataBytes(newContentLength: Long, transformer: () Transformer[ByteString, ByteString]): UniversalEntity = {
val t = transformer()
val newData = (t.onNext(data) ++ t.onTermination(None)).join
assert(newData.length.toLong == newContentLength, s"Transformer didn't produce as much bytes (${newData.length}:'${newData.utf8String}') as claimed ($newContentLength)")
copy(data = newData)
}
def withContentType(contentType: ContentType): Strict =
if (contentType == this.contentType) this else copy(contentType = contentType)
@ -185,6 +199,8 @@ object HttpEntity {
HttpEntity.Chunked(contentType, chunks)
}
override def transformDataBytes(newContentLength: Long, transformer: () Transformer[ByteString, ByteString]): UniversalEntity =
Default(contentType, newContentLength, data.transform("transformDataBytes-with-new-length-Default", transformer))
def withContentType(contentType: ContentType): Default =
if (contentType == this.contentType) this else copy(contentType = contentType)

View file

@ -58,6 +58,46 @@ private[http] object StreamUtils {
override def onError(cause: scala.Throwable): Unit = throw f(cause)
}
def sliceBytesTransformer(start: Long, length: Long): Transformer[ByteString, ByteString] =
new Transformer[ByteString, ByteString] {
type State = Transformer[ByteString, ByteString]
def skipping = new State {
var toSkip = start
def onNext(element: ByteString): immutable.Seq[ByteString] =
if (element.length < toSkip) {
// keep skipping
toSkip -= element.length
Nil
} else {
become(taking(length))
// toSkip <= element.length <= Int.MaxValue
currentState.onNext(element.drop(toSkip.toInt))
}
}
def taking(initiallyRemaining: Long) = new State {
var remaining: Long = initiallyRemaining
def onNext(element: ByteString): immutable.Seq[ByteString] = {
val data = element.take(math.min(remaining, Int.MaxValue).toInt)
remaining -= data.size
if (remaining <= 0) become(finishing)
data :: Nil
}
}
def finishing = new State {
override def isComplete: Boolean = true
def onNext(element: ByteString): immutable.Seq[ByteString] =
throw new IllegalStateException("onNext called on complete stream")
}
var currentState: State = if (start > 0) skipping else taking(length)
def become(state: State): Unit = currentState = state
override def isComplete: Boolean = currentState.isComplete
def onNext(element: ByteString): immutable.Seq[ByteString] = currentState.onNext(element)
override def onTermination(e: Option[Throwable]): immutable.Seq[ByteString] = currentState.onTermination(e)
}
def mapEntityError(f: Throwable Throwable): RequestEntity RequestEntity =
_.transformDataBytes(() mapErrorTransformer(f))
}

View file

@ -8,13 +8,14 @@ import language.implicitConversions
import language.higherKinds
import java.nio.charset.Charset
import com.typesafe.config.Config
import akka.stream.FlattenStrategy
import akka.stream.{ FlowMaterializer, FlattenStrategy, Transformer }
import akka.stream.scaladsl.{ Flow, Source }
import scala.concurrent.Future
import scala.util.matching.Regex
import akka.event.LoggingAdapter
import akka.util.ByteString
import akka.actor._
import akka.stream.Transformer
import scala.collection.immutable
package object util {
private[http] val UTF8 = Charset.forName("UTF8")
@ -53,7 +54,7 @@ package object util {
.flatten(FlattenStrategy.concat)
}
private[http] implicit class SourceWithPrintEvent[T](val underlying: Source[T]) {
private[http] implicit class EnhancedSource[T](val underlying: Source[T]) {
def printEvent(marker: String): Source[T] =
underlying.transform("transform",
() new Transformer[T, T] {
@ -66,6 +67,14 @@ package object util {
Nil
}
})
/**
* Drain this stream into a Vector and provide it as a future value.
*
* FIXME: Should be part of akka-streams
*/
def collectAll(implicit materializer: FlowMaterializer): Future[immutable.Seq[T]] =
underlying.fold(Vector.empty[T])(_ :+ _)
}
private[http] def errorLogger(log: LoggingAdapter, msg: String): Transformer[ByteString, ByteString] =