Merge pull request #18562 from ktoso/wip-illegal-headers-warning-bad-ktoso

=htc #18533 make configuration of parsing less confusing
This commit is contained in:
Konrad Malawski 2015-10-06 10:40:06 +02:00
commit f9909d79ab
12 changed files with 309 additions and 35 deletions

View file

@ -88,7 +88,9 @@ akka.http {
}
# Modify to tweak parsing settings on the server-side only.
parsing = ${akka.http.parsing}
parsing {
# no overrides by default, see `akka.http.parsing` for default values
}
}
client {
@ -142,7 +144,9 @@ akka.http {
}
# Modify to tweak parsing settings on the client-side only.
parsing = ${akka.http.parsing}
parsing {
# no overrides by default, see `akka.http.parsing` for default values
}
}
host-connection-pool {
@ -179,15 +183,73 @@ akka.http {
idle-timeout = 30 s
# Modify to tweak client settings for host connection pools only.
client = ${akka.http.client}
#
# IMPORTANT:
# Please note that this section mirrors `akka.http.client` however is used only for pool-based APIs,
# such as `Http().superPool` or `Http().singleRequest`.
client = {
# The default value of the `User-Agent` header to produce if no
# explicit `User-Agent`-header was included in a request.
# If this value is the empty string and no header was included in
# the request, no `User-Agent` header will be rendered at all.
user-agent-header = akka-http/${akka.version}
# The time period within which the TCP connecting process must be completed.
connecting-timeout = 10s
# The time after which an idle connection will be automatically closed.
# Set to `infinite` to completely disable idle timeouts.
idle-timeout = 60 s
# The initial size of the buffer to render the request headers in.
# Can be used for fine-tuning request rendering performance but probably
# doesn't have to be fiddled with in most applications.
request-header-size-hint = 512
# The proxy configurations to be used for requests with the specified
# scheme.
proxy {
# Proxy settings for unencrypted HTTP requests
# Set to 'none' to always connect directly, 'default' to use the system
# settings as described in http://docs.oracle.com/javase/6/docs/technotes/guides/net/proxies.html
# or specify the proxy host, port and non proxy hosts as demonstrated
# in the following example:
# http {
# host = myproxy.com
# port = 8080
# non-proxy-hosts = ["*.direct-access.net"]
# }
http = default
# Proxy settings for HTTPS requests (currently unsupported)
https = default
}
# Socket options to set for the listening socket. If a setting is left
# undefined, it will use whatever the default on the system is.
socket-options {
so-receive-buffer-size = undefined
so-send-buffer-size = undefined
so-reuse-address = undefined
so-traffic-class = undefined
tcp-keep-alive = undefined
tcp-oob-inline = undefined
tcp-no-delay = undefined
}
# IMPORTANT: Please note that this section is replicated in `client` and `server`.
parsing {
# no overrides by default, see `akka.http.parsing` for default values
}
}
}
# The (default) configuration of the HTTP message parser for the server and the client.
# IMPORTANT: These settings (i.e. children of `akka.http.parsing`) can't be directly
# overridden in `application.conf` to change the parser settings for client and server
# at the same time. Instead, override the concrete settings beneath
# `akka.http.server.parsing` and `akka.http.client.parsing`
# where these settings are copied to.
# Modify to tweak default parsing settings.
#
# IMPORTANT:
# Please note that this sections settings can be overriden by the corresponding settings in:
# `akka.http.server.parsing`, `akka.http.client.parsing` or `akka.http.http-connection-pool.client.parsing`.
parsing {
# The limits for the various parts of the HTTP message parser.
max-uri-length = 2k

View file

@ -29,14 +29,15 @@ final case class ClientConnectionSettings(
}
object ClientConnectionSettings extends SettingsCompanion[ClientConnectionSettings]("akka.http.client") {
def fromSubConfig(c: Config) = {
def fromSubConfig(root: Config, inner: Config) = {
val c = inner.withFallback(root.getConfig(prefix))
apply(
c.getString("user-agent-header").toOption.map(`User-Agent`(_)),
c getFiniteDuration "connecting-timeout",
c getPotentiallyInfiniteDuration "idle-timeout",
c getIntBytes "request-header-size-hint",
SocketOptionSettings fromSubConfig c.getConfig("socket-options"),
ParserSettings fromSubConfig c.getConfig("parsing"))
SocketOptionSettings.fromSubConfig(root, c.getConfig("socket-options")),
ParserSettings.fromSubConfig(root, c.getConfig("parsing")))
}
/**

View file

@ -5,15 +5,14 @@
package akka.http
import java.lang.{ Iterable JIterable }
import akka.http.scaladsl.HttpsContext
import com.typesafe.config.Config
import scala.collection.immutable
import scala.concurrent.duration.Duration
import akka.japi.Util._
import akka.actor.ActorSystem
import akka.event.LoggingAdapter
import akka.http.impl.util._
import akka.io.Inet
import akka.http.scaladsl.HttpsContext
import com.typesafe.config.Config
import scala.concurrent.duration.Duration
final case class HostConnectionPoolSetup(host: String, port: Int, setup: ConnectionPoolSetup)
@ -42,18 +41,18 @@ final case class ConnectionPoolSettings(
require(maxRetries >= 0, "max-retries must be >= 0")
require(maxOpenRequests > 0 && (maxOpenRequests & (maxOpenRequests - 1)) == 0, "max-open-requests must be a power of 2 > 0")
require(pipeliningLimit > 0, "pipelining-limit must be > 0")
require(idleTimeout >= Duration.Zero, "idleTimeout must be >= 0")
require(idleTimeout >= Duration.Zero, "idle-timeout must be >= 0")
}
object ConnectionPoolSettings extends SettingsCompanion[ConnectionPoolSettings]("akka.http.host-connection-pool") {
def fromSubConfig(c: Config) = {
def fromSubConfig(root: Config, c: Config) = {
apply(
c getInt "max-connections",
c getInt "max-retries",
c getInt "max-open-requests",
c getInt "pipelining-limit",
c getPotentiallyInfiniteDuration "idle-timeout",
ClientConnectionSettings fromSubConfig c.getConfig("client"))
ClientConnectionSettings.fromSubConfig(root, c.getConfig("client")))
}
/**

View file

@ -55,7 +55,8 @@ final case class ParserSettings(
}
object ParserSettings extends SettingsCompanion[ParserSettings]("akka.http.parsing") {
def fromSubConfig(c: Config) = {
def fromSubConfig(root: Config, inner: Config) = {
val c = inner.withFallback(root.getConfig(prefix))
val cacheConfig = c getConfig "header-cache"
apply(

View file

@ -45,7 +45,7 @@ object ServerSettings extends SettingsCompanion[ServerSettings]("akka.http.serve
}
implicit def timeoutsShortcut(s: ServerSettings): Timeouts = s.timeouts
def fromSubConfig(c: Config) = apply(
def fromSubConfig(root: Config, c: Config) = apply(
c.getString("server-header").toOption.map(Server(_)),
Timeouts(
c getPotentiallyInfiniteDuration "idle-timeout",
@ -57,7 +57,7 @@ object ServerSettings extends SettingsCompanion[ServerSettings]("akka.http.serve
c getBoolean "verbose-error-messages",
c getIntBytes "response-header-size-hint",
c getInt "backlog",
SocketOptionSettings fromSubConfig c.getConfig("socket-options"),
SocketOptionSettings.fromSubConfig(root, c.getConfig("socket-options")),
defaultHostHeader =
HttpHeader.parse("Host", c getString "default-host-header") match {
case HttpHeader.ParsingResult.Ok(x: Host, Nil) x
@ -65,7 +65,7 @@ object ServerSettings extends SettingsCompanion[ServerSettings]("akka.http.serve
val info = result.errors.head.withSummary("Configured `default-host-header` is illegal")
throw new ConfigurationException(info.formatPretty)
},
ParserSettings fromSubConfig c.getConfig("parsing"))
ParserSettings.fromSubConfig(root, c.getConfig("parsing")))
def apply(optionalSettings: Option[ServerSettings])(implicit actorRefFactory: ActorRefFactory): ServerSettings =
optionalSettings getOrElse apply(actorSystem)

View file

@ -15,7 +15,7 @@ import akka.actor.ActorSystem
/**
* INTERNAL API
*/
private[http] abstract class SettingsCompanion[T](prefix: String) {
private[http] abstract class SettingsCompanion[T](protected val prefix: String) {
private final val MaxCached = 8
private[this] var cache = ListMap.empty[ActorSystem, T]
@ -41,9 +41,9 @@ private[http] abstract class SettingsCompanion[T](prefix: String) {
.withFallback(defaultReference(getClass.getClassLoader)))
def apply(config: Config): T =
fromSubConfig(config getConfig prefix)
fromSubConfig(config, config getConfig prefix)
def fromSubConfig(c: Config): T
def fromSubConfig(root: Config, c: Config): T
}
private[http] object SettingsCompanion {

View file

@ -12,11 +12,11 @@ import akka.io.Inet.SocketOption
import com.typesafe.config.Config
private[http] object SocketOptionSettings {
def fromSubConfig(config: Config): immutable.Traversable[SocketOption] = {
def fromSubConfig(root: Config, c: Config): immutable.Traversable[SocketOption] = {
def so[T](setting: String)(f: (Config, String) T)(cons: T SocketOption): List[SocketOption] =
config.getString(setting) match {
c.getString(setting) match {
case "undefined" Nil
case x cons(f(config, setting)) :: Nil
case x cons(f(c, setting)) :: Nil
}
so("so-receive-buffer-size")(_ getIntBytes _)(Inet.SO.ReceiveBufferSize) :::

View file

@ -37,6 +37,8 @@ class HttpExt(config: Config)(implicit system: ActorSystem) extends akka.actor.E
// SYNCHRONIZED ACCESS ONLY!
private[this] var _defaultClientHttpsContext: HttpsContext = _
// ** SERVER ** //
/**
* Creates a [[Source]] of [[IncomingConnection]] instances which represents a prospective HTTP server binding
* on the given `endpoint`.
@ -52,6 +54,9 @@ class HttpExt(config: Config)(implicit system: ActorSystem) extends akka.actor.E
*
* If no ``port`` is explicitly given (or the port value is negative) the protocol's default port will be used,
* which is 80 for HTTP and 443 for HTTPS.
*
* To configure additional settings for a server started using this method,
* use the `akka.http.server` config section or pass in a [[ServerSettings]] explicitly.
*/
def bind(interface: String, port: Int = -1,
settings: ServerSettings = ServerSettings(system),
@ -76,6 +81,9 @@ class HttpExt(config: Config)(implicit system: ActorSystem) extends akka.actor.E
*
* The number of concurrently accepted connections can be configured by overriding
* the `akka.http.server.max-connections` setting.
*
* To configure additional settings for a server started using this method,
* use the `akka.http.server` config section or pass in a [[ServerSettings]] explicitly.
*/
def bindAndHandle(handler: Flow[HttpRequest, HttpResponse, Any],
interface: String, port: Int = -1,
@ -114,6 +122,9 @@ class HttpExt(config: Config)(implicit system: ActorSystem) extends akka.actor.E
*
* The number of concurrently accepted connections can be configured by overriding
* the `akka.http.server.max-connections` setting.
*
* To configure additional settings for a server started using this method,
* use the `akka.http.server` config section or pass in a [[ServerSettings]] explicitly.
*/
def bindAndHandleSync(handler: HttpRequest HttpResponse,
interface: String, port: Int = -1,
@ -128,6 +139,9 @@ class HttpExt(config: Config)(implicit system: ActorSystem) extends akka.actor.E
*
* The number of concurrently accepted connections can be configured by overriding
* the `akka.http.server.max-connections` setting.
*
* To configure additional settings for a server started using this method,
* use the `akka.http.server` config section or pass in a [[ServerSettings]] explicitly.
*/
def bindAndHandleAsync(handler: HttpRequest Future[HttpResponse],
interface: String, port: Int = -1,
@ -140,8 +154,10 @@ class HttpExt(config: Config)(implicit system: ActorSystem) extends akka.actor.E
type ServerLayer = Http.ServerLayer
/**
* Constructs a [[ServerLayer]] stage using the configured default [[ServerSettings]]. The returned [[BidiFlow]]
* can only be materialized once.
* Constructs a [[ServerLayer]] stage using the configured default [[ServerSettings]],
* configured using the `akka.http.server` config section.
*
* The returned [[BidiFlow]] can only be materialized once.
*/
def serverLayer()(implicit mat: Materializer): ServerLayer = serverLayer(ServerSettings(system))
@ -155,9 +171,14 @@ class HttpExt(config: Config)(implicit system: ActorSystem) extends akka.actor.E
log: LoggingAdapter = system.log)(implicit mat: Materializer): ServerLayer =
HttpServerBluePrint(settings, remoteAddress, log)
// ** CLIENT ** //
/**
* Creates a [[Flow]] representing a prospective HTTP client connection to the given endpoint.
* Every materialization of the produced flow will attempt to establish a new outgoing connection.
*
* To configure additional settings for requests made using this method,
* use the `akka.http.client` config section or pass in a [[ClientConnectionSettings]] explicitly.
*/
def outgoingConnection(host: String, port: Int = 80,
localAddress: Option[InetSocketAddress] = None,
@ -170,6 +191,9 @@ class HttpExt(config: Config)(implicit system: ActorSystem) extends akka.actor.E
*
* If an explicit [[HttpsContext]] is given then it rather than the configured default [[HttpsContext]] will be used
* for encryption on the connection.
*
* To configure additional settings for requests made using this method,
* use the `akka.http.client` config section or pass in a [[ClientConnectionSettings]] explicitly.
*/
def outgoingConnectionTls(host: String, port: Int = 443,
localAddress: Option[InetSocketAddress] = None,
@ -196,7 +220,8 @@ class HttpExt(config: Config)(implicit system: ActorSystem) extends akka.actor.E
type ClientLayer = Http.ClientLayer
/**
* Constructs a [[ClientLayer]] stage using the configured default [[ClientConnectionSettings]].
* Constructs a [[ClientLayer]] stage using the configured default [[ClientConnectionSettings]],
* configured using the `akka.http.client` config section.
*/
def clientLayer(hostHeader: Host): ClientLayer =
clientLayer(hostHeader, ClientConnectionSettings(system))
@ -209,6 +234,8 @@ class HttpExt(config: Config)(implicit system: ActorSystem) extends akka.actor.E
log: LoggingAdapter = system.log): ClientLayer =
OutgoingConnectionBlueprint(hostHeader, settings, log)
// ** CONNECTION POOL ** //
/**
* Starts a new connection pool to the given host and configuration and returns a [[Flow]] which dispatches
* the requests from all its materializations across this pool.
@ -222,6 +249,9 @@ class HttpExt(config: Config)(implicit system: ActorSystem) extends akka.actor.E
* response for A.
* In order to allow for easy response-to-request association the flow takes in a custom, opaque context
* object of type ``T`` from the application which is emitted together with the corresponding response.
*
* To configure additional settings for the pool (and requests made using it),
* use the `akka.http.host-connection-pool` config section or pass in a [[ConnectionPoolSettings]] explicitly.
*/
def newHostConnectionPool[T](host: String, port: Int = 80,
settings: ConnectionPoolSettings = ConnectionPoolSettings(system),
@ -235,6 +265,9 @@ class HttpExt(config: Config)(implicit system: ActorSystem) extends akka.actor.E
*
* If an explicit [[HttpsContext]] is given then it rather than the configured default [[HttpsContext]] will be used
* for encryption on the connections.
*
* To configure additional settings for the pool (and requests made using it),
* use the `akka.http.host-connection-pool` config section or pass in a [[ConnectionPoolSettings]] explicitly.
*/
def newHostConnectionPoolTls[T](host: String, port: Int = 443,
settings: ConnectionPoolSettings = ConnectionPoolSettings(system),
@ -280,6 +313,9 @@ class HttpExt(config: Config)(implicit system: ActorSystem) extends akka.actor.E
* response for A.
* In order to allow for easy response-to-request association the flow takes in a custom, opaque context
* object of type ``T`` from the application which is emitted together with the corresponding response.
*
* To configure additional settings for the pool (and requests made using it),
* use the `akka.http.host-connection-pool` config section or pass in a [[ConnectionPoolSettings]] explicitly.
*/
def cachedHostConnectionPool[T](host: String, port: Int = 80,
settings: ConnectionPoolSettings = ConnectionPoolSettings(system),
@ -294,6 +330,9 @@ class HttpExt(config: Config)(implicit system: ActorSystem) extends akka.actor.E
*
* If an explicit [[HttpsContext]] is given then it rather than the configured default [[HttpsContext]] will be used
* for encryption on the connections.
*
* To configure additional settings for the pool (and requests made using it),
* use the `akka.http.host-connection-pool` config section or pass in a [[ConnectionPoolSettings]] explicitly.
*/
def cachedHostConnectionPoolTls[T](host: String, port: Int = 443,
settings: ConnectionPoolSettings = ConnectionPoolSettings(system),
@ -338,6 +377,9 @@ class HttpExt(config: Config)(implicit system: ActorSystem) extends akka.actor.E
* response for A.
* In order to allow for easy response-to-request association the flow takes in a custom, opaque context
* object of type ``T`` from the application which is emitted together with the corresponding response.
*
* To configure additional settings for the pool (and requests made using it),
* use the `akka.http.host-connection-pool` config section or pass in a [[ConnectionPoolSettings]] explicitly.
*/
def superPool[T](settings: ConnectionPoolSettings = ConnectionPoolSettings(system),
httpsContext: Option[HttpsContext] = None,

View file

@ -0,0 +1,141 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.impl.engine.client
import akka.actor.ActorSystem
import akka.http.{ ClientConnectionSettings, ConnectionPoolSettings, ServerSettings }
import akka.stream.testkit.AkkaSpec
import com.typesafe.config.ConfigFactory
class HttpConfigurationSpec extends AkkaSpec {
val On = true
val Off = false
"Reference configurations" should {
"have default client and server `parsing` settings" in {
ServerSettings(system).parserSettings.toString should ===(ClientConnectionSettings(system).parserSettings.toString)
}
"have default client and pool `parsing` settings" in {
ServerSettings(system).parserSettings.toString should ===(ConnectionPoolSettings(system).connectionSettings.parserSettings.toString)
}
"have default client and pool `client` settings" in {
ClientConnectionSettings(system).toString should ===(ConnectionPoolSettings(system).connectionSettings.toString)
}
"override value from `akka.http.parsing` by setting `akka.http.client.parsing`" in {
configuredSystem("""akka.http.client.parsing.illegal-header-warnings = off""") { sys
val client = ClientConnectionSettings(sys)
client.parserSettings.illegalHeaderWarnings should ===(Off)
val pool = ConnectionPoolSettings(sys)
pool.connectionSettings.parserSettings.illegalHeaderWarnings should ===(Off)
val server = ServerSettings(sys)
server.parserSettings.illegalHeaderWarnings should ===(On)
}
}
"override `akka.http.parsing` by setting `akka.http.host-connection-pool.client.parsing` setting" in {
configuredSystem("""akka.http.host-connection-pool.client.parsing.illegal-header-warnings = off""") { sys
val client = ClientConnectionSettings(sys)
client.parserSettings.illegalHeaderWarnings should ===(On)
val pool = ConnectionPoolSettings(sys)
pool.connectionSettings.parserSettings.illegalHeaderWarnings should ===(Off)
val server = ServerSettings(sys)
server.parserSettings.illegalHeaderWarnings should ===(On)
}
}
"set `akka.http.host-connection-pool.client.idle-timeout` only" in {
configuredSystem("""akka.http.host-connection-pool.client.idle-timeout = 1337s""") { sys
import scala.concurrent.duration._
val client = ClientConnectionSettings(sys)
client.idleTimeout should ===(60.seconds)
val pool = ConnectionPoolSettings(sys)
pool.connectionSettings.idleTimeout should ===(1337.seconds)
val server = ServerSettings(sys)
server.idleTimeout should ===(60.seconds) // no change, default akka.http.server.idle-timeout
}
}
"set `akka.http.server.idle-timeout` only" in {
configuredSystem("""akka.http.server.idle-timeout = 1337s""") { sys
import scala.concurrent.duration._
val client = ClientConnectionSettings(sys)
client.idleTimeout should ===(60.seconds)
val pool = ConnectionPoolSettings(sys)
pool.connectionSettings.idleTimeout should ===(60.seconds)
val server = ServerSettings(sys)
server.idleTimeout should ===(1337.seconds)
}
}
"change parser settings for all by setting `akka.http.parsing`" in {
configuredSystem("""akka.http.parsing.illegal-header-warnings = off""") { sys
val client = ClientConnectionSettings(sys)
client.parserSettings.illegalHeaderWarnings should ===(Off)
val pool = ConnectionPoolSettings(sys)
pool.connectionSettings.parserSettings.illegalHeaderWarnings should ===(Off)
val server = ServerSettings(sys)
server.parserSettings.illegalHeaderWarnings should ===(Off)
}
}
"change parser settings for all by setting `akka.http.parsing`, unless client/server override it" in {
configuredSystem("""
akka.http {
parsing.illegal-header-warnings = off
server.parsing.illegal-header-warnings = on
client.parsing.illegal-header-warnings = on // also affects host-connection-pool.client
}""") { sys
val client = ClientConnectionSettings(sys)
client.parserSettings.illegalHeaderWarnings should ===(On)
val pool = ConnectionPoolSettings(sys)
pool.connectionSettings.parserSettings.illegalHeaderWarnings should ===(On)
val server = ServerSettings(sys)
server.parserSettings.illegalHeaderWarnings should ===(On)
}
}
"change parser settings for all by setting `akka.http.parsing`, unless all override it" in {
configuredSystem("""
akka.http {
parsing.illegal-header-warnings = off
server.parsing.illegal-header-warnings = on
client.parsing.illegal-header-warnings = on
host-connection-pool.client.parsing.illegal-header-warnings = off
}""") { sys
val client = ClientConnectionSettings(sys)
client.parserSettings.illegalHeaderWarnings should ===(On)
val pool = ConnectionPoolSettings(sys)
pool.connectionSettings.parserSettings.illegalHeaderWarnings should ===(Off)
val server = ServerSettings(sys)
server.parserSettings.illegalHeaderWarnings should ===(On)
}
}
}
def configuredSystem(overrides: String)(block: ActorSystem Unit) = {
val config = ConfigFactory.parseString(overrides).withFallback(ConfigFactory.load())
// we go via ActorSystem in order to hit the settings caching infrastructure
val sys = ActorSystem("config-testing", config)
try block(sys) finally sys.shutdown()
}
}