Permalink
Browse files

! routing: have `encodeResponse` automatically tie in `autoChunkFileB…

…ytes`

 In order to prevent `encodeResponse` from having to load the complete file content for compression it needs to automatically apply `autoChunkFileBytes` to its inner route. However, since the latter requires an implicit ActorRefFactory (for being able to spawn the chunk streaming actor underneath) we need to refactor `encodeResponse` to a magnet based signature.
 This change trickles down to all depending directives, most importantly `compressResponse`. The "no-paren overload" of `compressResponse` needs to go (since it cannot be magnetized), which opens up the chance to unify `compressResponse` and `compresseResponseWith`.
 For symmetry we also change `decompressRequest` accordingly.
  • Loading branch information...
sirthias committed Sep 4, 2013
1 parent fcc83fc commit e3defb4173471b4f9107e0921f504cb47059c747
@@ -71,7 +71,7 @@ class HttpServiceExamplesSpec extends Specification with Specs2RouteTest {
} ~
post {
// decompresses the request with Gzip or Deflate when required
- decompressRequest {
+ decompressRequest() {
// unmarshal with in-scope unmarshaller
entity(as[Order]) { order =>
// transfer to newly spawned actor
@@ -126,7 +126,7 @@ class HttpServiceExamplesSpec extends Specification with Specs2RouteTest {
cache(simpleCache) {
// optionally compresses the response with Gzip or Deflate
// if the client accepts compressed responses
- compressResponse {
+ compressResponse() {
// serve up static content from a JAR resource
getFromResourceDirectory("docs")
}
@@ -262,31 +262,31 @@ class EncodingDirectivesSpec extends RoutingSpec {
"the compressResponse directive" should {
"produce a GZIP compressed response if the request has no Accept-Encoding header" in {
Get("/") ~> {
- compressResponse { yeah }
+ compressResponse() { yeah }
} ~> check {
response must haveContentEncoding(gzip)
body === HttpEntity(ContentType(`text/plain`, `UTF-8`), yeahGzipped)
}
}
"produce a GZIP compressed response if the request has an `Accept-Encoding: gzip, deflate` header" in {
Get("/") ~> `Accept-Encoding`(gzip, deflate) ~> {
- compressResponse { yeah }
+ compressResponse() { yeah }
} ~> check {
response must haveContentEncoding(gzip)
body === HttpEntity(ContentType(`text/plain`, `UTF-8`), yeahGzipped)
}
}
"produce a Deflate compressed response if the request has an `Accept-Encoding: deflate` header" in {
Get("/") ~> `Accept-Encoding`(deflate) ~> {
- compressResponse { yeah }
+ compressResponse() { yeah }
} ~> check {
response must haveContentEncoding(deflate)
body === HttpEntity(ContentType(`text/plain`, `UTF-8`), yeahDeflated)
}
}
"produce an uncompressed response if the request has an `Accept-Encoding: identity` header" in {
Get("/") ~> `Accept-Encoding`(identity) ~> {
- compressResponse { completeOk }
+ compressResponse() { completeOk }
} ~> check {
response === Ok
response must haveNoContentEncoding
@@ -299,31 +299,31 @@ class EncodingDirectivesSpec extends RoutingSpec {
"the compressResponseIfRequested directive" should {
"produce an uncompressed response if the request has no Accept-Encoding header" in {
Get("/") ~> {
- compressResponseIfRequested { yeah }
+ compressResponseIfRequested() { yeah }
} ~> check {
response must haveNoContentEncoding
body === HttpEntity(ContentType(`text/plain`, `UTF-8`), "Yeah!")
}
}
"produce a GZIP compressed response if the request has an `Accept-Encoding: deflate, gzip` header" in {
Get("/") ~> `Accept-Encoding`(deflate, gzip) ~> {
- compressResponseIfRequested { yeah }
+ compressResponseIfRequested() { yeah }
} ~> check {
response must haveContentEncoding(gzip)
body === HttpEntity(ContentType(`text/plain`, `UTF-8`), yeahGzipped)
}
}
"produce a Deflate encoded response if the request has an `Accept-Encoding: deflate` header" in {
Get("/") ~> `Accept-Encoding`(deflate) ~> {
- compressResponseIfRequested { yeah }
+ compressResponseIfRequested() { yeah }
} ~> check {
response must haveContentEncoding(deflate)
body === HttpEntity(ContentType(`text/plain`, `UTF-8`), yeahDeflated)
}
}
"produce an uncompressed response if the request has an `Accept-Encoding: identity` header" in {
Get("/") ~> `Accept-Encoding`(identity) ~> {
- compressResponseIfRequested { completeOk }
+ compressResponseIfRequested() { completeOk }
} ~> check {
response === Ok
response must haveNoContentEncoding
@@ -336,31 +336,31 @@ class EncodingDirectivesSpec extends RoutingSpec {
"the compressResponseWith directive" should {
"produce a response compressed with the specified Encoder if the request has a matching Accept-Encoding header" in {
Get("/") ~> `Accept-Encoding`(gzip) ~> {
- compressResponseWith(Gzip) { yeah }
+ compressResponse(Gzip) { yeah }
} ~> check {
response must haveContentEncoding(gzip)
body === HttpEntity(ContentType(`text/plain`, `UTF-8`), yeahGzipped)
}
}
"produce a response compressed with one of the specified Encoders if the request has a matching Accept-Encoding header" in {
Get("/") ~> `Accept-Encoding`(deflate) ~> {
- compressResponseWith(Gzip, Deflate) { yeah }
+ compressResponse(Gzip, Deflate) { yeah }
} ~> check {
response must haveContentEncoding(deflate)
body === HttpEntity(ContentType(`text/plain`, `UTF-8`), yeahDeflated)
}
}
"produce a response compressed with the first of the specified Encoders if the request has no Accept-Encoding header" in {
Get("/") ~> {
- compressResponseWith(Gzip, Deflate) { yeah }
+ compressResponse(Gzip, Deflate) { yeah }
} ~> check {
response must haveContentEncoding(gzip)
body === HttpEntity(ContentType(`text/plain`, `UTF-8`), yeahGzipped)
}
}
"reject the request if it has an Accept-Encoding header with an encoding that doesn't match" in {
Get("/") ~> `Accept-Encoding`(deflate) ~> {
- compressResponseWith(Gzip) { yeah }
+ compressResponse(Gzip) { yeah }
} ~> check {
rejection === UnacceptedResponseEncodingRejection(gzip)
}
@@ -372,31 +372,29 @@ class EncodingDirectivesSpec extends RoutingSpec {
"the decompressRequest directive" should {
"decompress the request content if it has a `Content-Encoding: gzip` header and the content is gzip encoded" in {
Get("/", helloGzipped) ~> `Content-Encoding`(gzip) ~> {
- decompressRequest { echoRequestContent }
+ decompressRequest() { echoRequestContent }
} ~> check { entityAs[String] === "Hello" }
}
"decompress the request content if it has a `Content-Encoding: deflate` header and the content is deflate encoded" in {
Get("/", helloDeflated) ~> `Content-Encoding`(deflate) ~> {
- decompressRequest { echoRequestContent }
+ decompressRequest() { echoRequestContent }
} ~> check { entityAs[String] === "Hello" }
}
"decompress the request content if it has a `Content-Encoding: identity` header and the content is not encoded" in {
Get("/", "yes") ~> `Content-Encoding`(identity) ~> {
- decompressRequest { echoRequestContent }
+ decompressRequest() { echoRequestContent }
} ~> check { entityAs[String] === "yes" }
}
"decompress the request content using NoEncoding if no Content-Encoding header is present" in {
- Get("/", "yes") ~> decompressRequest { echoRequestContent } ~> check { entityAs[String] === "yes" }
+ Get("/", "yes") ~> decompressRequest() { echoRequestContent } ~> check { entityAs[String] === "yes" }
}
"reject the request if it has a `Content-Encoding: deflate` header but the request is compressed with Gzip" in {
Get("/", helloGzipped) ~> `Content-Encoding`(deflate) ~> {
- decompressRequest { echoRequestContent }
+ decompressRequest() { echoRequestContent }
} ~> check {
- rejections must beLike {
- case Seq(UnsupportedRequestEncodingRejection(`gzip`),
- CorruptRequestEncodingRejection(_),
- UnsupportedRequestEncodingRejection(`identity`)) ok
- }
+ rejections(0) === UnsupportedRequestEncodingRejection(gzip)
+ rejections(1) must beAnInstanceOf[CorruptRequestEncodingRejection]
+ rejections(2) === UnsupportedRequestEncodingRejection(identity)
}
}
}
@@ -406,18 +404,18 @@ class EncodingDirectivesSpec extends RoutingSpec {
"the decompressRequestWith directive" should {
"decompress the request content if its `Content-Encoding` header matches the specified encoder" in {
Get("/", helloGzipped) ~> `Content-Encoding`(gzip) ~> {
- decompressRequestWith(Gzip) { echoRequestContent }
+ decompressRequest(Gzip) { echoRequestContent }
} ~> check { entityAs[String] === "Hello" }
}
"reject the request if its `Content-Encoding` header doesn't match the specified encoder" in {
Get("/", helloGzipped) ~> `Content-Encoding`(deflate) ~> {
- decompressRequestWith(Gzip) { echoRequestContent }
+ decompressRequest(Gzip) { echoRequestContent }
} ~> check {
rejection === UnsupportedRequestEncodingRejection(gzip)
}
}
"reject the request when decompressing with GZIP and no Content-Encoding header is present" in {
- Get("/", "yes") ~> decompressRequestWith(Gzip) { echoRequestContent } ~> check {
+ Get("/", "yes") ~> decompressRequest(Gzip) { echoRequestContent } ~> check {
rejection === UnsupportedRequestEncodingRejection(gzip)
}
}
@@ -426,7 +424,7 @@ class EncodingDirectivesSpec extends RoutingSpec {
//# decompress-compress-combination-example
"the (decompressRequest & compressResponse) compound directive" should {
- val decompressCompress = (decompressRequest & compressResponse)
+ val decompressCompress = (decompressRequest() & compressResponse())
"decompress a GZIP compressed request and produce a GZIP compressed response if the request has no Accept-Encoding header" in {
Get("/", helloGzipped) ~> `Content-Encoding`(gzip) ~> {
decompressCompress { echoRequestContent }
@@ -455,7 +453,7 @@ class EncodingDirectivesSpec extends RoutingSpec {
//#
"the (decompressRequest & compressResponseIfRequested) compound directive" should {
- val decompressCompressIfRequested = (decompressRequest & compressResponseIfRequested)
+ val decompressCompressIfRequested = (decompressRequest() & compressResponseIfRequested())
"decode a GZIP encoded request and produce a non-encoded response if the request has no Accept-Encoding header" in {
Get("/", helloGzipped) ~> `Content-Encoding`(gzip) ~> {
decompressCompressIfRequested { echoRequestContent }
@@ -20,9 +20,11 @@ package directives
import spray.util._
import spray.http._
import spray.httpx.encoding._
+import akka.actor.ActorRefFactory
trait EncodingDirectives {
import BasicDirectives._
+ import ChunkingDirectives._
import MiscDirectives._
import RouteDirectives._
@@ -55,7 +57,8 @@ trait EncodingDirectives {
/**
* Wraps its inner Route with encoding support using the given Encoder.
*/
- def encodeResponse(encoder: Encoder) = {
+ def encodeResponse(magnet: EncodeResponseMagnet) = {
+ import magnet._
def applyEncoder = mapRequestContext { ctx
@volatile var compressor: Compressor = null
ctx.withHttpResponsePartMultiplied {
@@ -76,7 +79,8 @@ trait EncodingDirectives {
}
responseEncodingAccepted(encoder.encoding) &
applyEncoder &
- cancelAllRejections(ofType[UnacceptedResponseEncodingRejection])
+ cancelAllRejections(ofType[UnacceptedResponseEncodingRejection]) &
+ autoChunkFileBytes(autoChunkThreshold, autoChunkSize)
}
/**
@@ -88,41 +92,65 @@ trait EncodingDirectives {
.flatMap(if (_) pass else reject(UnacceptedResponseEncodingRejection(encoding)))
/**
- * Wraps its inner Route with response compression, only falling back to
- * uncompressed responses if the client specifically requests the "identity"
- * encoding and preferring Gzip over Deflate.
+ * Wraps its inner Route with response compression, using the specified
+ * encoders in the given order of preference.
+ * If no encoders are specifically given Gzip, Deflate and NoEncoding
+ * are used in this order, depending on what the client accepts.
*/
- def compressResponse: Directive0 = compressResponseWith(Gzip, Deflate, NoEncoding)
+ def compressResponse(magnet: CompressResponseMagnet): Directive0 = {
+ import magnet._
+ encoders.tail.foldLeft(encodeResponse(encoders.head)) { (r, encoder) r | encodeResponse(encoder) }
+ }
/**
* Wraps its inner Route with response compression if and only if the client
- * specifically requests compression with an Accept-Encoding header.
- */
- def compressResponseIfRequested: Directive0 = compressResponseWith(NoEncoding, Gzip, Deflate)
-
- /**
- * Wraps its inner Route with response compression, using the specified
- * encoders in order of preference.
+ * specifically requests compression with an `Accept-Encoding` header.
*/
- def compressResponseWith(first: Encoder, more: Encoder*): Directive0 =
- if (more.isEmpty) encodeResponse(first)
- else more.foldLeft(encodeResponse(first)) { (r, encoder) r | encodeResponse(encoder) }
+ def compressResponseIfRequested(magnet: CompressResponseMagnet): Directive0 = {
+ import magnet._
+ compressResponse(NoEncoding, Gzip, Deflate)
+ }
/**
- * Wraps its inner Route with request decompression, assuming
- * Gzip compressed requests but falling back to Deflate or no
- * compression if the request contains the relevant Content-Encoding
- * header.
+ * Decompresses the incoming request if it is GZip or Deflate encoded.
+ * Uncompressed requests are passed on to the inner route unchanged.
*/
- def decompressRequest: Directive0 = decompressRequestWith(Gzip, Deflate, NoEncoding)
+ def decompressRequest(): Directive0 = decompressRequest(Gzip, Deflate, NoEncoding)
/**
- * Wraps its inner Route with request decompression, trying the specified
- * decoders in turn.
+ * Decompresses the incoming request if it encoded with one of the given
+ * encoders. If the request encoding doesn't match one of the given encoders
+ * the request is rejected with an `UnsupportedRequestEncodingRejection`.
*/
- def decompressRequestWith(first: Decoder, more: Decoder*): Directive0 =
+ def decompressRequest(first: Decoder, more: Decoder*): Directive0 =
if (more.isEmpty) decodeRequest(first)
else more.foldLeft(decodeRequest(first)) { (r, decoder) r | decodeRequest(decoder) }
}
object EncodingDirectives extends EncodingDirectives
+
+class EncodeResponseMagnet(val encoder: Encoder, val autoChunkThreshold: Long = 128 * 1024,
+ val autoChunkSize: Int = 128 * 1024)(implicit val refFactory: ActorRefFactory)
+object EncodeResponseMagnet {
+ implicit def fromEncoder(encoder: Encoder)(implicit factory: ActorRefFactory): EncodeResponseMagnet =
+ new EncodeResponseMagnet(encoder)
+ implicit def fromEncoderThresholdAndChunkSize(t: (Encoder, Long, Int))(implicit factory: ActorRefFactory): EncodeResponseMagnet =
+ new EncodeResponseMagnet(t._1, t._2, t._3)
+}
+
+class CompressResponseMagnet(val encoders: List[Encoder])(implicit val refFactory: ActorRefFactory)
+object CompressResponseMagnet {
+ implicit def fromUnit(u: Unit)(implicit refFactory: ActorRefFactory): CompressResponseMagnet =
+ new CompressResponseMagnet(Gzip :: Deflate :: NoEncoding :: Nil)
+ implicit def fromEncoders1(e: Encoder)(implicit refFactory: ActorRefFactory): CompressResponseMagnet =
+ new CompressResponseMagnet(e :: Nil)
+ implicit def fromEncoders2(t: (Encoder, Encoder))(implicit refFactory: ActorRefFactory): CompressResponseMagnet =
+ new CompressResponseMagnet(t._1 :: t._2 :: Nil)
+ implicit def fromEncoders3(t: (Encoder, Encoder, Encoder))(implicit refFactory: ActorRefFactory): CompressResponseMagnet =
+ new CompressResponseMagnet(t._1 :: t._2 :: t._3 :: Nil)
+}
+
+class RefFactoryMagnet(implicit val refFactory: ActorRefFactory)
+object RefFactoryMagnet {
+ implicit def fromUnit(u: Unit)(implicit refFactory: ActorRefFactory): RefFactoryMagnet = new RefFactoryMagnet
+}

0 comments on commit e3defb4

Please sign in to comment.