From 15c77e339230cc58be157a92a6eb0758b70606c6 Mon Sep 17 00:00:00 2001 From: "Richard S. Imaoka" Date: Mon, 11 Jul 2016 00:00:34 +0900 Subject: [PATCH] Document HTTP custom method (#20508) --- .../CustomHttpMethodExamplesTest.java | 76 +++++++++++++++++++ .../method-directives/extractMethod.rst | 6 ++ .../directives/CustomHttpMethodSpec.scala | 56 ++++++++++++++ .../method-directives/extractMethod.rst | 10 +++ .../akka/http/javadsl/model/HttpMethod.java | 8 ++ .../akka/http/javadsl/model/HttpMethods.java | 13 ++++ .../model/RequestEntityAcceptance.java | 14 ++++ .../model/RequestEntityAcceptances.java | 14 ++++ .../akka/http/scaladsl/model/HttpMethod.scala | 3 +- project/MiMa.scala | 3 + 10 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 akka-docs/rst/java/code/docs/http/javadsl/server/directives/CustomHttpMethodExamplesTest.java create mode 100644 akka-docs/rst/scala/code/docs/http/scaladsl/server/directives/CustomHttpMethodSpec.scala create mode 100644 akka-http-core/src/main/java/akka/http/javadsl/model/RequestEntityAcceptance.java create mode 100644 akka-http-core/src/main/java/akka/http/javadsl/model/RequestEntityAcceptances.java diff --git a/akka-docs/rst/java/code/docs/http/javadsl/server/directives/CustomHttpMethodExamplesTest.java b/akka-docs/rst/java/code/docs/http/javadsl/server/directives/CustomHttpMethodExamplesTest.java new file mode 100644 index 0000000000..8631efbac0 --- /dev/null +++ b/akka-docs/rst/java/code/docs/http/javadsl/server/directives/CustomHttpMethodExamplesTest.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2015-2016 Lightbend Inc. + */ +package docs.http.javadsl.server.directives; + +import akka.NotUsed; +import akka.actor.ActorSystem; +import akka.event.LoggingAdapter; +import akka.event.NoLogging; +import akka.http.javadsl.ConnectHttp; +import akka.http.javadsl.Http; +import akka.http.javadsl.ServerBinding; +import akka.http.javadsl.model.*; +import akka.http.javadsl.server.Route; +import akka.http.javadsl.settings.ParserSettings; +import akka.http.javadsl.settings.ServerSettings; +import akka.http.javadsl.testkit.JUnitRouteTest; +import akka.stream.Materializer; +import akka.stream.javadsl.Flow; +import org.junit.Test; + +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; + +import static akka.http.javadsl.model.HttpProtocols.HTTP_1_0; +import static akka.http.javadsl.model.RequestEntityAcceptances.Expected; + +public class CustomHttpMethodExamplesTest extends JUnitRouteTest { + + @Test + public void testComposition() throws InterruptedException, ExecutionException { + ActorSystem system = system(); + Materializer materializer = materializer(); + LoggingAdapter loggingAdapter = NoLogging.getInstance(); + + int port = 9090; + String host = "127.0.0.1"; + + //#customHttpMethod + HttpMethod BOLT = + HttpMethods.createCustom("BOLT", false, true, Expected); + final ParserSettings parserSettings = + ParserSettings.create(system).withCustomMethods(BOLT); + final ServerSettings serverSettings = + ServerSettings.create(system).withParserSettings(parserSettings); + + final Route routes = route( + extractMethod( method -> + complete( "This is a " + method.name() + " request.") + ) + ); + final Flow handler = routes.flow(system, materializer); + final Http http = Http.get(system); + final CompletionStage binding = + http.bindAndHandle( + handler, + ConnectHttp.toHost(host, port), + serverSettings, + loggingAdapter, + materializer); + + HttpRequest request = HttpRequest.create() + .withUri("http://" + host + ":" + Integer.toString(port)) + .withMethod(BOLT) + .withProtocol(HTTP_1_0); + + CompletionStage response = http.singleRequest(request, materializer); + //#customHttpMethod + + assertResult(StatusCodes.OK, response.toCompletableFuture().get().status()); + assertResult( + "This is a BOLT request.", + response.toCompletableFuture().get().entity().toStrict(3000, materializer).toCompletableFuture().get().getData().utf8String() + ); + } +} diff --git a/akka-docs/rst/java/http/routing-dsl/directives/method-directives/extractMethod.rst b/akka-docs/rst/java/http/routing-dsl/directives/method-directives/extractMethod.rst index edcf4a62cf..042f028dd5 100644 --- a/akka-docs/rst/java/http/routing-dsl/directives/method-directives/extractMethod.rst +++ b/akka-docs/rst/java/http/routing-dsl/directives/method-directives/extractMethod.rst @@ -17,3 +17,9 @@ print what type of request it was - independent of what actual HttpMethod it was .. includecode:: ../../../../code/docs/http/javadsl/server/directives/MethodDirectivesExamplesTest.java#extractMethod +Custom Http Method +------------------ + +When you define a custom HttpMethod, you can define a route using extractMethod. + + .. includecode:: ../../../../code/docs/http/javadsl/server/directives/CustomHttpMethodExamplesTest.java#customHttpMethod diff --git a/akka-docs/rst/scala/code/docs/http/scaladsl/server/directives/CustomHttpMethodSpec.scala b/akka-docs/rst/scala/code/docs/http/scaladsl/server/directives/CustomHttpMethodSpec.scala new file mode 100644 index 0000000000..5fe711b713 --- /dev/null +++ b/akka-docs/rst/scala/code/docs/http/scaladsl/server/directives/CustomHttpMethodSpec.scala @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2009-2016 Lightbend Inc. + */ + +package docs.http.scaladsl.server.directives + +import akka.http.scaladsl.{ Http, TestUtils } +import akka.http.scaladsl.client.RequestBuilding +import akka.http.scaladsl.model.HttpProtocols._ +import akka.http.scaladsl.model.RequestEntityAcceptance.Expected +import akka.http.scaladsl.model._ +import akka.http.scaladsl.server.Directives +import akka.stream.ActorMaterializer +import akka.testkit.AkkaSpec +import akka.util.ByteString +import org.scalatest.concurrent.ScalaFutures + +import scala.concurrent.duration._ + +class CustomHttpMethodSpec extends AkkaSpec with ScalaFutures + with Directives with RequestBuilding { + + implicit val mat = ActorMaterializer() + + "Http" should { + "allow registering custom method" in { + import system.dispatcher + val (_, host, port) = TestUtils.temporaryServerHostnameAndPort() + + //#application-custom + import akka.http.scaladsl.settings.{ ParserSettings, ServerSettings } + + // define custom media type: + val BOLT = HttpMethod.custom("BOLT", safe = false, + idempotent = true, requestEntityAcceptance = Expected) + + // add custom method to parser settings: + val parserSettings = ParserSettings(system).withCustomMethods(BOLT) + val serverSettings = ServerSettings(system).withParserSettings(parserSettings) + + val routes = extractMethod { method ⇒ + complete(s"This is a ${method.name} method request.") + } + val binding = Http().bindAndHandle(routes, host, port, settings = serverSettings) + //#application-custom + + val request = HttpRequest(BOLT, s"http://$host:$port/", protocol = `HTTP/1.0`) + val response = Http().singleRequest(request).futureValue + + response.status should ===(StatusCodes.OK) + val responseBody = response.toStrict(1.second).futureValue.entity.dataBytes.runFold(ByteString.empty)(_ ++ _).futureValue.utf8String + responseBody should ===("This is a BOLT method request.") + } + } +} + diff --git a/akka-docs/rst/scala/http/routing-dsl/directives/method-directives/extractMethod.rst b/akka-docs/rst/scala/http/routing-dsl/directives/method-directives/extractMethod.rst index e0ff7743ff..9c25b91741 100644 --- a/akka-docs/rst/scala/http/routing-dsl/directives/method-directives/extractMethod.rst +++ b/akka-docs/rst/scala/http/routing-dsl/directives/method-directives/extractMethod.rst @@ -22,3 +22,13 @@ print what type of request it was - independent of what actual HttpMethod it was .. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/MethodDirectivesExamplesSpec.scala :snippet: extractMethod-example + + +Custom Http Method +------------------ + +When you define a custom HttpMethod, you can define a route using extractMethod. + +.. includecode:: ../../../../code/docs/http/scaladsl/server/directives/CustomHttpMethodSpec.scala + :include: application-custom + diff --git a/akka-http-core/src/main/java/akka/http/javadsl/model/HttpMethod.java b/akka-http-core/src/main/java/akka/http/javadsl/model/HttpMethod.java index f554bd438e..69f95a050f 100644 --- a/akka-http-core/src/main/java/akka/http/javadsl/model/HttpMethod.java +++ b/akka-http-core/src/main/java/akka/http/javadsl/model/HttpMethod.java @@ -42,6 +42,14 @@ public abstract class HttpMethod { /** * Returns the entity acceptance level for this method. + * @deprecated Use {@link #getRequestEntityAcceptance} instead, which returns {@link akka.http.javadsl.model.RequestEntityAcceptance}. */ + @Deprecated public abstract akka.http.scaladsl.model.RequestEntityAcceptance requestEntityAcceptance(); + + /** + * Java API: Returns the entity acceptance level for this method. + */ + // TODO: Rename it to requestEntityAcceptance() in Akka 3.0 + public abstract akka.http.javadsl.model.RequestEntityAcceptance getRequestEntityAcceptance(); } diff --git a/akka-http-core/src/main/java/akka/http/javadsl/model/HttpMethods.java b/akka-http-core/src/main/java/akka/http/javadsl/model/HttpMethods.java index 70835f1266..0085398a83 100644 --- a/akka-http-core/src/main/java/akka/http/javadsl/model/HttpMethods.java +++ b/akka-http-core/src/main/java/akka/http/javadsl/model/HttpMethods.java @@ -27,11 +27,24 @@ public final class HttpMethods { /** * Create a custom method type. + * @deprecated Use {@link #createCustom} instead. */ + @Deprecated public static HttpMethod custom(String value, boolean safe, boolean idempotent, akka.http.scaladsl.model.RequestEntityAcceptance requestEntityAcceptance) { return akka.http.scaladsl.model.HttpMethod.custom(value, safe, idempotent, requestEntityAcceptance); } + /** + * Create a custom method type. + */ + // TODO: Rename it to custom() in Akka 3.0 + public static HttpMethod createCustom(String value, boolean safe, boolean idempotent, akka.http.javadsl.model.RequestEntityAcceptance requestEntityAcceptance) { + //This cast is safe as implementation of RequestEntityAcceptance only exists in Scala + akka.http.scaladsl.model.RequestEntityAcceptance scalaRequestEntityAcceptance + = (akka.http.scaladsl.model.RequestEntityAcceptance) requestEntityAcceptance; + return akka.http.scaladsl.model.HttpMethod.custom(value, safe, idempotent, scalaRequestEntityAcceptance); + } + /** * Looks up a predefined HTTP method with the given name. */ diff --git a/akka-http-core/src/main/java/akka/http/javadsl/model/RequestEntityAcceptance.java b/akka-http-core/src/main/java/akka/http/javadsl/model/RequestEntityAcceptance.java new file mode 100644 index 0000000000..f2c8c69d63 --- /dev/null +++ b/akka-http-core/src/main/java/akka/http/javadsl/model/RequestEntityAcceptance.java @@ -0,0 +1,14 @@ +/** + * Copyright (C) 2009-2016 Lightbend Inc. + */ + +package akka.http.javadsl.model; + +/** + * @see RequestEntityAcceptances for convenience access to often used values. + * Do not extend this to a concrete Java class, + * as implementation of RequestEntityAcceptation should only exist in Scala + */ +public abstract class RequestEntityAcceptance { + public abstract boolean isEntityAccepted(); +} diff --git a/akka-http-core/src/main/java/akka/http/javadsl/model/RequestEntityAcceptances.java b/akka-http-core/src/main/java/akka/http/javadsl/model/RequestEntityAcceptances.java new file mode 100644 index 0000000000..9063b17961 --- /dev/null +++ b/akka-http-core/src/main/java/akka/http/javadsl/model/RequestEntityAcceptances.java @@ -0,0 +1,14 @@ +/** + * Copyright (C) 2009-2016 Lightbend Inc. + */ + + +package akka.http.javadsl.model; + +public final class RequestEntityAcceptances { + private RequestEntityAcceptances() {} + + public static final RequestEntityAcceptance Expected = akka.http.scaladsl.model.RequestEntityAcceptance.Expected$.MODULE$; + public static final RequestEntityAcceptance Tolerated = akka.http.scaladsl.model.RequestEntityAcceptance.Tolerated$.MODULE$; + public static final RequestEntityAcceptance Disallowed = akka.http.scaladsl.model.RequestEntityAcceptance.Disallowed$.MODULE$; +} diff --git a/akka-http-core/src/main/scala/akka/http/scaladsl/model/HttpMethod.scala b/akka-http-core/src/main/scala/akka/http/scaladsl/model/HttpMethod.scala index 7ee304c81f..820678bc09 100644 --- a/akka-http-core/src/main/scala/akka/http/scaladsl/model/HttpMethod.scala +++ b/akka-http-core/src/main/scala/akka/http/scaladsl/model/HttpMethod.scala @@ -8,7 +8,7 @@ import akka.http.impl.util._ import akka.http.javadsl.{ model ⇒ jm } import akka.http.scaladsl.model.RequestEntityAcceptance._ -sealed trait RequestEntityAcceptance { +sealed trait RequestEntityAcceptance extends jm.RequestEntityAcceptance { def isEntityAccepted: Boolean } object RequestEntityAcceptance { @@ -36,6 +36,7 @@ final case class HttpMethod private[http] ( requestEntityAcceptance: RequestEntityAcceptance) extends jm.HttpMethod with SingletonValueRenderable { override def isEntityAccepted: Boolean = requestEntityAcceptance.isEntityAccepted override def toString: String = s"HttpMethod($value)" + override def getRequestEntityAcceptance: jm.RequestEntityAcceptance = requestEntityAcceptance } object HttpMethod { diff --git a/project/MiMa.scala b/project/MiMa.scala index 106a7c14bc..aab553715f 100644 --- a/project/MiMa.scala +++ b/project/MiMa.scala @@ -909,6 +909,9 @@ object MiMa extends AutoPlugin { // #20994 adding new decode method, since we're on JDK7+ now ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.util.ByteString.decodeString"), + // #20508 HTTP: Document how to be able to support custom request methods + ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.javadsl.model.HttpMethod.getRequestEntityAcceptance"), + // #20976 provide different options to deal with the illegal response header value ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.javadsl.settings.ParserSettings.getIllegalResponseHeaderValueProcessingMode"), ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.scaladsl.settings.ParserSettings.illegalResponseHeaderValueProcessingMode")