=htc #16482 preserve header order during parse

This commit is contained in:
2beaucoup 2014-11-12 13:17:24 +01:00
parent bd3ee6b54f
commit f0c83631e1
4 changed files with 19 additions and 19 deletions

View file

@ -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)

View file

@ -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
} }

View file

@ -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 {

View file

@ -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)
} }