Merge pull request #16195 from spray/w/15930-RangeDirectives
+htp #15930 import RangeDirectives from spray
This commit is contained in:
commit
04385f4d91
8 changed files with 341 additions and 6 deletions
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue