=htc #16482 preserve header order during parse
This commit is contained in:
parent
bd3ee6b54f
commit
f0c83631e1
4 changed files with 19 additions and 19 deletions
|
|
@ -97,7 +97,7 @@ private[http] abstract class HttpMessageParser[Output >: MessageOutput <: Parser
|
||||||
|
|
||||||
def badProtocol: Nothing
|
def badProtocol: Nothing
|
||||||
|
|
||||||
@tailrec final def parseHeaderLines(input: ByteString, lineStart: Int, headers: List[HttpHeader] = Nil,
|
@tailrec final def parseHeaderLines(input: ByteString, lineStart: Int, headers: ListBuffer[HttpHeader] = ListBuffer[HttpHeader](),
|
||||||
headerCount: Int = 0, ch: Option[Connection] = None,
|
headerCount: Int = 0, ch: Option[Connection] = None,
|
||||||
clh: Option[`Content-Length`] = None, cth: Option[`Content-Type`] = None,
|
clh: Option[`Content-Length`] = None, cth: Option[`Content-Type`] = None,
|
||||||
teh: Option[`Transfer-Encoding`] = None, e100c: Boolean = false,
|
teh: Option[`Transfer-Encoding`] = None, e100c: Boolean = false,
|
||||||
|
|
@ -117,7 +117,7 @@ private[http] abstract class HttpMessageParser[Output >: MessageOutput <: Parser
|
||||||
case HttpHeaderParser.EmptyHeader ⇒
|
case HttpHeaderParser.EmptyHeader ⇒
|
||||||
val close = HttpMessage.connectionCloseExpected(protocol, ch)
|
val close = HttpMessage.connectionCloseExpected(protocol, ch)
|
||||||
setCompletionHandling(CompletionIsEntityStreamError)
|
setCompletionHandling(CompletionIsEntityStreamError)
|
||||||
parseEntity(headers, protocol, input, lineEnd, clh, cth, teh, e100c, hh, close)
|
parseEntity(headers.toList, protocol, input, lineEnd, clh, cth, teh, e100c, hh, close)
|
||||||
|
|
||||||
case h: `Content-Length` ⇒ clh match {
|
case h: `Content-Length` ⇒ clh match {
|
||||||
case None ⇒ parseHeaderLines(input, lineEnd, headers, headerCount + 1, ch, Some(h), cth, teh, e100c, hh)
|
case None ⇒ parseHeaderLines(input, lineEnd, headers, headerCount + 1, ch, Some(h), cth, teh, e100c, hh)
|
||||||
|
|
@ -134,21 +134,21 @@ private[http] abstract class HttpMessageParser[Output >: MessageOutput <: Parser
|
||||||
case Some(x) ⇒ parseHeaderLines(input, lineEnd, headers, headerCount, ch, clh, cth, Some(x append h.encodings), e100c, hh)
|
case Some(x) ⇒ parseHeaderLines(input, lineEnd, headers, headerCount, ch, clh, cth, Some(x append h.encodings), e100c, hh)
|
||||||
}
|
}
|
||||||
case h: Connection ⇒ ch match {
|
case h: Connection ⇒ ch match {
|
||||||
case None ⇒ parseHeaderLines(input, lineEnd, h :: headers, headerCount + 1, Some(h), clh, cth, teh, e100c, hh)
|
case None ⇒ parseHeaderLines(input, lineEnd, headers += h, headerCount + 1, Some(h), clh, cth, teh, e100c, hh)
|
||||||
case Some(x) ⇒ parseHeaderLines(input, lineEnd, headers, headerCount, Some(x append h.tokens), clh, cth, teh, e100c, hh)
|
case Some(x) ⇒ parseHeaderLines(input, lineEnd, headers, headerCount, Some(x append h.tokens), clh, cth, teh, e100c, hh)
|
||||||
}
|
}
|
||||||
case h: Host ⇒
|
case h: Host ⇒
|
||||||
if (!hh) parseHeaderLines(input, lineEnd, h :: headers, headerCount + 1, ch, clh, cth, teh, e100c, hh = true)
|
if (!hh) parseHeaderLines(input, lineEnd, headers += h, headerCount + 1, ch, clh, cth, teh, e100c, hh = true)
|
||||||
else failMessageStart("HTTP message must not contain more than one Host header")
|
else failMessageStart("HTTP message must not contain more than one Host header")
|
||||||
|
|
||||||
case h: Expect ⇒ parseHeaderLines(input, lineEnd, h :: headers, headerCount + 1, ch, clh, cth, teh, e100c = true, hh)
|
case h: Expect ⇒ parseHeaderLines(input, lineEnd, headers += h, headerCount + 1, ch, clh, cth, teh, e100c = true, hh)
|
||||||
|
|
||||||
case h ⇒ parseHeaderLines(input, lineEnd, h :: headers, headerCount + 1, ch, clh, cth, teh, e100c, hh)
|
case h ⇒ parseHeaderLines(input, lineEnd, headers += h, headerCount + 1, ch, clh, cth, teh, e100c, hh)
|
||||||
}
|
}
|
||||||
} else failMessageStart(s"HTTP message contains more than the configured limit of $maxHeaderCount headers")
|
} else failMessageStart(s"HTTP message contains more than the configured limit of $maxHeaderCount headers")
|
||||||
|
|
||||||
// work-around for compiler complaining about non-tail-recursion if we inline this method
|
// work-around for compiler complaining about non-tail-recursion if we inline this method
|
||||||
def parseHeaderLinesAux(headers: List[HttpHeader], headerCount: Int, ch: Option[Connection],
|
def parseHeaderLinesAux(headers: ListBuffer[HttpHeader], headerCount: Int, ch: Option[Connection],
|
||||||
clh: Option[`Content-Length`], cth: Option[`Content-Type`], teh: Option[`Transfer-Encoding`],
|
clh: Option[`Content-Length`], cth: Option[`Content-Type`], teh: Option[`Transfer-Encoding`],
|
||||||
e100c: Boolean, hh: Boolean)(input: ByteString, lineStart: Int): StateResult =
|
e100c: Boolean, hh: Boolean)(input: ByteString, lineStart: Int): StateResult =
|
||||||
parseHeaderLines(input, lineStart, headers, headerCount, ch, clh, cth, teh, e100c, hh)
|
parseHeaderLines(input, lineStart, headers, headerCount, ch, clh, cth, teh, e100c, hh)
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ class ClientServerSpec extends WordSpec with Matchers with BeforeAndAfterAll {
|
||||||
|
|
||||||
val serverInSub = serverIn.expectSubscription()
|
val serverInSub = serverIn.expectSubscription()
|
||||||
serverInSub.request(1)
|
serverInSub.request(1)
|
||||||
private val HttpRequest(POST, uri, List(`User-Agent`(_), Host(_, _), Accept(Vector(MediaRanges.`*/*`))),
|
private val HttpRequest(POST, uri, List(Accept(Seq(MediaRanges.`*/*`)), Host(_, _), `User-Agent`(_)),
|
||||||
Chunked(`chunkedContentType`, chunkStream), HttpProtocols.`HTTP/1.1`) = serverIn.expectNext()
|
Chunked(`chunkedContentType`, chunkStream), HttpProtocols.`HTTP/1.1`) = serverIn.expectNext()
|
||||||
uri shouldEqual Uri(s"http://$hostname:$port/chunked")
|
uri shouldEqual Uri(s"http://$hostname:$port/chunked")
|
||||||
Await.result(chunkStream.grouped(4).runWith(Sink.head), 100.millis) shouldEqual chunks
|
Await.result(chunkStream.grouped(4).runWith(Sink.head), 100.millis) shouldEqual chunks
|
||||||
|
|
@ -107,7 +107,7 @@ class ClientServerSpec extends WordSpec with Matchers with BeforeAndAfterAll {
|
||||||
|
|
||||||
val clientInSub = clientIn.expectSubscription()
|
val clientInSub = clientIn.expectSubscription()
|
||||||
clientInSub.request(1)
|
clientInSub.request(1)
|
||||||
val HttpResponse(StatusCodes.PartialContent, List(Date(_), Server(_), RawHeader("Age", "42")),
|
val HttpResponse(StatusCodes.PartialContent, List(RawHeader("Age", "42"), Server(_), Date(_)),
|
||||||
Chunked(`chunkedContentType`, chunkStream2), HttpProtocols.`HTTP/1.1`) = clientIn.expectNext()
|
Chunked(`chunkedContentType`, chunkStream2), HttpProtocols.`HTTP/1.1`) = clientIn.expectNext()
|
||||||
Await.result(chunkStream2.grouped(1000).runWith(Sink.head), 100.millis) shouldEqual chunks
|
Await.result(chunkStream2.grouped(1000).runWith(Sink.head), 100.millis) shouldEqual chunks
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ class RequestParserSpec extends FreeSpec with Matchers with BeforeAndAfterAll {
|
||||||
|Content-length: 17
|
|Content-length: 17
|
||||||
|
|
|
|
||||||
|Shake your BOODY!""" should parseTo {
|
|Shake your BOODY!""" should parseTo {
|
||||||
HttpRequest(POST, "/resource/yes", List(Connection("keep-alive"), `User-Agent`("curl/7.19.7 xyz")),
|
HttpRequest(POST, "/resource/yes", List(`User-Agent`("curl/7.19.7 xyz"), Connection("keep-alive")),
|
||||||
"Shake your BOODY!", `HTTP/1.0`)
|
"Shake your BOODY!", `HTTP/1.0`)
|
||||||
}
|
}
|
||||||
closeAfterResponseCompletion shouldEqual Seq(false)
|
closeAfterResponseCompletion shouldEqual Seq(false)
|
||||||
|
|
@ -91,7 +91,7 @@ class RequestParserSpec extends FreeSpec with Matchers with BeforeAndAfterAll {
|
||||||
|Shake your BOODY!GET / HTTP/1.0
|
|Shake your BOODY!GET / HTTP/1.0
|
||||||
|
|
|
|
||||||
|""" should parseTo(
|
|""" should parseTo(
|
||||||
HttpRequest(POST, "/resource/yes", List(Connection("keep-alive"), `User-Agent`("curl/7.19.7 xyz")),
|
HttpRequest(POST, "/resource/yes", List(`User-Agent`("curl/7.19.7 xyz"), Connection("keep-alive")),
|
||||||
"Shake your BOODY!".getBytes, `HTTP/1.0`),
|
"Shake your BOODY!".getBytes, `HTTP/1.0`),
|
||||||
HttpRequest(protocol = `HTTP/1.0`))
|
HttpRequest(protocol = `HTTP/1.0`))
|
||||||
closeAfterResponseCompletion shouldEqual Seq(false, true)
|
closeAfterResponseCompletion shouldEqual Seq(false, true)
|
||||||
|
|
@ -107,8 +107,8 @@ class RequestParserSpec extends FreeSpec with Matchers with BeforeAndAfterAll {
|
||||||
| fancy
|
| fancy
|
||||||
|
|
|
|
||||||
|""" should parseTo {
|
|""" should parseTo {
|
||||||
HttpRequest(DELETE, "/abc", List(Connection("close", "fancy"), Accept(MediaRanges.`*/*`),
|
HttpRequest(DELETE, "/abc", List(`User-Agent`("curl/7.19.7 abc xyz"), Accept(MediaRanges.`*/*`),
|
||||||
`User-Agent`("curl/7.19.7 abc xyz")), protocol = `HTTP/1.0`)
|
Connection("close", "fancy")), protocol = `HTTP/1.0`)
|
||||||
}
|
}
|
||||||
closeAfterResponseCompletion shouldEqual Seq(true)
|
closeAfterResponseCompletion shouldEqual Seq(true)
|
||||||
}
|
}
|
||||||
|
|
@ -165,7 +165,7 @@ class RequestParserSpec extends FreeSpec with Matchers with BeforeAndAfterAll {
|
||||||
|Host: ping
|
|Host: ping
|
||||||
|
|
|
|
||||||
|"""
|
|"""
|
||||||
val baseRequest = HttpRequest(PATCH, "/data", List(Host("ping"), Connection("lalelu")))
|
val baseRequest = HttpRequest(PATCH, "/data", List(Connection("lalelu"), Host("ping")))
|
||||||
|
|
||||||
"request start" in new Test {
|
"request start" in new Test {
|
||||||
Seq(start, "rest") should generalMultiParseTo(
|
Seq(start, "rest") should generalMultiParseTo(
|
||||||
|
|
@ -217,7 +217,7 @@ class RequestParserSpec extends FreeSpec with Matchers with BeforeAndAfterAll {
|
||||||
|
|
|
|
||||||
|""") should generalMultiParseTo(
|
|""") should generalMultiParseTo(
|
||||||
Right(baseRequest.withEntity(Chunked(`application/pdf`,
|
Right(baseRequest.withEntity(Chunked(`application/pdf`,
|
||||||
source(LastChunk("nice=true", List(RawHeader("Bar", "xyz"), RawHeader("Foo", "pip apo"))))))))
|
source(LastChunk("nice=true", List(RawHeader("Foo", "pip apo"), RawHeader("Bar", "xyz"))))))))
|
||||||
closeAfterResponseCompletion shouldEqual Seq(false)
|
closeAfterResponseCompletion shouldEqual Seq(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -276,7 +276,7 @@ class RequestParserSpec extends FreeSpec with Matchers with BeforeAndAfterAll {
|
||||||
|Host: ping
|
|Host: ping
|
||||||
|
|
|
|
||||||
|"""
|
|"""
|
||||||
val baseRequest = HttpRequest(PATCH, "/data", List(Host("ping"), Connection("lalelu")),
|
val baseRequest = HttpRequest(PATCH, "/data", List(Connection("lalelu"), Host("ping")),
|
||||||
HttpEntity.Chunked(`application/octet-stream`, source()))
|
HttpEntity.Chunked(`application/octet-stream`, source()))
|
||||||
|
|
||||||
"an illegal char after chunk size" in new Test {
|
"an illegal char after chunk size" in new Test {
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ class ResponseParserSpec extends FreeSpec with Matchers with BeforeAndAfterAll {
|
||||||
|Content-Type: text/plain; charset=UTF-8
|
|Content-Type: text/plain; charset=UTF-8
|
||||||
|
|
|
|
||||||
|Sh""", "ake your BOODY!HTTP/1.") should generalMultiParseTo(
|
|Sh""", "ake your BOODY!HTTP/1.") should generalMultiParseTo(
|
||||||
Right(HttpResponse(InternalServerError, List(Connection("close"), `User-Agent`("curl/7.19.7 xyz")),
|
Right(HttpResponse(InternalServerError, List(`User-Agent`("curl/7.19.7 xyz"), Connection("close")),
|
||||||
"Shake your BOODY!")))
|
"Shake your BOODY!")))
|
||||||
closeAfterResponseCompletion shouldEqual Seq(true)
|
closeAfterResponseCompletion shouldEqual Seq(true)
|
||||||
}
|
}
|
||||||
|
|
@ -130,7 +130,7 @@ class ResponseParserSpec extends FreeSpec with Matchers with BeforeAndAfterAll {
|
||||||
|Server: spray-can
|
|Server: spray-can
|
||||||
|
|
|
|
||||||
|"""
|
|"""
|
||||||
val baseResponse = HttpResponse(headers = List(Server("spray-can"), Connection("lalelu")))
|
val baseResponse = HttpResponse(headers = List(Connection("lalelu"), Server("spray-can")))
|
||||||
|
|
||||||
"response start" in new Test {
|
"response start" in new Test {
|
||||||
Seq(start, "rest") should generalMultiParseTo(
|
Seq(start, "rest") should generalMultiParseTo(
|
||||||
|
|
@ -181,7 +181,7 @@ class ResponseParserSpec extends FreeSpec with Matchers with BeforeAndAfterAll {
|
||||||
|
|
|
|
||||||
|HT""") should generalMultiParseTo(
|
|HT""") should generalMultiParseTo(
|
||||||
Right(baseResponse.withEntity(Chunked(`application/pdf`,
|
Right(baseResponse.withEntity(Chunked(`application/pdf`,
|
||||||
source(LastChunk("nice=true", List(RawHeader("Bar", "xyz"), RawHeader("Foo", "pip apo"))))))),
|
source(LastChunk("nice=true", List(RawHeader("Foo", "pip apo"), RawHeader("Bar", "xyz"))))))),
|
||||||
Left(MessageStartError(400: StatusCode, ErrorInfo("Illegal HTTP message start"))))
|
Left(MessageStartError(400: StatusCode, ErrorInfo("Illegal HTTP message start"))))
|
||||||
closeAfterResponseCompletion shouldEqual Seq(false)
|
closeAfterResponseCompletion shouldEqual Seq(false)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue