=htc Improve handling of Transfer-Encoding header, closes #15490
This commit is contained in:
parent
0afebd1c39
commit
36904eadcb
12 changed files with 211 additions and 59 deletions
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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" - {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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`,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue