Merge pull request #19526 from ktoso/spray-wip-16819-mathias
PR #19479 with identity fixup
This commit is contained in:
commit
ef77b56e66
29 changed files with 795 additions and 399 deletions
|
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package akka.http.javadsl;
|
||||||
|
|
||||||
|
import akka.http.javadsl.model.HttpRequest;
|
||||||
|
import akka.http.javadsl.model.HttpResponse;
|
||||||
|
import akka.japi.Function;
|
||||||
|
import scala.concurrent.duration.Duration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables programmatic access to the server-side request timeout logic.
|
||||||
|
*/
|
||||||
|
public interface TimeoutAccess {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to set a new timeout.
|
||||||
|
* The timeout period is measured as of the point in time that the end of the request has been received,
|
||||||
|
* which may be in the past or in the future!
|
||||||
|
* Use `Duration.Inf` to completely disable request timeout checking for this request.
|
||||||
|
*
|
||||||
|
* Due to the inherent raciness it is not guaranteed that the update will be applied before
|
||||||
|
* the previously set timeout has expired!
|
||||||
|
*/
|
||||||
|
void updateTimeout(Duration timeout);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to set a new timeout handler, which produces the timeout response for a
|
||||||
|
* given request. Note that the handler must produce the response synchronously and shouldn't block!
|
||||||
|
*
|
||||||
|
* Due to the inherent raciness it is not guaranteed that the update will be applied before
|
||||||
|
* the previously set timeout has expired!
|
||||||
|
*/
|
||||||
|
void updateHandler(Function<HttpRequest, HttpResponse> handler);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to set a new timeout and handler at the same time.
|
||||||
|
*
|
||||||
|
* Due to the inherent raciness it is not guaranteed that the update will be applied before
|
||||||
|
* the previously set timeout has expired!
|
||||||
|
*/
|
||||||
|
void update(Duration timeout, Function<HttpRequest, HttpResponse> handler);
|
||||||
|
}
|
||||||
|
|
@ -34,4 +34,14 @@ public abstract class HttpHeader {
|
||||||
* Returns !is(nameInLowerCase).
|
* Returns !is(nameInLowerCase).
|
||||||
*/
|
*/
|
||||||
public abstract boolean isNot(String nameInLowerCase);
|
public abstract boolean isNot(String nameInLowerCase);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true iff the header is to be rendered in requests.
|
||||||
|
*/
|
||||||
|
public abstract boolean renderInRequests();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true iff the header is to be rendered in responses.
|
||||||
|
*/
|
||||||
|
public abstract boolean renderInResponses();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,4 @@ package akka.http.javadsl.model.headers;
|
||||||
public abstract class CustomHeader extends akka.http.scaladsl.model.HttpHeader {
|
public abstract class CustomHeader extends akka.http.scaladsl.model.HttpHeader {
|
||||||
public abstract String name();
|
public abstract String name();
|
||||||
public abstract String value();
|
public abstract String value();
|
||||||
|
|
||||||
protected abstract boolean suppressRendering();
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package akka.http.javadsl.model.headers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model for the synthetic `Timeout-Access` header.
|
||||||
|
*/
|
||||||
|
public abstract class TimeoutAccess extends akka.http.scaladsl.model.HttpHeader {
|
||||||
|
public abstract akka.http.javadsl.TimeoutAccess timeoutAccess();
|
||||||
|
|
||||||
|
public static TimeoutAccess create(akka.http.javadsl.TimeoutAccess timeoutAccess) {
|
||||||
|
return new akka.http.scaladsl.model.headers.Timeout$minusAccess((akka.http.scaladsl.TimeoutAccess) timeoutAccess);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,6 +18,18 @@ akka.http {
|
||||||
# Set to `infinite` to completely disable idle connection timeouts.
|
# Set to `infinite` to completely disable idle connection timeouts.
|
||||||
idle-timeout = 60 s
|
idle-timeout = 60 s
|
||||||
|
|
||||||
|
# Defines the default time period within which the application has to
|
||||||
|
# produce an HttpResponse for any given HttpRequest it received.
|
||||||
|
# The timeout begins to run when the *end* of the request has been
|
||||||
|
# received, so even potentially long uploads can have a short timeout.
|
||||||
|
# Set to `infinite` to completely disable request timeout checking.
|
||||||
|
#
|
||||||
|
# If this setting is not `infinite` the HTTP server layer attaches a
|
||||||
|
# `Timeout-Access` header to the request, which enables programmatic
|
||||||
|
# customization of the timeout period and timeout response for each
|
||||||
|
# request individually.
|
||||||
|
request-timeout = 20 s
|
||||||
|
|
||||||
# The time period within which the TCP binding process must be completed.
|
# The time period within which the TCP binding process must be completed.
|
||||||
# Set to `infinite` to disable.
|
# Set to `infinite` to disable.
|
||||||
bind-timeout = 1s
|
bind-timeout = 1s
|
||||||
|
|
@ -273,13 +285,16 @@ akka.http {
|
||||||
max-chunk-ext-length = 256
|
max-chunk-ext-length = 256
|
||||||
max-chunk-size = 1m
|
max-chunk-size = 1m
|
||||||
|
|
||||||
# Maximum content length which should not be exceeded by incoming HttpRequests.
|
# Default maximum content length which should not be exceeded by incoming request entities.
|
||||||
# For file uploads which use the entityBytes Source of an incoming HttpRequest it is safe to
|
# Can be changed at runtime (to a higher or lower value) via the `HttpEntity::withSizeLimit` method.
|
||||||
# set this to a very high value (or to `infinite` if feeling very adventurous) as the streaming
|
# Note that it is not necessarily a problem to set this to a high value as all stream operations
|
||||||
# upload will be back-pressured properly by Akka Streams.
|
# are always properly backpressured.
|
||||||
# Please note however that this setting is a global property, and is applied to all incoming requests,
|
# Nevertheless you might want to apply some limit in order to prevent a single client from consuming
|
||||||
# not only file uploads consumed in a streaming fashion, so pick this limit wisely.
|
# an excessive amount of server resources.
|
||||||
max-content-length = 8m
|
#
|
||||||
|
# Set to `infinite` to completely disable entity length checks. (Even then you can still apply one
|
||||||
|
# programmatically via `withSizeLimit`.)
|
||||||
|
max-content-length = 8m
|
||||||
|
|
||||||
# Sets the strictness mode for parsing request target URIs.
|
# Sets the strictness mode for parsing request target URIs.
|
||||||
# The following values are defined:
|
# The following values are defined:
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,10 @@ final case class ServerSettings(
|
||||||
|
|
||||||
object ServerSettings extends SettingsCompanion[ServerSettings]("akka.http.server") {
|
object ServerSettings extends SettingsCompanion[ServerSettings]("akka.http.server") {
|
||||||
final case class Timeouts(idleTimeout: Duration,
|
final case class Timeouts(idleTimeout: Duration,
|
||||||
|
requestTimeout: Duration,
|
||||||
bindTimeout: FiniteDuration) {
|
bindTimeout: FiniteDuration) {
|
||||||
|
require(idleTimeout > Duration.Zero, "idleTimeout must be infinite or > 0")
|
||||||
|
require(requestTimeout > Duration.Zero, "requestTimeout must be infinite or > 0")
|
||||||
require(bindTimeout > Duration.Zero, "bindTimeout must be > 0")
|
require(bindTimeout > Duration.Zero, "bindTimeout must be > 0")
|
||||||
}
|
}
|
||||||
implicit def timeoutsShortcut(s: ServerSettings): Timeouts = s.timeouts
|
implicit def timeoutsShortcut(s: ServerSettings): Timeouts = s.timeouts
|
||||||
|
|
@ -55,6 +58,7 @@ object ServerSettings extends SettingsCompanion[ServerSettings]("akka.http.serve
|
||||||
c.getString("server-header").toOption.map(Server(_)),
|
c.getString("server-header").toOption.map(Server(_)),
|
||||||
Timeouts(
|
Timeouts(
|
||||||
c getPotentiallyInfiniteDuration "idle-timeout",
|
c getPotentiallyInfiniteDuration "idle-timeout",
|
||||||
|
c getPotentiallyInfiniteDuration "request-timeout",
|
||||||
c getFiniteDuration "bind-timeout"),
|
c getFiniteDuration "bind-timeout"),
|
||||||
c getInt "max-connections",
|
c getInt "max-connections",
|
||||||
c getInt "pipelining-limit",
|
c getInt "pipelining-limit",
|
||||||
|
|
|
||||||
|
|
@ -163,18 +163,7 @@ private object PoolSlot {
|
||||||
case FromConnection(OnNext(response: HttpResponse)) ⇒
|
case FromConnection(OnNext(response: HttpResponse)) ⇒
|
||||||
val requestContext = inflightRequests.head
|
val requestContext = inflightRequests.head
|
||||||
inflightRequests = inflightRequests.tail
|
inflightRequests = inflightRequests.tail
|
||||||
val (entity, whenCompleted) = response.entity match {
|
val (entity, whenCompleted) = HttpEntity.captureTermination(response.entity)
|
||||||
case x: HttpEntity.Strict ⇒ x -> FastFuture.successful(())
|
|
||||||
case x: HttpEntity.Default ⇒
|
|
||||||
val (newData, whenCompleted) = StreamUtils.captureTermination(x.data)
|
|
||||||
x.copy(data = newData) -> whenCompleted
|
|
||||||
case x: HttpEntity.CloseDelimited ⇒
|
|
||||||
val (newData, whenCompleted) = StreamUtils.captureTermination(x.data)
|
|
||||||
x.copy(data = newData) -> whenCompleted
|
|
||||||
case x: HttpEntity.Chunked ⇒
|
|
||||||
val (newChunks, whenCompleted) = StreamUtils.captureTermination(x.chunks)
|
|
||||||
x.copy(chunks = newChunks) -> whenCompleted
|
|
||||||
}
|
|
||||||
val delivery = ResponseDelivery(ResponseContext(requestContext, Success(response withEntity entity)))
|
val delivery = ResponseDelivery(ResponseContext(requestContext, Success(response withEntity entity)))
|
||||||
import fm.executionContext
|
import fm.executionContext
|
||||||
val requestCompleted = SlotEvent.RequestCompletedFuture(whenCompleted.map(_ ⇒ SlotEvent.RequestCompleted(slotIx)))
|
val requestCompleted = SlotEvent.RequestCompletedFuture(whenCompleted.map(_ ⇒ SlotEvent.RequestCompleted(slotIx)))
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,7 @@ private[http] final class BodyPartParser(defaultContentType: ContentType,
|
||||||
else if (doubleDash(input, ix)) terminate()
|
else if (doubleDash(input, ix)) terminate()
|
||||||
else fail("Illegal multipart boundary in message content")
|
else fail("Illegal multipart boundary in message content")
|
||||||
|
|
||||||
case HttpHeaderParser.EmptyHeader ⇒ parseEntity(headers.toList, contentType)(input, lineEnd)
|
case EmptyHeader ⇒ parseEntity(headers.toList, contentType)(input, lineEnd)
|
||||||
|
|
||||||
case h: `Content-Type` ⇒
|
case h: `Content-Type` ⇒
|
||||||
if (cth.isEmpty) parseHeaderLines(input, lineEnd, headers, headerCount + 1, Some(h))
|
if (cth.isEmpty) parseHeaderLines(input, lineEnd, headers, headerCount + 1, Some(h))
|
||||||
|
|
@ -261,6 +261,8 @@ private[http] object BodyPartParser {
|
||||||
val boundaryChar = CharPredicate.Digit ++ CharPredicate.Alpha ++ "'()+_,-./:=? "
|
val boundaryChar = CharPredicate.Digit ++ CharPredicate.Alpha ++ "'()+_,-./:=? "
|
||||||
|
|
||||||
private object BoundaryHeader extends HttpHeader {
|
private object BoundaryHeader extends HttpHeader {
|
||||||
|
def renderInRequests = false
|
||||||
|
def renderInResponses = false
|
||||||
def name = ""
|
def name = ""
|
||||||
def lowercaseName = ""
|
def lowercaseName = ""
|
||||||
def value = ""
|
def value = ""
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@ import scala.annotation.tailrec
|
||||||
import akka.parboiled2.CharUtils
|
import akka.parboiled2.CharUtils
|
||||||
import akka.util.ByteString
|
import akka.util.ByteString
|
||||||
import akka.http.impl.util._
|
import akka.http.impl.util._
|
||||||
import akka.http.scaladsl.model.{ IllegalHeaderException, StatusCodes, HttpHeader, ErrorInfo, Uri }
|
import akka.http.scaladsl.model.{ IllegalHeaderException, StatusCodes, HttpHeader, ErrorInfo }
|
||||||
import akka.http.scaladsl.model.headers.RawHeader
|
import akka.http.scaladsl.model.headers.{ EmptyHeader, RawHeader }
|
||||||
import akka.http.impl.model.parser.HeaderParser
|
import akka.http.impl.model.parser.HeaderParser
|
||||||
import akka.http.impl.model.parser.CharacterClasses._
|
import akka.http.impl.model.parser.CharacterClasses._
|
||||||
|
|
||||||
|
|
@ -414,14 +414,6 @@ private[http] object HttpHeaderParser {
|
||||||
def headerValueCacheLimit(headerName: String): Int
|
def headerValueCacheLimit(headerName: String): Int
|
||||||
}
|
}
|
||||||
|
|
||||||
object EmptyHeader extends HttpHeader {
|
|
||||||
def name = ""
|
|
||||||
def lowercaseName = ""
|
|
||||||
def value = ""
|
|
||||||
def render[R <: Rendering](r: R): r.type = r
|
|
||||||
override def toString = "EmptyHeader"
|
|
||||||
}
|
|
||||||
|
|
||||||
private def predefinedHeaders = Seq(
|
private def predefinedHeaders = Seq(
|
||||||
"Accept: *",
|
"Accept: *",
|
||||||
"Accept: */*",
|
"Accept: */*",
|
||||||
|
|
|
||||||
|
|
@ -136,7 +136,7 @@ private[http] abstract class HttpMessageParser[Output >: MessageOutput <: Parser
|
||||||
resultHeader match {
|
resultHeader match {
|
||||||
case null ⇒ continue(input, lineStart)(parseHeaderLinesAux(headers, headerCount, ch, clh, cth, teh, e100c, hh))
|
case null ⇒ continue(input, lineStart)(parseHeaderLinesAux(headers, headerCount, ch, clh, cth, teh, e100c, hh))
|
||||||
|
|
||||||
case HttpHeaderParser.EmptyHeader ⇒
|
case EmptyHeader ⇒
|
||||||
val close = HttpMessage.connectionCloseExpected(protocol, ch)
|
val close = HttpMessage.connectionCloseExpected(protocol, ch)
|
||||||
setCompletionHandling(CompletionIsEntityStreamError)
|
setCompletionHandling(CompletionIsEntityStreamError)
|
||||||
parseEntity(headers.toList, protocol, input, lineEnd, clh, cth, teh, e100c, hh, close)
|
parseEntity(headers.toList, protocol, input, lineEnd, clh, cth, teh, e100c, hh, close)
|
||||||
|
|
@ -206,7 +206,7 @@ private[http] abstract class HttpMessageParser[Output >: MessageOutput <: Parser
|
||||||
catch { case e: ParsingException ⇒ errorInfo = e.info; 0 }
|
catch { case e: ParsingException ⇒ errorInfo = e.info; 0 }
|
||||||
if (errorInfo eq null) {
|
if (errorInfo eq null) {
|
||||||
headerParser.resultHeader match {
|
headerParser.resultHeader match {
|
||||||
case HttpHeaderParser.EmptyHeader ⇒
|
case EmptyHeader ⇒
|
||||||
val lastChunk =
|
val lastChunk =
|
||||||
if (extension.isEmpty && headers.isEmpty) HttpEntity.LastChunk else HttpEntity.LastChunk(extension, headers)
|
if (extension.isEmpty && headers.isEmpty) HttpEntity.LastChunk else HttpEntity.LastChunk(extension, headers)
|
||||||
emit(EntityChunk(lastChunk))
|
emit(EntityChunk(lastChunk))
|
||||||
|
|
|
||||||
|
|
@ -78,8 +78,8 @@ private[http] class HttpRequestRendererFactory(userAgentHeader: Option[headers.`
|
||||||
case x: `Raw-Request-URI` ⇒ // we never render this header
|
case x: `Raw-Request-URI` ⇒ // we never render this header
|
||||||
renderHeaders(tail, hostHeaderSeen, userAgentSeen, transferEncodingSeen)
|
renderHeaders(tail, hostHeaderSeen, userAgentSeen, transferEncodingSeen)
|
||||||
|
|
||||||
case x: CustomHeader ⇒
|
case x: CustomHeader if x.renderInRequests ⇒
|
||||||
if (!x.suppressRendering) render(x)
|
render(x)
|
||||||
renderHeaders(tail, hostHeaderSeen, userAgentSeen, transferEncodingSeen)
|
renderHeaders(tail, hostHeaderSeen, userAgentSeen, transferEncodingSeen)
|
||||||
|
|
||||||
case x: RawHeader if (x is "content-type") || (x is "content-length") || (x is "transfer-encoding") ||
|
case x: RawHeader if (x is "content-type") || (x is "content-length") || (x is "transfer-encoding") ||
|
||||||
|
|
@ -88,7 +88,8 @@ private[http] class HttpRequestRendererFactory(userAgentHeader: Option[headers.`
|
||||||
renderHeaders(tail, hostHeaderSeen, userAgentSeen, transferEncodingSeen)
|
renderHeaders(tail, hostHeaderSeen, userAgentSeen, transferEncodingSeen)
|
||||||
|
|
||||||
case x ⇒
|
case x ⇒
|
||||||
render(x)
|
if (x.renderInRequests) render(x)
|
||||||
|
else log.warning("HTTP header '{}' is not allowed in requests", x)
|
||||||
renderHeaders(tail, hostHeaderSeen, userAgentSeen, transferEncodingSeen)
|
renderHeaders(tail, hostHeaderSeen, userAgentSeen, transferEncodingSeen)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -161,8 +161,8 @@ private[http] class HttpResponseRendererFactory(serverHeader: Option[headers.Ser
|
||||||
render(x)
|
render(x)
|
||||||
renderHeaders(tail, alwaysClose, connHeader, serverSeen = true, transferEncodingSeen, dateSeen)
|
renderHeaders(tail, alwaysClose, connHeader, serverSeen = true, transferEncodingSeen, dateSeen)
|
||||||
|
|
||||||
case x: CustomHeader ⇒
|
case x: CustomHeader if x.renderInResponses ⇒
|
||||||
if (!x.suppressRendering) render(x)
|
render(x)
|
||||||
renderHeaders(tail, alwaysClose, connHeader, serverSeen, transferEncodingSeen, dateSeen)
|
renderHeaders(tail, alwaysClose, connHeader, serverSeen, transferEncodingSeen, dateSeen)
|
||||||
|
|
||||||
case x: RawHeader if (x is "content-type") || (x is "content-length") || (x is "transfer-encoding") ||
|
case x: RawHeader if (x is "content-type") || (x is "content-length") || (x is "transfer-encoding") ||
|
||||||
|
|
@ -171,7 +171,8 @@ private[http] class HttpResponseRendererFactory(serverHeader: Option[headers.Ser
|
||||||
renderHeaders(tail, alwaysClose, connHeader, serverSeen, transferEncodingSeen, dateSeen)
|
renderHeaders(tail, alwaysClose, connHeader, serverSeen, transferEncodingSeen, dateSeen)
|
||||||
|
|
||||||
case x ⇒
|
case x ⇒
|
||||||
render(x)
|
if (x.renderInResponses) render(x)
|
||||||
|
else log.warning("HTTP header '{}' is not allowed in responses", x)
|
||||||
renderHeaders(tail, alwaysClose, connHeader, serverSeen, transferEncodingSeen, dateSeen)
|
renderHeaders(tail, alwaysClose, connHeader, serverSeen, transferEncodingSeen, dateSeen)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,19 @@
|
||||||
package akka.http.impl.engine.server
|
package akka.http.impl.engine.server
|
||||||
|
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
import java.util.Random
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
import akka.stream.impl.fusing.GraphInterpreter
|
import scala.concurrent.{ Promise, Future }
|
||||||
|
import scala.concurrent.duration.{ Deadline, FiniteDuration, Duration }
|
||||||
import scala.collection.immutable
|
import scala.collection.immutable
|
||||||
import org.reactivestreams.{ Publisher, Subscriber }
|
|
||||||
import scala.util.control.NonFatal
|
import scala.util.control.NonFatal
|
||||||
|
import akka.actor.Cancellable
|
||||||
|
import akka.japi.Function
|
||||||
import akka.event.LoggingAdapter
|
import akka.event.LoggingAdapter
|
||||||
|
import akka.util.ByteString
|
||||||
|
import akka.stream._
|
||||||
|
import akka.stream.io._
|
||||||
|
import akka.stream.scaladsl._
|
||||||
|
import akka.stream.stage._
|
||||||
import akka.http.ServerSettings
|
import akka.http.ServerSettings
|
||||||
import akka.http.impl.engine.HttpConnectionTimeoutException
|
import akka.http.impl.engine.HttpConnectionTimeoutException
|
||||||
import akka.http.impl.engine.parsing.ParserOutput._
|
import akka.http.impl.engine.parsing.ParserOutput._
|
||||||
|
|
@ -18,16 +25,12 @@ import akka.http.impl.engine.parsing._
|
||||||
import akka.http.impl.engine.rendering.{ HttpResponseRendererFactory, ResponseRenderingContext, ResponseRenderingOutput }
|
import akka.http.impl.engine.rendering.{ HttpResponseRendererFactory, ResponseRenderingContext, ResponseRenderingOutput }
|
||||||
import akka.http.impl.engine.ws._
|
import akka.http.impl.engine.ws._
|
||||||
import akka.http.impl.util._
|
import akka.http.impl.util._
|
||||||
import akka.http.scaladsl.Http
|
import akka.http.scaladsl.util.FastFuture.EnhancedFuture
|
||||||
|
import akka.http.scaladsl.{ TimeoutAccess, Http }
|
||||||
|
import akka.http.scaladsl.model.headers.`Timeout-Access`
|
||||||
|
import akka.http.javadsl.model
|
||||||
import akka.http.scaladsl.model._
|
import akka.http.scaladsl.model._
|
||||||
import akka.stream._
|
|
||||||
import akka.stream.impl.ConstantFun
|
|
||||||
import akka.stream.io._
|
|
||||||
import akka.stream.scaladsl._
|
|
||||||
import akka.stream.stage._
|
|
||||||
import akka.util.ByteString
|
|
||||||
import akka.http.scaladsl.model.ws.Message
|
import akka.http.scaladsl.model.ws.Message
|
||||||
import akka.stream.impl.fusing.SubSource
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* INTERNAL API
|
* INTERNAL API
|
||||||
|
|
@ -54,6 +57,7 @@ private[http] object HttpServerBluePrint {
|
||||||
def apply(settings: ServerSettings, remoteAddress: Option[InetSocketAddress], log: LoggingAdapter): Http.ServerLayer = {
|
def apply(settings: ServerSettings, remoteAddress: Option[InetSocketAddress], log: LoggingAdapter): Http.ServerLayer = {
|
||||||
val theStack =
|
val theStack =
|
||||||
userHandlerGuard(settings.pipeliningLimit) atop
|
userHandlerGuard(settings.pipeliningLimit) atop
|
||||||
|
requestTimeoutSupport(settings.timeouts.requestTimeout) atop
|
||||||
requestPreparation(settings) atop
|
requestPreparation(settings) atop
|
||||||
controller(settings, log) atop
|
controller(settings, log) atop
|
||||||
parsingRendering(settings, log) atop
|
parsingRendering(settings, log) atop
|
||||||
|
|
@ -78,6 +82,12 @@ private[http] object HttpServerBluePrint {
|
||||||
def requestPreparation(settings: ServerSettings): BidiFlow[HttpResponse, HttpResponse, RequestOutput, HttpRequest, Unit] =
|
def requestPreparation(settings: ServerSettings): BidiFlow[HttpResponse, HttpResponse, RequestOutput, HttpRequest, Unit] =
|
||||||
BidiFlow.fromFlows(Flow[HttpResponse], new PrepareRequests(settings))
|
BidiFlow.fromFlows(Flow[HttpResponse], new PrepareRequests(settings))
|
||||||
|
|
||||||
|
def requestTimeoutSupport(timeout: Duration): BidiFlow[HttpResponse, HttpResponse, HttpRequest, HttpRequest, Unit] =
|
||||||
|
timeout match {
|
||||||
|
case x: FiniteDuration ⇒ BidiFlow.fromGraph(new RequestTimeoutSupport(x)).reversed
|
||||||
|
case _ ⇒ BidiFlow.identity
|
||||||
|
}
|
||||||
|
|
||||||
final class PrepareRequests(settings: ServerSettings) extends GraphStage[FlowShape[RequestOutput, HttpRequest]] {
|
final class PrepareRequests(settings: ServerSettings) extends GraphStage[FlowShape[RequestOutput, HttpRequest]] {
|
||||||
val in = Inlet[RequestOutput]("RequestStartThenRunIgnore.in")
|
val in = Inlet[RequestOutput]("RequestStartThenRunIgnore.in")
|
||||||
val out = Outlet[HttpRequest]("RequestStartThenRunIgnore.out")
|
val out = Outlet[HttpRequest]("RequestStartThenRunIgnore.out")
|
||||||
|
|
@ -97,6 +107,7 @@ private[http] object HttpServerBluePrint {
|
||||||
|
|
||||||
val entity = createEntity(entityCreator) withSizeLimit settings.parserSettings.maxContentLength
|
val entity = createEntity(entityCreator) withSizeLimit settings.parserSettings.maxContentLength
|
||||||
push(out, HttpRequest(effectiveMethod, uri, effectiveHeaders, entity, protocol))
|
push(out, HttpRequest(effectiveMethod, uri, effectiveHeaders, entity, protocol))
|
||||||
|
case _ ⇒ throw new IllegalStateException
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setHandler(in, idle)
|
setHandler(in, idle)
|
||||||
|
|
@ -173,6 +184,104 @@ private[http] object HttpServerBluePrint {
|
||||||
.via(Flow[ResponseRenderingOutput].transform(() ⇒ errorHandling(errorHandler)).named("errorLogger"))
|
.via(Flow[ResponseRenderingOutput].transform(() ⇒ errorHandling(errorHandler)).named("errorLogger"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class RequestTimeoutSupport(initialTimeout: FiniteDuration)
|
||||||
|
extends GraphStage[BidiShape[HttpRequest, HttpRequest, HttpResponse, HttpResponse]] {
|
||||||
|
private val requestIn = Inlet[HttpRequest]("requestIn")
|
||||||
|
private val requestOut = Outlet[HttpRequest]("requestOut")
|
||||||
|
private val responseIn = Inlet[HttpResponse]("responseIn")
|
||||||
|
private val responseOut = Outlet[HttpResponse]("responseOut")
|
||||||
|
|
||||||
|
override def initialAttributes = Attributes.name("RequestTimeoutSupport")
|
||||||
|
|
||||||
|
val shape = new BidiShape(requestIn, requestOut, responseIn, responseOut)
|
||||||
|
|
||||||
|
def createLogic(effectiveAttributes: Attributes) = new GraphStageLogic(shape) {
|
||||||
|
var openTimeouts = immutable.Queue[TimeoutAccessImpl]()
|
||||||
|
setHandler(requestIn, new InHandler {
|
||||||
|
def onPush(): Unit = {
|
||||||
|
val request = grab(requestIn)
|
||||||
|
val (entity, requestEnd) = HttpEntity.captureTermination(request.entity)
|
||||||
|
val access = new TimeoutAccessImpl(request, initialTimeout, requestEnd,
|
||||||
|
getAsyncCallback(emitTimeoutResponse), interpreter.materializer)
|
||||||
|
openTimeouts = openTimeouts.enqueue(access)
|
||||||
|
push(requestOut, request.copy(headers = request.headers :+ `Timeout-Access`(access), entity = entity))
|
||||||
|
}
|
||||||
|
override def onUpstreamFinish() = complete(requestOut)
|
||||||
|
override def onUpstreamFailure(ex: Throwable) = fail(requestOut, ex)
|
||||||
|
def emitTimeoutResponse(response: (TimeoutAccess, HttpResponse)) =
|
||||||
|
if (openTimeouts.head eq response._1) {
|
||||||
|
emit(responseOut, response._2, () ⇒ complete(responseOut))
|
||||||
|
} // else the application response arrived after we scheduled the timeout response, which is close but ok
|
||||||
|
})
|
||||||
|
// TODO: provide and use default impl for simply connecting an input and an output port as we do here
|
||||||
|
setHandler(requestOut, new OutHandler {
|
||||||
|
def onPull(): Unit = pull(requestIn)
|
||||||
|
override def onDownstreamFinish() = cancel(requestIn)
|
||||||
|
})
|
||||||
|
setHandler(responseIn, new InHandler {
|
||||||
|
def onPush(): Unit = {
|
||||||
|
openTimeouts.head.clear()
|
||||||
|
openTimeouts = openTimeouts.tail
|
||||||
|
push(responseOut, grab(responseIn))
|
||||||
|
}
|
||||||
|
override def onUpstreamFinish() = complete(responseOut)
|
||||||
|
override def onUpstreamFailure(ex: Throwable) = fail(responseOut, ex)
|
||||||
|
})
|
||||||
|
setHandler(responseOut, new OutHandler {
|
||||||
|
def onPull(): Unit = pull(responseIn)
|
||||||
|
override def onDownstreamFinish() = cancel(responseIn)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TimeoutSetup(val timeoutBase: Deadline,
|
||||||
|
val scheduledTask: Cancellable,
|
||||||
|
val timeout: Duration,
|
||||||
|
val handler: HttpRequest ⇒ HttpResponse)
|
||||||
|
|
||||||
|
private class TimeoutAccessImpl(request: HttpRequest, initialTimeout: FiniteDuration, requestEnd: Future[Unit],
|
||||||
|
trigger: AsyncCallback[(TimeoutAccess, HttpResponse)], materializer: Materializer)
|
||||||
|
extends AtomicReference[Future[TimeoutSetup]] with TimeoutAccess with (HttpRequest ⇒ HttpResponse) { self ⇒
|
||||||
|
import materializer.executionContext
|
||||||
|
|
||||||
|
set {
|
||||||
|
requestEnd.fast.map(_ ⇒ new TimeoutSetup(Deadline.now, schedule(initialTimeout, this), initialTimeout, this))
|
||||||
|
}
|
||||||
|
|
||||||
|
override def apply(request: HttpRequest) = HttpResponse(StatusCodes.ServiceUnavailable, entity = "The server was not able " +
|
||||||
|
"to produce a timely response to your request.\r\nPlease try again in a short while!")
|
||||||
|
|
||||||
|
def clear(): Unit = // best effort timeout cancellation
|
||||||
|
get.fast.foreach(setup ⇒ if (setup.scheduledTask ne null) setup.scheduledTask.cancel())
|
||||||
|
|
||||||
|
override def updateTimeout(timeout: Duration): Unit = update(timeout, null: HttpRequest ⇒ HttpResponse)
|
||||||
|
override def updateHandler(handler: HttpRequest ⇒ HttpResponse): Unit = update(null, handler)
|
||||||
|
override def update(timeout: Duration, handler: HttpRequest ⇒ HttpResponse): Unit = {
|
||||||
|
val promise = Promise[TimeoutSetup]()
|
||||||
|
for (old ← getAndSet(promise.future).fast)
|
||||||
|
promise.success {
|
||||||
|
if ((old.scheduledTask eq null) || old.scheduledTask.cancel()) {
|
||||||
|
val newHandler = if (handler eq null) old.handler else handler
|
||||||
|
val newTimeout = if (timeout eq null) old.timeout else timeout
|
||||||
|
val newScheduling = newTimeout match {
|
||||||
|
case x: FiniteDuration ⇒ schedule(old.timeoutBase + x - Deadline.now, newHandler)
|
||||||
|
case _ ⇒ null // don't schedule a new timeout
|
||||||
|
}
|
||||||
|
new TimeoutSetup(old.timeoutBase, newScheduling, newTimeout, newHandler)
|
||||||
|
} else old // too late, the previously set timeout cannot be cancelled anymore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private def schedule(delay: FiniteDuration, handler: HttpRequest ⇒ HttpResponse): Cancellable =
|
||||||
|
materializer.scheduleOnce(delay, new Runnable { def run() = trigger.invoke(self, handler(request)) })
|
||||||
|
|
||||||
|
import akka.http.impl.util.JavaMapping.Implicits._
|
||||||
|
/** JAVA API **/
|
||||||
|
def update(timeout: Duration, handler: Function[model.HttpRequest, model.HttpResponse]): Unit =
|
||||||
|
update(timeout, handler(_: HttpRequest).asScala)
|
||||||
|
def updateHandler(handler: Function[model.HttpRequest, model.HttpResponse]): Unit =
|
||||||
|
updateHandler(handler(_: HttpRequest).asScala)
|
||||||
|
}
|
||||||
|
|
||||||
class ControllerStage(settings: ServerSettings, log: LoggingAdapter)
|
class ControllerStage(settings: ServerSettings, log: LoggingAdapter)
|
||||||
extends GraphStage[BidiShape[RequestOutput, RequestOutput, HttpResponse, ResponseRenderingContext]] {
|
extends GraphStage[BidiShape[RequestOutput, RequestOutput, HttpResponse, ResponseRenderingContext]] {
|
||||||
private val requestParsingIn = Inlet[RequestOutput]("requestParsingIn")
|
private val requestParsingIn = Inlet[RequestOutput]("requestParsingIn")
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ private[http] final case class UpgradeToWebsocketResponseHeader(handler: Either[
|
||||||
extends InternalCustomHeader("UpgradeToWebsocketResponseHeader")
|
extends InternalCustomHeader("UpgradeToWebsocketResponseHeader")
|
||||||
|
|
||||||
private[http] abstract class InternalCustomHeader(val name: String) extends CustomHeader {
|
private[http] abstract class InternalCustomHeader(val name: String) extends CustomHeader {
|
||||||
override def suppressRendering: Boolean = true
|
final def renderInRequests = false
|
||||||
|
final def renderInResponses = false
|
||||||
def value(): String = ""
|
def value: String = ""
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package akka.http.scaladsl
|
||||||
|
|
||||||
|
import scala.concurrent.duration.Duration
|
||||||
|
import akka.http.scaladsl.model.{ HttpResponse, HttpRequest }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables programmatic access to the server-side request timeout logic.
|
||||||
|
*/
|
||||||
|
trait TimeoutAccess extends akka.http.javadsl.TimeoutAccess {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to set a new timeout.
|
||||||
|
* The timeout period is measured as of the point in time that the end of the request has been received,
|
||||||
|
* which may be in the past or in the future!
|
||||||
|
* Use `Duration.Inf` to completely disable request timeout checking for this request.
|
||||||
|
*
|
||||||
|
* Due to the inherent raciness it is not guaranteed that the update will be applied before
|
||||||
|
* the previously set timeout has expired!
|
||||||
|
*/
|
||||||
|
def updateTimeout(timeout: Duration): Unit
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to set a new timeout handler, which produces the timeout response for a
|
||||||
|
* given request. Note that the handler must produce the response synchronously and shouldn't block!
|
||||||
|
*
|
||||||
|
* Due to the inherent raciness it is not guaranteed that the update will be applied before
|
||||||
|
* the previously set timeout has expired!
|
||||||
|
*/
|
||||||
|
def updateHandler(handler: HttpRequest ⇒ HttpResponse): Unit
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to set a new timeout and handler at the same time.
|
||||||
|
*
|
||||||
|
* Due to the inherent raciness it is not guaranteed that the update will be applied before
|
||||||
|
* the previously set timeout has expired!
|
||||||
|
*/
|
||||||
|
def update(timeout: Duration, handler: HttpRequest ⇒ HttpResponse): Unit
|
||||||
|
}
|
||||||
|
|
@ -21,6 +21,7 @@ import akka.{ japi, stream }
|
||||||
import akka.http.scaladsl.model.ContentType.{ NonBinary, Binary }
|
import akka.http.scaladsl.model.ContentType.{ NonBinary, Binary }
|
||||||
import akka.http.scaladsl.util.FastFuture
|
import akka.http.scaladsl.util.FastFuture
|
||||||
import akka.http.javadsl.{ model ⇒ jm }
|
import akka.http.javadsl.{ model ⇒ jm }
|
||||||
|
import akka.http.impl.util.StreamUtils
|
||||||
import akka.http.impl.util.JavaMapping.Implicits._
|
import akka.http.impl.util.JavaMapping.Implicits._
|
||||||
|
|
||||||
import scala.compat.java8.OptionConverters._
|
import scala.compat.java8.OptionConverters._
|
||||||
|
|
@ -503,4 +504,24 @@ object HttpEntity {
|
||||||
private object SizeLimit {
|
private object SizeLimit {
|
||||||
val Disabled = -1 // any negative value will do
|
val Disabled = -1 // any negative value will do
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* INTERNAL API
|
||||||
|
*/
|
||||||
|
private[http] def captureTermination[T <: HttpEntity](entity: T): (T, Future[Unit]) =
|
||||||
|
entity match {
|
||||||
|
case x: HttpEntity.Strict ⇒ x.asInstanceOf[T] -> FastFuture.successful(())
|
||||||
|
case x: HttpEntity.Default ⇒
|
||||||
|
val (newData, whenCompleted) = StreamUtils.captureTermination(x.data)
|
||||||
|
x.copy(data = newData).asInstanceOf[T] -> whenCompleted
|
||||||
|
case x: HttpEntity.Chunked ⇒
|
||||||
|
val (newChunks, whenCompleted) = StreamUtils.captureTermination(x.chunks)
|
||||||
|
x.copy(chunks = newChunks).asInstanceOf[T] -> whenCompleted
|
||||||
|
case x: HttpEntity.CloseDelimited ⇒
|
||||||
|
val (newData, whenCompleted) = StreamUtils.captureTermination(x.data)
|
||||||
|
x.copy(data = newData).asInstanceOf[T] -> whenCompleted
|
||||||
|
case x: HttpEntity.IndefiniteLength ⇒
|
||||||
|
val (newData, whenCompleted) = StreamUtils.captureTermination(x.data)
|
||||||
|
x.copy(data = newData).asInstanceOf[T] -> whenCompleted
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,16 +9,12 @@ import java.net.InetSocketAddress
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.util
|
import java.util
|
||||||
import javax.net.ssl.SSLSession
|
import javax.net.ssl.SSLSession
|
||||||
|
|
||||||
import akka.stream.io.ScalaSessionAPI
|
|
||||||
|
|
||||||
import scala.reflect.ClassTag
|
import scala.reflect.ClassTag
|
||||||
import scala.util.{ Failure, Success, Try }
|
import scala.util.{ Failure, Success, Try }
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
import scala.collection.immutable
|
import scala.collection.immutable
|
||||||
|
|
||||||
import akka.parboiled2.util.Base64
|
import akka.parboiled2.util.Base64
|
||||||
|
import akka.stream.io.ScalaSessionAPI
|
||||||
import akka.http.impl.util._
|
import akka.http.impl.util._
|
||||||
import akka.http.javadsl.{ model ⇒ jm }
|
import akka.http.javadsl.{ model ⇒ jm }
|
||||||
import akka.http.scaladsl.model._
|
import akka.http.scaladsl.model._
|
||||||
|
|
@ -41,6 +37,8 @@ sealed abstract class ModeledCompanion[T: ClassTag] extends Renderable {
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed trait ModeledHeader extends HttpHeader with Serializable {
|
sealed trait ModeledHeader extends HttpHeader with Serializable {
|
||||||
|
def renderInRequests: Boolean = false // default implementation
|
||||||
|
def renderInResponses: Boolean = false // default implementation
|
||||||
def name: String = companion.name
|
def name: String = companion.name
|
||||||
def value: String = renderValue(new StringRendering).get
|
def value: String = renderValue(new StringRendering).get
|
||||||
def lowercaseName: String = companion.lowercaseName
|
def lowercaseName: String = companion.lowercaseName
|
||||||
|
|
@ -49,6 +47,11 @@ sealed trait ModeledHeader extends HttpHeader with Serializable {
|
||||||
protected def companion: ModeledCompanion[_]
|
protected def companion: ModeledCompanion[_]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private[headers] sealed trait RequestHeader extends ModeledHeader { override def renderInRequests = true }
|
||||||
|
private[headers] sealed trait ResponseHeader extends ModeledHeader { override def renderInResponses = true }
|
||||||
|
private[headers] sealed trait RequestResponseHeader extends RequestHeader with ResponseHeader
|
||||||
|
private[headers] sealed trait SyntheticHeader extends ModeledHeader
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Superclass for user-defined custom headers defined by implementing `name` and `value`.
|
* Superclass for user-defined custom headers defined by implementing `name` and `value`.
|
||||||
*
|
*
|
||||||
|
|
@ -57,9 +60,6 @@ sealed trait ModeledHeader extends HttpHeader with Serializable {
|
||||||
* as they allow the custom header to be matched from [[RawHeader]] and vice-versa.
|
* as they allow the custom header to be matched from [[RawHeader]] and vice-versa.
|
||||||
*/
|
*/
|
||||||
abstract class CustomHeader extends jm.headers.CustomHeader {
|
abstract class CustomHeader extends jm.headers.CustomHeader {
|
||||||
/** Override to return true if this header shouldn't be rendered */
|
|
||||||
def suppressRendering: Boolean = false
|
|
||||||
|
|
||||||
def lowercaseName: String = name.toRootLowerCase
|
def lowercaseName: String = name.toRootLowerCase
|
||||||
final def render[R <: Rendering](r: R): r.type = r ~~ name ~~ ':' ~~ ' ' ~~ value
|
final def render[R <: Rendering](r: R): r.type = r ~~ name ~~ ':' ~~ ' ' ~~ value
|
||||||
}
|
}
|
||||||
|
|
@ -98,17 +98,243 @@ abstract class ModeledCustomHeader[H <: ModeledCustomHeader[H]] extends CustomHe
|
||||||
def companion: ModeledCustomHeaderCompanion[H]
|
def companion: ModeledCustomHeaderCompanion[H]
|
||||||
|
|
||||||
final override def name = companion.name
|
final override def name = companion.name
|
||||||
final override def lowercaseName: String = name.toRootLowerCase
|
final override def lowercaseName = name.toRootLowerCase
|
||||||
}
|
}
|
||||||
|
|
||||||
import akka.http.impl.util.JavaMapping.Implicits._
|
import akka.http.impl.util.JavaMapping.Implicits._
|
||||||
|
|
||||||
|
// http://tools.ietf.org/html/rfc7231#section-5.3.2
|
||||||
|
object Accept extends ModeledCompanion[Accept] {
|
||||||
|
def apply(mediaRanges: MediaRange*): Accept = apply(immutable.Seq(mediaRanges: _*))
|
||||||
|
implicit val mediaRangesRenderer = Renderer.defaultSeqRenderer[MediaRange] // cache
|
||||||
|
}
|
||||||
|
final case class Accept(mediaRanges: immutable.Seq[MediaRange]) extends jm.headers.Accept with RequestHeader {
|
||||||
|
import Accept.mediaRangesRenderer
|
||||||
|
def renderValue[R <: Rendering](r: R): r.type = r ~~ mediaRanges
|
||||||
|
protected def companion = Accept
|
||||||
|
def acceptsAll = mediaRanges.exists(mr ⇒ mr.isWildcard && mr.qValue > 0f)
|
||||||
|
|
||||||
|
/** Java API */
|
||||||
|
def getMediaRanges: Iterable[jm.MediaRange] = mediaRanges.asJava
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://tools.ietf.org/html/rfc7231#section-5.3.3
|
||||||
|
object `Accept-Charset` extends ModeledCompanion[`Accept-Charset`] {
|
||||||
|
def apply(first: HttpCharsetRange, more: HttpCharsetRange*): `Accept-Charset` = apply(immutable.Seq(first +: more: _*))
|
||||||
|
implicit val charsetRangesRenderer = Renderer.defaultSeqRenderer[HttpCharsetRange] // cache
|
||||||
|
}
|
||||||
|
final case class `Accept-Charset`(charsetRanges: immutable.Seq[HttpCharsetRange]) extends jm.headers.AcceptCharset
|
||||||
|
with RequestHeader {
|
||||||
|
require(charsetRanges.nonEmpty, "charsetRanges must not be empty")
|
||||||
|
import `Accept-Charset`.charsetRangesRenderer
|
||||||
|
def renderValue[R <: Rendering](r: R): r.type = r ~~ charsetRanges
|
||||||
|
protected def companion = `Accept-Charset`
|
||||||
|
|
||||||
|
/** Java API */
|
||||||
|
def getCharsetRanges: Iterable[jm.HttpCharsetRange] = charsetRanges.asJava
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://tools.ietf.org/html/rfc7231#section-5.3.4
|
||||||
|
object `Accept-Encoding` extends ModeledCompanion[`Accept-Encoding`] {
|
||||||
|
def apply(encodings: HttpEncodingRange*): `Accept-Encoding` = apply(immutable.Seq(encodings: _*))
|
||||||
|
implicit val encodingsRenderer = Renderer.defaultSeqRenderer[HttpEncodingRange] // cache
|
||||||
|
}
|
||||||
|
final case class `Accept-Encoding`(encodings: immutable.Seq[HttpEncodingRange]) extends jm.headers.AcceptEncoding
|
||||||
|
with RequestHeader {
|
||||||
|
import `Accept-Encoding`.encodingsRenderer
|
||||||
|
def renderValue[R <: Rendering](r: R): r.type = r ~~ encodings
|
||||||
|
protected def companion = `Accept-Encoding`
|
||||||
|
|
||||||
|
/** Java API */
|
||||||
|
def getEncodings: Iterable[jm.headers.HttpEncodingRange] = encodings.asJava
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://tools.ietf.org/html/rfc7231#section-5.3.5
|
||||||
|
object `Accept-Language` extends ModeledCompanion[`Accept-Language`] {
|
||||||
|
def apply(first: LanguageRange, more: LanguageRange*): `Accept-Language` = apply(immutable.Seq(first +: more: _*))
|
||||||
|
implicit val languagesRenderer = Renderer.defaultSeqRenderer[LanguageRange] // cache
|
||||||
|
}
|
||||||
|
final case class `Accept-Language`(languages: immutable.Seq[LanguageRange]) extends jm.headers.AcceptLanguage
|
||||||
|
with RequestHeader {
|
||||||
|
require(languages.nonEmpty, "languages must not be empty")
|
||||||
|
import `Accept-Language`.languagesRenderer
|
||||||
|
def renderValue[R <: Rendering](r: R): r.type = r ~~ languages
|
||||||
|
protected def companion = `Accept-Language`
|
||||||
|
|
||||||
|
/** Java API */
|
||||||
|
def getLanguages: Iterable[jm.headers.LanguageRange] = languages.asJava
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://tools.ietf.org/html/rfc7233#section-2.3
|
||||||
|
object `Accept-Ranges` extends ModeledCompanion[`Accept-Ranges`] {
|
||||||
|
def apply(rangeUnits: RangeUnit*): `Accept-Ranges` = apply(immutable.Seq(rangeUnits: _*))
|
||||||
|
implicit val rangeUnitsRenderer = Renderer.defaultSeqRenderer[RangeUnit] // cache
|
||||||
|
}
|
||||||
|
final case class `Accept-Ranges`(rangeUnits: immutable.Seq[RangeUnit]) extends jm.headers.AcceptRanges
|
||||||
|
with RequestHeader {
|
||||||
|
import `Accept-Ranges`.rangeUnitsRenderer
|
||||||
|
def renderValue[R <: Rendering](r: R): r.type = if (rangeUnits.isEmpty) r ~~ "none" else r ~~ rangeUnits
|
||||||
|
protected def companion = `Accept-Ranges`
|
||||||
|
|
||||||
|
/** Java API */
|
||||||
|
def getRangeUnits: Iterable[jm.headers.RangeUnit] = rangeUnits.asJava
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://www.w3.org/TR/cors/#access-control-allow-credentials-response-header
|
||||||
|
object `Access-Control-Allow-Credentials` extends ModeledCompanion[`Access-Control-Allow-Credentials`]
|
||||||
|
final case class `Access-Control-Allow-Credentials`(allow: Boolean)
|
||||||
|
extends jm.headers.AccessControlAllowCredentials with RequestHeader {
|
||||||
|
def renderValue[R <: Rendering](r: R): r.type = r ~~ allow.toString
|
||||||
|
protected def companion = `Access-Control-Allow-Credentials`
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://www.w3.org/TR/cors/#access-control-allow-headers-response-header
|
||||||
|
object `Access-Control-Allow-Headers` extends ModeledCompanion[`Access-Control-Allow-Headers`] {
|
||||||
|
def apply(headers: String*): `Access-Control-Allow-Headers` = apply(immutable.Seq(headers: _*))
|
||||||
|
implicit val headersRenderer = Renderer.defaultSeqRenderer[String] // cache
|
||||||
|
}
|
||||||
|
final case class `Access-Control-Allow-Headers`(headers: immutable.Seq[String])
|
||||||
|
extends jm.headers.AccessControlAllowHeaders with RequestHeader {
|
||||||
|
import `Access-Control-Allow-Headers`.headersRenderer
|
||||||
|
def renderValue[R <: Rendering](r: R): r.type = r ~~ headers
|
||||||
|
protected def companion = `Access-Control-Allow-Headers`
|
||||||
|
|
||||||
|
/** Java API */
|
||||||
|
def getHeaders: Iterable[String] = headers.asJava
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://www.w3.org/TR/cors/#access-control-allow-methods-response-header
|
||||||
|
object `Access-Control-Allow-Methods` extends ModeledCompanion[`Access-Control-Allow-Methods`] {
|
||||||
|
def apply(methods: HttpMethod*): `Access-Control-Allow-Methods` = apply(immutable.Seq(methods: _*))
|
||||||
|
implicit val methodsRenderer = Renderer.defaultSeqRenderer[HttpMethod] // cache
|
||||||
|
}
|
||||||
|
final case class `Access-Control-Allow-Methods`(methods: immutable.Seq[HttpMethod])
|
||||||
|
extends jm.headers.AccessControlAllowMethods with RequestHeader {
|
||||||
|
import `Access-Control-Allow-Methods`.methodsRenderer
|
||||||
|
def renderValue[R <: Rendering](r: R): r.type = r ~~ methods
|
||||||
|
protected def companion = `Access-Control-Allow-Methods`
|
||||||
|
|
||||||
|
/** Java API */
|
||||||
|
def getMethods: Iterable[jm.HttpMethod] = methods.asJava
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://www.w3.org/TR/cors/#access-control-allow-origin-response-header
|
||||||
|
object `Access-Control-Allow-Origin` extends ModeledCompanion[`Access-Control-Allow-Origin`] {
|
||||||
|
val `*` = forRange(HttpOriginRange.`*`)
|
||||||
|
val `null` = forRange(HttpOriginRange())
|
||||||
|
def apply(origin: HttpOrigin) = forRange(HttpOriginRange(origin))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an `Access-Control-Allow-Origin` header for the given origin range.
|
||||||
|
*
|
||||||
|
* CAUTION: Even though allowed by the spec (http://www.w3.org/TR/cors/#access-control-allow-origin-response-header)
|
||||||
|
* `Access-Control-Allow-Origin` headers with more than a single origin appear to be largely unsupported in the field.
|
||||||
|
* Make sure to thoroughly test such usages with all expected clients!
|
||||||
|
*/
|
||||||
|
def forRange(range: HttpOriginRange) = new `Access-Control-Allow-Origin`(range)
|
||||||
|
}
|
||||||
|
final case class `Access-Control-Allow-Origin` private (range: HttpOriginRange)
|
||||||
|
extends jm.headers.AccessControlAllowOrigin with RequestHeader {
|
||||||
|
def renderValue[R <: Rendering](r: R): r.type = r ~~ range
|
||||||
|
protected def companion = `Access-Control-Allow-Origin`
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://www.w3.org/TR/cors/#access-control-expose-headers-response-header
|
||||||
|
object `Access-Control-Expose-Headers` extends ModeledCompanion[`Access-Control-Expose-Headers`] {
|
||||||
|
def apply(headers: String*): `Access-Control-Expose-Headers` = apply(immutable.Seq(headers: _*))
|
||||||
|
implicit val headersRenderer = Renderer.defaultSeqRenderer[String] // cache
|
||||||
|
}
|
||||||
|
final case class `Access-Control-Expose-Headers`(headers: immutable.Seq[String])
|
||||||
|
extends jm.headers.AccessControlExposeHeaders with RequestHeader {
|
||||||
|
import `Access-Control-Expose-Headers`.headersRenderer
|
||||||
|
def renderValue[R <: Rendering](r: R): r.type = r ~~ headers
|
||||||
|
protected def companion = `Access-Control-Expose-Headers`
|
||||||
|
|
||||||
|
/** Java API */
|
||||||
|
def getHeaders: Iterable[String] = headers.asJava
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://www.w3.org/TR/cors/#access-control-max-age-response-header
|
||||||
|
object `Access-Control-Max-Age` extends ModeledCompanion[`Access-Control-Max-Age`]
|
||||||
|
final case class `Access-Control-Max-Age`(deltaSeconds: Long) extends jm.headers.AccessControlMaxAge
|
||||||
|
with RequestHeader {
|
||||||
|
def renderValue[R <: Rendering](r: R): r.type = r ~~ deltaSeconds
|
||||||
|
protected def companion = `Access-Control-Max-Age`
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://www.w3.org/TR/cors/#access-control-request-headers-request-header
|
||||||
|
object `Access-Control-Request-Headers` extends ModeledCompanion[`Access-Control-Request-Headers`] {
|
||||||
|
def apply(headers: String*): `Access-Control-Request-Headers` = apply(immutable.Seq(headers: _*))
|
||||||
|
implicit val headersRenderer = Renderer.defaultSeqRenderer[String] // cache
|
||||||
|
}
|
||||||
|
final case class `Access-Control-Request-Headers`(headers: immutable.Seq[String])
|
||||||
|
extends jm.headers.AccessControlRequestHeaders with RequestHeader {
|
||||||
|
import `Access-Control-Request-Headers`.headersRenderer
|
||||||
|
def renderValue[R <: Rendering](r: R): r.type = r ~~ headers
|
||||||
|
protected def companion = `Access-Control-Request-Headers`
|
||||||
|
|
||||||
|
/** Java API */
|
||||||
|
def getHeaders: Iterable[String] = headers.asJava
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://www.w3.org/TR/cors/#access-control-request-method-request-header
|
||||||
|
object `Access-Control-Request-Method` extends ModeledCompanion[`Access-Control-Request-Method`]
|
||||||
|
final case class `Access-Control-Request-Method`(method: HttpMethod) extends jm.headers.AccessControlRequestMethod
|
||||||
|
with RequestHeader {
|
||||||
|
def renderValue[R <: Rendering](r: R): r.type = r ~~ method
|
||||||
|
protected def companion = `Access-Control-Request-Method`
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://tools.ietf.org/html/rfc7234#section-5.1
|
||||||
|
object Age extends ModeledCompanion[Age]
|
||||||
|
final case class Age(deltaSeconds: Long) extends jm.headers.Age with ResponseHeader {
|
||||||
|
def renderValue[R <: Rendering](r: R): r.type = r ~~ deltaSeconds
|
||||||
|
protected def companion = Age
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://tools.ietf.org/html/rfc7231#section-7.4.1
|
||||||
|
object Allow extends ModeledCompanion[Allow] {
|
||||||
|
def apply(methods: HttpMethod*): Allow = apply(immutable.Seq(methods: _*))
|
||||||
|
implicit val methodsRenderer = Renderer.defaultSeqRenderer[HttpMethod] // cache
|
||||||
|
}
|
||||||
|
final case class Allow(methods: immutable.Seq[HttpMethod]) extends jm.headers.Allow with ResponseHeader {
|
||||||
|
import Allow.methodsRenderer
|
||||||
|
def renderValue[R <: Rendering](r: R): r.type = r ~~ methods
|
||||||
|
protected def companion = Allow
|
||||||
|
|
||||||
|
/** Java API */
|
||||||
|
def getMethods: Iterable[jm.HttpMethod] = methods.asJava
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://tools.ietf.org/html/rfc7235#section-4.2
|
||||||
|
object Authorization extends ModeledCompanion[Authorization]
|
||||||
|
final case class Authorization(credentials: HttpCredentials) extends jm.headers.Authorization with RequestHeader {
|
||||||
|
def renderValue[R <: Rendering](r: R): r.type = r ~~ credentials
|
||||||
|
protected def companion = Authorization
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://tools.ietf.org/html/rfc7234#section-5.2
|
||||||
|
object `Cache-Control` extends ModeledCompanion[`Cache-Control`] {
|
||||||
|
def apply(first: CacheDirective, more: CacheDirective*): `Cache-Control` = apply(immutable.Seq(first +: more: _*))
|
||||||
|
implicit val directivesRenderer = Renderer.defaultSeqRenderer[CacheDirective] // cache
|
||||||
|
}
|
||||||
|
final case class `Cache-Control`(directives: immutable.Seq[CacheDirective]) extends jm.headers.CacheControl
|
||||||
|
with RequestResponseHeader {
|
||||||
|
require(directives.nonEmpty, "directives must not be empty")
|
||||||
|
import `Cache-Control`.directivesRenderer
|
||||||
|
def renderValue[R <: Rendering](r: R): r.type = r ~~ directives
|
||||||
|
protected def companion = `Cache-Control`
|
||||||
|
|
||||||
|
/** Java API */
|
||||||
|
def getDirectives: Iterable[jm.headers.CacheDirective] = directives.asJava
|
||||||
|
}
|
||||||
|
|
||||||
// http://tools.ietf.org/html/rfc7230#section-6.1
|
// http://tools.ietf.org/html/rfc7230#section-6.1
|
||||||
object Connection extends ModeledCompanion[Connection] {
|
object Connection extends ModeledCompanion[Connection] {
|
||||||
def apply(first: String, more: String*): Connection = apply(immutable.Seq(first +: more: _*))
|
def apply(first: String, more: String*): Connection = apply(immutable.Seq(first +: more: _*))
|
||||||
implicit val tokensRenderer = Renderer.defaultSeqRenderer[String] // cache
|
implicit val tokensRenderer = Renderer.defaultSeqRenderer[String] // cache
|
||||||
}
|
}
|
||||||
final case class Connection(tokens: immutable.Seq[String]) extends ModeledHeader {
|
final case class Connection(tokens: immutable.Seq[String]) extends RequestResponseHeader {
|
||||||
require(tokens.nonEmpty, "tokens must not be empty")
|
require(tokens.nonEmpty, "tokens must not be empty")
|
||||||
import Connection.tokensRenderer
|
import Connection.tokensRenderer
|
||||||
def renderValue[R <: Rendering](r: R): r.type = r ~~ tokens
|
def renderValue[R <: Rendering](r: R): r.type = r ~~ tokens
|
||||||
|
|
@ -133,277 +359,20 @@ object `Content-Length` extends ModeledCompanion[`Content-Length`]
|
||||||
* Instances of this class will only be created transiently during header parsing and will never appear
|
* Instances of this class will only be created transiently during header parsing and will never appear
|
||||||
* in HttpMessage.header. To access the Content-Length, see subclasses of HttpEntity.
|
* in HttpMessage.header. To access the Content-Length, see subclasses of HttpEntity.
|
||||||
*/
|
*/
|
||||||
final case class `Content-Length` private[http] (length: Long) extends ModeledHeader {
|
final case class `Content-Length` private[http] (length: Long) extends RequestResponseHeader {
|
||||||
def renderValue[R <: Rendering](r: R): r.type = r ~~ length
|
def renderValue[R <: Rendering](r: R): r.type = r ~~ length
|
||||||
protected def companion = `Content-Length`
|
protected def companion = `Content-Length`
|
||||||
}
|
}
|
||||||
|
|
||||||
// http://tools.ietf.org/html/rfc7231#section-5.1.1
|
|
||||||
object Expect extends ModeledCompanion[Expect] {
|
|
||||||
val `100-continue` = new Expect() {}
|
|
||||||
}
|
|
||||||
sealed abstract case class Expect private () extends ModeledHeader {
|
|
||||||
final def renderValue[R <: Rendering](r: R): r.type = r ~~ "100-continue"
|
|
||||||
protected def companion = Expect
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://tools.ietf.org/html/rfc7230#section-5.4
|
|
||||||
object Host extends ModeledCompanion[Host] {
|
|
||||||
def apply(authority: Uri.Authority): Host = apply(authority.host, authority.port)
|
|
||||||
def apply(address: InetSocketAddress): Host = apply(address.getHostString, address.getPort)
|
|
||||||
def apply(host: String): Host = apply(host, 0)
|
|
||||||
def apply(host: String, port: Int): Host = apply(Uri.Host(host), port)
|
|
||||||
val empty = Host("")
|
|
||||||
}
|
|
||||||
final case class Host(host: Uri.Host, port: Int = 0) extends jm.headers.Host with ModeledHeader {
|
|
||||||
import UriRendering.HostRenderer
|
|
||||||
require((port >> 16) == 0, "Illegal port: " + port)
|
|
||||||
def isEmpty = host.isEmpty
|
|
||||||
def renderValue[R <: Rendering](r: R): r.type = if (port > 0) r ~~ host ~~ ':' ~~ port else r ~~ host
|
|
||||||
protected def companion = Host
|
|
||||||
def equalsIgnoreCase(other: Host): Boolean = host.equalsIgnoreCase(other.host) && port == other.port
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://tools.ietf.org/html/rfc7233#section-3.2
|
|
||||||
object `If-Range` extends ModeledCompanion[`If-Range`] {
|
|
||||||
def apply(tag: EntityTag): `If-Range` = apply(Left(tag))
|
|
||||||
def apply(timestamp: DateTime): `If-Range` = apply(Right(timestamp))
|
|
||||||
}
|
|
||||||
final case class `If-Range`(entityTagOrDateTime: Either[EntityTag, DateTime]) extends ModeledHeader {
|
|
||||||
def renderValue[R <: Rendering](r: R): r.type =
|
|
||||||
entityTagOrDateTime match {
|
|
||||||
case Left(tag) ⇒ r ~~ tag
|
|
||||||
case Right(dateTime) ⇒ dateTime.renderRfc1123DateTimeString(r)
|
|
||||||
}
|
|
||||||
protected def companion = `If-Range`
|
|
||||||
}
|
|
||||||
|
|
||||||
final case class RawHeader(name: String, value: String) extends jm.headers.RawHeader {
|
|
||||||
val lowercaseName = name.toRootLowerCase
|
|
||||||
def render[R <: Rendering](r: R): r.type = r ~~ name ~~ ':' ~~ ' ' ~~ value
|
|
||||||
}
|
|
||||||
object RawHeader {
|
|
||||||
def unapply[H <: HttpHeader](customHeader: H): Option[(String, String)] =
|
|
||||||
Some(customHeader.name -> customHeader.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://tools.ietf.org/html/rfc7231#section-5.3.2
|
|
||||||
object Accept extends ModeledCompanion[Accept] {
|
|
||||||
def apply(mediaRanges: MediaRange*): Accept = apply(immutable.Seq(mediaRanges: _*))
|
|
||||||
implicit val mediaRangesRenderer = Renderer.defaultSeqRenderer[MediaRange] // cache
|
|
||||||
}
|
|
||||||
final case class Accept(mediaRanges: immutable.Seq[MediaRange]) extends jm.headers.Accept with ModeledHeader {
|
|
||||||
import Accept.mediaRangesRenderer
|
|
||||||
def renderValue[R <: Rendering](r: R): r.type = r ~~ mediaRanges
|
|
||||||
protected def companion = Accept
|
|
||||||
def acceptsAll = mediaRanges.exists(mr ⇒ mr.isWildcard && mr.qValue > 0f)
|
|
||||||
|
|
||||||
/** Java API */
|
|
||||||
def getMediaRanges: Iterable[jm.MediaRange] = mediaRanges.asJava
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://tools.ietf.org/html/rfc7231#section-5.3.3
|
|
||||||
object `Accept-Charset` extends ModeledCompanion[`Accept-Charset`] {
|
|
||||||
def apply(first: HttpCharsetRange, more: HttpCharsetRange*): `Accept-Charset` = apply(immutable.Seq(first +: more: _*))
|
|
||||||
implicit val charsetRangesRenderer = Renderer.defaultSeqRenderer[HttpCharsetRange] // cache
|
|
||||||
}
|
|
||||||
final case class `Accept-Charset`(charsetRanges: immutable.Seq[HttpCharsetRange]) extends jm.headers.AcceptCharset with ModeledHeader {
|
|
||||||
require(charsetRanges.nonEmpty, "charsetRanges must not be empty")
|
|
||||||
import `Accept-Charset`.charsetRangesRenderer
|
|
||||||
def renderValue[R <: Rendering](r: R): r.type = r ~~ charsetRanges
|
|
||||||
protected def companion = `Accept-Charset`
|
|
||||||
|
|
||||||
/** Java API */
|
|
||||||
def getCharsetRanges: Iterable[jm.HttpCharsetRange] = charsetRanges.asJava
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://tools.ietf.org/html/rfc7231#section-5.3.4
|
|
||||||
object `Accept-Encoding` extends ModeledCompanion[`Accept-Encoding`] {
|
|
||||||
def apply(encodings: HttpEncodingRange*): `Accept-Encoding` = apply(immutable.Seq(encodings: _*))
|
|
||||||
implicit val encodingsRenderer = Renderer.defaultSeqRenderer[HttpEncodingRange] // cache
|
|
||||||
}
|
|
||||||
final case class `Accept-Encoding`(encodings: immutable.Seq[HttpEncodingRange]) extends jm.headers.AcceptEncoding with ModeledHeader {
|
|
||||||
import `Accept-Encoding`.encodingsRenderer
|
|
||||||
def renderValue[R <: Rendering](r: R): r.type = r ~~ encodings
|
|
||||||
protected def companion = `Accept-Encoding`
|
|
||||||
|
|
||||||
/** Java API */
|
|
||||||
def getEncodings: Iterable[jm.headers.HttpEncodingRange] = encodings.asJava
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://tools.ietf.org/html/rfc7231#section-5.3.5
|
|
||||||
object `Accept-Language` extends ModeledCompanion[`Accept-Language`] {
|
|
||||||
def apply(first: LanguageRange, more: LanguageRange*): `Accept-Language` = apply(immutable.Seq(first +: more: _*))
|
|
||||||
implicit val languagesRenderer = Renderer.defaultSeqRenderer[LanguageRange] // cache
|
|
||||||
}
|
|
||||||
final case class `Accept-Language`(languages: immutable.Seq[LanguageRange]) extends jm.headers.AcceptLanguage with ModeledHeader {
|
|
||||||
require(languages.nonEmpty, "languages must not be empty")
|
|
||||||
import `Accept-Language`.languagesRenderer
|
|
||||||
def renderValue[R <: Rendering](r: R): r.type = r ~~ languages
|
|
||||||
protected def companion = `Accept-Language`
|
|
||||||
|
|
||||||
/** Java API */
|
|
||||||
def getLanguages: Iterable[jm.headers.LanguageRange] = languages.asJava
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://tools.ietf.org/html/rfc7233#section-2.3
|
|
||||||
object `Accept-Ranges` extends ModeledCompanion[`Accept-Ranges`] {
|
|
||||||
def apply(rangeUnits: RangeUnit*): `Accept-Ranges` = apply(immutable.Seq(rangeUnits: _*))
|
|
||||||
implicit val rangeUnitsRenderer = Renderer.defaultSeqRenderer[RangeUnit] // cache
|
|
||||||
}
|
|
||||||
final case class `Accept-Ranges`(rangeUnits: immutable.Seq[RangeUnit]) extends jm.headers.AcceptRanges with ModeledHeader {
|
|
||||||
import `Accept-Ranges`.rangeUnitsRenderer
|
|
||||||
def renderValue[R <: Rendering](r: R): r.type = if (rangeUnits.isEmpty) r ~~ "none" else r ~~ rangeUnits
|
|
||||||
protected def companion = `Accept-Ranges`
|
|
||||||
|
|
||||||
/** Java API */
|
|
||||||
def getRangeUnits: Iterable[jm.headers.RangeUnit] = rangeUnits.asJava
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://www.w3.org/TR/cors/#access-control-allow-credentials-response-header
|
|
||||||
object `Access-Control-Allow-Credentials` extends ModeledCompanion[`Access-Control-Allow-Credentials`]
|
|
||||||
final case class `Access-Control-Allow-Credentials`(allow: Boolean) extends jm.headers.AccessControlAllowCredentials with ModeledHeader {
|
|
||||||
def renderValue[R <: Rendering](r: R): r.type = r ~~ allow.toString
|
|
||||||
protected def companion = `Access-Control-Allow-Credentials`
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://www.w3.org/TR/cors/#access-control-allow-headers-response-header
|
|
||||||
object `Access-Control-Allow-Headers` extends ModeledCompanion[`Access-Control-Allow-Headers`] {
|
|
||||||
def apply(headers: String*): `Access-Control-Allow-Headers` = apply(immutable.Seq(headers: _*))
|
|
||||||
implicit val headersRenderer = Renderer.defaultSeqRenderer[String] // cache
|
|
||||||
}
|
|
||||||
final case class `Access-Control-Allow-Headers`(headers: immutable.Seq[String]) extends jm.headers.AccessControlAllowHeaders with ModeledHeader {
|
|
||||||
import `Access-Control-Allow-Headers`.headersRenderer
|
|
||||||
def renderValue[R <: Rendering](r: R): r.type = r ~~ headers
|
|
||||||
protected def companion = `Access-Control-Allow-Headers`
|
|
||||||
|
|
||||||
/** Java API */
|
|
||||||
def getHeaders: Iterable[String] = headers.asJava
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://www.w3.org/TR/cors/#access-control-allow-methods-response-header
|
|
||||||
object `Access-Control-Allow-Methods` extends ModeledCompanion[`Access-Control-Allow-Methods`] {
|
|
||||||
def apply(methods: HttpMethod*): `Access-Control-Allow-Methods` = apply(immutable.Seq(methods: _*))
|
|
||||||
implicit val methodsRenderer = Renderer.defaultSeqRenderer[HttpMethod] // cache
|
|
||||||
}
|
|
||||||
final case class `Access-Control-Allow-Methods`(methods: immutable.Seq[HttpMethod]) extends jm.headers.AccessControlAllowMethods with ModeledHeader {
|
|
||||||
import `Access-Control-Allow-Methods`.methodsRenderer
|
|
||||||
def renderValue[R <: Rendering](r: R): r.type = r ~~ methods
|
|
||||||
protected def companion = `Access-Control-Allow-Methods`
|
|
||||||
|
|
||||||
/** Java API */
|
|
||||||
def getMethods: Iterable[jm.HttpMethod] = methods.asJava
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://www.w3.org/TR/cors/#access-control-allow-origin-response-header
|
|
||||||
object `Access-Control-Allow-Origin` extends ModeledCompanion[`Access-Control-Allow-Origin`] {
|
|
||||||
val `*` = forRange(HttpOriginRange.`*`)
|
|
||||||
val `null` = forRange(HttpOriginRange())
|
|
||||||
def apply(origin: HttpOrigin) = forRange(HttpOriginRange(origin))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an `Access-Control-Allow-Origin` header for the given origin range.
|
|
||||||
*
|
|
||||||
* CAUTION: Even though allowed by the spec (http://www.w3.org/TR/cors/#access-control-allow-origin-response-header)
|
|
||||||
* `Access-Control-Allow-Origin` headers with more than a single origin appear to be largely unsupported in the field.
|
|
||||||
* Make sure to thoroughly test such usages with all expected clients!
|
|
||||||
*/
|
|
||||||
def forRange(range: HttpOriginRange) = new `Access-Control-Allow-Origin`(range)
|
|
||||||
}
|
|
||||||
final case class `Access-Control-Allow-Origin` private (range: HttpOriginRange) extends jm.headers.AccessControlAllowOrigin with ModeledHeader {
|
|
||||||
def renderValue[R <: Rendering](r: R): r.type = r ~~ range
|
|
||||||
protected def companion = `Access-Control-Allow-Origin`
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://www.w3.org/TR/cors/#access-control-expose-headers-response-header
|
|
||||||
object `Access-Control-Expose-Headers` extends ModeledCompanion[`Access-Control-Expose-Headers`] {
|
|
||||||
def apply(headers: String*): `Access-Control-Expose-Headers` = apply(immutable.Seq(headers: _*))
|
|
||||||
implicit val headersRenderer = Renderer.defaultSeqRenderer[String] // cache
|
|
||||||
}
|
|
||||||
final case class `Access-Control-Expose-Headers`(headers: immutable.Seq[String]) extends jm.headers.AccessControlExposeHeaders with ModeledHeader {
|
|
||||||
import `Access-Control-Expose-Headers`.headersRenderer
|
|
||||||
def renderValue[R <: Rendering](r: R): r.type = r ~~ headers
|
|
||||||
protected def companion = `Access-Control-Expose-Headers`
|
|
||||||
|
|
||||||
/** Java API */
|
|
||||||
def getHeaders: Iterable[String] = headers.asJava
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://www.w3.org/TR/cors/#access-control-max-age-response-header
|
|
||||||
object `Access-Control-Max-Age` extends ModeledCompanion[`Access-Control-Max-Age`]
|
|
||||||
final case class `Access-Control-Max-Age`(deltaSeconds: Long) extends jm.headers.AccessControlMaxAge with ModeledHeader {
|
|
||||||
def renderValue[R <: Rendering](r: R): r.type = r ~~ deltaSeconds
|
|
||||||
protected def companion = `Access-Control-Max-Age`
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://www.w3.org/TR/cors/#access-control-request-headers-request-header
|
|
||||||
object `Access-Control-Request-Headers` extends ModeledCompanion[`Access-Control-Request-Headers`] {
|
|
||||||
def apply(headers: String*): `Access-Control-Request-Headers` = apply(immutable.Seq(headers: _*))
|
|
||||||
implicit val headersRenderer = Renderer.defaultSeqRenderer[String] // cache
|
|
||||||
}
|
|
||||||
final case class `Access-Control-Request-Headers`(headers: immutable.Seq[String]) extends jm.headers.AccessControlRequestHeaders with ModeledHeader {
|
|
||||||
import `Access-Control-Request-Headers`.headersRenderer
|
|
||||||
def renderValue[R <: Rendering](r: R): r.type = r ~~ headers
|
|
||||||
protected def companion = `Access-Control-Request-Headers`
|
|
||||||
|
|
||||||
/** Java API */
|
|
||||||
def getHeaders: Iterable[String] = headers.asJava
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://www.w3.org/TR/cors/#access-control-request-method-request-header
|
|
||||||
object `Access-Control-Request-Method` extends ModeledCompanion[`Access-Control-Request-Method`]
|
|
||||||
final case class `Access-Control-Request-Method`(method: HttpMethod) extends jm.headers.AccessControlRequestMethod with ModeledHeader {
|
|
||||||
def renderValue[R <: Rendering](r: R): r.type = r ~~ method
|
|
||||||
protected def companion = `Access-Control-Request-Method`
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://tools.ietf.org/html/rfc7234#section-5.1
|
|
||||||
object Age extends ModeledCompanion[Age]
|
|
||||||
final case class Age(deltaSeconds: Long) extends jm.headers.Age with ModeledHeader {
|
|
||||||
def renderValue[R <: Rendering](r: R): r.type = r ~~ deltaSeconds
|
|
||||||
protected def companion = Age
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://tools.ietf.org/html/rfc7231#section-7.4.1
|
|
||||||
object Allow extends ModeledCompanion[Allow] {
|
|
||||||
def apply(methods: HttpMethod*): Allow = apply(immutable.Seq(methods: _*))
|
|
||||||
implicit val methodsRenderer = Renderer.defaultSeqRenderer[HttpMethod] // cache
|
|
||||||
}
|
|
||||||
final case class Allow(methods: immutable.Seq[HttpMethod]) extends jm.headers.Allow with ModeledHeader {
|
|
||||||
import Allow.methodsRenderer
|
|
||||||
def renderValue[R <: Rendering](r: R): r.type = r ~~ methods
|
|
||||||
protected def companion = Allow
|
|
||||||
|
|
||||||
/** Java API */
|
|
||||||
def getMethods: Iterable[jm.HttpMethod] = methods.asJava
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://tools.ietf.org/html/rfc7235#section-4.2
|
|
||||||
object Authorization extends ModeledCompanion[Authorization]
|
|
||||||
final case class Authorization(credentials: HttpCredentials) extends jm.headers.Authorization with ModeledHeader {
|
|
||||||
def renderValue[R <: Rendering](r: R): r.type = r ~~ credentials
|
|
||||||
protected def companion = Authorization
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://tools.ietf.org/html/rfc7234#section-5.2
|
|
||||||
object `Cache-Control` extends ModeledCompanion[`Cache-Control`] {
|
|
||||||
def apply(first: CacheDirective, more: CacheDirective*): `Cache-Control` = apply(immutable.Seq(first +: more: _*))
|
|
||||||
implicit val directivesRenderer = Renderer.defaultSeqRenderer[CacheDirective] // cache
|
|
||||||
}
|
|
||||||
final case class `Cache-Control`(directives: immutable.Seq[CacheDirective]) extends jm.headers.CacheControl with ModeledHeader {
|
|
||||||
require(directives.nonEmpty, "directives must not be empty")
|
|
||||||
import `Cache-Control`.directivesRenderer
|
|
||||||
def renderValue[R <: Rendering](r: R): r.type = r ~~ directives
|
|
||||||
protected def companion = `Cache-Control`
|
|
||||||
|
|
||||||
/** Java API */
|
|
||||||
def getDirectives: Iterable[jm.headers.CacheDirective] = directives.asJava
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://tools.ietf.org/html/rfc6266
|
// http://tools.ietf.org/html/rfc6266
|
||||||
object `Content-Disposition` extends ModeledCompanion[`Content-Disposition`]
|
object `Content-Disposition` extends ModeledCompanion[`Content-Disposition`]
|
||||||
final case class `Content-Disposition`(dispositionType: ContentDispositionType, params: Map[String, String] = Map.empty) extends jm.headers.ContentDisposition with ModeledHeader {
|
final case class `Content-Disposition`(dispositionType: ContentDispositionType, params: Map[String, String] = Map.empty)
|
||||||
def renderValue[R <: Rendering](r: R): r.type = { r ~~ dispositionType; params foreach { case (k, v) ⇒ r ~~ "; " ~~ k ~~ '=' ~~# v }; r }
|
extends jm.headers.ContentDisposition with RequestResponseHeader {
|
||||||
|
def renderValue[R <: Rendering](r: R): r.type = {
|
||||||
|
r ~~ dispositionType
|
||||||
|
params foreach { case (k, v) ⇒ r ~~ "; " ~~ k ~~ '=' ~~# v }
|
||||||
|
r
|
||||||
|
}
|
||||||
protected def companion = `Content-Disposition`
|
protected def companion = `Content-Disposition`
|
||||||
|
|
||||||
/** Java API */
|
/** Java API */
|
||||||
|
|
@ -415,7 +384,8 @@ object `Content-Encoding` extends ModeledCompanion[`Content-Encoding`] {
|
||||||
def apply(first: HttpEncoding, more: HttpEncoding*): `Content-Encoding` = apply(immutable.Seq(first +: more: _*))
|
def apply(first: HttpEncoding, more: HttpEncoding*): `Content-Encoding` = apply(immutable.Seq(first +: more: _*))
|
||||||
implicit val encodingsRenderer = Renderer.defaultSeqRenderer[HttpEncoding] // cache
|
implicit val encodingsRenderer = Renderer.defaultSeqRenderer[HttpEncoding] // cache
|
||||||
}
|
}
|
||||||
final case class `Content-Encoding`(encodings: immutable.Seq[HttpEncoding]) extends jm.headers.ContentEncoding with ModeledHeader {
|
final case class `Content-Encoding`(encodings: immutable.Seq[HttpEncoding]) extends jm.headers.ContentEncoding
|
||||||
|
with RequestResponseHeader {
|
||||||
require(encodings.nonEmpty, "encodings must not be empty")
|
require(encodings.nonEmpty, "encodings must not be empty")
|
||||||
import `Content-Encoding`.encodingsRenderer
|
import `Content-Encoding`.encodingsRenderer
|
||||||
def renderValue[R <: Rendering](r: R): r.type = r ~~ encodings
|
def renderValue[R <: Rendering](r: R): r.type = r ~~ encodings
|
||||||
|
|
@ -429,7 +399,8 @@ final case class `Content-Encoding`(encodings: immutable.Seq[HttpEncoding]) exte
|
||||||
object `Content-Range` extends ModeledCompanion[`Content-Range`] {
|
object `Content-Range` extends ModeledCompanion[`Content-Range`] {
|
||||||
def apply(byteContentRange: ByteContentRange): `Content-Range` = apply(RangeUnits.Bytes, byteContentRange)
|
def apply(byteContentRange: ByteContentRange): `Content-Range` = apply(RangeUnits.Bytes, byteContentRange)
|
||||||
}
|
}
|
||||||
final case class `Content-Range`(rangeUnit: RangeUnit, contentRange: ContentRange) extends jm.headers.ContentRange with ModeledHeader {
|
final case class `Content-Range`(rangeUnit: RangeUnit, contentRange: ContentRange) extends jm.headers.ContentRange
|
||||||
|
with RequestResponseHeader {
|
||||||
def renderValue[R <: Rendering](r: R): r.type = r ~~ rangeUnit ~~ ' ' ~~ contentRange
|
def renderValue[R <: Rendering](r: R): r.type = r ~~ rangeUnit ~~ ' ' ~~ contentRange
|
||||||
protected def companion = `Content-Range`
|
protected def companion = `Content-Range`
|
||||||
}
|
}
|
||||||
|
|
@ -440,7 +411,8 @@ object `Content-Type` extends ModeledCompanion[`Content-Type`]
|
||||||
* Instances of this class will only be created transiently during header parsing and will never appear
|
* Instances of this class will only be created transiently during header parsing and will never appear
|
||||||
* in HttpMessage.header. To access the Content-Type, see subclasses of HttpEntity.
|
* in HttpMessage.header. To access the Content-Type, see subclasses of HttpEntity.
|
||||||
*/
|
*/
|
||||||
final case class `Content-Type` private[http] (contentType: ContentType) extends jm.headers.ContentType with ModeledHeader {
|
final case class `Content-Type` private[http] (contentType: ContentType) extends jm.headers.ContentType
|
||||||
|
with RequestResponseHeader {
|
||||||
def renderValue[R <: Rendering](r: R): r.type = r ~~ contentType
|
def renderValue[R <: Rendering](r: R): r.type = r ~~ contentType
|
||||||
protected def companion = `Content-Type`
|
protected def companion = `Content-Type`
|
||||||
}
|
}
|
||||||
|
|
@ -452,7 +424,7 @@ object Cookie extends ModeledCompanion[Cookie] {
|
||||||
def apply(values: (String, String)*): Cookie = apply(values.map(HttpCookiePair(_)).toList)
|
def apply(values: (String, String)*): Cookie = apply(values.map(HttpCookiePair(_)).toList)
|
||||||
implicit val cookiePairsRenderer = Renderer.seqRenderer[HttpCookiePair](separator = "; ") // cache
|
implicit val cookiePairsRenderer = Renderer.seqRenderer[HttpCookiePair](separator = "; ") // cache
|
||||||
}
|
}
|
||||||
final case class Cookie(cookies: immutable.Seq[HttpCookiePair]) extends jm.headers.Cookie with ModeledHeader {
|
final case class Cookie(cookies: immutable.Seq[HttpCookiePair]) extends jm.headers.Cookie with RequestHeader {
|
||||||
require(cookies.nonEmpty, "cookies must not be empty")
|
require(cookies.nonEmpty, "cookies must not be empty")
|
||||||
import Cookie.cookiePairsRenderer
|
import Cookie.cookiePairsRenderer
|
||||||
def renderValue[R <: Rendering](r: R): r.type = r ~~ cookies
|
def renderValue[R <: Rendering](r: R): r.type = r ~~ cookies
|
||||||
|
|
@ -464,42 +436,80 @@ final case class Cookie(cookies: immutable.Seq[HttpCookiePair]) extends jm.heade
|
||||||
|
|
||||||
// http://tools.ietf.org/html/rfc7231#section-7.1.1.2
|
// http://tools.ietf.org/html/rfc7231#section-7.1.1.2
|
||||||
object Date extends ModeledCompanion[Date]
|
object Date extends ModeledCompanion[Date]
|
||||||
final case class Date(date: DateTime) extends jm.headers.Date with ModeledHeader {
|
final case class Date(date: DateTime) extends jm.headers.Date with RequestResponseHeader {
|
||||||
def renderValue[R <: Rendering](r: R): r.type = date.renderRfc1123DateTimeString(r)
|
def renderValue[R <: Rendering](r: R): r.type = date.renderRfc1123DateTimeString(r)
|
||||||
protected def companion = Date
|
protected def companion = Date
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* INTERNAL API
|
||||||
|
*/
|
||||||
|
private[headers] object EmptyCompanion extends ModeledCompanion[EmptyHeader.type]
|
||||||
|
/**
|
||||||
|
* INTERNAL API
|
||||||
|
*/
|
||||||
|
private[http] object EmptyHeader extends SyntheticHeader {
|
||||||
|
def renderValue[R <: Rendering](r: R): r.type = r
|
||||||
|
protected def companion: ModeledCompanion[EmptyHeader.type] = EmptyCompanion
|
||||||
|
}
|
||||||
|
|
||||||
// http://tools.ietf.org/html/rfc7232#section-2.3
|
// http://tools.ietf.org/html/rfc7232#section-2.3
|
||||||
object ETag extends ModeledCompanion[ETag] {
|
object ETag extends ModeledCompanion[ETag] {
|
||||||
def apply(tag: String, weak: Boolean = false): ETag = ETag(EntityTag(tag, weak))
|
def apply(tag: String, weak: Boolean = false): ETag = ETag(EntityTag(tag, weak))
|
||||||
|
|
||||||
}
|
}
|
||||||
final case class ETag(etag: EntityTag) extends jm.headers.ETag with ModeledHeader {
|
final case class ETag(etag: EntityTag) extends jm.headers.ETag with ResponseHeader {
|
||||||
def renderValue[R <: Rendering](r: R): r.type = r ~~ etag
|
def renderValue[R <: Rendering](r: R): r.type = r ~~ etag
|
||||||
protected def companion = ETag
|
protected def companion = ETag
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// http://tools.ietf.org/html/rfc7231#section-5.1.1
|
||||||
|
object Expect extends ModeledCompanion[Expect] {
|
||||||
|
val `100-continue` = new Expect() {}
|
||||||
|
}
|
||||||
|
sealed abstract case class Expect private () extends RequestHeader {
|
||||||
|
final def renderValue[R <: Rendering](r: R): r.type = r ~~ "100-continue"
|
||||||
|
protected def companion = Expect
|
||||||
|
}
|
||||||
|
|
||||||
// http://tools.ietf.org/html/rfc7234#section-5.3
|
// http://tools.ietf.org/html/rfc7234#section-5.3
|
||||||
object Expires extends ModeledCompanion[Expires]
|
object Expires extends ModeledCompanion[Expires]
|
||||||
final case class Expires(date: DateTime) extends jm.headers.Expires with ModeledHeader {
|
final case class Expires(date: DateTime) extends jm.headers.Expires with ResponseHeader {
|
||||||
def renderValue[R <: Rendering](r: R): r.type = date.renderRfc1123DateTimeString(r)
|
def renderValue[R <: Rendering](r: R): r.type = date.renderRfc1123DateTimeString(r)
|
||||||
protected def companion = Expires
|
protected def companion = Expires
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// http://tools.ietf.org/html/rfc7230#section-5.4
|
||||||
|
object Host extends ModeledCompanion[Host] {
|
||||||
|
def apply(authority: Uri.Authority): Host = apply(authority.host, authority.port)
|
||||||
|
def apply(address: InetSocketAddress): Host = apply(address.getHostString, address.getPort)
|
||||||
|
def apply(host: String): Host = apply(host, 0)
|
||||||
|
def apply(host: String, port: Int): Host = apply(Uri.Host(host), port)
|
||||||
|
val empty = Host("")
|
||||||
|
}
|
||||||
|
final case class Host(host: Uri.Host, port: Int = 0) extends jm.headers.Host with RequestHeader {
|
||||||
|
import UriRendering.HostRenderer
|
||||||
|
require((port >> 16) == 0, "Illegal port: " + port)
|
||||||
|
def isEmpty = host.isEmpty
|
||||||
|
def renderValue[R <: Rendering](r: R): r.type = if (port > 0) r ~~ host ~~ ':' ~~ port else r ~~ host
|
||||||
|
protected def companion = Host
|
||||||
|
def equalsIgnoreCase(other: Host): Boolean = host.equalsIgnoreCase(other.host) && port == other.port
|
||||||
|
}
|
||||||
|
|
||||||
// http://tools.ietf.org/html/rfc7232#section-3.1
|
// http://tools.ietf.org/html/rfc7232#section-3.1
|
||||||
object `If-Match` extends ModeledCompanion[`If-Match`] {
|
object `If-Match` extends ModeledCompanion[`If-Match`] {
|
||||||
val `*` = `If-Match`(EntityTagRange.`*`)
|
val `*` = `If-Match`(EntityTagRange.`*`)
|
||||||
def apply(first: EntityTag, more: EntityTag*): `If-Match` =
|
def apply(first: EntityTag, more: EntityTag*): `If-Match` =
|
||||||
`If-Match`(EntityTagRange(first +: more: _*))
|
`If-Match`(EntityTagRange(first +: more: _*))
|
||||||
}
|
}
|
||||||
final case class `If-Match`(m: EntityTagRange) extends jm.headers.IfMatch with ModeledHeader {
|
final case class `If-Match`(m: EntityTagRange) extends jm.headers.IfMatch with RequestHeader {
|
||||||
def renderValue[R <: Rendering](r: R): r.type = r ~~ m
|
def renderValue[R <: Rendering](r: R): r.type = r ~~ m
|
||||||
protected def companion = `If-Match`
|
protected def companion = `If-Match`
|
||||||
}
|
}
|
||||||
|
|
||||||
// http://tools.ietf.org/html/rfc7232#section-3.3
|
// http://tools.ietf.org/html/rfc7232#section-3.3
|
||||||
object `If-Modified-Since` extends ModeledCompanion[`If-Modified-Since`]
|
object `If-Modified-Since` extends ModeledCompanion[`If-Modified-Since`]
|
||||||
final case class `If-Modified-Since`(date: DateTime) extends jm.headers.IfModifiedSince with ModeledHeader {
|
final case class `If-Modified-Since`(date: DateTime) extends jm.headers.IfModifiedSince with RequestHeader {
|
||||||
def renderValue[R <: Rendering](r: R): r.type = date.renderRfc1123DateTimeString(r)
|
def renderValue[R <: Rendering](r: R): r.type = date.renderRfc1123DateTimeString(r)
|
||||||
protected def companion = `If-Modified-Since`
|
protected def companion = `If-Modified-Since`
|
||||||
}
|
}
|
||||||
|
|
@ -510,21 +520,35 @@ object `If-None-Match` extends ModeledCompanion[`If-None-Match`] {
|
||||||
def apply(first: EntityTag, more: EntityTag*): `If-None-Match` =
|
def apply(first: EntityTag, more: EntityTag*): `If-None-Match` =
|
||||||
`If-None-Match`(EntityTagRange(first +: more: _*))
|
`If-None-Match`(EntityTagRange(first +: more: _*))
|
||||||
}
|
}
|
||||||
final case class `If-None-Match`(m: EntityTagRange) extends jm.headers.IfNoneMatch with ModeledHeader {
|
final case class `If-None-Match`(m: EntityTagRange) extends jm.headers.IfNoneMatch with RequestHeader {
|
||||||
def renderValue[R <: Rendering](r: R): r.type = r ~~ m
|
def renderValue[R <: Rendering](r: R): r.type = r ~~ m
|
||||||
protected def companion = `If-None-Match`
|
protected def companion = `If-None-Match`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// http://tools.ietf.org/html/rfc7233#section-3.2
|
||||||
|
object `If-Range` extends ModeledCompanion[`If-Range`] {
|
||||||
|
def apply(tag: EntityTag): `If-Range` = apply(Left(tag))
|
||||||
|
def apply(timestamp: DateTime): `If-Range` = apply(Right(timestamp))
|
||||||
|
}
|
||||||
|
final case class `If-Range`(entityTagOrDateTime: Either[EntityTag, DateTime]) extends RequestHeader {
|
||||||
|
def renderValue[R <: Rendering](r: R): r.type =
|
||||||
|
entityTagOrDateTime match {
|
||||||
|
case Left(tag) ⇒ r ~~ tag
|
||||||
|
case Right(dateTime) ⇒ dateTime.renderRfc1123DateTimeString(r)
|
||||||
|
}
|
||||||
|
protected def companion = `If-Range`
|
||||||
|
}
|
||||||
|
|
||||||
// http://tools.ietf.org/html/rfc7232#section-3.4
|
// http://tools.ietf.org/html/rfc7232#section-3.4
|
||||||
object `If-Unmodified-Since` extends ModeledCompanion[`If-Unmodified-Since`]
|
object `If-Unmodified-Since` extends ModeledCompanion[`If-Unmodified-Since`]
|
||||||
final case class `If-Unmodified-Since`(date: DateTime) extends jm.headers.IfUnmodifiedSince with ModeledHeader {
|
final case class `If-Unmodified-Since`(date: DateTime) extends jm.headers.IfUnmodifiedSince with RequestHeader {
|
||||||
def renderValue[R <: Rendering](r: R): r.type = date.renderRfc1123DateTimeString(r)
|
def renderValue[R <: Rendering](r: R): r.type = date.renderRfc1123DateTimeString(r)
|
||||||
protected def companion = `If-Unmodified-Since`
|
protected def companion = `If-Unmodified-Since`
|
||||||
}
|
}
|
||||||
|
|
||||||
// http://tools.ietf.org/html/rfc7232#section-2.2
|
// http://tools.ietf.org/html/rfc7232#section-2.2
|
||||||
object `Last-Modified` extends ModeledCompanion[`Last-Modified`]
|
object `Last-Modified` extends ModeledCompanion[`Last-Modified`]
|
||||||
final case class `Last-Modified`(date: DateTime) extends jm.headers.LastModified with ModeledHeader {
|
final case class `Last-Modified`(date: DateTime) extends jm.headers.LastModified with ResponseHeader {
|
||||||
def renderValue[R <: Rendering](r: R): r.type = date.renderRfc1123DateTimeString(r)
|
def renderValue[R <: Rendering](r: R): r.type = date.renderRfc1123DateTimeString(r)
|
||||||
protected def companion = `Last-Modified`
|
protected def companion = `Last-Modified`
|
||||||
}
|
}
|
||||||
|
|
@ -535,7 +559,7 @@ object Link extends ModeledCompanion[Link] {
|
||||||
def apply(values: LinkValue*): Link = apply(immutable.Seq(values: _*))
|
def apply(values: LinkValue*): Link = apply(immutable.Seq(values: _*))
|
||||||
implicit val valuesRenderer = Renderer.defaultSeqRenderer[LinkValue] // cache
|
implicit val valuesRenderer = Renderer.defaultSeqRenderer[LinkValue] // cache
|
||||||
}
|
}
|
||||||
final case class Link(values: immutable.Seq[LinkValue]) extends jm.headers.Link with ModeledHeader {
|
final case class Link(values: immutable.Seq[LinkValue]) extends jm.headers.Link with RequestResponseHeader {
|
||||||
import Link.valuesRenderer
|
import Link.valuesRenderer
|
||||||
def renderValue[R <: Rendering](r: R): r.type = r ~~ values
|
def renderValue[R <: Rendering](r: R): r.type = r ~~ values
|
||||||
protected def companion = Link
|
protected def companion = Link
|
||||||
|
|
@ -546,7 +570,7 @@ final case class Link(values: immutable.Seq[LinkValue]) extends jm.headers.Link
|
||||||
|
|
||||||
// http://tools.ietf.org/html/rfc7231#section-7.1.2
|
// http://tools.ietf.org/html/rfc7231#section-7.1.2
|
||||||
object Location extends ModeledCompanion[Location]
|
object Location extends ModeledCompanion[Location]
|
||||||
final case class Location(uri: Uri) extends jm.headers.Location with ModeledHeader {
|
final case class Location(uri: Uri) extends jm.headers.Location with ResponseHeader {
|
||||||
def renderValue[R <: Rendering](r: R): r.type = { import UriRendering.UriRenderer; r ~~ uri }
|
def renderValue[R <: Rendering](r: R): r.type = { import UriRendering.UriRenderer; r ~~ uri }
|
||||||
protected def companion = Location
|
protected def companion = Location
|
||||||
|
|
||||||
|
|
@ -558,7 +582,7 @@ final case class Location(uri: Uri) extends jm.headers.Location with ModeledHead
|
||||||
object Origin extends ModeledCompanion[Origin] {
|
object Origin extends ModeledCompanion[Origin] {
|
||||||
def apply(origins: HttpOrigin*): Origin = apply(immutable.Seq(origins: _*))
|
def apply(origins: HttpOrigin*): Origin = apply(immutable.Seq(origins: _*))
|
||||||
}
|
}
|
||||||
final case class Origin(origins: immutable.Seq[HttpOrigin]) extends jm.headers.Origin with ModeledHeader {
|
final case class Origin(origins: immutable.Seq[HttpOrigin]) extends jm.headers.Origin with RequestHeader {
|
||||||
def renderValue[R <: Rendering](r: R): r.type = if (origins.isEmpty) r ~~ "null" else r ~~ origins
|
def renderValue[R <: Rendering](r: R): r.type = if (origins.isEmpty) r ~~ "null" else r ~~ origins
|
||||||
protected def companion = Origin
|
protected def companion = Origin
|
||||||
|
|
||||||
|
|
@ -571,7 +595,8 @@ object `Proxy-Authenticate` extends ModeledCompanion[`Proxy-Authenticate`] {
|
||||||
def apply(first: HttpChallenge, more: HttpChallenge*): `Proxy-Authenticate` = apply(immutable.Seq(first +: more: _*))
|
def apply(first: HttpChallenge, more: HttpChallenge*): `Proxy-Authenticate` = apply(immutable.Seq(first +: more: _*))
|
||||||
implicit val challengesRenderer = Renderer.defaultSeqRenderer[HttpChallenge] // cache
|
implicit val challengesRenderer = Renderer.defaultSeqRenderer[HttpChallenge] // cache
|
||||||
}
|
}
|
||||||
final case class `Proxy-Authenticate`(challenges: immutable.Seq[HttpChallenge]) extends jm.headers.ProxyAuthenticate with ModeledHeader {
|
final case class `Proxy-Authenticate`(challenges: immutable.Seq[HttpChallenge]) extends jm.headers.ProxyAuthenticate
|
||||||
|
with ResponseHeader {
|
||||||
require(challenges.nonEmpty, "challenges must not be empty")
|
require(challenges.nonEmpty, "challenges must not be empty")
|
||||||
import `Proxy-Authenticate`.challengesRenderer
|
import `Proxy-Authenticate`.challengesRenderer
|
||||||
def renderValue[R <: Rendering](r: R): r.type = r ~~ challenges
|
def renderValue[R <: Rendering](r: R): r.type = r ~~ challenges
|
||||||
|
|
@ -583,7 +608,8 @@ final case class `Proxy-Authenticate`(challenges: immutable.Seq[HttpChallenge])
|
||||||
|
|
||||||
// http://tools.ietf.org/html/rfc7235#section-4.4
|
// http://tools.ietf.org/html/rfc7235#section-4.4
|
||||||
object `Proxy-Authorization` extends ModeledCompanion[`Proxy-Authorization`]
|
object `Proxy-Authorization` extends ModeledCompanion[`Proxy-Authorization`]
|
||||||
final case class `Proxy-Authorization`(credentials: HttpCredentials) extends jm.headers.ProxyAuthorization with ModeledHeader {
|
final case class `Proxy-Authorization`(credentials: HttpCredentials) extends jm.headers.ProxyAuthorization
|
||||||
|
with RequestHeader {
|
||||||
def renderValue[R <: Rendering](r: R): r.type = r ~~ credentials
|
def renderValue[R <: Rendering](r: R): r.type = r ~~ credentials
|
||||||
protected def companion = `Proxy-Authorization`
|
protected def companion = `Proxy-Authorization`
|
||||||
}
|
}
|
||||||
|
|
@ -594,7 +620,8 @@ object Range extends ModeledCompanion[Range] {
|
||||||
def apply(ranges: immutable.Seq[ByteRange]): Range = Range(RangeUnits.Bytes, ranges)
|
def apply(ranges: immutable.Seq[ByteRange]): Range = Range(RangeUnits.Bytes, ranges)
|
||||||
implicit val rangesRenderer = Renderer.defaultSeqRenderer[ByteRange] // cache
|
implicit val rangesRenderer = Renderer.defaultSeqRenderer[ByteRange] // cache
|
||||||
}
|
}
|
||||||
final case class Range(rangeUnit: RangeUnit, ranges: immutable.Seq[ByteRange]) extends jm.headers.Range with ModeledHeader {
|
final case class Range(rangeUnit: RangeUnit, ranges: immutable.Seq[ByteRange]) extends jm.headers.Range
|
||||||
|
with RequestHeader {
|
||||||
require(ranges.nonEmpty, "ranges must not be empty")
|
require(ranges.nonEmpty, "ranges must not be empty")
|
||||||
import Range.rangesRenderer
|
import Range.rangesRenderer
|
||||||
def renderValue[R <: Rendering](r: R): r.type = r ~~ rangeUnit ~~ '=' ~~ ranges
|
def renderValue[R <: Rendering](r: R): r.type = r ~~ rangeUnit ~~ '=' ~~ ranges
|
||||||
|
|
@ -604,22 +631,33 @@ final case class Range(rangeUnit: RangeUnit, ranges: immutable.Seq[ByteRange]) e
|
||||||
def getRanges: Iterable[jm.headers.ByteRange] = ranges.asJava
|
def getRanges: Iterable[jm.headers.ByteRange] = ranges.asJava
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final case class RawHeader(name: String, value: String) extends jm.headers.RawHeader {
|
||||||
|
def renderInRequests = true
|
||||||
|
def renderInResponses = true
|
||||||
|
val lowercaseName = name.toRootLowerCase
|
||||||
|
def render[R <: Rendering](r: R): r.type = r ~~ name ~~ ':' ~~ ' ' ~~ value
|
||||||
|
}
|
||||||
|
object RawHeader {
|
||||||
|
def unapply[H <: HttpHeader](customHeader: H): Option[(String, String)] =
|
||||||
|
Some(customHeader.name -> customHeader.value)
|
||||||
|
}
|
||||||
|
|
||||||
object `Raw-Request-URI` extends ModeledCompanion[`Raw-Request-URI`]
|
object `Raw-Request-URI` extends ModeledCompanion[`Raw-Request-URI`]
|
||||||
final case class `Raw-Request-URI`(uri: String) extends jm.headers.RawRequestURI with ModeledHeader {
|
final case class `Raw-Request-URI`(uri: String) extends jm.headers.RawRequestURI with SyntheticHeader {
|
||||||
def renderValue[R <: Rendering](r: R): r.type = r ~~ uri
|
def renderValue[R <: Rendering](r: R): r.type = r ~~ uri
|
||||||
protected def companion = `Raw-Request-URI`
|
protected def companion = `Raw-Request-URI`
|
||||||
}
|
}
|
||||||
|
|
||||||
object `Remote-Address` extends ModeledCompanion[`Remote-Address`]
|
object `Remote-Address` extends ModeledCompanion[`Remote-Address`]
|
||||||
final case class `Remote-Address`(address: RemoteAddress) extends jm.headers.RemoteAddress with ModeledHeader {
|
final case class `Remote-Address`(address: RemoteAddress) extends jm.headers.RemoteAddress with SyntheticHeader {
|
||||||
def renderValue[R <: Rendering](r: R): r.type = r ~~ address
|
def renderValue[R <: Rendering](r: R): r.type = r ~~ address
|
||||||
protected def companion = `Remote-Address`
|
protected def companion = `Remote-Address`
|
||||||
}
|
}
|
||||||
|
|
||||||
// http://tools.ietf.org/html/rfc7231#section-5.5.2
|
// http://tools.ietf.org/html/rfc7231#section-5.5.2
|
||||||
object Referer extends ModeledCompanion[Referer]
|
object Referer extends ModeledCompanion[Referer]
|
||||||
final case class Referer(uri: Uri) extends jm.headers.Referer with ModeledHeader {
|
final case class Referer(uri: Uri) extends jm.headers.Referer with RequestHeader {
|
||||||
require(uri.fragment == None, "Referer header URI must not contain a fragment")
|
require(uri.fragment.isEmpty, "Referer header URI must not contain a fragment")
|
||||||
require(uri.authority.userinfo.isEmpty, "Referer header URI must not contain a userinfo component")
|
require(uri.authority.userinfo.isEmpty, "Referer header URI must not contain a userinfo component")
|
||||||
|
|
||||||
def renderValue[R <: Rendering](r: R): r.type = { import UriRendering.UriRenderer; r ~~ uri }
|
def renderValue[R <: Rendering](r: R): r.type = { import UriRendering.UriRenderer; r ~~ uri }
|
||||||
|
|
@ -649,9 +687,8 @@ private[http] object `Sec-WebSocket-Accept` extends ModeledCompanion[`Sec-WebSoc
|
||||||
/**
|
/**
|
||||||
* INTERNAL API
|
* INTERNAL API
|
||||||
*/
|
*/
|
||||||
private[http] final case class `Sec-WebSocket-Accept`(key: String) extends ModeledHeader {
|
private[http] final case class `Sec-WebSocket-Accept`(key: String) extends ResponseHeader {
|
||||||
protected[http] def renderValue[R <: Rendering](r: R): r.type = r ~~ key
|
protected[http] def renderValue[R <: Rendering](r: R): r.type = r ~~ key
|
||||||
|
|
||||||
protected def companion = `Sec-WebSocket-Accept`
|
protected def companion = `Sec-WebSocket-Accept`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -665,11 +702,11 @@ private[http] object `Sec-WebSocket-Extensions` extends ModeledCompanion[`Sec-We
|
||||||
/**
|
/**
|
||||||
* INTERNAL API
|
* INTERNAL API
|
||||||
*/
|
*/
|
||||||
private[http] final case class `Sec-WebSocket-Extensions`(extensions: immutable.Seq[WebsocketExtension]) extends ModeledHeader {
|
private[http] final case class `Sec-WebSocket-Extensions`(extensions: immutable.Seq[WebsocketExtension])
|
||||||
|
extends ResponseHeader {
|
||||||
require(extensions.nonEmpty, "Sec-WebSocket-Extensions.extensions must not be empty")
|
require(extensions.nonEmpty, "Sec-WebSocket-Extensions.extensions must not be empty")
|
||||||
import `Sec-WebSocket-Extensions`.extensionsRenderer
|
import `Sec-WebSocket-Extensions`.extensionsRenderer
|
||||||
protected[http] def renderValue[R <: Rendering](r: R): r.type = r ~~ extensions
|
protected[http] def renderValue[R <: Rendering](r: R): r.type = r ~~ extensions
|
||||||
|
|
||||||
protected def companion = `Sec-WebSocket-Extensions`
|
protected def companion = `Sec-WebSocket-Extensions`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -686,9 +723,8 @@ private[http] object `Sec-WebSocket-Key` extends ModeledCompanion[`Sec-WebSocket
|
||||||
/**
|
/**
|
||||||
* INTERNAL API
|
* INTERNAL API
|
||||||
*/
|
*/
|
||||||
private[http] final case class `Sec-WebSocket-Key`(key: String) extends ModeledHeader {
|
private[http] final case class `Sec-WebSocket-Key`(key: String) extends RequestHeader {
|
||||||
protected[http] def renderValue[R <: Rendering](r: R): r.type = r ~~ key
|
protected[http] def renderValue[R <: Rendering](r: R): r.type = r ~~ key
|
||||||
|
|
||||||
protected def companion = `Sec-WebSocket-Key`
|
protected def companion = `Sec-WebSocket-Key`
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -708,11 +744,11 @@ private[http] object `Sec-WebSocket-Protocol` extends ModeledCompanion[`Sec-WebS
|
||||||
/**
|
/**
|
||||||
* INTERNAL API
|
* INTERNAL API
|
||||||
*/
|
*/
|
||||||
private[http] final case class `Sec-WebSocket-Protocol`(protocols: immutable.Seq[String]) extends ModeledHeader {
|
private[http] final case class `Sec-WebSocket-Protocol`(protocols: immutable.Seq[String])
|
||||||
|
extends RequestResponseHeader {
|
||||||
require(protocols.nonEmpty, "Sec-WebSocket-Protocol.protocols must not be empty")
|
require(protocols.nonEmpty, "Sec-WebSocket-Protocol.protocols must not be empty")
|
||||||
import `Sec-WebSocket-Protocol`.protocolsRenderer
|
import `Sec-WebSocket-Protocol`.protocolsRenderer
|
||||||
protected[http] def renderValue[R <: Rendering](r: R): r.type = r ~~ protocols
|
protected[http] def renderValue[R <: Rendering](r: R): r.type = r ~~ protocols
|
||||||
|
|
||||||
protected def companion = `Sec-WebSocket-Protocol`
|
protected def companion = `Sec-WebSocket-Protocol`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -726,14 +762,13 @@ private[http] object `Sec-WebSocket-Version` extends ModeledCompanion[`Sec-WebSo
|
||||||
/**
|
/**
|
||||||
* INTERNAL API
|
* INTERNAL API
|
||||||
*/
|
*/
|
||||||
private[http] final case class `Sec-WebSocket-Version`(versions: immutable.Seq[Int]) extends ModeledHeader {
|
private[http] final case class `Sec-WebSocket-Version`(versions: immutable.Seq[Int])
|
||||||
|
extends RequestResponseHeader {
|
||||||
require(versions.nonEmpty, "Sec-WebSocket-Version.versions must not be empty")
|
require(versions.nonEmpty, "Sec-WebSocket-Version.versions must not be empty")
|
||||||
require(versions.forall(v ⇒ v >= 0 && v <= 255), s"Sec-WebSocket-Version.versions must be in the range 0 <= version <= 255 but were $versions")
|
require(versions.forall(v ⇒ v >= 0 && v <= 255), s"Sec-WebSocket-Version.versions must be in the range 0 <= version <= 255 but were $versions")
|
||||||
import `Sec-WebSocket-Version`.versionsRenderer
|
import `Sec-WebSocket-Version`.versionsRenderer
|
||||||
protected[http] def renderValue[R <: Rendering](r: R): r.type = r ~~ versions
|
protected[http] def renderValue[R <: Rendering](r: R): r.type = r ~~ versions
|
||||||
|
def hasVersion(versionNumber: Int): Boolean = versions contains versionNumber
|
||||||
def hasVersion(versionNumber: Int): Boolean = versions.exists(_ == versionNumber)
|
|
||||||
|
|
||||||
protected def companion = `Sec-WebSocket-Version`
|
protected def companion = `Sec-WebSocket-Version`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -743,7 +778,7 @@ object Server extends ModeledCompanion[Server] {
|
||||||
def apply(first: ProductVersion, more: ProductVersion*): Server = apply(immutable.Seq(first +: more: _*))
|
def apply(first: ProductVersion, more: ProductVersion*): Server = apply(immutable.Seq(first +: more: _*))
|
||||||
implicit val productsRenderer = Renderer.seqRenderer[ProductVersion](separator = " ") // cache
|
implicit val productsRenderer = Renderer.seqRenderer[ProductVersion](separator = " ") // cache
|
||||||
}
|
}
|
||||||
final case class Server(products: immutable.Seq[ProductVersion]) extends jm.headers.Server with ModeledHeader {
|
final case class Server(products: immutable.Seq[ProductVersion]) extends jm.headers.Server with ResponseHeader {
|
||||||
require(products.nonEmpty, "products must not be empty")
|
require(products.nonEmpty, "products must not be empty")
|
||||||
import Server.productsRenderer
|
import Server.productsRenderer
|
||||||
def renderValue[R <: Rendering](r: R): r.type = r ~~ products
|
def renderValue[R <: Rendering](r: R): r.type = r ~~ products
|
||||||
|
|
@ -755,11 +790,18 @@ final case class Server(products: immutable.Seq[ProductVersion]) extends jm.head
|
||||||
|
|
||||||
// https://tools.ietf.org/html/rfc6265
|
// https://tools.ietf.org/html/rfc6265
|
||||||
object `Set-Cookie` extends ModeledCompanion[`Set-Cookie`]
|
object `Set-Cookie` extends ModeledCompanion[`Set-Cookie`]
|
||||||
final case class `Set-Cookie`(cookie: HttpCookie) extends jm.headers.SetCookie with ModeledHeader {
|
final case class `Set-Cookie`(cookie: HttpCookie) extends jm.headers.SetCookie with ResponseHeader {
|
||||||
def renderValue[R <: Rendering](r: R): r.type = r ~~ cookie
|
def renderValue[R <: Rendering](r: R): r.type = r ~~ cookie
|
||||||
protected def companion = `Set-Cookie`
|
protected def companion = `Set-Cookie`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object `Timeout-Access` extends ModeledCompanion[`Timeout-Access`]
|
||||||
|
final case class `Timeout-Access`(timeoutAccess: akka.http.scaladsl.TimeoutAccess)
|
||||||
|
extends jm.headers.TimeoutAccess with SyntheticHeader {
|
||||||
|
def renderValue[R <: Rendering](r: R): r.type = r ~~ timeoutAccess.toString
|
||||||
|
protected def companion = `Timeout-Access`
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model for the synthetic `Tls-Session-Info` header which carries the SSLSession of the connection
|
* Model for the synthetic `Tls-Session-Info` header which carries the SSLSession of the connection
|
||||||
* the message carrying this header was received with.
|
* the message carrying this header was received with.
|
||||||
|
|
@ -770,17 +812,14 @@ final case class `Set-Cookie`(cookie: HttpCookie) extends jm.headers.SetCookie w
|
||||||
* akka.http.[client|server].parsing.tls-session-info-header = on
|
* akka.http.[client|server].parsing.tls-session-info-header = on
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
final case class `Tls-Session-Info`(session: SSLSession) extends jm.headers.TlsSessionInfo with ScalaSessionAPI {
|
object `Tls-Session-Info` extends ModeledCompanion[`Tls-Session-Info`]
|
||||||
override def suppressRendering: Boolean = true
|
final case class `Tls-Session-Info`(session: SSLSession) extends jm.headers.TlsSessionInfo with SyntheticHeader
|
||||||
override def toString = s"SSL-Session-Info($session)"
|
with ScalaSessionAPI {
|
||||||
def name(): String = "SSL-Session-Info"
|
def renderValue[R <: Rendering](r: R): r.type = r ~~ session.toString
|
||||||
def value(): String = ""
|
protected def companion = `Tls-Session-Info`
|
||||||
|
|
||||||
/** Java API */
|
/** Java API */
|
||||||
def getSession(): SSLSession = session
|
def getSession: SSLSession = session
|
||||||
|
|
||||||
def lowercaseName: String = name.toRootLowerCase
|
|
||||||
def render[R <: Rendering](r: R): r.type = r ~~ name ~~ ':' ~~ ' ' ~~ value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// http://tools.ietf.org/html/rfc7230#section-3.3.1
|
// http://tools.ietf.org/html/rfc7230#section-3.3.1
|
||||||
|
|
@ -788,7 +827,8 @@ object `Transfer-Encoding` extends ModeledCompanion[`Transfer-Encoding`] {
|
||||||
def apply(first: TransferEncoding, more: TransferEncoding*): `Transfer-Encoding` = apply(immutable.Seq(first +: more: _*))
|
def apply(first: TransferEncoding, more: TransferEncoding*): `Transfer-Encoding` = apply(immutable.Seq(first +: more: _*))
|
||||||
implicit val encodingsRenderer = Renderer.defaultSeqRenderer[TransferEncoding] // cache
|
implicit val encodingsRenderer = Renderer.defaultSeqRenderer[TransferEncoding] // cache
|
||||||
}
|
}
|
||||||
final case class `Transfer-Encoding`(encodings: immutable.Seq[TransferEncoding]) extends jm.headers.TransferEncoding with ModeledHeader {
|
final case class `Transfer-Encoding`(encodings: immutable.Seq[TransferEncoding]) extends jm.headers.TransferEncoding
|
||||||
|
with RequestResponseHeader {
|
||||||
require(encodings.nonEmpty, "encodings must not be empty")
|
require(encodings.nonEmpty, "encodings must not be empty")
|
||||||
import `Transfer-Encoding`.encodingsRenderer
|
import `Transfer-Encoding`.encodingsRenderer
|
||||||
def isChunked: Boolean = encodings.last == TransferEncodings.chunked
|
def isChunked: Boolean = encodings.last == TransferEncodings.chunked
|
||||||
|
|
@ -812,7 +852,7 @@ final case class `Transfer-Encoding`(encodings: immutable.Seq[TransferEncoding])
|
||||||
object Upgrade extends ModeledCompanion[Upgrade] {
|
object Upgrade extends ModeledCompanion[Upgrade] {
|
||||||
implicit val protocolsRenderer = Renderer.defaultSeqRenderer[UpgradeProtocol]
|
implicit val protocolsRenderer = Renderer.defaultSeqRenderer[UpgradeProtocol]
|
||||||
}
|
}
|
||||||
final case class Upgrade(protocols: immutable.Seq[UpgradeProtocol]) extends ModeledHeader {
|
final case class Upgrade(protocols: immutable.Seq[UpgradeProtocol]) extends RequestResponseHeader {
|
||||||
import Upgrade.protocolsRenderer
|
import Upgrade.protocolsRenderer
|
||||||
protected[http] def renderValue[R <: Rendering](r: R): r.type = r ~~ protocols
|
protected[http] def renderValue[R <: Rendering](r: R): r.type = r ~~ protocols
|
||||||
|
|
||||||
|
|
@ -827,7 +867,7 @@ object `User-Agent` extends ModeledCompanion[`User-Agent`] {
|
||||||
def apply(first: ProductVersion, more: ProductVersion*): `User-Agent` = apply(immutable.Seq(first +: more: _*))
|
def apply(first: ProductVersion, more: ProductVersion*): `User-Agent` = apply(immutable.Seq(first +: more: _*))
|
||||||
implicit val productsRenderer = Renderer.seqRenderer[ProductVersion](separator = " ") // cache
|
implicit val productsRenderer = Renderer.seqRenderer[ProductVersion](separator = " ") // cache
|
||||||
}
|
}
|
||||||
final case class `User-Agent`(products: immutable.Seq[ProductVersion]) extends jm.headers.UserAgent with ModeledHeader {
|
final case class `User-Agent`(products: immutable.Seq[ProductVersion]) extends jm.headers.UserAgent with RequestHeader {
|
||||||
require(products.nonEmpty, "products must not be empty")
|
require(products.nonEmpty, "products must not be empty")
|
||||||
import `User-Agent`.productsRenderer
|
import `User-Agent`.productsRenderer
|
||||||
def renderValue[R <: Rendering](r: R): r.type = r ~~ products
|
def renderValue[R <: Rendering](r: R): r.type = r ~~ products
|
||||||
|
|
@ -842,7 +882,8 @@ object `WWW-Authenticate` extends ModeledCompanion[`WWW-Authenticate`] {
|
||||||
def apply(first: HttpChallenge, more: HttpChallenge*): `WWW-Authenticate` = apply(immutable.Seq(first +: more: _*))
|
def apply(first: HttpChallenge, more: HttpChallenge*): `WWW-Authenticate` = apply(immutable.Seq(first +: more: _*))
|
||||||
implicit val challengesRenderer = Renderer.defaultSeqRenderer[HttpChallenge] // cache
|
implicit val challengesRenderer = Renderer.defaultSeqRenderer[HttpChallenge] // cache
|
||||||
}
|
}
|
||||||
final case class `WWW-Authenticate`(challenges: immutable.Seq[HttpChallenge]) extends jm.headers.WWWAuthenticate with ModeledHeader {
|
final case class `WWW-Authenticate`(challenges: immutable.Seq[HttpChallenge]) extends jm.headers.WWWAuthenticate
|
||||||
|
with ResponseHeader {
|
||||||
require(challenges.nonEmpty, "challenges must not be empty")
|
require(challenges.nonEmpty, "challenges must not be empty")
|
||||||
import `WWW-Authenticate`.challengesRenderer
|
import `WWW-Authenticate`.challengesRenderer
|
||||||
def renderValue[R <: Rendering](r: R): r.type = r ~~ challenges
|
def renderValue[R <: Rendering](r: R): r.type = r ~~ challenges
|
||||||
|
|
@ -858,7 +899,8 @@ object `X-Forwarded-For` extends ModeledCompanion[`X-Forwarded-For`] {
|
||||||
def apply(first: RemoteAddress, more: RemoteAddress*): `X-Forwarded-For` = apply(immutable.Seq(first +: more: _*))
|
def apply(first: RemoteAddress, more: RemoteAddress*): `X-Forwarded-For` = apply(immutable.Seq(first +: more: _*))
|
||||||
implicit val addressesRenderer = Renderer.defaultSeqRenderer[RemoteAddress] // cache
|
implicit val addressesRenderer = Renderer.defaultSeqRenderer[RemoteAddress] // cache
|
||||||
}
|
}
|
||||||
final case class `X-Forwarded-For`(addresses: immutable.Seq[RemoteAddress]) extends jm.headers.XForwardedFor with ModeledHeader {
|
final case class `X-Forwarded-For`(addresses: immutable.Seq[RemoteAddress]) extends jm.headers.XForwardedFor
|
||||||
|
with RequestHeader {
|
||||||
require(addresses.nonEmpty, "addresses must not be empty")
|
require(addresses.nonEmpty, "addresses must not be empty")
|
||||||
import `X-Forwarded-For`.addressesRenderer
|
import `X-Forwarded-For`.addressesRenderer
|
||||||
def renderValue[R <: Rendering](r: R): r.type = r ~~ addresses
|
def renderValue[R <: Rendering](r: R): r.type = r ~~ addresses
|
||||||
|
|
|
||||||
|
|
@ -356,6 +356,8 @@ class ConnectionPoolSpec extends AkkaSpec("""
|
||||||
}
|
}
|
||||||
|
|
||||||
case class ConnNrHeader(nr: Int) extends CustomHeader {
|
case class ConnNrHeader(nr: Int) extends CustomHeader {
|
||||||
|
def renderInRequests = false
|
||||||
|
def renderInResponses = true
|
||||||
def name = "Conn-Nr"
|
def name = "Conn-Nr"
|
||||||
def value = nr.toString
|
def value = nr.toString
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ class HttpHeaderParserSpec extends WordSpec with Matchers with BeforeAndAfterAll
|
||||||
}
|
}
|
||||||
|
|
||||||
"retrieve the EmptyHeader" in new TestSetup() {
|
"retrieve the EmptyHeader" in new TestSetup() {
|
||||||
parseAndCache("\r\n")() shouldEqual HttpHeaderParser.EmptyHeader
|
parseAndCache("\r\n")() shouldEqual EmptyHeader
|
||||||
}
|
}
|
||||||
|
|
||||||
"retrieve a cached header with an exact header name match" in new TestSetup() {
|
"retrieve a cached header with an exact header name match" in new TestSetup() {
|
||||||
|
|
|
||||||
|
|
@ -66,11 +66,11 @@ class RequestRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll
|
||||||
"POST request, a few headers (incl. a custom Host header) and no body" in new TestSetup() {
|
"POST request, a few headers (incl. a custom Host header) and no body" in new TestSetup() {
|
||||||
HttpRequest(POST, "/abc/xyz", List(
|
HttpRequest(POST, "/abc/xyz", List(
|
||||||
RawHeader("X-Fancy", "naa"),
|
RawHeader("X-Fancy", "naa"),
|
||||||
Age(0),
|
Link(Uri("http://akka.io"), LinkParams.first),
|
||||||
Host("spray.io", 9999))) should renderTo {
|
Host("spray.io", 9999))) should renderTo {
|
||||||
"""POST /abc/xyz HTTP/1.1
|
"""POST /abc/xyz HTTP/1.1
|
||||||
|X-Fancy: naa
|
|X-Fancy: naa
|
||||||
|Age: 0
|
|Link: <http://akka.io>; rel=first
|
||||||
|Host: spray.io:9999
|
|Host: spray.io:9999
|
||||||
|User-Agent: akka-http/1.0.0
|
|User-Agent: akka-http/1.0.0
|
||||||
|Content-Length: 0
|
|Content-Length: 0
|
||||||
|
|
@ -262,8 +262,10 @@ class RequestRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"render a CustomHeader header" - {
|
"render a CustomHeader header" - {
|
||||||
"if suppressRendering = false" in new TestSetup(None) {
|
"if renderInRequests = true" in new TestSetup(None) {
|
||||||
case class MyHeader(number: Int) extends CustomHeader {
|
case class MyHeader(number: Int) extends CustomHeader {
|
||||||
|
def renderInRequests = true
|
||||||
|
def renderInResponses = false
|
||||||
def name: String = "X-My-Header"
|
def name: String = "X-My-Header"
|
||||||
def value: String = s"No$number"
|
def value: String = s"No$number"
|
||||||
}
|
}
|
||||||
|
|
@ -275,10 +277,10 @@ class RequestRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll
|
||||||
|"""
|
|"""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"not if suppressRendering = true" in new TestSetup(None) {
|
"not if renderInRequests = false" in new TestSetup(None) {
|
||||||
case class MyInternalHeader(number: Int) extends CustomHeader {
|
case class MyInternalHeader(number: Int) extends CustomHeader {
|
||||||
override def suppressRendering: Boolean = true
|
def renderInRequests = false
|
||||||
|
def renderInResponses = false
|
||||||
def name: String = "X-My-Internal-Header"
|
def name: String = "X-My-Internal-Header"
|
||||||
def value: String = s"No$number"
|
def value: String = s"No$number"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -414,8 +414,10 @@ class ResponseRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll
|
||||||
}
|
}
|
||||||
|
|
||||||
"render a CustomHeader header" - {
|
"render a CustomHeader header" - {
|
||||||
"if suppressRendering = false" in new TestSetup(None) {
|
"if renderInResponses = true" in new TestSetup(None) {
|
||||||
case class MyHeader(number: Int) extends CustomHeader {
|
case class MyHeader(number: Int) extends CustomHeader {
|
||||||
|
def renderInRequests = false
|
||||||
|
def renderInResponses = true
|
||||||
def name: String = "X-My-Header"
|
def name: String = "X-My-Header"
|
||||||
def value: String = s"No$number"
|
def value: String = s"No$number"
|
||||||
}
|
}
|
||||||
|
|
@ -428,10 +430,10 @@ class ResponseRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll
|
||||||
|"""
|
|"""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"not if suppressRendering = true" in new TestSetup(None) {
|
"not if renderInResponses = false" in new TestSetup(None) {
|
||||||
case class MyInternalHeader(number: Int) extends CustomHeader {
|
case class MyInternalHeader(number: Int) extends CustomHeader {
|
||||||
override def suppressRendering: Boolean = true
|
def renderInRequests = false
|
||||||
|
def renderInResponses = false
|
||||||
def name: String = "X-My-Internal-Header"
|
def name: String = "X-My-Internal-Header"
|
||||||
def value: String = s"No$number"
|
def value: String = s"No$number"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,10 @@ import HttpEntity._
|
||||||
import MediaTypes._
|
import MediaTypes._
|
||||||
import HttpMethods._
|
import HttpMethods._
|
||||||
|
|
||||||
class HttpServerSpec extends AkkaSpec("akka.loggers = []\n akka.loglevel = OFF") with Inside { spec ⇒
|
class HttpServerSpec extends AkkaSpec(
|
||||||
|
"""akka.loggers = []
|
||||||
|
akka.loglevel = OFF
|
||||||
|
akka.http.server.request-timeout = infinite""") with Inside { spec ⇒
|
||||||
implicit val materializer = ActorMaterializer()
|
implicit val materializer = ActorMaterializer()
|
||||||
|
|
||||||
"The server implementation" should {
|
"The server implementation" should {
|
||||||
|
|
@ -698,6 +701,82 @@ class HttpServerSpec extends AkkaSpec("akka.loggers = []\n akka.loglevel = OFF")
|
||||||
request.headers should contain(`Remote-Address`(RemoteAddress(theAddress, Some(8080))))
|
request.headers should contain(`Remote-Address`(RemoteAddress(theAddress, Some(8080))))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"support request timeouts" which {
|
||||||
|
|
||||||
|
"are defined via the config" in new RequestTimeoutTestSetup(10.millis) {
|
||||||
|
send("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
|
||||||
|
expectRequest().header[`Timeout-Access`] shouldBe defined
|
||||||
|
expectResponseWithWipedDate(
|
||||||
|
"""HTTP/1.1 503 Service Unavailable
|
||||||
|
|Server: akka-http/test
|
||||||
|
|Date: XXXX
|
||||||
|
|Content-Type: text/plain; charset=UTF-8
|
||||||
|
|Content-Length: 105
|
||||||
|
|
|
||||||
|
|The server was not able to produce a timely response to your request.
|
||||||
|
|Please try again in a short while!""")
|
||||||
|
}
|
||||||
|
|
||||||
|
"are programmatically increased (not expiring)" in new RequestTimeoutTestSetup(10.millis) {
|
||||||
|
send("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
|
||||||
|
expectRequest().header[`Timeout-Access`].foreach(_.timeoutAccess.updateTimeout(50.millis))
|
||||||
|
netOut.expectNoBytes(30.millis)
|
||||||
|
responses.sendNext(HttpResponse())
|
||||||
|
expectResponseWithWipedDate(
|
||||||
|
"""HTTP/1.1 200 OK
|
||||||
|
|Server: akka-http/test
|
||||||
|
|Date: XXXX
|
||||||
|
|Content-Length: 0
|
||||||
|
|
|
||||||
|
|""")
|
||||||
|
}
|
||||||
|
|
||||||
|
"are programmatically increased (expiring)" in new RequestTimeoutTestSetup(10.millis) {
|
||||||
|
send("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
|
||||||
|
expectRequest().header[`Timeout-Access`].foreach(_.timeoutAccess.updateTimeout(50.millis))
|
||||||
|
netOut.expectNoBytes(30.millis)
|
||||||
|
expectResponseWithWipedDate(
|
||||||
|
"""HTTP/1.1 503 Service Unavailable
|
||||||
|
|Server: akka-http/test
|
||||||
|
|Date: XXXX
|
||||||
|
|Content-Type: text/plain; charset=UTF-8
|
||||||
|
|Content-Length: 105
|
||||||
|
|
|
||||||
|
|The server was not able to produce a timely response to your request.
|
||||||
|
|Please try again in a short while!""")
|
||||||
|
}
|
||||||
|
|
||||||
|
"are programmatically decreased" in new RequestTimeoutTestSetup(50.millis) {
|
||||||
|
send("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
|
||||||
|
expectRequest().header[`Timeout-Access`].foreach(_.timeoutAccess.updateTimeout(10.millis))
|
||||||
|
val mark = System.nanoTime()
|
||||||
|
expectResponseWithWipedDate(
|
||||||
|
"""HTTP/1.1 503 Service Unavailable
|
||||||
|
|Server: akka-http/test
|
||||||
|
|Date: XXXX
|
||||||
|
|Content-Type: text/plain; charset=UTF-8
|
||||||
|
|Content-Length: 105
|
||||||
|
|
|
||||||
|
|The server was not able to produce a timely response to your request.
|
||||||
|
|Please try again in a short while!""")
|
||||||
|
(System.nanoTime() - mark) should be < (40 * 1000000L)
|
||||||
|
}
|
||||||
|
|
||||||
|
"have a programmatically set timeout handler" in new RequestTimeoutTestSetup(10.millis) {
|
||||||
|
send("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
|
||||||
|
val timeoutResponse = HttpResponse(StatusCodes.InternalServerError, entity = "OOPS!")
|
||||||
|
expectRequest().header[`Timeout-Access`].foreach(_.timeoutAccess.updateHandler(_ ⇒ timeoutResponse))
|
||||||
|
expectResponseWithWipedDate(
|
||||||
|
"""HTTP/1.1 500 Internal Server Error
|
||||||
|
|Server: akka-http/test
|
||||||
|
|Date: XXXX
|
||||||
|
|Content-Type: text/plain; charset=UTF-8
|
||||||
|
|Content-Length: 5
|
||||||
|
|
|
||||||
|
|OOPS!""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
"add `Connection: close` to early responses" in new TestSetup {
|
"add `Connection: close` to early responses" in new TestSetup {
|
||||||
send("""POST / HTTP/1.1
|
send("""POST / HTTP/1.1
|
||||||
|Host: example.com
|
|Host: example.com
|
||||||
|
|
@ -723,8 +802,7 @@ class HttpServerSpec extends AkkaSpec("akka.loggers = []\n akka.loglevel = OFF")
|
||||||
netOut.expectComplete()
|
netOut.expectComplete()
|
||||||
}
|
}
|
||||||
|
|
||||||
def isDefinedVia = afterWord("is defined via")
|
"support request length verification" which afterWord("is defined via") {
|
||||||
"support request length verification" which isDefinedVia {
|
|
||||||
|
|
||||||
class LengthVerificationTest(maxContentLength: Int) extends TestSetup(maxContentLength) {
|
class LengthVerificationTest(maxContentLength: Int) extends TestSetup(maxContentLength) {
|
||||||
val entityBase = "0123456789ABCD"
|
val entityBase = "0123456789ABCD"
|
||||||
|
|
@ -912,4 +990,10 @@ class HttpServerSpec extends AkkaSpec("akka.loggers = []\n akka.loglevel = OFF")
|
||||||
else s.copy(parserSettings = s.parserSettings.copy(maxContentLength = maxContentLength))
|
else s.copy(parserSettings = s.parserSettings.copy(maxContentLength = maxContentLength))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
class RequestTimeoutTestSetup(requestTimeout: Duration) extends TestSetup {
|
||||||
|
override def settings = {
|
||||||
|
val s = super.settings
|
||||||
|
s.copy(timeouts = s.timeouts.copy(requestTimeout = requestTimeout))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ class ClientServerSpec extends WordSpec with Matchers with BeforeAndAfterAll wit
|
||||||
akka.stdout-loglevel = ERROR
|
akka.stdout-loglevel = ERROR
|
||||||
windows-connection-abort-workaround-enabled = auto
|
windows-connection-abort-workaround-enabled = auto
|
||||||
akka.log-dead-letters = OFF
|
akka.log-dead-letters = OFF
|
||||||
""")
|
akka.http.server.request-timeout = infinite""")
|
||||||
implicit val system = ActorSystem(getClass.getSimpleName, testConf)
|
implicit val system = ActorSystem(getClass.getSimpleName, testConf)
|
||||||
import system.dispatcher
|
import system.dispatcher
|
||||||
implicit val materializer = ActorMaterializer()
|
implicit val materializer = ActorMaterializer()
|
||||||
|
|
|
||||||
|
|
@ -14,22 +14,30 @@ object ModeledCustomHeaderSpec {
|
||||||
|
|
||||||
//#modeled-api-key-custom-header
|
//#modeled-api-key-custom-header
|
||||||
object ApiTokenHeader extends ModeledCustomHeaderCompanion[ApiTokenHeader] {
|
object ApiTokenHeader extends ModeledCustomHeaderCompanion[ApiTokenHeader] {
|
||||||
|
def renderInRequests = false
|
||||||
|
def renderInResponses = false
|
||||||
override val name = "apiKey"
|
override val name = "apiKey"
|
||||||
override def parse(value: String) = Try(new ApiTokenHeader(value))
|
override def parse(value: String) = Try(new ApiTokenHeader(value))
|
||||||
}
|
}
|
||||||
final class ApiTokenHeader(token: String) extends ModeledCustomHeader[ApiTokenHeader] {
|
final class ApiTokenHeader(token: String) extends ModeledCustomHeader[ApiTokenHeader] {
|
||||||
|
def renderInRequests = false
|
||||||
|
def renderInResponses = false
|
||||||
override val companion = ApiTokenHeader
|
override val companion = ApiTokenHeader
|
||||||
override def value: String = token
|
override def value: String = token
|
||||||
}
|
}
|
||||||
//#modeled-api-key-custom-header
|
//#modeled-api-key-custom-header
|
||||||
|
|
||||||
object DifferentHeader extends ModeledCustomHeaderCompanion[DifferentHeader] {
|
object DifferentHeader extends ModeledCustomHeaderCompanion[DifferentHeader] {
|
||||||
|
def renderInRequests = false
|
||||||
|
def renderInResponses = false
|
||||||
override val name = "different"
|
override val name = "different"
|
||||||
override def parse(value: String) =
|
override def parse(value: String) =
|
||||||
if (value contains " ") Failure(new Exception("Contains illegal whitespace!"))
|
if (value contains " ") Failure(new Exception("Contains illegal whitespace!"))
|
||||||
else Success(new DifferentHeader(value))
|
else Success(new DifferentHeader(value))
|
||||||
}
|
}
|
||||||
final class DifferentHeader(token: String) extends ModeledCustomHeader[DifferentHeader] {
|
final class DifferentHeader(token: String) extends ModeledCustomHeader[DifferentHeader] {
|
||||||
|
def renderInRequests = false
|
||||||
|
def renderInResponses = false
|
||||||
override val companion = DifferentHeader
|
override val companion = DifferentHeader
|
||||||
override def value = token
|
override def value = token
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,8 +47,8 @@ private[http] object ExtractionMap {
|
||||||
def addAll(values: Map[RequestVal[_], Any]): ExtractionMap =
|
def addAll(values: Map[RequestVal[_], Any]): ExtractionMap =
|
||||||
ExtractionMap(map ++ values)
|
ExtractionMap(map ++ values)
|
||||||
|
|
||||||
// CustomHeader methods
|
def renderInRequests = false
|
||||||
override def suppressRendering: Boolean = true
|
def renderInResponses = false
|
||||||
def name(): String = "ExtractedValues"
|
def name(): String = "ExtractedValues"
|
||||||
def value(): String = "<empty>"
|
def value(): String = "<empty>"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -243,8 +243,8 @@ private[akka] final case class Fold[In, Out](zero: Out, f: (Out, In) ⇒ Out, de
|
||||||
*/
|
*/
|
||||||
final case class Intersperse[T](start: Option[T], inject: T, end: Option[T]) extends GraphStage[FlowShape[T, T]] {
|
final case class Intersperse[T](start: Option[T], inject: T, end: Option[T]) extends GraphStage[FlowShape[T, T]] {
|
||||||
ReactiveStreamsCompliance.requireNonNullElement(inject)
|
ReactiveStreamsCompliance.requireNonNullElement(inject)
|
||||||
if(start.isDefined) ReactiveStreamsCompliance.requireNonNullElement(start.get)
|
if (start.isDefined) ReactiveStreamsCompliance.requireNonNullElement(start.get)
|
||||||
if(end.isDefined) ReactiveStreamsCompliance.requireNonNullElement(end.get)
|
if (end.isDefined) ReactiveStreamsCompliance.requireNonNullElement(end.get)
|
||||||
|
|
||||||
private val in = Inlet[T]("in")
|
private val in = Inlet[T]("in")
|
||||||
private val out = Outlet[T]("out")
|
private val out = Outlet[T]("out")
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,17 @@ package akka.stream.javadsl
|
||||||
|
|
||||||
import akka.japi.function
|
import akka.japi.function
|
||||||
import akka.stream._
|
import akka.stream._
|
||||||
|
import akka.stream.scaladsl.Flow
|
||||||
|
|
||||||
import scala.concurrent.duration.FiniteDuration
|
import scala.concurrent.duration.FiniteDuration
|
||||||
|
|
||||||
object BidiFlow {
|
object BidiFlow {
|
||||||
|
|
||||||
|
private[this] val _identity: BidiFlow[Object, Object, Object, Object, Unit] =
|
||||||
|
BidiFlow.fromFlows(Flow.of(classOf[Object]), Flow.of(classOf[Object]))
|
||||||
|
|
||||||
|
def identity[A, B]: BidiFlow[A, A, B, B, Unit] = _identity.asInstanceOf[BidiFlow[A, A, B, B, Unit]]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A graph with the shape of a BidiFlow logically is a BidiFlow, this method makes
|
* A graph with the shape of a BidiFlow logically is a BidiFlow, this method makes
|
||||||
* it so also in type.
|
* it so also in type.
|
||||||
|
|
|
||||||
|
|
@ -155,6 +155,11 @@ final class BidiFlow[-I1, +O1, -I2, +O2, +Mat](private[stream] override val modu
|
||||||
}
|
}
|
||||||
|
|
||||||
object BidiFlow {
|
object BidiFlow {
|
||||||
|
private[this] val _identity: BidiFlow[Any, Any, Any, Any, Unit] =
|
||||||
|
BidiFlow.fromFlows(Flow[Any], Flow[Any])
|
||||||
|
|
||||||
|
def identity[A, B]: BidiFlow[A, A, B, B, Unit] = _identity.asInstanceOf[BidiFlow[A, A, B, B, Unit]]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A graph with the shape of a flow logically is a flow, this method makes
|
* A graph with the shape of a flow logically is a flow, this method makes
|
||||||
* it so also in type.
|
* it so also in type.
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import akka.stream._
|
||||||
import akka.stream.impl._
|
import akka.stream.impl._
|
||||||
import akka.stream.impl.fusing.GraphStages
|
import akka.stream.impl.fusing.GraphStages
|
||||||
import akka.stream.impl.fusing.GraphStages.MaterializedValueSource
|
import akka.stream.impl.fusing.GraphStages.MaterializedValueSource
|
||||||
import akka.stream.impl.Stages.{DefaultAttributes, StageModule, SymbolicStage}
|
import akka.stream.impl.Stages.{ DefaultAttributes, StageModule, SymbolicStage }
|
||||||
import akka.stream.impl.StreamLayout._
|
import akka.stream.impl.StreamLayout._
|
||||||
import akka.stream.stage.{ OutHandler, InHandler, GraphStageLogic, GraphStage }
|
import akka.stream.stage.{ OutHandler, InHandler, GraphStageLogic, GraphStage }
|
||||||
import scala.annotation.unchecked.uncheckedVariance
|
import scala.annotation.unchecked.uncheckedVariance
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue