=htc Improve handling of Transfer-Encoding header, closes #15490

This commit is contained in:
Mathias 2014-09-24 11:03:00 +02:00
parent 0afebd1c39
commit 36904eadcb
12 changed files with 211 additions and 59 deletions

View file

@ -247,4 +247,10 @@ private[http] abstract class HttpMessageParser[Output >: ParserOutput.MessageOut
val chunks = Flow(entityChunks).collect { case ParserOutput.EntityChunk(chunk) chunk }.toPublisher()
HttpEntity.Chunked(contentType(cth), chunks)
}
def addTransferEncodingWithChunkedPeeled(headers: List[HttpHeader], teh: `Transfer-Encoding`): List[HttpHeader] =
teh.withChunkedPeeled match {
case Some(x) x :: headers
case None headers
}
}

View file

@ -110,7 +110,8 @@ private[http] class HttpRequestParser(_settings: ParserSettings,
clh: Option[`Content-Length`], cth: Option[`Content-Type`], teh: Option[`Transfer-Encoding`],
hostHeaderPresent: Boolean, closeAfterResponseCompletion: Boolean): StateResult =
if (hostHeaderPresent || protocol == HttpProtocols.`HTTP/1.0`) {
def emitRequestStart(createEntity: Publisher[ParserOutput.RequestOutput] RequestEntity) =
def emitRequestStart(createEntity: Publisher[ParserOutput.RequestOutput] RequestEntity,
headers: List[HttpHeader] = headers) =
emit(ParserOutput.RequestStart(method, uri, protocol, headers, createEntity, closeAfterResponseCompletion))
teh match {
@ -135,12 +136,14 @@ private[http] class HttpRequestParser(_settings: ParserSettings,
}
case Some(te)
if (te.encodings.size == 1 && te.hasChunked) {
val completedHeaders = addTransferEncodingWithChunkedPeeled(headers, te)
if (te.isChunked) {
if (clh.isEmpty) {
emitRequestStart(chunkedEntity(cth))
emitRequestStart(chunkedEntity(cth), completedHeaders)
parseChunk(input, bodyStart, closeAfterResponseCompletion)
} else fail("A chunked request must not contain a Content-Length header.")
} else fail(NotImplemented, s"`$te` is not supported by this server")
} else parseEntity(completedHeaders, protocol, input, bodyStart, clh, cth, teh = None, hostHeaderPresent,
closeAfterResponseCompletion)
}
} else fail("Request is missing required `Host` header")
}

View file

@ -75,7 +75,8 @@ private[http] class HttpResponseParser(_settings: ParserSettings,
def parseEntity(headers: List[HttpHeader], protocol: HttpProtocol, input: ByteString, bodyStart: Int,
clh: Option[`Content-Length`], cth: Option[`Content-Type`], teh: Option[`Transfer-Encoding`],
hostHeaderPresent: Boolean, closeAfterResponseCompletion: Boolean): StateResult = {
def emitResponseStart(createEntity: Publisher[ParserOutput.ResponseOutput] ResponseEntity) =
def emitResponseStart(createEntity: Publisher[ParserOutput.ResponseOutput] ResponseEntity,
headers: List[HttpHeader] = headers) =
emit(ParserOutput.ResponseStart(statusCode, protocol, headers, createEntity, closeAfterResponseCompletion))
def finishEmptyResponse() = {
emitResponseStart(emptyEntity(cth))
@ -106,12 +107,14 @@ private[http] class HttpResponseParser(_settings: ParserSettings,
}
case Some(te)
if (te.encodings.size == 1 && te.hasChunked) {
val completedHeaders = addTransferEncodingWithChunkedPeeled(headers, te)
if (te.isChunked) {
if (clh.isEmpty) {
emitResponseStart(chunkedEntity(cth))
emitResponseStart(chunkedEntity(cth), completedHeaders)
parseChunk(input, bodyStart, closeAfterResponseCompletion)
} else fail("A chunked request must not contain a Content-Length header.")
} else fail(s"`$te` is not supported by this client")
} else parseEntity(completedHeaders, protocol, input, bodyStart, clh, cth, teh = None, hostHeaderPresent,
closeAfterResponseCompletion)
}
} else finishEmptyResponse()
}

View file

@ -46,45 +46,54 @@ private[http] class HttpRequestRendererFactory(userAgentHeader: Option[headers.`
def render(h: HttpHeader) = r ~~ h ~~ CrLf
@tailrec def renderHeaders(remaining: List[HttpHeader], hostHeaderSeen: Boolean = false,
userAgentSeen: Boolean = false): Unit =
userAgentSeen: Boolean = false, transferEncodingSeen: Boolean = false): Unit =
remaining match {
case head :: tail head match {
case x: `Content-Length`
suppressionWarning(log, x, "explicit `Content-Length` header is not allowed. Use the appropriate HttpEntity subtype.")
renderHeaders(tail, hostHeaderSeen, userAgentSeen)
renderHeaders(tail, hostHeaderSeen, userAgentSeen, transferEncodingSeen)
case x: `Content-Type`
suppressionWarning(log, x, "explicit `Content-Type` header is not allowed. Set `HttpRequest.entity.contentType` instead.")
renderHeaders(tail, hostHeaderSeen, userAgentSeen)
renderHeaders(tail, hostHeaderSeen, userAgentSeen, transferEncodingSeen)
case `Transfer-Encoding`(_)
suppressionWarning(log, head)
renderHeaders(tail, hostHeaderSeen, userAgentSeen)
case x: `Transfer-Encoding`
x.withChunkedPeeled match {
case None
suppressionWarning(log, head)
renderHeaders(tail, hostHeaderSeen, userAgentSeen, transferEncodingSeen)
case Some(te)
// if the user applied some custom transfer-encoding we need to keep the header
render(if (entity.isChunked && !entity.isKnownEmpty) te.withChunked else te)
renderHeaders(tail, hostHeaderSeen, userAgentSeen, transferEncodingSeen = true)
}
case x: `Host`
render(x)
renderHeaders(tail, hostHeaderSeen = true, userAgentSeen)
renderHeaders(tail, hostHeaderSeen = true, userAgentSeen, transferEncodingSeen)
case x: `User-Agent`
render(x)
renderHeaders(tail, hostHeaderSeen, userAgentSeen = true)
renderHeaders(tail, hostHeaderSeen, userAgentSeen = true, transferEncodingSeen)
case x: `Raw-Request-URI` // we never render this header
renderHeaders(tail, hostHeaderSeen, userAgentSeen)
renderHeaders(tail, hostHeaderSeen, userAgentSeen, transferEncodingSeen)
case x: RawHeader if (x is "content-type") || (x is "content-length") || (x is "transfer-encoding") ||
(x is "host") || (x is "user-agent")
suppressionWarning(log, x, "illegal RawHeader")
renderHeaders(tail, hostHeaderSeen, userAgentSeen)
renderHeaders(tail, hostHeaderSeen, userAgentSeen, transferEncodingSeen)
case x
render(x)
renderHeaders(tail, hostHeaderSeen, userAgentSeen)
renderHeaders(tail, hostHeaderSeen, userAgentSeen, transferEncodingSeen)
}
case Nil
if (!hostHeaderSeen) r ~~ Host(ctx.serverAddress) ~~ CrLf
if (!userAgentSeen && userAgentHeader.isDefined) r ~~ userAgentHeader.get ~~ CrLf
if (entity.isChunked && !entity.isKnownEmpty && !transferEncodingSeen)
r ~~ `Transfer-Encoding` ~~ ChunkedBytes ~~ CrLf
}
def renderContentLength(contentLength: Long): Unit = {
@ -105,10 +114,10 @@ private[http] class HttpRequestRendererFactory(userAgentHeader: Option[headers.`
case HttpEntity.Default(_, contentLength, data)
renderContentLength(contentLength)
renderByteStrings(r,
Flow(data).transform("checkContentLenght", () new CheckContentLengthTransformer(contentLength)).toPublisher())
Flow(data).transform("checkContentLength", () new CheckContentLengthTransformer(contentLength)).toPublisher())
case HttpEntity.Chunked(_, chunks)
r ~~ `Transfer-Encoding` ~~ ChunkedBytes ~~ CrLf ~~ CrLf
r ~~ CrLf
renderByteStrings(r, Flow(chunks).transform("chunkTransform", () new ChunkTransformer).toPublisher())
}

View file

@ -68,38 +68,53 @@ private[http] class HttpResponseRendererFactory(serverHeader: Option[headers.Ser
def render(h: HttpHeader) = r ~~ h ~~ CrLf
def mustRenderTransferEncodingChunkedHeader =
entity.isChunked && (!entity.isKnownEmpty || ctx.requestMethod == HttpMethods.HEAD) && (ctx.requestProtocol == `HTTP/1.1`)
@tailrec def renderHeaders(remaining: List[HttpHeader], alwaysClose: Boolean = false,
connHeader: Connection = null, serverHeaderSeen: Boolean = false): Unit =
connHeader: Connection = null, serverHeaderSeen: Boolean = false,
transferEncodingSeen: Boolean = false): Unit =
remaining match {
case head :: tail head match {
case x: `Content-Length`
suppressionWarning(log, x, "explicit `Content-Length` header is not allowed. Use the appropriate HttpEntity subtype.")
renderHeaders(tail, alwaysClose, connHeader, serverHeaderSeen)
renderHeaders(tail, alwaysClose, connHeader, serverHeaderSeen, transferEncodingSeen)
case x: `Content-Type`
suppressionWarning(log, x, "explicit `Content-Type` header is not allowed. Set `HttpResponse.entity.contentType` instead.")
renderHeaders(tail, alwaysClose, connHeader, serverHeaderSeen)
renderHeaders(tail, alwaysClose, connHeader, serverHeaderSeen, transferEncodingSeen)
case `Transfer-Encoding`(_) | Date(_)
case Date(_)
suppressionWarning(log, head)
renderHeaders(tail, alwaysClose, connHeader, serverHeaderSeen)
renderHeaders(tail, alwaysClose, connHeader, serverHeaderSeen, transferEncodingSeen)
case x: `Transfer-Encoding`
x.withChunkedPeeled match {
case None
suppressionWarning(log, head)
renderHeaders(tail, alwaysClose, connHeader, serverHeaderSeen, transferEncodingSeen)
case Some(te)
// if the user applied some custom transfer-encoding we need to keep the header
render(if (mustRenderTransferEncodingChunkedHeader) te.withChunked else te)
renderHeaders(tail, alwaysClose, connHeader, serverHeaderSeen, transferEncodingSeen = true)
}
case x: `Connection`
val connectionHeader = if (connHeader eq null) x else Connection(x.tokens ++ connHeader.tokens)
renderHeaders(tail, alwaysClose, connectionHeader, serverHeaderSeen)
renderHeaders(tail, alwaysClose, connectionHeader, serverHeaderSeen, transferEncodingSeen)
case x: `Server`
render(x)
renderHeaders(tail, alwaysClose, connHeader, serverHeaderSeen = true)
renderHeaders(tail, alwaysClose, connHeader, serverHeaderSeen = true, transferEncodingSeen)
case x: RawHeader if (x is "content-type") || (x is "content-length") || (x is "transfer-encoding") ||
(x is "date") || (x is "server") || (x is "connection")
suppressionWarning(log, x, "illegal RawHeader")
renderHeaders(tail, alwaysClose, connHeader, serverHeaderSeen)
renderHeaders(tail, alwaysClose, connHeader, serverHeaderSeen, transferEncodingSeen)
case x
render(x)
renderHeaders(tail, alwaysClose, connHeader, serverHeaderSeen)
renderHeaders(tail, alwaysClose, connHeader, serverHeaderSeen, transferEncodingSeen)
}
case Nil
@ -113,6 +128,8 @@ private[http] class HttpResponseRendererFactory(serverHeader: Option[headers.Ser
case `HTTP/1.1` if close r ~~ Connection ~~ CloseBytes ~~ CrLf
case _ // no need for rendering
}
if (mustRenderTransferEncodingChunkedHeader && !transferEncodingSeen)
r ~~ `Transfer-Encoding` ~~ ChunkedBytes ~~ CrLf
}
def byteStrings(entityBytes: Publisher[ByteString]): List[Publisher[ByteString]] =
@ -145,8 +162,6 @@ private[http] class HttpResponseRendererFactory(serverHeader: Option[headers.Ser
else {
renderHeaders(headers.toList)
renderEntityContentType(r, entity)
if (!entity.isKnownEmpty || ctx.requestMethod == HttpMethods.HEAD)
r ~~ `Transfer-Encoding` ~~ ChunkedBytes ~~ CrLf
r ~~ CrLf
byteStrings(Flow(chunks).transform("renderChunks", () new ChunkTransformer).toPublisher())
}

View file

@ -536,7 +536,15 @@ object `Transfer-Encoding` extends ModeledCompanion {
final case class `Transfer-Encoding`(encodings: immutable.Seq[TransferEncoding]) extends japi.headers.TransferEncoding with ModeledHeader {
require(encodings.nonEmpty, "encodings must not be empty")
import `Transfer-Encoding`.encodingsRenderer
def hasChunked: Boolean = encodings contains TransferEncodings.chunked
def isChunked: Boolean = encodings.last == TransferEncodings.chunked
def withChunked: `Transfer-Encoding` = if (isChunked) this else `Transfer-Encoding`(encodings :+ TransferEncodings.chunked)
def withChunkedPeeled: Option[`Transfer-Encoding`] =
if (isChunked) {
encodings.init match {
case Nil None
case remaining Some(`Transfer-Encoding`(remaining))
}
} else Some(this)
def renderValue[R <: Rendering](r: R): r.type = r ~~ encodings
protected def companion = `Transfer-Encoding`

View file

@ -7,7 +7,7 @@ package akka.http
import language.implicitConversions
import java.nio.charset.Charset
import com.typesafe.config.Config
import org.reactivestreams.{Subscription, Subscriber, Publisher}
import org.reactivestreams.{ Subscription, Subscriber, Publisher }
import scala.util.matching.Regex
import akka.event.LoggingAdapter
import akka.util.ByteString

View file

@ -133,6 +133,16 @@ class RequestParserSpec extends FreeSpec with Matchers with BeforeAndAfterAll {
closeAfterResponseCompletion shouldEqual Seq(true)
}
"with a funky `Transfer-Encoding` header" in new Test {
"""GET / HTTP/1.1
|Transfer-Encoding: foo, chunked, bar
|Host: x
|
|""" should parseTo(HttpRequest(GET, "/", List(`Transfer-Encoding`(TransferEncodings.Extension("foo"),
TransferEncodings.chunked, TransferEncodings.Extension("bar")), Host("x"))))
closeAfterResponseCompletion shouldEqual Seq(false)
}
"with several identical `Content-Type` headers" in new Test {
"""GET /data HTTP/1.1
|Host: x
@ -207,6 +217,17 @@ class RequestParserSpec extends FreeSpec with Matchers with BeforeAndAfterAll {
}
}
"properly parse a chunked request with additional transfer encodings" in new Test {
"""PATCH /data HTTP/1.1
|Transfer-Encoding: fancy, chunked
|Content-Type: application/pdf
|Host: ping
|
|""" should parseTo(HttpRequest(PATCH, "/data", List(`Transfer-Encoding`(TransferEncodings.Extension("fancy")),
Host("ping")), HttpEntity.Chunked(`application/pdf`, publisher())))
closeAfterResponseCompletion shouldEqual Seq(false)
}
"reject a message chunk with" - {
val start =
"""PATCH /data HTTP/1.1

View file

@ -66,6 +66,19 @@ class ResponseParserSpec extends FreeSpec with Matchers with BeforeAndAfterAll {
closeAfterResponseCompletion shouldEqual Seq(false)
}
"a response funky `Transfer-Encoding` header" in new Test {
override def parserSettings: ParserSettings =
super.parserSettings.withCustomStatusCodes(ServerOnTheMove)
"""HTTP/1.1 331 Server on the move
|Transfer-Encoding: foo, chunked, bar
|Content-Length: 0
|
|""" should parseTo(HttpResponse(ServerOnTheMove, List(`Transfer-Encoding`(TransferEncodings.Extension("foo"),
TransferEncodings.chunked, TransferEncodings.Extension("bar")))))
closeAfterResponseCompletion shouldEqual Seq(false)
}
"a response with one header, a body, but no Content-Length header" in new Test {
"""HTTP/1.0 404 Not Found
|Host: api.example.com
@ -167,6 +180,16 @@ class ResponseParserSpec extends FreeSpec with Matchers with BeforeAndAfterAll {
publisher(LastChunk("nice=true", List(RawHeader("Bar", "xyz"), RawHeader("Foo", "pip apo"))))))))
closeAfterResponseCompletion shouldEqual Seq(false)
}
"response with additional transfer encodings" in new Test {
"""HTTP/1.1 200 OK
|Transfer-Encoding: fancy, chunked
|Content-Type: application/pdf
|
|""" should parseTo(HttpResponse(headers = List(`Transfer-Encoding`(TransferEncodings.Extension("fancy"))),
entity = HttpEntity.Chunked(`application/pdf`, publisher())))
closeAfterResponseCompletion shouldEqual Seq(false)
}
}
"reject a response with" - {

View file

@ -90,7 +90,7 @@ class RequestRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll
HttpRequest(PUT, "/abc/xyz", List(
RawHeader("X-Fancy", "naa"),
RawHeader("Cache-Control", "public"),
Host("spray.io"))).withEntity(HttpEntity(ContentTypes.NoContentType, "The content please!")) should renderTo {
Host("spray.io")), HttpEntity(ContentTypes.NoContentType, "The content please!")) should renderTo {
"""PUT /abc/xyz HTTP/1.1
|X-Fancy: naa
|Cache-Control: public
@ -101,12 +101,26 @@ class RequestRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll
|The content please!"""
}
}
"PUT request with a custom Transfer-Encoding header" in new TestSetup() {
HttpRequest(PUT, "/abc/xyz", List(`Transfer-Encoding`(TransferEncodings.Extension("fancy"))))
.withEntity("The content please!") should renderTo {
"""PUT /abc/xyz HTTP/1.1
|Transfer-Encoding: fancy
|Host: test.com:8080
|User-Agent: spray-can/1.0.0
|Content-Type: text/plain; charset=UTF-8
|Content-Length: 19
|
|The content please!"""
}
}
}
"proper render a chunked" - {
"PUT request with empty chunk stream and custom Content-Type" in new TestSetup() {
HttpRequest(PUT, "/abc/xyz").withEntity(Chunked(ContentTypes.`text/plain`, publisher())) should renderTo {
HttpRequest(PUT, "/abc/xyz", entity = Chunked(ContentTypes.`text/plain`, publisher())) should renderTo {
"""PUT /abc/xyz HTTP/1.1
|Host: test.com:8080
|User-Agent: spray-can/1.0.0
@ -118,13 +132,32 @@ class RequestRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll
}
"POST request with body" in new TestSetup() {
HttpRequest(POST, "/abc/xyz")
.withEntity(Chunked(ContentTypes.`text/plain`, publisher("XXXX", "ABCDEFGHIJKLMNOPQRSTUVWXYZ"))) should renderTo {
HttpRequest(POST, "/abc/xyz", entity = Chunked(ContentTypes.`text/plain`,
publisher("XXXX", "ABCDEFGHIJKLMNOPQRSTUVWXYZ"))) should renderTo {
"""POST /abc/xyz HTTP/1.1
|Host: test.com:8080
|User-Agent: spray-can/1.0.0
|Transfer-Encoding: chunked
|Content-Type: text/plain
|
|4
|XXXX
|1a
|ABCDEFGHIJKLMNOPQRSTUVWXYZ
|0
|
|"""
}
}
"POST request with custom Transfer-Encoding header" in new TestSetup() {
HttpRequest(POST, "/abc/xyz", List(`Transfer-Encoding`(TransferEncodings.Extension("fancy"))),
entity = Chunked(ContentTypes.`text/plain`, publisher("XXXX", "ABCDEFGHIJKLMNOPQRSTUVWXYZ"))) should renderTo {
"""POST /abc/xyz HTTP/1.1
|Transfer-Encoding: fancy, chunked
|Host: test.com:8080
|User-Agent: spray-can/1.0.0
|Content-Type: text/plain
|Transfer-Encoding: chunked
|
|4
|XXXX

View file

@ -31,8 +31,8 @@ class ResponseRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll
implicit val materializer = FlowMaterializer()
"The response preparation logic should properly render" - {
"a response with no body" - {
"with status 200, no headers and no body" in new TestSetup() {
"a response with no body," - {
"status 200 and no headers" in new TestSetup() {
HttpResponse(200) should renderTo {
"""HTTP/1.1 200 OK
|Server: akka-http/1.0.0
@ -43,7 +43,7 @@ class ResponseRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll
}
}
"with status 304, a few headers and no body" in new TestSetup() {
"status 304 and a few headers" in new TestSetup() {
HttpResponse(304, List(RawHeader("X-Fancy", "of course"), RawHeader("Age", "0"))) should renderTo {
"""HTTP/1.1 304 Not Modified
|X-Fancy: of course
@ -55,7 +55,7 @@ class ResponseRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll
|"""
}
}
"with a custom status code, no headers and no body" in new TestSetup() {
"a custom status code and no headers" in new TestSetup() {
HttpResponse(ServerOnTheMove) should renderTo {
"""HTTP/1.1 330 Server on the move
|Server: akka-http/1.0.0
@ -83,8 +83,8 @@ class ResponseRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll
}
}
"a response with a Strict body" - {
"with status 400, a few headers and a body" in new TestSetup() {
"a response with a Strict body," - {
"status 400 and a few headers" in new TestSetup() {
HttpResponse(400, List(RawHeader("Age", "30"), Connection("Keep-Alive")), "Small f*ck up overhere!") should renderTo {
"""HTTP/1.1 400 Bad Request
|Age: 30
@ -97,7 +97,7 @@ class ResponseRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll
}
}
"with status 400, a few headers and a body with an explicitly suppressed Content Type header" in new TestSetup() {
"status 400, a few headers and a body with an explicitly suppressed Content Type header" in new TestSetup() {
HttpResponse(400, List(RawHeader("Age", "30"), Connection("Keep-Alive")),
HttpEntity(contentType = ContentTypes.NoContentType, "Small f*ck up overhere!")) should renderTo {
"""HTTP/1.1 400 Bad Request
@ -109,9 +109,23 @@ class ResponseRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll
|Small f*ck up overhere!"""
}
}
"status 200 and a custom Transfer-Encoding header" in new TestSetup() {
HttpResponse(headers = List(`Transfer-Encoding`(TransferEncodings.Extension("fancy"))),
entity = "All good") should renderTo {
"""HTTP/1.1 200 OK
|Transfer-Encoding: fancy
|Server: akka-http/1.0.0
|Date: Thu, 25 Aug 2011 09:10:29 GMT
|Content-Type: text/plain; charset=UTF-8
|Content-Length: 8
|
|All good"""
}
}
}
"a response with a Default (streamed with explicit content-length body" - {
"with status 400, a few headers and a body" in new TestSetup() {
"a response with a Default (streamed with explicit content-length body," - {
"status 400 and a few headers" in new TestSetup() {
HttpResponse(400, List(RawHeader("Age", "30"), Connection("Keep-Alive")),
entity = Default(contentType = ContentTypes.`text/plain(UTF-8)`, 23, publisher(ByteString("Small f*ck up overhere!")))) should renderTo {
"""HTTP/1.1 400 Bad Request
@ -124,14 +138,14 @@ class ResponseRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll
|Small f*ck up overhere!"""
}
}
"with one chunk and incorrect (too large) Content-Length" in new TestSetup() {
"one chunk and incorrect (too large) Content-Length" in new TestSetup() {
the[RuntimeException] thrownBy {
HttpResponse(200, entity = Default(ContentTypes.`application/json`, 10,
publisher(ByteString("body123")))) should renderTo("")
} should have message "HTTP message had declared Content-Length 10 but entity chunk stream amounts to 3 bytes less"
}
"with one chunk and incorrect (too small) Content-Length" in new TestSetup() {
"one chunk and incorrect (too small) Content-Length" in new TestSetup() {
the[RuntimeException] thrownBy {
HttpResponse(200, entity = Default(ContentTypes.`application/json`, 5,
publisher(ByteString("body123")))) should renderTo("")
@ -198,8 +212,8 @@ class ResponseRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll
"""HTTP/1.1 200 OK
|Server: akka-http/1.0.0
|Date: Thu, 25 Aug 2011 09:10:29 GMT
|Content-Type: text/plain; charset=UTF-8
|Transfer-Encoding: chunked
|Content-Type: text/plain; charset=UTF-8
|
|7
|Yahoooo
@ -216,8 +230,8 @@ class ResponseRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll
"""HTTP/1.1 200 OK
|Server: akka-http/1.0.0
|Date: Thu, 25 Aug 2011 09:10:29 GMT
|Content-Type: text/plain; charset=UTF-8
|Transfer-Encoding: chunked
|Content-Type: text/plain; charset=UTF-8
|
|7;key=value;another="tl;dr"
|body123
@ -228,9 +242,26 @@ class ResponseRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll
|"""
}
}
"with a custom Transfer-Encoding header" in new TestSetup() {
HttpResponse(headers = List(`Transfer-Encoding`(TransferEncodings.Extension("fancy"))),
entity = Chunked(ContentTypes.`text/plain(UTF-8)`, publisher("Yahoooo"))) should renderTo {
"""HTTP/1.1 200 OK
|Transfer-Encoding: fancy, chunked
|Server: akka-http/1.0.0
|Date: Thu, 25 Aug 2011 09:10:29 GMT
|Content-Type: text/plain; charset=UTF-8
|
|7
|Yahoooo
|0
|
|"""
}
}
}
"chunked responses with HTTP/1.0 requests" - {
"chunked responses to a HTTP/1.0 request" - {
"with two chunks" in new TestSetup() {
ResponseRenderingContext(
requestProtocol = HttpProtocols.`HTTP/1.0`,

View file

@ -350,7 +350,7 @@ class HttpServerPipelineSpec extends AkkaSpec with Matchers with BeforeAndAfterA
expectRequest shouldEqual HttpRequest(HttpMethods.HEAD, uri = "http://example.com/", headers = List(Host("example.com")))
}
"not emit entites when responding to HEAD requests if transparent-head-requests is enabled (with Strict)" in new TestSetup {
"not emit entities when responding to HEAD requests if transparent-head-requests is enabled (with Strict)" in new TestSetup {
override def settings = ServerSettings(system).copy(serverHeader = Some(Server(List(ProductVersion("akka-http", "test")))))
send("""HEAD / HTTP/1.1
|Host: example.com
@ -373,7 +373,7 @@ class HttpServerPipelineSpec extends AkkaSpec with Matchers with BeforeAndAfterA
}
}
"not emit entites when responding to HEAD requests if transparent-head-requests is enabled (with Default)" in new TestSetup {
"not emit entities when responding to HEAD requests if transparent-head-requests is enabled (with Default)" in new TestSetup {
override def settings = ServerSettings(system).copy(serverHeader = Some(Server(List(ProductVersion("akka-http", "test")))))
send("""HEAD / HTTP/1.1
|Host: example.com
@ -402,7 +402,7 @@ class HttpServerPipelineSpec extends AkkaSpec with Matchers with BeforeAndAfterA
}
}
"not emit entites when responding to HEAD requests if transparent-head-requests is enabled (with CloseDelimited)" in new TestSetup {
"not emit entities when responding to HEAD requests if transparent-head-requests is enabled (with CloseDelimited)" in new TestSetup {
override def settings = ServerSettings(system).copy(serverHeader = Some(Server(List(ProductVersion("akka-http", "test")))))
send("""HEAD / HTTP/1.1
|Host: example.com
@ -433,7 +433,7 @@ class HttpServerPipelineSpec extends AkkaSpec with Matchers with BeforeAndAfterA
netOut.expectNoMsg(50.millis)
}
"not emit entites when responding to HEAD requests if transparent-head-requests is enabled (with Chunked)" in new TestSetup {
"not emit entities when responding to HEAD requests if transparent-head-requests is enabled (with Chunked)" in new TestSetup {
override def settings = ServerSettings(system).copy(serverHeader = Some(Server(List(ProductVersion("akka-http", "test")))))
send("""HEAD / HTTP/1.1
|Host: example.com
@ -455,14 +455,14 @@ class HttpServerPipelineSpec extends AkkaSpec with Matchers with BeforeAndAfterA
"""|HTTP/1.1 200 OK
|Server: akka-http/test
|Date: XXXX
|Content-Type: text/plain
|Transfer-Encoding: chunked
|Content-Type: text/plain
|
|""".stripMarginWithNewline("\r\n")
}
}
"respect Connetion headers of HEAD requests if transparent-head-requests is enabled" in new TestSetup {
"respect Connection headers of HEAD requests if transparent-head-requests is enabled" in new TestSetup {
override def settings = ServerSettings(system).copy(serverHeader = Some(Server(List(ProductVersion("akka-http", "test")))))
send("""HEAD / HTTP/1.1
|Host: example.com