in http-core/src/main/scala/org/apache/pekko/http/impl/engine/http2/client/ResponseParsing.scala [35:120]
def parseResponse(httpHeaderParser: HttpHeaderParser, settings: ParserSettings, attributes: Attributes)
: Http2SubStream => HttpResponse = { subStream =>
val tlsSessionInfoHeader: Option[`Tls-Session-Info`] =
if (settings.includeTlsSessionInfoHeader) {
attributes.get[HttpAttributes.TLSSessionInfo].map(sslSessionInfo =>
model.headers.`Tls-Session-Info`(sslSessionInfo.session))
} else None
val sslSessionAttribute: Option[SSLSession] =
if (settings.includeSslSessionAttribute)
attributes.get[HttpAttributes.TLSSessionInfo].map(_.session)
else
None
@tailrec
def rec(
remainingHeaders: Seq[(String, AnyRef)],
status: StatusCode = null,
contentType: OptionVal[ContentType] = OptionVal.None,
contentLength: Long = -1,
seenRegularHeader: Boolean = false,
headers: VectorBuilder[HttpHeader] = new VectorBuilder[HttpHeader]): HttpResponse =
if (remainingHeaders.isEmpty) {
// https://httpwg.org/specs/rfc7540.html#rfc.section.8.1.2.4: these pseudo header fields are mandatory for a response
checkRequiredPseudoHeader(":status", status)
val entity = subStream.createEntity(contentLength, contentType)
// user access to tls session info
if (tlsSessionInfoHeader.isDefined) headers += tlsSessionInfoHeader.get
val response = HttpResponse(
status = status,
headers = headers.result(),
entity = entity,
HttpProtocols.`HTTP/2.0`).withAttributes(subStream.correlationAttributes)
sslSessionAttribute match {
case Some(sslSession) => response.addAttribute(AttributeKeys.sslSession, SslSessionInfo(sslSession))
case None => response
}
} else remainingHeaders.head match {
case (":status", statusCodeValue: String) =>
checkUniquePseudoHeader(":status", status)
checkNoRegularHeadersBeforePseudoHeader(":status", seenRegularHeader)
rec(remainingHeaders.tail, statusCodeValue.toInt, contentType, contentLength, seenRegularHeader, headers)
case ("content-type", contentTypeValue: ContentType) =>
if (contentType.isEmpty)
rec(remainingHeaders.tail, status, OptionVal.Some(contentTypeValue), contentLength, seenRegularHeader,
headers)
else
malformedRequest("HTTP message must not contain more than one content-type header")
case ("content-type", ct: String) =>
if (contentType.isEmpty) {
val contentTypeValue =
ContentType.parse(ct).right.getOrElse(malformedRequest(s"Invalid content-type: '$ct'"))
rec(remainingHeaders.tail, status, OptionVal.Some(contentTypeValue), contentLength, seenRegularHeader,
headers)
} else malformedRequest("HTTP message must not contain more than one content-type header")
case ("content-length", length: String) =>
if (contentLength == -1) {
val contentLengthValue = length.toLong
if (contentLengthValue < 0)
malformedRequest("HTTP message must not contain a negative content-length header")
rec(remainingHeaders.tail, status, contentType, contentLengthValue, seenRegularHeader, headers)
} else malformedRequest("HTTP message must not contain more than one content-length header")
case (name, _) if name.startsWith(":") =>
malformedRequest(s"Unexpected pseudo-header '$name' in response")
case (_, httpHeader: HttpHeader) =>
rec(remainingHeaders.tail, status, contentType, contentLength, seenRegularHeader = true,
headers += httpHeader)
case (name, value: String) =>
val httpHeader = parseHeaderPair(httpHeaderParser, name, value)
validateHeader(httpHeader)
rec(remainingHeaders.tail, status, contentType, contentLength, seenRegularHeader = true,
headers += httpHeader)
}
rec(subStream.initialHeaders.keyValuePairs)
}