From 72f8544efd70416dc929fd33434cb7487664beca Mon Sep 17 00:00:00 2001 From: Michal Sitko Date: Tue, 28 Jun 2016 12:14:44 +0200 Subject: [PATCH] +htp #18929 add withSizeLimit directive (#20760) withSizeLimit and withoutSizeLimit directives added --- .../MiscDirectivesExamplesTest.java | 64 ++++++++++++++ .../routing-dsl/directives/alphabetically.rst | 2 + .../directives/misc-directives/index.rst | 2 + .../misc-directives/withSizeLimit.rst | 20 +++++ .../misc-directives/withoutSizeLimit.rst | 19 +++++ .../MiscDirectivesExamplesSpec.scala | 84 ++++++++++++++++++ .../routing-dsl/directives/alphabetically.rst | 2 + .../directives/misc-directives/index.rst | 4 +- .../misc-directives/withSizeLimit.rst | 40 +++++++++ .../misc-directives/withoutSizeLimit.rst | 26 ++++++ .../server/directives/MiscDirectivesTest.java | 24 ++++++ .../server/WithoutSizeLimitSpec.scala | 72 ++++++++++++++++ .../directives/MiscDirectivesSpec.scala | 85 +++++++++++++++++++ .../server/directives/MiscDirectives.scala | 19 +++++ .../server/directives/MiscDirectives.scala | 25 ++++++ 15 files changed, 487 insertions(+), 1 deletion(-) create mode 100644 akka-docs/rst/java/code/docs/http/javadsl/server/directives/MiscDirectivesExamplesTest.java create mode 100644 akka-docs/rst/java/http/routing-dsl/directives/misc-directives/withSizeLimit.rst create mode 100644 akka-docs/rst/java/http/routing-dsl/directives/misc-directives/withoutSizeLimit.rst create mode 100644 akka-docs/rst/scala/http/routing-dsl/directives/misc-directives/withSizeLimit.rst create mode 100644 akka-docs/rst/scala/http/routing-dsl/directives/misc-directives/withoutSizeLimit.rst create mode 100644 akka-http-tests/src/test/scala/akka/http/scaladsl/server/WithoutSizeLimitSpec.scala diff --git a/akka-docs/rst/java/code/docs/http/javadsl/server/directives/MiscDirectivesExamplesTest.java b/akka-docs/rst/java/code/docs/http/javadsl/server/directives/MiscDirectivesExamplesTest.java new file mode 100644 index 00000000000..0f36352b76a --- /dev/null +++ b/akka-docs/rst/java/code/docs/http/javadsl/server/directives/MiscDirectivesExamplesTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2016-2016 Lightbend Inc. + */ +package docs.http.javadsl.server.directives; + +import akka.http.javadsl.model.HttpRequest; +import akka.http.javadsl.model.StatusCodes; +import akka.http.javadsl.server.Route; +import akka.http.javadsl.server.Unmarshaller; +import akka.http.javadsl.testkit.JUnitRouteTest; +import org.junit.Test; + +import java.util.Arrays; +import java.util.function.Function; + +public class MiscDirectivesExamplesTest extends JUnitRouteTest { + + @Test + public void testWithSizeLimit() { + //#withSizeLimitExample + final Route route = withSizeLimit(500, () -> + entity(Unmarshaller.entityToString(), (entity) -> + complete("ok") + ) + ); + + Function withEntityOfSize = (sizeLimit) -> { + char[] charArray = new char[sizeLimit]; + Arrays.fill(charArray, '0'); + return HttpRequest.POST("/").withEntity(new String(charArray)); + }; + + // tests: + testRoute(route).run(withEntityOfSize.apply(500)) + .assertStatusCode(StatusCodes.OK); + + testRoute(route).run(withEntityOfSize.apply(501)) + .assertStatusCode(StatusCodes.BAD_REQUEST); + //#withSizeLimitExample + } + + @Test + public void testWithoutSizeLimit() { + //#withoutSizeLimitExample + final Route route = withoutSizeLimit(() -> + entity(Unmarshaller.entityToString(), (entity) -> + complete("ok") + ) + ); + + Function withEntityOfSize = (sizeLimit) -> { + char[] charArray = new char[sizeLimit]; + Arrays.fill(charArray, '0'); + return HttpRequest.POST("/").withEntity(new String(charArray)); + }; + + // tests: + // will work even if you have configured akka.http.parsing.max-content-length = 500 + testRoute(route).run(withEntityOfSize.apply(501)) + .assertStatusCode(StatusCodes.OK); + //#withoutSizeLimitExample + } + +} diff --git a/akka-docs/rst/java/http/routing-dsl/directives/alphabetically.rst b/akka-docs/rst/java/http/routing-dsl/directives/alphabetically.rst index 93a10fbd12f..53188a52c66 100644 --- a/akka-docs/rst/java/http/routing-dsl/directives/alphabetically.rst +++ b/akka-docs/rst/java/http/routing-dsl/directives/alphabetically.rst @@ -139,6 +139,7 @@ Directive Description :ref:`-uploadedFile-java-` Streams one uploaded file from a multipart request to a file on disk :ref:`-validate-java-` Checks a given condition before running its inner route :ref:`-withoutRequestTimeout-java-` Disables :ref:`request timeouts ` for a given route. +:ref:`-withoutSizeLimit-java-` Skips request entity size check :ref:`-withExecutionContext-java-` Runs its inner route with the given alternative ``ExecutionContext`` :ref:`-withMaterializer-java-` Runs its inner route with the given alternative ``Materializer`` :ref:`-withLog-java-` Runs its inner route with the given alternative ``LoggingAdapter`` @@ -146,5 +147,6 @@ Directive Description :ref:`-withRequestTimeout-java-` Configures the :ref:`request timeouts ` for a given route. :ref:`-withRequestTimeoutResponse-java-` Prepares the ``HttpResponse`` that is emitted if a request timeout is triggered. ``RequestContext => RequestContext`` function :ref:`-withSettings-java-` Runs its inner route with the given alternative ``RoutingSettings`` +:ref:`-withSizeLimit-java-` Applies request entity size check ================================================ ============================================================================ diff --git a/akka-docs/rst/java/http/routing-dsl/directives/misc-directives/index.rst b/akka-docs/rst/java/http/routing-dsl/directives/misc-directives/index.rst index 861e527ef65..997731d05b6 100644 --- a/akka-docs/rst/java/http/routing-dsl/directives/misc-directives/index.rst +++ b/akka-docs/rst/java/http/routing-dsl/directives/misc-directives/index.rst @@ -12,3 +12,5 @@ MiscDirectives requestEntityPresent selectPreferredLanguage validate + withoutSizeLimit + withSizeLimit diff --git a/akka-docs/rst/java/http/routing-dsl/directives/misc-directives/withSizeLimit.rst b/akka-docs/rst/java/http/routing-dsl/directives/misc-directives/withSizeLimit.rst new file mode 100644 index 00000000000..faef2f3ebdf --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/misc-directives/withSizeLimit.rst @@ -0,0 +1,20 @@ +.. _-withSizeLimit-java-: + +withSizeLimit +=============== + +Description +----------- +Fails the stream with ``EntityStreamSizeException`` if its request entity size exceeds given limit. Limit given +as parameter overrides limit configured with ``akka.http.parsing.max-content-length``. + +The whole mechanism of entity size checking is intended to prevent certain Denial-of-Service attacks. +So suggested setup is to have ``akka.http.parsing.max-content-length`` relatively low and use ``withSizeLimit`` +directive for endpoints which expects bigger entities. + +See also :ref:`-withoutSizeLimit-java-` for skipping request entity size check. + +Example +------- + +.. includecode:: ../../../../code/docs/http/javadsl/server/directives/MiscDirectivesExamplesTest.java#withSizeLimitExample diff --git a/akka-docs/rst/java/http/routing-dsl/directives/misc-directives/withoutSizeLimit.rst b/akka-docs/rst/java/http/routing-dsl/directives/misc-directives/withoutSizeLimit.rst new file mode 100644 index 00000000000..de16f30131f --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/misc-directives/withoutSizeLimit.rst @@ -0,0 +1,19 @@ +.. _-withoutSizeLimit-java-: + +withoutSizeLimit +================ + +Description +----------- +Skips request entity size verification. + +The whole mechanism of entity size checking is intended to prevent certain Denial-of-Service attacks. +So suggested setup is to have ``akka.http.parsing.max-content-length`` relatively low and use ``withoutSizeLimit`` +directive just for endpoints for which size verification should not be performed. + +See also :ref:`-withSizeLimit-java-` for setting request entity size limit. + +Example +------- + +.. includecode:: ../../../../code/docs/http/javadsl/server/directives/MiscDirectivesExamplesTest.java#withSizeLimitExample diff --git a/akka-docs/rst/scala/code/docs/http/scaladsl/server/directives/MiscDirectivesExamplesSpec.scala b/akka-docs/rst/scala/code/docs/http/scaladsl/server/directives/MiscDirectivesExamplesSpec.scala index dfa22e6eca9..49ca6be86bf 100644 --- a/akka-docs/rst/scala/code/docs/http/scaladsl/server/directives/MiscDirectivesExamplesSpec.scala +++ b/akka-docs/rst/scala/code/docs/http/scaladsl/server/directives/MiscDirectivesExamplesSpec.scala @@ -22,6 +22,7 @@ class MiscDirectivesExamplesSpec extends RoutingSpec { responseAs[String] shouldEqual "Client's ip is 192.168.3.12" } } + "rejectEmptyResponse-example" in { val route = rejectEmptyResponse { path("even" / IntNumber) { i => @@ -42,6 +43,7 @@ class MiscDirectivesExamplesSpec extends RoutingSpec { responseAs[String] shouldEqual "Number 28 is even." } } + "requestEntityEmptyPresent-example" in { val route = requestEntityEmpty { @@ -59,6 +61,7 @@ class MiscDirectivesExamplesSpec extends RoutingSpec { responseAs[String] shouldEqual "request entity empty" } } + "selectPreferredLanguage-example" in { val request = Get() ~> `Accept-Language`( Language("en-US"), @@ -78,6 +81,7 @@ class MiscDirectivesExamplesSpec extends RoutingSpec { } } ~> check { responseAs[String] shouldEqual "de-DE" } } + "validate-example" in { val route = extractUri { uri => @@ -94,4 +98,84 @@ class MiscDirectivesExamplesSpec extends RoutingSpec { rejection shouldEqual ValidationRejection("Path too long: '/abcdefghijkl'", None) } } + + "withSizeLimit-example" in { + val route = withSizeLimit(500) { + entity(as[String]) { _ ⇒ + complete(HttpResponse()) + } + } + + // tests: + def entityOfSize(size: Int) = + HttpEntity(ContentTypes.`text/plain(UTF-8)`, "0" * size) + + Post("/abc", entityOfSize(500)) ~> route ~> check { + status shouldEqual StatusCodes.OK + } + + Post("/abc", entityOfSize(501)) ~> Route.seal(route) ~> check { + status shouldEqual StatusCodes.BadRequest + } + + } + + "withSizeLimit-execution-moment-example" in { + val route = withSizeLimit(500) { + complete(HttpResponse()) + } + + // tests: + def entityOfSize(size: Int) = + HttpEntity(ContentTypes.`text/plain(UTF-8)`, "0" * size) + + Post("/abc", entityOfSize(500)) ~> route ~> check { + status shouldEqual StatusCodes.OK + } + + Post("/abc", entityOfSize(501)) ~> route ~> check { + status shouldEqual StatusCodes.OK + } + } + + "withSizeLimit-nested-example" in { + val route = + withSizeLimit(500) { + withSizeLimit(800) { + entity(as[String]) { _ ⇒ + complete(HttpResponse()) + } + } + } + + // tests: + def entityOfSize(size: Int) = + HttpEntity(ContentTypes.`text/plain(UTF-8)`, "0" * size) + Post("/abc", entityOfSize(800)) ~> route ~> check { + status shouldEqual StatusCodes.OK + } + + Post("/abc", entityOfSize(801)) ~> Route.seal(route) ~> check { + status shouldEqual StatusCodes.BadRequest + } + } + + "withoutSizeLimit-example" in { + val route = + withoutSizeLimit { + entity(as[String]) { _ ⇒ + complete(HttpResponse()) + } + } + + // tests: + def entityOfSize(size: Int) = + HttpEntity(ContentTypes.`text/plain(UTF-8)`, "0" * size) + + // will work even if you have configured akka.http.parsing.max-content-length = 500 + Post("/abc", entityOfSize(501)) ~> route ~> check { + status shouldEqual StatusCodes.OK + } + } + } 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 9093b04a759..abe414440fc 100644 --- a/akka-docs/rst/scala/http/routing-dsl/directives/alphabetically.rst +++ b/akka-docs/rst/scala/http/routing-dsl/directives/alphabetically.rst @@ -218,6 +218,7 @@ Directive Description :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:`-withoutSizeLimit-` Skips request entity size check :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`` @@ -227,4 +228,5 @@ Directive Description :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:`-withSizeLimit-` Applies request entity size check =========================================== ============================================================================ diff --git a/akka-docs/rst/scala/http/routing-dsl/directives/misc-directives/index.rst b/akka-docs/rst/scala/http/routing-dsl/directives/misc-directives/index.rst index 73837a23508..9857ed19237 100644 --- a/akka-docs/rst/scala/http/routing-dsl/directives/misc-directives/index.rst +++ b/akka-docs/rst/scala/http/routing-dsl/directives/misc-directives/index.rst @@ -11,4 +11,6 @@ MiscDirectives requestEntityEmpty requestEntityPresent selectPreferredLanguage - validate \ No newline at end of file + validate + withoutSizeLimit + withSizeLimit \ No newline at end of file diff --git a/akka-docs/rst/scala/http/routing-dsl/directives/misc-directives/withSizeLimit.rst b/akka-docs/rst/scala/http/routing-dsl/directives/misc-directives/withSizeLimit.rst new file mode 100644 index 00000000000..7f944e5fab3 --- /dev/null +++ b/akka-docs/rst/scala/http/routing-dsl/directives/misc-directives/withSizeLimit.rst @@ -0,0 +1,40 @@ +.. _-withSizeLimit-: + +withSizeLimit +=============== + +Signature +--------- + +.. includecode2:: /../../akka-http/src/main/scala/akka/http/scaladsl/server/directives/MiscDirectives.scala + :snippet: withSizeLimit + +Description +----------- +Fails the stream with ``EntityStreamSizeException`` if its request entity size exceeds given limit. Limit given +as parameter overrides limit configured with ``akka.http.parsing.max-content-length``. + +The whole mechanism of entity size checking is intended to prevent certain Denial-of-Service attacks. +So suggested setup is to have ``akka.http.parsing.max-content-length`` relatively low and use ``withSizeLimit`` +directive for endpoints which expects bigger entities. + +See also :ref:`-withoutSizeLimit-` for skipping request entity size check. + +Examples +-------- + +.. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/MiscDirectivesExamplesSpec.scala + :snippet: withSizeLimit-example + +Beware that request entity size check is executed when entity is consumed. Therefore in the following example +even request with entity greater than argument to ``withSizeLimit`` will succeed (because this route +does not consume entity): + +.. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/MiscDirectivesExamplesSpec.scala + :snippet: withSizeLimit-execution-moment-example + +Directive ``withSizeLimit`` is implemented in terms of ``HttpEntity.withSizeLimit`` which means that in case of +nested ``withSizeLimit`` directives the innermost is applied: + +.. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/MiscDirectivesExamplesSpec.scala + :snippet: withSizeLimit-nested-example diff --git a/akka-docs/rst/scala/http/routing-dsl/directives/misc-directives/withoutSizeLimit.rst b/akka-docs/rst/scala/http/routing-dsl/directives/misc-directives/withoutSizeLimit.rst new file mode 100644 index 00000000000..8833e0e90c3 --- /dev/null +++ b/akka-docs/rst/scala/http/routing-dsl/directives/misc-directives/withoutSizeLimit.rst @@ -0,0 +1,26 @@ +.. _-withoutSizeLimit-: + +withoutSizeLimit +================ + +Signature +--------- + +.. includecode2:: /../../akka-http/src/main/scala/akka/http/scaladsl/server/directives/MiscDirectives.scala + :snippet: withoutSizeLimit + +Description +----------- +Skips request entity size verification. + +The whole mechanism of entity size checking is intended to prevent certain Denial-of-Service attacks. +So suggested setup is to have ``akka.http.parsing.max-content-length`` relatively low and use ``withoutSizeLimit`` +directive just for endpoints for which size verification should not be performed. + +See also :ref:`-withSizeLimit-` for setting request entity size limit. + +Example +------- + +.. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/MiscDirectivesExamplesSpec.scala + :snippet: withoutSizeLimit-example diff --git a/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/MiscDirectivesTest.java b/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/MiscDirectivesTest.java index aded1cbad12..12002d791c5 100644 --- a/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/MiscDirectivesTest.java +++ b/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/MiscDirectivesTest.java @@ -11,12 +11,14 @@ import akka.http.javadsl.model.headers.RawHeader; import akka.http.javadsl.model.headers.XForwardedFor; import akka.http.javadsl.model.headers.XRealIp; +import akka.http.javadsl.server.Unmarshaller; import akka.http.javadsl.testkit.JUnitRouteTest; import akka.http.javadsl.testkit.TestRoute; import org.junit.Test; import java.net.InetAddress; import java.net.UnknownHostException; +import java.util.Arrays; public class MiscDirectivesTest extends JUnitRouteTest { @@ -73,4 +75,26 @@ public void testClientIpExtraction() throws UnknownHostException { .assertStatusCode(StatusCodes.NOT_FOUND); } + @Test + public void testWithSizeLimit() { + TestRoute route = testRoute(withSizeLimit(500, () -> + entity(Unmarshaller.entityToString(), (entity) -> complete("ok")) + )); + + route + .run(withEntityOfSize(500)) + .assertStatusCode(StatusCodes.OK); + + route + .run(withEntityOfSize(501)) + .assertStatusCode(StatusCodes.BAD_REQUEST); + + } + + private HttpRequest withEntityOfSize(int sizeLimit) { + char[] charArray = new char[sizeLimit]; + Arrays.fill(charArray, '0'); + return HttpRequest.POST("/").withEntity(new String(charArray)); + } + } diff --git a/akka-http-tests/src/test/scala/akka/http/scaladsl/server/WithoutSizeLimitSpec.scala b/akka-http-tests/src/test/scala/akka/http/scaladsl/server/WithoutSizeLimitSpec.scala new file mode 100644 index 00000000000..5cea8ab9a53 --- /dev/null +++ b/akka-http-tests/src/test/scala/akka/http/scaladsl/server/WithoutSizeLimitSpec.scala @@ -0,0 +1,72 @@ +/** + * Copyright (C) 2009-2016 Lightbend Inc. + */ + +package akka.http.scaladsl.server + +import akka.actor.ActorSystem +import akka.http.scaladsl.client.RequestBuilding +import akka.http.scaladsl.model._ +import akka.http.scaladsl.server.Directives._ +import akka.http.scaladsl.{ Http, TestUtils } +import akka.stream.ActorMaterializer +import com.typesafe.config.{ Config, ConfigFactory } +import org.scalatest.{ BeforeAndAfterAll, Matchers, WordSpec } + +import scala.concurrent.Await +import scala.concurrent.duration._ + +class WithoutSizeLimitSpec extends WordSpec with Matchers with RequestBuilding with BeforeAndAfterAll { + val testConf: Config = ConfigFactory.parseString(""" + akka.loggers = ["akka.testkit.TestEventListener"] + akka.loglevel = ERROR + akka.stdout-loglevel = ERROR + akka.http.parsing.max-content-length = 800""") + implicit val system = ActorSystem(getClass.getSimpleName, testConf) + import system.dispatcher + implicit val materializer = ActorMaterializer() + + "the withoutSizeLimit directive" should { + "accept entities bigger than configured with akka.http.parsing.max-content-length" in { + val route = + path("noDirective") { + post { + entity(as[String]) { _ ⇒ + complete(HttpEntity(ContentTypes.`text/html(UTF-8)`, "

Say hello to akka-http

")) + } + } + } ~ + path("withoutSizeLimit") { + post { + withoutSizeLimit { + entity(as[String]) { _ ⇒ + complete(HttpEntity(ContentTypes.`text/html(UTF-8)`, "

Say hello to akka-http

")) + } + } + } + } + + val (_, hostName, port) = TestUtils.temporaryServerHostnameAndPort() + + val future = for { + _ ← Http().bindAndHandle(route, hostName, port) + + requestToNoDirective = Post(s"http://$hostName:$port/noDirective", entityOfSize(801)) + responseWithoutDirective ← Http().singleRequest(requestToNoDirective) + _ = responseWithoutDirective.status shouldEqual StatusCodes.BadRequest + + requestToDirective = Post(s"http://$hostName:$port/withoutSizeLimit", entityOfSize(801)) + responseWithDirective ← Http().singleRequest(requestToDirective) + } yield responseWithDirective + + val response = Await.result(future, 5 seconds) + response.status shouldEqual StatusCodes.OK + } + } + + override def afterAll() = { + system.terminate + } + + private def entityOfSize(size: Int) = HttpEntity(ContentTypes.`text/plain(UTF-8)`, "0" * size) +} diff --git a/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/MiscDirectivesSpec.scala b/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/MiscDirectivesSpec.scala index 58a6c10b44c..ed22f1feaf0 100644 --- a/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/MiscDirectivesSpec.scala +++ b/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/MiscDirectivesSpec.scala @@ -65,6 +65,89 @@ class MiscDirectivesSpec extends RoutingSpec { } } + "the withSizeLimit directive" should { + "not apply if entity is not consumed" in { + val route = withSizeLimit(500) { completeOk } + + Post("/abc", entityOfSize(500)) ~> route ~> check { + status shouldEqual StatusCodes.OK + } + + Post("/abc", entityOfSize(501)) ~> route ~> check { + status shouldEqual StatusCodes.OK + } + } + + "apply if entity is consumed" in { + val route = withSizeLimit(500) { + entity(as[String]) { _ ⇒ + completeOk + } + } + + Post("/abc", entityOfSize(500)) ~> route ~> check { + status shouldEqual StatusCodes.OK + } + + Post("/abc", entityOfSize(501)) ~> Route.seal(route) ~> check { + status shouldEqual StatusCodes.BadRequest + } + } + + "properly handle nested directives by applying innermost `withSizeLimit` directive" in { + val route = + withSizeLimit(500) { + withSizeLimit(800) { + entity(as[String]) { _ ⇒ + completeOk + } + } + } + + Post("/abc", entityOfSize(800)) ~> route ~> check { + status shouldEqual StatusCodes.OK + } + + Post("/abc", entityOfSize(801)) ~> Route.seal(route) ~> check { + status shouldEqual StatusCodes.BadRequest + } + + val route2 = + withSizeLimit(500) { + withSizeLimit(400) { + entity(as[String]) { _ ⇒ + completeOk + } + } + } + + Post("/abc", entityOfSize(400)) ~> route2 ~> check { + status shouldEqual StatusCodes.OK + } + + Post("/abc", entityOfSize(401)) ~> Route.seal(route2) ~> check { + status shouldEqual StatusCodes.BadRequest + } + } + } + + "the withoutSizeLimit directive" should { + "skip request entity size verification" in { + val route = + withSizeLimit(500) { + withoutSizeLimit { + entity(as[String]) { _ ⇒ + completeOk + } + } + } + + Post("/abc", entityOfSize(501)) ~> route ~> check { + status shouldEqual StatusCodes.OK + } + } + } + implicit class AddStringToIn(acceptLanguageHeaderString: String) { def test(body: ((String*) ⇒ String) ⇒ Unit): Unit = s"properly handle `$acceptLanguageHeaderString`" in { @@ -88,4 +171,6 @@ class MiscDirectivesSpec extends RoutingSpec { } def remoteAddress(ip: String) = RemoteAddress(InetAddress.getByName(ip)) + + private def entityOfSize(size: Int) = HttpEntity(ContentTypes.`text/plain(UTF-8)`, "0" * size) } diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/directives/MiscDirectives.scala b/akka-http/src/main/scala/akka/http/javadsl/server/directives/MiscDirectives.scala index f7a35637fc2..338f738ee6f 100644 --- a/akka-http/src/main/scala/akka/http/javadsl/server/directives/MiscDirectives.scala +++ b/akka-http/src/main/scala/akka/http/javadsl/server/directives/MiscDirectives.scala @@ -61,6 +61,25 @@ abstract class MiscDirectives extends MethodDirectives { D.rejectEmptyResponse { inner.get.delegate } } + /** + * Fails the stream with [[akka.http.scaladsl.model.EntityStreamSizeException]] if its request entity size exceeds + * given limit. Limit given as parameter overrides limit configured with ``akka.http.parsing.max-content-length``. + * + * Beware that request entity size check is executed when entity is consumed. + */ + def withSizeLimit(maxBytes: Long, inner: Supplier[Route]): Route = RouteAdapter { + D.withSizeLimit(maxBytes) { inner.get.delegate } + } + + /** + * Disables the size limit (configured by `akka.http.parsing.max-content-length` by default) checking on the incoming + * [[akka.http.javadsl.model.HttpRequest]] entity. + * Can be useful when handling arbitrarily large data uploads in specific parts of your routes. + */ + def withoutSizeLimit(inner: Supplier[Route]): Route = RouteAdapter { + D.withoutSizeLimit { inner.get.delegate } + } + /** * Inspects the request's `Accept-Language` header and determines, * which of the given language alternatives is preferred by the client. diff --git a/akka-http/src/main/scala/akka/http/scaladsl/server/directives/MiscDirectives.scala b/akka-http/src/main/scala/akka/http/scaladsl/server/directives/MiscDirectives.scala index 8d33d10b0ef..9f70c1f116b 100644 --- a/akka-http/src/main/scala/akka/http/scaladsl/server/directives/MiscDirectives.scala +++ b/akka-http/src/main/scala/akka/http/scaladsl/server/directives/MiscDirectives.scala @@ -6,6 +6,7 @@ package akka.http.scaladsl.server package directives import akka.http.scaladsl.model._ +import akka.http.scaladsl.server.directives.BasicDirectives._ import headers._ /** @@ -71,6 +72,27 @@ trait MiscDirectives { BasicDirectives.extractRequest.map { request ⇒ LanguageNegotiator(request.headers).pickLanguage(first :: List(more: _*)) getOrElse first } + + /** + * Fails the stream with [[akka.http.scaladsl.model.EntityStreamSizeException]] if its request entity size exceeds + * given limit. Limit given as parameter overrides limit configured with `akka.http.parsing.max-content-length`. + * + * Beware that request entity size check is executed when entity is consumed. + * + * @group misc + */ + def withSizeLimit(maxBytes: Long): Directive0 = + mapRequestContext(_.mapRequest(_.mapEntity(_.withSizeLimit(maxBytes)))) + + /** + * + * Disables the size limit (configured by `akka.http.parsing.max-content-length` by default) checking on the incoming + * [[HttpRequest]] entity. + * Can be useful when handling arbitrarily large data uploads in specific parts of your routes. + * + * @group misc + */ + def withoutSizeLimit: Directive0 = MiscDirectives._withoutSizeLimit } object MiscDirectives extends MiscDirectives { @@ -95,4 +117,7 @@ object MiscDirectives extends MiscDirectives { case Complete(response) if response.entity.isKnownEmpty ⇒ Rejected(Nil) case x ⇒ x } + + private val _withoutSizeLimit: Directive0 = + mapRequestContext(_.mapRequest(_.mapEntity(_.withoutSizeLimit))) }