diff --git a/docs/documentation/spray-routing/code/docs/HttpServiceExamplesSpec.scala b/docs/documentation/spray-routing/code/docs/HttpServiceExamplesSpec.scala index cf86b40660..bef3f4fcca 100644 --- a/docs/documentation/spray-routing/code/docs/HttpServiceExamplesSpec.scala +++ b/docs/documentation/spray-routing/code/docs/HttpServiceExamplesSpec.scala @@ -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") } diff --git a/spray-routing-tests/src/test/scala/spray/routing/EncodingDirectivesSpec.scala b/spray-routing-tests/src/test/scala/spray/routing/EncodingDirectivesSpec.scala index e58f084f75..ebbe200c7b 100644 --- a/spray-routing-tests/src/test/scala/spray/routing/EncodingDirectivesSpec.scala +++ b/spray-routing-tests/src/test/scala/spray/routing/EncodingDirectivesSpec.scala @@ -262,7 +262,7 @@ 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) @@ -270,7 +270,7 @@ class EncodingDirectivesSpec extends RoutingSpec { } "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) @@ -278,7 +278,7 @@ class EncodingDirectivesSpec extends RoutingSpec { } "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) @@ -286,7 +286,7 @@ class EncodingDirectivesSpec extends RoutingSpec { } "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,7 +299,7 @@ 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!") @@ -307,7 +307,7 @@ class EncodingDirectivesSpec extends RoutingSpec { } "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) @@ -315,7 +315,7 @@ class EncodingDirectivesSpec extends RoutingSpec { } "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) @@ -323,7 +323,7 @@ class EncodingDirectivesSpec extends RoutingSpec { } "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,7 +336,7 @@ 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) @@ -344,7 +344,7 @@ class EncodingDirectivesSpec extends RoutingSpec { } "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) @@ -352,7 +352,7 @@ class EncodingDirectivesSpec extends RoutingSpec { } "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) @@ -360,7 +360,7 @@ class EncodingDirectivesSpec extends RoutingSpec { } "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 } diff --git a/spray-routing/src/main/scala/spray/routing/directives/EncodingDirectives.scala b/spray-routing/src/main/scala/spray/routing/directives/EncodingDirectives.scala index 9872ed6947..2750a3fa7b 100644 --- a/spray-routing/src/main/scala/spray/routing/directives/EncodingDirectives.scala +++ b/spray-routing/src/main/scala/spray/routing/directives/EncodingDirectives.scala @@ -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 +} \ No newline at end of file