Skip to content
This repository has been archived by the owner on Apr 24, 2024. It is now read-only.

Commit

Permalink
! httpx: decode should remove Content-Encoding header from message
Browse files Browse the repository at this point in the history
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 22, 2013
1 parent 7222a6f commit f5b1535
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 6 deletions.
14 changes: 11 additions & 3 deletions spray-httpx/src/main/scala/spray/httpx/encoding/Decoder.scala
Expand Up @@ -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
}

Expand All @@ -38,4 +46,4 @@ abstract class Decompressor {
}

protected def decompress(buffer: Array[Byte], offset: Int): Int
}
}
Expand Up @@ -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 {
Expand Down
53 changes: 53 additions & 0 deletions spray-httpx/src/test/scala/spray/httpx/encoding/DecoderSpec.scala
@@ -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
}
}
}
Expand Up @@ -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
Expand All @@ -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)
}
Expand Down Expand Up @@ -68,4 +73,4 @@ class DeflateSpec extends Specification with CodecSpecSupport {
val ios = new InflaterOutputStream(output); ios.write(bytes); ios.close()
output.toByteArray
}
}
}
56 changes: 56 additions & 0 deletions spray-httpx/src/test/scala/spray/httpx/encoding/EncoderSpec.scala
@@ -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
}
}
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -87,4 +92,4 @@ class GzipSpec extends Specification with CodecSpecSupport {
output.toByteArray
}

}
}

0 comments on commit f5b1535

Please sign in to comment.