From 2bb7e029efd16cb3a49218ddadf1a2d557f65f6c Mon Sep 17 00:00:00 2001 From: Johannes Rudolph Date: Mon, 4 May 2015 10:17:35 +0200 Subject: [PATCH] =htc #17348 don't render extra LastChunk at the and of Chunked entity --- .../impl/engine/rendering/RenderSupport.scala | 12 ++--- .../rendering/RequestRendererSpec.scala | 51 +++++++++++++++++++ .../rendering/ResponseRendererSpec.scala | 22 +++++++- .../http/scaladsl/model/HttpEntitySpec.scala | 4 ++ 4 files changed, 80 insertions(+), 9 deletions(-) diff --git a/akka-http-core/src/main/scala/akka/http/impl/engine/rendering/RenderSupport.scala b/akka-http-core/src/main/scala/akka/http/impl/engine/rendering/RenderSupport.scala index 3ebb0c8624..ead3bd2308 100644 --- a/akka-http-core/src/main/scala/akka/http/impl/engine/rendering/RenderSupport.scala +++ b/akka-http-core/src/main/scala/akka/http/impl/engine/rendering/RenderSupport.scala @@ -57,19 +57,15 @@ private object RenderSupport { } class ChunkTransformer extends StatefulStage[HttpEntity.ChunkStreamPart, ByteString] { - var lastChunkSeen = false - override def initial = new State { override def onPush(chunk: HttpEntity.ChunkStreamPart, ctx: Context[ByteString]): SyncDirective = { - if (chunk.isLastChunk) - lastChunkSeen = true - ctx.push(renderChunk(chunk)) + val bytes = renderChunk(chunk) + if (chunk.isLastChunk) ctx.pushAndFinish(bytes) + else ctx.push(bytes) } } - override def onUpstreamFinish(ctx: Context[ByteString]): TerminationDirective = - if (lastChunkSeen) super.onUpstreamFinish(ctx) - else terminationEmit(Iterator.single(defaultLastChunkBytes), ctx) + terminationEmit(Iterator.single(defaultLastChunkBytes), ctx) } object CheckContentLengthTransformer { diff --git a/akka-http-core/src/test/scala/akka/http/impl/engine/rendering/RequestRendererSpec.scala b/akka-http-core/src/test/scala/akka/http/impl/engine/rendering/RequestRendererSpec.scala index 014c89f31d..cbe73e20e6 100644 --- a/akka-http-core/src/test/scala/akka/http/impl/engine/rendering/RequestRendererSpec.scala +++ b/akka-http-core/src/test/scala/akka/http/impl/engine/rendering/RequestRendererSpec.scala @@ -150,6 +150,57 @@ class RequestRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll } } + "POST request with chunked body and explicit LastChunk" in new TestSetup() { + val chunks = + List( + ChunkStreamPart("XXXX"), + ChunkStreamPart("ABCDEFGHIJKLMNOPQRSTUVWXYZ"), + LastChunk) + + HttpRequest(POST, "/abc/xyz", entity = Chunked(ContentTypes.`text/plain`, + Source(chunks))) 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 chunked body and extra LastChunks at the end (which should be ignored)" in new TestSetup() { + val chunks = + List( + ChunkStreamPart("XXXX"), + ChunkStreamPart("ABCDEFGHIJKLMNOPQRSTUVWXYZ"), + LastChunk, + LastChunk) + + HttpRequest(POST, "/abc/xyz", entity = Chunked(ContentTypes.`text/plain`, + Source(chunks))) 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`, source("XXXX", "ABCDEFGHIJKLMNOPQRSTUVWXYZ"))) should renderTo { diff --git a/akka-http-core/src/test/scala/akka/http/impl/engine/rendering/ResponseRendererSpec.scala b/akka-http-core/src/test/scala/akka/http/impl/engine/rendering/ResponseRendererSpec.scala index d2f49ada56..25e0ecbb12 100644 --- a/akka-http-core/src/test/scala/akka/http/impl/engine/rendering/ResponseRendererSpec.scala +++ b/akka-http-core/src/test/scala/akka/http/impl/engine/rendering/ResponseRendererSpec.scala @@ -286,7 +286,27 @@ class ResponseRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll "with one chunk and an explicit LastChunk" in new TestSetup() { HttpResponse(entity = Chunked(ContentTypes.`text/plain(UTF-8)`, source(Chunk(ByteString("body123"), """key=value;another="tl;dr""""), - LastChunk("foo=bar", List(Age(30), RawHeader("Cache-Control", "public")))))) should renderTo { + LastChunk("foo=bar", List(Age(30), RawHeader("Cache-Control", "public")))).via(printEvent("source")))) should renderTo { + """HTTP/1.1 200 OK + |Server: akka-http/1.0.0 + |Date: Thu, 25 Aug 2011 09:10:29 GMT + |Transfer-Encoding: chunked + |Content-Type: text/plain; charset=UTF-8 + | + |7;key=value;another="tl;dr" + |body123 + |0;foo=bar + |Age: 30 + |Cache-Control: public + | + |""" + } + } + + "with one chunk and and extra LastChunks at the end (which should be ignored)" in new TestSetup() { + HttpResponse(entity = Chunked(ContentTypes.`text/plain(UTF-8)`, + source(Chunk(ByteString("body123"), """key=value;another="tl;dr""""), + LastChunk("foo=bar", List(Age(30), RawHeader("Cache-Control", "public"))), LastChunk))) should renderTo { """HTTP/1.1 200 OK |Server: akka-http/1.0.0 |Date: Thu, 25 Aug 2011 09:10:29 GMT diff --git a/akka-http-core/src/test/scala/akka/http/scaladsl/model/HttpEntitySpec.scala b/akka-http-core/src/test/scala/akka/http/scaladsl/model/HttpEntitySpec.scala index ec0567e6f4..d57463ce21 100644 --- a/akka-http-core/src/test/scala/akka/http/scaladsl/model/HttpEntitySpec.scala +++ b/akka-http-core/src/test/scala/akka/http/scaladsl/model/HttpEntitySpec.scala @@ -98,6 +98,10 @@ class HttpEntitySpec extends FreeSpec with MustMatchers with BeforeAndAfterAll { Chunked(tpe, source(Chunk(abc), Chunk(fgh), Chunk(ijk), LastChunk)) must transformTo(Strict(tpe, doubleChars("abcfghijk") ++ trailer)) } + "Chunked with extra LastChunk" in { + Chunked(tpe, source(Chunk(abc), Chunk(fgh), Chunk(ijk), LastChunk, LastChunk)) must + transformTo(Strict(tpe, doubleChars("abcfghijk") ++ trailer)) + } } }