in http-core/src/main/scala/org/apache/pekko/http/impl/engine/rendering/HttpRequestRendererFactory.scala [43:170]
def renderToSource(ctx: RequestRenderingContext): Source[ByteString, Any] = render(ctx).byteStream
def render(ctx: RequestRenderingContext): RequestRenderingOutput = {
val r = new ByteStringRendering(requestHeaderSizeHint, log.warning)
import ctx.request._
def renderRequestLine(): Unit = {
r ~~ method ~~ ' '
val rawRequestUriRendered = headers.exists {
case `Raw-Request-URI`(rawUri) =>
r ~~ rawUri; true
case _ => false
}
if (!rawRequestUriRendered) UriRendering.renderUriWithoutFragment(r, uri, UTF8)
r ~~ ' ' ~~ protocol ~~ CrLf
}
def render(h: HttpHeader) = r ~~ h
@tailrec def renderHeaders(remaining: List[HttpHeader], hostHeaderSeen: Boolean = false,
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, transferEncodingSeen)
case x: `Content-Type` =>
suppressionWarning(log, x,
"explicit `Content-Type` header is not allowed. Set `HttpRequest.entity.contentType` instead.")
renderHeaders(tail, hostHeaderSeen, userAgentSeen, transferEncodingSeen)
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, transferEncodingSeen)
case x: `User-Agent` =>
render(x)
renderHeaders(tail, hostHeaderSeen, userAgentSeen = true, transferEncodingSeen)
case x: `Raw-Request-URI` => // we never render this header
renderHeaders(tail, hostHeaderSeen, userAgentSeen, transferEncodingSeen)
case x: CustomHeader =>
if (x.renderInRequests) render(x)
renderHeaders(tail, hostHeaderSeen, userAgentSeen, transferEncodingSeen)
case x: RawHeader
if (x.is("content-type")) || (x.is("content-length")) ||
(x.is("transfer-encoding")) =>
suppressionWarning(log, x, "illegal RawHeader")
renderHeaders(tail, hostHeaderSeen, userAgentSeen, transferEncodingSeen)
case x: RawHeader if x.is("user-agent") =>
render(x)
renderHeaders(tail, hostHeaderSeen, userAgentSeen = true, transferEncodingSeen)
case x: RawHeader if x.is("host") =>
render(x)
renderHeaders(tail, hostHeaderSeen = true, userAgentSeen, transferEncodingSeen)
case x =>
if (x.renderInRequests) render(x)
else log.warning("HTTP header '{}' is not allowed in requests", x)
renderHeaders(tail, hostHeaderSeen, userAgentSeen, transferEncodingSeen)
}
case Nil =>
if (!hostHeaderSeen) r ~~ ctx.hostHeader
if (!userAgentSeen && userAgentHeader.isDefined) r ~~ userAgentHeader.get
if (entity.isChunked && !entity.isKnownEmpty && !transferEncodingSeen)
r ~~ `Transfer-Encoding` ~~ ChunkedBytes ~~ CrLf
}
def renderContentLength(contentLength: Long) =
if (method.isEntityAccepted && (contentLength > 0 || method.requestEntityAcceptance == Expected))
r ~~ `Content-Length` ~~ contentLength ~~ CrLf
else r
def renderStreamed(body: Source[ByteString, Any]): RequestRenderingOutput = {
val headerPart = Source.single(r.get)
val stream = ctx.sendEntityTrigger match {
case None => headerPart ++ body
case Some(future) =>
val barrier = Source.future(future).drop(1).asInstanceOf[Source[ByteString, Any]]
(headerPart ++ barrier ++ body).recoverWithRetries(-1,
{ case HttpResponseParser.OneHundredContinueError => Source.empty })
}
RequestRenderingOutput.Streamed(stream)
}
def completeRequestRendering(): RequestRenderingOutput =
entity match {
case x if x.isKnownEmpty =>
renderContentLength(0) ~~ CrLf
RequestRenderingOutput.Strict(r.get)
case HttpEntity.Strict(_, data) =>
renderContentLength(data.length) ~~ CrLf
if (ctx.sendEntityTrigger.isDefined) renderStreamed(Source.single(data))
else RequestRenderingOutput.Strict(r.get ++ data)
case HttpEntity.Default(_, contentLength, data) =>
renderContentLength(contentLength) ~~ CrLf
renderStreamed(data.via(CheckContentLengthTransformer.flow(contentLength)))
case HttpEntity.Chunked(_, chunks) =>
r ~~ CrLf
renderStreamed(chunks.via(ChunkTransformer.flow))
}
renderRequestLine()
renderHeaders(headers.toList)
renderEntityContentType(r, entity)
completeRequestRendering()
}