+doc,htp #19896 directives for RequestTimeout and docs
This commit is contained in:
parent
27c004d274
commit
2d7d24dee6
24 changed files with 523 additions and 70 deletions
|
|
@ -1,55 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (C) 2015 Lightbend Inc. <http://www.lightbend.com/>
|
|
||||||
*/
|
|
||||||
package docs.stream.cookbook
|
|
||||||
|
|
||||||
import akka.stream.scaladsl.{ Sink, Source }
|
|
||||||
|
|
||||||
import scala.collection.immutable
|
|
||||||
import scala.concurrent.{ Await, Future }
|
|
||||||
import scala.concurrent.duration._
|
|
||||||
|
|
||||||
class RecipeSeq extends RecipeSpec {
|
|
||||||
|
|
||||||
"Recipe for draining a stream into a strict collection" must {
|
|
||||||
|
|
||||||
"work" in {
|
|
||||||
//#draining-to-seq-unsafe
|
|
||||||
val result = immutable.Seq[Message]("1", "2", "3")
|
|
||||||
val myData = Source(result)
|
|
||||||
|
|
||||||
val unsafe: Future[Seq[Message]] = myData.runWith(Sink.seq) // dangerous!
|
|
||||||
//#draining-to-seq-unsafe
|
|
||||||
|
|
||||||
Await.result(unsafe, 3.seconds) should be(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
"work together with limit(n)" in {
|
|
||||||
//#draining-to-seq-safe
|
|
||||||
val result = List("1", "2", "3")
|
|
||||||
val myData = Source(result)
|
|
||||||
val max = 100
|
|
||||||
|
|
||||||
// OK. Future will fail with a `StreamLimitReachedException`
|
|
||||||
// if the number of incoming elements is larger than max
|
|
||||||
val safe1: Future[immutable.Seq[Message]] = myData.limit(max).runWith(Sink.seq)
|
|
||||||
//#draining-to-seq-safe
|
|
||||||
|
|
||||||
Await.result(safe1, 3.seconds) should be(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
"work together with take(n)" in {
|
|
||||||
val result = List("1", "2", "3")
|
|
||||||
val myData = Source(result)
|
|
||||||
val max = 100
|
|
||||||
|
|
||||||
//#draining-to-seq-safe
|
|
||||||
// OK. Collect up until max-th elements only, then cancel upstream
|
|
||||||
val safe2: Future[immutable.Seq[Message]] = myData.take(max).runWith(Sink.seq)
|
|
||||||
//#draining-to-seq-safe
|
|
||||||
|
|
||||||
Await.result(safe2, 3.seconds) should be(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -31,8 +31,8 @@ code with ``git pull``::
|
||||||
git pull origin master
|
git pull origin master
|
||||||
|
|
||||||
|
|
||||||
sbt - Simple Build Tool
|
sbt
|
||||||
=======================
|
===
|
||||||
|
|
||||||
Akka is using the excellent `sbt`_ build system. So the first thing you have to
|
Akka is using the excellent `sbt`_ build system. So the first thing you have to
|
||||||
do is to download and install sbt. You can read more about how to do that in the
|
do is to download and install sbt. You can read more about how to do that in the
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@ Sponsors
|
||||||
========
|
========
|
||||||
|
|
||||||
Lightbend
|
Lightbend
|
||||||
--------
|
---------
|
||||||
|
|
||||||
Lightbend is the company behind the Akka Project, Scala Programming Language,
|
Lightbend is the company behind the Akka Project, Scala Programming Language,
|
||||||
Play Web Framework, Scala IDE, Simple Build Tool and many other open source
|
Play Web Framework, Scala IDE, sbt and many other open source
|
||||||
projects. It also provides the Lightbend Stack, a full-featured development
|
projects. It also provides the Lightbend Stack, a full-featured development
|
||||||
stack consisting of AKka, Play and Scala. Learn more at
|
stack consisting of AKka, Play and Scala. Learn more at
|
||||||
`lightbend.com <http://www.lightbend.com>`_.
|
`lightbend.com <http://www.lightbend.com>`_.
|
||||||
|
|
|
||||||
13
akka-docs/rst/scala/code/docs/CompileOnlySpec.scala
Normal file
13
akka-docs/rst/scala/code/docs/CompileOnlySpec.scala
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package docs
|
||||||
|
|
||||||
|
trait CompileOnlySpec {
|
||||||
|
/**
|
||||||
|
* Given a block of code... does NOT execute it.
|
||||||
|
* Useful when writing code samples in tests, which should only be compiled.
|
||||||
|
*/
|
||||||
|
def compileOnlySpec(body: => Unit) = ()
|
||||||
|
}
|
||||||
|
|
@ -12,18 +12,18 @@ import akka.http.scaladsl.model._
|
||||||
import akka.stream.ActorMaterializer
|
import akka.stream.ActorMaterializer
|
||||||
import akka.stream.scaladsl.{ Flow, Sink }
|
import akka.stream.scaladsl.{ Flow, Sink }
|
||||||
import akka.testkit.TestActors
|
import akka.testkit.TestActors
|
||||||
|
import docs.CompileOnlySpec
|
||||||
import org.scalatest.{ Matchers, WordSpec }
|
import org.scalatest.{ Matchers, WordSpec }
|
||||||
import scala.language.postfixOps
|
import scala.language.postfixOps
|
||||||
|
|
||||||
import scala.concurrent.{ ExecutionContext, Future }
|
import scala.concurrent.{ ExecutionContext, Future }
|
||||||
|
|
||||||
class HttpServerExampleSpec extends WordSpec with Matchers {
|
class HttpServerExampleSpec extends WordSpec with Matchers
|
||||||
|
with CompileOnlySpec {
|
||||||
|
|
||||||
// never actually called
|
// never actually called
|
||||||
val log: LoggingAdapter = null
|
val log: LoggingAdapter = null
|
||||||
|
|
||||||
def compileOnlySpec(body: => Unit) = ()
|
|
||||||
|
|
||||||
"binding-example" in compileOnlySpec {
|
"binding-example" in compileOnlySpec {
|
||||||
import akka.http.scaladsl.Http
|
import akka.http.scaladsl.Http
|
||||||
import akka.stream.ActorMaterializer
|
import akka.stream.ActorMaterializer
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,9 @@ package docs.http.scaladsl.server
|
||||||
|
|
||||||
import akka.http.scaladsl.server.Directives
|
import akka.http.scaladsl.server.Directives
|
||||||
import akka.http.scaladsl.testkit.ScalatestRouteTest
|
import akka.http.scaladsl.testkit.ScalatestRouteTest
|
||||||
|
import docs.CompileOnlySpec
|
||||||
import org.scalatest.{ Matchers, WordSpec }
|
import org.scalatest.{ Matchers, WordSpec }
|
||||||
|
|
||||||
abstract class RoutingSpec extends WordSpec with Matchers with Directives with ScalatestRouteTest
|
abstract class RoutingSpec extends WordSpec with Matchers
|
||||||
|
with Directives with ScalatestRouteTest
|
||||||
|
with CompileOnlySpec
|
||||||
|
|
|
||||||
|
|
@ -260,6 +260,7 @@ class BasicDirectivesExamplesSpec extends RoutingSpec {
|
||||||
//#1mapResponse-advanced
|
//#1mapResponse-advanced
|
||||||
trait ApiRoutes {
|
trait ApiRoutes {
|
||||||
protected def system: ActorSystem
|
protected def system: ActorSystem
|
||||||
|
|
||||||
private val log = Logging(system, "ApiRoutes")
|
private val log = Logging(system, "ApiRoutes")
|
||||||
|
|
||||||
private val NullJsonEntity = HttpEntity(ContentTypes.`application/json`, "{}")
|
private val NullJsonEntity = HttpEntity(ContentTypes.`application/json`, "{}")
|
||||||
|
|
@ -800,5 +801,4 @@ class BasicDirectivesExamplesSpec extends RoutingSpec {
|
||||||
//#
|
//#
|
||||||
}
|
}
|
||||||
|
|
||||||
private def compileOnlySpec(block: => Unit) = pending
|
}
|
||||||
}
|
|
||||||
|
|
@ -102,5 +102,4 @@ class FileAndResourceDirectivesExamplesSpec extends RoutingSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private def compileOnlySpec(block: => Unit) = pending
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package docs.http.scaladsl.server.directives
|
||||||
|
|
||||||
|
import akka.http.scaladsl.model.{ HttpResponse, StatusCodes }
|
||||||
|
import akka.http.scaladsl.server.RoutingSpec
|
||||||
|
import docs.CompileOnlySpec
|
||||||
|
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
import scala.concurrent.{ Future, Promise }
|
||||||
|
|
||||||
|
class TimeoutDirectivesExamplesSpec extends RoutingSpec with CompileOnlySpec {
|
||||||
|
|
||||||
|
"Request Timeout" should {
|
||||||
|
"be configurable in routing layer" in compileOnlySpec {
|
||||||
|
//#withRequestTimeout-plain
|
||||||
|
val route =
|
||||||
|
path("timeout") {
|
||||||
|
withRequestTimeout(3.seconds) {
|
||||||
|
val response: Future[String] = slowFuture() // very slow
|
||||||
|
complete(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#
|
||||||
|
}
|
||||||
|
"without timeout" in compileOnlySpec {
|
||||||
|
//#withoutRequestTimeout-1
|
||||||
|
val route =
|
||||||
|
path("timeout") {
|
||||||
|
withoutRequestTimeout {
|
||||||
|
val response: Future[String] = slowFuture() // very slow
|
||||||
|
complete(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#
|
||||||
|
}
|
||||||
|
|
||||||
|
"allow mapping the response while setting the timeout" in compileOnlySpec {
|
||||||
|
//#withRequestTimeout-with-handler
|
||||||
|
val timeoutResponse = HttpResponse(StatusCodes.EnhanceYourCalm,
|
||||||
|
entity = "Unable to serve response within time limit, please enchance your calm.")
|
||||||
|
|
||||||
|
val route =
|
||||||
|
path("timeout") {
|
||||||
|
// updates timeout and handler at
|
||||||
|
withRequestTimeout(1.milli, request => timeoutResponse) {
|
||||||
|
val response: Future[String] = slowFuture() // very slow
|
||||||
|
complete(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#
|
||||||
|
}
|
||||||
|
|
||||||
|
"allow mapping the response" in compileOnlySpec {
|
||||||
|
pending // compile only spec since requires actuall Http server to be run
|
||||||
|
|
||||||
|
//#withRequestTimeoutResponse
|
||||||
|
val timeoutResponse = HttpResponse(StatusCodes.EnhanceYourCalm,
|
||||||
|
entity = "Unable to serve response within time limit, please enchance your calm.")
|
||||||
|
|
||||||
|
val route =
|
||||||
|
path("timeout") {
|
||||||
|
withRequestTimeout(1.milli) {
|
||||||
|
withRequestTimeoutResponse(request => timeoutResponse) {
|
||||||
|
val response: Future[String] = slowFuture() // very slow
|
||||||
|
complete(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def slowFuture(): Future[String] = Promise[String].future
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -65,7 +65,12 @@ Timeouts
|
||||||
|
|
||||||
Currently Akka HTTP doesn't implement client-side request timeout checking itself as this functionality can be regarded
|
Currently Akka HTTP doesn't implement client-side request timeout checking itself as this functionality can be regarded
|
||||||
as a more general purpose streaming infrastructure feature.
|
as a more general purpose streaming infrastructure feature.
|
||||||
However, akka-stream should soon provide such a feature.
|
|
||||||
|
It should be noted that Akka Streams provide various timeout functionality so any API that uses a streams can benefit
|
||||||
|
from the stream stages such as ``idleTimeout``, ``completionTimeout``, ``initialTimeout`` and even ``throttle``.
|
||||||
|
To learn more about these refer to their documentation in Akka Streams (and Scala Doc).
|
||||||
|
|
||||||
|
For more details about timeout support in Akka HTTP in general refer to :ref:`http-timeouts`.
|
||||||
|
|
||||||
|
|
||||||
.. _http-client-layer:
|
.. _http-client-layer:
|
||||||
|
|
|
||||||
|
|
@ -18,4 +18,5 @@ which are specific to one side only.
|
||||||
unmarshalling
|
unmarshalling
|
||||||
de-coding
|
de-coding
|
||||||
json-support
|
json-support
|
||||||
xml-support
|
xml-support
|
||||||
|
timeouts
|
||||||
76
akka-docs/rst/scala/http/common/timeouts.rst
Normal file
76
akka-docs/rst/scala/http/common/timeouts.rst
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
.. _http-timeouts:
|
||||||
|
|
||||||
|
Akka HTTP Timeouts
|
||||||
|
==================
|
||||||
|
|
||||||
|
Akka HTTP comes with a variety of built-in timeout mechanisms to protect your servers from malicious attacks or
|
||||||
|
programming mistakes. Some of these are simply configuration options (which may be overriden in code) while others
|
||||||
|
are left to the streaming APIs and are easily implementable as patterns in user-code directly.
|
||||||
|
|
||||||
|
Common timeouts
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Idle timeouts
|
||||||
|
^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The ``idle-timeout`` is a global setting which sets the maximum inactivity time of a given connection.
|
||||||
|
In other words, if a connection is open but no request/response is being written to it for over ``idle-timeout`` time,
|
||||||
|
the connection will be automatically closed.
|
||||||
|
|
||||||
|
The setting works the same way for all connections, be it server-side or client-side, and it's configurable
|
||||||
|
independently for each of those using the following keys::
|
||||||
|
|
||||||
|
akka.http.server.idle-timeout
|
||||||
|
akka.http.client.idle-timeout
|
||||||
|
akka.http.http-connection-pool.idle-timeout
|
||||||
|
akka.http.http-connection-pool.client.idle-timeout
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
For the connection pooled client side the idle period is counted only when the pool has no pending requests waiting.
|
||||||
|
|
||||||
|
|
||||||
|
Server timeouts
|
||||||
|
---------------
|
||||||
|
|
||||||
|
.. _request-timeout:
|
||||||
|
|
||||||
|
Request timeout
|
||||||
|
^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Request timeouts are a mechanism that limits the maximum time it may take to produce an ``HttpResponse`` from a route.
|
||||||
|
If that deadline is not met the server will automatically inject a Service Unavailable HTTP response and close the connection
|
||||||
|
to prevent it from leaking and staying around indefinitely (for example if by programming error a Future would never complete,
|
||||||
|
never sending the real response otherwise).
|
||||||
|
|
||||||
|
The default ``HttpResponse`` that is written when a request timeout is exceeded looks like this:
|
||||||
|
|
||||||
|
.. includecode2:: /../../akka-http-core/src/main/scala/akka/http/impl/engine/server/HttpServerBluePrint.scala
|
||||||
|
:snippet: default-request-timeout-httpresponse
|
||||||
|
|
||||||
|
A default request timeout is applied globally to all routes and can be configured using the
|
||||||
|
``akka.http.server.request-timeout`` setting (which defaults to 20 seconds).
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Please note that if multiple requests (``R1,R2,R3,...``) were sent by a client (see "HTTP pipelining")
|
||||||
|
using the same connection and the ``n-th`` request triggers a request timeout the server will reply with an Http Response
|
||||||
|
and close the connection, leaving the ``(n+1)-th`` (and subsequent requests on the same connection) unhandled.
|
||||||
|
|
||||||
|
The request timeout can be configured at run-time for a given route using the any of the :ref:`TimeoutDirectives`.
|
||||||
|
|
||||||
|
Bind timeout
|
||||||
|
^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The bind timeout is the time period within which the TCP binding process must be completed (using any of the ``Http().bind*`` methods).
|
||||||
|
It can be configured using the ``akka.http.server.bind-timeout`` setting.
|
||||||
|
|
||||||
|
Client timeouts
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Connecting timeout
|
||||||
|
^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The connecting timeout is the time period within which the TCP connecting process must be completed.
|
||||||
|
Tweaking it should rarely be required, but it allows erroring out the connection in case a connection
|
||||||
|
is unable to be established for a given amount of time.
|
||||||
|
|
||||||
|
it can be configured using the ``akka.http.client.connecting-timeout`` setting.
|
||||||
|
|
@ -210,10 +210,14 @@ Directive Description
|
||||||
:ref:`-tprovide-` Injects a given tuple of values into a directive
|
:ref:`-tprovide-` Injects a given tuple of values into a directive
|
||||||
:ref:`-uploadedFile-` Streams one uploaded file from a multipart request to a file on disk
|
:ref:`-uploadedFile-` Streams one uploaded file from a multipart request to a file on disk
|
||||||
:ref:`-validate-` Checks a given condition before running its inner route
|
:ref:`-validate-` Checks a given condition before running its inner route
|
||||||
|
:ref:`-withoutRequestTimeout-` Disables :ref:`request timeouts <request-timeout>` for a given route.
|
||||||
:ref:`-withExecutionContext-` Runs its inner route with the given alternative ``ExecutionContext``
|
:ref:`-withExecutionContext-` Runs its inner route with the given alternative ``ExecutionContext``
|
||||||
:ref:`-withMaterializer-` Runs its inner route with the given alternative ``Materializer``
|
:ref:`-withMaterializer-` Runs its inner route with the given alternative ``Materializer``
|
||||||
:ref:`-withLog-` Runs its inner route with the given alternative ``LoggingAdapter``
|
:ref:`-withLog-` Runs its inner route with the given alternative ``LoggingAdapter``
|
||||||
:ref:`-withRangeSupport-` Adds ``Accept-Ranges: bytes`` to responses to GET requests, produces partial
|
:ref:`-withRangeSupport-` Adds ``Accept-Ranges: bytes`` to responses to GET requests, produces partial
|
||||||
responses if the initial request contained a valid ``Range`` header
|
responses if the initial request contained a valid ``Range`` header
|
||||||
|
:ref:`-withRequestTimeout-` Configures the :ref:`request timeouts <request-timeout>` for a given route.
|
||||||
|
:ref:`-withRequestTimeoutResponse-` Prepares the ``HttpResponse`` that is emitted if a request timeout is triggered.
|
||||||
|
``RequestContext => RequestContext`` function
|
||||||
:ref:`-withSettings-` Runs its inner route with the given alternative ``RoutingSettings``
|
:ref:`-withSettings-` Runs its inner route with the given alternative ``RoutingSettings``
|
||||||
=========================================== ============================================================================
|
=========================================== ============================================================================
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,9 @@ Directives creating or transforming the response
|
||||||
:ref:`BasicDirectives` and :ref:`MiscDirectives`
|
:ref:`BasicDirectives` and :ref:`MiscDirectives`
|
||||||
Directives handling or transforming response properties.
|
Directives handling or transforming response properties.
|
||||||
|
|
||||||
|
:ref:`TimeoutDirectives`
|
||||||
|
Configure request timeouts and automatic timeout responses.
|
||||||
|
|
||||||
|
|
||||||
List of predefined directives by trait
|
List of predefined directives by trait
|
||||||
--------------------------------------
|
--------------------------------------
|
||||||
|
|
@ -104,3 +107,4 @@ List of predefined directives by trait
|
||||||
scheme-directives/index
|
scheme-directives/index
|
||||||
security-directives/index
|
security-directives/index
|
||||||
websocket-directives/index
|
websocket-directives/index
|
||||||
|
timeout-directives/index
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
.. _TimeoutDirectives:
|
||||||
|
|
||||||
|
TimeoutDirectives
|
||||||
|
=================
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
|
withRequestTimeout
|
||||||
|
withoutRequestTimeout
|
||||||
|
withRequestTimeoutResponse
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
.. _-withRequestTimeout-:
|
||||||
|
|
||||||
|
withRequestTimeout
|
||||||
|
==================
|
||||||
|
|
||||||
|
Signature
|
||||||
|
---------
|
||||||
|
|
||||||
|
.. includecode2:: /../../akka-http/src/main/scala/akka/http/scaladsl/server/directives/TimeoutDirectives.scala
|
||||||
|
:snippet: withRequestTimeout
|
||||||
|
|
||||||
|
Description
|
||||||
|
-----------
|
||||||
|
|
||||||
|
This directive enables "late" (during request processing) control over the :ref:`request-timeout` feature in Akka HTTP.
|
||||||
|
|
||||||
|
The timeout can be either loosened or made more tight using this directive, however one should be aware that it is
|
||||||
|
inherently racy (which may especially show with very tight timeouts) since a timeout may already have been triggered
|
||||||
|
when this directive executes.
|
||||||
|
|
||||||
|
In case of pipelined HTTP requests (multiple requests being accepted on the same connection before sending the first response)
|
||||||
|
a the request timeout failure of the ``n-th`` request *will shut down the connection* causing the already enqueued requests
|
||||||
|
to be dropped. This is by-design, as the request timeout feature serves as a "safety net" in case of programming errors
|
||||||
|
(e.g. a Future that never completes thus potentially blocking the entire connection forever) or malicious attacks on the server.
|
||||||
|
|
||||||
|
Optionally, a timeout handler may be provided in which is called when a time-out is triggered and must produce an
|
||||||
|
``HttpResponse`` that will be sent back to the client instead of the "too late" response (in case it'd ever arrive).
|
||||||
|
See also :ref:`-withRequestTimeoutResponse-` if only looking to customise the timeout response without changing the timeout itself.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
Please note that setting the timeout from within a directive is inherently racy (as the "point in time from which
|
||||||
|
we're measuring the timeout" is already in the past (the moment we started handling the request), so if the existing
|
||||||
|
timeout already was triggered before your directive had the chance to change it, an timeout may still be logged.
|
||||||
|
|
||||||
|
It is recommended to use a larger statically configured timeout (think of it as a "safety net" against programming errors
|
||||||
|
or malicious attackers) and if needed tighten it using the directives – not the other way around.
|
||||||
|
|
||||||
|
For more information about various timeouts in Akka HTTP see :ref:`http-timeouts`.
|
||||||
|
|
||||||
|
Example
|
||||||
|
-------
|
||||||
|
|
||||||
|
.. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/TimeoutDirectivesExamplesSpec.scala
|
||||||
|
:snippet: withRequestTimeout-plain
|
||||||
|
|
||||||
|
With setting the handler at the same time:
|
||||||
|
|
||||||
|
.. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/TimeoutDirectivesExamplesSpec.scala
|
||||||
|
:snippet: withRequestTimeout-with-handler
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
.. _-withRequestTimeoutResponse-:
|
||||||
|
|
||||||
|
withRequestTimeoutResponse
|
||||||
|
==========================
|
||||||
|
|
||||||
|
Signature
|
||||||
|
---------
|
||||||
|
|
||||||
|
.. includecode2:: /../../akka-http/src/main/scala/akka/http/scaladsl/server/directives/TimeoutDirectives.scala
|
||||||
|
:snippet: withRequestTimeoutResponse
|
||||||
|
|
||||||
|
Description
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Allows customising the ``HttpResponse`` that will be sent to clients in case of a :ref:`request-timeout`.
|
||||||
|
|
||||||
|
See also :ref:`-withRequestTimeout-` or :ref:`-withoutRequestTimeout-` if interested in dynamically changing the timeout
|
||||||
|
for a given route instead.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
Please note that setting handler is inherently racy as the timeout is measured from starting to handle the request
|
||||||
|
to its deadline, thus if the timeout triggers before the ``withRequestTimeoutResponse`` executed it would have emitted
|
||||||
|
the default timeout HttpResponse.
|
||||||
|
|
||||||
|
In practice this can only be a problem with very tight timeouts, so with default settings
|
||||||
|
of request timeouts being measured in seconds it shouldn't be a problem in reality (though certainly a possibility still).
|
||||||
|
|
||||||
|
To learn more about various timeouts in Akka HTTP and how to configure them see :ref:`http-timeouts`.
|
||||||
|
|
||||||
|
Example
|
||||||
|
-------
|
||||||
|
|
||||||
|
.. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/TimeoutDirectivesExamplesSpec.scala
|
||||||
|
:snippet: withRequestTimeoutResponse
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
.. _-withoutRequestTimeout-:
|
||||||
|
|
||||||
|
withoutRequestTimeout
|
||||||
|
=====================
|
||||||
|
|
||||||
|
Signature
|
||||||
|
---------
|
||||||
|
|
||||||
|
.. includecode2:: /../../akka-http/src/main/scala/akka/http/scaladsl/server/directives/TimeoutDirectives.scala
|
||||||
|
:snippet: withoutRequestTimeout
|
||||||
|
|
||||||
|
Description
|
||||||
|
-----------
|
||||||
|
|
||||||
|
This directive enables "late" (during request processing) control over the :ref:`request-timeout` feature in Akka HTTP.
|
||||||
|
|
||||||
|
It is not recommended to turn off request timeouts using this method as it is inherently racy and disabling request timeouts
|
||||||
|
basically turns off the safety net against programming mistakes that it provides.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
Please note that setting the timeout from within a directive is inherently racy (as the "point in time from which
|
||||||
|
we're measuring the timeout" is already in the past (the moment we started handling the request), so if the existing
|
||||||
|
timeout already was triggered before your directive had the chance to change it, an timeout may still be logged.
|
||||||
|
|
||||||
|
For more information about various timeouts in Akka HTTP see :ref:`http-timeouts`.
|
||||||
|
|
||||||
|
Example
|
||||||
|
-------
|
||||||
|
|
||||||
|
.. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/TimeoutDirectivesExamplesSpec.scala
|
||||||
|
:snippet: withoutRequestTimeout
|
||||||
|
|
@ -315,8 +315,11 @@ private[http] object HttpServerBluePrint {
|
||||||
requestEnd.fast.map(_ ⇒ new TimeoutSetup(Deadline.now, schedule(initialTimeout, this), initialTimeout, this))
|
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 " +
|
override def apply(request: HttpRequest) =
|
||||||
|
//#default-request-timeout-httpresponse
|
||||||
|
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!")
|
"to produce a timely response to your request.\r\nPlease try again in a short while!")
|
||||||
|
//#default-request-timeout-httpresponse
|
||||||
|
|
||||||
def clear(): Unit = // best effort timeout cancellation
|
def clear(): Unit = // best effort timeout cancellation
|
||||||
get.fast.foreach(setup ⇒ if (setup.scheduledTask ne null) setup.scheduledTask.cancel())
|
get.fast.foreach(setup ⇒ if (setup.scheduledTask ne null) setup.scheduledTask.cancel())
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||||
|
*/
|
||||||
|
package akka.http.scaladsl.server
|
||||||
|
|
||||||
|
import akka.actor.ActorSystem
|
||||||
|
import akka.http.scaladsl.{ Http, TestUtils }
|
||||||
|
import akka.http.scaladsl.client.RequestBuilding
|
||||||
|
import akka.http.scaladsl.model.{ HttpResponse, HttpRequest }
|
||||||
|
import akka.stream.ActorMaterializer
|
||||||
|
import akka.testkit.AkkaSpec
|
||||||
|
import org.scalatest.concurrent.{ IntegrationPatience, ScalaFutures }
|
||||||
|
import org.scalatest.{ BeforeAndAfterAll, Matchers, WordSpecLike }
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
import scala.concurrent.Await
|
||||||
|
|
||||||
|
/** INTERNAL API - not (yet?) ready for public consuption */
|
||||||
|
private[akka] trait IntegrationRoutingSpec extends WordSpecLike with Matchers with BeforeAndAfterAll
|
||||||
|
with Directives with RequestBuilding
|
||||||
|
with ScalaFutures with IntegrationPatience {
|
||||||
|
|
||||||
|
implicit val system = ActorSystem(AkkaSpec.getCallerName(getClass))
|
||||||
|
implicit val mat = ActorMaterializer()
|
||||||
|
import system.dispatcher
|
||||||
|
|
||||||
|
override protected def afterAll(): Unit = {
|
||||||
|
Await.ready(system.terminate(), 3.seconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
implicit class DSL(request: HttpRequest) {
|
||||||
|
def ~!>(route: Route) = new Prepped(request, route)
|
||||||
|
}
|
||||||
|
|
||||||
|
final case class Prepped(request: HttpRequest, route: Route)
|
||||||
|
|
||||||
|
implicit class Checking(p: Prepped) {
|
||||||
|
def ~!>(checking: HttpResponse ⇒ Unit) = {
|
||||||
|
val (_, host, port) = TestUtils.temporaryServerHostnameAndPort()
|
||||||
|
val binding = Http().bindAndHandle(p.route, host, port)
|
||||||
|
|
||||||
|
try {
|
||||||
|
val targetUri = p.request.uri.withHost(host).withPort(port).withScheme("http")
|
||||||
|
val response = Http().singleRequest(p.request.withUri(targetUri)).futureValue
|
||||||
|
checking(response)
|
||||||
|
} finally binding.flatMap(_.unbind()).futureValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -5,11 +5,13 @@
|
||||||
package akka.http.scaladsl.server
|
package akka.http.scaladsl.server
|
||||||
|
|
||||||
import akka.http.scaladsl.marshallers.xml.ScalaXmlSupport
|
import akka.http.scaladsl.marshallers.xml.ScalaXmlSupport
|
||||||
|
import akka.http.scaladsl.model.{ StatusCodes, HttpResponse }
|
||||||
import akka.http.scaladsl.server.directives.Credentials
|
import akka.http.scaladsl.server.directives.Credentials
|
||||||
import com.typesafe.config.{ ConfigFactory, Config }
|
import com.typesafe.config.{ ConfigFactory, Config }
|
||||||
import akka.actor.ActorSystem
|
import akka.actor.ActorSystem
|
||||||
import akka.stream.ActorMaterializer
|
import akka.stream.ActorMaterializer
|
||||||
import akka.http.scaladsl.Http
|
import akka.http.scaladsl.Http
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
object TestServer extends App {
|
object TestServer extends App {
|
||||||
val testConf: Config = ConfigFactory.parseString("""
|
val testConf: Config = ConfigFactory.parseString("""
|
||||||
|
|
@ -31,7 +33,11 @@ object TestServer extends App {
|
||||||
val bindingFuture = Http().bindAndHandle({
|
val bindingFuture = Http().bindAndHandle({
|
||||||
get {
|
get {
|
||||||
path("") {
|
path("") {
|
||||||
complete(index)
|
withRequestTimeout(1.milli, _ ⇒ HttpResponse(StatusCodes.EnhanceYourCalm,
|
||||||
|
entity = "Unable to serve response within time limit, please enchance your calm.")) {
|
||||||
|
Thread.sleep(1000)
|
||||||
|
complete(index)
|
||||||
|
}
|
||||||
} ~
|
} ~
|
||||||
path("secure") {
|
path("secure") {
|
||||||
authenticateBasicPF("My very secure site", auth) { user ⇒
|
authenticateBasicPF("My very secure site", auth) { user ⇒
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package akka.http.scaladsl.server.directives
|
||||||
|
|
||||||
|
import akka.http.scaladsl.model.{ HttpResponse, StatusCodes }
|
||||||
|
import akka.http.scaladsl.server.IntegrationRoutingSpec
|
||||||
|
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
import scala.concurrent.{ Future, Promise }
|
||||||
|
|
||||||
|
class TimeoutDirectivesSpec extends IntegrationRoutingSpec {
|
||||||
|
|
||||||
|
"Request Timeout" should {
|
||||||
|
"be configurable in routing layer" in {
|
||||||
|
|
||||||
|
val route = path("timeout") {
|
||||||
|
withRequestTimeout(3.seconds) {
|
||||||
|
val response: Future[String] = slowFuture() // very slow
|
||||||
|
complete(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Get("/timeout") ~!> route ~!> { response ⇒
|
||||||
|
import response._
|
||||||
|
|
||||||
|
status should ===(StatusCodes.ServiceUnavailable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"allow mapping the response" in {
|
||||||
|
val timeoutResponse = HttpResponse(StatusCodes.EnhanceYourCalm,
|
||||||
|
entity = "Unable to serve response within time limit, please enchance your calm.")
|
||||||
|
|
||||||
|
val route =
|
||||||
|
path("timeout") {
|
||||||
|
withRequestTimeout(500.millis) {
|
||||||
|
withRequestTimeoutResponse(request ⇒ timeoutResponse) {
|
||||||
|
val response: Future[String] = slowFuture() // very slow
|
||||||
|
complete(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} ~
|
||||||
|
path("equivalent") {
|
||||||
|
// updates timeout and handler at
|
||||||
|
withRequestTimeout(500.millis, request ⇒ timeoutResponse) {
|
||||||
|
val response: Future[String] = slowFuture() // very slow
|
||||||
|
complete(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Get("/timeout") ~!> route ~!> { response ⇒
|
||||||
|
import response._
|
||||||
|
status should ===(StatusCodes.EnhanceYourCalm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def slowFuture(): Future[String] = Promise[String].future
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -23,6 +23,7 @@ trait Directives extends RouteConcatenation
|
||||||
with MethodDirectives
|
with MethodDirectives
|
||||||
with MiscDirectives
|
with MiscDirectives
|
||||||
with ParameterDirectives
|
with ParameterDirectives
|
||||||
|
with TimeoutDirectives
|
||||||
with PathDirectives
|
with PathDirectives
|
||||||
with RangeDirectives
|
with RangeDirectives
|
||||||
with RespondWithDirectives
|
with RespondWithDirectives
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package akka.http.scaladsl.server.directives
|
||||||
|
|
||||||
|
import akka.http.scaladsl.model._
|
||||||
|
import akka.http.scaladsl.model.headers.`Timeout-Access`
|
||||||
|
import akka.http.scaladsl.server.{ Directive, Directive0 }
|
||||||
|
|
||||||
|
import scala.concurrent.duration.Duration
|
||||||
|
|
||||||
|
trait TimeoutDirectives {
|
||||||
|
|
||||||
|
def withoutRequestTimeout: Directive0 =
|
||||||
|
withRequestTimeout(Duration.Inf)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to set a new request timeout and handler (if provided) 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 withRequestTimeout(timeout: Duration): Directive0 =
|
||||||
|
withRequestTimeout(timeout, None)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to set a new request timeout and handler (if provided) 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!
|
||||||
|
*
|
||||||
|
* @param handler optional custom "timeout response" function. If left None, the default timeout HttpResponse will be used.
|
||||||
|
*/
|
||||||
|
def withRequestTimeout(timeout: Duration, handler: HttpRequest ⇒ HttpResponse): Directive0 =
|
||||||
|
withRequestTimeout(timeout, Some(handler))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to set a new request timeout and handler (if provided) 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!
|
||||||
|
*
|
||||||
|
* @param handler optional custom "timeout response" function. If left None, the default timeout HttpResponse will be used.
|
||||||
|
*/
|
||||||
|
def withRequestTimeout(timeout: Duration, handler: Option[HttpRequest ⇒ HttpResponse]): Directive0 =
|
||||||
|
Directive { inner ⇒
|
||||||
|
ctx ⇒
|
||||||
|
ctx.request.header[`Timeout-Access`] match {
|
||||||
|
case Some(t) ⇒
|
||||||
|
handler match {
|
||||||
|
case Some(h) ⇒ t.timeoutAccess.update(timeout, h)
|
||||||
|
case _ ⇒ t.timeoutAccess.updateTimeout(timeout)
|
||||||
|
}
|
||||||
|
case _ ⇒ ctx.log.warning("withRequestTimeout was used in route however no request-timeout is set!")
|
||||||
|
}
|
||||||
|
inner()(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to set a new request 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 withRequestTimeoutResponse(handler: HttpRequest ⇒ HttpResponse): Directive0 =
|
||||||
|
Directive { inner ⇒
|
||||||
|
ctx ⇒
|
||||||
|
ctx.request.header[`Timeout-Access`] match {
|
||||||
|
case Some(t) ⇒ t.timeoutAccess.updateHandler(handler)
|
||||||
|
case _ ⇒ ctx.log.warning("withRequestTimeoutResponse was used in route however no request-timeout is set!")
|
||||||
|
}
|
||||||
|
inner()(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
object TimeoutDirectives extends TimeoutDirectives
|
||||||
Loading…
Add table
Add a link
Reference in a new issue