Permalink
Browse files

! httpx: decode should remove Content-Encoding header from message

This fixes #316 by changing Decoder.decode to remove the Content-Encoding header.

I added extra round-trip unit tests to the DeflateSpec and GzipSpec but I also added some basic unit tests for the Encoder and Decoder abstract classes themselves.
  • Loading branch information...
agemooij committed Jul 20, 2013
1 parent 7222a6f commit f5b15357222948cc5dfd433aedf3ffd03d83c666
@@ -17,14 +17,22 @@
package spray.httpx.encoding
import spray.http._
+import HttpHeaders._
trait Decoder {
def encoding: HttpEncoding
- def decode[T <: HttpMessage](message: T): T#Self = message.mapEntity {
- _.flatMap { case HttpBody(contentType, buffer) HttpEntity(contentType, newDecompressor.decompress(buffer)) }
+ def decode[T <: HttpMessage](message: T): T#Self = message.entity match {
+ case HttpBody(contentType, buffer) if message.headers exists isContentEncodingHeader
+ message.withHeadersAndEntity(
+ headers = message.headers filterNot isContentEncodingHeader,
+ entity = HttpEntity(contentType, newDecompressor.decompress(buffer)))
+
+ case _ message.message
}
+ private val isContentEncodingHeader: HttpHeader Boolean = _.isInstanceOf[`Content-Encoding`]
+
def newDecompressor: Decompressor
}
@@ -38,4 +46,4 @@ abstract class Decompressor {
}
protected def decompress(buffer: Array[Byte], offset: Int): Int
-}
+}
@@ -52,7 +52,7 @@ class ResponseTransformationSpec extends Specification with RequestBuilding with
"support response decompression" in {
val pipeline = encode(Gzip) ~> echo ~> decode(Gzip)
- pipeline(Get("/abc", "Hello")).await === HttpResponse(200, "Hello", List(`Content-Encoding`(HttpEncodings.gzip)))
+ pipeline(Get("/abc", "Hello")).await === HttpResponse(200, "Hello")
}
"support request authentication" in {
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2011-2013 spray.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package spray.httpx.encoding
+
+import spray.http._
+import HttpHeaders._
+import org.specs2.mutable.Specification
+
+class DecoderSpec extends Specification with CodecSpecSupport {
+
+ "A Decoder" should {
+ "not transform the message if it doesn't contain a Content-Encoding header" in {
+ val request = HttpRequest(entity = HttpEntity(smallText))
+ DummyDecoder.decode(request) === request
+ }
+ "correctly transform the message if it contains a Content-Encoding header" in {
+ val request = HttpRequest(entity = HttpEntity(smallText), headers = List(`Content-Encoding`(DummyDecoder.encoding)))
+ val decoded = DummyDecoder.decode(request)
+ decoded.headers === Nil
+ decoded.entity === HttpEntity(dummyDecompress(smallText))
+ }
+ }
+
+ def dummyDecompress(s: String): String = new String(dummyDecompress(s.getBytes("UTF8")), "UTF8")
+ def dummyDecompress(bytes: Array[Byte]): Array[Byte] = DummyDecompressor.decompress(bytes)
+
+ case object DummyDecoder extends Decoder {
+ val encoding = HttpEncodings.compress
+ def newDecompressor = DummyDecompressor
+ }
+
+ case object DummyDecompressor extends Decompressor {
+ protected def decompress(buffer: Array[Byte], offset: Int) = {
+ output.write(buffer, 0, buffer.length)
+ output.write("compressed".getBytes("UTF8"))
+ -1
+ }
+ }
+}
@@ -16,6 +16,7 @@
package spray.httpx.encoding
+import spray.http.{ HttpRequest, HttpEntity }
import java.io.ByteArrayOutputStream
import java.util.zip.{ InflaterOutputStream, DeflaterOutputStream }
import org.specs2.mutable.Specification
@@ -41,6 +42,10 @@ class DeflateSpec extends Specification with CodecSpecSupport {
"properly roundtip encode/decode a large string" in {
ourInflate(ourDeflate(largeTextBytes)) must readAs(largeText)
}
+ "properly roundtip encode/decode an HttpRequest" in {
+ val request = HttpRequest(entity = HttpEntity(largeText))
+ Deflate.decode(Deflate.encode(request)) === request
+ }
"provide a better compression ratio than the standard Deflater/Inflater streams" in {
ourDeflate(largeTextBytes).length must be_<(streamDeflate(largeTextBytes).length)
}
@@ -68,4 +73,4 @@ class DeflateSpec extends Specification with CodecSpecSupport {
val ios = new InflaterOutputStream(output); ios.write(bytes); ios.close()
output.toByteArray
}
-}
+}
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2011-2013 spray.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package spray.httpx.encoding
+
+import spray.http._
+import HttpHeaders._
+import org.specs2.mutable.Specification
+
+class EncoderSpec extends Specification with CodecSpecSupport {
+
+ "An Encoder" should {
+ "not transform the message if messageFilter returns false" in {
+ val request = HttpRequest(entity = HttpEntity(smallText.getBytes("UTF8")))
+ DummyEncoder.encode(request) === request
+ }
+ "correctly transform the HttpMessage if messageFilter returns true" in {
+ val request = HttpRequest(entity = HttpEntity(smallText))
+ val encoded = DummyEncoder.encode(request)
+ encoded.headers === List(`Content-Encoding`(DummyEncoder.encoding))
+ encoded.entity === HttpEntity(dummyCompress(smallText))
+ }
+ }
+
+ def dummyCompress(s: String): String = new String(dummyCompress(s.getBytes("UTF8")), "UTF8")
+ def dummyCompress(bytes: Array[Byte]): Array[Byte] = DummyCompressor.compress(bytes).finish()
+
+ case object DummyEncoder extends Encoder {
+ val messageFilter = Encoder.DefaultFilter
+ val encoding = HttpEncodings.compress
+ def newCompressor = DummyCompressor
+ }
+
+ case object DummyCompressor extends Compressor {
+ def compress(buffer: Array[Byte]) = {
+ if (buffer.length > 0) output.write(buffer, 0, buffer.length)
+ output.write("compressed".getBytes("UTF8"))
+ this
+ }
+ def flush() = getBytes
+ def finish() = getBytes
+ }
+}
@@ -16,6 +16,7 @@
package spray.httpx.encoding
+import spray.http.{ HttpRequest, HttpEntity }
import java.io.{ ByteArrayInputStream, ByteArrayOutputStream }
import java.util.zip.{ ZipException, GZIPInputStream, GZIPOutputStream }
import org.parboiled.common.FileUtils
@@ -43,6 +44,10 @@ class GzipSpec extends Specification with CodecSpecSupport {
"properly roundtip encode/decode a large string" in {
ourGunzip(ourGzip(largeTextBytes)) must readAs(largeText)
}
+ "properly roundtip encode/decode an HttpRequest" in {
+ val request = HttpRequest(entity = HttpEntity(largeText))
+ Gzip.decode(Gzip.encode(request)) === request
+ }
"provide a better compression ratio than the standard Gzipr/Gunzip streams" in {
ourGzip(largeTextBytes).length must be_<(streamGzip(largeTextBytes).length)
}
@@ -87,4 +92,4 @@ class GzipSpec extends Specification with CodecSpecSupport {
output.toByteArray
}
-}
+}

0 comments on commit f5b1535

Please sign in to comment.