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

@ -0,0 +1,125 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.server
package directives
import akka.http.model.StatusCodes._
import akka.http.model._
import akka.http.model.headers._
import akka.http.util._
import org.scalatest.{ Inside, Inspectors }
import scala.concurrent.Await
import scala.concurrent.duration._
class RangeDirectivesSpec extends RoutingSpec with Inspectors with Inside {
lazy val wrs =
mapSettings(_.copy(rangeCountLimit = 10, rangeCoalescingThreshold = 1L)) &
withRangeSupport
def bytes(length: Byte) = Array.tabulate[Byte](length)(_.toByte)
"The `withRangeSupport` directive" should {
def completeWithRangedBytes(length: Byte) = wrs(complete(bytes(length)))
"return an Accept-Ranges(bytes) header for GET requests" in {
Get() ~> { wrs { complete("any") } } ~> check {
headers should contain(`Accept-Ranges`(RangeUnits.Bytes))
}
}
"not return an Accept-Ranges(bytes) header for non-GET requests" in {
Put() ~> { wrs { complete("any") } } ~> check {
headers should not contain `Accept-Ranges`(RangeUnits.Bytes)
}
}
"return a Content-Range header for a ranged request with a single range" in {
Get() ~> addHeader(Range(ByteRange(0, 1))) ~> completeWithRangedBytes(10) ~> check {
headers should contain(`Content-Range`(ContentRange(0, 1, 10)))
status shouldEqual PartialContent
responseAs[Array[Byte]] shouldEqual bytes(2)
}
}
"return a partial response for a ranged request with a single range with undefined lastBytePosition" in {
Get() ~> addHeader(Range(ByteRange.fromOffset(5))) ~> completeWithRangedBytes(10) ~> check {
responseAs[Array[Byte]] shouldEqual Array[Byte](5, 6, 7, 8, 9)
}
}
"return a partial response for a ranged request with a single suffix range" in {
Get() ~> addHeader(Range(ByteRange.suffix(1))) ~> completeWithRangedBytes(10) ~> check {
responseAs[Array[Byte]] shouldEqual Array[Byte](9)
}
}
"return a partial response for a ranged request with a overlapping suffix range" in {
Get() ~> addHeader(Range(ByteRange.suffix(100))) ~> completeWithRangedBytes(10) ~> check {
responseAs[Array[Byte]] shouldEqual bytes(10)
}
}
"be transparent to non-GET requests" in {
Post() ~> addHeader(Range(ByteRange(1, 2))) ~> completeWithRangedBytes(5) ~> check {
responseAs[Array[Byte]] shouldEqual bytes(5)
}
}
"be transparent to non-200 responses" in {
Get() ~> addHeader(Range(ByteRange(1, 2))) ~> sealRoute(wrs(reject())) ~> check {
status == NotFound
headers.exists { case `Content-Range`(_, _) true; case _ false } shouldEqual false
}
}
"reject an unsatisfiable single range" in {
Get() ~> addHeader(Range(ByteRange(100, 200))) ~> completeWithRangedBytes(10) ~> check {
rejection shouldEqual UnsatisfiableRangeRejection(Seq(ByteRange(100, 200)), 10)
}
}
"reject an unsatisfiable single suffix range with length 0" in {
Get() ~> addHeader(Range(ByteRange.suffix(0))) ~> completeWithRangedBytes(42) ~> check {
rejection shouldEqual UnsatisfiableRangeRejection(Seq(ByteRange.suffix(0)), 42)
}
}
"return a mediaType of 'multipart/byteranges' for a ranged request with multiple ranges" in {
Get() ~> addHeader(Range(ByteRange(0, 10), ByteRange(0, 10))) ~> completeWithRangedBytes(10) ~> check {
mediaType.withParams(Map.empty) shouldEqual MediaTypes.`multipart/byteranges`
}
}
"return a 'multipart/byteranges' for a ranged request with multiple coalesced ranges with preserved order" in {
Get() ~> addHeader(Range(ByteRange(5, 10), ByteRange(0, 1), ByteRange(1, 2))) ~> {
wrs { complete("Some random and not super short entity.") }
} ~> check {
header[`Content-Range`] should be(None)
val parts = Await.result(responseAs[Multipart.ByteRanges].parts.collectAll, 1.second)
parts.size shouldEqual 2
inside(parts(0)) {
case Multipart.ByteRanges.BodyPart(range, entity, unit, headers)
range shouldEqual ContentRange.Default(5, 10, Some(39))
unit shouldEqual RangeUnits.Bytes
Await.result(entity.dataBytes.utf8String, 100.millis) shouldEqual "random"
}
inside(parts(1)) {
case Multipart.ByteRanges.BodyPart(range, entity, unit, headers)
range shouldEqual ContentRange.Default(0, 2, Some(39))
unit shouldEqual RangeUnits.Bytes
Await.result(entity.dataBytes.utf8String, 100.millis) shouldEqual "Som"
}
}
}
"reject a request with too many requested ranges" in {
val ranges = (1 to 20).map(a ByteRange.fromOffset(a))
Get() ~> addHeader(Range(ranges)) ~> completeWithRangedBytes(100) ~> check {
rejection shouldEqual TooManyRangesRejection(10)
}
}
}
}