diff --git a/akka-docs-dev/rst/scala/code/docs/stream/cookbook/RecipeSeq.scala b/akka-docs-dev/rst/scala/code/docs/stream/cookbook/RecipeSeq.scala deleted file mode 100644 index 8be87b7858..0000000000 --- a/akka-docs-dev/rst/scala/code/docs/stream/cookbook/RecipeSeq.scala +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright (C) 2015 Lightbend Inc. - */ -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) - } - } - -} diff --git a/akka-docs/rst/dev/building-akka.rst b/akka-docs/rst/dev/building-akka.rst index ce44ba9150..808f144818 100644 --- a/akka-docs/rst/dev/building-akka.rst +++ b/akka-docs/rst/dev/building-akka.rst @@ -31,8 +31,8 @@ code with ``git pull``:: git pull origin master -sbt - Simple Build Tool -======================= +sbt +=== 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 diff --git a/akka-docs/rst/project/sponsors.rst b/akka-docs/rst/project/sponsors.rst index a7acf286a8..2f99dbbc7d 100644 --- a/akka-docs/rst/project/sponsors.rst +++ b/akka-docs/rst/project/sponsors.rst @@ -4,10 +4,10 @@ Sponsors ======== Lightbend --------- +--------- 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 stack consisting of AKka, Play and Scala. Learn more at `lightbend.com `_. diff --git a/akka-docs/rst/scala/code/docs/CompileOnlySpec.scala b/akka-docs/rst/scala/code/docs/CompileOnlySpec.scala new file mode 100644 index 0000000000..e326923f8c --- /dev/null +++ b/akka-docs/rst/scala/code/docs/CompileOnlySpec.scala @@ -0,0 +1,13 @@ +/* + * Copyright (C) 2009-2016 Lightbend Inc. + */ + +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) = () +} diff --git a/akka-docs/rst/scala/code/docs/http/scaladsl/HttpServerExampleSpec.scala b/akka-docs/rst/scala/code/docs/http/scaladsl/HttpServerExampleSpec.scala index acd403ac11..08f1262838 100644 --- a/akka-docs/rst/scala/code/docs/http/scaladsl/HttpServerExampleSpec.scala +++ b/akka-docs/rst/scala/code/docs/http/scaladsl/HttpServerExampleSpec.scala @@ -12,18 +12,18 @@ import akka.http.scaladsl.model._ import akka.stream.ActorMaterializer import akka.stream.scaladsl.{ Flow, Sink } import akka.testkit.TestActors +import docs.CompileOnlySpec import org.scalatest.{ Matchers, WordSpec } import scala.language.postfixOps import scala.concurrent.{ ExecutionContext, Future } -class HttpServerExampleSpec extends WordSpec with Matchers { +class HttpServerExampleSpec extends WordSpec with Matchers + with CompileOnlySpec { // never actually called val log: LoggingAdapter = null - def compileOnlySpec(body: => Unit) = () - "binding-example" in compileOnlySpec { import akka.http.scaladsl.Http import akka.stream.ActorMaterializer diff --git a/akka-docs/rst/scala/code/docs/http/scaladsl/server/RoutingSpec.scala b/akka-docs/rst/scala/code/docs/http/scaladsl/server/RoutingSpec.scala index 20917f958f..a86ba5bac1 100644 --- a/akka-docs/rst/scala/code/docs/http/scaladsl/server/RoutingSpec.scala +++ b/akka-docs/rst/scala/code/docs/http/scaladsl/server/RoutingSpec.scala @@ -6,6 +6,9 @@ package docs.http.scaladsl.server import akka.http.scaladsl.server.Directives import akka.http.scaladsl.testkit.ScalatestRouteTest +import docs.CompileOnlySpec 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 diff --git a/akka-docs/rst/scala/code/docs/http/scaladsl/server/directives/BasicDirectivesExamplesSpec.scala b/akka-docs/rst/scala/code/docs/http/scaladsl/server/directives/BasicDirectivesExamplesSpec.scala index ae2cca83a1..4818717a64 100644 --- a/akka-docs/rst/scala/code/docs/http/scaladsl/server/directives/BasicDirectivesExamplesSpec.scala +++ b/akka-docs/rst/scala/code/docs/http/scaladsl/server/directives/BasicDirectivesExamplesSpec.scala @@ -260,6 +260,7 @@ class BasicDirectivesExamplesSpec extends RoutingSpec { //#1mapResponse-advanced trait ApiRoutes { protected def system: ActorSystem + private val log = Logging(system, "ApiRoutes") private val NullJsonEntity = HttpEntity(ContentTypes.`application/json`, "{}") @@ -800,5 +801,4 @@ class BasicDirectivesExamplesSpec extends RoutingSpec { //# } - private def compileOnlySpec(block: => Unit) = pending -} +} \ No newline at end of file diff --git a/akka-docs/rst/scala/code/docs/http/scaladsl/server/directives/FileAndResourceDirectivesExamplesSpec.scala b/akka-docs/rst/scala/code/docs/http/scaladsl/server/directives/FileAndResourceDirectivesExamplesSpec.scala index bec1027176..22959f7157 100644 --- a/akka-docs/rst/scala/code/docs/http/scaladsl/server/directives/FileAndResourceDirectivesExamplesSpec.scala +++ b/akka-docs/rst/scala/code/docs/http/scaladsl/server/directives/FileAndResourceDirectivesExamplesSpec.scala @@ -102,5 +102,4 @@ class FileAndResourceDirectivesExamplesSpec extends RoutingSpec { } } - private def compileOnlySpec(block: => Unit) = pending } diff --git a/akka-docs/rst/scala/code/docs/http/scaladsl/server/directives/TimeoutDirectivesExamplesSpec.scala b/akka-docs/rst/scala/code/docs/http/scaladsl/server/directives/TimeoutDirectivesExamplesSpec.scala new file mode 100644 index 0000000000..5536e96d99 --- /dev/null +++ b/akka-docs/rst/scala/code/docs/http/scaladsl/server/directives/TimeoutDirectivesExamplesSpec.scala @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2009-2016 Lightbend Inc. + */ + +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 + +} diff --git a/akka-docs/rst/scala/http/client-side/connection-level.rst b/akka-docs/rst/scala/http/client-side/connection-level.rst index ec98c22686..b29ba71176 100644 --- a/akka-docs/rst/scala/http/client-side/connection-level.rst +++ b/akka-docs/rst/scala/http/client-side/connection-level.rst @@ -65,7 +65,12 @@ Timeouts 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. -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: diff --git a/akka-docs/rst/scala/http/common/index.rst b/akka-docs/rst/scala/http/common/index.rst index 2688e1f75a..bf8c2b34e7 100644 --- a/akka-docs/rst/scala/http/common/index.rst +++ b/akka-docs/rst/scala/http/common/index.rst @@ -18,4 +18,5 @@ which are specific to one side only. unmarshalling de-coding json-support - xml-support \ No newline at end of file + xml-support + timeouts \ No newline at end of file diff --git a/akka-docs/rst/scala/http/common/timeouts.rst b/akka-docs/rst/scala/http/common/timeouts.rst new file mode 100644 index 0000000000..21c512f173 --- /dev/null +++ b/akka-docs/rst/scala/http/common/timeouts.rst @@ -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. \ No newline at end of file diff --git a/akka-docs/rst/scala/http/routing-dsl/directives/alphabetically.rst b/akka-docs/rst/scala/http/routing-dsl/directives/alphabetically.rst index 07654b87b6..87a02f5e8c 100644 --- a/akka-docs/rst/scala/http/routing-dsl/directives/alphabetically.rst +++ b/akka-docs/rst/scala/http/routing-dsl/directives/alphabetically.rst @@ -210,10 +210,14 @@ Directive Description :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:`-validate-` Checks a given condition before running its inner route +:ref:`-withoutRequestTimeout-` Disables :ref:`request timeouts ` for a given route. :ref:`-withExecutionContext-` Runs its inner route with the given alternative ``ExecutionContext`` :ref:`-withMaterializer-` Runs its inner route with the given alternative ``Materializer`` :ref:`-withLog-` Runs its inner route with the given alternative ``LoggingAdapter`` :ref:`-withRangeSupport-` Adds ``Accept-Ranges: bytes`` to responses to GET requests, produces partial responses if the initial request contained a valid ``Range`` header +:ref:`-withRequestTimeout-` Configures the :ref:`request timeouts ` 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`` =========================================== ============================================================================ diff --git a/akka-docs/rst/scala/http/routing-dsl/directives/by-trait.rst b/akka-docs/rst/scala/http/routing-dsl/directives/by-trait.rst index 46552c0f73..1d2b08062d 100644 --- a/akka-docs/rst/scala/http/routing-dsl/directives/by-trait.rst +++ b/akka-docs/rst/scala/http/routing-dsl/directives/by-trait.rst @@ -74,6 +74,9 @@ Directives creating or transforming the response :ref:`BasicDirectives` and :ref:`MiscDirectives` Directives handling or transforming response properties. +:ref:`TimeoutDirectives` + Configure request timeouts and automatic timeout responses. + List of predefined directives by trait -------------------------------------- @@ -104,3 +107,4 @@ List of predefined directives by trait scheme-directives/index security-directives/index websocket-directives/index + timeout-directives/index diff --git a/akka-docs/rst/scala/http/routing-dsl/directives/timeout-directives/index.rst b/akka-docs/rst/scala/http/routing-dsl/directives/timeout-directives/index.rst new file mode 100644 index 0000000000..e19f58c365 --- /dev/null +++ b/akka-docs/rst/scala/http/routing-dsl/directives/timeout-directives/index.rst @@ -0,0 +1,11 @@ +.. _TimeoutDirectives: + +TimeoutDirectives +================= + +.. toctree:: + :maxdepth: 1 + + withRequestTimeout + withoutRequestTimeout + withRequestTimeoutResponse \ No newline at end of file diff --git a/akka-docs/rst/scala/http/routing-dsl/directives/timeout-directives/withRequestTimeout.rst b/akka-docs/rst/scala/http/routing-dsl/directives/timeout-directives/withRequestTimeout.rst new file mode 100644 index 0000000000..62ccbb227d --- /dev/null +++ b/akka-docs/rst/scala/http/routing-dsl/directives/timeout-directives/withRequestTimeout.rst @@ -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 diff --git a/akka-docs/rst/scala/http/routing-dsl/directives/timeout-directives/withRequestTimeoutResponse.rst b/akka-docs/rst/scala/http/routing-dsl/directives/timeout-directives/withRequestTimeoutResponse.rst new file mode 100644 index 0000000000..6fd76faa56 --- /dev/null +++ b/akka-docs/rst/scala/http/routing-dsl/directives/timeout-directives/withRequestTimeoutResponse.rst @@ -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 diff --git a/akka-docs/rst/scala/http/routing-dsl/directives/timeout-directives/withoutRequestTimeout.rst b/akka-docs/rst/scala/http/routing-dsl/directives/timeout-directives/withoutRequestTimeout.rst new file mode 100644 index 0000000000..b9ef619443 --- /dev/null +++ b/akka-docs/rst/scala/http/routing-dsl/directives/timeout-directives/withoutRequestTimeout.rst @@ -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 diff --git a/akka-http-core/src/main/scala/akka/http/impl/engine/server/HttpServerBluePrint.scala b/akka-http-core/src/main/scala/akka/http/impl/engine/server/HttpServerBluePrint.scala index 81ab837c88..b16cfe86d2 100644 --- a/akka-http-core/src/main/scala/akka/http/impl/engine/server/HttpServerBluePrint.scala +++ b/akka-http-core/src/main/scala/akka/http/impl/engine/server/HttpServerBluePrint.scala @@ -315,8 +315,11 @@ private[http] object HttpServerBluePrint { 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!") + //#default-request-timeout-httpresponse def clear(): Unit = // best effort timeout cancellation get.fast.foreach(setup ⇒ if (setup.scheduledTask ne null) setup.scheduledTask.cancel()) diff --git a/akka-http-tests/src/test/scala/akka/http/scaladsl/server/IntegrationRoutingSpec.scala b/akka-http-tests/src/test/scala/akka/http/scaladsl/server/IntegrationRoutingSpec.scala new file mode 100644 index 0000000000..a931209d7f --- /dev/null +++ b/akka-http-tests/src/test/scala/akka/http/scaladsl/server/IntegrationRoutingSpec.scala @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2009-2016 Lightbend Inc. + */ +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 + } + } + +} diff --git a/akka-http-tests/src/test/scala/akka/http/scaladsl/server/TestServer.scala b/akka-http-tests/src/test/scala/akka/http/scaladsl/server/TestServer.scala index 86a404fe80..48fa0d9a47 100644 --- a/akka-http-tests/src/test/scala/akka/http/scaladsl/server/TestServer.scala +++ b/akka-http-tests/src/test/scala/akka/http/scaladsl/server/TestServer.scala @@ -5,11 +5,13 @@ package akka.http.scaladsl.server import akka.http.scaladsl.marshallers.xml.ScalaXmlSupport +import akka.http.scaladsl.model.{ StatusCodes, HttpResponse } import akka.http.scaladsl.server.directives.Credentials import com.typesafe.config.{ ConfigFactory, Config } import akka.actor.ActorSystem import akka.stream.ActorMaterializer import akka.http.scaladsl.Http +import scala.concurrent.duration._ object TestServer extends App { val testConf: Config = ConfigFactory.parseString(""" @@ -31,7 +33,11 @@ object TestServer extends App { val bindingFuture = Http().bindAndHandle({ get { 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") { authenticateBasicPF("My very secure site", auth) { user ⇒ diff --git a/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/TimeoutDirectivesSpec.scala b/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/TimeoutDirectivesSpec.scala new file mode 100644 index 0000000000..8a324f3bb9 --- /dev/null +++ b/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/TimeoutDirectivesSpec.scala @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2009-2016 Lightbend Inc. + */ + +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 + +} diff --git a/akka-http/src/main/scala/akka/http/scaladsl/server/Directives.scala b/akka-http/src/main/scala/akka/http/scaladsl/server/Directives.scala index 97f356a2d1..b5af98cfb5 100644 --- a/akka-http/src/main/scala/akka/http/scaladsl/server/Directives.scala +++ b/akka-http/src/main/scala/akka/http/scaladsl/server/Directives.scala @@ -23,6 +23,7 @@ trait Directives extends RouteConcatenation with MethodDirectives with MiscDirectives with ParameterDirectives + with TimeoutDirectives with PathDirectives with RangeDirectives with RespondWithDirectives diff --git a/akka-http/src/main/scala/akka/http/scaladsl/server/directives/TimeoutDirectives.scala b/akka-http/src/main/scala/akka/http/scaladsl/server/directives/TimeoutDirectives.scala new file mode 100644 index 0000000000..5b0f1fead0 --- /dev/null +++ b/akka-http/src/main/scala/akka/http/scaladsl/server/directives/TimeoutDirectives.scala @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2009-2016 Lightbend Inc. + */ + +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 \ No newline at end of file