withRangeSupport

Transforms the response from its inner route into a 206 Partial Content response if the client requested only part of the resource with a Range header.

Signature

def withRangeSupport(): Directive0
def withRangeSupport(rangeCountLimit: Int, rangeCoalescingThreshold:Long): Directive0

The signature shown is simplified, the real signature uses magnets. [1]

[1]See The Magnet Pattern for an explanation of magnet-based overloading.

Description

Augments responses to GET requests with an Accept-Ranges: bytes header and converts them into partial responses if the request contains a valid Range request header. The requested byte-ranges are coalesced (merged) if they lie closer together than the specified rangeCoalescingThreshold argument.

In order to prevent the server from becoming overloaded with trying to prepare multipart/byteranges responses for high numbers of potentially very small ranges the directive rejects requests requesting more than rangeCountLimit ranges with a TooManyRangesRejection. Requests with unsatisfiable ranges are rejected with an UnsatisfiableRangeRejection.

The withRangeSupport() form (without parameters) uses the range-coalescing-threshold and range-count-limit settings from the spray.routing configuration.

This directive is transparent to non-GET requests.

See also: https://tools.ietf.org/html/draft-ietf-httpbis-p5-range/

Example

val route =
  withRangeSupport(4, 2L) {
    complete("ABCDEFGH")
  }

Get() ~> addHeader(Range(ByteRange(3, 4))) ~> route ~> check {
  headers must contain(`Content-Range`(ContentRange(3, 4, 8)))
  status === StatusCodes.PartialContent
  responseAs[String] === "DE"
}

Get() ~> addHeader(Range(ByteRange(0, 1), ByteRange(1, 2), ByteRange(6, 7))) ~> route ~> check {
  headers must not(contain(like[HttpHeader] { case `Content-Range`(_, _)  ok }))
  responseAs[MultipartByteRanges] must beLike {
    case MultipartByteRanges(
      BodyPart(entity1, `Content-Range`(RangeUnit.Bytes, range1) +: _) +:
      BodyPart(entity2, `Content-Range`(RangeUnit.Bytes, range2) +: _) +: Seq()
    )  entity1.asString === "ABC" and range1 === ContentRange(0, 2, 8) and
      entity2.asString === "GH" and range2 === ContentRange(6, 7, 8)
  }
}