diff --git a/.attach_pid6563 b/.attach_pid6563 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/akka-actor/src/main/scala/akka/japi/JavaAPI.scala b/akka-actor/src/main/scala/akka/japi/JavaAPI.scala index bc51779198..2a28ca795f 100644 --- a/akka-actor/src/main/scala/akka/japi/JavaAPI.scala +++ b/akka-actor/src/main/scala/akka/japi/JavaAPI.scala @@ -240,6 +240,13 @@ object Util { def immutableSingletonSeq[T](value: T): immutable.Seq[T] = value :: Nil + def javaArrayList[T](seq: Seq[T]): java.util.List[T] = { + val size = seq.size + val l = new java.util.ArrayList[T](size) + seq.foreach(l.add) // TODO could be optimised based on type of Seq + l + } + /** * Turns an [[java.lang.Iterable]] into an immutable Scala IndexedSeq (by copying it). */ diff --git a/akka-docs/rst/java/code/docs/http/javadsl/server/FormFieldRequestValsExampleTest.java b/akka-docs/rst/java/code/docs/http/javadsl/server/FormFieldRequestValsExampleTest.java index 22ef4ae234..fc5ae71c28 100644 --- a/akka-docs/rst/java/code/docs/http/javadsl/server/FormFieldRequestValsExampleTest.java +++ b/akka-docs/rst/java/code/docs/http/javadsl/server/FormFieldRequestValsExampleTest.java @@ -4,27 +4,28 @@ package docs.http.javadsl.server; + +import org.junit.Test; + import akka.http.javadsl.model.FormData; import akka.http.javadsl.model.HttpRequest; import akka.http.javadsl.server.Route; -import akka.http.javadsl.server.values.FormField; -import akka.http.javadsl.server.values.FormFields; +import akka.http.javadsl.server.StringUnmarshallers; +import akka.http.javadsl.server.StringUnmarshaller; +import akka.http.javadsl.server.Unmarshaller; import akka.http.javadsl.testkit.JUnitRouteTest; import akka.japi.Pair; -import org.junit.Test; public class FormFieldRequestValsExampleTest extends JUnitRouteTest { @Test public void testFormFieldVals() { //#simple - FormField name = FormFields.stringValue("name"); - FormField age = FormFields.intValue("age"); final Route route = - route( - handleWith2(name, age, (ctx, n, a) -> - ctx.complete(String.format("Name: %s, age: %d", n, a)) + formField("name", n -> + formField(StringUnmarshallers.INTEGER, "age", a -> + complete(String.format("Name: %s, age: %d", n, a)) ) ); @@ -44,13 +45,11 @@ public class FormFieldRequestValsExampleTest extends JUnitRouteTest { @Test public void testFormFieldValsUnmarshaling() { //#custom-unmarshal - FormField sampleId = FormFields.fromString("id", SampleId.class, s -> new SampleId(Integer.valueOf(s))); + Unmarshaller SAMPLE_ID = StringUnmarshaller.sync(s -> new SampleId(Integer.valueOf(s))); final Route route = - route( - handleWith1(sampleId, (ctx, sid) -> - ctx.complete(String.format("SampleId: %s", sid.id)) - ) + formField(SAMPLE_ID, "id", sid -> + complete(String.format("SampleId: %s", sid.id)) ); // tests: diff --git a/akka-docs/rst/java/code/docs/http/javadsl/server/HeaderRequestValsExampleTest.java b/akka-docs/rst/java/code/docs/http/javadsl/server/HeaderRequestValsExampleTest.java index e7ac90d936..873184445c 100644 --- a/akka-docs/rst/java/code/docs/http/javadsl/server/HeaderRequestValsExampleTest.java +++ b/akka-docs/rst/java/code/docs/http/javadsl/server/HeaderRequestValsExampleTest.java @@ -4,28 +4,23 @@ package docs.http.javadsl.server; +import org.junit.Test; + import akka.http.javadsl.model.HttpRequest; import akka.http.javadsl.model.headers.Host; import akka.http.javadsl.model.headers.RawHeader; -import akka.http.javadsl.server.RequestVal; import akka.http.javadsl.server.Route; -import akka.http.javadsl.server.values.Headers; import akka.http.javadsl.testkit.JUnitRouteTest; -import org.junit.Test; public class HeaderRequestValsExampleTest extends JUnitRouteTest { @Test public void testHeaderVals() { //#by-class - // extract the entire header instance: - RequestVal host = Headers.byClass(Host.class).instance(); final Route route = - route( - handleWith1(host, (ctx, h) -> - ctx.complete(String.format("Host header was: %s", h.host())) - ) + extractHost(host -> + complete(String.format("Host header was: %s", host)) ); // tests: @@ -41,14 +36,11 @@ public class HeaderRequestValsExampleTest extends JUnitRouteTest { @Test public void testHeaderByName() { //#by-name - // extract the `value` of the header: - final RequestVal XFishName = Headers.byName("X-Fish-Name").value(); final Route route = - route( - handleWith1(XFishName, (ctx, xFishName) -> - ctx.complete(String.format("The `X-Fish-Name` header's value was: %s", xFishName)) - ) + // extract the `value` of the header: + headerValueByName("X-Fish-Name", xFishName -> + complete(String.format("The `X-Fish-Name` header's value was: %s", xFishName)) ); // tests: diff --git a/akka-docs/rst/java/code/docs/http/javadsl/server/HighLevelServerBindFailureExample.java b/akka-docs/rst/java/code/docs/http/javadsl/server/HighLevelServerBindFailureExample.java index 036d0e8f56..e58bcd9535 100644 --- a/akka-docs/rst/java/code/docs/http/javadsl/server/HighLevelServerBindFailureExample.java +++ b/akka-docs/rst/java/code/docs/http/javadsl/server/HighLevelServerBindFailureExample.java @@ -5,28 +5,41 @@ package docs.http.javadsl.server; //#binding-failure-high-level-example + +import akka.NotUsed; import akka.actor.ActorSystem; -import akka.http.scaladsl.Http; +import akka.http.javadsl.ConnectHttp; +import akka.http.javadsl.ServerBinding; +import akka.http.javadsl.model.HttpRequest; +import akka.http.javadsl.model.HttpResponse; +import akka.http.javadsl.server.Route; +import akka.http.javadsl.Http; +import akka.stream.ActorMaterializer; +import akka.stream.javadsl.Flow; import java.io.IOException; import java.util.concurrent.CompletionStage; public class HighLevelServerBindFailureExample { - public static void main(String[] args) throws IOException { - // boot up server using the route as defined below - final ActorSystem system = ActorSystem.create(); + public static void main(String[] args) throws IOException { + // boot up server using the route as defined below + final ActorSystem system = ActorSystem.create(); + final ActorMaterializer materializer = ActorMaterializer.create(system); - // HttpApp.bindRoute expects a route being provided by HttpApp.createRoute - CompletionStage bindingFuture = - new HighLevelServerExample().bindRoute("localhost", 8080, system); + // HttpApp.bindRoute expects a route being provided by HttpApp.createRoute + final HighLevelServerExample app = new HighLevelServerExample(); + final Route route = app.createRoute(); - bindingFuture.exceptionally(failure -> { - System.err.println("Something very bad happened! " + failure.getMessage()); - system.terminate(); - return null; - }); + final Flow handler = route.flow(system, materializer); + final CompletionStage binding = Http.get(system).bindAndHandle(handler, ConnectHttp.toHost("127.0.0.1", 8080), materializer); - system.terminate(); - } + binding.exceptionally(failure -> { + System.err.println("Something very bad happened! " + failure.getMessage()); + system.terminate(); + return null; + }); + + system.terminate(); + } } //#binding-failure-high-level-example diff --git a/akka-docs/rst/java/code/docs/http/javadsl/server/HighLevelServerExample.java b/akka-docs/rst/java/code/docs/http/javadsl/server/HighLevelServerExample.java index 350ec2415f..3537964e61 100644 --- a/akka-docs/rst/java/code/docs/http/javadsl/server/HighLevelServerExample.java +++ b/akka-docs/rst/java/code/docs/http/javadsl/server/HighLevelServerExample.java @@ -5,65 +5,73 @@ package docs.http.javadsl.server; //#high-level-server-example + +import akka.NotUsed; import akka.actor.ActorSystem; +import akka.http.javadsl.ConnectHttp; +import akka.http.javadsl.Http; +import akka.http.javadsl.ServerBinding; import akka.http.javadsl.model.ContentTypes; -import akka.http.javadsl.model.MediaTypes; -import akka.http.javadsl.server.*; -import akka.http.javadsl.server.values.Parameters; +import akka.http.javadsl.model.HttpEntities; +import akka.http.javadsl.model.HttpRequest; +import akka.http.javadsl.model.HttpResponse; +import akka.http.javadsl.server.AllDirectives; +import akka.http.javadsl.server.Route; +import akka.stream.ActorMaterializer; +import akka.stream.javadsl.Flow; import java.io.IOException; +import java.util.concurrent.CompletionStage; -public class HighLevelServerExample extends HttpApp { - public static void main(String[] args) throws IOException { - // boot up server using the route as defined below - ActorSystem system = ActorSystem.create(); +public class HighLevelServerExample extends AllDirectives { + public static void main(String[] args) throws IOException { + // boot up server using the route as defined below + ActorSystem system = ActorSystem.create(); - // HttpApp.bindRoute expects a route being provided by HttpApp.createRoute - new HighLevelServerExample().bindRoute("localhost", 8080, system); - System.out.println("Type RETURN to exit"); - System.in.read(); - system.terminate(); - } + // HttpApp.bindRoute expects a route being provided by HttpApp.createRoute + final HighLevelServerExample app = new HighLevelServerExample(); - // A RequestVal is a type-safe representation of some aspect of the request. - // In this case it represents the `name` URI parameter of type String. - private RequestVal name = Parameters.stringValue("name").withDefault("Mister X"); + final Http http = Http.get(system); + final ActorMaterializer materializer = ActorMaterializer.create(system); - @Override - public Route createRoute() { - // This handler generates responses to `/hello?name=XXX` requests - Route helloRoute = - handleWith1(name, - // in Java 8 the following becomes simply - // (ctx, name) -> ctx.complete("Hello " + name + "!") - new Handler1() { - @Override - public RouteResult apply(RequestContext ctx, String name) { - return ctx.complete("Hello " + name + "!"); - } - }); + final Flow routeFlow = app.createRoute().flow(system, materializer); + final CompletionStage binding = http.bindAndHandle(routeFlow, ConnectHttp.toHost("localhost", 8080), materializer); - return - // here the complete behavior for this server is defined - route( - // only handle GET requests - get( - // matches the empty path - pathSingleSlash().route( - // return a constant string with a certain content type - complete(ContentTypes.TEXT_HTML_UTF8, - "Hello world!") - ), - path("ping").route( - // return a simple `text/plain` response - complete("PONG!") - ), - path("hello").route( - // uses the route defined above - helloRoute - ) - ) - ); - } + System.out.println("Type RETURN to exit"); + System.in.read(); + + binding + .thenCompose(ServerBinding::unbind) + .thenAccept(unbound -> system.terminate()); + } + + public Route createRoute() { + // This handler generates responses to `/hello?name=XXX` requests + Route helloRoute = + parameterOptional("name", optName -> { + String name = optName.orElse("Mister X"); + return complete("Hello " + name + "!"); + }); + + return + // here the complete behavior for this server is defined + + // only handle GET requests + get(() -> route( + // matches the empty path + pathSingleSlash(() -> + // return a constant string with a certain content type + complete(HttpEntities.create(ContentTypes.TEXT_HTML_UTF8, "Hello world!")) + ), + path("ping", () -> + // return a simple `text/plain` response + complete("PONG!") + ), + path("hello", () -> + // uses the route defined above + helloRoute + ) + )); + } } -//#high-level-server-example \ No newline at end of file +//#high-level-server-example diff --git a/akka-docs/rst/java/code/docs/http/javadsl/server/HttpBasicAuthenticatorExample.java b/akka-docs/rst/java/code/docs/http/javadsl/server/HttpBasicAuthenticatorExample.java index b87cf4417c..4d0c8eebe9 100644 --- a/akka-docs/rst/java/code/docs/http/javadsl/server/HttpBasicAuthenticatorExample.java +++ b/akka-docs/rst/java/code/docs/http/javadsl/server/HttpBasicAuthenticatorExample.java @@ -4,59 +4,35 @@ package docs.http.javadsl.server; import java.util.Optional; + +import org.junit.Test; + import akka.http.javadsl.model.HttpRequest; import akka.http.javadsl.model.headers.Host; -import akka.http.javadsl.server.Handler1; -import akka.http.javadsl.server.RequestContext; import akka.http.javadsl.server.Route; -import akka.http.javadsl.server.RouteResult; -import akka.http.javadsl.server.values.BasicCredentials; -import akka.http.javadsl.server.values.HttpBasicAuthenticator; import akka.http.javadsl.testkit.JUnitRouteTest; import akka.http.scaladsl.model.headers.Authorization; -import java.util.Optional; -import java.util.concurrent.CompletionStage; - -import org.junit.Test; -import scala.Option; -import scala.concurrent.Future; - public class HttpBasicAuthenticatorExample extends JUnitRouteTest { + private final String hardcodedPassword = "correcthorsebatterystaple"; + + private Optional authenticate(Optional creds) { + // this is where your actual authentication logic would go + return creds + .filter(c -> c.verify(hardcodedPassword)) // Only allow users that provide the right password + .map(c -> c.identifier()); // Provide the username down to the inner route + } @Test public void testBasicAuthenticator() { //#basic-authenticator-java - final HttpBasicAuthenticator authentication = new HttpBasicAuthenticator("My realm") { - - private final String hardcodedPassword = "correcthorsebatterystaple"; - - public CompletionStage> authenticate(BasicCredentials credentials) { - // this is where your actual authentication logic would go - if (credentials.available() && // no anonymous access - credentials.verify(hardcodedPassword)) { - return authenticateAs(credentials.identifier()); - } else { - return refuseAccess(); - } - } - }; final Route route = - authentication.route( - handleWith1( - authentication, - new Handler1() { - public RouteResult apply(RequestContext ctx, String user) { - return ctx.complete("Hello " + user + "!"); - } - - } - ) + authenticateBasic("My realm", this::authenticate, user -> + complete("Hello " + user + "!") ); - // tests: final HttpRequest okRequest = HttpRequest diff --git a/akka-docs/rst/java/code/docs/http/javadsl/server/HttpServerExampleDocTest.java b/akka-docs/rst/java/code/docs/http/javadsl/server/HttpServerExampleDocTest.java index 11d2e36945..37a773e5f6 100644 --- a/akka-docs/rst/java/code/docs/http/javadsl/server/HttpServerExampleDocTest.java +++ b/akka-docs/rst/java/code/docs/http/javadsl/server/HttpServerExampleDocTest.java @@ -6,18 +6,11 @@ package docs.http.javadsl.server; import akka.NotUsed; import akka.actor.ActorSystem; -import akka.dispatch.OnFailure; import akka.http.javadsl.ConnectHttp; import akka.http.javadsl.Http; import akka.http.javadsl.IncomingConnection; import akka.http.javadsl.ServerBinding; import akka.http.javadsl.model.*; -import akka.http.javadsl.model.ContentTypes; -import akka.http.javadsl.model.HttpMethods; -import akka.http.javadsl.model.HttpRequest; -import akka.http.javadsl.model.HttpResponse; -import akka.http.javadsl.model.Uri; -import akka.http.scaladsl.model.HttpEntity; import akka.japi.function.Function; import akka.stream.ActorMaterializer; import akka.stream.Materializer; @@ -34,180 +27,182 @@ import java.util.concurrent.TimeUnit; @SuppressWarnings("unused") public class HttpServerExampleDocTest { - public static void bindingExample() throws Exception { - //#binding-example - ActorSystem system = ActorSystem.create(); - Materializer materializer = ActorMaterializer.create(system); + public static void bindingExample() throws Exception { + //#binding-example + ActorSystem system = ActorSystem.create(); + Materializer materializer = ActorMaterializer.create(system); - Source> serverSource = - Http.get(system).bind(ConnectHttp.toHost("localhost", 8080), materializer); + Source> serverSource = + Http.get(system).bind(ConnectHttp.toHost("localhost", 8080), materializer); - CompletionStage serverBindingFuture = - serverSource.to(Sink.foreach(connection -> { - System.out.println("Accepted new connection from " + connection.remoteAddress()); - // ... and then actually handle the connection - } - )).run(materializer); - //#binding-example - serverBindingFuture.toCompletableFuture().get(3, TimeUnit.SECONDS); - } - - public static void bindingFailureExample() throws Exception { - //#binding-failure-handling - ActorSystem system = ActorSystem.create(); - Materializer materializer = ActorMaterializer.create(system); - - Source> serverSource = - Http.get(system).bind(ConnectHttp.toHost("localhost", 80), materializer); - - CompletionStage serverBindingFuture = - serverSource.to(Sink.foreach(connection -> { - System.out.println("Accepted new connection from " + connection.remoteAddress()); - // ... and then actually handle the connection - } - )).run(materializer); - - serverBindingFuture.whenCompleteAsync((binding, failure) -> { - // possibly report the failure somewhere... - }, system.dispatcher()); - //#binding-failure-handling - serverBindingFuture.toCompletableFuture().get(3, TimeUnit.SECONDS); - } - - public static void connectionSourceFailureExample() throws Exception { - //#incoming-connections-source-failure-handling - ActorSystem system = ActorSystem.create(); - Materializer materializer = ActorMaterializer.create(system); - - Source> serverSource = - Http.get(system).bind(ConnectHttp.toHost("localhost", 8080), materializer); - - Flow failureDetection = - Flow.of(IncomingConnection.class).watchTermination((notUsed, termination) -> { - termination.whenComplete((done, cause) -> { - if (cause != null) { - // signal the failure to external monitoring service! - } - }); - return NotUsed.getInstance(); - }); - - CompletionStage serverBindingFuture = - serverSource - .via(failureDetection) // feed signals through our custom stage - .to(Sink.foreach(connection -> { - System.out.println("Accepted new connection from " + connection.remoteAddress()); - // ... and then actually handle the connection - })) - .run(materializer); - //#incoming-connections-source-failure-handling - serverBindingFuture.toCompletableFuture().get(3, TimeUnit.SECONDS); - } - - public static void connectionStreamFailureExample() throws Exception { - //#connection-stream-failure-handling - ActorSystem system = ActorSystem.create(); - Materializer materializer = ActorMaterializer.create(system); - - Source> serverSource = - Http.get(system).bind(ConnectHttp.toHost("localhost", 8080), materializer); - - Flow failureDetection = - Flow.of(HttpRequest.class) - .watchTermination((notUsed, termination) -> { - termination.whenComplete((done, cause) -> { - if (cause != null) { - // signal the failure to external monitoring service! - } - }); - return NotUsed.getInstance(); - }); - - Flow httpEcho = - Flow.of(HttpRequest.class) - .via(failureDetection) - .map(request -> { - Source bytes = request.entity().getDataBytes(); - HttpEntity.Chunked entity = HttpEntities.create(ContentTypes.TEXT_PLAIN_UTF8, bytes); - - return HttpResponse.create() - .withEntity(entity); - }); - - CompletionStage serverBindingFuture = - serverSource.to(Sink.foreach(conn -> { - System.out.println("Accepted new connection from " + conn.remoteAddress()); - conn.handleWith(httpEcho, materializer); - } - )).run(materializer); - //#connection-stream-failure-handling - serverBindingFuture.toCompletableFuture().get(3, TimeUnit.SECONDS); - } - - public static void fullServerExample() throws Exception { - //#full-server-example - ActorSystem system = ActorSystem.create(); - //#full-server-example - try { - //#full-server-example - final Materializer materializer = ActorMaterializer.create(system); - - Source> serverSource = - Http.get(system).bind(ConnectHttp.toHost("localhost", 8080), materializer); - - //#request-handler - final Function requestHandler = - new Function() { - private final HttpResponse NOT_FOUND = - HttpResponse.create() - .withStatus(404) - .withEntity("Unknown resource!"); - - - @Override - public HttpResponse apply(HttpRequest request) throws Exception { - Uri uri = request.getUri(); - if (request.method() == HttpMethods.GET) { - if (uri.path().equals("/")) - return - HttpResponse.create() - .withEntity(ContentTypes.TEXT_HTML_UTF8, - "Hello world!"); - else if (uri.path().equals("/hello")) { - String name = uri.query().get("name").orElse("Mister X"); - - return - HttpResponse.create() - .withEntity("Hello " + name + "!"); - } - else if (uri.path().equals("/ping")) - return HttpResponse.create().withEntity("PONG!"); - else - return NOT_FOUND; - } - else return NOT_FOUND; - } - }; - //#request-handler - - CompletionStage serverBindingFuture = - serverSource.to(Sink.foreach(connection -> { - System.out.println("Accepted new connection from " + connection.remoteAddress()); - - connection.handleWithSyncHandler(requestHandler, materializer); - // this is equivalent to - //connection.handleWith(Flow.of(HttpRequest.class).map(requestHandler), materializer); - })).run(materializer); - //#full-server-example - - serverBindingFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); // will throw if binding fails - System.out.println("Press ENTER to stop."); - new BufferedReader(new InputStreamReader(System.in)).readLine(); - } finally { - system.terminate(); + CompletionStage serverBindingFuture = + serverSource.to(Sink.foreach(connection -> { + System.out.println("Accepted new connection from " + connection.remoteAddress()); + // ... and then actually handle the connection } + )).run(materializer); + //#binding-example + serverBindingFuture.toCompletableFuture().get(3, TimeUnit.SECONDS); + } + + public static void bindingFailureExample() throws Exception { + //#binding-failure-handling + ActorSystem system = ActorSystem.create(); + Materializer materializer = ActorMaterializer.create(system); + + Source> serverSource = + Http.get(system).bind(ConnectHttp.toHost("localhost", 80), materializer); + + CompletionStage serverBindingFuture = + serverSource.to(Sink.foreach(connection -> { + System.out.println("Accepted new connection from " + connection.remoteAddress()); + // ... and then actually handle the connection + } + )).run(materializer); + + serverBindingFuture.whenCompleteAsync((binding, failure) -> { + // possibly report the failure somewhere... + }, system.dispatcher()); + //#binding-failure-handling + serverBindingFuture.toCompletableFuture().get(3, TimeUnit.SECONDS); + } + + public static void connectionSourceFailureExample() throws Exception { + //#incoming-connections-source-failure-handling + ActorSystem system = ActorSystem.create(); + Materializer materializer = ActorMaterializer.create(system); + + Source> serverSource = + Http.get(system).bind(ConnectHttp.toHost("localhost", 8080), materializer); + + Flow failureDetection = + Flow.of(IncomingConnection.class).watchTermination((notUsed, termination) -> { + termination.whenComplete((done, cause) -> { + if (cause != null) { + // signal the failure to external monitoring service! + } + }); + return NotUsed.getInstance(); + }); + + CompletionStage serverBindingFuture = + serverSource + .via(failureDetection) // feed signals through our custom stage + .to(Sink.foreach(connection -> { + System.out.println("Accepted new connection from " + connection.remoteAddress()); + // ... and then actually handle the connection + })) + .run(materializer); + //#incoming-connections-source-failure-handling + serverBindingFuture.toCompletableFuture().get(3, TimeUnit.SECONDS); + } + + public static void connectionStreamFailureExample() throws Exception { + //#connection-stream-failure-handling + ActorSystem system = ActorSystem.create(); + Materializer materializer = ActorMaterializer.create(system); + + Source> serverSource = + Http.get(system).bind(ConnectHttp.toHost("localhost", 8080), materializer); + + Flow failureDetection = + Flow.of(HttpRequest.class) + .watchTermination((notUsed, termination) -> { + termination.whenComplete((done, cause) -> { + if (cause != null) { + // signal the failure to external monitoring service! + } + }); + return NotUsed.getInstance(); + }); + + Flow httpEcho = + Flow.of(HttpRequest.class) + .via(failureDetection) + .map(request -> { + Source bytes = request.entity().getDataBytes(); + HttpEntity.Chunked entity = HttpEntities.create(ContentTypes.TEXT_PLAIN_UTF8, bytes); + + return HttpResponse.create() + .withEntity(entity); + }); + + CompletionStage serverBindingFuture = + serverSource.to(Sink.foreach(conn -> { + System.out.println("Accepted new connection from " + conn.remoteAddress()); + conn.handleWith(httpEcho, materializer); + } + )).run(materializer); + //#connection-stream-failure-handling + serverBindingFuture.toCompletableFuture().get(3, TimeUnit.SECONDS); + } + + public static void fullServerExample() throws Exception { + //#full-server-example + ActorSystem system = ActorSystem.create(); + //#full-server-example + try { + //#full-server-example + final Materializer materializer = ActorMaterializer.create(system); + + Source> serverSource = + Http.get(system).bind(ConnectHttp.toHost("localhost", 8080), materializer); + + //#request-handler + final Function requestHandler = + new Function() { + private final HttpResponse NOT_FOUND = + HttpResponse.create() + .withStatus(404) + .withEntity("Unknown resource!"); + + + @Override + public HttpResponse apply(HttpRequest request) throws Exception { + Uri uri = request.getUri(); + if (request.method() == HttpMethods.GET) { + if (uri.path().equals("/")) { + return + HttpResponse.create() + .withEntity(ContentTypes.TEXT_HTML_UTF8, + "Hello world!"); + } else if (uri.path().equals("/hello")) { + String name = uri.query().get("name").orElse("Mister X"); + + return + HttpResponse.create() + .withEntity("Hello " + name + "!"); + } else if (uri.path().equals("/ping")) { + return HttpResponse.create().withEntity("PONG!"); + } else { + return NOT_FOUND; + } + } else { + return NOT_FOUND; + } + } + }; + //#request-handler + + CompletionStage serverBindingFuture = + serverSource.to(Sink.foreach(connection -> { + System.out.println("Accepted new connection from " + connection.remoteAddress()); + + connection.handleWithSyncHandler(requestHandler, materializer); + // this is equivalent to + //connection.handleWith(Flow.of(HttpRequest.class).map(requestHandler), materializer); + })).run(materializer); + //#full-server-example + + serverBindingFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); // will throw if binding fails + System.out.println("Press ENTER to stop."); + new BufferedReader(new InputStreamReader(System.in)).readLine(); + } finally { + system.terminate(); } - public static void main(String[] args) throws Exception { - fullServerExample(); - } + } + + public static void main(String[] args) throws Exception { + fullServerExample(); + } } diff --git a/akka-docs/rst/java/code/docs/http/javadsl/server/OAuth2AuthenticatorExample.java b/akka-docs/rst/java/code/docs/http/javadsl/server/OAuth2AuthenticatorExample.java index e477feede1..5f2b7da862 100644 --- a/akka-docs/rst/java/code/docs/http/javadsl/server/OAuth2AuthenticatorExample.java +++ b/akka-docs/rst/java/code/docs/http/javadsl/server/OAuth2AuthenticatorExample.java @@ -5,62 +5,36 @@ package docs.http.javadsl.server; import akka.http.javadsl.model.HttpRequest; import akka.http.javadsl.model.headers.Host; -import akka.http.javadsl.model.headers.OAuth2BearerToken; -import akka.http.javadsl.server.Handler1; -import akka.http.javadsl.server.RequestContext; import akka.http.javadsl.server.Route; -import akka.http.javadsl.server.RouteResult; -import akka.http.javadsl.server.values.BasicCredentials; -import akka.http.javadsl.server.values.HttpBasicAuthenticator; -import akka.http.javadsl.server.values.OAuth2Authenticator; -import akka.http.javadsl.server.values.OAuth2Credentials; import akka.http.javadsl.testkit.JUnitRouteTest; import akka.http.scaladsl.model.headers.Authorization; import java.util.Optional; -import java.util.concurrent.CompletionStage; import org.junit.Test; -import scala.Option; -import scala.concurrent.Future; public class OAuth2AuthenticatorExample extends JUnitRouteTest { + private final String hardcodedToken = "token"; + + private Optional authenticate(Optional creds) { + // this is where your actual authentication logic would go, looking up the user + // based on the token or something in that direction + + // We will not allow anonymous access. + return creds + .filter(c -> c.verify(hardcodedToken)) // + .map(c -> c.identifier()); // Provide the "identifier" down to the inner route + // (for OAuth2, that's actually just the token) + } @Test public void testOAuth2Authenticator() { //#oauth2-authenticator-java - final OAuth2Authenticator authentication = new OAuth2Authenticator("My realm") { - - private final String hardcodedToken = "token"; - - @Override - public CompletionStage> authenticate(OAuth2Credentials credentials) { - // this is where your actual authentication logic would go, looking up the user - // based on the token or something in that direction - if (credentials.available() && // no anonymous access - credentials.verify(hardcodedToken)) { - // not a secret + identity pair, so this is actually the token - return authenticateAs(credentials.identifier()); - } else { - return refuseAccess(); - } - } - - }; - final Route route = - authentication.route( - handleWith1( - authentication, - new Handler1() { - public RouteResult apply(RequestContext ctx, String token) { - return ctx.complete("The secret token is: " + token); - } - - } - ) - ); + authenticateOAuth2("My realm", this::authenticate, token -> + complete("The secret token is: " + token) + ); // tests: diff --git a/akka-docs/rst/java/code/docs/http/javadsl/server/PathDirectiveExampleTest.java b/akka-docs/rst/java/code/docs/http/javadsl/server/PathDirectiveExampleTest.java index 396ea7066d..26461988e2 100644 --- a/akka-docs/rst/java/code/docs/http/javadsl/server/PathDirectiveExampleTest.java +++ b/akka-docs/rst/java/code/docs/http/javadsl/server/PathDirectiveExampleTest.java @@ -5,73 +5,75 @@ package docs.http.javadsl.server; import akka.http.javadsl.model.StatusCodes; -import akka.http.javadsl.server.Handler1; -import akka.http.javadsl.server.values.PathMatcher; -import akka.http.javadsl.server.values.PathMatchers; +import akka.http.javadsl.server.PathMatchers; import akka.http.javadsl.testkit.JUnitRouteTest; + import org.junit.Test; public class PathDirectiveExampleTest extends JUnitRouteTest { - @Test - public void testPathPrefix() { - //#path-examples - // matches "/test" - path("test").route( - completeWithStatus(StatusCodes.OK) - ); + @Test + public void testPathPrefix() { + //#path-examples + // matches "/test" + path("test", () -> + complete(StatusCodes.OK) + ); - // matches "/test", as well - path(PathMatchers.segment("test")).route( - completeWithStatus(StatusCodes.OK) - ); + // matches "/test", as well + path(PathMatchers.segment("test"), () -> + complete(StatusCodes.OK) + ); - // matches "/admin/user" - path("admin", "user").route( - completeWithStatus(StatusCodes.OK) - ); + // matches "/admin/user" + path(PathMatchers.segment("admin") + .slash("user"), () -> + complete(StatusCodes.OK) + ); - // matches "/admin/user", as well - pathPrefix("admin").route( - path("user").route( - completeWithStatus(StatusCodes.OK) - ) - ); + // matches "/admin/user", as well + pathPrefix("admin", () -> + path("user", () -> + complete(StatusCodes.OK) + ) + ); - // matches "/admin/user/" - Handler1 completeWithUserId = - (ctx, userId) -> ctx.complete("Hello user " + userId); - PathMatcher userId = PathMatchers.intValue(); - pathPrefix("admin", "user").route( - path(userId).route( - handleWith1(userId, completeWithUserId) - ) - ); + // matches "/admin/user/" + path(PathMatchers.segment("admin") + .slash("user") + .slash(PathMatchers.integerSegment()), userId -> { + return complete("Hello user " + userId); + } + ); - // matches "/admin/user/", as well - path("admin", "user", userId).route( - handleWith1(userId, completeWithUserId) - ); + // matches "/admin/user/", as well + pathPrefix("admin", () -> + path("user", () -> + path(PathMatchers.integerSegment(), userId -> + complete("Hello user " + userId) + ) + ) + ); - // never matches - path("admin").route( // oops this only matches "/admin" - path("user").route( - completeWithStatus(StatusCodes.OK) - ) - ); + // never matches + path("admin", () -> // oops this only matches "/admin", and no sub-paths + path("user", () -> + complete(StatusCodes.OK) + ) + ); - // matches "/user/" with the first subroute, "/user" (without a trailing slash) - // with the second subroute, and "/user/" with the last one. - pathPrefix("user").route( - pathSingleSlash().route( - completeWithStatus(StatusCodes.OK) - ), - pathEnd().route( - completeWithStatus(StatusCodes.OK) - ), - path(userId).route( - handleWith1(userId, completeWithUserId) - ) - ); - //#path-examples - } -} \ No newline at end of file + // matches "/user/" with the first subroute, "/user" (without a trailing slash) + // with the second subroute, and "/user/" with the last one. + pathPrefix("user", () -> route( + pathSingleSlash(() -> + complete(StatusCodes.OK) + ), + pathEnd(() -> + complete(StatusCodes.OK) + ), + path(PathMatchers.integerSegment(), userId -> + complete("Hello user " + userId) + ) + )); + //#path-examples + } +} diff --git a/akka-docs/rst/java/code/docs/http/javadsl/server/WebSocketCoreExample.java b/akka-docs/rst/java/code/docs/http/javadsl/server/WebSocketCoreExample.java index 1f34bea623..89c985a866 100644 --- a/akka-docs/rst/java/code/docs/http/javadsl/server/WebSocketCoreExample.java +++ b/akka-docs/rst/java/code/docs/http/javadsl/server/WebSocketCoreExample.java @@ -5,6 +5,7 @@ package docs.http.javadsl.server; //#websocket-example-using-core + import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.concurrent.CompletionStage; @@ -12,11 +13,6 @@ import java.util.concurrent.TimeUnit; import akka.NotUsed; import akka.http.javadsl.ConnectHttp; -import scala.concurrent.Await; -import scala.concurrent.Future; -import scala.concurrent.duration.FiniteDuration; -import scala.runtime.BoxedUnit; - import akka.japi.Function; import akka.japi.JavaPartialFunction; @@ -34,66 +30,76 @@ import akka.http.javadsl.model.ws.Message; import akka.http.javadsl.model.ws.TextMessage; import akka.http.javadsl.model.ws.WebSocket; +@SuppressWarnings("Convert2MethodRef") public class WebSocketCoreExample { - //#websocket-handling - public static HttpResponse handleRequest(HttpRequest request) { - System.out.println("Handling request to " + request.getUri()); + + //#websocket-handling + public static HttpResponse handleRequest(HttpRequest request) { + System.out.println("Handling request to " + request.getUri()); - if (request.getUri().path().equals("/greeter")) - return WebSocket.handleWebSocketRequestWith(request, greeter()); - else - return HttpResponse.create().withStatus(404); + if (request.getUri().path().equals("/greeter")) { + final Flow greeterFlow = greeter(); + return WebSocket.handleWebSocketRequestWith(request, greeterFlow); + } else { + return HttpResponse.create().withStatus(404); } - //#websocket-handling + } + //#websocket-handling - public static void main(String[] args) throws Exception { - ActorSystem system = ActorSystem.create(); + public static void main(String[] args) throws Exception { + ActorSystem system = ActorSystem.create(); - try { - final Materializer materializer = ActorMaterializer.create(system); + try { + final Materializer materializer = ActorMaterializer.create(system); - CompletionStage serverBindingFuture = - Http.get(system).bindAndHandleSync( - new Function() { - public HttpResponse apply(HttpRequest request) throws Exception { - return handleRequest(request); - } - }, ConnectHttp.toHost("localhost", 8080), materializer); + final Function handler = request -> handleRequest(request); + CompletionStage serverBindingFuture = + Http.get(system).bindAndHandleSync( + handler, ConnectHttp.toHost("localhost", 8080), materializer); - // will throw if binding fails - serverBindingFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); - System.out.println("Press ENTER to stop."); - new BufferedReader(new InputStreamReader(System.in)).readLine(); - } finally { - system.terminate(); - } + // will throw if binding fails + serverBindingFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + System.out.println("Press ENTER to stop."); + new BufferedReader(new InputStreamReader(System.in)).readLine(); + } finally { + system.terminate(); } + } - //#websocket-handler - /** - * A handler that treats incoming messages as a name, - * and responds with a greeting to that name - */ - public static Flow greeter() { - return - Flow.create() - .collect(new JavaPartialFunction() { - @Override - public Message apply(Message msg, boolean isCheck) throws Exception { - if (isCheck) - if (msg.isText()) return null; - else throw noMatch(); - else - return handleTextMessage(msg.asTextMessage()); - } - }); + //#websocket-handler + + /** + * A handler that treats incoming messages as a name, + * and responds with a greeting to that name + */ + public static Flow greeter() { + return + Flow.create() + .collect(new JavaPartialFunction() { + @Override + public Message apply(Message msg, boolean isCheck) throws Exception { + if (isCheck) { + if (msg.isText()) { + return null; + } else { + throw noMatch(); + } + } else { + return handleTextMessage(msg.asTextMessage()); + } + } + }); + } + + public static TextMessage handleTextMessage(TextMessage msg) { + if (msg.isStrict()) // optimization that directly creates a simple response... + { + return TextMessage.create("Hello " + msg.getStrictText()); + } else // ... this would suffice to handle all text messages in a streaming fashion + { + return TextMessage.create(Source.single("Hello ").concat(msg.getStreamedText())); } - public static TextMessage handleTextMessage(TextMessage msg) { - if (msg.isStrict()) // optimization that directly creates a simple response... - return TextMessage.create("Hello "+msg.getStrictText()); - else // ... this would suffice to handle all text messages in a streaming fashion - return TextMessage.create(Source.single("Hello ").concat(msg.getStreamedText())); - } - //#websocket-handler + } + //#websocket-handler } -//#websocket-example-using-core \ No newline at end of file +//#websocket-example-using-core diff --git a/akka-docs/rst/java/code/docs/http/javadsl/server/WebSocketRoutingExample.java b/akka-docs/rst/java/code/docs/http/javadsl/server/WebSocketRoutingExample.java index 35a93372a6..c40facb357 100644 --- a/akka-docs/rst/java/code/docs/http/javadsl/server/WebSocketRoutingExample.java +++ b/akka-docs/rst/java/code/docs/http/javadsl/server/WebSocketRoutingExample.java @@ -4,51 +4,51 @@ package docs.http.javadsl.server; +import akka.NotUsed; +import akka.http.javadsl.server.AllDirectives; import akka.http.javadsl.server.Route; - import akka.japi.JavaPartialFunction; - import akka.stream.javadsl.Flow; import akka.stream.javadsl.Source; - import akka.http.javadsl.model.ws.Message; import akka.http.javadsl.model.ws.TextMessage; -import akka.http.javadsl.server.HttpApp; +public class WebSocketRoutingExample extends AllDirectives { -public class WebSocketRoutingExample extends HttpApp { - //#websocket-route - @Override - public Route createRoute() { - return - path("greeter").route( - handleWebSocketMessages(greeter()) - ); - } - //#websocket-route + //#websocket-route + public Route createRoute() { + return + path("greeter", () -> + handleWebSocketMessages(greeter()) + ); + } + //#websocket-route - /** - * A handler that treats incoming messages as a name, - * and responds with a greeting to that name - */ - public static Flow greeter() { - return - Flow.create() - .collect(new JavaPartialFunction() { - @Override - public Message apply(Message msg, boolean isCheck) throws Exception { - if (isCheck) - if (msg.isText()) return null; - else throw noMatch(); - else - return handleTextMessage(msg.asTextMessage()); - } - }); - } - public static TextMessage handleTextMessage(TextMessage msg) { - if (msg.isStrict()) // optimization that directly creates a simple response... - return TextMessage.create("Hello "+msg.getStrictText()); - else // ... this would suffice to handle all text messages in a streaming fashion - return TextMessage.create(Source.single("Hello ").concat(msg.getStreamedText())); + /** + * A handler that treats incoming messages as a name, + * and responds with a greeting to that name + */ + public static Flow greeter() { + return + Flow.create() + .collect(new JavaPartialFunction() { + @Override + public Message apply(Message msg, boolean isCheck) throws Exception { + if (isCheck) { + if (msg.isText()) return null; + else throw noMatch(); + } else return handleTextMessage(msg.asTextMessage()); + } + }); + } + + public static TextMessage handleTextMessage(TextMessage msg) { + if (msg.isStrict()) // optimization that directly creates a simple response... + { + return TextMessage.create("Hello " + msg.getStrictText()); + } else // ... this would suffice to handle all text messages in a streaming fashion + { + return TextMessage.create(Source.single("Hello ").concat(msg.getStreamedText())); } + } } diff --git a/akka-docs/rst/java/code/docs/http/javadsl/server/directives/CustomDirectivesExamplesTest.java b/akka-docs/rst/java/code/docs/http/javadsl/server/directives/CustomDirectivesExamplesTest.java new file mode 100644 index 0000000000..e2459752d4 --- /dev/null +++ b/akka-docs/rst/java/code/docs/http/javadsl/server/directives/CustomDirectivesExamplesTest.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2015-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.model.headers.RawHeader; +import akka.http.javadsl.server.Route; +import akka.http.javadsl.testkit.JUnitRouteTest; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Supplier; +import java.util.function.Function; + +public class CustomDirectivesExamplesTest extends JUnitRouteTest { + + //#labeling-1 + public Route getOrPut(Supplier inner) { + return get(inner).orElse(put(inner)); + } + //# + + @Test + public void testLabeling() { + // tests: + + //#labeling-2 + Route route = getOrPut(() -> complete("ok")); + //# + + testRoute(route).run(HttpRequest.GET("/")) + .assertStatusCode(StatusCodes.OK); + + testRoute(route).run(HttpRequest.PUT("/")) + .assertStatusCode(StatusCodes.OK); + + } + + + public static class MyCredentials { + private final String userId; + private final String secret; + + public MyCredentials(String userId, String secret) { + this.userId = userId; + this.secret = secret; + } + + public String getUserId() { + return userId; + } + + public boolean safeSecretVerification(String correct) { + // of course this is not what you would do in a real app + return correct.equals(secret); + } + + } + public static enum MyRole { + USER, + ADMIN + } + + //#composition-1 + // the composed custom directive + /** + * @param authenticate A function returns a set of roles for the credentials of a user + * @param inner Inner route to execute if the provided credentials has the given role + * if not, the request is completed with a + */ + public Route headerBasedAuth(Function> authenticate, MyRole requiredRole, Supplier inner) { + return headerValueByName("X-My-User-Id", (userId) -> { + return headerValueByName("X-My-User-Secret", (secret) -> { + Set userRoles = authenticate.apply(new MyCredentials(userId, secret)); + if (userRoles.contains(requiredRole)) { + return inner.get(); + } else { + return complete(StatusCodes.FORBIDDEN, "Role " + requiredRole + " required for access"); + } + }); + }); + } + //# + + @Test + public void testComposition() { + // tests: + + //#composition-2 + // a function for authentication + Function> authLogic = + (credentials) -> { + if (credentials.userId.equals("admin") && credentials.safeSecretVerification("secret")) + return new HashSet<>(Arrays.asList(MyRole.USER, MyRole.ADMIN)); + else + return Collections.emptySet(); + }; + + // and then using the custom route + Route route = get(() -> + path("admin", () -> + headerBasedAuth(authLogic, MyRole.ADMIN, () -> complete(StatusCodes.OK, "admin stuff")) + ) + ); + //# + + + testRoute(route).run(HttpRequest.GET("/admin")) + .assertStatusCode(StatusCodes.BAD_REQUEST); + + testRoute(route).run(HttpRequest.GET("/admin").addHeaders( + Arrays.asList(RawHeader.create("X-My-User-Id", "user"), RawHeader.create("X-My-User-Secret", "wrong")))) + .assertStatusCode(StatusCodes.FORBIDDEN); + + testRoute(route).run(HttpRequest.GET("/admin").addHeaders( + Arrays.asList(RawHeader.create("X-My-User-Id", "admin"), RawHeader.create("X-My-User-Secret", "secret")))) + .assertStatusCode(StatusCodes.OK); + + } + +} diff --git a/akka-docs/rst/java/code/docs/http/javadsl/server/directives/HostDirectivesExamplesTest.java b/akka-docs/rst/java/code/docs/http/javadsl/server/directives/HostDirectivesExamplesTest.java index 8568377493..03515d748c 100644 --- a/akka-docs/rst/java/code/docs/http/javadsl/server/directives/HostDirectivesExamplesTest.java +++ b/akka-docs/rst/java/code/docs/http/javadsl/server/directives/HostDirectivesExamplesTest.java @@ -7,15 +7,14 @@ package docs.http.javadsl.server.directives; import java.util.Arrays; import java.util.regex.Pattern; -import akka.http.javadsl.model.HttpMethod; +import org.junit.Test; + import akka.http.javadsl.model.HttpRequest; import akka.http.javadsl.model.StatusCodes; import akka.http.javadsl.model.headers.Host; -import akka.http.javadsl.server.*; +import akka.http.javadsl.server.Route; import akka.http.javadsl.testkit.JUnitRouteTest; -import org.junit.Test; - public class HostDirectivesExamplesTest extends JUnitRouteTest { @Test @@ -23,7 +22,7 @@ public class HostDirectivesExamplesTest extends JUnitRouteTest { //#host1 final Route matchListOfHosts = host( Arrays.asList("api.company.com", "rest.company.com"), - completeWithStatus(StatusCodes.OK)); + () -> complete(StatusCodes.OK)); testRoute(matchListOfHosts).run(HttpRequest.GET("/").addHeader(Host.create("api.company.com"))) .assertStatusCode(StatusCodes.OK); @@ -34,7 +33,7 @@ public class HostDirectivesExamplesTest extends JUnitRouteTest { public void testHostPredicate() { //#host2 final Route shortOnly = host(hostname -> hostname.length() < 10, - completeWithStatus(StatusCodes.OK)); + () -> complete(StatusCodes.OK)); testRoute(shortOnly).run(HttpRequest.GET("/").addHeader(Host.create("short.com"))) .assertStatusCode(StatusCodes.OK); @@ -47,10 +46,9 @@ public class HostDirectivesExamplesTest extends JUnitRouteTest { @Test public void testExtractHost() { //#extractHostname - final RequestVal host = RequestVals.host(); - final Route route = handleWith1(host, - (ctx, hn) -> ctx.complete("Hostname: " + hn)); + final Route route = extractHost(hn -> + complete("Hostname: " + hn)); testRoute(route).run(HttpRequest.GET("/").addHeader(Host.create("company.com", 9090))) .assertEntity("Hostname: company.com"); @@ -60,18 +58,12 @@ public class HostDirectivesExamplesTest extends JUnitRouteTest { @Test public void testMatchAndExtractHost() { //#matchAndExtractHost - final RequestVal hostPrefix = RequestVals - .matchAndExtractHost(Pattern.compile("api|rest")); - final Route hostPrefixRoute = handleWith1(hostPrefix, - (ctx, prefix) -> ctx.complete("Extracted prefix: " + prefix)); + final Route hostPrefixRoute = host(Pattern.compile("api|rest"), prefix -> + complete("Extracted prefix: " + prefix)); - final RequestVal hostPart = RequestVals.matchAndExtractHost(Pattern - .compile("public.(my|your)company.com")); - - final Route hostPartRoute = handleWith1( - hostPart, - (ctx, captured) -> ctx.complete("You came through " + captured + final Route hostPartRoute = host(Pattern.compile("public.(my|your)company.com"), captured -> + complete("You came through " + captured + " company")); final Route route = route(hostPrefixRoute, hostPartRoute); @@ -90,9 +82,10 @@ public class HostDirectivesExamplesTest extends JUnitRouteTest { public void testFailingMatchAndExtractHost() { //#failing-matchAndExtractHost // this will throw IllegalArgumentException - final RequestVal hostRegex = RequestVals - .matchAndExtractHost(Pattern - .compile("server-([0-9]).company.(com|net|org)")); + final Route hostRegex = host(Pattern.compile("server-([0-9]).company.(com|net|org)"), s -> + // will not reach here + complete(s) + ); //#failing-matchAndExtractHost } diff --git a/akka-docs/rst/java/code/docs/http/javadsl/server/directives/MethodDirectivesExamplesTest.java b/akka-docs/rst/java/code/docs/http/javadsl/server/directives/MethodDirectivesExamplesTest.java index 0947f127d5..39cf1bbbe7 100644 --- a/akka-docs/rst/java/code/docs/http/javadsl/server/directives/MethodDirectivesExamplesTest.java +++ b/akka-docs/rst/java/code/docs/http/javadsl/server/directives/MethodDirectivesExamplesTest.java @@ -4,20 +4,19 @@ package docs.http.javadsl.server.directives; -import akka.http.javadsl.model.HttpMethod; +import org.junit.Test; + import akka.http.javadsl.model.HttpMethods; import akka.http.javadsl.model.HttpRequest; import akka.http.javadsl.model.StatusCodes; -import akka.http.javadsl.server.*; +import akka.http.javadsl.server.Route; import akka.http.javadsl.testkit.JUnitRouteTest; -import org.junit.Test; - public class MethodDirectivesExamplesTest extends JUnitRouteTest { @Test public void testDelete() { //#delete - final Route route = delete(complete("This is a DELETE request.")); + final Route route = delete(() -> complete("This is a DELETE request.")); testRoute(route).run(HttpRequest.DELETE("/")).assertEntity( "This is a DELETE request."); @@ -27,7 +26,7 @@ public class MethodDirectivesExamplesTest extends JUnitRouteTest { @Test public void testGet() { //#get - final Route route = get(complete("This is a GET request.")); + final Route route = get(() -> complete("This is a GET request.")); testRoute(route).run(HttpRequest.GET("/")).assertEntity( "This is a GET request."); @@ -37,7 +36,7 @@ public class MethodDirectivesExamplesTest extends JUnitRouteTest { @Test public void testHead() { //#head - final Route route = head(complete("This is a HEAD request.")); + final Route route = head(() -> complete("This is a HEAD request.")); testRoute(route).run(HttpRequest.HEAD("/")).assertEntity( "This is a HEAD request."); @@ -47,7 +46,7 @@ public class MethodDirectivesExamplesTest extends JUnitRouteTest { @Test public void testOptions() { //#options - final Route route = options(complete("This is a OPTIONS request.")); + final Route route = options(() -> complete("This is a OPTIONS request.")); testRoute(route).run(HttpRequest.OPTIONS("/")).assertEntity( "This is a OPTIONS request."); @@ -57,7 +56,7 @@ public class MethodDirectivesExamplesTest extends JUnitRouteTest { @Test public void testPatch() { //#patch - final Route route = patch(complete("This is a PATCH request.")); + final Route route = patch(() -> complete("This is a PATCH request.")); testRoute(route).run(HttpRequest.PATCH("/").withEntity("patch content")) .assertEntity("This is a PATCH request."); @@ -67,7 +66,7 @@ public class MethodDirectivesExamplesTest extends JUnitRouteTest { @Test public void testPost() { //#post - final Route route = post(complete("This is a POST request.")); + final Route route = post(() -> complete("This is a POST request.")); testRoute(route).run(HttpRequest.POST("/").withEntity("post content")) .assertEntity("This is a POST request."); @@ -77,7 +76,7 @@ public class MethodDirectivesExamplesTest extends JUnitRouteTest { @Test public void testPut() { //#put - final Route route = put(complete("This is a PUT request.")); + final Route route = put(() -> complete("This is a PUT request.")); testRoute(route).run(HttpRequest.PUT("/").withEntity("put content")) .assertEntity("This is a PUT request."); @@ -88,7 +87,7 @@ public class MethodDirectivesExamplesTest extends JUnitRouteTest { public void testMethodExample() { //#method-example final Route route = method(HttpMethods.PUT, - complete("This is a PUT request.")); + () -> complete("This is a PUT request.")); testRoute(route).run(HttpRequest.PUT("/").withEntity("put content")) .assertEntity("This is a PUT request."); @@ -101,15 +100,15 @@ public class MethodDirectivesExamplesTest extends JUnitRouteTest { @Test public void testExtractMethodExample() { //#extractMethod - final RequestVal requestMethod = RequestVals.requestMethod(); - final Route otherMethod = handleWith1( - requestMethod, - (ctx, method) -> ctx.complete("This " + method.value() - + " request, clearly is not a GET!")); - - final Route route = route(get(complete("This is a GET request.")), - otherMethod); + final Route route = route( + get(() -> + complete("This is a GET request.") + ), + extractMethod(method -> + complete("This " + method.value() + " request, clearly is not a GET!") + ) + ); testRoute(route).run(HttpRequest.GET("/")).assertEntity( "This is a GET request."); @@ -121,4 +120,31 @@ public class MethodDirectivesExamplesTest extends JUnitRouteTest { "This HEAD request, clearly is not a GET!"); //#extractMethod } + + @Test + public void testOverrideMethodWithParameter() { + //#overrideMethodWithParameter + + final Route route = route( + overrideMethodWithParameter("method", () -> + route( + get(() -> complete("This looks like a GET request.")), + post(() -> complete("This looks like a POST request.")) + ) + ) + ); + + + // tests: + testRoute(route).run(HttpRequest.GET("/?method=POST")).assertEntity( + "This looks like a POST request."); + + testRoute(route).run(HttpRequest.POST("/?method=get")) + .assertEntity("This looks like a GET request."); + + testRoute(route).run(HttpRequest.GET("/?method=hallo")).assertEntity( + "The server either does not recognize the request method, or it lacks the ability to fulfill the request."); + + //#overrideMethodWithParameter + } } diff --git a/akka-docs/rst/java/code/docs/http/javadsl/server/testkit/MyAppService.java b/akka-docs/rst/java/code/docs/http/javadsl/server/testkit/MyAppService.java index f0b63293e2..88a82ee69b 100644 --- a/akka-docs/rst/java/code/docs/http/javadsl/server/testkit/MyAppService.java +++ b/akka-docs/rst/java/code/docs/http/javadsl/server/testkit/MyAppService.java @@ -5,29 +5,51 @@ package docs.http.javadsl.server.testkit; //#simple-app -import akka.http.javadsl.server.*; -import akka.http.javadsl.server.values.Parameters; -public class MyAppService extends HttpApp { - RequestVal x = Parameters.doubleValue("x"); - RequestVal y = Parameters.doubleValue("y"); +import akka.actor.ActorSystem; +import akka.http.javadsl.ConnectHttp; +import akka.http.javadsl.Http; +import akka.http.javadsl.server.AllDirectives; +import akka.http.javadsl.server.Route; +import akka.http.javadsl.server.StringUnmarshallers; +import akka.http.javadsl.server.examples.simple.SimpleServerApp; +import akka.stream.ActorMaterializer; - public RouteResult add(RequestContext ctx, double x, double y) { - return ctx.complete("x + y = " + (x + y)); - } +import java.io.IOException; - @Override - public Route createRoute() { - return - route( - get( - pathPrefix("calculator").route( - path("add").route( - handleReflectively(this, "add", x, y) - ) - ) - ) - ); - } +public class MyAppService extends AllDirectives { + + public String add(double x, double y) { + return "x + y = " + (x + y); + } + + public Route createRoute() { + return + get(() -> + pathPrefix("calculator", () -> + path("add", () -> + parameter(StringUnmarshallers.DOUBLE, "x", x -> + parameter(StringUnmarshallers.DOUBLE, "y", y -> + complete(add(x, y)) + ) + ) + ) + ) + ); + } + + public static void main(String[] args) throws IOException { + final ActorSystem system = ActorSystem.create(); + final ActorMaterializer materializer = ActorMaterializer.create(system); + + final SimpleServerApp app = new SimpleServerApp(); + + final ConnectHttp host = ConnectHttp.toHost("127.0.0.1"); + + Http.get(system).bindAndHandle(app.createRoute().flow(system, materializer), host, materializer); + + System.console().readLine("Type RETURN to exit..."); + system.terminate(); + } } -//#simple-app \ No newline at end of file +//#simple-app diff --git a/akka-docs/rst/java/http/client-side/connection-level.rst b/akka-docs/rst/java/http/client-side/connection-level.rst index 558237a521..0cc3438a26 100644 --- a/akka-docs/rst/java/http/client-side/connection-level.rst +++ b/akka-docs/rst/java/http/client-side/connection-level.rst @@ -61,13 +61,7 @@ explicitly drained by attaching it to ``Sink.ignore()``. 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. - -It should be noted that Akka Streams provide various timeout functionality so any API that uses streams can benefit -from the stream stages such as ``idleTimeout``, ``backpressureTimeout``, ``completionTimeout``, ``initialTimeout`` -and ``throttle``. To learn more about these refer to their documentation in Akka Streams (and Java Doc). - +Timeouts are configured in the same way for Scala and Akka. See :ref:`http-timeouts-java` . .. _http-client-layer-java: diff --git a/akka-docs/rst/java/http/common/de-coding.rst b/akka-docs/rst/java/http/common/de-coding.rst new file mode 100644 index 0000000000..022c19c907 --- /dev/null +++ b/akka-docs/rst/java/http/common/de-coding.rst @@ -0,0 +1,16 @@ +Encoding / Decoding +=================== + +The `HTTP spec`_ defines a ``Content-Encoding`` header, which signifies whether the entity body of an HTTP message is +"encoded" and, if so, by which algorithm. The only commonly used content encodings are compression algorithms. + +Currently Akka HTTP supports the compression and decompression of HTTP requests and responses with the ``gzip`` or +``deflate`` encodings. +The core logic for this lives in the `akka.http.scaladsl.coding`_ package. + +The support is not enabled automatically, but must be explicitly requested. +For enabling message encoding/decoding with :ref:`Routing DSL ` see the :ref:`CodingDirectives`. + +.. _HTTP spec: http://tools.ietf.org/html/rfc7231#section-3.1.2.1 +.. _akka.http.scaladsl.coding: @github@/akka-http/src/main/scala/akka/http/scaladsl/coding + diff --git a/akka-docs/rst/java/http/common/index.rst b/akka-docs/rst/java/http/common/index.rst new file mode 100644 index 0000000000..d156611329 --- /dev/null +++ b/akka-docs/rst/java/http/common/index.rst @@ -0,0 +1,21 @@ +.. _http-java-common: + +Common Abstractions (Client- and Server-Side) +============================================= + +HTTP and related specifications define a great number of concepts and functionality that is not specific to either +HTTP's client- or server-side since they are meaningful on both end of an HTTP connection. +The documentation for their counterparts in Akka HTTP lives in this section rather than in the ones for the +:ref:`Client-Side API `, :ref:`http-low-level-server-side-api` or :ref:`http-high-level-server-side-api`, +which are specific to one side only. + + +.. toctree:: + :maxdepth: 2 + + ../http-model + marshalling + unmarshalling + de-coding + json-support + timeouts diff --git a/akka-docs/rst/java/http/routing-dsl/json-support.rst b/akka-docs/rst/java/http/common/json-support.rst similarity index 63% rename from akka-docs/rst/java/http/routing-dsl/json-support.rst rename to akka-docs/rst/java/http/common/json-support.rst index 6052d3d58f..0fd1ee1514 100644 --- a/akka-docs/rst/java/http/routing-dsl/json-support.rst +++ b/akka-docs/rst/java/http/common/json-support.rst @@ -18,13 +18,13 @@ Json Support via Jackson To make use of the support module, you need to add a dependency on `akka-http-jackson-experimental`. -Use ``akka.http.javadsl.marshallers.jackson.Jackson.jsonAs[T]`` to create a ``RequestVal`` which expects the request -body to be of type ``application/json`` and converts it to ``T`` using Jackson. +Use ``akka.http.javadsl.marshallers.jackson.Jackson.unmarshaller(T.class)`` to create an ``Unmarshaller`` which expects the request +body (HttpEntity) to be of type ``application/json`` and converts it to ``T`` using Jackson. See `this example`__ in the sources for an example. -Use ``akka.http.javadsl.marshallers.jackson.Jackson.json[T]`` to create a ``Marshaller`` which can be used with -``RequestContext.completeAs`` to convert a POJO to an HttpResponse. +Use ``akka.http.javadsl.marshallers.jackson.Jackson.marshaller(T.class)`` to create a ``Marshaller`` which can be used with +``RequestContext.complete`` or ``RouteDirectives.complete`` to convert a POJO to an HttpResponse. .. _jackson: https://github.com/FasterXML/jackson diff --git a/akka-docs/rst/java/http/common/marshalling.rst b/akka-docs/rst/java/http/common/marshalling.rst new file mode 100644 index 0000000000..cf098e1b88 --- /dev/null +++ b/akka-docs/rst/java/http/common/marshalling.rst @@ -0,0 +1,166 @@ +.. _http-marshalling-java: + +Marshalling +=========== +TODO overhaul for Java + +"Marshalling" is the process of converting a higher-level (object) structure into some kind of lower-level +representation, often a "wire format". Other popular names for it are "Serialization" or "Pickling". + +In Akka HTTP "Marshalling" means the conversion of an object of type ``T`` into a lower-level target type, +e.g. a ``MessageEntity`` (which forms the "entity body" of an HTTP request or response) or a full ``HttpRequest`` or +``HttpResponse``. + + +Basic Design +------------ + +Marshalling of instances of type ``A`` into instances of type ``B`` is performed by a ``Marshaller[A, B]``. +Akka HTTP also predefines a number of helpful aliases for the types of marshallers that you'll likely work with most: + +.. includecode2:: /../../akka-http/src/main/scala/akka/http/scaladsl/marshalling/package.scala + :snippet: marshaller-aliases + +Contrary to what you might initially expect ``Marshaller[A, B]`` is not a plain function ``A => B`` but rather +essentially a function ``A => Future[List[Marshalling[B]]]``. +Let's dissect this rather complicated looking signature piece by piece to understand what marshallers are designed this +way. +Given an instance of type ``A`` a ``Marshaller[A, B]`` produces: + +1. A ``Future``: This is probably quite clear. Marshallers are not required to synchronously produce a result, so instead +they return a future, which allows for asynchronicity in the marshalling process. + +2. of ``List``: Rather than only a single target representation for ``A`` marshallers can offer several ones. Which +one will be rendered onto the wire in the end is decided by content negotiation. +For example, the ``ToEntityMarshaller[OrderConfirmation]`` might offer a JSON as well as an XML representation. +The client can decide through the addition of an ``Accept`` request header which one is preferred. If the client doesn't +express a preference the first representation is picked. + +3. of ``Marshalling[B]``: Rather than returning an instance of ``B`` directly marshallers first produce a +``Marshalling[B]``. This allows for querying the ``MediaType`` and potentially the ``HttpCharset`` that the marshaller +will produce before the actual marshalling is triggered. Apart from enabling content negotiation this design allows for +delaying the actual construction of the marshalling target instance to the very last moment when it is really needed. + +This is how ``Marshalling`` is defined: + +.. includecode2:: /../../akka-http/src/main/scala/akka/http/scaladsl/marshalling/Marshaller.scala + :snippet: marshalling + + +Predefined Marshallers +---------------------- + +Akka HTTP already predefines a number of marshallers for the most common types. +Specifically these are: + +- PredefinedToEntityMarshallers_ + + - ``Array[Byte]`` + - ``ByteString`` + - ``Array[Char]`` + - ``String`` + - ``akka.http.scaladsl.model.FormData`` + - ``akka.http.scaladsl.model.MessageEntity`` + - ``T <: akka.http.scaladsl.model.Multipart`` + +- PredefinedToResponseMarshallers_ + + - ``T``, if a ``ToEntityMarshaller[T]`` is available + - ``HttpResponse`` + - ``StatusCode`` + - ``(StatusCode, T)``, if a ``ToEntityMarshaller[T]`` is available + - ``(Int, T)``, if a ``ToEntityMarshaller[T]`` is available + - ``(StatusCode, immutable.Seq[HttpHeader], T)``, if a ``ToEntityMarshaller[T]`` is available + - ``(Int, immutable.Seq[HttpHeader], T)``, if a ``ToEntityMarshaller[T]`` is available + +- PredefinedToRequestMarshallers_ + + - ``HttpRequest`` + - ``Uri`` + - ``(HttpMethod, Uri, T)``, if a ``ToEntityMarshaller[T]`` is available + - ``(HttpMethod, Uri, immutable.Seq[HttpHeader], T)``, if a ``ToEntityMarshaller[T]`` is available + +- GenericMarshallers_ + + - ``Marshaller[Throwable, T]`` + - ``Marshaller[Option[A], B]``, if a ``Marshaller[A, B]`` and an ``EmptyValue[B]`` is available + - ``Marshaller[Either[A1, A2], B]``, if a ``Marshaller[A1, B]`` and a ``Marshaller[A2, B]`` is available + - ``Marshaller[Future[A], B]``, if a ``Marshaller[A, B]`` is available + - ``Marshaller[Try[A], B]``, if a ``Marshaller[A, B]`` is available + +.. _PredefinedToEntityMarshallers: @github@/akka-http/src/main/scala/akka/http/scaladsl/marshalling/PredefinedToEntityMarshallers.scala +.. _PredefinedToResponseMarshallers: @github@/akka-http/src/main/scala/akka/http/scaladsl/marshalling/PredefinedToResponseMarshallers.scala +.. _PredefinedToRequestMarshallers: @github@/akka-http/src/main/scala/akka/http/scaladsl/marshalling/PredefinedToRequestMarshallers.scala +.. _GenericMarshallers: @github@/akka-http/src/main/scala/akka/http/scaladsl/marshalling/GenericMarshallers.scala + + +Implicit Resolution +------------------- + +The marshalling infrastructure of Akka HTTP relies on a type-class based approach, which means that ``Marshaller`` +instances from a certain type ``A`` to a certain type ``B`` have to be available implicitly. + +The implicits for most of the predefined marshallers in Akka HTTP are provided through the companion object of the +``Marshaller`` trait. This means that they are always available and never need to be explicitly imported. +Additionally, you can simply "override" them by bringing your own custom version into local scope. + + +Custom Marshallers +------------------ + +Akka HTTP gives you a few convenience tools for constructing marshallers for your own types. +Before you do that you need to think about what kind of marshaller you want to create. +If all your marshaller needs to produce is a ``MessageEntity`` then you should probably provide a +``ToEntityMarshaller[T]``. The advantage here is that it will work on both the client- as well as the server-side since +a ``ToResponseMarshaller[T]`` as well as a ``ToRequestMarshaller[T]`` can automatically be created if a +``ToEntityMarshaller[T]`` is available. + +If, however, your marshaller also needs to set things like the response status code, the request method, the request URI +or any headers then a ``ToEntityMarshaller[T]`` won't work. You'll need to fall down to providing a +``ToResponseMarshaller[T]`` or a ``ToRequestMarshaller[T]`` directly. + +For writing you own marshallers you won't have to "manually" implement the ``Marshaller`` trait directly. +Rather, it should be possible to use one of the convenience construction helpers defined on the ``Marshaller`` +companion: + +.. includecode2:: /../../akka-http/src/main/scala/akka/http/scaladsl/marshalling/Marshaller.scala + :snippet: marshaller-creation + + +Deriving Marshallers +-------------------- + +Sometimes you can save yourself some work by reusing existing marshallers for your custom ones. +The idea is to "wrap" an existing marshaller with some logic to "re-target" it to your type. + +In this regard wrapping a marshaller can mean one or both of the following two things: + +- Transform the input before it reaches the wrapped marshaller +- Transform the output of the wrapped marshaller + +For the latter (transforming the output) you can use ``baseMarshaller.map``, which works exactly as it does for functions. +For the former (transforming the input) you have four alternatives: + +- ``baseMarshaller.compose`` +- ``baseMarshaller.composeWithEC`` +- ``baseMarshaller.wrap`` +- ``baseMarshaller.wrapWithEC`` + +``compose`` works just like it does for functions. +``wrap`` is a compose that allows you to also change the ``ContentType`` that the marshaller marshals to. +The ``...WithEC`` variants allow you to receive an ``ExecutionContext`` internally if you need one, without having to +depend on one being available implicitly at the usage site. + + +Using Marshallers +----------------- + +In many places throughput Akka HTTP marshallers are used implicitly, e.g. when you define how to :ref:`-complete-` a +request using the :ref:`Routing DSL `. + +However, you can also use the marshalling infrastructure directly if you wish, which can be useful for example in tests. +The best entry point for this is the ``akka.http.scaladsl.marshalling.Marshal`` object, which you can use like this: + +.. TODO rewrite for Java +.. .. includecode2:: ../../code/docs/http/scaladsl/MarshalSpec.scala + :snippet: use marshal diff --git a/akka-docs/rst/java/http/common/timeouts.rst b/akka-docs/rst/java/http/common/timeouts.rst new file mode 100644 index 0000000000..fe38391e6f --- /dev/null +++ b/akka-docs/rst/java/http/common/timeouts.rst @@ -0,0 +1,76 @@ +.. _http-timeouts-java: + +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-java: + +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/java/http/common/unmarshalling.rst b/akka-docs/rst/java/http/common/unmarshalling.rst new file mode 100644 index 0000000000..9aaea15c66 --- /dev/null +++ b/akka-docs/rst/java/http/common/unmarshalling.rst @@ -0,0 +1,124 @@ +.. _http-unmarshalling-java: + +Unmarshalling +============= +TODO overhaul for Java + +"Unmarshalling" is the process of converting some kind of a lower-level representation, often a "wire format", into a +higher-level (object) structure. Other popular names for it are "Deserialization" or "Unpickling". + +In Akka HTTP "Unmarshalling" means the conversion of a lower-level source object, e.g. a ``MessageEntity`` +(which forms the "entity body" of an HTTP request or response) or a full ``HttpRequest`` or ``HttpResponse``, +into an instance of type ``T``. + + +Basic Design +------------ + +Unmarshalling of instances of type ``A`` into instances of type ``B`` is performed by an ``Unmarshaller[A, B]``. +Akka HTTP also predefines a number of helpful aliases for the types of unmarshallers that you'll likely work with most: + +.. includecode2:: /../../akka-http/src/main/scala/akka/http/scaladsl/unmarshalling/package.scala + :snippet: unmarshaller-aliases + +At its core an ``Unmarshaller[A, B]`` is very similar to a ``Function>`` and as such quite a bit simpler +than its :ref:`marshalling ` counterpart. The process of unmarshalling does not have to support +content negotiation which saves two additional layers of indirection that are required on the marshalling side. + + +Predefined Unmarshallers +------------------------ + +Akka HTTP already predefines a number of marshallers for the most common types. +Specifically these are: + +- PredefinedFromStringUnmarshallers_ + + - ``Byte`` + - ``Short`` + - ``Int`` + - ``Long`` + - ``Float`` + - ``Double`` + - ``Boolean`` + +- PredefinedFromEntityUnmarshallers_ + + - ``Array[Byte]`` + - ``ByteString`` + - ``Array[Char]`` + - ``String`` + - ``akka.http.scaladsl.model.FormData`` + +- GenericUnmarshallers_ + + - ``Unmarshaller[T, T]`` (identity unmarshaller) + - ``Unmarshaller[Option[A], B]``, if an ``Unmarshaller[A, B]`` is available + - ``Unmarshaller[A, Option[B]]``, if an ``Unmarshaller[A, B]`` is available + +.. _PredefinedFromStringUnmarshallers: @github@/akka-http/src/main/scala/akka/http/scaladsl/unmarshalling/PredefinedFromStringUnmarshallers.scala +.. _PredefinedFromEntityUnmarshallers: @github@/akka-http/src/main/scala/akka/http/scaladsl/unmarshalling/PredefinedFromEntityUnmarshallers.scala +.. _GenericUnmarshallers: @github@/akka-http/src/main/scala/akka/http/scaladsl/unmarshalling/GenericUnmarshallers.scala + + +Implicit Resolution +------------------- + +The unmarshalling infrastructure of Akka HTTP relies on a type-class based approach, which means that ``Unmarshaller`` +instances from a certain type ``A`` to a certain type ``B`` have to be available implicitly. + +The implicits for most of the predefined unmarshallers in Akka HTTP are provided through the companion object of the +``Unmarshaller`` trait. This means that they are always available and never need to be explicitly imported. +Additionally, you can simply "override" them by bringing your own custom version into local scope. + + +Custom Unmarshallers +-------------------- + +Akka HTTP gives you a few convenience tools for constructing unmarshallers for your own types. +Usually you won't have to "manually" implement the ``Unmarshaller`` trait directly. +Rather, it should be possible to use one of the convenience construction helpers defined on the ``Marshaller`` +companion: + +TODO rewrite sample for Java + +.. +.. includecode2:: /../../akka-http/src/main/scala/akka/http/scaladsl/unmarshalling/Unmarshaller.scala + :snippet: unmarshaller-creation + + +Deriving Unmarshallers +---------------------- + +Sometimes you can save yourself some work by reusing existing unmarshallers for your custom ones. +The idea is to "wrap" an existing unmarshaller with some logic to "re-target" it to your type. + +Usually what you want to do is to transform the output of some existing unmarshaller and convert it to your type. +For this type of unmarshaller transformation Akka HTTP defines these methods: + +- ``baseUnmarshaller.transform`` +- ``baseUnmarshaller.map`` +- ``baseUnmarshaller.mapWithInput`` +- ``baseUnmarshaller.flatMap`` +- ``baseUnmarshaller.flatMapWithInput`` +- ``baseUnmarshaller.recover`` +- ``baseUnmarshaller.withDefaultValue`` +- ``baseUnmarshaller.mapWithCharset`` (only available for FromEntityUnmarshallers) +- ``baseUnmarshaller.forContentTypes`` (only available for FromEntityUnmarshallers) + +The method signatures should make their semantics relatively clear. + + +Using Unmarshallers +------------------- + +In many places throughput Akka HTTP unmarshallers are used implicitly, e.g. when you want to access the :ref:`-entity-` +of a request using the :ref:`Routing DSL `. + +However, you can also use the unmarshalling infrastructure directly if you wish, which can be useful for example in tests. +The best entry point for this is the ``akka.http.scaladsl.unmarshalling.Unmarshal`` object, which you can use like this: + +.. TODO rewrite for java +.. .. includecode2:: ../../code/docs/http/scaladsl/UnmarshalSpec.scala + :snippet: use unmarshal + diff --git a/akka-docs/rst/java/http/index.rst b/akka-docs/rst/java/http/index.rst index 941d803ceb..1a086d69b9 100644 --- a/akka-docs/rst/java/http/index.rst +++ b/akka-docs/rst/java/http/index.rst @@ -36,7 +36,8 @@ akka-http-jackson server-side/websocket-support routing-dsl/index client-side/index - server-side-https-support + common/index configuration + server-side-https-support -.. _jackson: https://github.com/FasterXML/jackson \ No newline at end of file +.. _jackson: https://github.com/FasterXML/jackson diff --git a/akka-docs/rst/java/http/routing-dsl/directives/alphabetically.rst b/akka-docs/rst/java/http/routing-dsl/directives/alphabetically.rst new file mode 100644 index 0000000000..c992c44ff8 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/alphabetically.rst @@ -0,0 +1,148 @@ +.. _Predefined Directives-java: + +Predefined Directives (alphabetically) +====================================== + +================================================ ============================================================================ +Directive Description +================================================ ============================================================================ +:ref:`-authenticateBasic-java-` Wraps the inner route with Http Basic authentication support using a given ``Authenticator`` +:ref:`-authenticateBasicAsync-java-` Wraps the inner route with Http Basic authentication support using a given ``AsyncAuthenticator`` +:ref:`-authenticateBasicPF-java-` Wraps the inner route with Http Basic authentication support using a given ``AuthenticatorPF`` +:ref:`-authenticateBasicPFAsync-java-` Wraps the inner route with Http Basic authentication support using a given ``AsyncAuthenticatorPF`` +:ref:`-authenticateOAuth2-java-` Wraps the inner route with OAuth Bearer Token authentication support using a given ``AuthenticatorPF`` +:ref:`-authenticateOAuth2Async-java-` Wraps the inner route with OAuth Bearer Token authentication support using a given ``AsyncAuthenticator`` +:ref:`-authenticateOAuth2PF-java-` Wraps the inner route with OAuth Bearer Token authentication support using a given ``AuthenticatorPF`` +:ref:`-authenticateOAuth2PFAsync-java-` Wraps the inner route with OAuth Bearer Token authentication support using a given ``AsyncAuthenticatorPF`` +:ref:`-authenticateOrRejectWithChallenge-java-` Lifts an authenticator function into a directive +:ref:`-authorize-java-` Applies the given authorization check to the request +:ref:`-authorizeAsync-java-` Applies the given asynchronous authorization check to the request +:ref:`-cancelRejection-java-` Adds a ``TransformationRejection`` cancelling all rejections equal to the given one to the rejections potentially coming back from the inner route. +:ref:`-cancelRejections-java-` Adds a ``TransformationRejection`` cancelling all matching rejections to the rejections potentially coming back from the inner route +:ref:`-complete-java-` Completes the request using the given arguments +:ref:`-completeOrRecoverWith-java-` "Unwraps" a ``CompletionStage`` and runs the inner route when the future has failed with the error as an extraction of type ``Throwable`` +:ref:`-completeWith-java-` Uses the marshaller for a given type to extract a completion function +:ref:`-conditional-java-` Wraps its inner route with support for conditional requests as defined by http://tools.ietf.org/html/rfc7232 +:ref:`-cookie-java-` Extracts the ``HttpCookie`` with the given name +:ref:`-decodeRequest-java-` Decompresses the request if it is ``gzip`` or ``deflate`` compressed +:ref:`-decodeRequestWith-java-` Decodes the incoming request using one of the given decoders +:ref:`-delete-java-` Rejects all non-DELETE requests +:ref:`-deleteCookie-java-` Adds a ``Set-Cookie`` response header expiring the given cookies +:ref:`-encodeResponse-java-` Encodes the response with the encoding that is requested by the client via the ``Accept-Encoding`` header (``NoCoding``, ``Gzip`` and ``Deflate``) +:ref:`-encodeResponseWith-java-` Encodes the response with the encoding that is requested by the client via the ``Accept-Encoding`` header (from a user-defined set) +:ref:`-entity-java-` Extracts the request entity unmarshalled to a given type +:ref:`-extract-java-` Extracts a single value using a ``RequestContext ⇒ T`` function +:ref:`-extractClientIP-java-` Extracts the client's IP from either the ``X-Forwarded-``, ``Remote-Address`` or ``X-Real-IP`` header +:ref:`-extractCredentials-java-` Extracts the potentially present ``HttpCredentials`` provided with the request's ``Authorization`` header +:ref:`-extractExecutionContext-java-` Extracts the ``ExecutionContext`` from the ``RequestContext`` +:ref:`-extractMaterializer-java-` Extracts the ``Materializer`` from the ``RequestContext`` +:ref:`-extractHost-java-` Extracts the hostname part of the Host request header value +:ref:`-extractLog-java-` Extracts the ``LoggingAdapter`` from the ``RequestContext`` +:ref:`-extractMethod-java-` Extracts the request method +:ref:`-extractRequest-java-` Extracts the current ``HttpRequest`` instance +:ref:`-extractRequestContext-java-` Extracts the ``RequestContext`` itself +:ref:`-extractScheme-java-` Extracts the URI scheme from the request +:ref:`-extractSettings-java-` Extracts the ``RoutingSettings`` from the ``RequestContext`` +:ref:`-extractUnmatchedPath-java-` Extracts the yet unmatched path from the ``RequestContext`` +:ref:`-extractUri-java-` Extracts the complete request URI +:ref:`-failWith-java-` Bubbles the given error up the response chain where it is dealt with by the closest :ref:`-handleExceptions-java-` directive and its ``ExceptionHandler`` +:ref:`-fileUpload-java-` Provides a stream of an uploaded file from a multipart request +:ref:`-formField-java-` Extracts an HTTP form field from the request +:ref:`-formFieldMap-java-` Extracts a number of HTTP form field from the request as a ``Map`` +:ref:`-formFieldMultiMap-java-` Extracts a number of HTTP form field from the request as a ``Map`` +:ref:`-formFieldList-java-` Extracts a number of HTTP form field from the request as a ``List>`` +:ref:`-get-java-` Rejects all non-GET requests +:ref:`-getFromBrowseableDirectories-java-` Serves the content of the given directories as a file-system browser, i.e. files are sent and directories served as browseable listings +:ref:`-getFromBrowseableDirectory-java-` Serves the content of the given directory as a file-system browser, i.e. files are sent and directories served as browseable listings +:ref:`-getFromDirectory-java-` Completes GET requests with the content of a file underneath a given file-system directory +:ref:`-getFromFile-java-` Completes GET requests with the content of a given file +:ref:`-getFromResource-java-` Completes GET requests with the content of a given class-path resource +:ref:`-getFromResourceDirectory-java-` Completes GET requests with the content of a file underneath a given "class-path resource directory" +:ref:`-handleExceptions-java-` Transforms exceptions thrown during evaluation of the inner route using the given ``ExceptionHandler`` +:ref:`-handleRejections-java-` Transforms rejections produced by the inner route using the given ``RejectionHandler`` +:ref:`-handleWebSocketMessages-java-` Handles websocket requests with the given handler and rejects other requests with an ``ExpectedWebSocketRequestRejection`` +:ref:`-handleWebSocketMessagesForProtocol-java-` Handles websocket requests with the given handler if the subprotocol matches and rejects other requests with an ``ExpectedWebSocketRequestRejection`` or an ``UnsupportedWebSocketSubprotocolRejection``. +:ref:`-handleWith-java-` Completes the request using a given function +:ref:`-head-java-` Rejects all non-HEAD requests +:ref:`-headerValue-java-` Extracts an HTTP header value using a given ``HttpHeader ⇒ Option`` function +:ref:`-headerValueByName-java-` Extracts the value of the first HTTP request header with a given name +:ref:`-headerValueByType-java-` Extracts the first HTTP request header of the given type +:ref:`-headerValuePF-java-` Extracts an HTTP header value using a given ``PartialFunction`` +:ref:`-host-java-` Rejects all requests with a non-matching host name +:ref:`-listDirectoryContents-java-` Completes GET requests with a unified listing of the contents of all given file-system directories +:ref:`-logRequest-java-` Produces a log entry for every incoming request +:ref:`-logRequestResult-java-` Produces a log entry for every incoming request and ``RouteResult`` +:ref:`-logResult-java-` Produces a log entry for every ``RouteResult`` +:ref:`-mapInnerRoute-java-` Transforms its inner ``Route`` with a ``Route => Route`` function +:ref:`-mapRejections-java-` Transforms rejections from a previous route with an ``List`` function +:ref:`-mapRequest-java-` Transforms the request with an ``HttpRequest => HttpRequest`` function +:ref:`-mapRequestContext-java-` Transforms the ``RequestContext`` with a ``RequestContext => RequestContext`` function +:ref:`-mapResponse-java-` Transforms the response with an ``HttpResponse => HttpResponse`` function +:ref:`-mapResponseEntity-java-` Transforms the response entity with an ``ResponseEntity ⇒ ResponseEntity`` function +:ref:`-mapResponseHeaders-java-` Transforms the response headers with an ``List`` function +:ref:`-mapRouteResult-java-` Transforms the ``RouteResult`` with a ``RouteResult ⇒ RouteResult`` function +:ref:`-mapRouteResultFuture-java-` Transforms the ``RouteResult`` future with a ``CompletionStage`` function +:ref:`-mapRouteResultPF-java-` Transforms the ``RouteResult`` with a ``PartialFunction`` +:ref:`-mapRouteResultWith-java-` Transforms the ``RouteResult`` with a ``RouteResult ⇒ CompletionStage`` function +:ref:`-mapRouteResultWithPF-java-` Transforms the ``RouteResult`` with a ``PartialFunction`` +:ref:`-mapSettings-java-` Transforms the ``RoutingSettings`` with a ``RoutingSettings ⇒ RoutingSettings`` function +:ref:`-mapUnmatchedPath-java-` Transforms the ``unmatchedPath`` of the ``RequestContext`` using a ``Uri.Path ⇒ Uri.Path`` function +:ref:`-method-java-` Rejects all requests whose HTTP method does not match the given one +:ref:`-onComplete-java-` "Unwraps" a ``CompletionStage`` and runs the inner route after future completion with the future's value as an extraction of type ``Try`` +:ref:`-onSuccess-java-` "Unwraps" a ``CompletionStage`` and runs the inner route after future completion with the future's value as an extraction of type ``T`` +:ref:`-optionalCookie-java-` Extracts the ``HttpCookiePair`` with the given name as an ``Option`` +:ref:`-optionalHeaderValue-java-` Extracts an optional HTTP header value using a given ``HttpHeader ⇒ Option`` function +:ref:`-optionalHeaderValueByName-java-` Extracts the value of the first optional HTTP request header with a given name +:ref:`-optionalHeaderValueByType-java-` Extracts the first optional HTTP request header of the given type +:ref:`-optionalHeaderValuePF-java-` Extracts an optional HTTP header value using a given ``PartialFunction`` +:ref:`-options-java-` Rejects all non-OPTIONS requests +:ref:`-overrideMethodWithParameter-java-` Changes the request method to the value of the specified query parameter +:ref:`-parameter-java-` Extracts a query parameter value from the request +:ref:`-parameterMap-java-` Extracts the request's query parameters as a ``Map`` +:ref:`-parameterMultiMap-java-` Extracts the request's query parameters as a ``Map>`` +:ref:`-parameterList-java-` Extracts the request's query parameters as a ``Seq>`` +:ref:`-pass-java-` Always simply passes the request on to its inner route, i.e. doesn't do anything, neither with the request nor the response +:ref:`-patch-java-` Rejects all non-PATCH requests +:ref:`-path-java-` Applies the given ``PathMatcher`` to the remaining unmatched path after consuming a leading slash +:ref:`-pathEnd-java-` Only passes on the request to its inner route if the request path has been matched completely +:ref:`-pathEndOrSingleSlash-java-` Only passes on the request to its inner route if the request path has been matched completely or only consists of exactly one remaining slash +:ref:`-pathPrefix-java-` Applies the given ``PathMatcher`` to a prefix of the remaining unmatched path after consuming a leading slash +:ref:`-pathPrefixTest-java-` Checks whether the unmatchedPath has a prefix matched by the given ``PathMatcher`` after implicitly consuming a leading slash +:ref:`-pathSingleSlash-java-` Only passes on the request to its inner route if the request path consists of exactly one remaining slash +:ref:`-pathSuffix-java-` Applies the given ``PathMatcher`` to a suffix of the remaining unmatched path (Caution: check java!) +:ref:`-pathSuffixTest-java-` Checks whether the unmatched path has a suffix matched by the given ``PathMatcher`` (Caution: check java!) +:ref:`-post-java-` Rejects all non-POST requests +:ref:`-provide-java-` Injects a given value into a directive +:ref:`-put-java-` Rejects all non-PUT requests +:ref:`-rawPathPrefix-java-` Applies the given matcher directly to a prefix of the unmatched path of the ``RequestContext``, without implicitly consuming a leading slash +:ref:`-rawPathPrefixTest-java-` Checks whether the unmatchedPath has a prefix matched by the given ``PathMatcher`` +:ref:`-recoverRejections-java-` Transforms rejections from the inner route with an ``List`` function +:ref:`-redirect-java-` Completes the request with redirection response of the given type to the given URI +:ref:`-redirectToNoTrailingSlashIfPresent-java-` If the request path ends with a slash, redirects to the same uri without trailing slash in the path +:ref:`-redirectToTrailingSlashIfMissing-java-` If the request path doesn't end with a slash, redirects to the same uri with trailing slash in the path +:ref:`-reject-java-` Rejects the request with the given rejections +:ref:`-rejectEmptyResponse-java-` Converts responses with an empty entity into (empty) rejections +:ref:`-requestEncodedWith-java-` Rejects the request with an ``UnsupportedRequestEncodingRejection`` if its encoding doesn't match the given one +:ref:`-requestEntityEmpty-java-` Rejects if the request entity is non-empty +:ref:`-requestEntityPresent-java-` Rejects with a ``RequestEntityExpectedRejection`` if the request entity is empty +:ref:`-respondWithDefaultHeader-java-` Adds a given response header if the response doesn't already contain a header with the same name +:ref:`-respondWithDefaultHeaders-java-` Adds the subset of the given headers to the response which doesn't already have a header with the respective name present in the response +:ref:`-respondWithHeader-java-` Unconditionally adds a given header to the outgoing response +:ref:`-respondWithHeaders-java-` Unconditionally adds the given headers to the outgoing response +:ref:`-responseEncodingAccepted-java-` Rejects the request with an ``UnacceptedResponseEncodingRejection`` if the given response encoding is not accepted by the client +:ref:`-scheme-java-` Rejects all requests whose URI scheme doesn't match the given one +:ref:`-selectPreferredLanguage-java-` Inspects the request's ``Accept-Language`` header and determines, which of a given set of language alternatives is preferred by the client +:ref:`-setCookie-java-` Adds a ``Set-Cookie`` response header with the given cookies +: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:`-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`` +:ref:`-withRangeSupport-java-` Adds ``Accept-Ranges: bytes`` to responses to GET requests, produces partial responses if the initial request contained a valid ``Range`` header +: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`` +================================================ ============================================================================ + diff --git a/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/cancelRejection.rst b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/cancelRejection.rst new file mode 100644 index 0000000000..8651b87a71 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/cancelRejection.rst @@ -0,0 +1,19 @@ +.. _-cancelRejection-java-: + +cancelRejection +=============== + +Description +----------- + +Adds a ``TransformationRejection`` cancelling all rejections equal to the +given one to the rejections potentially coming back from the inner route. + +Read :ref:`rejections-java` to learn more about rejections. + +For more advanced handling of rejections refer to the :ref:`-handleRejections-java-` directive +which provides a nicer DSL for building rejection handlers. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/cancelRejections.rst b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/cancelRejections.rst new file mode 100644 index 0000000000..c91ae5649f --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/cancelRejections.rst @@ -0,0 +1,21 @@ +.. _-cancelRejections-java-: + +cancelRejections +================ + +Description +----------- + +Adds a ``TransformationRejection`` cancelling all rejections created by the inner route for which +the condition argument function returns ``true``. + +See also :ref:`-cancelRejection-java-`, for canceling a specific rejection. + +Read :ref:`rejections-java` to learn more about rejections. + +For more advanced handling of rejections refer to the :ref:`-handleRejections-java-` directive +which provides a nicer DSL for building rejection handlers. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/extract.rst b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/extract.rst new file mode 100644 index 0000000000..45bfebf4d0 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/extract.rst @@ -0,0 +1,16 @@ +.. _-extract-java-: + +extract +======= + +Description +----------- + +The ``extract`` directive is used as a building block for :ref:`Custom Directives-java` to extract data from the +``RequestContext`` and provide it to the inner route. + +See :ref:`ProvideDirectives-java` for an overview of similar directives. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/extractExecutionContext.rst b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/extractExecutionContext.rst new file mode 100644 index 0000000000..878538ca6e --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/extractExecutionContext.rst @@ -0,0 +1,17 @@ +.. _-extractExecutionContext-java-: + +extractExecutionContext +======================= + +Description +----------- + +Extracts the ``ExecutionContext`` from the ``RequestContext``. + +See :ref:`-withExecutionContext-java-` to see how to customise the execution context provided for an inner route. + +See :ref:`-extract-java-` to learn more about how extractions work. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/extractLog.rst b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/extractLog.rst new file mode 100644 index 0000000000..02e3d7b825 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/extractLog.rst @@ -0,0 +1,18 @@ +.. _-extractLog-java-: + +extractLog +========== + +Description +----------- + +Extracts a :class:`LoggingAdapter` from the request context which can be used for logging inside the route. + +The ``extractLog`` directive is used for providing logging to routes, such that they don't have to depend on +closing over a logger provided in the class body. + +See :ref:`-extract-java-` and :ref:`ProvideDirectives-java` for an overview of similar directives. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/extractMaterializer.rst b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/extractMaterializer.rst new file mode 100644 index 0000000000..447a0698d6 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/extractMaterializer.rst @@ -0,0 +1,16 @@ +.. _-extractMaterializer-java-: + +extractMaterializer +=================== + +Description +----------- + +Extracts the ``Materializer`` from the ``RequestContext``, which can be useful when you want to run an +Akka Stream directly in your route. + +See also :ref:`-withMaterializer-java-` to see how to customise the used materializer for specific inner routes. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/extractRequest.rst b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/extractRequest.rst new file mode 100644 index 0000000000..ac990e314a --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/extractRequest.rst @@ -0,0 +1,16 @@ +.. _-extractRequest-java-: + +extractRequest +============== + +Description +----------- +Extracts the complete ``HttpRequest`` instance. + +Use ``extractRequest`` to extract just the complete URI of the request. Usually there's little use of +extracting the complete request because extracting of most of the aspects of HttpRequests is handled by specialized +directives. See :ref:`Request Directives-java`. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/extractRequestContext.rst b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/extractRequestContext.rst new file mode 100644 index 0000000000..44d1efa7f3 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/extractRequestContext.rst @@ -0,0 +1,19 @@ +.. _-extractRequestContext-java-: + +extractRequestContext +===================== + +Description +----------- + +Extracts the request's underlying :class:`RequestContext`. + +This directive is used as a building block for most of the other directives, +which extract the context and by inspecting some of it's values can decide +what to do with the request - for example provide a value, or reject the request. + +See also :ref:`-extractRequest-java-` if only interested in the :class:`HttpRequest` instance itself. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/extractSettings.rst b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/extractSettings.rst new file mode 100644 index 0000000000..a694279c5b --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/extractSettings.rst @@ -0,0 +1,16 @@ +.. _-extractSettings-java-: + +extractSettings +=============== + +Description +----------- + +Extracts the :class:`RoutingSettings` from the :class:`RequestContext`. + +By default the settings of the ``Http()`` extension running the route will be returned. +It is possible to override the settings for specific sub-routes by using the :ref:`-withSettings-java-` directive. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/extractUnmatchedPath.rst b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/extractUnmatchedPath.rst new file mode 100644 index 0000000000..a0a07266c4 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/extractUnmatchedPath.rst @@ -0,0 +1,18 @@ +.. _-extractUnmatchedPath-java-: + +extractUnmatchedPath +==================== + +Description +----------- +Extracts the unmatched path from the request context. + +The ``extractUnmatchedPath`` directive extracts the remaining path that was not yet matched by any of the :ref:`PathDirectives-java` +(or any custom ones that change the unmatched path field of the request context). You can use it for building directives +that handle complete suffixes of paths (like the ``getFromDirectory`` directives and similar ones). + +Use ``mapUnmatchedPath`` to change the value of the unmatched path. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/extractUri.rst b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/extractUri.rst new file mode 100644 index 0000000000..875ab01f1e --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/extractUri.rst @@ -0,0 +1,15 @@ +.. _-extractUri-java-: + +extractUri +========== + +Description +----------- +Access the full URI of the request. + +Use :ref:`SchemeDirectives-java`, :ref:`HostDirectives-java`, :ref:`PathDirectives-java`, and :ref:`ParameterDirectives-java` for more +targeted access to parts of the URI. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/index.rst b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/index.rst new file mode 100644 index 0000000000..39238f6d99 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/index.rst @@ -0,0 +1,123 @@ +.. _BasicDirectives-java: + +BasicDirectives +=============== + +Basic directives are building blocks for building :ref:`Custom Directives`. As such they +usually aren't used in a route directly but rather in the definition of new directives. + + +.. _ProvideDirectives-java: + +Providing Values to Inner Routes +-------------------------------- + +These directives provide values to the inner routes with extractions. They can be distinguished +on two axes: a) provide a constant value or extract a value from the ``RequestContext`` b) provide +a single value or a tuple of values. + + * :ref:`-extract-java-` + * :ref:`-extractExecutionContext-java-` + * :ref:`-extractMaterializer-java-` + * :ref:`-extractLog-java-` + * :ref:`-extractRequest-java-` + * :ref:`-extractRequestContext-java-` + * :ref:`-extractSettings-java-` + * :ref:`-extractUnmatchedPath-java-` + * :ref:`-extractUri-java-` + * :ref:`-provide-java-` + + +.. _Request Transforming Directives-java: + +Transforming the Request(Context) +--------------------------------- + + * :ref:`-mapRequest-java-` + * :ref:`-mapRequestContext-java-` + * :ref:`-mapSettings-java-` + * :ref:`-mapUnmatchedPath-java-` + * :ref:`-withExecutionContext-java-` + * :ref:`-withMaterializer-java-` + * :ref:`-withLog-java-` + * :ref:`-withSettings-java-` + + +.. _Response Transforming Directives-java: + +Transforming the Response +------------------------- + +These directives allow to hook into the response path and transform the complete response or +the parts of a response or the list of rejections: + + * :ref:`-mapResponse-java-` + * :ref:`-mapResponseEntity-java-` + * :ref:`-mapResponseHeaders-java-` + + +.. _Result Transformation Directives-java: + +Transforming the RouteResult +---------------------------- + +These directives allow to transform the RouteResult of the inner route. + + * :ref:`-cancelRejection-java-` + * :ref:`-cancelRejections-java-` + * :ref:`-mapRejections-java-` + * :ref:`-mapRouteResult-java-` + * :ref:`-mapRouteResultFuture-java-` + * :ref:`-mapRouteResultPF-java-` + * :ref:`-mapRouteResultWith-java-` + * :ref:`-mapRouteResultWithPF-java-` + * :ref:`-recoverRejections-java-` + * :ref:`-recoverRejectionsWith-java-` + + +Other +----- + + * :ref:`-mapInnerRoute-java-` + * :ref:`-pass-java-` + + +Alphabetically +-------------- + +.. toctree:: + :maxdepth: 1 + + cancelRejection + cancelRejections + extract + extractExecutionContext + extractMaterializer + extractLog + extractRequest + extractRequestContext + extractSettings + extractUnmatchedPath + extractUri + mapInnerRoute + mapRejections + mapRequest + mapRequestContext + mapResponse + mapResponseEntity + mapResponseHeaders + mapRouteResult + mapRouteResultFuture + mapRouteResultPF + mapRouteResultWith + mapRouteResultWithPF + mapSettings + mapUnmatchedPath + pass + provide + recoverRejections + recoverRejectionsWith + withExecutionContext + withMaterializer + withLog + withSettings diff --git a/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapInnerRoute.rst b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapInnerRoute.rst new file mode 100644 index 0000000000..f2908a90e5 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapInnerRoute.rst @@ -0,0 +1,15 @@ +.. _-mapInnerRoute-java-: + +mapInnerRoute +============= + +Description +----------- +Changes the execution model of the inner route by wrapping it with arbitrary logic. + +The ``mapInnerRoute`` directive is used as a building block for :ref:`Custom Directives-java` to replace the inner route +with any other route. Usually, the returned route wraps the original one with custom execution logic. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapRejections.rst b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapRejections.rst new file mode 100644 index 0000000000..34fdf1d440 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapRejections.rst @@ -0,0 +1,19 @@ +.. _-mapRejections-java-: + +mapRejections +============= + +Description +----------- + +**Low level directive** – unless you're sure you need to be working on this low-level you might instead +want to try the :ref:`-handleRejections-java-` directive which provides a nicer DSL for building rejection handlers. + +The ``mapRejections`` directive is used as a building block for :ref:`Custom Directives-java` to transform a list +of rejections from the inner route to a new list of rejections. + +See :ref:`Response Transforming Directives-java` for similar directives. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapRequest.rst b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapRequest.rst new file mode 100644 index 0000000000..87c3a8fa3b --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapRequest.rst @@ -0,0 +1,19 @@ +.. _-mapRequest-java-: + +mapRequest +========== + +Description +----------- +Transforms the request before it is handled by the inner route. + +The ``mapRequest`` directive is used as a building block for :ref:`Custom Directives-java` to transform a request before it +is handled by the inner route. Changing the ``request.uri`` parameter has no effect on path matching in the inner route +because the unmatched path is a separate field of the ``RequestContext`` value which is passed into routes. To change +the unmatched path or other fields of the ``RequestContext`` use the :ref:`-mapRequestContext-java-` directive. + +See :ref:`Request Transforming Directives-java` for an overview of similar directives. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapRequestContext.rst b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapRequestContext.rst new file mode 100644 index 0000000000..39cd8cc3c7 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapRequestContext.rst @@ -0,0 +1,18 @@ +.. _-mapRequestContext-java-: + +mapRequestContext +================= + +Description +----------- +Transforms the ``RequestContext`` before it is passed to the inner route. + +The ``mapRequestContext`` directive is used as a building block for :ref:`Custom Directives-java` to transform +the request context before it is passed to the inner route. To change only the request value itself the +:ref:`-mapRequest-java-` directive can be used instead. + +See :ref:`Request Transforming Directives-java` for an overview of similar directives. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapResponse.rst b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapResponse.rst new file mode 100644 index 0000000000..c4a53d4466 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapResponse.rst @@ -0,0 +1,21 @@ +.. _-mapResponse-java-: + +mapResponse +=========== + +Description +----------- + +The ``mapResponse`` directive is used as a building block for :ref:`Custom Directives-java` to transform a response that +was generated by the inner route. This directive transforms complete responses. + +See also :ref:`-mapResponseHeaders-java-` or :ref:`-mapResponseEntity-java-` for more specialized variants and +:ref:`Response Transforming Directives-java` for similar directives. + +Example: Override status +------------------------ +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. + +Example: Default to empty JSON response on errors +------------------------------------------------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapResponseEntity.rst b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapResponseEntity.rst new file mode 100644 index 0000000000..8994140991 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapResponseEntity.rst @@ -0,0 +1,16 @@ +.. _-mapResponseEntity-java-: + +mapResponseEntity +================= + +Description +----------- + +The ``mapResponseEntity`` directive is used as a building block for :ref:`Custom Directives-java` to transform a +response entity that was generated by the inner route. + +See :ref:`Response Transforming Directives-java` for similar directives. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapResponseHeaders.rst b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapResponseHeaders.rst new file mode 100644 index 0000000000..eacf9bb0c1 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapResponseHeaders.rst @@ -0,0 +1,17 @@ +.. _-mapResponseHeaders-java-: + +mapResponseHeaders +================== + +Description +----------- +Changes the list of response headers that was generated by the inner route. + +The ``mapResponseHeaders`` directive is used as a building block for :ref:`Custom Directives-java` to transform the list of +response headers that was generated by the inner route. + +See :ref:`Response Transforming Directives-java` for similar directives. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapRouteResult.rst b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapRouteResult.rst new file mode 100644 index 0000000000..d440ba759d --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapRouteResult.rst @@ -0,0 +1,17 @@ +.. _-mapRouteResult-java-: + +mapRouteResult +============== + +Description +----------- +Changes the message the inner route sends to the responder. + +The ``mapRouteResult`` directive is used as a building block for :ref:`Custom Directives-java` to transform the +:class:`RouteResult` coming back from the inner route. + +See :ref:`Result Transformation Directives-java` for similar directives. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapRouteResultFuture.rst b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapRouteResultFuture.rst new file mode 100644 index 0000000000..0a0e33b8c5 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapRouteResultFuture.rst @@ -0,0 +1,20 @@ +.. _-mapRouteResultFuture-java-: + +mapRouteResultFuture +==================== + +Description +----------- + +Asynchronous version of :ref:`-mapRouteResult-java-`. + +It's similar to :ref:`-mapRouteResultWith-java-`, however it's +``Function, CompletionStage>`` +instead of ``Function>`` which may be useful when +combining multiple transformations and / or wanting to ``recover`` from a failed route result. + +See :ref:`Result Transformation Directives-java` for similar directives. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapRouteResultPF.rst b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapRouteResultPF.rst new file mode 100644 index 0000000000..8ff60a8305 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapRouteResultPF.rst @@ -0,0 +1,20 @@ +.. _-mapRouteResultPF-java-: + +mapRouteResultPF +================ + +Description +----------- +*Partial Function* version of :ref:`-mapRouteResult-java-`. + +Changes the message the inner route sends to the responder. + +The ``mapRouteResult`` directive is used as a building block for :ref:`Custom Directives-java` to transform the +:class:`RouteResult` coming back from the inner route. It's similar to the :ref:`-mapRouteResult-java-` directive but allows to +specify a partial function that doesn't have to handle all potential ``RouteResult`` instances. + +See :ref:`Result Transformation Directives-java` for similar directives. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapRouteResultWith.rst b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapRouteResultWith.rst new file mode 100644 index 0000000000..b58e4de9ee --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapRouteResultWith.rst @@ -0,0 +1,19 @@ +.. _-mapRouteResultWith-java-: + +mapRouteResultWith +================== + +Description +----------- + +Changes the message the inner route sends to the responder. + +The ``mapRouteResult`` directive is used as a building block for :ref:`Custom Directives-java` to transform the +:class:`RouteResult` coming back from the inner route. It's similar to the :ref:`-mapRouteResult-java-` directive but +returning a ``CompletionStage`` instead of a result immediately, which may be useful for longer running transformations. + +See :ref:`Result Transformation Directives-java` for similar directives. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapRouteResultWithPF.rst b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapRouteResultWithPF.rst new file mode 100644 index 0000000000..bf13964fac --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapRouteResultWithPF.rst @@ -0,0 +1,20 @@ +.. _-mapRouteResultWithPF-java-: + +mapRouteResultWithPF +==================== + +Description +----------- + +Asynchronous variant of :ref:`-mapRouteResultPF-java-`. + +Changes the message the inner route sends to the responder. + +The ``mapRouteResult`` directive is used as a building block for :ref:`Custom Directives-java` to transform the +:class:`RouteResult` coming back from the inner route. + +See :ref:`Result Transformation Directives-java` for similar directives. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapSettings.rst b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapSettings.rst new file mode 100644 index 0000000000..763ca2fc73 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapSettings.rst @@ -0,0 +1,15 @@ +.. _-mapSettings-java-: + +mapSettings +=========== + +Description +----------- + +Transforms the ``RoutingSettings`` with a ``Function``. + +See also :ref:`-withSettings-java-` or :ref:`-extractSettings-java-`. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapUnmatchedPath.rst b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapUnmatchedPath.rst new file mode 100644 index 0000000000..6cef0c4cc3 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/mapUnmatchedPath.rst @@ -0,0 +1,17 @@ +.. _-mapUnmatchedPath-java-: + +mapUnmatchedPath +================ + +Description +----------- +Transforms the unmatchedPath field of the request context for inner routes. + +The ``mapUnmatchedPath`` directive is used as a building block for writing :ref:`Custom Directives-java`. You can use it +for implementing custom path matching directives. + +Use ``extractUnmatchedPath`` for extracting the current value of the unmatched path. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/pass.rst b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/pass.rst new file mode 100644 index 0000000000..3547026189 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/pass.rst @@ -0,0 +1,14 @@ +.. _-pass-java-: + +pass +==== + +Description +----------- +A directive that passes the request unchanged to its inner route. + +It is usually used as a "neutral element" when combining directives generically. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/provide.rst b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/provide.rst new file mode 100644 index 0000000000..290f0f07ef --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/provide.rst @@ -0,0 +1,16 @@ +.. _-provide-java-: + +provide +======= +Description +----------- +Provides a constant value to the inner route. + +The `provide` directive is used as a building block for :ref:`Custom Directives-java` to provide a single value to the +inner route. + +See :ref:`ProvideDirectives-java` for an overview of similar directives. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/recoverRejections.rst b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/recoverRejections.rst new file mode 100644 index 0000000000..e561f9c515 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/recoverRejections.rst @@ -0,0 +1,20 @@ +.. _-recoverRejections-java-: + +recoverRejections +================= +Description +----------- + +**Low level directive** – unless you're sure you need to be working on this low-level you might instead +want to try the :ref:`-handleRejections-java-` directive which provides a nicer DSL for building rejection handlers. + +Transforms rejections from the inner route with a ``Function, RouteResult>``. +A ``RouteResult`` is either a ``Complete`` containing the ``HttpResponse`` or a ``Rejected`` containing the +rejections. + +.. note:: + To learn more about how and why rejections work read the :ref:`rejections-java` section of the documentation. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/recoverRejectionsWith.rst b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/recoverRejectionsWith.rst new file mode 100644 index 0000000000..7b010dbdbc --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/recoverRejectionsWith.rst @@ -0,0 +1,23 @@ +.. _-recoverRejectionsWith-java-: + +recoverRejectionsWith +===================== + +Description +----------- + +**Low level directive** – unless you're sure you need to be working on this low-level you might instead +want to try the :ref:`-handleRejections-java-` directive which provides a nicer DSL for building rejection handlers. + +Transforms rejections from the inner route with a ``Function, CompletionStage>``. + +Asynchronous version of :ref:`-recoverRejections-java-`. + +See :ref:`-recoverRejections-java-` (the synchronous equivalent of this directive) for a detailed description. + +.. note:: + To learn more about how and why rejections work read the :ref:`rejections-java` section of the documentation. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/withExecutionContext.rst b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/withExecutionContext.rst new file mode 100644 index 0000000000..746cdbb2be --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/withExecutionContext.rst @@ -0,0 +1,17 @@ +.. _-withExecutionContext-java-: + +withExecutionContext +==================== + +Description +----------- + +Allows running an inner route using an alternative ``ExecutionContextExecutor`` in place of the default one. + +The execution context can be extracted in an inner route using :ref:`-extractExecutionContext-java-` directly, +or used by directives which internally extract the materializer without sufracing this fact in the API. + + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/withLog.rst b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/withLog.rst new file mode 100644 index 0000000000..e183d088b9 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/withLog.rst @@ -0,0 +1,17 @@ +.. _-withLog-java-: + +withLog +======= + +Description +----------- + +Allows running an inner route using an alternative :class:`LoggingAdapter` in place of the default one. + +The logging adapter can be extracted in an inner route using :ref:`-extractLog-java-` directly, +or used by directives which internally extract the materializer without surfacing this fact in the API. + + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/withMaterializer.rst b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/withMaterializer.rst new file mode 100644 index 0000000000..8037dd11ff --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/withMaterializer.rst @@ -0,0 +1,17 @@ +.. _-withMaterializer-java-: + +withMaterializer +================ + +Description +----------- + +Allows running an inner route using an alternative ``Materializer`` in place of the default one. + +The materializer can be extracted in an inner route using :ref:`-extractMaterializer-java-` directly, +or used by directives which internally extract the materializer without sufracing this fact in the API +(e.g. responding with a Chunked entity). + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/withSettings.rst b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/withSettings.rst new file mode 100644 index 0000000000..362e269ab1 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/basic-directives/withSettings.rst @@ -0,0 +1,16 @@ +.. _-withSettings-java-: + +withSettings +============ + +Description +----------- + +Allows running an inner route using an alternative :class:`RoutingSettings` in place of the default one. + +The execution context can be extracted in an inner route using :ref:`-extractSettings-java-` directly, +or used by directives which internally extract the materializer without sufracing this fact in the API. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/by-trait.rst b/akka-docs/rst/java/http/routing-dsl/directives/by-trait.rst new file mode 100644 index 0000000000..4970fdb2d1 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/by-trait.rst @@ -0,0 +1,110 @@ +Predefined Directives (by trait) +================================ + +All predefined directives are organized into traits that form one part of the overarching ``Directives`` trait. + +.. _Request Directives-java: + +Directives filtering or extracting from the request +--------------------------------------------------- + +:ref:`MethodDirectives-java` + Filter and extract based on the request method. + +:ref:`HeaderDirectives-java` + Filter and extract based on request headers. + +:ref:`PathDirectives-java` + Filter and extract from the request URI path. + +:ref:`HostDirectives-java` + Filter and extract based on the target host. + +:ref:`ParameterDirectives-java`, :ref:`FormFieldDirectives-java` + Filter and extract based on query parameters or form fields. + +:ref:`CodingDirectives-java` + Filter and decode compressed request content. + +:ref:`MarshallingDirectives-java` + Extract the request entity. + +:ref:`SchemeDirectives-java` + Filter and extract based on the request scheme. + +:ref:`SecurityDirectives-java` + Handle authentication data from the request. + +:ref:`CookieDirectives-java` + Filter and extract cookies. + +:ref:`BasicDirectives-java` and :ref:`MiscDirectives-java` + Directives handling request properties. + +:ref:`FileUploadDirectives-java` + Handle file uploads. + + +.. _Response Directives-java: + +Directives creating or transforming the response +------------------------------------------------ + +:ref:`CacheConditionDirectives-java` + Support for conditional requests (``304 Not Modified`` responses). + +:ref:`CookieDirectives-java` + Set, modify, or delete cookies. + +:ref:`CodingDirectives-java` + Compress responses. + +:ref:`FileAndResourceDirectives-java` + Deliver responses from files and resources. + +:ref:`RangeDirectives-java` + Support for range requests (``206 Partial Content`` responses). + +:ref:`RespondWithDirectives-java` + Change response properties. + +:ref:`RouteDirectives-java` + Complete or reject a request with a response. + +:ref:`BasicDirectives-java` and :ref:`MiscDirectives-java` + Directives handling or transforming response properties. + +:ref:`TimeoutDirectives-java` + Configure request timeouts and automatic timeout responses. + + +List of predefined directives by trait +-------------------------------------- + +.. toctree:: + :maxdepth: 1 + + basic-directives/index + cache-condition-directives/index + coding-directives/index + cookie-directives/index + debugging-directives/index + execution-directives/index + file-and-resource-directives/index + file-upload-directives/index + form-field-directives/index + future-directives/index + header-directives/index + host-directives/index + marshalling-directives/index + method-directives/index + misc-directives/index + parameter-directives/index + path-directives/index + range-directives/index + respond-with-directives/index + route-directives/index + scheme-directives/index + security-directives/index + websocket-directives/index + timeout-directives/index diff --git a/akka-docs/rst/java/http/routing-dsl/directives/cache-condition-directives/conditional.rst b/akka-docs/rst/java/http/routing-dsl/directives/cache-condition-directives/conditional.rst new file mode 100644 index 0000000000..a18d63c5c2 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/cache-condition-directives/conditional.rst @@ -0,0 +1,33 @@ +.. _-conditional-java-: + +conditional +=========== + +Description +----------- + +Wraps its inner route with support for Conditional Requests as defined +by http://tools.ietf.org/html/draft-ietf-httpbis-p4-conditional-26. + + +Depending on the given ``eTag`` and ``lastModified`` values this directive immediately responds with +``304 Not Modified`` or ``412 Precondition Failed`` (without calling its inner route) if the request comes with the +respective conditional headers. Otherwise the request is simply passed on to its inner route. + +The algorithm implemented by this directive closely follows what is defined in `this section`__ of the +`HTTPbis spec`__. + +All responses (the ones produces by this directive itself as well as the ones coming back from the inner route) are +augmented with respective ``ETag`` and ``Last-Modified`` response headers. + +Since this directive requires the ``EntityTag`` and ``lastModified`` time stamp for the resource as concrete arguments +it is usually used quite deep down in the route structure (i.e. close to the leaf-level), where the exact resource +targeted by the request has already been established and the respective ETag/Last-Modified values can be determined. + + +The :ref:`FileAndResourceDirectives-java` internally use the ``conditional`` directive for ETag and Last-Modified support +(if the ``akka.http.routing.file-get-conditional`` setting is enabled). + +__ http://tools.ietf.org/html/draft-ietf-httpbis-p4-conditional-26#section-6 +__ https://datatracker.ietf.org/wg/httpbis/ + diff --git a/akka-docs/rst/java/http/routing-dsl/directives/cache-condition-directives/index.rst b/akka-docs/rst/java/http/routing-dsl/directives/cache-condition-directives/index.rst new file mode 100644 index 0000000000..8734c2cc82 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/cache-condition-directives/index.rst @@ -0,0 +1,9 @@ +.. _CacheConditionDirectives-java: + +CacheConditionDirectives +======================== + +.. toctree:: + :maxdepth: 1 + + conditional diff --git a/akka-docs/rst/java/http/routing-dsl/directives/coding-directives/decodeRequest.rst b/akka-docs/rst/java/http/routing-dsl/directives/coding-directives/decodeRequest.rst new file mode 100644 index 0000000000..a8d38d2fda --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/coding-directives/decodeRequest.rst @@ -0,0 +1,13 @@ +.. _-decodeRequest-java-: + +decodeRequest +============= + +Description +----------- + +Decompresses the incoming request if it is ``gzip`` or ``deflate`` compressed. Uncompressed requests are passed through untouched. If the request encoded with another encoding the request is rejected with an ``UnsupportedRequestEncodingRejection``. + +Example +------- +..TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/coding-directives/decodeRequestWith.rst b/akka-docs/rst/java/http/routing-dsl/directives/coding-directives/decodeRequestWith.rst new file mode 100644 index 0000000000..430bfcb8e2 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/coding-directives/decodeRequestWith.rst @@ -0,0 +1,13 @@ +.. _-decodeRequestWith-java-: + +decodeRequestWith +================= + +Description +----------- + +Decodes the incoming request if it is encoded with one of the given encoders. If the request encoding doesn't match one of the given encoders the request is rejected with an ``UnsupportedRequestEncodingRejection``. If no decoders are given the default encoders (``Gzip``, ``Deflate``, ``NoCoding``) are used. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/coding-directives/encodeResponse.rst b/akka-docs/rst/java/http/routing-dsl/directives/coding-directives/encodeResponse.rst new file mode 100644 index 0000000000..5030bc6c18 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/coding-directives/encodeResponse.rst @@ -0,0 +1,19 @@ +.. _-encodeResponse-java-: + +encodeResponse +============== + +Description +----------- + +Encodes the response with the encoding that is requested by the client via the ``Accept-Encoding`` header or rejects the request with an ``UnacceptedResponseEncodingRejection(supportedEncodings)``. + +The response encoding is determined by the rules specified in RFC7231_. + +If the ``Accept-Encoding`` header is missing or empty or specifies an encoding other than identity, gzip or deflate then no encoding is used. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. + +.. _RFC7231: http://tools.ietf.org/html/rfc7231#section-5.3.4 diff --git a/akka-docs/rst/java/http/routing-dsl/directives/coding-directives/encodeResponseWith.rst b/akka-docs/rst/java/http/routing-dsl/directives/coding-directives/encodeResponseWith.rst new file mode 100644 index 0000000000..b3ffe1413a --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/coding-directives/encodeResponseWith.rst @@ -0,0 +1,22 @@ +.. _-encodeResponseWith-java-: + +encodeResponseWith +================== + +Description +----------- + +Encodes the response with the encoding that is requested by the client via the ``Accept-Encoding`` if it is among the provided encoders or rejects the request with an ``UnacceptedResponseEncodingRejection(supportedEncodings)``. + +The response encoding is determined by the rules specified in RFC7231_. + +If the ``Accept-Encoding`` header is missing then the response is encoded using the ``first`` encoder. + +If the ``Accept-Encoding`` header is empty and ``NoCoding`` is part of the encoders then no +response encoding is used. Otherwise the request is rejected. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. + +.. _RFC7231: http://tools.ietf.org/html/rfc7231#section-5.3.4 diff --git a/akka-docs/rst/java/http/routing-dsl/directives/coding-directives/index.rst b/akka-docs/rst/java/http/routing-dsl/directives/coding-directives/index.rst new file mode 100644 index 0000000000..2974708076 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/coding-directives/index.rst @@ -0,0 +1,14 @@ +.. _CodingDirectives-java: + +CodingDirectives +================ + +.. toctree:: + :maxdepth: 1 + + decodeRequest + decodeRequestWith + encodeResponse + encodeResponseWith + requestEncodedWith + responseEncodingAccepted diff --git a/akka-docs/rst/java/http/routing-dsl/directives/coding-directives/requestEncodedWith.rst b/akka-docs/rst/java/http/routing-dsl/directives/coding-directives/requestEncodedWith.rst new file mode 100644 index 0000000000..a57d355117 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/coding-directives/requestEncodedWith.rst @@ -0,0 +1,15 @@ +.. _-requestEncodedWith-java-: + +requestEncodedWith +================== + +Description +----------- + +Passes the request to the inner route if the request is encoded with the argument encoding. Otherwise, rejects the request with an ``UnacceptedRequestEncodingRejection(encoding)``. + +This directive is the building block for ``decodeRequest`` to reject unsupported encodings. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/coding-directives/responseEncodingAccepted.rst b/akka-docs/rst/java/http/routing-dsl/directives/coding-directives/responseEncodingAccepted.rst new file mode 100644 index 0000000000..100019fcee --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/coding-directives/responseEncodingAccepted.rst @@ -0,0 +1,13 @@ +.. _-responseEncodingAccepted-java-: + +responseEncodingAccepted +======================== + +Description +----------- + +Passes the request to the inner route if the request accepts the argument encoding. Otherwise, rejects the request with an ``UnacceptedResponseEncodingRejection(encoding)``. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/cookie-directives/cookie.rst b/akka-docs/rst/java/http/routing-dsl/directives/cookie-directives/cookie.rst new file mode 100644 index 0000000000..2b3ad3ef53 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/cookie-directives/cookie.rst @@ -0,0 +1,16 @@ +.. _-cookie-java-: + +cookie +====== + +Description +----------- +Extracts a cookie with a given name from a request or otherwise rejects the request with a ``MissingCookieRejection`` if +the cookie is missing. + +Use the :ref:`-optionalCookie-java-` directive instead if you want to support missing cookies in your inner route. + + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/cookie-directives/deleteCookie.rst b/akka-docs/rst/java/http/routing-dsl/directives/cookie-directives/deleteCookie.rst new file mode 100644 index 0000000000..3dbc100934 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/cookie-directives/deleteCookie.rst @@ -0,0 +1,14 @@ +.. _-deleteCookie-java-: + +deleteCookie +============ + +Description +----------- +Adds a header to the response to request the removal of the cookie with the given name on the client. + +Use the :ref:`-setCookie-java-` directive to update a cookie. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/cookie-directives/index.rst b/akka-docs/rst/java/http/routing-dsl/directives/cookie-directives/index.rst new file mode 100644 index 0000000000..1ffc38de8e --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/cookie-directives/index.rst @@ -0,0 +1,12 @@ +.. _CookieDirectives-java: + +CookieDirectives +================ + +.. toctree:: + :maxdepth: 1 + + cookie + deleteCookie + optionalCookie + setCookie diff --git a/akka-docs/rst/java/http/routing-dsl/directives/cookie-directives/optionalCookie.rst b/akka-docs/rst/java/http/routing-dsl/directives/cookie-directives/optionalCookie.rst new file mode 100644 index 0000000000..2b4570f644 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/cookie-directives/optionalCookie.rst @@ -0,0 +1,15 @@ +.. _-optionalCookie-java-: + +optionalCookie +============== + +Description +----------- +Extracts an optional cookie with a given name from a request. + +Use the :ref:`-cookie-java-` directive instead if the inner route does not handle a missing cookie. + + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/cookie-directives/setCookie.rst b/akka-docs/rst/java/http/routing-dsl/directives/cookie-directives/setCookie.rst new file mode 100644 index 0000000000..5a71ce6670 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/cookie-directives/setCookie.rst @@ -0,0 +1,15 @@ +.. _-setCookie-java-: + +setCookie +========= + +Description +----------- +Adds a header to the response to request the update of the cookie with the given name on the client. + +Use the :ref:`-deleteCookie-java-` directive to delete a cookie. + + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/custom-directives.rst b/akka-docs/rst/java/http/routing-dsl/directives/custom-directives.rst new file mode 100644 index 0000000000..f001d12c95 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/custom-directives.rst @@ -0,0 +1,35 @@ +.. _Custom Directives-java: + +Custom Directives +================= +Part of the power of akka-http directives comes from the ease with which it’s possible to define +custom directives at differing levels of abstraction. + +There are essentially three ways of creating custom directives: + +1. By introducing new “labels” for configurations of existing directives +2. By transforming existing directives +3. By writing a directive “from scratch” + +Configuration Labeling +______________________ +The easiest way to create a custom directive is to simply assign a new name for a certain configuration +of one or more existing directives. In fact, most of the predefined akka-http directives can be considered +named configurations of more low-level directives. + +The basic technique is explained in the chapter about Composing Directives, where, for example, a new directive +``getOrPut`` is defined like this: + +.. includecode2:: ../../../code/docs/http/javadsl/server/directives/CustomDirectivesExamplesTest.java + :snippet: labeling + +Multiple directives can be nested to produce a single directive out of multiple like this: + +.. includecode2:: ../../../code/docs/http/javadsl/server/directives/CustomDirectivesExamplesTest.java + :snippet: composition + + +Another example is the :ref:`MethodDirectives-java` which are simply instances of a preconfigured :ref:`-method-java-` directive. +The low-level directives that most often form the basis of higher-level “named configuration” directives are grouped +together in the :ref:`BasicDirectives-java` trait. + diff --git a/akka-docs/rst/java/http/routing-dsl/directives/debugging-directives/index.rst b/akka-docs/rst/java/http/routing-dsl/directives/debugging-directives/index.rst new file mode 100644 index 0000000000..406b615bbf --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/debugging-directives/index.rst @@ -0,0 +1,11 @@ +.. _DebuggingDirectives-java: + +DebuggingDirectives +=================== + +.. toctree:: + :maxdepth: 1 + + logRequest + logRequestResult + logResult diff --git a/akka-docs/rst/java/http/routing-dsl/directives/debugging-directives/logRequest.rst b/akka-docs/rst/java/http/routing-dsl/directives/debugging-directives/logRequest.rst new file mode 100644 index 0000000000..c5785ba998 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/debugging-directives/logRequest.rst @@ -0,0 +1,19 @@ +.. _-logRequest-java-: + +logRequest +========== + +Description +----------- + +Logs the request. The directive is available with the following parameters: + + * A marker to prefix each log message with. + * A log level. + * A function that creates a :class:``LogEntry`` from the :class:``HttpRequest`` + +Use ``logResult`` for logging the response, or ``logRequestResult`` for logging both. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/debugging-directives/logRequestResult.rst b/akka-docs/rst/java/http/routing-dsl/directives/debugging-directives/logRequestResult.rst new file mode 100644 index 0000000000..87374e53e6 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/debugging-directives/logRequestResult.rst @@ -0,0 +1,16 @@ +.. _-logRequestResult-java-: + +logRequestResult +================ + +Description +----------- +Logs both, the request and the response. + +This directive is a combination of :ref:`-logRequest-java-` and :ref:`-logResult-java-`. + +See :ref:`-logRequest-java-` for the general description how these directives work. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/debugging-directives/logResult.rst b/akka-docs/rst/java/http/routing-dsl/directives/debugging-directives/logResult.rst new file mode 100644 index 0000000000..d11148ef6a --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/debugging-directives/logResult.rst @@ -0,0 +1,16 @@ +.. _-logResult-java-: + +logResult +========= + +Description +----------- +Logs the response. + +See :ref:`-logRequest-java-` for the general description how these directives work. + +Use ``logRequest`` for logging the request, or ``logRequestResult`` for logging both. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/execution-directives/handleExceptions.rst b/akka-docs/rst/java/http/routing-dsl/directives/execution-directives/handleExceptions.rst new file mode 100644 index 0000000000..888fb4447d --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/execution-directives/handleExceptions.rst @@ -0,0 +1,17 @@ +.. _-handleExceptions-java-: + +handleExceptions +================ + +Description +----------- +Catches exceptions thrown by the inner route and handles them using the specified ``ExceptionHandler``. + +Using this directive is an alternative to using a global implicitly defined ``ExceptionHandler`` that +applies to the complete route. + +See :ref:`exception-handling-java` for general information about options for handling exceptions. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/execution-directives/handleRejections.rst b/akka-docs/rst/java/http/routing-dsl/directives/execution-directives/handleRejections.rst new file mode 100644 index 0000000000..8c0ef2d868 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/execution-directives/handleRejections.rst @@ -0,0 +1,16 @@ +.. _-handleRejections-java-: + +handleRejections +================ + +Description +----------- + +Using this directive is an alternative to using a global implicitly defined ``RejectionHandler`` that +applies to the complete route. + +See :ref:`rejections-java` for general information about options for handling rejections. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/execution-directives/index.rst b/akka-docs/rst/java/http/routing-dsl/directives/execution-directives/index.rst new file mode 100644 index 0000000000..0f69dcf109 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/execution-directives/index.rst @@ -0,0 +1,10 @@ +.. _ExecutionDirectives-java: + +ExecutionDirectives +=================== + +.. toctree:: + :maxdepth: 1 + + handleExceptions + handleRejections diff --git a/akka-docs/rst/java/http/routing-dsl/directives/file-and-resource-directives/getFromBrowseableDirectories.rst b/akka-docs/rst/java/http/routing-dsl/directives/file-and-resource-directives/getFromBrowseableDirectories.rst new file mode 100644 index 0000000000..502e30a32d --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/file-and-resource-directives/getFromBrowseableDirectories.rst @@ -0,0 +1,22 @@ +.. _-getFromBrowseableDirectories-java-: + +getFromBrowseableDirectories +============================ + +Description +----------- + +The ``getFromBrowseableDirectories`` is a combination of serving files from the specified directories +(like ``getFromDirectory``) and listing a browseable directory with ``listDirectoryContents``. + +Nesting this directive beneath ``get`` is not necessary as this directive will only respond to ``GET`` requests. + +Use ``getFromBrowseableDirectory`` to serve only one directory. + +Use ``getFromDirectory`` if directory browsing isn't required. + +For more details refer to :ref:`-getFromBrowseableDirectory-java-`. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/file-and-resource-directives/getFromBrowseableDirectory.rst b/akka-docs/rst/java/http/routing-dsl/directives/file-and-resource-directives/getFromBrowseableDirectory.rst new file mode 100644 index 0000000000..0523adb48e --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/file-and-resource-directives/getFromBrowseableDirectory.rst @@ -0,0 +1,43 @@ +.. _-getFromBrowseableDirectory-java-: + +getFromBrowseableDirectory +========================== + +Description +----------- + +The ``getFromBrowseableDirectories`` is a combination of serving files from the specified directories (like +``getFromDirectory``) and listing a browseable directory with ``listDirectoryContents``. + +Nesting this directive beneath ``get`` is not necessary as this directive will only respond to ``GET`` requests. + +Use ``getFromBrowseableDirectory`` to serve only one directory. + +Use ``getFromDirectory`` if directory browsing isn't required. + +For more details refer to :ref:`-getFromBrowseableDirectory-java-`. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. + + +Default file listing page example +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Directives which list directories (e.g. ``getFromBrowsableDirectory``) use an implicit ``DirectoryRenderer`` +instance to perfm the actual rendering of the file listing. This rendered can be easily overriden by simply +providing one in-scope for the directives to use, so you can build your custom directory listings. + + +The default renderer is ``akka.http.scaladsl.server.directives.FileAndResourceDirectives.defaultDirectoryRenderer``, +and renders a listing which looks like this: + +.. figure:: ../../../../../images/akka-http-file-listing.png + :scale: 75% + :align: center + + Example page rendered by the ``defaultDirectoryRenderer``. + +It's possible to turn off rendering the footer stating which version of Akka HTTP is rendering this page by configuring +the ``akka.http.routing.render-vanity-footer`` configuration option to ``off``. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/file-and-resource-directives/getFromDirectory.rst b/akka-docs/rst/java/http/routing-dsl/directives/file-and-resource-directives/getFromDirectory.rst new file mode 100644 index 0000000000..1459b17392 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/file-and-resource-directives/getFromDirectory.rst @@ -0,0 +1,30 @@ +.. _-getFromDirectory-java-: + +getFromDirectory +================ + +Description +----------- + +Allows exposing a directory's files for GET requests for its contents. + +The ``unmatchedPath`` (see :ref:`-extractUnmatchedPath-java-`) of the ``RequestContext`` is first transformed by +the given ``pathRewriter`` function, before being appended to the given directory name to build the final file name. + +To serve a single file use :ref:`-getFromFile-java-`. +To serve browsable directory listings use :ref:`-getFromBrowseableDirectories-java-`. +To serve files from a classpath directory use :ref:`-getFromResourceDirectory-java-` instead. + +Note that it's not required to wrap this directive with ``get`` as this directive will only respond to ``GET`` requests. + +.. note:: + The file's contents will be read using an Akka Streams `Source` which *automatically uses + a pre-configured dedicated blocking io dispatcher*, which separates the blocking file operations from the rest of the stream. + + Note also that thanks to using Akka Streams internally, the file will be served at the highest speed reachable by + the client, and not faster – i.e. the file will *not* end up being loaded in full into memory before writing it to + the client. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/file-and-resource-directives/getFromFile.rst b/akka-docs/rst/java/http/routing-dsl/directives/file-and-resource-directives/getFromFile.rst new file mode 100644 index 0000000000..81d26733be --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/file-and-resource-directives/getFromFile.rst @@ -0,0 +1,30 @@ +.. _-getFromFile-java-: + +getFromFile +=========== + +Description +----------- + +Allows exposing a file to be streamed to the client issuing the request. + +The ``unmatchedPath`` (see :ref:`-extractUnmatchedPath-java-`) of the ``RequestContext`` is first transformed by +the given ``pathRewriter`` function, before being appended to the given directory name to build the final file name. + +To files from a given directory use :ref:`-getFromDirectory-java-`. +To serve browsable directory listings use :ref:`-getFromBrowseableDirectories-java-`. +To serve files from a classpath directory use :ref:`-getFromResourceDirectory-java-` instead. + +Note that it's not required to wrap this directive with ``get`` as this directive will only respond to ``GET`` requests. + +.. note:: + The file's contents will be read using an Akka Streams `Source` which *automatically uses + a pre-configured dedicated blocking io dispatcher*, which separates the blocking file operations from the rest of the stream. + + Note also that thanks to using Akka Streams internally, the file will be served at the highest speed reachable by + the client, and not faster – i.e. the file will *not* end up being loaded in full into memory before writing it to + the client. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/file-and-resource-directives/getFromResource.rst b/akka-docs/rst/java/http/routing-dsl/directives/file-and-resource-directives/getFromResource.rst new file mode 100644 index 0000000000..17754ec360 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/file-and-resource-directives/getFromResource.rst @@ -0,0 +1,18 @@ +.. _-getFromResource-java-: + +getFromResource +=============== + +Description +----------- + +Completes ``GET`` requests with the content of the given classpath resource. + +For details refer to :ref:`-getFromFile-java-` which works the same way but obtaining the file from the filesystem +instead of the applications classpath. + +Note that it's not required to wrap this directive with ``get`` as this directive will only respond to ``GET`` requests. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/file-and-resource-directives/getFromResourceDirectory.rst b/akka-docs/rst/java/http/routing-dsl/directives/file-and-resource-directives/getFromResourceDirectory.rst new file mode 100644 index 0000000000..32d8369cae --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/file-and-resource-directives/getFromResourceDirectory.rst @@ -0,0 +1,18 @@ +.. _-getFromResourceDirectory-java-: + +getFromResourceDirectory +======================== + +Description +----------- + +Completes ``GET`` requests with the content of the given classpath resource directory. + +For details refer to :ref:`-getFromDirectory-java-` which works the same way but obtaining the file from the filesystem +instead of the applications classpath. + +Note that it's not required to wrap this directive with ``get`` as this directive will only respond to ``GET`` requests. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/file-and-resource-directives/index.rst b/akka-docs/rst/java/http/routing-dsl/directives/file-and-resource-directives/index.rst new file mode 100644 index 0000000000..d1a4adb5fa --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/file-and-resource-directives/index.rst @@ -0,0 +1,24 @@ +.. _FileAndResourceDirectives-java: + +FileAndResourceDirectives +========================= + +Like the :ref:`RouteDirectives-java` the ``FileAndResourceDirectives`` are somewhat special in akka-http's routing DSL. +Contrary to all other directives they do not produce instances of type ``Directive[L <: HList]`` but rather "plain" +routes of type ``Route``. +The reason is that they are not meant for wrapping an inner route (like most other directives, as intermediate-level +elements of a route structure, do) but rather form the actual route structure **leaves**. + +So in most cases the inner-most element of a route structure branch is one of the :ref:`RouteDirectives-java` or +``FileAndResourceDirectives``. + +.. toctree:: + :maxdepth: 1 + + getFromBrowseableDirectories + getFromBrowseableDirectory + getFromDirectory + getFromFile + getFromResource + getFromResourceDirectory + listDirectoryContents diff --git a/akka-docs/rst/java/http/routing-dsl/directives/file-and-resource-directives/listDirectoryContents.rst b/akka-docs/rst/java/http/routing-dsl/directives/file-and-resource-directives/listDirectoryContents.rst new file mode 100644 index 0000000000..b0b0de9455 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/file-and-resource-directives/listDirectoryContents.rst @@ -0,0 +1,23 @@ +.. _-listDirectoryContents-java-: + +listDirectoryContents +===================== + +Description +----------- + +Completes GET requests with a unified listing of the contents of all given directories. The actual rendering of the +directory contents is performed by the in-scope ``Marshaller[DirectoryListing]``. + +To just serve files use :ref:`-getFromDirectory-java-`. + +To serve files and provide a browseable directory listing use :ref:`-getFromBrowseableDirectories-java-` instead. + +The rendering can be overridden by providing a custom ``Marshaller[DirectoryListing]``, you can read more about it in +:ref:`-getFromDirectory-java-` 's documentation. + +Note that it's not required to wrap this directive with ``get`` as this directive will only respond to ``GET`` requests. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/file-upload-directives/fileUpload.rst b/akka-docs/rst/java/http/routing-dsl/directives/file-upload-directives/fileUpload.rst new file mode 100644 index 0000000000..7c3f703edf --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/file-upload-directives/fileUpload.rst @@ -0,0 +1,21 @@ +.. _-fileUpload-java-: + +fileUpload +========== + +Description +----------- +Simple access to the stream of bytes for a file uploaded as a multipart form together with metadata +about the upload as extracted value. + +If there is no field with the given name the request will be rejected, if there are multiple file parts +with the same name, the first one will be used and the subsequent ones ignored. + + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. + +:: + + curl --form "csv=@uploadFile.txt" http://: diff --git a/akka-docs/rst/java/http/routing-dsl/directives/file-upload-directives/index.rst b/akka-docs/rst/java/http/routing-dsl/directives/file-upload-directives/index.rst new file mode 100644 index 0000000000..b008255cad --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/file-upload-directives/index.rst @@ -0,0 +1,10 @@ +.. _FileUploadDirectives-java: + +FileUploadDirectives +==================== + +.. toctree:: + :maxdepth: 1 + + uploadedFile + fileUpload diff --git a/akka-docs/rst/java/http/routing-dsl/directives/file-upload-directives/uploadedFile.rst b/akka-docs/rst/java/http/routing-dsl/directives/file-upload-directives/uploadedFile.rst new file mode 100644 index 0000000000..7d66d3afa9 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/file-upload-directives/uploadedFile.rst @@ -0,0 +1,23 @@ +.. _-uploadedFile-java-: + +uploadedFile +============ + +Description +----------- +Streams the contents of a file uploaded as a multipart form into a temporary file on disk and provides the file and +metadata about the upload as extracted value. + +If there is an error writing to disk the request will be failed with the thrown exception, if there is no field +with the given name the request will be rejected, if there are multiple file parts with the same name, the first +one will be used and the subsequent ones ignored. + +.. note:: + This directive will stream contents of the request into a file, however one can not start processing these + until the file has been written completely. For streaming APIs it is preferred to use the :ref:`-fileUpload-java-` + directive, as it allows for streaming handling of the incoming data bytes. + + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/form-field-directives/formField.rst b/akka-docs/rst/java/http/routing-dsl/directives/form-field-directives/formField.rst new file mode 100644 index 0000000000..6711ae2b37 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/form-field-directives/formField.rst @@ -0,0 +1,11 @@ +.. _-formField-java-: + +formField +========= + +----------- +Allows extracting a single Form field sent in the request. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/form-field-directives/formFieldList.rst b/akka-docs/rst/java/http/routing-dsl/directives/form-field-directives/formFieldList.rst new file mode 100644 index 0000000000..260e57db43 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/form-field-directives/formFieldList.rst @@ -0,0 +1,20 @@ +.. _-formFieldList-java-: + +formFieldList +============= + +Description +----------- +Extracts all HTTP form fields at once in the original order as (name, value) tuples of type ``Map.Entry``. + +This directive can be used if the exact order of form fields is important or if parameters can occur several times. + +Warning +------- +The directive reads all incoming HTT form fields without any configured upper bound. +It means, that requests with form fields holding significant amount of data (ie. during a file upload) +can cause performance issues or even an ``OutOfMemoryError`` s. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/form-field-directives/formFieldMap.rst b/akka-docs/rst/java/http/routing-dsl/directives/form-field-directives/formFieldMap.rst new file mode 100644 index 0000000000..f8c34c0f8e --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/form-field-directives/formFieldMap.rst @@ -0,0 +1,19 @@ +.. _-formFieldMap-java-: + +formFieldMap +============ + +Description +----------- +Extracts all HTTP form fields at once as a ``Map`` mapping form field names to form field values. + +If form data contain a field value several times, the map will contain the last one. + +Warning +------- +Use of this directive can result in performance degradation or even in ``OutOfMemoryError`` s. +See :ref:`-formFieldList-java-` for details. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/form-field-directives/formFieldMultiMap.rst b/akka-docs/rst/java/http/routing-dsl/directives/form-field-directives/formFieldMultiMap.rst new file mode 100644 index 0000000000..7e4322023c --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/form-field-directives/formFieldMultiMap.rst @@ -0,0 +1,22 @@ +.. _-formFieldMultiMap-java-: + +formFieldMultiMap +================= + +Description +----------- + +Extracts all HTTP form fields at once as a multi-map of type ``Map>`` mapping +a form name to a list of all its values. + +This directive can be used if form fields can occur several times. + +The order of values is *not* specified. + +Warning +------- +Use of this directive can result in performance degradation or even in ``OutOfMemoryError`` s. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/form-field-directives/index.rst b/akka-docs/rst/java/http/routing-dsl/directives/form-field-directives/index.rst new file mode 100644 index 0000000000..c773f9e96c --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/form-field-directives/index.rst @@ -0,0 +1,12 @@ +.. _FormFieldDirectives-java: + +FormFieldDirectives +=================== + +.. toctree:: + :maxdepth: 1 + + formField + formFieldList + formFieldMap + formFieldMultiMap diff --git a/akka-docs/rst/java/http/routing-dsl/directives/future-directives/completeOrRecoverWith.rst b/akka-docs/rst/java/http/routing-dsl/directives/future-directives/completeOrRecoverWith.rst new file mode 100644 index 0000000000..9f309a5678 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/future-directives/completeOrRecoverWith.rst @@ -0,0 +1,18 @@ +.. _-completeOrRecoverWith-java-: + +completeOrRecoverWith +===================== + +Description +----------- +"Unwraps" a ``CompletionStage`` and runs the inner route when the stage has failed +with the stage's failure exception as an extraction of type ``Throwable``. +If the completion stage succeeds the request is completed using the values marshaller +(This directive therefore requires a marshaller for the completion stage value type to be +provided.) + +To handle the successful case manually as well, use the :ref:`-onComplete-java-` directive, instead. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/future-directives/index.rst b/akka-docs/rst/java/http/routing-dsl/directives/future-directives/index.rst new file mode 100644 index 0000000000..c47037fb07 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/future-directives/index.rst @@ -0,0 +1,14 @@ +.. _FutureDirectives-java: + +FuturesDirectives +================= + +Future directives can be used to run inner routes once the provided ``Future[T]`` has been completed. + +.. toctree:: + :maxdepth: 1 + + onComplete + onSuccess + completeOrRecoverWith + diff --git a/akka-docs/rst/java/http/routing-dsl/directives/future-directives/onComplete.rst b/akka-docs/rst/java/http/routing-dsl/directives/future-directives/onComplete.rst new file mode 100644 index 0000000000..5af35e8922 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/future-directives/onComplete.rst @@ -0,0 +1,18 @@ +.. _-onComplete-java-: + +onComplete +========== + +Description +----------- +Evaluates its parameter of type ``CompletionStage``, and once it has been completed, extracts its +result as a value of type ``Try`` and passes it to the inner route. A ``Try`` can either be a ``Success`` containing +the ``T`` value or a ``Failure`` containing the ``Throwable``. + +To handle the ``Failure`` case automatically and only work with the result value, use :ref:`-onSuccess-java-`. + +To complete with a successful result automatically and just handle the failure result, use :ref:`-completeOrRecoverWith-java-`, instead. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/future-directives/onSuccess.rst b/akka-docs/rst/java/http/routing-dsl/directives/future-directives/onSuccess.rst new file mode 100644 index 0000000000..55a9522713 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/future-directives/onSuccess.rst @@ -0,0 +1,17 @@ +.. _-onSuccess-java-: + +onSuccess +========= + +Description +----------- +Evaluates its parameter of type ``CompletionStage``, and once it has been completed successfully, +extracts its result as a value of type ``T`` and passes it to the inner route. + +If the future fails its failure throwable is bubbled up to the nearest ``ExceptionHandler``. + +To handle the ``Failure`` case manually as well, use :ref:`-onComplete-java-`, instead. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/header-directives/headerValue.rst b/akka-docs/rst/java/http/routing-dsl/directives/header-directives/headerValue.rst new file mode 100644 index 0000000000..35528a1a7b --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/header-directives/headerValue.rst @@ -0,0 +1,22 @@ +.. _-headerValue-java-: + +headerValue +=========== + +Description +----------- +Traverses the list of request headers with the specified function and extracts the first value the function returns as +``Optional[value]``. + +The :ref:`-headerValue-java-` directive is a mixture of ``map`` and ``find`` on the list of request headers. The specified function +is called once for each header until the function returns ``Optional(value)``. This value is extracted and presented to the +inner route. If the function throws an exception the request is rejected with a ``MalformedHeaderRejection``. If the +function returns ``Optional.empty`` for every header the request is rejected as "NotFound". + +This directive is the basis for building other request header related directives. + +See also :ref:`-headerValuePF-java-` for a nicer syntactic alternative. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/header-directives/headerValueByName.rst b/akka-docs/rst/java/http/routing-dsl/directives/header-directives/headerValueByName.rst new file mode 100644 index 0000000000..e2f7f6c051 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/header-directives/headerValueByName.rst @@ -0,0 +1,17 @@ +.. _-headerValueByName-java-: + +headerValueByName +================= + +Description +----------- +Extracts the value of the HTTP request header with the given name. + +If no header with a matching name is found the request is rejected with a ``MissingHeaderRejection``. + +If the header is expected to be missing in some cases or to customize +handling when the header is missing use the :ref:`-optionalHeaderValueByName-java-` directive instead. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/header-directives/headerValueByType.rst b/akka-docs/rst/java/http/routing-dsl/directives/header-directives/headerValueByType.rst new file mode 100644 index 0000000000..1de557eac7 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/header-directives/headerValueByType.rst @@ -0,0 +1,22 @@ +.. _-headerValueByType-java-: + +headerValueByType +================= + +Description +----------- +Traverses the list of request headers and extracts the first header of the given type. + +The ``headerValueByType`` directive finds a header of the given type in the list of request header. If no header of +the given type is found the request is rejected with a ``MissingHeaderRejection``. + +If the header is expected to be missing in some cases or to customize handling when the header +is missing use the :ref:`-optionalHeaderValueByType-java-` directive instead. + +.. note:: + Custom headers will only be matched by this directive if they extend ``ModeledCustomHeader`` + from the Scala DSL and there is currently no API for the Java DSL (Ticket #20415) + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/header-directives/headerValuePF.rst b/akka-docs/rst/java/http/routing-dsl/directives/header-directives/headerValuePF.rst new file mode 100644 index 0000000000..a9b28e6edc --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/header-directives/headerValuePF.rst @@ -0,0 +1,19 @@ +.. _-headerValuePF-java-: + +headerValuePF +============= + +Description +----------- +Calls the specified partial function with the first request header the function is ``isDefinedAt`` and extracts the +result of calling the function. + +The ``headerValuePF`` directive is an alternative syntax version of :ref:`-headerValue-java-`. + +If the function throws an exception the request is rejected with a ``MalformedHeaderRejection``. + +If the function is not defined for any header the request is rejected as "NotFound". + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/header-directives/index.rst b/akka-docs/rst/java/http/routing-dsl/directives/header-directives/index.rst new file mode 100644 index 0000000000..c4dee3dc8c --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/header-directives/index.rst @@ -0,0 +1,19 @@ +.. _HeaderDirectives-java: + +HeaderDirectives +================ + +Header directives can be used to extract header values from the request. To change +response headers use one of the :ref:`RespondWithDirectives-java`. + +.. toctree:: + :maxdepth: 1 + + headerValue + headerValueByName + headerValueByType + headerValuePF + optionalHeaderValue + optionalHeaderValueByName + optionalHeaderValueByType + optionalHeaderValuePF diff --git a/akka-docs/rst/java/http/routing-dsl/directives/header-directives/optionalHeaderValue.rst b/akka-docs/rst/java/http/routing-dsl/directives/header-directives/optionalHeaderValue.rst new file mode 100644 index 0000000000..3ced2fa1c3 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/header-directives/optionalHeaderValue.rst @@ -0,0 +1,16 @@ +.. _-optionalHeaderValue-java-: + +optionalHeaderValue +=================== + +Description +----------- +Traverses the list of request headers with the specified function and extracts the first value the function returns as +``Optional[value]``. + +The ``optionalHeaderValue`` directive is similar to the :ref:`-headerValue-java-` directive but always extracts an ``Option`` +value instead of rejecting the request if no matching header could be found. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/header-directives/optionalHeaderValueByName.rst b/akka-docs/rst/java/http/routing-dsl/directives/header-directives/optionalHeaderValueByName.rst new file mode 100644 index 0000000000..aac914dc60 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/header-directives/optionalHeaderValueByName.rst @@ -0,0 +1,15 @@ +.. _-optionalHeaderValueByName-java-: + +optionalHeaderValueByName +========================= + +Description +----------- +Optionally extracts the value of the HTTP request header with the given name. + +The ``optionalHeaderValueByName`` directive is similar to the :ref:`-headerValueByName-java-` directive but always extracts +an ``Optional`` value instead of rejecting the request if no matching header could be found. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/header-directives/optionalHeaderValueByType.rst b/akka-docs/rst/java/http/routing-dsl/directives/header-directives/optionalHeaderValueByType.rst new file mode 100644 index 0000000000..2e72d51022 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/header-directives/optionalHeaderValueByType.rst @@ -0,0 +1,21 @@ +.. _-optionalHeaderValueByType-java-: + +optionalHeaderValueByType +========================= + +Description +----------- +Optionally extracts the value of the HTTP request header of the given type. + +The ``optionalHeaderValueByType`` directive is similar to the :ref:`-headerValueByType-java-` directive but always extracts +an ``Optional`` value instead of rejecting the request if no matching header could be found. + +.. note:: + Custom headers will only be matched by this directive if they extend ``ModeledCustomHeader`` + from the Scala DSL and there is currently no API for the Java DSL (Ticket #20415) + + To learn more about defining custom headers, read: :ref:`custom-headers-scala`. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/header-directives/optionalHeaderValuePF.rst b/akka-docs/rst/java/http/routing-dsl/directives/header-directives/optionalHeaderValuePF.rst new file mode 100644 index 0000000000..2244913d0f --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/header-directives/optionalHeaderValuePF.rst @@ -0,0 +1,16 @@ +.. _-optionalHeaderValuePF-java-: + +optionalHeaderValuePF +===================== + +Description +----------- +Calls the specified partial function with the first request header the function is ``isDefinedAt`` and extracts the +result of calling the function. + +The ``optionalHeaderValuePF`` directive is similar to the :ref:`-headerValuePF-java-` directive but always extracts an ``Optional`` +value instead of rejecting the request if no matching header could be found. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/host-directives/extractHost.rst b/akka-docs/rst/java/http/routing-dsl/directives/host-directives/extractHostName.rst similarity index 89% rename from akka-docs/rst/java/http/routing-dsl/directives/host-directives/extractHost.rst rename to akka-docs/rst/java/http/routing-dsl/directives/host-directives/extractHostName.rst index b01e058889..ac50fe175a 100644 --- a/akka-docs/rst/java/http/routing-dsl/directives/host-directives/extractHost.rst +++ b/akka-docs/rst/java/http/routing-dsl/directives/host-directives/extractHostName.rst @@ -1,7 +1,7 @@ .. _-extractHost-java-: -RequestVals.host -================ +extractHostName +=============== Extract the hostname part of the ``Host`` request header and expose it as a ``String`` extraction to its inner route. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/host-directives/index.rst b/akka-docs/rst/java/http/routing-dsl/directives/host-directives/index.rst index 0212747027..269b3da2d5 100644 --- a/akka-docs/rst/java/http/routing-dsl/directives/host-directives/index.rst +++ b/akka-docs/rst/java/http/routing-dsl/directives/host-directives/index.rst @@ -10,6 +10,6 @@ contained in incoming requests as well as extracting its value for usage in inne :maxdepth: 1 host - extractHost + extractHostName \ No newline at end of file diff --git a/akka-docs/rst/java/http/routing-dsl/directives/index.rst b/akka-docs/rst/java/http/routing-dsl/directives/index.rst index 5bcb199c6e..e11a9f7f84 100644 --- a/akka-docs/rst/java/http/routing-dsl/directives/index.rst +++ b/akka-docs/rst/java/http/routing-dsl/directives/index.rst @@ -3,68 +3,86 @@ Directives ========== -A directive is a wrapper for a route or a list of alternative routes that adds one or more of the following -functionality to its nested route(s): - -* it filters the request and lets only matching requests pass (e.g. the `get` directive lets only GET-requests pass) -* it modifies the request or the ``RequestContext`` (e.g. the `path` directives filters on the unmatched path and then - passes an updated ``RequestContext`` unmatched path) -* it modifies the response coming out of the nested route - -akka-http provides a set of predefined directives for various tasks. You can access them by either extending from -``akka.http.javadsl.server.AllDirectives`` or by importing them statically with -``import static akka.http.javadsl.server.Directives.*;``. - -These classes of directives are currently defined: - -BasicDirectives - Contains methods to create routes that complete with a static values or allow specifying :ref:`handlers-java` to - process a request. - -CacheConditionDirectives - Contains a single directive ``conditional`` that wraps its inner route with support for Conditional Requests as defined - by `RFC 7234`_. - -CodingDirectives - Contains directives to decode compressed requests and encode responses. - -CookieDirectives - Contains a single directive ``setCookie`` to aid adding a cookie to a response. - -ExecutionDirectives - Contains directives to deal with exceptions that occurred during routing. - -FileAndResourceDirectives - Contains directives to serve resources from files on the file system or from the classpath. - -HostDirectives - Contains directives to filter on the ``Host`` header of the incoming request. - -MethodDirectives - Contains directives to filter on the HTTP method of the incoming request. - -MiscDirectives - Contains directives that validate a request by user-defined logic. - -:ref:`path-directives-java` - Contains directives to match and filter on the URI path of the incoming request. - -RangeDirectives - Contains a single directive ``withRangeSupport`` that adds support for retrieving partial responses. - -SchemeDirectives - Contains a single directive ``scheme`` to filter requests based on the URI scheme (http vs. https). - -WebSocketDirectives - Contains directives to support answering WebSocket requests. - -TODO this page should be rewritten as the corresponding Scala page +A "Directive" is a small building block used for creating arbitrarily complex :ref:`route structures `. +Akka HTTP already pre-defines a large number of directives and you can easily construct your own: .. toctree:: - :maxdepth: 1 + :maxdepth: 1 - path-directives - method-directives/index - host-directives/index + alphabetically + by-trait + custom-directives -.. _`RFC 7234`: http://tools.ietf.org/html/rfc7234 \ No newline at end of file + +Basics +------ + +:ref:`Routes-java` effectively are simply highly specialised functions that take a ``RequestContext`` and eventually ``complete`` it, +which could (and often should) happen asynchronously. + +With the :ref:`-complete-java-` directive this becomes even shorter:: + + Route route = complete("yeah"); + +Writing multiple routes that are tried as alternatives (in-order of definition), is as simple as using the ``route(route1, route2)``, +method:: + + Route routes = route( + pathSingleSlash(() -> + getFromResource("web/calculator.html") + ), + path("hello", () -> complete("World!)) + ); + + +You could also simply define a "catch all" completion by providing it as the last route to attempt to match. +In the example below we use the ``get()`` (one of the :ref:`MethodDirectives-java`) to match all incoming ``GET`` +requests for that route, and all other requests will be routed towards the other "catch all" route, that completes the route:: + + Route route = + get( + () -> complete("Received GET") + ).orElse( + () -> complete("Received something else") + ) + + +If no route matches a given request, a default ``404 Not Found`` response will be returned as response. + +Structure +--------- + +The general anatomy of a directive is as follows:: + + directiveName(arguments [, ...], (extractions [, ...]) -> { + ... // inner route + }) + +It has a name, zero or more arguments and optionally an inner route (The :ref:`RouteDirectives-java` are special in that they +are always used at the leaf-level and as such cannot have inner routes). + +Additionally directives can "extract" a number of values and make them available to their inner routes as function +arguments. When seen "from the outside" a directive with its inner route form an expression of type ``Route``. + + +What Directives do +------------------ + +A directive can do one or more of the following: + +.. rst-class:: wide + +* Transform the incoming ``RequestContext`` before passing it on to its inner route (i.e. modify the request) +* Filter the ``RequestContext`` according to some logic, i.e. only pass on certain requests and reject others +* Extract values from the ``RequestContext`` and make them available to its inner route as "extractions" +* Chain some logic into the :class:`RouteResult` future transformation chain (i.e. modify the response or rejection) +* Complete the request + +This means a ``Directive`` completely wraps the functionality of its inner route and can apply arbitrarily complex +transformations, both (or either) on the request and on the response side. + + +Composing Directives +-------------------- + +TODO rewrite for Java API diff --git a/akka-docs/rst/java/http/routing-dsl/directives/marshalling-directives/completeWith.rst b/akka-docs/rst/java/http/routing-dsl/directives/marshalling-directives/completeWith.rst new file mode 100644 index 0000000000..e272cbac19 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/marshalling-directives/completeWith.rst @@ -0,0 +1,34 @@ +.. _-completeWith-java-: + +completeWith +============ + +Description +----------- +Uses the marshaller for a given type to produce a completion function that is passed to its +inner route. You can use it to decouple marshaller resolution from request completion. + +The ``completeWith`` directive works in conjuction with ``instanceOf`` and ``spray.httpx.marshalling`` +to convert higher-level (object) structure into some lower-level serialized "wire format". +:ref:`The marshalling documentation ` explains this process in detail. +This directive simplifies exposing types to clients via a route while providing some +form of access to the current context. + +``completeWith`` is similar to ``handleWith``. The main difference is with ``completeWith`` you must eventually call +the completion function generated by ``completeWith``. ``handleWith`` will automatically call ``complete`` when the +``handleWith`` function returns. + +Examples +-------- + +The following example uses ``spray-json`` to marshall a simple ``Person`` class to a json +response. It utilizes ``SprayJsonSupport`` via the ``PersonJsonSupport`` object as the in-scope +unmarshaller. + +TODO: Add example snippets + +The ``findPerson`` takes an argument of type ``Person => Unit`` which is generated by the ``completeWith`` +call. We can handle any logic we want in ``findPerson`` and call our completion function to +complete the request. + +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/marshalling-directives/entity.rst b/akka-docs/rst/java/http/routing-dsl/directives/marshalling-directives/entity.rst new file mode 100644 index 0000000000..c4fb9b2caf --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/marshalling-directives/entity.rst @@ -0,0 +1,43 @@ +.. _-entity-java-: + +entity +====== + +Description +----------- +Unmarshalls the request entity to the given type and passes it to its inner Route. An unmarshaller +returns an ``Either`` with ``Right(value)`` if successful or ``Left(exception)`` for a failure. +The ``entity`` method will either pass the ``value`` to the inner route or map the ``exception`` to a +:class:``akka.http.javadsl.server.Rejection``. + +The ``entity`` directive works in conjuction with ``as`` and ``akka.http.scaladsl.unmarshalling`` to +convert some serialized "wire format" value into a higher-level object structure. +:ref:`The unmarshalling documentation ` explains this process in detail. +This directive simplifies extraction and error handling to the specified type from the request. + +An unmarshaller will return a ``Left(exception)`` in the case of an error. This is converted to a +``akka.http.scaladsl.server.Rejection`` within the ``entity`` directive. The following table lists how exceptions +are mapped to rejections: + +========================== ============ +Left(exception) Rejection +-------------------------- ------------ +``ContentExpected`` ``RequestEntityExpectedRejection`` +``UnsupportedContentType`` ``UnsupportedRequestContentTypeRejection``, which lists the supported types +``MaformedContent`` ``MalformedRequestContentRejection``, with an error message and cause +========================== ============ + +Examples +-------- + +The following example uses ``spray-json`` to unmarshall a json request into a simple ``Person`` +class. It utilizes ``SprayJsonSupport`` via the ``PersonJsonSupport`` object as the in-scope unmarshaller. + +TODO: Add example snippets. + +It is also possible to use the ``entity`` directive to obtain raw ``JsValue`` ( spray-json_ ) objects, by simply using +``as[JsValue]``, or any other JSON type for which you have marshallers in-scope. + +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. + +.. _spray-json: https://github.com/spray/spray-json diff --git a/akka-docs/rst/java/http/routing-dsl/directives/marshalling-directives/handleWith.rst b/akka-docs/rst/java/http/routing-dsl/directives/marshalling-directives/handleWith.rst new file mode 100644 index 0000000000..979ab3c00f --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/marshalling-directives/handleWith.rst @@ -0,0 +1,37 @@ +.. _-handleWith-java-: + +handleWith +========== + +Description +----------- +Completes the request using the given function. The input to the function is produced with +the in-scope entity unmarshaller and the result value of the function is marshalled with +the in-scope marshaller. ``handleWith`` can be a convenient method combining ``entity`` with +``complete``. + +The ``handleWith`` directive is used when you want to handle a route with a given function of +type A ⇒ B. ``handleWith`` will use both an in-scope unmarshaller to convert a request into +type A and an in-scope marshaller to convert type B into a response. This is helpful when your +core business logic resides in some other class or you want your business logic to be independent +of the REST interface written with akka-http. You can use ``handleWith`` to "hand off" processing +to a given function without requiring any akka-http-specific functionality. + +``handleWith`` is similar to ``produce``. The main difference is ``handleWith`` automatically +calls ``complete`` when the function passed to ``handleWith`` returns. Using ``produce`` you +must explicity call the completion function passed from the ``produce`` function. + +See :ref:`marshalling ` and :ref:`unmarshalling ` for guidance +on marshalling entities with akka-http. + +Examples +-------- + +The following example uses an ``updatePerson`` function with a ``Person`` case class as an input and output. We plug this function into our route using ``handleWith``. +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. + +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. + +The PersonJsonSupport object handles both marshalling and unmarshalling of the Person case class. + +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/marshalling-directives/index.rst b/akka-docs/rst/java/http/routing-dsl/directives/marshalling-directives/index.rst new file mode 100644 index 0000000000..013bc9613c --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/marshalling-directives/index.rst @@ -0,0 +1,34 @@ +.. _MarshallingDirectives-java: + +Marshalling Directives +====================== + +Marshalling directives work in conjunction with ``akka.http.scaladsl.marshalling`` and ``akka.http.scaladsl.unmarshalling`` to convert +a request entity to a specific type or a type to a response. + +See :ref:`marshalling ` and :ref:`unmarshalling ` for specific +serialization (also known as pickling) guidance. + +Marshalling directives usually rely on an in-scope implicit marshaller to handle conversion. + +.. toctree:: + :maxdepth: 1 + + completeWith + entity + handleWith + +Understanding Specific Marshalling Directives +--------------------------------------------- + +======================================= ======================================= +directive behavior +======================================= ======================================= +:ref:`-completeWith-java-` Uses a marshaller for a given type to produce a completion function for an inner route. Used in conjuction with *instanceOf* to format responses. +:ref:`-entity-java-` Unmarshalls the request entity to the given type and passes it to its inner route. Used in conjection with *as* to convert requests to objects. +:ref:`-handleWith-java-` Completes a request with a given function, using an in-scope unmarshaller for an input and in-scope marshaller for the output. +======================================= ======================================= + + + + diff --git a/akka-docs/rst/java/http/routing-dsl/directives/method-directives/delete.rst b/akka-docs/rst/java/http/routing-dsl/directives/method-directives/delete.rst index f9bd200bd9..205881387d 100644 --- a/akka-docs/rst/java/http/routing-dsl/directives/method-directives/delete.rst +++ b/akka-docs/rst/java/http/routing-dsl/directives/method-directives/delete.rst @@ -2,9 +2,11 @@ delete ====== - Matches requests with HTTP method ``DELETE``. +Description +----------- + This directive filters an incoming request by its HTTP method. Only requests with method ``DELETE`` are passed on to the inner route. All others are rejected with a ``MethodRejection``, which is translated into a ``405 Method Not Allowed`` response 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 b0d9abb1f6..edcf4a62cf 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 @@ -3,6 +3,9 @@ extractMethod ============= +Description +----------- + Extracts the :class:`HttpMethod` from the request context and provides it for use for other directives explicitly. Example diff --git a/akka-docs/rst/java/http/routing-dsl/directives/method-directives/get.rst b/akka-docs/rst/java/http/routing-dsl/directives/method-directives/get.rst index c843d53644..6113d28bfa 100644 --- a/akka-docs/rst/java/http/routing-dsl/directives/method-directives/get.rst +++ b/akka-docs/rst/java/http/routing-dsl/directives/method-directives/get.rst @@ -2,9 +2,11 @@ get === - Matches requests with HTTP method ``GET``. +Description +----------- + This directive filters the incoming request by its HTTP method. Only requests with method ``GET`` are passed on to the inner route. All others are rejected with a ``MethodRejection``, which is translated into a ``405 Method Not Allowed`` response diff --git a/akka-docs/rst/java/http/routing-dsl/directives/method-directives/head.rst b/akka-docs/rst/java/http/routing-dsl/directives/method-directives/head.rst index 3c0361f0dc..ff7419303b 100644 --- a/akka-docs/rst/java/http/routing-dsl/directives/method-directives/head.rst +++ b/akka-docs/rst/java/http/routing-dsl/directives/method-directives/head.rst @@ -2,9 +2,11 @@ head ==== - Matches requests with HTTP method ``HEAD``. +Description +----------- + This directive filters the incoming request by its HTTP method. Only requests with method ``HEAD`` are passed on to the inner route. All others are rejected with a ``MethodRejection``, which is translated into a ``405 Method Not Allowed`` response @@ -17,4 +19,4 @@ by the default ``RejectionHandler``. Example ------- -.. includecode:: ../../../../code/docs/http/javadsl/server/directives/MethodDirectivesExamplesTest.java#head \ No newline at end of file +.. includecode:: ../../../../code/docs/http/javadsl/server/directives/MethodDirectivesExamplesTest.java#head diff --git a/akka-docs/rst/java/http/routing-dsl/directives/method-directives/index.rst b/akka-docs/rst/java/http/routing-dsl/directives/method-directives/index.rst index e63c3e900c..10b6776de0 100644 --- a/akka-docs/rst/java/http/routing-dsl/directives/method-directives/index.rst +++ b/akka-docs/rst/java/http/routing-dsl/directives/method-directives/index.rst @@ -1,4 +1,4 @@ -.. _method-directives-java: +.. _MethodDirectives-java: MethodDirectives ================ diff --git a/akka-docs/rst/java/http/routing-dsl/directives/method-directives/options.rst b/akka-docs/rst/java/http/routing-dsl/directives/method-directives/options.rst index 8603620464..c20e71d950 100644 --- a/akka-docs/rst/java/http/routing-dsl/directives/method-directives/options.rst +++ b/akka-docs/rst/java/http/routing-dsl/directives/method-directives/options.rst @@ -2,9 +2,11 @@ options ======= - Matches requests with HTTP method ``OPTIONS``. +Description +----------- + This directive filters the incoming request by its HTTP method. Only requests with method ``OPTIONS`` are passed on to the inner route. All others are rejected with a ``MethodRejection``, which is translated into a ``405 Method Not Allowed`` response diff --git a/akka-docs/rst/java/http/routing-dsl/directives/method-directives/overrideMethodWithParameter.rst b/akka-docs/rst/java/http/routing-dsl/directives/method-directives/overrideMethodWithParameter.rst index 86811021ba..ee709eac90 100644 --- a/akka-docs/rst/java/http/routing-dsl/directives/method-directives/overrideMethodWithParameter.rst +++ b/akka-docs/rst/java/http/routing-dsl/directives/method-directives/overrideMethodWithParameter.rst @@ -3,9 +3,21 @@ overrideMethodWithParameter =========================== -TODO ... +Changes the HTTP method of the request to the value of the specified query string parameter. + +Description +----------- + +If the query string parameter is not specified this directive has no effect. If the query string is specified as something that is not +a HTTP method, then this directive completes the request with a `501 Not Implemented` response. + + +This directive is useful for: + +- Use in combination with JSONP (JSONP only supports GET) +- Supporting older browsers that lack support for certain HTTP methods. E.g. IE8 does not support PATCH Example ------- -TODO sample is missing, also in Scala documentation +.. includecode:: ../../../../code/docs/http/javadsl/server/directives/MethodDirectivesExamplesTest.java#overrideMethodWithParameter diff --git a/akka-docs/rst/java/http/routing-dsl/directives/method-directives/patch.rst b/akka-docs/rst/java/http/routing-dsl/directives/method-directives/patch.rst index 112d7b1b61..6137df7460 100644 --- a/akka-docs/rst/java/http/routing-dsl/directives/method-directives/patch.rst +++ b/akka-docs/rst/java/http/routing-dsl/directives/method-directives/patch.rst @@ -5,6 +5,9 @@ patch Matches requests with HTTP method ``PATCH``. +Description +----------- + This directive filters the incoming request by its HTTP method. Only requests with method ``PATCH`` are passed on to the inner route. All others are rejected with a ``MethodRejection``, which is translated into a ``405 Method Not Allowed`` response diff --git a/akka-docs/rst/java/http/routing-dsl/directives/method-directives/post.rst b/akka-docs/rst/java/http/routing-dsl/directives/method-directives/post.rst index 1243fa5072..30ce625fac 100644 --- a/akka-docs/rst/java/http/routing-dsl/directives/method-directives/post.rst +++ b/akka-docs/rst/java/http/routing-dsl/directives/method-directives/post.rst @@ -5,6 +5,9 @@ post Matches requests with HTTP method ``POST``. +Description +----------- + This directive filters the incoming request by its HTTP method. Only requests with method ``POST`` are passed on to the inner route. All others are rejected with a ``MethodRejection``, which is translated into a ``405 Method Not Allowed`` response diff --git a/akka-docs/rst/java/http/routing-dsl/directives/method-directives/put.rst b/akka-docs/rst/java/http/routing-dsl/directives/method-directives/put.rst index 26b628aa38..2ae9d994a8 100644 --- a/akka-docs/rst/java/http/routing-dsl/directives/method-directives/put.rst +++ b/akka-docs/rst/java/http/routing-dsl/directives/method-directives/put.rst @@ -5,6 +5,9 @@ put Matches requests with HTTP method ``PUT``. +Description +----------- + This directive filters the incoming request by its HTTP method. Only requests with method ``PUT`` are passed on to the inner route. All others are rejected with a ``MethodRejection``, which is translated into a ``405 Method Not Allowed`` response diff --git a/akka-docs/rst/java/http/routing-dsl/directives/misc-directives/extractClientIP.rst b/akka-docs/rst/java/http/routing-dsl/directives/misc-directives/extractClientIP.rst new file mode 100644 index 0000000000..e6fdd3db9e --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/misc-directives/extractClientIP.rst @@ -0,0 +1,16 @@ +.. _-extractClientIP-java-: + +extractClientIP +=============== + +Description +----------- +Provides the value of ``X-Forwarded-For``, ``Remote-Address``, or ``X-Real-IP`` headers as an instance of ``HttpIp``. + +The akka-http server engine adds the ``Remote-Address`` header to every request automatically if the respective +setting ``akka.http.server.remote-address-header`` is set to ``on``. Per default it is set to ``off``. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. + 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 new file mode 100644 index 0000000000..861e527ef6 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/misc-directives/index.rst @@ -0,0 +1,14 @@ +.. _MiscDirectives-java: + +MiscDirectives +============== + +.. toctree:: + :maxdepth: 1 + + extractClientIP + rejectEmptyResponse + requestEntityEmpty + requestEntityPresent + selectPreferredLanguage + validate diff --git a/akka-docs/rst/java/http/routing-dsl/directives/misc-directives/rejectEmptyResponse.rst b/akka-docs/rst/java/http/routing-dsl/directives/misc-directives/rejectEmptyResponse.rst new file mode 100644 index 0000000000..d1d5bb9556 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/misc-directives/rejectEmptyResponse.rst @@ -0,0 +1,16 @@ +.. _-rejectEmptyResponse-java-: + +rejectEmptyResponse +=================== + +Description +----------- +Replaces a response with no content with an empty rejection. + +The ``rejectEmptyResponse`` directive is mostly used with marshalling ``Option[T]`` instances. The value ``None`` is +usually marshalled to an empty but successful result. In many cases ``None`` should instead be handled as +``404 Not Found`` which is the effect of using ``rejectEmptyResponse``. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/misc-directives/requestEntityEmpty.rst b/akka-docs/rst/java/http/routing-dsl/directives/misc-directives/requestEntityEmpty.rst new file mode 100644 index 0000000000..fe59040063 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/misc-directives/requestEntityEmpty.rst @@ -0,0 +1,17 @@ +.. _-requestEntityEmpty-java-: + +requestEntityEmpty +================== + +Description +----------- +A filter that checks if the request entity is empty and only then passes processing to the inner route. +Otherwise, the request is rejected. + + +See also :ref:`-requestEntityPresent-java-` for the opposite effect. + + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/misc-directives/requestEntityPresent.rst b/akka-docs/rst/java/http/routing-dsl/directives/misc-directives/requestEntityPresent.rst new file mode 100644 index 0000000000..44ec6e4130 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/misc-directives/requestEntityPresent.rst @@ -0,0 +1,16 @@ +.. _-requestEntityPresent-java-: + +requestEntityPresent +==================== + +Description +----------- +A simple filter that checks if the request entity is present and only then passes processing to the inner route. +Otherwise, the request is rejected. + +See also :ref:`-requestEntityEmpty-java-` for the opposite effect. + + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/misc-directives/selectPreferredLanguage.rst b/akka-docs/rst/java/http/routing-dsl/directives/misc-directives/selectPreferredLanguage.rst new file mode 100644 index 0000000000..1a277e6c16 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/misc-directives/selectPreferredLanguage.rst @@ -0,0 +1,17 @@ +.. _-selectPreferredLanguage-java-: + +selectPreferredLanguage +======================= + +Description +----------- +Inspects the request's ``Accept-Language`` header and determines, +which of a given set of language alternatives is preferred by the client according to content negotiation rules +defined by http://tools.ietf.org/html/rfc7231#section-5.3.5. + +If there are several best language alternatives that the client has equal preference for +(even if this preference is zero!) the order of the arguments is used as a tie breaker (first one wins). + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/misc-directives/validate.rst b/akka-docs/rst/java/http/routing-dsl/directives/misc-directives/validate.rst new file mode 100644 index 0000000000..f91dd642bb --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/misc-directives/validate.rst @@ -0,0 +1,14 @@ +.. _-validate-java-: + +validate +======== +Allows validating a precondition before handling a route. + +Description +----------- +Checks an arbitrary condition and passes control to the inner route if it returns ``true``. +Otherwise, rejects the request with a ``ValidationRejection`` containing the given error message. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/parameter-directives/index.rst b/akka-docs/rst/java/http/routing-dsl/directives/parameter-directives/index.rst new file mode 100644 index 0000000000..249c508f2f --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/parameter-directives/index.rst @@ -0,0 +1,44 @@ +.. _ParameterDirectives-java: + +ParameterDirectives +=================== + +.. toctree:: + :maxdepth: 1 + + parameter + parameterMap + parameterMultiMap + parameterSeq + +.. _which-parameter-directive-java: + +When to use which parameter directive? +-------------------------------------- + +Usually, you want to use the high-level :ref:`-parameter-java-` directive. When you need +more low-level access you can use the table below to decide which directive +to use which shows properties of different parameter directives. + +================================ ====== ======== ===== +directive level ordering multi +================================ ====== ======== ===== +:ref:`-parameter-java-` high no no +:ref:`-parameterMap-java-` low no no +:ref:`-parameterMultiMap-java-` low no yes +:ref:`-parameterList-java-` low yes yes +================================ ====== ======== ===== + +level + high-level parameter directives extract subset of all parameters by name and allow conversions + and automatically report errors if expectations are not met, low-level directives give you + all parameters at once, leaving all further processing to you + +ordering + original ordering from request URL is preserved + +multi + multiple values per parameter name are possible + +.. note:: + If you need to extract multiple parameters, apply the ``parameter`` directive multiple times. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/parameter-directives/parameter.rst b/akka-docs/rst/java/http/routing-dsl/directives/parameter-directives/parameter.rst new file mode 100644 index 0000000000..93491087c1 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/parameter-directives/parameter.rst @@ -0,0 +1,15 @@ +.. _-parameter-java-: + +parameter +========= +Extracts a *query* parameter value from the request. + +Description +----------- +See :ref:`-parameter-java-` for a detailed description of this directive. + +See :ref:`which-parameter-directive-java` to understand when to use which directive. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/parameter-directives/parameterMap.rst b/akka-docs/rst/java/http/routing-dsl/directives/parameter-directives/parameterMap.rst new file mode 100644 index 0000000000..7713310308 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/parameter-directives/parameterMap.rst @@ -0,0 +1,15 @@ +.. _-parameterMap-java-: + +parameterMap +============ +Extracts all parameters at once as a ``Map`` mapping parameter names to parameter values. + +Description +----------- +If a query contains a parameter value several times, the map will contain the last one. + +See also :ref:`which-parameter-directive-java` to understand when to use which directive. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/parameter-directives/parameterMultiMap.rst b/akka-docs/rst/java/http/routing-dsl/directives/parameter-directives/parameterMultiMap.rst new file mode 100644 index 0000000000..75bceb91b6 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/parameter-directives/parameterMultiMap.rst @@ -0,0 +1,20 @@ +.. _-parameterMultiMap-java-: + +parameterMultiMap +================= + +Description +----------- + +Extracts all parameters at once as a multi-map of type ``Map>`` mapping +a parameter name to a list of all its values. + +This directive can be used if parameters can occur several times. + +The order of values is *not* specified. + +See :ref:`which-parameter-directive-java` to understand when to use which directive. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/parameter-directives/parameterSeq.rst b/akka-docs/rst/java/http/routing-dsl/directives/parameter-directives/parameterSeq.rst new file mode 100644 index 0000000000..89c6808c91 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/parameter-directives/parameterSeq.rst @@ -0,0 +1,16 @@ +.. _-parameterList-java-: + +parameterList +============= + +Description +----------- +Extracts all parameters at once in the original order as (name, value) tuples of type ``Map.Entry``. + +This directive can be used if the exact order of parameters is important or if parameters can occur several times. + +See :ref:`which-parameter-directive-java` to understand when to use which directive. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/path-directives.rst b/akka-docs/rst/java/http/routing-dsl/directives/path-directives.rst index 3655e182c9..2bce0e81da 100644 --- a/akka-docs/rst/java/http/routing-dsl/directives/path-directives.rst +++ b/akka-docs/rst/java/http/routing-dsl/directives/path-directives.rst @@ -30,9 +30,8 @@ Other path matchers defined in ``PathMatchers`` match the end of the path (``Pat (``PathMatchers.SLASH``), or nothing at all (``PathMatchers.NEUTRAL``). Many path matchers are hybrids that can both match (by using them with one of the PathDirectives) and extract values, -i.e. they are :ref:`request-vals-java`. Extracting a path matcher value (i.e. using it with ``handleWithX``) is only -allowed if it nested inside a path directive that uses that path matcher and so specifies at which position the value -should be extracted from the path. +Extracting a path matcher value (i.e. using it with ``handleWithX``) is only allowed if it nested inside a path +directive that uses that path matcher and so specifies at which position the value should be extracted from the path. Predefined path matchers allow extraction of various types of values: diff --git a/akka-docs/rst/java/http/routing-dsl/directives/path-directives/index.rst b/akka-docs/rst/java/http/routing-dsl/directives/path-directives/index.rst new file mode 100644 index 0000000000..842b211064 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/path-directives/index.rst @@ -0,0 +1,21 @@ +.. _PathDirectives-java: + +PathDirectives +============== + +.. toctree:: + :maxdepth: 1 + + path + pathEnd + pathEndOrSingleSlash + pathPrefix + pathPrefixTest + pathSingleSlash + pathSuffix + pathSuffixTest + rawPathPrefix + rawPathPrefixTest + redirectToNoTrailingSlashIfPresent + redirectToTrailingSlashIfMissing + ../path-directives diff --git a/akka-docs/rst/java/http/routing-dsl/directives/path-directives/path.rst b/akka-docs/rst/java/http/routing-dsl/directives/path-directives/path.rst new file mode 100644 index 0000000000..afbf2475fb --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/path-directives/path.rst @@ -0,0 +1,30 @@ +.. _-path-java-: + +path +==== + +Description +----------- +Matches the complete unmatched path of the ``RequestContext`` against the given ``PathMatcher``, potentially extracts +one or more values (depending on the type of the argument). + +This directive filters incoming requests based on the part of their URI that hasn't been matched yet by other +potentially existing :ref:`-pathPrefix-java-` directives on higher levels of the routing structure. +Its one parameter is usually an expression evaluating to a ``PathMatcher`` instance (see also: :ref:`pathmatcher-dsl`). + +As opposed to the :ref:`-rawPathPrefix-java-` or :ref:`-rawPathPrefixTest-java-` directives ``path`` automatically adds a leading +slash to its ``PathMatcher`` argument, you therefore don't have to start your matching expression with an explicit slash. + +The ``path`` directive attempts to match the **complete** remaining path, not just a prefix. If you only want to match +a path prefix and then delegate further filtering to a lower level in your routing structure use the :ref:`-pathPrefix-java-` +directive instead. As a consequence it doesn't make sense to nest a ``path`` or :ref:`-pathPrefix-java-` directive +underneath another ``path`` directive, as there is no way that they will ever match (since the unmatched path underneath +a ``path`` directive will always be empty). + +Depending on the type of its ``PathMatcher`` argument the ``path`` directive extracts zero or more values from the URI. +If the match fails the request is rejected with an :ref:`empty rejection set `. + + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/path-directives/pathEnd.rst b/akka-docs/rst/java/http/routing-dsl/directives/path-directives/pathEnd.rst new file mode 100644 index 0000000000..fa7513832a --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/path-directives/pathEnd.rst @@ -0,0 +1,18 @@ +.. _-pathEnd-java-: + +pathEnd +======= + +Description +----------- +Only passes the request to its inner route if the unmatched path of the ``RequestContext`` is empty, i.e. the request +path has been fully matched by a higher-level :ref:`-path-java-` or :ref:`-pathPrefix-java-` directive. + + +This directive is a simple alias for ``rawPathPrefix(PathEnd)`` and is mostly used on an +inner-level to discriminate "path already fully matched" from other alternatives (see the example below). + + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/path-directives/pathEndOrSingleSlash.rst b/akka-docs/rst/java/http/routing-dsl/directives/path-directives/pathEndOrSingleSlash.rst new file mode 100644 index 0000000000..c1512f73b7 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/path-directives/pathEndOrSingleSlash.rst @@ -0,0 +1,19 @@ +.. _-pathEndOrSingleSlash-java-: + +pathEndOrSingleSlash +==================== + +Description +----------- +Only passes the request to its inner route if the unmatched path of the ``RequestContext`` is either empty +or contains only one single slash. + +This directive is a simple alias for ``rawPathPrefix(Slash.? ~ PathEnd)`` and is mostly used on an inner-level to +discriminate "path already fully matched" from other alternatives (see the example below). + +It is equivalent to ``pathEnd | pathSingleSlash`` but slightly more efficient. + + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/path-directives/pathPrefix.rst b/akka-docs/rst/java/http/routing-dsl/directives/path-directives/pathPrefix.rst new file mode 100644 index 0000000000..4f8b5bae96 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/path-directives/pathPrefix.rst @@ -0,0 +1,24 @@ +.. _-pathPrefix-java-: + +pathPrefix +========== + +Description +----------- +Matches and consumes a prefix of the unmatched path of the ``RequestContext`` against the given ``PathMatcher``, +potentially extracts one or more values (depending on the type of the argument). + +This directive filters incoming requests based on the part of their URI that hasn't been matched yet by other +potentially existing ``pathPrefix`` or :ref:`-rawPathPrefix-java-` directives on higher levels of the routing structure. +Its one parameter is usually an expression evaluating to a ``PathMatcher`` instance (see also: :ref:`pathmatcher-dsl`). + +As opposed to its :ref:`-rawPathPrefix-java-` counterpart ``pathPrefix`` automatically adds a leading slash to its +``PathMatcher`` argument, you therefore don't have to start your matching expression with an explicit slash. + +Depending on the type of its ``PathMatcher`` argument the ``pathPrefix`` directive extracts zero or more values from +the URI. If the match fails the request is rejected with an :ref:`empty rejection set `. + + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/path-directives/pathPrefixTest.rst b/akka-docs/rst/java/http/routing-dsl/directives/path-directives/pathPrefixTest.rst new file mode 100644 index 0000000000..10e9374179 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/path-directives/pathPrefixTest.rst @@ -0,0 +1,27 @@ +.. _-pathPrefixTest-java-: + +pathPrefixTest +============== + +Description +----------- +Checks whether the unmatched path of the ``RequestContext`` has a prefix matched by the given ``PathMatcher``. +Potentially extracts one or more values (depending on the type of the argument) but doesn't consume its match from +the unmatched path. + +This directive is very similar to the :ref:`-pathPrefix-java-` directive with the one difference that the path prefix +it matched (if it matched) is *not* consumed. The unmatched path of the ``RequestContext`` is therefore left as +is even in the case that the directive successfully matched and the request is passed on to its inner route. + +For more info on how to create a ``PathMatcher`` see :ref:`pathmatcher-dsl`. + +As opposed to its :ref:`-rawPathPrefixTest-java-` counterpart ``pathPrefixTest`` automatically adds a leading slash to its +``PathMatcher`` argument, you therefore don't have to start your matching expression with an explicit slash. + +Depending on the type of its ``PathMatcher`` argument the ``pathPrefixTest`` directive extracts zero or more values from +the URI. If the match fails the request is rejected with an :ref:`empty rejection set `. + + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/path-directives/pathSingleSlash.rst b/akka-docs/rst/java/http/routing-dsl/directives/path-directives/pathSingleSlash.rst new file mode 100644 index 0000000000..a9c58d02f3 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/path-directives/pathSingleSlash.rst @@ -0,0 +1,17 @@ +.. _-pathSingleSlash-java-: + +pathSingleSlash +=============== + +Description +----------- +Only passes the request to its inner route if the unmatched path of the ``RequestContext`` +contains exactly one single slash. + +This directive is a simple alias for ``pathPrefix(PathEnd)`` and is mostly used for matching requests to the root URI +(``/``) on an inner-level to discriminate "all path segments matched" from other alternatives (see the example below). + + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/path-directives/pathSuffix.rst b/akka-docs/rst/java/http/routing-dsl/directives/path-directives/pathSuffix.rst new file mode 100644 index 0000000000..109603e960 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/path-directives/pathSuffix.rst @@ -0,0 +1,27 @@ +.. _-pathSuffix-java-: + +pathSuffix +========== + +Description +----------- +Matches and consumes a suffix of the unmatched path of the ``RequestContext`` against the given ``PathMatcher``, +potentially extracts one or more values (depending on the type of the argument). + +This directive filters incoming requests based on the part of their URI that hasn't been matched yet by other +potentially existing path matching directives on higher levels of the routing structure. +Its one parameter is usually an expression evaluating to a ``PathMatcher`` instance (see also: :ref:`pathmatcher-dsl`). + +As opposed to :ref:`-pathPrefix-java-` this directive matches and consumes the unmatched path from the right, i.e. the end. + +.. caution:: For efficiency reasons, the given ``PathMatcher`` must match the desired suffix in reversed-segment + order, i.e. ``pathSuffix("baz" / "bar")`` would match ``/foo/bar/baz``! The order within a segment match is + not reversed. + +Depending on the type of its ``PathMatcher`` argument the ``pathPrefix`` directive extracts zero or more values from +the URI. If the match fails the request is rejected with an :ref:`empty rejection set `. + + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/path-directives/pathSuffixTest.rst b/akka-docs/rst/java/http/routing-dsl/directives/path-directives/pathSuffixTest.rst new file mode 100644 index 0000000000..46738f3f5e --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/path-directives/pathSuffixTest.rst @@ -0,0 +1,28 @@ +.. _-pathSuffixTest-java-: + +pathSuffixTest +============== + +Description +----------- +Checks whether the unmatched path of the ``RequestContext`` has a suffix matched by the given ``PathMatcher``. +Potentially extracts one or more values (depending on the type of the argument) but doesn't consume its match from +the unmatched path. + +This directive is very similar to the :ref:`-pathSuffix-java-` directive with the one difference that the path suffix +it matched (if it matched) is *not* consumed. The unmatched path of the ``RequestContext`` is therefore left as +is even in the case that the directive successfully matched and the request is passed on to its inner route. + +As opposed to :ref:`-pathPrefixTest-java-` this directive matches and consumes the unmatched path from the right, i.e. the end. + +.. caution:: For efficiency reasons, the given ``PathMatcher`` must match the desired suffix in reversed-segment + order, i.e. ``pathSuffixTest("baz" / "bar")`` would match ``/foo/bar/baz``! The order within a segment match is + not reversed. + +Depending on the type of its ``PathMatcher`` argument the ``pathSuffixTest`` directive extracts zero or more values from +the URI. If the match fails the request is rejected with an :ref:`empty rejection set `. + + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/path-directives/rawPathPrefix.rst b/akka-docs/rst/java/http/routing-dsl/directives/path-directives/rawPathPrefix.rst new file mode 100644 index 0000000000..73bd809966 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/path-directives/rawPathPrefix.rst @@ -0,0 +1,24 @@ +.. _-rawPathPrefix-java-: + +rawPathPrefix +============= + +Description +----------- +Matches and consumes a prefix of the unmatched path of the ``RequestContext`` against the given ``PathMatcher``, +potentially extracts one or more values (depending on the type of the argument). + +This directive filters incoming requests based on the part of their URI that hasn't been matched yet by other +potentially existing ``rawPathPrefix`` or :ref:`-pathPrefix-java-` directives on higher levels of the routing structure. +Its one parameter is usually an expression evaluating to a ``PathMatcher`` instance (see also: :ref:`pathmatcher-dsl`). + +As opposed to its :ref:`-pathPrefix-java-` counterpart ``rawPathPrefix`` does *not* automatically add a leading slash to its +``PathMatcher`` argument. Rather its ``PathMatcher`` argument is applied to the unmatched path as is. + +Depending on the type of its ``PathMatcher`` argument the ``rawPathPrefix`` directive extracts zero or more values from +the URI. If the match fails the request is rejected with an :ref:`empty rejection set `. + + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/path-directives/rawPathPrefixTest.rst b/akka-docs/rst/java/http/routing-dsl/directives/path-directives/rawPathPrefixTest.rst new file mode 100644 index 0000000000..8b31b86fcc --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/path-directives/rawPathPrefixTest.rst @@ -0,0 +1,27 @@ +.. _-rawPathPrefixTest-java-: + +rawPathPrefixTest +================= + +Description +----------- +Checks whether the unmatched path of the ``RequestContext`` has a prefix matched by the given ``PathMatcher``. +Potentially extracts one or more values (depending on the type of the argument) but doesn't consume its match from +the unmatched path. + +This directive is very similar to the :ref:`-pathPrefix-java-` directive with the one difference that the path prefix +it matched (if it matched) is *not* consumed. The unmatched path of the ``RequestContext`` is therefore left as +is even in the case that the directive successfully matched and the request is passed on to its inner route. + +For more info on how to create a ``PathMatcher`` see :ref:`pathmatcher-dsl`. + +As opposed to its :ref:`-pathPrefixTest-java-` counterpart ``rawPathPrefixTest`` does *not* automatically add a leading slash +to its ``PathMatcher`` argument. Rather its ``PathMatcher`` argument is applied to the unmatched path as is. + +Depending on the type of its ``PathMatcher`` argument the ``rawPathPrefixTest`` directive extracts zero or more values +from the URI. If the match fails the request is rejected with an :ref:`empty rejection set `. + + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/path-directives/redirectToNoTrailingSlashIfPresent.rst b/akka-docs/rst/java/http/routing-dsl/directives/path-directives/redirectToNoTrailingSlashIfPresent.rst new file mode 100644 index 0000000000..6f15e151ba --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/path-directives/redirectToNoTrailingSlashIfPresent.rst @@ -0,0 +1,29 @@ +.. _-redirectToNoTrailingSlashIfPresent-java-: + +redirectToNoTrailingSlashIfPresent +================================== + +Description +----------- +If the requested path does end with a trailing ``/`` character, +redirects to the same path without that trailing slash.. + +Redirects the HTTP Client to the same resource yet without the trailing ``/``, in case the request contained it. +When redirecting an HttpResponse with the given redirect response code (i.e. ``MovedPermanently`` or ``TemporaryRedirect`` +etc.) as well as a simple HTML page containing a "*click me to follow redirect*" link to be used in case the client can not, +or refuses to for security reasons, automatically follow redirects. + +Please note that the inner paths **MUST NOT** end with an explicit trailing slash (e.g. ``"things"./``) +for the re-directed-to route to match. + +A good read on the subject of how to deal with trailing slashes is available on `Google Webmaster Central - To Slash or not to Slash`_. + +See also :ref:`-redirectToTrailingSlashIfMissing-java-` for the opposite behaviour. + +.. _Google Webmaster Central - To Slash or not to Slash: http://googlewebmastercentral.blogspot.de/2010/04/to-slash-or-not-to-slash.html + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. + +See also :ref:`-redirectToTrailingSlashIfMissing-java-` which achieves the opposite - redirecting paths in case they do *not* have a trailing slash. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/path-directives/redirectToTrailingSlashIfMissing.rst b/akka-docs/rst/java/http/routing-dsl/directives/path-directives/redirectToTrailingSlashIfMissing.rst new file mode 100644 index 0000000000..1552eb67f4 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/path-directives/redirectToTrailingSlashIfMissing.rst @@ -0,0 +1,25 @@ +.. _-redirectToTrailingSlashIfMissing-java-: + +redirectToTrailingSlashIfMissing +================================ + +Description +----------- +If the requested path does not end with a trailing ``/`` character, +redirects to the same path followed by such trailing slash. + +Redirects the HTTP Client to the same resource yet followed by a trailing ``/``, in case the request did not contain it. +When redirecting an HttpResponse with the given redirect response code (i.e. ``MovedPermanently`` or ``TemporaryRedirect`` +etc.) as well as a simple HTML page containing a "*click me to follow redirect*" link to be used in case the client can not, +or refuses to for security reasons, automatically follow redirects. + +Please note that the inner paths **MUST** end with an explicit trailing slash (e.g. ``"things"./``) for the +re-directed-to route to match. + +See also :ref:`-redirectToNoTrailingSlashIfPresent-java-` for the opposite behaviour. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. + +See also :ref:`-redirectToNoTrailingSlashIfPresent-java-` which achieves the opposite - redirecting paths in case they do have a trailing slash. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/range-directives/index.rst b/akka-docs/rst/java/http/routing-dsl/directives/range-directives/index.rst new file mode 100644 index 0000000000..2602efb419 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/range-directives/index.rst @@ -0,0 +1,9 @@ +.. _RangeDirectives-java: + +RangeDirectives +=============== + +.. toctree:: + :maxdepth: 1 + + withRangeSupport diff --git a/akka-docs/rst/java/http/routing-dsl/directives/range-directives/withRangeSupport.rst b/akka-docs/rst/java/http/routing-dsl/directives/range-directives/withRangeSupport.rst new file mode 100644 index 0000000000..263d6325d6 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/range-directives/withRangeSupport.rst @@ -0,0 +1,30 @@ +.. _-withRangeSupport-java-: + +withRangeSupport +================ + +Description +----------- +Transforms the response from its inner route into a ``206 Partial Content`` +response if the client requested only part of the resource with a ``Range`` header. + +Augments responses to ``GET`` requests with an ``Accept-Ranges: bytes`` header and converts them into partial responses +if the request contains a valid ``Range`` request header. The requested byte-ranges are coalesced (merged) if they +lie closer together than the specified ``rangeCoalescingThreshold`` argument. + +In order to prevent the server from becoming overloaded with trying to prepare ``multipart/byteranges`` responses for +high numbers of potentially very small ranges the directive rejects requests requesting more than ``rangeCountLimit`` +ranges with a ``TooManyRangesRejection``. +Requests with unsatisfiable ranges are rejected with an ``UnsatisfiableRangeRejection``. + +The ``withRangeSupport()`` form (without parameters) uses the ``range-coalescing-threshold`` and ``range-count-limit`` +settings from the ``akka.http.routing`` configuration. + +This directive is transparent to non-``GET`` requests. + +See also: https://tools.ietf.org/html/rfc7233 + + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/respond-with-directives/index.rst b/akka-docs/rst/java/http/routing-dsl/directives/respond-with-directives/index.rst new file mode 100644 index 0000000000..210f0a5bf9 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/respond-with-directives/index.rst @@ -0,0 +1,13 @@ +.. _RespondWithDirectives-java: + +RespondWithDirectives +===================== + +.. toctree:: + :maxdepth: 1 + + respondWithDefaultHeader + respondWithDefaultHeaders + respondWithHeader + respondWithHeaders + respondWithHeaders diff --git a/akka-docs/rst/java/http/routing-dsl/directives/respond-with-directives/respondWithDefaultHeader.rst b/akka-docs/rst/java/http/routing-dsl/directives/respond-with-directives/respondWithDefaultHeader.rst new file mode 100644 index 0000000000..d110cd9e95 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/respond-with-directives/respondWithDefaultHeader.rst @@ -0,0 +1,21 @@ +.. _-respondWithDefaultHeader-java-: + +respondWithDefaultHeader +======================== + +Description +----------- +Adds a given HTTP header to all responses coming back from its inner route only if a header with the same name doesn't +exist yet in the response. + + +This directive transforms ``HttpResponse`` and ``ChunkedResponseStart`` messages coming back from its inner route by +potentially adding the given ``HttpHeader`` instance to the headers list. +The header is only added if there is no header instance with the same name (case insensitively) already present in the +response. + +See also :ref:`-respondWithDefaultHeaders-java-` if you'd like to add more than one header. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/respond-with-directives/respondWithDefaultHeaders.rst b/akka-docs/rst/java/http/routing-dsl/directives/respond-with-directives/respondWithDefaultHeaders.rst new file mode 100644 index 0000000000..da81de4a33 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/respond-with-directives/respondWithDefaultHeaders.rst @@ -0,0 +1,35 @@ +.. _-respondWithDefaultHeaders-java-: + +respondWithDefaultHeaders +========================= + +Description +----------- +Adds the given HTTP headers to all responses coming back from its inner route only if a respective header with the same +name doesn't exist yet in the response. + + +This directive transforms ``HttpResponse`` and ``ChunkedResponseStart`` messages coming back from its inner route by +potentially adding the given ``HttpHeader`` instances to the headers list. +A header is only added if there is no header instance with the same name (case insensitively) already present in the +response. + +See also :ref:`-respondWithDefaultHeader-java-` if you'd like to add only a single header. + + +Example +------- + +The ``respondWithDefaultHeaders`` directive is equivalent to the ``respondWithDefaultHeader`` directive which +is shown in the example below, however it allows including multiple default headers at once in the directive, like so:: + + respondWithDefaultHeaders( + Origin(HttpOrigin("http://akka.io"), + RawHeader("X-Fish-Name", "Blippy"))) { /*...*/ } + + +The semantics remain the same however, as explained by the following example: + +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. + +See the :ref:`-respondWithDefaultHeader-java-` directive for an example with only one header. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/respond-with-directives/respondWithHeader.rst b/akka-docs/rst/java/http/routing-dsl/directives/respond-with-directives/respondWithHeader.rst new file mode 100644 index 0000000000..655f010f04 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/respond-with-directives/respondWithHeader.rst @@ -0,0 +1,17 @@ +.. _-respondWithHeader-java-: + +respondWithHeader +================= + +Description +----------- +Adds a given HTTP header to all responses coming back from its inner route. + +This directive transforms ``HttpResponse`` and ``ChunkedResponseStart`` messages coming back from its inner route by +adding the given ``HttpHeader`` instance to the headers list. + +See also :ref:`-respondWithHeaders-java-` if you'd like to add more than one header. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/respond-with-directives/respondWithHeaders.rst b/akka-docs/rst/java/http/routing-dsl/directives/respond-with-directives/respondWithHeaders.rst new file mode 100644 index 0000000000..ff4b79f70e --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/respond-with-directives/respondWithHeaders.rst @@ -0,0 +1,18 @@ +.. _-respondWithHeaders-java-: + +respondWithHeaders +================== + +Description +----------- +Adds the given HTTP headers to all responses coming back from its inner route. + +This directive transforms ``HttpResponse`` and ``ChunkedResponseStart`` messages coming back from its inner route by +adding the given ``HttpHeader`` instances to the headers list. + +See also :ref:`-respondWithHeader-java-` if you'd like to add just a single header. + + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/route-directives/complete.rst b/akka-docs/rst/java/http/routing-dsl/directives/route-directives/complete.rst new file mode 100644 index 0000000000..0380a70a9b --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/route-directives/complete.rst @@ -0,0 +1,20 @@ +.. _-complete-java-: + +complete +======== + +Description +----------- + +Completes the request using the given argument(s). + +``complete`` uses the given arguments to construct a ``Route`` which simply calls ``complete`` on the ``RequestContext`` +with the respective ``HttpResponse`` instance. +Completing the request will send the response "back up" the route structure where all the logic runs that wrapping +directives have potentially chained into the :class:`RouteResult` future transformation chain. + +Please note that the ``complete`` directive has multiple variants, like + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/route-directives/failWith.rst b/akka-docs/rst/java/http/routing-dsl/directives/route-directives/failWith.rst new file mode 100644 index 0000000000..5e5aae085c --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/route-directives/failWith.rst @@ -0,0 +1,27 @@ +.. _-failWith-java-: + +failWith +======== + +Description +----------- +Bubbles up the given error through the route structure where it is dealt with by the closest ``handleExceptions`` +directive and its :class:`ExceptionHandler`. + +``failWith`` explicitly raises an exception that gets bubbled up through the route structure to be picked up by the +nearest ``handleExceptions`` directive. Using ``failWith`` rather than simply throwing an exception enables the route +structure's :ref:`exception-handling-java` mechanism to deal with the exception even if the current route is executed +asynchronously on another thread (e.g. in a ``Future`` or separate actor). + +If no ``handleExceptions`` is present above the respective location in the +route structure the top-level routing logic will handle the exception and translate it into a corresponding +``HttpResponse`` using the in-scope ``ExceptionHandler`` (see also the :ref:`exception-handling-java` chapter). + +There is one notable special case: If the given exception is a ``RejectionError`` exception it is *not* bubbled up, +but rather the wrapped exception is unpacked and "executed". This allows the "tunneling" of a rejection via an +exception. + + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/route-directives/index.rst b/akka-docs/rst/java/http/routing-dsl/directives/route-directives/index.rst new file mode 100644 index 0000000000..af790f6afc --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/route-directives/index.rst @@ -0,0 +1,21 @@ +.. _RouteDirectives-java: + +RouteDirectives +=============== + +The ``RouteDirectives`` have a special role in akka-http's routing DSL. Contrary to all other directives (except most +:ref:`FileAndResourceDirectives-java`) they do not produce instances of type ``Directive[L <: HList]`` but rather "plain" +routes of type ``Route``. +The reason is that the ``RouteDirectives`` are not meant for wrapping an inner route (like most other directives, as +intermediate-level elements of a route structure, do) but rather form the leaves of the actual route structure **leaves**. + +So in most cases the inner-most element of a route structure branch is one of the ``RouteDirectives`` (or +:ref:`FileAndResourceDirectives-java`): + +.. toctree:: + :maxdepth: 1 + + complete + failWith + redirect + reject diff --git a/akka-docs/rst/java/http/routing-dsl/directives/route-directives/redirect.rst b/akka-docs/rst/java/http/routing-dsl/directives/route-directives/redirect.rst new file mode 100644 index 0000000000..bd98427222 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/route-directives/redirect.rst @@ -0,0 +1,18 @@ +.. _-redirect-java-: + +redirect +======== + +Description +----------- +Completes the request with a redirection response to a given targer URI and of a given redirection type (status code). + +``redirect`` is a convenience helper for completing the request with a redirection response. +It is equivalent to this snippet relying on the ``complete`` directive: + +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. + + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/route-directives/reject.rst b/akka-docs/rst/java/http/routing-dsl/directives/route-directives/reject.rst new file mode 100644 index 0000000000..2a21de1c68 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/route-directives/reject.rst @@ -0,0 +1,22 @@ +.. _-reject-java-: + +reject +====== + +Description +----------- +Explicitly rejects the request optionally using the given rejection(s). + +``reject`` uses the given rejection instances (which might be the empty ``Seq``) to construct a ``Route`` which simply +calls ``requestContext.reject``. See the chapter on :ref:`rejections-java` for more information on what this means. + +After the request has been rejected at the respective point it will continue to flow through the routing structure in +the search for a route that is able to complete it. + +The explicit ``reject`` directive is used mostly when building :ref:`Custom Directives`, e.g. inside of a ``flatMap`` +modifier for "filtering out" certain cases. + + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/scheme-directives/extractScheme.rst b/akka-docs/rst/java/http/routing-dsl/directives/scheme-directives/extractScheme.rst new file mode 100644 index 0000000000..a1f7fb3dfe --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/scheme-directives/extractScheme.rst @@ -0,0 +1,14 @@ +.. _-extractScheme-java-: + +extractScheme +============= + +Description +----------- +Extracts the Uri scheme (i.e. "``http``", "``https``", etc.) for an incoming request. + +For rejecting a request if it doesn't match a specified scheme name, see the :ref:`-scheme-java-` directive. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/scheme-directives/index.rst b/akka-docs/rst/java/http/routing-dsl/directives/scheme-directives/index.rst new file mode 100644 index 0000000000..66b46793c5 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/scheme-directives/index.rst @@ -0,0 +1,13 @@ +.. _SchemeDirectives-java: + +SchemeDirectives +================ + +Scheme directives can be used to extract the Uri scheme (i.e. "http", "https", etc.) +from requests or to reject any request that does not match a specified scheme name. + +.. toctree:: + :maxdepth: 1 + + extractScheme + scheme diff --git a/akka-docs/rst/java/http/routing-dsl/directives/scheme-directives/scheme.rst b/akka-docs/rst/java/http/routing-dsl/directives/scheme-directives/scheme.rst new file mode 100644 index 0000000000..c4c65261e2 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/scheme-directives/scheme.rst @@ -0,0 +1,21 @@ +.. _-scheme-java-: + +scheme +====== + +Description +----------- +Rejects a request if its Uri scheme does not match a given one. + +The ``scheme`` directive can be used to match requests by their Uri scheme, only passing +through requests that match the specified scheme and rejecting all others. + +A typical use case for the ``scheme`` directive would be to reject requests coming in over +http instead of https, or to redirect such requests to the matching https URI with a +``MovedPermanently``. + +For simply extracting the scheme name, see the :ref:`-extractScheme-java-` directive. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateBasic.rst b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateBasic.rst new file mode 100644 index 0000000000..fb3999f259 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateBasic.rst @@ -0,0 +1,30 @@ +.. _-authenticateBasic-java-: + +authenticateBasic +================= +Wraps the inner route with Http Basic authentication support using a given ``Authenticator``. + +Description +----------- +Provides support for handling `HTTP Basic Authentication`_. + +Given a function returning an ``Optional`` with a value upon successful authentication and an empty ``Optional`` otherwise, +respectively applies the inner route or rejects the request with a :class:`AuthenticationFailedRejection` rejection, +which by default is mapped to an ``401 Unauthorized`` response. + +Longer-running authentication tasks (like looking up credentials in a database) should use the :ref:`-authenticateBasicAsync-java-` +variant of this directive which allows it to run without blocking routing layer of Akka HTTP, freeing it for other requests. + +Standard HTTP-based authentication which uses the ``WWW-Authenticate`` header containing challenge data and +``Authorization`` header for receiving credentials is implemented in subclasses of ``HttpAuthenticator``. + +See :ref:`credentials-and-timing-attacks-java` for details about verifying the secret. + +.. warning:: + Make sure to use basic authentication only over SSL/TLS because credentials are transferred in plaintext. + +.. _HTTP Basic Authentication: https://en.wikipedia.org/wiki/Basic_auth + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateBasicAsync.rst b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateBasicAsync.rst new file mode 100644 index 0000000000..4cd3f54777 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateBasicAsync.rst @@ -0,0 +1,28 @@ +.. _-authenticateBasicAsync-java-: + +authenticateBasicAsync +====================== +Wraps the inner route with Http Basic authentication support using a given ``AsyncAuthenticator``. + +Description +----------- +This variant of the :ref:`-authenticateBasic-java-` directive returns a ``Future>`` which allows freeing up the routing +layer of Akka HTTP, freeing it for other requests. It should be used whenever an authentication is expected to take +a longer amount of time (e.g. looking up the user in a database). + +In case the returned option is an empty ``Optional`` the request is rejected with a :class:`AuthenticationFailedRejection`, +which by default is mapped to an ``401 Unauthorized`` response. + +Standard HTTP-based authentication which uses the ``WWW-Authenticate`` header containing challenge data and +``Authorization`` header for receiving credentials is implemented in subclasses of ``HttpAuthenticator``. + +See :ref:`credentials-and-timing-attacks-java` for details about verifying the secret. + +.. warning:: + Make sure to use basic authentication only over SSL/TLS because credentials are transferred in plaintext. + +.. _HTTP Basic Authentication: https://en.wikipedia.org/wiki/Basic_auth + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateBasicPF.rst b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateBasicPF.rst new file mode 100644 index 0000000000..f5731af93f --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateBasicPF.rst @@ -0,0 +1,28 @@ +.. _-authenticateBasicPF-java-: + +authenticateBasicPF +=================== +Wraps the inner route with Http Basic authentication support using a given ``AuthenticatorPF``. + +Description +----------- +Provides support for handling `HTTP Basic Authentication`_. + +Refer to :ref:`-authenticateBasic-java-` for a detailed description of this directive. + +Its semantics are equivalent to ``authenticateBasicPF`` 's, where not handling a case in the Partial Function (PF) +leaves the request to be rejected with a :class:`AuthenticationFailedRejection` rejection. + +Longer-running authentication tasks (like looking up credentials in a database) should use :ref:`-authenticateBasicAsync-java-` +or :ref:`-authenticateBasicPFAsync-java-` if you prefer to use the ``PartialFunction`` syntax. + +See :ref:`credentials-and-timing-attacks-java` for details about verifying the secret. + +.. warning:: + Make sure to use basic authentication only over SSL/TLS because credentials are transferred in plaintext. + +.. _HTTP Basic Authentication: https://en.wikipedia.org/wiki/Basic_auth + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateBasicPFAsync.rst b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateBasicPFAsync.rst new file mode 100644 index 0000000000..ff0e95174e --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateBasicPFAsync.rst @@ -0,0 +1,25 @@ +.. _-authenticateBasicPFAsync-java-: + +authenticateBasicPFAsync +======================== +Wraps the inner route with Http Basic authentication support using a given ``AsyncAuthenticatorPF``. + +Description +----------- +Provides support for handling `HTTP Basic Authentication`_. + +Refer to :ref:`-authenticateBasic-java-` for a detailed description of this directive. + +Its semantics are equivalent to ``authenticateBasicPF`` 's, where not handling a case in the Partial Function (PF) +leaves the request to be rejected with a :class:`AuthenticationFailedRejection` rejection. + +See :ref:`credentials-and-timing-attacks-java` for details about verifying the secret. + +.. warning:: + Make sure to use basic authentication only over SSL/TLS because credentials are transferred in plaintext. + +.. _HTTP Basic Authentication: https://en.wikipedia.org/wiki/Basic_auth + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateOAuth2.rst b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateOAuth2.rst new file mode 100644 index 0000000000..38fa54e681 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateOAuth2.rst @@ -0,0 +1,33 @@ +.. _-authenticateOAuth2-java-: + +authenticateOAuth2 +================== +Wraps the inner route with OAuth Bearer Token authentication support using a given ``AuthenticatorPF`` + +Description +----------- +Provides support for extracting the so-called "*Bearer Token*" from the :class:`Authorization` HTTP Header, +which is used to initiate an OAuth2 authorization. + +.. warning:: + This directive does not implement the complete OAuth2 protocol, but instead enables implementing it, + by extracting the needed token from the HTTP headers. + +Given a function returning ``Some`` upon successful authentication and ``None`` otherwise, +respectively applies the inner route or rejects the request with a :class:`AuthenticationFailedRejection` rejection, +which by default is mapped to an ``401 Unauthorized`` response. + +Longer-running authentication tasks (like looking up credentials in a database) should use the :ref:`-authenticateOAuth2Async-java-` +variant of this directive which allows it to run without blocking routing layer of Akka HTTP, freeing it for other requests. + +See :ref:`credentials-and-timing-attacks-java` for details about verifying the secret. + +For more information on how OAuth2 works see `RFC 6750`_. + +.. _RFC 6750: https://tools.ietf.org/html/rfc6750 + +Example +------- + +Usage in code is exactly the same as :ref:`-authenticateBasic-java-`, +with the difference that one must validate the token as OAuth2 dictates (which is currently not part of Akka HTTP itself). diff --git a/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateOAuth2Async.rst b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateOAuth2Async.rst new file mode 100644 index 0000000000..760112e0e9 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateOAuth2Async.rst @@ -0,0 +1,33 @@ +.. _-authenticateOAuth2Async-java-: + +authenticateOAuth2Async +======================= +Wraps the inner route with OAuth Bearer Token authentication support using a given ``AsyncAuthenticator``. + +Description +----------- +Provides support for extracting the so-called "*Bearer Token*" from the :class:`Authorization` HTTP Header, +which is used to initiate an OAuth2 authorization. + +.. warning:: + This directive does not implement the complete OAuth2 protocol, but instead enables implementing it, + by extracting the needed token from the HTTP headers. + +Given a function returning ``Some`` upon successful authentication and ``None`` otherwise, +respectively applies the inner route or rejects the request with a :class:`AuthenticationFailedRejection` rejection, +which by default is mapped to an ``401 Unauthorized`` response. + +See also :ref:`-authenticateOAuth2-java-` if the authorization operation is rather quick, and does not have to execute asynchronously. + +See :ref:`credentials-and-timing-attacks-java` for details about verifying the secret. + +For more information on how OAuth2 works see `RFC 6750`_. + +.. _RFC 6750: https://tools.ietf.org/html/rfc6750 + + +Example +------- + +Usage in code is exactly the same as :ref:`-authenticateBasicAsync-java-`, +with the difference that one must validate the token as OAuth2 dictates (which is currently not part of Akka HTTP itself). diff --git a/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateOAuth2PF.rst b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateOAuth2PF.rst new file mode 100644 index 0000000000..43e56e51ef --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateOAuth2PF.rst @@ -0,0 +1,34 @@ +.. _-authenticateOAuth2PF-java-: + +authenticateOAuth2PF +==================== +Wraps the inner route with OAuth Bearer Token authentication support using a given ``AuthenticatorPF``. + +Description +----------- +Provides support for extracting the so-called "*Bearer Token*" from the :class:`Authorization` HTTP Header, +which is used to initiate an OAuth2 authorization. + +.. warning:: + This directive does not implement the complete OAuth2 protocol, but instead enables implementing it, + by extracting the needed token from the HTTP headers. + +Refer to :ref:`-authenticateOAuth2-java-` for a detailed description of this directive. + +Its semantics are equivalent to ``authenticateOAuth2PF`` 's, where not handling a case in the Partial Function (PF) +leaves the request to be rejected with a :class:`AuthenticationFailedRejection` rejection. + +Longer-running authentication tasks (like looking up credentials in a database) should use the :ref:`-authenticateOAuth2Async-java-` +variant of this directive which allows it to run without blocking routing layer of Akka HTTP, freeing it for other requests. + +See :ref:`credentials-and-timing-attacks-java` for details about verifying the secret. + +For more information on how OAuth2 works see `RFC 6750`_. + +.. _RFC 6750: https://tools.ietf.org/html/rfc6750 + +Example +------- + +Usage in code is exactly the same as :ref:`-authenticateBasicPF-java-`, +with the difference that one must validate the token as OAuth2 dictates (which is currently not part of Akka HTTP itself). diff --git a/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateOAuth2PFAsync.rst b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateOAuth2PFAsync.rst new file mode 100644 index 0000000000..1af2e3136c --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateOAuth2PFAsync.rst @@ -0,0 +1,34 @@ +.. _-authenticateOAuth2PFAsync-java-: + +authenticateOAuth2PFAsync +========================= +Wraps the inner route with OAuth Bearer Token authentication support using a given ``AsyncAuthenticatorPF``. + +Description +----------- +Provides support for extracting the so-called "*Bearer Token*" from the :class:`Authorization` HTTP Header, +which is used to initiate an OAuth2 authorization. + +.. warning:: + This directive does not implement the complete OAuth2 protocol, but instead enables implementing it, + by extracting the needed token from the HTTP headers. + +Refer to :ref:`-authenticateOAuth2-java-` for a detailed description of this directive. + +Its semantics are equivalent to ``authenticateOAuth2PF`` 's, where not handling a case in the Partial Function (PF) +leaves the request to be rejected with a :class:`AuthenticationFailedRejection` rejection. + +See also :ref:`-authenticateOAuth2PF-java-` if the authorization operation is rather quick, and does not have to execute asynchronously. + +See :ref:`credentials-and-timing-attacks-java` for details about verifying the secret. + +For more information on how OAuth2 works see `RFC 6750`_. + +.. _RFC 6750: https://tools.ietf.org/html/rfc6750 + + +Example +------- + +Usage in code is exactly the same as :ref:`-authenticateBasicPFAsync-java-`, +with the difference that one must validate the token as OAuth2 dictates (which is currently not part of Akka HTTP itself). diff --git a/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateOrRejectWithChallenge.rst b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateOrRejectWithChallenge.rst new file mode 100644 index 0000000000..76509bdb2d --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateOrRejectWithChallenge.rst @@ -0,0 +1,19 @@ +.. _-authenticateOrRejectWithChallenge-java-: + +authenticateOrRejectWithChallenge +================================= +Lifts an authenticator function into a directive. + +Description +----------- +This directive allows implementing the low level challange-response type of authentication that some services may require. + +More details about challenge-response authentication are available in the `RFC 2617`_, `RFC 7616`_ and `RFC 7617`_. + +.. _RFC 2617: http://tools.ietf.org/html/rfc2617 +.. _RFC 7616: http://tools.ietf.org/html/rfc7616 +.. _RFC 7617: http://tools.ietf.org/html/rfc7617 + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authorize.rst b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authorize.rst new file mode 100644 index 0000000000..caa435d414 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authorize.rst @@ -0,0 +1,27 @@ +.. _-authorize-java-: + +authorize +========= +Applies the given authorization check to the request. + +Description +----------- +The user-defined authorization check can either be supplied as a ``=> Boolean`` value which is calculated +just from information out of the lexical scope, or as a function ``RequestContext => Boolean`` which can also +take information from the request itself into account. + +If the check returns ``true`` the request is passed on to the inner route unchanged, otherwise an +``AuthorizationFailedRejection`` is created, triggering a ``403 Forbidden`` response by default +(the same as in the case of an ``AuthenticationFailedRejection``). + +In a common use-case you would check if a user (e.g. supplied by any of the ``authenticate*`` family of directives, +e.g. :ref:`-authenticateBasic-java-`) is allowed to access the inner routes, e.g. by checking if the user has the needed permissions. + +See also :ref:`-authorize-java-` for the asynchronous version of this directive. + +.. note:: + See also :ref:`authentication-vs-authorization-java` to understand the differences between those. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authorizeAsync.rst b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authorizeAsync.rst new file mode 100644 index 0000000000..c1920a79d8 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authorizeAsync.rst @@ -0,0 +1,28 @@ +.. _-authorizeAsync-java-: + +authorizeAsync +============== +Applies the given authorization check to the request. + +Description +----------- + +The user-defined authorization check can either be supplied as a ``=> Future[Boolean]`` value which is calculated +just from information out of the lexical scope, or as a function ``RequestContext => Future[Boolean]`` which can also +take information from the request itself into account. + +If the check returns ``true`` or the ``Future`` is failed the request is passed on to the inner route unchanged, +otherwise an ``AuthorizationFailedRejection`` is created, triggering a ``403 Forbidden`` response by default +(the same as in the case of an ``AuthenticationFailedRejection``). + +In a common use-case you would check if a user (e.g. supplied by any of the ``authenticate*`` family of directives, +e.g. :ref:`-authenticateBasic-java-`) is allowed to access the inner routes, e.g. by checking if the user has the needed permissions. + +See also :ref:`-authorize-java-` for the synchronous version of this directive. + +.. note:: + See also :ref:`authentication-vs-authorization-java` to understand the differences between those. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/security-directives/extractCredentials.rst b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/extractCredentials.rst new file mode 100644 index 0000000000..d8c61a5d64 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/extractCredentials.rst @@ -0,0 +1,16 @@ +.. _-extractCredentials-java-: + +extractCredentials +================== + +Description +----------- + +Extracts the potentially present ``HttpCredentials`` provided with the request's ``Authorization`` header, +which can be then used to implement some custom authentication or authorization logic. + +See :ref:`credentials-and-timing-attacks-java` for details about verifying the secret. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/security-directives/index.rst b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/index.rst new file mode 100644 index 0000000000..476e89ebd1 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/index.rst @@ -0,0 +1,84 @@ +.. _SecurityDirectives-java: + +SecurityDirectives +================== + +.. toctree:: + :maxdepth: 1 + + authenticateBasic + authenticateBasicAsync + authenticateBasicPF + authenticateBasicPFAsync + authenticateOrRejectWithChallenge + authenticateOAuth2 + authenticateOAuth2Async + authenticateOAuth2PF + authenticateOAuth2PFAsync + authenticateOrRejectWithChallenge + authorize + authorizeAsync + extractCredentials + + +.. _authentication-vs-authorization-java: + +Authentication vs. Authorization +-------------------------------- + +**Authentication** is the process of establishing a known identity for the user, whereby 'identity' is defined in the +context of the application. This may be done with a username/password combination, a cookie, a pre-defined IP or some +other mechanism. After authentication the system believes that it knows who the user is. + +**Authorization** is the process of determining, whether a given user is allowed access to a given resource or not. In +most cases, in order to be able to authorize a user (i.e. allow access to some part of the system) the users identity +must already have been established, i.e. he/she must have been authenticated. Without prior authentication the +authorization would have to be very crude, e.g. "allow access for *all* users" or "allow access for *noone*". Only after +authentication will it be possible to, e.g., "allow access to the statistics resource for *admins*, but not for regular +*members*". + +Authentication and authorization may happen at the same time, e.g. when everyone who can properly be authenticated is +also allowed access (which is often a very simple and somewhat implicit authorization logic). In other cases the +system might have one mechanism for authentication (e.g. establishing user identity via an LDAP lookup) and another one +for authorization (e.g. a database lookup for retrieving user access rights). + + +Authentication and Authorization in HTTP +---------------------------------------- + +HTTP provides a general framework for access control and authentication, via an extensible set of challenge-response +authentication schemes, which can be used by a server to challenge a client request and by a client to provide +authentication information. The general mechanism is defined in `RFC 7235`_. + +The "HTTP Authentication Scheme Registry" defines the namespace for the authentication schemes in challenges and +credentials. You can see the currently registered schemes at http://www.iana.org/assignments/http-authschemes. + +At this point Akka HTTP only implements the "'Basic' HTTP Authentication Scheme" whose most current specification can be +found here: https://datatracker.ietf.org/doc/draft-ietf-httpauth-basicauth-update/. + +.. _RFC 7235: http://tools.ietf.org/html/rfc7235 + +Low-level OAuth2 "Bearer Token" directives +------------------------------------------ +The OAuth2 directives currently provided in Akka HTTP are not a full OAuth2 protocol implementation, +they are only a means of extracting the so called ``Bearer Token`` from the ``Authorization`` HTTP Header, +as defined in `RFC 6750`_, and allow users to validate and complete the protocol. + +.. _RFC 6750: https://tools.ietf.org/html/rfc6750 + + +.. _credentials-and-timing-attacks-java: + +Credentials and password timing attacks +--------------------------------------- + +When transforming request ``Credentials`` into an application specific user identifier the naive solution for +checking the secret (password) would be a regular string comparison, but doing this would open up the application to +timing attacks. See for example `Timing Attacks Explained`_ for an explanation of the problem. + +.. _Timing Attacks Explained: http://emerose.com/timing-attacks-explained + +To protect users of the library from that mistake the secret is not available through the API, instead the method +``Credentials.Provided.verify(String)`` should be used. It does a constant time comparison rather than returning early +upon finding the first non-equal character. + diff --git a/akka-docs/rst/java/http/routing-dsl/directives/timeout-directives/index.rst b/akka-docs/rst/java/http/routing-dsl/directives/timeout-directives/index.rst new file mode 100644 index 0000000000..fe1d4dfb74 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/timeout-directives/index.rst @@ -0,0 +1,11 @@ +.. _TimeoutDirectives-java: + +TimeoutDirectives +================= + +.. toctree:: + :maxdepth: 1 + + withRequestTimeout + withoutRequestTimeout + withRequestTimeoutResponse diff --git a/akka-docs/rst/java/http/routing-dsl/directives/timeout-directives/withRequestTimeout.rst b/akka-docs/rst/java/http/routing-dsl/directives/timeout-directives/withRequestTimeout.rst new file mode 100644 index 0000000000..43fe7c2376 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/timeout-directives/withRequestTimeout.rst @@ -0,0 +1,40 @@ +.. _-withRequestTimeout-java-: + +withRequestTimeout +================== + +Description +----------- + +This directive enables "late" (during request processing) control over the :ref:`request-timeout-java` 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-java-` 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-java`. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. + +With setting the handler at the same time: + +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/timeout-directives/withRequestTimeoutResponse.rst b/akka-docs/rst/java/http/routing-dsl/directives/timeout-directives/withRequestTimeoutResponse.rst new file mode 100644 index 0000000000..cff7040784 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/timeout-directives/withRequestTimeoutResponse.rst @@ -0,0 +1,26 @@ +.. _-withRequestTimeoutResponse-java-: + +withRequestTimeoutResponse +========================== + +Description +----------- + +Allows customising the ``HttpResponse`` that will be sent to clients in case of a :ref:`request-timeout-java`. + +See also :ref:`-withRequestTimeout-java-` or :ref:`-withoutRequestTimeout-java-` 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-java`. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/timeout-directives/withoutRequestTimeout.rst b/akka-docs/rst/java/http/routing-dsl/directives/timeout-directives/withoutRequestTimeout.rst new file mode 100644 index 0000000000..271489b739 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/timeout-directives/withoutRequestTimeout.rst @@ -0,0 +1,23 @@ +.. _-withoutRequestTimeout-java-: + +withoutRequestTimeout +===================== + +Description +----------- + +This directive enables "late" (during request processing) control over the :ref:`request-timeout-java` 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-java`. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/websocket-directives/handleWebSocketMessages.rst b/akka-docs/rst/java/http/routing-dsl/directives/websocket-directives/handleWebSocketMessages.rst new file mode 100644 index 0000000000..f63b3bb4c8 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/websocket-directives/handleWebSocketMessages.rst @@ -0,0 +1,19 @@ +.. _-handleWebSocketMessages-java-: + +handleWebSocketMessages +======================= + +Description +----------- + +The directive first checks if the request was a valid WebSocket handshake request and if yes, it completes the request +with the passed handler. Otherwise, the request is rejected with an ``ExpectedWebSocketRequestRejection``. + +WebSocket subprotocols offered in the ``Sec-WebSocket-Protocol`` header of the request are ignored. If you want to +support several protocols use the :ref:`-handleWebSocketMessagesForProtocol-java-` directive, instead. + +For more information about the WebSocket support, see :ref:`server-side-websocket-support-java`. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/websocket-directives/handleWebSocketMessagesForProtocol.rst b/akka-docs/rst/java/http/routing-dsl/directives/websocket-directives/handleWebSocketMessagesForProtocol.rst new file mode 100644 index 0000000000..c4f981d96b --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/websocket-directives/handleWebSocketMessagesForProtocol.rst @@ -0,0 +1,23 @@ +.. _-handleWebSocketMessagesForProtocol-java-: + +handleWebSocketMessagesForProtocol +================================== + +Description +----------- +Handles WebSocket requests with the given handler if the given subprotocol is offered in the ``Sec-WebSocket-Protocol`` +header of the request and rejects other requests with an ``ExpectedWebSocketRequestRejection`` or an +``UnsupportedWebSocketSubprotocolRejection``. + +The directive first checks if the request was a valid WebSocket handshake request and if the request offers the passed +subprotocol name. If yes, the directive completes the request with the passed handler. Otherwise, the request is +either rejected with an ``ExpectedWebSocketRequestRejection`` or an ``UnsupportedWebSocketSubprotocolRejection``. + +To support several subprotocols, for example at the same path, several instances of ``handleWebSocketMessagesForProtocol`` can +be chained using ``~`` as you can see in the below example. + +For more information about the WebSocket support, see :ref:`server-side-websocket-support-java`. + +Example +------- +TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. diff --git a/akka-docs/rst/java/http/routing-dsl/directives/websocket-directives/index.rst b/akka-docs/rst/java/http/routing-dsl/directives/websocket-directives/index.rst new file mode 100644 index 0000000000..58b568639a --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/directives/websocket-directives/index.rst @@ -0,0 +1,10 @@ +.. _WebSocketDirectives-java: + +WebSocketDirectives +=================== + +.. toctree:: + :maxdepth: 1 + + handleWebSocketMessages + handleWebSocketMessagesForProtocol diff --git a/akka-docs/rst/java/http/routing-dsl/exception-handling.rst b/akka-docs/rst/java/http/routing-dsl/exception-handling.rst new file mode 100644 index 0000000000..f0d81bb9c9 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/exception-handling.rst @@ -0,0 +1,26 @@ +.. _exception-handling-java: + +Exception Handling +================== + +Exceptions thrown during route execution bubble up through the route structure to the next enclosing +:ref:`-handleExceptions-java-` directive or the top of your route structure. + +Similarly to the way that :ref:`rejections-java` are handled the :ref:`-handleExceptions-java-` directive delegates the actual job +of converting an exception to its argument, an ``ExceptionHandler``. + +An ``ExceptionHandler`` is a partial function, so it can choose which exceptions it would like to handle and +which not. Unhandled exceptions will simply continue to bubble up in the route structure. +At the root of the route tree any still unhandled exception will be dealt with by the top-level handler which always +handles *all* exceptions. + +``Route.seal`` internally wraps its argument route with the :ref:`-handleExceptions-java-` directive in order to "catch" and +handle any exception. + +So, if you'd like to customize the way certain exceptions are handled you need to write a custom ``ExceptionHandler``. +Once you have defined your custom ``ExceptionHandler`` you can supply it as argument to the :ref:`-handleExceptions-java-` directive. +That will apply your handler to the inner route given to that directive. + +Here is an example for wiring up a custom handler via :ref:`-handleExceptions-java-`: + +TODO diff --git a/akka-docs/rst/java/http/routing-dsl/handlers.rst b/akka-docs/rst/java/http/routing-dsl/handlers.rst deleted file mode 100644 index 6201f5951e..0000000000 --- a/akka-docs/rst/java/http/routing-dsl/handlers.rst +++ /dev/null @@ -1,143 +0,0 @@ -.. _handlers-java: - -Handlers -======== - -Handlers implement the actual application-defined logic for a certain trace in the routing tree. Most of the leaves of -the routing tree will be routes created from handlers. Creating a ``Route`` from a handler is achieved using the -``BasicDirectives.handleWith`` overloads. They come in several forms: - -* with a single ``Handler`` argument and a variable number of ``RequestVal`` (may be 0) -* with a number ``n`` of ``RequestVal`` arguments and a ``HandlerN`` argument -* with a ``Class`` and/or instance and a method name String argument and a variable number of ``RequestVal`` (may be 0) - arguments - -Simple Handler --------------- - -In its simplest form a ``Handler`` is a SAM class that defines application behavior -by inspecting the ``RequestContext`` and returning a ``RouteResult``: - -.. includecode:: /../../akka-http/src/main/scala/akka/http/javadsl/server/Handler.scala - :include: handler - -Such a handler inspects the ``RequestContext`` it receives and uses the ``RequestContext``'s methods to -create a response: - -.. includecode:: /../../akka-http-tests/src/test/java/docs/http/javadsl/server/HandlerExampleDocTest.java - :include: simple-handler - -The handler can include any kind of logic but must return a ``RouteResult`` in the end which can only -be created by using one of the ``RequestContext`` methods. - -A handler instance can be used once or several times as shown in the full example: - -.. includecode:: /../../akka-http-tests/src/test/java/docs/http/javadsl/server/HandlerExampleDocTest.java - :include: simple-handler-example-full - -Handlers and Request Values ---------------------------- - -In many cases, instead of manually inspecting the request, a handler will make use of :ref:`request-vals-java` -to extract details from the request. This is possible using one of the other ``handleWith`` overloads that bind -the values of one or more request values with a ``HandlerN`` instance to produce a ``Route``: - -.. includecode:: /../../akka-http-tests/src/test/java/docs/http/javadsl/server/HandlerExampleDocTest.java - :include: handler2 - -The handler here implements multiplication of two integers. However, it doesn't need to specify where these -parameters come from. In ``handleWith``, as many request values of the matching type have to be specified as the -handler needs. This can be seen in the full example: - -.. includecode:: /../../akka-http-tests/src/test/java/docs/http/javadsl/server/HandlerExampleDocTest.java - :include: handler2-example-full - -Here, the handler is again being reused. First, in creating a route that expects URI parameters ``x`` and ``y``. This -route is then used in the route structure. And second, the handler is used with another set of ``RequestVal`` in the -route structure, this time representing segments from the URI path. - -Handlers in Java 8 ------------------- - -Handlers are in fact simply classes which extend ``akka.japi.function.FunctionN`` in order to make reasoning -about the number of handled arguments easier. For example, a :class:`Handler1[String]` is simply a -``Function2[RequestContext, String, RouteResult]``. You can think of handlers as hot-dogs, where each ``T`` -type represents a sausage, put between the "buns" which are ``RequestContext`` and ``RouteResult``. - -In Java 8 handlers can be provided as function literals or method references. The previous example can then be written -like this: - -.. includecode:: /../../akka-http-tests/src/test/java/docs/http/javadsl/server/HandlerExampleDocTest.java - :include: handler2-java8-example-full - - -.. note:: - The reason the ``handleWith##`` methods include the number of handled values is because otherwise (if overloading would - be used, for all 22 methods) error messages generated by ``javac`` end up being very long and not readable, i.e. - if one type of a handler does not match the given values, *all* possible candidates would be printed in the error message - (22 of them), instead of just the one arity-matching method, pointing out that the type does not match. - - We opted for better error messages as we feel this is more helpful when developing applications, - instead of having one overloaded method which looks nice when everything works, but procudes hard to read error - messages if something does not match up. - -Providing Handlers by Reflection --------------------------------- - -Using Java before Java 8, writing out handlers as (anonymous) classes can be unwieldy. Therefore, ``handleReflectively`` -overloads are provided that allow writing handler as simple methods and specifying them by name: - -.. includecode:: /../../akka-http-tests/src/test/java/docs/http/javadsl/server/HandlerExampleDocTest.java - :include: reflective - -The complete calculator example can then be written like this: - -.. includecode:: /../../akka-http-tests/src/test/java/docs/http/javadsl/server/HandlerExampleDocTest.java - :include: reflective-example-full - -There are alternative overloads for ``handleReflectively`` that take a ``Class`` instead of an object instance to refer to -static methods. The referenced method must be publicly accessible. - -Deferring Result Creation -------------------------- - -Sometimes a handler cannot directly complete the request but needs to do some processing asynchronously. In this case -the completion of a request needs to be deferred until the result has been generated. This is supported by the routing -DSL in two ways: either you can use one of the ``handleWithAsyncN`` methods passing an ``AsyncHandlerN`` which -returns a ``CompletionStage``, i.e. an eventual ``RouteResult``, or you can also use a regular handler as shown -above and use ``RequestContext.completeWith`` for completion which takes an ``CompletionStage`` as an argument. - -This is demonstrated in the following example. Consider a asynchronous service defined like this -(making use of Java 8 lambdas): - -.. includecode:: /../../akka-http-tests/src/test/java/docs/http/javadsl/server/HandlerExampleDocTest.java - :include: async-service-definition - -Here the calculator runs the actual calculation in the background and only eventually returns the result. The HTTP -service should provide a front-end to that service without having to block while waiting for the results. As explained -above this can be done in two ways. - -First, you can use ``handleWithAsyncN`` to be able to return a ``CompletionStage``: - -.. includecode:: /../../akka-http-tests/src/test/java/docs/http/javadsl/server/HandlerExampleDocTest.java - :include: async-handler-1 - -The handler invokes the service and then maps the calculation result to a ``RouteResult`` using ``CompletionStage.thenApplyAsync`` and -returns the resulting ``CompletionStage``. Note that you should always explicitly provide an executor that designates -where the future transformation task is executed, using the JDK’s global ForkJoinPool is not recommended. - -Otherwise, you can also still use ``handleWithN`` and use ``RequestContext.completeWith`` to "convert" a -``CompletionStage`` into a ``RouteResult`` as shown here: - -.. includecode:: /../../akka-http-tests/src/test/java/docs/http/javadsl/server/HandlerExampleDocTest.java - :include: async-handler-2 - -Using this style, you can decide in your handler if you want to return a direct synchronous result or if you need -to defer completion. - -Both alternatives will not block and show the same runtime behavior. - -Here's the complete example: - -.. includecode:: /../../akka-http-tests/src/test/java/docs/http/javadsl/server/HandlerExampleDocTest.java - :include: async-example-full diff --git a/akka-docs/rst/java/http/routing-dsl/index.rst b/akka-docs/rst/java/http/routing-dsl/index.rst index 311915951b..d5e754af0b 100644 --- a/akka-docs/rst/java/http/routing-dsl/index.rst +++ b/akka-docs/rst/java/http/routing-dsl/index.rst @@ -3,6 +3,11 @@ High-level Server-Side API ========================== +In addition to the :ref:`http-low-level-server-side-api-java` Akka HTTP provides a very flexible "Routing DSL" for elegantly +defining RESTful web services. It picks up where the low-level API leaves off and offers much of the higher-level +functionality of typical web servers or frameworks, like deconstruction of URIs, content negotiation or +static content serving. + To use the high-level API you need to add a dependency to the ``akka-http-experimental`` module. .. toctree:: @@ -11,8 +16,57 @@ To use the high-level API you need to add a dependency to the ``akka-http-experi overview routes directives/index - request-vals/index - handlers marshalling + exception-handling + rejections testkit - json-support \ No newline at end of file + +Handling HTTP Server failures in the High-Level API +--------------------------------------------------- +There are various situations when failure may occur while initialising or running an Akka HTTP server. +Akka by default will log all these failures, however sometimes one may want to react to failures in addition +to them just being logged, for example by shutting down the actor system, or notifying some external monitoring +end-point explicitly. + +Bind failures +^^^^^^^^^^^^^ +For example the server might be unable to bind to the given port. For example when the port +is already taken by another application, or if the port is privileged (i.e. only usable by ``root``). +In this case the "binding future" will fail immediatly, and we can react to if by listening on the CompletionStage's completion: + +.. includecode:: ../../code/docs/http/javadsl/server/HighLevelServerBindFailureExample.java + :include: binding-failure-high-level-example + + +.. note:: + For a more low-level overview of the kinds of failures that can happen and also more fine-grained control over them + refer to the :ref:`handling-http-server-failures-low-level-java` documentation. + +Failures and exceptions inside the Routing DSL +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Exception handling within the Routing DSL is done by providing :class:`ExceptionHandler` s which are documented in-depth +in the :ref:`exception-handling-java` section of the documtnation. You can use them to transform exceptions into +:class:`HttpResponse` s with apropriate error codes and human-readable failure descriptions. + +File uploads +^^^^^^^^^^^^ +TODO not possible in Java DSL since there + +For high level directives to handle uploads see the :ref:`FileUploadDirectives-java`. + +Handling a simple file upload from for example a browser form with a `file` input can be done +by accepting a `Multipart.FormData` entity, note that the body parts are `Source` rather than +all available right away, and so is the individual body part payload so you will need to consume +those streams both for the file and for the form fields. + +Here is a simple example which just dumps the uploaded file into a temporary file on disk, collects +some form fields and saves an entry to a fictive database: + +TODO missing example 1 + +You can transform the uploaded files as they arrive rather than storing then in a temporary file as +in the previous example. In this example we accept any number of ``.csv`` files, parse those into lines +and split each line before we send it to an actor for further processing: + +TODO missing example 2 diff --git a/akka-docs/rst/java/http/routing-dsl/marshalling.rst b/akka-docs/rst/java/http/routing-dsl/marshalling.rst index a12a1cfc7c..899fc11abb 100644 --- a/akka-docs/rst/java/http/routing-dsl/marshalling.rst +++ b/akka-docs/rst/java/http/routing-dsl/marshalling.rst @@ -17,27 +17,27 @@ On the server-side marshalling is used to convert a application-domain object to contain an ``Accept`` header that lists acceptable content types for the client. A marshaller contains the logic to negotiate the result content types based on the ``Accept`` and the ``AcceptCharset`` headers. -Marshallers can be specified when completing a request with ``RequestContext.completeAs`` or by using the ``BasicDirectives.completeAs`` -directives. +Marshallers can be specified when completing a request with ``RequestContext.complete`` or by using one of the +``RouteDirectives.complete`` directives. These marshallers are provided by akka-http: * Use :ref:`json-jackson-support-java` to create an marshaller that can convert a POJO to an ``application/json`` response using jackson_. - * Use ``Marshallers.toEntityString``, ``Marshallers.toEntityBytes``, ``Marshallers.toEntityByteString``, - ``Marshallers.toEntity``, and ``Marshallers.toResponse`` to create custom marshallers. + * Use ``Marshaller.stringToEntity``, ``Marshaller.byteArrayToEntity``, ``Marshaller.byteStringToEntity``, + combined with ``Marshaller.entityToResponse`` to create custom marshallers. Unmarshalling ------------- -On the server-side unmarshalling is used to convert a request (entity) to a application-domain object. This means -unmarshalling to a certain type is represented by a ``RequestVal``. Currently, several options are provided to create -an unmarshalling ``RequestVal``: +On the server-side unmarshalling is used to convert a request (entity) to a application-domain object. This is done +in the ``MarshallingDirectives.request`` or ``MarshallingDirectives.entity`` directive. There are several unmarshallers +provided by akka-http: * Use :ref:`json-jackson-support-java` to create an unmarshaller that can convert an ``application/json`` request to a POJO using jackson_. - * Use the predefined ``Unmarshallers.String``, ``Unmarshallers.ByteString``, ``Unmarshallers.ByteArray``, - ``Unmarshallers.CharArray`` to convert to those basic types. - * Use ``Unmarshallers.fromMessage`` or ``Unmarshaller.fromEntity`` to create a custom unmarshaller. + * Use the predefined ``Unmarshaller.entityToString``, ``Unmarshaller.entityToByteString``, ``Unmarshaller.entityToByteArray``, + ``Unmarshaller.entityToCharArray`` to convert to those basic types. + * Use ``Unmarshaller.sync`` or ``Unmarshaller.async`` to create a custom unmarshaller. .. _jackson: https://github.com/FasterXML/jackson \ No newline at end of file diff --git a/akka-docs/rst/java/http/routing-dsl/overview.rst b/akka-docs/rst/java/http/routing-dsl/overview.rst index f02af76681..727b61df91 100644 --- a/akka-docs/rst/java/http/routing-dsl/overview.rst +++ b/akka-docs/rst/java/http/routing-dsl/overview.rst @@ -24,77 +24,18 @@ Here's the complete example rewritten using the composable high-level API: .. includecode:: ../../code/docs/http/javadsl/server/HighLevelServerExample.java :include: high-level-server-example -Heart of the high-level architecture is the route tree. It is a big expression of type ``Route`` -that is evaluated only once during startup time of your service. It completely describes how your service -should react to any request. +The core of the Routing DSL becomes available with a single import:: -The type ``Route`` is the basic building block of the route tree. It defines if and a how a request should -be handled. Routes are composed to form the route tree in the following two ways. + import akka.http.javadsl.server.Directives.*; -A route can be wrapped by a "Directive" which adds some behavioral aspect to its wrapped "inner route". ``path("ping")`` is such -a directive that implements a path filter, i.e. it only passes control to its inner route when the unmatched path -matches ``"ping"``. Directives can be more versatile than this: A directive can also transform the request before -passing it into its inner route or transform a response that comes out of its inner route. It's a general and powerful -abstraction that allows to package any kind of HTTP processing into well-defined blocks that can be freely combined. -akka-http defines a library of predefined directives and routes for all the various aspects of dealing with -HTTP requests and responses. +Or by extending the ``akka.http.javadsl.server.AllDirectives`` class which brings together all directives into a single class +for easier access:: -Read more about :ref:`directives-java`. + extends AllDirectives -The other way of composition is defining a list of ``Route`` alternatives. Alternative routes are tried one after -the other until one route "accepts" the request and provides a response. Otherwise, a route can also "reject" a request, -in which case further alternatives are explored. Alternatives are specified by passing a list of routes either -to ``Directive.route()`` as in ``pathSingleSlash().route()`` or to directives that directly take a variable number -of inner routes as argument like ``get()`` here. - -Read more about :ref:`routes-java`. - -Another important building block is a ``RequestVal``. It represents a value that can be extracted from a -request (like the URI parameter ``Parameters.stringValue("name")`` in the example) and which is then interpreted -as a value of type ``T``. Examples of HTTP aspects represented by a ``RequestVal`` are URI parameters, HTTP form -fields, details of the request like headers, URI, the entity, or authentication data. - -Read more about :ref:`request-vals-java`. - -The actual application-defined processing of a request is defined with a ``Handler`` instance or by specifying -a handling method with reflection. A handler can receive the value of any request values and is converted into -a ``Route`` by using one of the ``BasicDirectives.handleWith`` directives. - -Read more about :ref:`handlers-java`. - -Requests or responses often contain data that needs to be interpreted or rendered in some way. -Akka-http provides the abstraction of ``Marshaller`` and ``Unmarshaller`` that define how domain model objects map -to HTTP entities. - -Read more about :ref:`marshalling-java`. - -akka-http contains a testkit that simplifies testing routes. It allows to run test-requests against (sub-)routes -quickly without running them over the network and helps with writing assertions on HTTP response properties. - -Read more about :ref:`http-testkit-java`. +Of course it is possible to directly import only the directives you need (i.e. ``WebSocketDirectives`` etc). .. _DRY: http://en.wikipedia.org/wiki/Don%27t_repeat_yourself .. _handling-http-server-failures-high-level-java: -Handling HTTP Server failures in the High-Level API ---------------------------------------------------- -There are various situations when failure may occur while initialising or running an Akka HTTP server. -Akka by default will log all these failures, however sometimes one may want to react to failures in addition -to them just being logged, for example by shutting down the actor system, or notifying some external monitoring -end-point explicitly. - -Bind failures -^^^^^^^^^^^^^ -For example the server might be unable to bind to the given port. For example when the port -is already taken by another application, or if the port is privileged (i.e. only usable by ``root``). -In this case the "binding future" will fail immediatly, and we can react to if by listening on the CompletionStage's completion: - -.. includecode:: ../../code/docs/http/javadsl/server/HighLevelServerBindFailureExample.java - :include: binding-failure-high-level-example - - -.. note:: - For a more low-level overview of the kinds of failures that can happen and also more fine-grained control over them - refer to the :ref:`handling-http-server-failures-low-level-java` documentation. - diff --git a/akka-docs/rst/java/http/routing-dsl/rejections.rst b/akka-docs/rst/java/http/routing-dsl/rejections.rst new file mode 100644 index 0000000000..5edc92c826 --- /dev/null +++ b/akka-docs/rst/java/http/routing-dsl/rejections.rst @@ -0,0 +1,137 @@ +.. _rejections-java: + +Rejections +========== +TODO update to Java APIs + +In the chapter about constructing :ref:`Routes` the ``~`` operator was introduced, which connects two routes in a way +that allows a second route to get a go at a request if the first route "rejected" it. The concept of "rejections" is +used by Akka HTTP for maintaining a more functional overall architecture and in order to be able to properly +handle all kinds of error scenarios. + +When a filtering directive, like the :ref:`-get-` directive, cannot let the request pass through to its inner route because +the filter condition is not satisfied (e.g. because the incoming request is not a GET request) the directive doesn't +immediately complete the request with an error response. Doing so would make it impossible for other routes chained in +after the failing filter to get a chance to handle the request. +Rather, failing filters "reject" the request in the same way as by explicitly calling ``requestContext.reject(...)``. + +After having been rejected by a route the request will continue to flow through the routing structure and possibly find +another route that can complete it. If there are more rejections all of them will be picked up and collected. + +If the request cannot be completed by (a branch of) the route structure an enclosing :ref:`-handleRejections-java-` directive +can be used to convert a set of rejections into an ``HttpResponse`` (which, in most cases, will be an error response). +``Route.seal`` internally wraps its argument route with the :ref:`-handleRejections-java-` directive in order to "catch" +and handle any rejection. + + +Predefined Rejections +--------------------- + +A rejection encapsulates a specific reason why a route was not able to handle a request. It is modeled as an object of +type ``Rejection``. Akka HTTP comes with a set of `predefined rejections`__, which are used by the many +:ref:`predefined directives `. + +Rejections are gathered up over the course of a Route evaluation and finally converted to ``HttpResponse`` replies by +the :ref:`-handleRejections-` directive if there was no way for the request to be completed. + +__ @github@/akka-http/src/main/scala/akka/http/scaladsl/server/Rejection.scala + + +.. _The RejectionHandler-java: + +The RejectionHandler +-------------------- + +The :ref:`-handleRejections-` directive delegates the actual job of converting a list of rejections to its argument, a +RejectionHandler__, which is defined like this:: + + trait RejectionHandler extends (immutable.Seq[Rejection] ⇒ Option[Route]) + +__ @github@/akka-http/src/main/scala/akka/http/scaladsl/server/RejectionHandler.scala + +Since a ``RejectionHandler`` returns an ``Option[Route]`` it can choose whether it would like to handle the current set +of rejections or not. If it returns ``None`` the rejections will simply continue to flow through the route structure. + +The default ``RejectionHandler`` applied by the top-level glue code that turns a ``Route`` into a +``Flow`` or async handler function for the :ref:`low-level API ` (via +``Route.handlerFlow`` or ``Route.asyncHandler``) will handle *all* rejections that reach it. + + +Rejection Cancellation +---------------------- + +As you can see from its definition above the ``RejectionHandler`` doesn't handle single rejections but a whole list of +them. This is because some route structure produce several "reasons" why a request could not be handled. + +Take this route structure for example: + +TODO missing sample + +For uncompressed POST requests this route structure would initially yield two rejections: + +- a ``MethodRejection`` produced by the :ref:`-get-` directive (which rejected because the request is not a GET request) +- an ``UnsupportedRequestEncodingRejection`` produced by the :ref:`-decodeRequestWith-` directive (which only accepts + gzip-compressed requests here) + +In reality the route even generates one more rejection, a ``TransformationRejection`` produced by the :ref:`-post-` +directive. It "cancels" all other potentially existing *MethodRejections*, since they are invalid after the +:ref:`-post-` directive allowed the request to pass (after all, the route structure *can* deal with POST requests). +These types of rejection cancellations are resolved *before* a ``RejectionHandler`` sees the rejection list. +So, for the example above the ``RejectionHandler`` will be presented with only a single-element rejection list, +containing nothing but the ``UnsupportedRequestEncodingRejection``. + + +.. _Empty Rejections-java: + +Empty Rejections +---------------- + +Since rejections are passed around in a list (or rather immutable ``Seq``) you might ask yourself what the semantics of +an empty rejection list are. In fact, empty rejection lists have well defined semantics. They signal that a request was +not handled because the respective resource could not be found. Akka HTTP reserves the special status of "empty +rejection" to this most common failure a service is likely to produce. + +So, for example, if the :ref:`-path-` directive rejects a request it does so with an empty rejection list. The +:ref:`-host-` directive behaves in the same way. + + +Customizing Rejection Handling +------------------------------ + +If you'd like to customize the way certain rejections are handled you'll have to write a custom +:ref:`RejectionHandler `. Here is an example: + +TODO missing sample + +The easiest way to construct a ``RejectionHandler`` is via the ``RejectionHandler.Builder`` that Akka HTTP provides. +After having created a new ``Builder`` instance with ``RejectionHandler.newBuilder()`` +you can attach handling logic for certain types of rejections through three helper methods: + +handle + Handles certain rejections with the given partial function. The partial function simply produces a ``Route`` which is + run when the rejection is "caught". This makes the full power of the Routing DSL available for defining rejection + handlers and even allows for recursing back into the main route structure if required. + +handleAll[T <: Rejection] + Handles all rejections of a certain type at the same time. This is useful for cases where your need access to more + than the first rejection of a certain type, e.g. for producing the error message to an unsupported request method. + +handleNotFound + As described :ref:`above ` "Resource Not Found" is special as it is represented with an empty + rejection set. The ``handleNotFound`` helper let's you specify the "recovery route" for this case. + +Even though you could handle several different rejection types in a single partial function supplied to ``handle`` +it is recommended to split these up into distinct ``handle`` attachments instead. +This way the priority between rejections is properly defined via the order of your ``handle`` clauses rather than the +(sometimes hard to predict or control) order of rejections in the rejection set. + +Once you have defined your custom ``RejectionHandler`` you have two options for "activating" it: + +1. Bring it into implicit scope at the top-level. +2. Supply it as argument to the :ref:`-handleRejections-` directive. + +In the first case your handler will be "sealed" (which means that it will receive the default handler as a fallback for +all cases your handler doesn't handle itself) and used for all rejections that are not handled within the route structure +itself. + +The second case allows you to restrict the applicability of your handler to certain branches of your route structure. diff --git a/akka-docs/rst/java/http/routing-dsl/request-vals/form-field-request-vals.rst b/akka-docs/rst/java/http/routing-dsl/request-vals/form-field-request-vals.rst deleted file mode 100644 index 0834fe4c92..0000000000 --- a/akka-docs/rst/java/http/routing-dsl/request-vals/form-field-request-vals.rst +++ /dev/null @@ -1,26 +0,0 @@ -.. _form-field-request-vals-java: - -Request Values: FormFields -========================== - -A collection of pre-defined :ref:`request-vals-java` that can be used to extract header values from incoming requests. - -Description ------------ -``FormField`` request values allow extracting fields submitted as ``application/x-www-form-urlencoded`` values or concrete instances from HTTP requests. - -The ``FormField`` request value builder is made up of 2 steps, initially you need to pick which what type of value you -want to extract from the field (for example ``intValue``, which would reject the route if the value is not an ``int``), -and then **optionally** you may specify if the value is optional (by calling ``optional()`` on the ``RequestVal``) -or has a default value (by calling ``withDefault()`` on the ``RequestVal``). - -Examples --------- - -Extracting form fields of a certain primitive type from a request: - -.. includecode:: ../../../code/docs/http/javadsl/server/FormFieldRequestValsExampleTest.java#simple - -Extracting values of custom type from a request by providing a conversion function: - -.. includecode:: ../../../code/docs/http/javadsl/server/FormFieldRequestValsExampleTest.java#custom-unmarshal diff --git a/akka-docs/rst/java/http/routing-dsl/request-vals/header-request-vals.rst b/akka-docs/rst/java/http/routing-dsl/request-vals/header-request-vals.rst deleted file mode 100644 index ced5d6d648..0000000000 --- a/akka-docs/rst/java/http/routing-dsl/request-vals/header-request-vals.rst +++ /dev/null @@ -1,33 +0,0 @@ -.. _header-request-vals-java: - -Request Values: Headers -======================= - -A collection of pre-defined :ref:`request-vals-java` that can be used to extract header values from incoming requests. - -Description ------------ -Header request values allow extracting ``HttpHeader`` values or concrete instances from HTTP requests. - -The ``RequestVal`` builder is made up of 2 steps, initially you need to pick which Header to extract (``byName`` or -``byClass``) and then you need to pick if the header is optionally available or required (i.e. the route should not -match if the header is not present in the request). This is done using one of the below depicted methods:: - - RequestVal instance() - RequestVal<> optionalInstance() - - RequestVal value() - RequestVal> optionalValue() - -Examples --------- - -Extracting a header by using a specific ``Header`` class (which are pre-defined in ``akka.http.javadsl.model.headers.*``): - -.. includecode:: ../../../code/docs/http/javadsl/server/HeaderRequestValsExampleTest.java - :include: by-class - -Extracting arbitrary headers by their name, for example custom headers (usually starting with ``X-...``): - -.. includecode:: ../../../code/docs/http/javadsl/server/HeaderRequestValsExampleTest.java - :include: by-name diff --git a/akka-docs/rst/java/http/routing-dsl/request-vals/http-basic-authenticator.rst b/akka-docs/rst/java/http/routing-dsl/request-vals/http-basic-authenticator.rst deleted file mode 100644 index bd817be74b..0000000000 --- a/akka-docs/rst/java/http/routing-dsl/request-vals/http-basic-authenticator.rst +++ /dev/null @@ -1,38 +0,0 @@ -.. _http-basic-authenticator-java: - -Request Values: Http Basic Auth -=============================== - -An abstract class to implement HTTP basic authentication - -Description ------------ -Http basic auth allows for protection of one or more routes with a username and password. - -To use it you subclass ``HttpBasicAuthenticator`` and provide your authentication logic. -There are two factory methods to create the authentication results to return from the authentication logic: -``authenticateAs(T)`` and ``refuseAccess()``. If the authentication is not very quick in memory, for example -calls a database, make sure you do not block the web server thread by executing that in a separate ``CompletionStage`` -and then ``flatMap`` the result into the authentication result. - -When you use the authenticator in your routes you must reference the concrete authenticator twice, -first as a directive wrapping all the routes it should be required for, and then as a request -value to extract the user object for use inside the logic of the handler. - -Note that to protect developers from opening up for a timing attack on the password it is not available -directly, instead a constant time string comparison is provided. For more information about timing attacks -on passwords see for example `Timing Attacks Explained`_ . - -.. _Timing Attacks Explained: http://emerose.com/timing-attacks-explained - - -Example -------- - -Authenticating or refusing access to a user based on a hardcoded password and using a ``String`` with the -username as internal representation of a user (in a real application it would probably be an instance of -a richer class describing an authenticated user). - - -.. includecode:: ../../../code/docs/http/javadsl/server/HttpBasicAuthenticatorExample.java - :include: basic-authenticator-java diff --git a/akka-docs/rst/java/http/routing-dsl/request-vals/index.rst b/akka-docs/rst/java/http/routing-dsl/request-vals/index.rst deleted file mode 100644 index 3e34dcd39c..0000000000 --- a/akka-docs/rst/java/http/routing-dsl/request-vals/index.rst +++ /dev/null @@ -1,60 +0,0 @@ -.. _request-vals-java: - -Request values -============== - -A request value of type ``RequestVal`` is a typed structure that represents some aspect of the request -that can be interpreted as a value of type ``T``. A ``RequestVal`` instance abstracts the knowledge about how -to extract a certain value from the request and interpret it as a ``T``. It is used in combination with -:ref:`handlers-java`. - -The advantage of representing a request detail as a ``RequestVal`` instead of performing ad-hoc analysis of -a request are: - - * you can define an "inventory" of HTTP primitives for your application that you can reuse in many places of your - application - * automatic handling of errors when an expected value was not found in a request or if it could not be interpreted - as the expected Java type - -Note, that the Scala version of the routing DSL has no direct correspondent to RequestVals. Instead, -a Scala-side ``Directive`` can have "extractions" that are reflected in the type of the ``Directive``. - -Predefined Request values -------------------------- - -akka-http provides a set of predefined request values for request data commonly accessed in a web -service. - -These request values are defined in the following objects: - -:ref:`akka.http.javadsl.server.values.FormFields ` - Contains request values for basic data like URI components, request method, peer address, or the entity data. -akka.http.javadsl.server.values.FormFieldsCookies - Contains request values representing cookies. -akka.http.javadsl.server.values.FormFields - Contains request values to access form fields unmarshalled to various primitive Java types. -:ref:`akka.http.javadsl.server.values.Headers ` - Contains request values to access request headers or header values. -akka.http.javadsl.server.values.FormFieldsHttpBasicAuthenticator - An abstract class to implement to create a request value representing a HTTP basic authenticated principal. -akka.http.javadsl.server.values.FormFieldsParameters - Contains request values to access URI paramaters unmarshalled to various primitive Java types. -akka.http.javadsl.server.values.FormFieldsPathMatchers - Contains request values to match and access URI path segments. -akka.http.javadsl.server.values.FormFieldsCustomRequestVal - An abstract class to implement arbitrary custom request values. -:ref:`akka.http.javadsl.server.values.HttpBasicAuthenticator.scala ` - An abstract class to implement HTTP basic authentication -:ref:`akka.http.javadsl.server.values.OAuth2Authenticator ` - An abstract class to implement Oauth 2 bearer token authentication - -See also --------- - -.. toctree:: - :maxdepth: 1 - - form-field-request-vals - header-request-vals - http-basic-authenticator - oauth2-authenticator diff --git a/akka-docs/rst/java/http/routing-dsl/request-vals/oauth2-authenticator.rst b/akka-docs/rst/java/http/routing-dsl/request-vals/oauth2-authenticator.rst deleted file mode 100644 index 6d7f071ee8..0000000000 --- a/akka-docs/rst/java/http/routing-dsl/request-vals/oauth2-authenticator.rst +++ /dev/null @@ -1,47 +0,0 @@ -.. _oauth2-authenticator-java: - -Request Values: OAuth 2 Bearer Token Authentication -=================================================== - -An abstract class to implement Oauth 2 bearer token authentication - -Description ------------ -Allows to protect one of more routes with authentication in the form of a OAuth2 Bearer Token. For more information -about OAuth 2 Bearer Token see `RFC6750`_. - -.. _RFC6750: https://tools.ietf.org/html/rfc6750 - -To use it you subclass ``OAuth2Authenticator`` and implement the ``authenticate`` method -to provide your own logic which verifies the OAuth2 credentials. When verification is done -the request can either be refused by returning the return value of ``refuseAccess()`` or completed -with an object that is application specific by returning the return value of ``authenticateAs(T)``. - -If the authentication is not very quick in memory, for example calls a separate authentication server -to verify the token, make sure you do not block the web server thread by executing that in a separate ``CompletionStage`` -and then ``flatMap`` the result into the authentication result. - -.. note:: OAuth2 Bearer Token sends the token as clear text and should ONLY EVER be used over - SSL/TLS - -When you use the OAuth2 authenticator in your routes you must reference the concrete authenticator twice, -first as a directive wrapping all the routes it should be required for, and then as a request -value to extract the user object for use inside the logic of the handler. - -Note that to protect developers from opening up for a timing attack on the token it is not available -directly, instead a constant time string comparison is provided. For more information about timing attacks -on passwords see for example `Timing Attacks Explained`_ . - -.. _Timing Attacks Explained: http://emerose.com/timing-attacks-explained - - -Example -------- - -Authenticating or refusing access to a user based on a hardcoded token and using a ``String`` with the -identity as internal representation of a user (in a real application it would probably be an instance of -a richer class describing an authenticated user). - - -.. includecode:: ../../../code/docs/http/javadsl/server/OAuth2AuthenticatorExample.java - :include: oauth2-authenticator-java diff --git a/akka-docs/rst/java/http/routing-dsl/routes.rst b/akka-docs/rst/java/http/routing-dsl/routes.rst index 46b536e1c3..7975242d7c 100644 --- a/akka-docs/rst/java/http/routing-dsl/routes.rst +++ b/akka-docs/rst/java/http/routing-dsl/routes.rst @@ -3,21 +3,45 @@ Routes ====== +The "Route" is the central concept of Akka HTTP's Routing DSL. All the structures you build with the DSL, no matter +whether they consists of a single line or span several hundred lines, are functions turning a ``RequestContext`` into +a ``CompletionStage``. + A ``Route`` itself is a function that operates on a ``RequestContext`` and returns a ``RouteResult``. The ``RequestContext`` is a data structure that contains the current request and auxiliary data like the so far unmatched path of the request URI that gets passed through the route structure. It also contains the current ``ExecutionContext`` and ``akka.stream.Materializer``, so that these don't have to be passed around manually. +Generally when a route receives a request (or rather a ``RequestContext`` for it) it can do one of these things: + +- Complete the request by returning the value of ``requestContext.complete(...)`` +- Reject the request by returning the value of ``requestContext.reject(...)`` (see :ref:`rejections-java`) +- Fail the request by returning the value of ``requestContext.fail(...)`` or by just throwing an exception (see :ref:`exception-handling-java`) +- Do any kind of asynchronous processing and instantly return a ``Future[RouteResult]`` to be eventually completed later + +The first case is pretty clear, by calling ``complete`` a given response is sent to the client as reaction to the +request. In the second case "reject" means that the route does not want to handle the request. You'll see further down +in the section about route composition what this is good for. + +A ``Route`` can be "sealed" using ``Route.seal``, which relies on the in-scope ``RejectionHandler`` and ``ExceptionHandler`` +instances to convert rejections and exceptions into appropriate HTTP responses for the client. + +Using ``Route.handlerFlow`` or ``Route.asyncHandler`` a ``Route`` can be lifted into a handler ``Flow`` or async handler +function to be used with a ``bindAndHandleXXX`` call from the :ref:`http-low-level-server-side-api`. + .. _request-context-java: RequestContext -------------- -The ``RequestContext`` achieves two goals: it allows access to request data and it is a factory for creating a -``RouteResult``. A user-defined handler (see :ref:`handlers-java`) that is usually used at the leaf position of -the route tree receives a ``RequestContext``, evaluates its content and then returns a result generated by one of -the methods of the context. +The request context wraps an ``HttpRequest`` instance to enrich it with additional information that are typically +required by the routing logic, like an ``ExecutionContext``, ``Materializer``, ``LoggingAdapter`` and the configured +``RoutingSettings``. It also contains the ``unmatchedPath``, a value that describes how much of the request URI has not +yet been matched by a :ref:`Path Directive `. + +The ``RequestContext`` itself is immutable but contains several helper methods which allow for convenient creation of +modified copies. .. _route-result-java: @@ -26,8 +50,8 @@ RouteResult The ``RouteResult`` is an opaque structure that represents possible results of evaluating a route. A ``RouteResult`` can only be created by using one of the methods of the ``RequestContext``. A result can either be a response, if -it was generated by one of the ``completeX`` methods, it can be an eventual result, i.e. a ``CompletionStage + directiveB(route(() -> + directiveC( ... // route 1 ), - d.route( + directiveD( ... // route 2 ), ... // route 3 - ), - e.route( + )), + directiveE( ... // route 4 ) - ) + )); Here five directives form a routing tree. diff --git a/akka-docs/rst/java/http/server-side/low-level-server-side-api.rst b/akka-docs/rst/java/http/server-side/low-level-server-side-api.rst index 71abf81f66..ff751fca87 100644 --- a/akka-docs/rst/java/http/server-side/low-level-server-side-api.rst +++ b/akka-docs/rst/java/http/server-side/low-level-server-side-api.rst @@ -134,25 +134,6 @@ Connection will also be closed if request entity has been cancelled (e.g. by att or consumed only partially (e.g. by using ``take`` combinator). In order to prevent this behaviour entity should be explicitly drained by attaching it to ``Sink.ignore()``. - -.. _serverSideHTTPS-java: - -Server-Side HTTPS Support -------------------------- - -Akka HTTP supports TLS encryption on the server-side as well as on the :ref:`client-side `. - -The central vehicle for configuring encryption is the ``HttpsConnectionContext``, which can be created using -the static method ``ConnectionContext.https`` which is defined like this: - -.. includecode:: /../../akka-http-core/src/main/scala/akka/http/javadsl/ConnectionContext.scala - :include: https-context-creation - -On the server-side the ``bind``, and ``bindAndHandleXXX`` methods of the `akka.http.javadsl.Http`_ extension define an -optional ``httpsContext`` parameter, which can receive the HTTPS configuration in the form of an ``HttpsContext`` -instance. -If defined encryption is enabled on all accepted connections. Otherwise it is disabled (which is the default). - .. _http-server-layer-java: Stand-Alone HTTP Layer Usage 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 9081c7d4c2..369b1a2c2a 100644 --- a/akka-docs/rst/scala/code/docs/http/scaladsl/HttpServerExampleSpec.scala +++ b/akka-docs/rst/scala/code/docs/http/scaladsl/HttpServerExampleSpec.scala @@ -417,7 +417,7 @@ class HttpServerExampleSpec extends WordSpec with Matchers getFromResourceDirectory("docs") } } ~ - path("oldApi" / Rest) { pathRest => + path("oldApi" / Remaining) { pathRest => redirect("http://oldapi.example.com/" + pathRest, MovedPermanently) } } @@ -516,7 +516,7 @@ class HttpServerExampleSpec extends WordSpec with Matchers parameter("bid".as[Int], "user") { (bid, user) => // place a bid, fire-and-forget auction ! Bid(user, bid) - complete(StatusCodes.Accepted, "bid placed") + complete((StatusCodes.Accepted, "bid placed")) } } get { 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 761b1b86c3..ab775cd7fe 100644 --- a/akka-docs/rst/scala/http/client-side/connection-level.rst +++ b/akka-docs/rst/scala/http/client-side/connection-level.rst @@ -70,7 +70,7 @@ It should be noted that Akka Streams provide various timeout functionality so an from the stream stages such as ``idleTimeout``, ``backpressureTimeout``, ``completionTimeout``, ``initialTimeout`` and ``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`. +For more details about timeout support in Akka HTTP in general refer to :ref:`http-timeouts-scala`. .. _http-client-layer: diff --git a/akka-docs/rst/scala/http/common/http-model.rst b/akka-docs/rst/scala/http/common/http-model.rst index 762202a49c..ce17044648 100644 --- a/akka-docs/rst/scala/http/common/http-model.rst +++ b/akka-docs/rst/scala/http/common/http-model.rst @@ -77,7 +77,7 @@ as shown here the Akka HTTP model defines a number of subclasses of ``HttpEntity stream of bytes. -.. _HttpEntity: +.. _HttpEntity-scala: HttpEntity ---------- @@ -257,7 +257,7 @@ Transfer-Encoding response will not be rendered onto the wire and trigger a warning being logged instead! Content-Length - The content length of a message is modelled via its :ref:`HttpEntity`. As such no ``Content-Length`` header will ever + The content length of a message is modelled via its :ref:`HttpEntity-scala`. As such no ``Content-Length`` header will ever be part of a message's ``header`` sequence. Similarly, a ``Content-Length`` header instance that is explicitly added to the ``headers`` of a request or response will not be rendered onto the wire and trigger a warning being logged instead! diff --git a/akka-docs/rst/scala/http/common/index.rst b/akka-docs/rst/scala/http/common/index.rst index bf8c2b34e7..2409341280 100644 --- a/akka-docs/rst/scala/http/common/index.rst +++ b/akka-docs/rst/scala/http/common/index.rst @@ -1,4 +1,4 @@ -.. _http-scala-common: +.. _http-scala-common-scala: Common Abstractions (Client- and Server-Side) ============================================= @@ -19,4 +19,4 @@ which are specific to one side only. de-coding json-support xml-support - timeouts \ No newline at end of file + timeouts diff --git a/akka-docs/rst/scala/http/common/timeouts.rst b/akka-docs/rst/scala/http/common/timeouts.rst index 21c512f173..b4996bbf78 100644 --- a/akka-docs/rst/scala/http/common/timeouts.rst +++ b/akka-docs/rst/scala/http/common/timeouts.rst @@ -1,4 +1,4 @@ -.. _http-timeouts: +.. _http-timeouts-scala: Akka HTTP Timeouts ================== @@ -32,7 +32,7 @@ independently for each of those using the following keys:: Server timeouts --------------- -.. _request-timeout: +.. _request-timeout-scala: Request timeout ^^^^^^^^^^^^^^^ @@ -73,4 +73,4 @@ The connecting timeout is the time period within which the TCP connecting proces 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 +it can be configured using the ``akka.http.client.connecting-timeout`` setting. diff --git a/akka-docs/rst/scala/http/common/xml-support.rst b/akka-docs/rst/scala/http/common/xml-support.rst index 32622c4905..82672a1cc8 100644 --- a/akka-docs/rst/scala/http/common/xml-support.rst +++ b/akka-docs/rst/scala/http/common/xml-support.rst @@ -28,4 +28,4 @@ Once you have done this (un)marshalling between XML and ``NodeSeq`` instances sh .. _Scala XML: https://github.com/scala/scala-xml -.. _ScalaXmlSupport: @github@/akka-http-marshallers-scala/akka-http-xml/src/main/scala/akka/http/scaladsl/marshallers/xml/ScalaXmlSupport.scala \ No newline at end of file +.. _ScalaXmlSupport: @github@/akka-http-marshallers-scala/akka-http-xml/src/main/scala/akka/http/scaladsl/marshallers/xml/ScalaXmlSupport.scala diff --git a/akka-docs/rst/scala/http/index.rst b/akka-docs/rst/scala/http/index.rst index 59410700dd..91b4858454 100644 --- a/akka-docs/rst/scala/http/index.rst +++ b/akka-docs/rst/scala/http/index.rst @@ -14,3 +14,4 @@ Akka HTTP client-side/index server-side-https-support migration-from-spray + migration-from-old-http-javadsl diff --git a/akka-docs/rst/scala/http/low-level-server-side-api.rst b/akka-docs/rst/scala/http/low-level-server-side-api.rst index 80fce716f4..c3b6bf0aed 100644 --- a/akka-docs/rst/scala/http/low-level-server-side-api.rst +++ b/akka-docs/rst/scala/http/low-level-server-side-api.rst @@ -118,7 +118,7 @@ Streaming Request/Response Entities Streaming of HTTP message entities is supported through subclasses of ``HttpEntity``. The application needs to be able to deal with streamed entities when receiving a request as well as, in many cases, when constructing responses. -See :ref:`HttpEntity` for a description of the alternatives. +See :ref:`HttpEntity-scala` for a description of the alternatives. If you rely on the :ref:`http-marshalling-scala` and/or :ref:`http-unmarshalling-scala` facilities provided by Akka HTTP then the conversion of custom types to and from streamed entities can be quite convenient. diff --git a/akka-docs/rst/scala/http/migration-from-old-http-javadsl.rst b/akka-docs/rst/scala/http/migration-from-old-http-javadsl.rst new file mode 100644 index 0000000000..e9bde1935d --- /dev/null +++ b/akka-docs/rst/scala/http/migration-from-old-http-javadsl.rst @@ -0,0 +1,63 @@ +.. _http-javadsl-migration-guide: + +Migration Guide from "old" HTTP JavaDSL +======================================= + +The so-called "old" JavaDSL for Akka HTTP was initially developed during the project's experimental phase, +and thanks to multiple user comments and contributions we were able to come up with a more Java 8 "feel", +which at the same time is also closer to the existing ScalaDSL. + +The previous DSL has been entirely removed and replaced with the the so-called "new" one. +Upgrading to the new DSL is **highly encouraged** since the old one not only was rather hard to work with, +it actually was not possible to express many typical use-cases using it. + +The most major changes include: + + +HttpApp is gone +--------------- +``HttpApp`` (a helper class containing a ``main()`` implementation) is gone, as we would like to encourage understanding +how the various elements of the API fit together. + +Instead developers should start applications "manually", by converting a ``Route`` to a ``Flow`` +using the ``Route.flow`` method. For examples of full apps refer to :ref:`http-testkit-java`. + +``RequestVal`` is gone +---------------------- +The old API heavily relied on the concept of "request values" which could be used to extract a value from a request context. + +Based on community feedback and our own experience we found them too hard to work with in more complex settings. +The concept of a request value has been completely removed, and replaced with proper "directives", exacly like in the ScalaDSL. + +**Previously**:: + + RequestVal host = Headers.byClass(Host.class).instance(); + + final Route route = + route( + handleWith1(host, (ctx, h) -> + ctx.complete(String.format("Host header was: %s", h.host())) + ) + ); + + +**Now**:: + + final Route route = + headerValueByType(Host.class, host -> complete("Host was: " + host)); + +All of ScalaDSL routing has corresponding JavaDSL +------------------------------------------------- +Both ``Route``, ``RouteResult`` and other important core concepts such as ``Rejections`` are now modeled 1:1 with Scala, +making is much simpler to understand one API based on the other one – tremendously useful when learning about some nice +pattern from blogs which used Scala, yet need to apply it in Java and the other way around. + +It is now possible to implement marshallers using Java. Refer to :ref:`marshalling-java` for details. + +Migration help +-------------- +As always, feel free to reach out via the `akka-user `_ mailing list or gitter channels, +to seek help or guidance when migrating from the old APIs. + +For Lightbend subscription owners it is possible to reach out to the core team for help in the migration by asking specific +questions via the `Lightbend customer portal `_. diff --git a/akka-docs/rst/scala/http/routing-dsl/case-class-extraction.rst b/akka-docs/rst/scala/http/routing-dsl/case-class-extraction.rst index 0df76efb50..6f6f1c3602 100644 --- a/akka-docs/rst/scala/http/routing-dsl/case-class-extraction.rst +++ b/akka-docs/rst/scala/http/routing-dsl/case-class-extraction.rst @@ -10,7 +10,7 @@ Consider this example: .. includecode2:: ../../code/docs/http/scaladsl/server/CaseClassExtractionExamplesSpec.scala :snippet: example-1 -Here the :ref:`-parameters-` directives is employed to extract three ``Int`` values, which are then used to construct an +Here the :ref:`-parameters-scala-` directives is employed to extract three ``Int`` values, which are then used to construct an instance of the ``Color`` case class. So far so good. However, if the model classes we'd like to work with have more than just a few parameters the overhead introduced by capturing the arguments as extractions only to feed them into the model class constructor directly afterwards can somewhat clutter up your route definitions. 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 9813f83b53..e528f3296e 100644 --- a/akka-docs/rst/scala/http/routing-dsl/directives/alphabetically.rst +++ b/akka-docs/rst/scala/http/routing-dsl/directives/alphabetically.rst @@ -64,7 +64,7 @@ Directive Description :ref:`-failWith-` Bubbles the given error up the response chain where it is dealt with by the closest :ref:`-handleExceptions-` directive and its ``ExceptionHandler`` :ref:`-fileUpload-` Provides a stream of an uploaded file from a multipart request -:ref:`-formField-` Extracts an HTTP form field from the request +:ref:`-formField-scala-` Extracts an HTTP form field from the request :ref:`-formFieldMap-` Extracts a number of HTTP form field from the request as a ``Map[String, String]`` :ref:`-formFieldMultiMap-` Extracts a number of HTTP form field from the request as @@ -150,7 +150,7 @@ Directive Description :ref:`-parameter-` Extracts a query parameter value from the request :ref:`-parameterMap-` Extracts the request's query parameters as a ``Map[String, String]`` :ref:`-parameterMultiMap-` Extracts the request's query parameters as a ``Map[String, List[String]]`` -:ref:`-parameters-` Extracts a number of query parameter values from the request +:ref:`-parameters-scala-` Extracts a number of query parameter values from the request :ref:`-parameterSeq-` Extracts the request's query parameters as a ``Seq[(String, String)]`` :ref:`-pass-` Always simply passes the request on to its inner route, i.e. doesn't do anything, neither with the request nor the response @@ -211,13 +211,13 @@ 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:`-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:`-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/form-field-directives/formField.rst b/akka-docs/rst/scala/http/routing-dsl/directives/form-field-directives/formField.rst index 37c99c283e..ed3aab8de5 100644 --- a/akka-docs/rst/scala/http/routing-dsl/directives/form-field-directives/formField.rst +++ b/akka-docs/rst/scala/http/routing-dsl/directives/form-field-directives/formField.rst @@ -1,4 +1,4 @@ -.. _-formField-: +.. _-formField-scala-: formField ========= diff --git a/akka-docs/rst/scala/http/routing-dsl/directives/form-field-directives/formFields.rst b/akka-docs/rst/scala/http/routing-dsl/directives/form-field-directives/formFields.rst index df6b0ba12c..639890f3e4 100644 --- a/akka-docs/rst/scala/http/routing-dsl/directives/form-field-directives/formFields.rst +++ b/akka-docs/rst/scala/http/routing-dsl/directives/form-field-directives/formFields.rst @@ -52,9 +52,9 @@ instance. Requests missing a required field or field value will be rejected with an appropriate rejection. -There's also a singular version, :ref:`-formField-`. +There's also a singular version, :ref:`-formField-scala-`. -Query parameters can be handled in a similar way, see :ref:`-parameters-`. +Query parameters can be handled in a similar way, see :ref:`-parameters-scala-`. Unmarshalling ------------- diff --git a/akka-docs/rst/scala/http/routing-dsl/directives/parameter-directives/index.rst b/akka-docs/rst/scala/http/routing-dsl/directives/parameter-directives/index.rst index 047ad11852..81b355bb30 100644 --- a/akka-docs/rst/scala/http/routing-dsl/directives/parameter-directives/index.rst +++ b/akka-docs/rst/scala/http/routing-dsl/directives/parameter-directives/index.rst @@ -17,19 +17,19 @@ ParameterDirectives When to use which parameter directive? -------------------------------------- -Usually, you want to use the high-level :ref:`-parameters-` directive. When you need +Usually, you want to use the high-level :ref:`-parameters-scala-` directive. When you need more low-level access you can use the table below to decide which directive to use which shows properties of different parameter directives. -========================== ====== ======== ===== -directive level ordering multi -========================== ====== ======== ===== -:ref:`-parameter-` high no no -:ref:`-parameters-` high no yes -:ref:`-parameterMap-` low no no -:ref:`-parameterMultiMap-` low no yes -:ref:`-parameterSeq-` low yes yes -========================== ====== ======== ===== +================================ ====== ======== ===== +directive level ordering multi +================================ ====== ======== ===== +:ref:`-parameter-` high no no +:ref:`-parameters-scala-` high no yes +:ref:`-parameterMap-` low no no +:ref:`-parameterMultiMap-` low no yes +:ref:`-parameterSeq-` low yes yes +================================ ====== ======== ===== level high-level parameter directives extract subset of all parameters by name and allow conversions diff --git a/akka-docs/rst/scala/http/routing-dsl/directives/parameter-directives/parameter.rst b/akka-docs/rst/scala/http/routing-dsl/directives/parameter-directives/parameter.rst index 9bead83e95..2151694d97 100644 --- a/akka-docs/rst/scala/http/routing-dsl/directives/parameter-directives/parameter.rst +++ b/akka-docs/rst/scala/http/routing-dsl/directives/parameter-directives/parameter.rst @@ -13,7 +13,7 @@ Description ----------- Extracts a *query* parameter value from the request. -See :ref:`-parameters-` for a detailed description of this directive. +See :ref:`-parameters-scala-` for a detailed description of this directive. See :ref:`which-parameter-directive` to understand when to use which directive. diff --git a/akka-docs/rst/scala/http/routing-dsl/directives/parameter-directives/parameters.rst b/akka-docs/rst/scala/http/routing-dsl/directives/parameter-directives/parameters.rst index cdc32f684c..082586e356 100644 --- a/akka-docs/rst/scala/http/routing-dsl/directives/parameter-directives/parameters.rst +++ b/akka-docs/rst/scala/http/routing-dsl/directives/parameter-directives/parameters.rst @@ -1,4 +1,4 @@ -.. _-parameters-: +.. _-parameters-scala-: parameters ========== 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 index 62ccbb227d..337cacf5ca 100644 --- 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 @@ -12,7 +12,7 @@ Signature Description ----------- -This directive enables "late" (during request processing) control over the :ref:`request-timeout` feature in Akka HTTP. +This directive enables "late" (during request processing) control over the :ref:`request-timeout-scala` 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 @@ -35,7 +35,7 @@ See also :ref:`-withRequestTimeoutResponse-` if only looking to customise the ti 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`. +For more information about various timeouts in Akka HTTP see :ref:`http-timeouts-scala`. Example ------- 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 index 6fd76faa56..028d75e55a 100644 --- 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 @@ -12,7 +12,7 @@ Signature Description ----------- -Allows customising the ``HttpResponse`` that will be sent to clients in case of a :ref:`request-timeout`. +Allows customising the ``HttpResponse`` that will be sent to clients in case of a :ref:`request-timeout-scala`. See also :ref:`-withRequestTimeout-` or :ref:`-withoutRequestTimeout-` if interested in dynamically changing the timeout for a given route instead. @@ -25,7 +25,7 @@ for a given route instead. 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`. +To learn more about various timeouts in Akka HTTP and how to configure them see :ref:`http-timeouts-scala`. Example ------- 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 index b9ef619443..48d936f995 100644 --- 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 @@ -12,7 +12,7 @@ Signature Description ----------- -This directive enables "late" (during request processing) control over the :ref:`request-timeout` feature in Akka HTTP. +This directive enables "late" (during request processing) control over the :ref:`request-timeout-scala` 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. @@ -22,7 +22,7 @@ basically turns off the safety net against programming mistakes that it provides 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`. +For more information about various timeouts in Akka HTTP see :ref:`http-timeouts-scala`. Example ------- diff --git a/akka-http-core/src/main/java/akka/http/javadsl/model/DateTime.java b/akka-http-core/src/main/java/akka/http/javadsl/model/DateTime.java index 7409f6049a..754c85d485 100644 --- a/akka-http-core/src/main/java/akka/http/javadsl/model/DateTime.java +++ b/akka-http-core/src/main/java/akka/http/javadsl/model/DateTime.java @@ -98,6 +98,16 @@ public abstract class DateTime { return akka.http.scaladsl.model.DateTime.now(); } + /** + * Creates a new `DateTime` that represents the point in time the given number of ms earlier. + */ + public abstract DateTime minus(long millis); + + /** + * Creates a new `DateTime` that represents the point in time the given number of ms later. + */ + public abstract DateTime plus(long millis); + /** * Returns a new DateTime instance parsed from IsoDateTimeString as Some(dateTime). Returns None if * parsing has failed. diff --git a/akka-http-core/src/main/java/akka/http/javadsl/model/HttpEntities.java b/akka-http-core/src/main/java/akka/http/javadsl/model/HttpEntities.java index ff221c51ac..10eb5832c8 100644 --- a/akka-http-core/src/main/java/akka/http/javadsl/model/HttpEntities.java +++ b/akka-http-core/src/main/java/akka/http/javadsl/model/HttpEntities.java @@ -8,7 +8,6 @@ import java.io.File; import java.nio.file.Path; import akka.http.impl.util.JavaAccessors; -import akka.http.scaladsl.model.HttpEntity; import akka.http.scaladsl.model.HttpEntity$; import akka.util.ByteString; import akka.stream.javadsl.Source; @@ -72,7 +71,7 @@ public final class HttpEntities { } public static HttpEntity.Chunked create(ContentType contentType, Source data) { - return HttpEntity.Chunked$.MODULE$.fromData((akka.http.scaladsl.model.ContentType) contentType, data.asScala()); + return akka.http.scaladsl.model.HttpEntity.Chunked$.MODULE$.fromData((akka.http.scaladsl.model.ContentType) contentType, data.asScala()); } public static HttpEntity.CloseDelimited createCloseDelimited(ContentType contentType, Source data) { diff --git a/akka-http-core/src/main/java/akka/http/javadsl/model/headers/CustomHeader.java b/akka-http-core/src/main/java/akka/http/javadsl/model/headers/CustomHeader.java index 745765de59..d0278289ce 100644 --- a/akka-http-core/src/main/java/akka/http/javadsl/model/headers/CustomHeader.java +++ b/akka-http-core/src/main/java/akka/http/javadsl/model/headers/CustomHeader.java @@ -4,6 +4,10 @@ package akka.http.javadsl.model.headers; +/** + * The model of an HTTP header. In its most basic form headers are simple name-value pairs. Header names + * are compared in a case-insensitive way. + */ public abstract class CustomHeader extends akka.http.scaladsl.model.HttpHeader { public abstract String name(); public abstract String value(); diff --git a/akka-http-core/src/main/scala/akka/http/impl/model/JavaQuery.scala b/akka-http-core/src/main/scala/akka/http/impl/model/JavaQuery.scala index 6c8e0f131d..aaf5719f8a 100644 --- a/akka-http-core/src/main/scala/akka/http/impl/model/JavaQuery.scala +++ b/akka-http-core/src/main/scala/akka/http/impl/model/JavaQuery.scala @@ -17,7 +17,6 @@ import akka.parboiled2.CharPredicate import scala.collection.JavaConverters._ import akka.http.impl.util.JavaMapping.Implicits._ -import scala.compat.java8.OptionConverters._ /** INTERNAL API */ case class JavaQuery(query: sm.Uri.Query) extends jm.Query { diff --git a/akka-http-core/src/main/scala/akka/http/impl/model/JavaUri.scala b/akka-http-core/src/main/scala/akka/http/impl/model/JavaUri.scala index 1003b69b02..80f53411be 100644 --- a/akka-http-core/src/main/scala/akka/http/impl/model/JavaUri.scala +++ b/akka-http-core/src/main/scala/akka/http/impl/model/JavaUri.scala @@ -11,7 +11,6 @@ import akka.http.scaladsl.model.Uri.ParsingMode import akka.http.javadsl.{ model ⇒ jm } import akka.http.scaladsl.{ model ⇒ sm } import akka.http.impl.util.JavaMapping.Implicits._ -import scala.compat.java8.OptionConverters._ /** INTERNAL API */ case class JavaUri(uri: sm.Uri) extends jm.Uri { diff --git a/akka-http-core/src/main/scala/akka/http/impl/util/JavaMapping.scala b/akka-http-core/src/main/scala/akka/http/impl/util/JavaMapping.scala index 0a3fa15137..bf22a80c3b 100644 --- a/akka-http-core/src/main/scala/akka/http/impl/util/JavaMapping.scala +++ b/akka-http-core/src/main/scala/akka/http/impl/util/JavaMapping.scala @@ -11,6 +11,7 @@ import akka.japi.Pair import akka.stream.{ Graph, FlowShape, javadsl, scaladsl } import scala.collection.immutable +import scala.compat.java8.OptionConverters import scala.reflect.ClassTag import akka.NotUsed import akka.http.impl.model.{ JavaQuery, JavaUri } @@ -18,7 +19,7 @@ import akka.http.javadsl.{ model ⇒ jm, HttpConnectionContext, ConnectionContex import akka.http.scaladsl.{ model ⇒ sm } import akka.http.javadsl.{ settings ⇒ js } -import scala.compat.java8.OptionConverters._ +import akka.http.impl.util.JavaMapping.Implicits._ import scala.util.Try @@ -84,11 +85,11 @@ private[http] object JavaMapping { } /** This trivial mapping isn't enabled by default to prevent it from conflicting with the `Inherited` ones */ - def identity[T]: JavaMapping[T, T] = - new JavaMapping[T, T] { - def toJava(scalaObject: T): J = scalaObject - def toScala(javaObject: T): S = javaObject - } + def identity[T]: JavaMapping[T, T] = _identityMapping.asInstanceOf[JavaMapping[T, T]] + private final val _identityMapping = new JavaMapping[Any, Any] { + def toJava(scalaObject: Any): Any = scalaObject + def toScala(javaObject: Any): Any = javaObject + } implicit def iterableMapping[_J, _S](implicit mapping: JavaMapping[_J, _S]): JavaMapping[jl.Iterable[_J], immutable.Seq[_S]] = new JavaMapping[jl.Iterable[_J], immutable.Seq[_S]] { @@ -106,8 +107,8 @@ private[http] object JavaMapping { } implicit def option[_J, _S](implicit mapping: JavaMapping[_J, _S]): JavaMapping[Optional[_J], Option[_S]] = new JavaMapping[Optional[_J], Option[_S]] { - def toScala(javaObject: Optional[_J]): Option[_S] = javaObject.asScala.map(mapping.toScala) - def toJava(scalaObject: Option[_S]): Optional[_J] = scalaObject.map(mapping.toJava).asJava + def toScala(javaObject: Optional[_J]): Option[_S] = OptionConverters.toScala(javaObject).map(mapping.toScala) + def toJava(scalaObject: Option[_S]): Optional[_J] = OptionConverters.toJava(scalaObject.map(mapping.toJava)) } implicit def flowMapping[JIn, SIn, JOut, SOut, M](implicit inMapping: JavaMapping[JIn, SIn], outMapping: JavaMapping[JOut, SOut]): JavaMapping[javadsl.Flow[JIn, JOut, M], scaladsl.Flow[SIn, SOut, M]] = @@ -189,6 +190,8 @@ private[http] object JavaMapping { implicit object HttpCharset extends Inherited[jm.HttpCharset, sm.HttpCharset] implicit object HttpCharsetRange extends Inherited[jm.HttpCharsetRange, sm.HttpCharsetRange] implicit object HttpEntity extends Inherited[jm.HttpEntity, sm.HttpEntity] + implicit object RequestEntity extends Inherited[jm.RequestEntity, sm.RequestEntity] + implicit object ResponseEntity extends Inherited[jm.ResponseEntity, sm.ResponseEntity] implicit object HttpHeader extends Inherited[jm.HttpHeader, sm.HttpHeader] implicit object HttpMethod extends Inherited[jm.HttpMethod, sm.HttpMethod] implicit object HttpProtocol extends Inherited[jm.HttpProtocol, sm.HttpProtocol] diff --git a/akka-http-core/src/main/scala/akka/http/javadsl/ConnectionContext.scala b/akka-http-core/src/main/scala/akka/http/javadsl/ConnectionContext.scala index 2fe0bdb16b..98610b2cd6 100644 --- a/akka-http-core/src/main/scala/akka/http/javadsl/ConnectionContext.scala +++ b/akka-http-core/src/main/scala/akka/http/javadsl/ConnectionContext.scala @@ -8,6 +8,7 @@ import javax.net.ssl.{ SSLContext, SSLParameters } import akka.http.scaladsl import akka.japi.Util import akka.stream.TLSClientAuth +import akka.http.impl.util.JavaMapping.Implicits._ import scala.compat.java8.OptionConverters import scala.collection.JavaConverters._ diff --git a/akka-http-core/src/main/scala/akka/http/scaladsl/model/DateTime.scala b/akka-http-core/src/main/scala/akka/http/scaladsl/model/DateTime.scala index 1b58ea5ff3..90b4d4a376 100644 --- a/akka-http-core/src/main/scala/akka/http/scaladsl/model/DateTime.scala +++ b/akka-http-core/src/main/scala/akka/http/scaladsl/model/DateTime.scala @@ -42,6 +42,17 @@ final case class DateTime private (year: Int, // the year */ def -(millis: Long): DateTime = DateTime(clicks - millis) + + /** + * Creates a new `DateTime` that represents the point in time the given number of ms earlier. + */ + def minus(millis: Long): DateTime = this - millis + + /** + * Creates a new `DateTime` that represents the point in time the given number of ms later. + */ + def plus(millis: Long): DateTime = this + millis + /** * `yyyy-mm-ddThh:mm:ss` */ diff --git a/akka-http-core/src/main/scala/akka/http/scaladsl/model/HttpEntity.scala b/akka-http-core/src/main/scala/akka/http/scaladsl/model/HttpEntity.scala index dad23d5d42..8d9cb297f9 100644 --- a/akka-http-core/src/main/scala/akka/http/scaladsl/model/HttpEntity.scala +++ b/akka-http-core/src/main/scala/akka/http/scaladsl/model/HttpEntity.scala @@ -24,7 +24,7 @@ import akka.{ NotUsed, stream } import akka.http.scaladsl.model.ContentType.{ NonBinary, Binary } import akka.http.scaladsl.util.FastFuture import akka.http.javadsl.{ model ⇒ jm } -import akka.http.impl.util.StreamUtils +import akka.http.impl.util.{JavaMapping, StreamUtils} import akka.http.impl.util.JavaMapping.Implicits._ import scala.compat.java8.OptionConverters._ @@ -196,6 +196,12 @@ sealed trait ResponseEntity extends HttpEntity with jm.ResponseEntity { def transformDataBytes(transformer: Flow[ByteString, ByteString, Any]): ResponseEntity } + +object ResponseEntity { + implicit def fromJava(entity: akka.http.javadsl.model.ResponseEntity)(implicit m: JavaMapping[akka.http.javadsl.model.ResponseEntity, ResponseEntity]): ResponseEntity = + JavaMapping.toScala(entity) +} + /* An entity that can be used for requests, responses, and body parts */ sealed trait UniversalEntity extends jm.UniversalEntity with MessageEntity with BodyPartEntity { def withContentType(contentType: ContentType): UniversalEntity diff --git a/akka-http-core/src/main/scala/akka/http/scaladsl/model/HttpMessage.scala b/akka-http-core/src/main/scala/akka/http/scaladsl/model/HttpMessage.scala index 823895edb4..827a7a6551 100644 --- a/akka-http-core/src/main/scala/akka/http/scaladsl/model/HttpMessage.scala +++ b/akka-http-core/src/main/scala/akka/http/scaladsl/model/HttpMessage.scala @@ -12,6 +12,7 @@ import java.util.Optional import scala.concurrent.duration.FiniteDuration import scala.concurrent.{ Future, ExecutionContext } import scala.collection.immutable +import scala.compat.java8.OptionConverters._ import scala.reflect.{ classTag, ClassTag } import akka.parboiled2.CharUtils import akka.stream.Materializer @@ -20,8 +21,7 @@ import akka.http.impl.util._ import akka.http.javadsl.{ model ⇒ jm } import akka.http.scaladsl.util.FastFuture._ import headers._ - -import scala.compat.java8.OptionConverters._ +import akka.http.impl.util.JavaMapping.Implicits._ /** * Common base class of HttpRequest and HttpResponse. diff --git a/akka-http-core/src/main/scala/akka/http/scaladsl/model/Multipart.scala b/akka-http-core/src/main/scala/akka/http/scaladsl/model/Multipart.scala index f7d3a8944d..fdbd6f7b98 100644 --- a/akka-http-core/src/main/scala/akka/http/scaladsl/model/Multipart.scala +++ b/akka-http-core/src/main/scala/akka/http/scaladsl/model/Multipart.scala @@ -22,7 +22,7 @@ import akka.http.scaladsl.model.headers._ import akka.http.impl.engine.rendering.BodyPartRenderer import akka.http.javadsl.{ model ⇒ jm } import FastFuture._ -import scala.compat.java8.OptionConverters._ +import akka.http.impl.util.JavaMapping.Implicits._ import scala.compat.java8.FutureConverters._ import java.util.concurrent.CompletionStage diff --git a/akka-http-core/src/main/scala/akka/http/scaladsl/model/ws/Message.scala b/akka-http-core/src/main/scala/akka/http/scaladsl/model/ws/Message.scala index 59ed41f773..355e45a161 100644 --- a/akka-http-core/src/main/scala/akka/http/scaladsl/model/ws/Message.scala +++ b/akka-http-core/src/main/scala/akka/http/scaladsl/model/ws/Message.scala @@ -11,7 +11,7 @@ import akka.util.ByteString /** * The ADT for WebSocket messages. A message can either be a binary or a text message. */ -sealed trait Message +sealed trait Message // FIXME: Why don't we extend akka.http.javadsl.model.ws.Message here? /** * Represents a WebSocket text message. A text message can either be a [[TextMessage.Strict]] in which case diff --git a/akka-http-core/src/test/java/akka/http/javadsl/model/JavaTestServer.java b/akka-http-core/src/test/java/akka/http/JavaTestServer.java similarity index 97% rename from akka-http-core/src/test/java/akka/http/javadsl/model/JavaTestServer.java rename to akka-http-core/src/test/java/akka/http/JavaTestServer.java index 400aa578f0..4b32285684 100644 --- a/akka-http-core/src/test/java/akka/http/javadsl/model/JavaTestServer.java +++ b/akka-http-core/src/test/java/akka/http/JavaTestServer.java @@ -2,13 +2,14 @@ * Copyright (C) 2009-2016 Lightbend Inc. */ -package akka.http.javadsl.model; +package akka.http; import akka.NotUsed; import akka.actor.ActorSystem; import akka.http.javadsl.ConnectHttp; import akka.http.javadsl.Http; import akka.http.javadsl.ServerBinding; +import akka.http.javadsl.model.JavaApiTestCases; import akka.http.javadsl.model.ws.Message; import akka.http.javadsl.model.ws.TextMessage; import akka.http.javadsl.model.ws.WebSocket; diff --git a/akka-http-marshallers-java/akka-http-jackson/build.sbt b/akka-http-marshallers-java/akka-http-jackson/build.sbt index cfdac17dd6..d4b5e3b031 100644 --- a/akka-http-marshallers-java/akka-http-jackson/build.sbt +++ b/akka-http-marshallers-java/akka-http-jackson/build.sbt @@ -7,3 +7,4 @@ OSGi.httpJackson Dependencies.httpJackson enablePlugins(ScaladocNoVerificationOfDiagrams) +disablePlugins(MimaPlugin) // still experimental diff --git a/akka-http-marshallers-java/akka-http-jackson/src/main/java/akka/http/javadsl/marshallers/jackson/Jackson.java b/akka-http-marshallers-java/akka-http-jackson/src/main/java/akka/http/javadsl/marshallers/jackson/Jackson.java new file mode 100644 index 0000000000..eb1bb70cea --- /dev/null +++ b/akka-http-marshallers-java/akka-http-jackson/src/main/java/akka/http/javadsl/marshallers/jackson/Jackson.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2009-2016 Typesafe Inc. + */ +package akka.http.javadsl.marshallers.jackson; + +import java.io.IOException; + +import akka.http.javadsl.model.HttpEntity; +import akka.http.javadsl.model.MediaTypes; +import akka.http.javadsl.model.RequestEntity; +import akka.http.javadsl.server.Marshaller; +import akka.http.javadsl.server.Unmarshaller; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; + +public class Jackson { + private static final ObjectMapper defaultObjectMapper = + new ObjectMapper().enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY); + + public static Marshaller marshaller() { + return marshaller(defaultObjectMapper); + } + + public static Marshaller marshaller(ObjectMapper mapper) { + return Marshaller.wrapEntity( + u -> toJSON(mapper, u), + Marshaller.stringToEntity(), + MediaTypes.APPLICATION_JSON + ); + } + + public static Unmarshaller unmarshaller(Class expectedType) { + return unmarshaller(defaultObjectMapper, expectedType); + } + + public static Unmarshaller unmarshaller(ObjectMapper mapper, Class expectedType) { + return Unmarshaller.forMediaType(MediaTypes.APPLICATION_JSON, Unmarshaller.entityToString()) + .thenApply(s -> fromJSON(mapper, s, expectedType)); + } + + private static String toJSON(ObjectMapper mapper, Object object) { + try { + return mapper.writeValueAsString(object); + } catch (JsonProcessingException e) { + throw new IllegalArgumentException("Cannot marshal to JSON: " + object, e); + } + } + + private static T fromJSON(ObjectMapper mapper, String json, Class expectedType) { + try { + return mapper.readerFor(expectedType).readValue(json); + } catch (IOException e) { + throw new IllegalArgumentException("Cannot unmarshal JSON as " + expectedType.getSimpleName(), e); + } + } +} diff --git a/akka-http-marshallers-java/akka-http-jackson/src/main/scala/akka/http/javadsl/marshallers/jackson/Jackson.scala b/akka-http-marshallers-java/akka-http-jackson/src/main/scala/akka/http/javadsl/marshallers/jackson/Jackson.scala deleted file mode 100644 index eff3dd95ff..0000000000 --- a/akka-http-marshallers-java/akka-http-jackson/src/main/scala/akka/http/javadsl/marshallers/jackson/Jackson.scala +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.javadsl.marshallers.jackson - -import com.fasterxml.jackson.databind.{ MapperFeature, ObjectMapper } -import scala.reflect.ClassTag -import akka.http.scaladsl.marshalling -import akka.http.scaladsl.unmarshalling -import akka.http.scaladsl.model.MediaTypes._ -import akka.http.javadsl.server.{ Unmarshaller, Marshaller } -import akka.http.impl.server.{ UnmarshallerImpl, MarshallerImpl } - -object Jackson { - private val objectMapper: ObjectMapper = new ObjectMapper().enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) - def json[T <: AnyRef]: Marshaller[T] = jsonMarshaller(objectMapper).asInstanceOf[Marshaller[T]] - def json[T <: AnyRef](objectMapper: ObjectMapper): Marshaller[T] = jsonMarshaller(objectMapper).asInstanceOf[Marshaller[T]] - def jsonAs[T](clazz: Class[T]): Unmarshaller[T] = jsonAs(objectMapper, clazz) - def jsonAs[T](objectMapper: ObjectMapper, clazz: Class[T]): Unmarshaller[T] = - UnmarshallerImpl[T] { - unmarshalling.Unmarshaller.messageUnmarshallerFromEntityUnmarshaller { // isn't implicitly inferred for unknown reasons - unmarshalling.Unmarshaller.stringUnmarshaller - .forContentTypes(`application/json`) - .map { jsonString ⇒ - val reader = objectMapper.reader(clazz) - clazz.cast(reader.readValue(jsonString)) - } - } - }(ClassTag(clazz)) - - private def jsonMarshaller(objectMapper: ObjectMapper): Marshaller[AnyRef] = - MarshallerImpl[AnyRef] { implicit ec ⇒ - marshalling.Marshaller.StringMarshaller.wrap(`application/json`) { (value: AnyRef) ⇒ - val writer = objectMapper.writer() - writer.writeValueAsString(value) - } - } -} diff --git a/akka-http-marshallers-scala/akka-http-spray-json/build.sbt b/akka-http-marshallers-scala/akka-http-spray-json/build.sbt index 9b0ad68674..87798cf403 100644 --- a/akka-http-marshallers-scala/akka-http-spray-json/build.sbt +++ b/akka-http-marshallers-scala/akka-http-spray-json/build.sbt @@ -5,3 +5,5 @@ AkkaBuild.experimentalSettings Formatting.formatSettings OSGi.httpSprayJson Dependencies.httpSprayJson + +disablePlugins(MimaPlugin) // still experimental diff --git a/akka-http-marshallers-scala/akka-http-xml/build.sbt b/akka-http-marshallers-scala/akka-http-xml/build.sbt index 336783a5b2..7c8f5d6739 100644 --- a/akka-http-marshallers-scala/akka-http-xml/build.sbt +++ b/akka-http-marshallers-scala/akka-http-xml/build.sbt @@ -5,3 +5,5 @@ AkkaBuild.experimentalSettings Formatting.formatSettings OSGi.httpXml Dependencies.httpXml + +disablePlugins(MimaPlugin) // still experimental diff --git a/akka-http-testkit/build.sbt b/akka-http-testkit/build.sbt index 7dff15fb9c..03c594c48a 100644 --- a/akka-http-testkit/build.sbt +++ b/akka-http-testkit/build.sbt @@ -6,3 +6,5 @@ OSGi.httpTestkit Dependencies.httpTestkit scalacOptions in Compile += "-language:postfixOps" + +disablePlugins(MimaPlugin) // testkit, no bin compat guaranteed diff --git a/akka-http-testkit/src/main/scala/akka/http/javadsl/testkit/JUnitRouteTest.scala b/akka-http-testkit/src/main/scala/akka/http/javadsl/testkit/JUnitRouteTest.scala index 0b0f600068..ec28b39247 100644 --- a/akka-http-testkit/src/main/scala/akka/http/javadsl/testkit/JUnitRouteTest.scala +++ b/akka-http-testkit/src/main/scala/akka/http/javadsl/testkit/JUnitRouteTest.scala @@ -5,27 +5,32 @@ package akka.http.javadsl.testkit import akka.actor.ActorSystem +import akka.event.Logging +import akka.http.javadsl.model.HttpRequest import akka.http.javadsl.server._ import akka.http.scaladsl.model.HttpResponse import akka.stream.{ Materializer, ActorMaterializer } +import com.typesafe.config.{ ConfigFactory, Config } import org.junit.rules.ExternalResource import org.junit.{ Assert, Rule } +import org.scalatest.junit.{ JUnitSuiteLike, JUnitSuite } import scala.concurrent.duration._ import scala.concurrent.Await +import akka.http.scaladsl.server.RouteResult /** * A RouteTest that uses JUnit assertions. ActorSystem and Materializer are provided as an [[org.junit.rules.ExternalResource]] * and their lifetime is automatically managed. */ -abstract class JUnitRouteTestBase extends RouteTest { +abstract class JUnitRouteTestBase extends RouteTest with JUnitSuiteLike { protected def systemResource: ActorSystemResource implicit def system: ActorSystem = systemResource.system implicit def materializer: Materializer = systemResource.materializer - protected def createTestResponse(response: HttpResponse): TestResponse = - new TestResponse(response, awaitDuration)(system.dispatcher, materializer) { + protected def createTestRouteResult(request: HttpRequest, result: RouteResult): TestRouteResult = + new TestRouteResult(result, awaitDuration)(system.dispatcher, materializer) { protected def assertEquals(expected: AnyRef, actual: AnyRef, message: String): Unit = - Assert.assertEquals(message, expected, actual) + reportDetails { Assert.assertEquals(message, expected, actual) } protected def assertEquals(expected: Int, actual: Int, message: String): Unit = Assert.assertEquals(message, expected, actual) @@ -37,21 +42,27 @@ abstract class JUnitRouteTestBase extends RouteTest { Assert.fail(message) throw new IllegalStateException("Assertion should have failed") } - } - protected def completeWithValueToString[T](value: RequestVal[T]): Route = - handleWith1(value, new Handler1[T] { - def apply(ctx: RequestContext, t: T): RouteResult = ctx.complete(t.toString) - }) + def reportDetails[T](block: ⇒ T): T = { + try block catch { + case t: Throwable ⇒ throw new AssertionError(t.getMessage + "\n" + + " Request was: " + request + "\n" + + " Route result was: " + result + "\n", t) + } + } + } } abstract class JUnitRouteTest extends JUnitRouteTestBase { - private[this] val _systemResource = new ActorSystemResource + protected def additionalConfig: Config = ConfigFactory.empty() + + private[this] val _systemResource = new ActorSystemResource(Logging.simpleName(getClass), additionalConfig) @Rule protected def systemResource: ActorSystemResource = _systemResource } -class ActorSystemResource extends ExternalResource { - protected def createSystem(): ActorSystem = ActorSystem() +class ActorSystemResource(name: String, additionalConfig: Config) extends ExternalResource { + protected def config = additionalConfig.withFallback(ConfigFactory.load()) + protected def createSystem(): ActorSystem = ActorSystem(name, config) protected def createMaterializer(system: ActorSystem): ActorMaterializer = ActorMaterializer()(system) implicit def system: ActorSystem = _system diff --git a/akka-http-testkit/src/main/scala/akka/http/javadsl/testkit/RouteTest.scala b/akka-http-testkit/src/main/scala/akka/http/javadsl/testkit/RouteTest.scala index 013bf84cc4..d78a43ffc0 100644 --- a/akka-http-testkit/src/main/scala/akka/http/javadsl/testkit/RouteTest.scala +++ b/akka-http-testkit/src/main/scala/akka/http/javadsl/testkit/RouteTest.scala @@ -4,29 +4,27 @@ package akka.http.javadsl.testkit -import akka.http.scaladsl.settings.RoutingSettings - import scala.annotation.varargs import scala.concurrent.ExecutionContextExecutor -import scala.concurrent.duration._ -import akka.stream.Materializer -import akka.http.scaladsl.server -import akka.http.javadsl.model.HttpRequest -import akka.http.javadsl.model.headers.Host -import akka.http.javadsl.server.{ HttpApp, AllDirectives, Route, Directives } -import akka.http.impl.util.JavaMapping.Implicits._ -import akka.http.impl.server.RouteImplementation -import akka.http.scaladsl.model.HttpResponse -import akka.http.scaladsl.server.{ RouteResult, Route ⇒ ScalaRoute } +import scala.concurrent.duration.DurationInt +import scala.concurrent.duration.FiniteDuration import akka.actor.ActorSystem import akka.event.NoLogging -import akka.http.impl.util._ +import akka.http.impl.util.AddFutureAwaitResult +import akka.http.impl.util.JavaMapping.Implicits.AddAsScala +import akka.http.javadsl.model.HttpRequest +import akka.http.javadsl.model.headers.Host +import akka.http.javadsl.server.AllDirectives +import akka.http.javadsl.server.Directives +import akka.http.javadsl.server.Route +import akka.http.scaladsl.server +import akka.http.scaladsl.server.{ ExceptionHandler, RequestContextImpl, RouteResult, Route ⇒ ScalaRoute } +import akka.http.scaladsl.settings.RoutingSettings +import akka.stream.Materializer /** * A base class to create route tests for testing libraries. An implementation needs to provide * code to provide and shutdown an [[akka.actor.ActorSystem]], [[akka.stream.Materializer]], and [[scala.concurrent.ExecutionContextExecutor]]. - * Also an implementation should provide instances of [[TestResponse]] to define the assertion - * facilities of the testing library. * * See `JUnitRouteTest` for an example of a concrete implementation. */ @@ -39,30 +37,32 @@ abstract class RouteTest extends AllDirectives { protected def defaultHostInfo: DefaultHostInfo = DefaultHostInfo(Host.create("example.com"), false) - def runRoute(route: Route, request: HttpRequest): TestResponse = + def runRoute(route: Route, request: HttpRequest): TestRouteResult = runRoute(route, request, defaultHostInfo) - def runRoute(route: Route, request: HttpRequest, defaultHostInfo: DefaultHostInfo): TestResponse = - runScalaRoute(ScalaRoute.seal(RouteImplementation(route)), request, defaultHostInfo) + def runRoute(route: Route, request: HttpRequest, defaultHostInfo: DefaultHostInfo): TestRouteResult = + runScalaRoute(route.seal(system, materializer).delegate, request, defaultHostInfo) - def runRouteUnSealed(route: Route, request: HttpRequest): TestResponse = + def runRouteUnSealed(route: Route, request: HttpRequest): TestRouteResult = runRouteUnSealed(route, request, defaultHostInfo) - def runRouteUnSealed(route: Route, request: HttpRequest, defaultHostInfo: DefaultHostInfo): TestResponse = - runScalaRoute(RouteImplementation(route), request, defaultHostInfo) + def runRouteUnSealed(route: Route, request: HttpRequest, defaultHostInfo: DefaultHostInfo): TestRouteResult = + runScalaRoute(route.delegate, request, defaultHostInfo) - private def runScalaRoute(scalaRoute: ScalaRoute, request: HttpRequest, defaultHostInfo: DefaultHostInfo): TestResponse = { + private def runScalaRoute(scalaRoute: ScalaRoute, request: HttpRequest, defaultHostInfo: DefaultHostInfo): TestRouteResult = { val effectiveRequest = request.asScala .withEffectiveUri( securedConnection = defaultHostInfo.isSecuredConnection(), defaultHostHeader = defaultHostInfo.getHost().asScala) - val result = scalaRoute(new server.RequestContextImpl(effectiveRequest, NoLogging, RoutingSettings(system))) + // this will give us the default exception handler + val sealedExceptionHandler = ExceptionHandler.seal(null) - result.awaitResult(awaitDuration) match { - case RouteResult.Complete(response) ⇒ createTestResponse(response) - case RouteResult.Rejected(ex) ⇒ throw new AssertionError("got unexpected rejection: " + ex) - } + val semiSealedRoute = // sealed for exceptions but not for rejections + akka.http.scaladsl.server.Directives.handleExceptions(sealedExceptionHandler)(scalaRoute) + + val result = semiSealedRoute(new server.RequestContextImpl(effectiveRequest, system.log, RoutingSettings(system))) + createTestRouteResult(request, result.awaitResult(awaitDuration)) } /** @@ -71,15 +71,10 @@ abstract class RouteTest extends AllDirectives { @varargs def testRoute(first: Route, others: Route*): TestRoute = new TestRoute { - val underlying: Route = Directives.route(first, others: _*) + val underlying: Route = Directives.route(first +: others: _*) - def run(request: HttpRequest): TestResponse = runRoute(underlying, request) + def run(request: HttpRequest): TestRouteResult = runRoute(underlying, request) } - /** - * Creates a [[TestRoute]] for the main route of an [[akka.http.javadsl.server.HttpApp]]. - */ - def testAppRoute(app: HttpApp): TestRoute = testRoute(app.createRoute()) - - protected def createTestResponse(response: HttpResponse): TestResponse + protected def createTestRouteResult(request: HttpRequest, result: RouteResult): TestRouteResult } diff --git a/akka-http-testkit/src/main/scala/akka/http/javadsl/testkit/TestRoute.scala b/akka-http-testkit/src/main/scala/akka/http/javadsl/testkit/TestRoute.scala index 8aab474679..175d3b5921 100644 --- a/akka-http-testkit/src/main/scala/akka/http/javadsl/testkit/TestRoute.scala +++ b/akka-http-testkit/src/main/scala/akka/http/javadsl/testkit/TestRoute.scala @@ -17,5 +17,5 @@ import akka.http.javadsl.server.Route */ trait TestRoute { def underlying: Route - def run(request: HttpRequest): TestResponse + def run(request: HttpRequest): TestRouteResult } \ No newline at end of file diff --git a/akka-http-testkit/src/main/scala/akka/http/javadsl/testkit/TestResponse.scala b/akka-http-testkit/src/main/scala/akka/http/javadsl/testkit/TestRouteResult.scala similarity index 55% rename from akka-http-testkit/src/main/scala/akka/http/javadsl/testkit/TestResponse.scala rename to akka-http-testkit/src/main/scala/akka/http/javadsl/testkit/TestRouteResult.scala index ff256cb49c..876707e98c 100644 --- a/akka-http-testkit/src/main/scala/akka/http/javadsl/testkit/TestResponse.scala +++ b/akka-http-testkit/src/main/scala/akka/http/javadsl/testkit/TestRouteResult.scala @@ -4,26 +4,42 @@ package akka.http.javadsl.testkit +import akka.http.scaladsl.server.RouteResult + import scala.reflect.ClassTag import scala.concurrent.ExecutionContext +import scala.concurrent.Await import scala.concurrent.duration.FiniteDuration import akka.util.ByteString import akka.stream.Materializer import akka.http.scaladsl.unmarshalling.Unmarshal import akka.http.scaladsl.model.HttpResponse import akka.http.impl.util._ -import akka.http.impl.server.UnmarshallerImpl import akka.http.impl.util.JavaMapping.Implicits._ -import akka.http.javadsl.server.Unmarshaller +import akka.http.javadsl.RoutingJavaMapping._ +import akka.http.javadsl.server.{Rejection, Unmarshaller} import akka.http.javadsl.model._ +import scala.collection.JavaConverters._ +import scala.annotation.varargs /** - * A wrapper for responses. + * A wrapper for route results. * * To support the testkit API, a third-party testing library needs to implement this class and provide * implementations for the abstract assertion methods. */ -abstract class TestResponse(_response: HttpResponse, awaitAtMost: FiniteDuration)(implicit ec: ExecutionContext, materializer: Materializer) { +abstract class TestRouteResult(_result: RouteResult, awaitAtMost: FiniteDuration)(implicit ec: ExecutionContext, materializer: Materializer) { + + private def _response = _result match { + case RouteResult.Complete(r) ⇒ r + case RouteResult.Rejected(rejections) ⇒ doFail("Expected route to complete, but was instead rejected with " + rejections) + } + + private def _rejections = _result match { + case RouteResult.Complete(r) ⇒ doFail("Request was not rejected, response was " + r) + case RouteResult.Rejected(ex) ⇒ ex + } + /** * Returns the strictified entity of the response. It will be strictified on first access. */ @@ -35,9 +51,19 @@ abstract class TestResponse(_response: HttpResponse, awaitAtMost: FiniteDuration lazy val response: HttpResponse = _response.withEntity(entity) /** - * Returns the media-type of the response's content-type + * Returns the response's content-type */ - def mediaType: MediaType = extractFromResponse(_.entity.contentType.mediaType) + def contentType: ContentType = _response.entity.contentType + + /** + * Returns a string representation of the response's content-type + */ + def contentTypeString: String = contentType.toString + + /** + * Returns the media-type of the the response's content-type + */ + def mediaType: MediaType = contentType.mediaType /** * Returns a string representation of the media-type of the response's content-type @@ -52,15 +78,15 @@ abstract class TestResponse(_response: HttpResponse, awaitAtMost: FiniteDuration /** * Returns the entity of the response unmarshalled with the given ``Unmarshaller``. */ - def entityAs[T](unmarshaller: Unmarshaller[T]): T = - Unmarshal(response) - .to(unmarshaller.asInstanceOf[UnmarshallerImpl[T]].scalaUnmarshaller, ec, materializer) + def entity[T](unmarshaller: Unmarshaller[HttpEntity, T]): T = + Unmarshal(response.entity) + .to(unmarshaller.asScala, ec, materializer) .awaitResult(awaitAtMost) /** * Returns the entity of the response interpreted as an UTF-8 encoded string. */ - def entityAsString: String = entity.getData.utf8String + def entityString: String = entity.getData.utf8String /** * Returns the [[akka.http.javadsl.model.StatusCode]] of the response. @@ -69,7 +95,6 @@ abstract class TestResponse(_response: HttpResponse, awaitAtMost: FiniteDuration /** * Returns the numeric status code of the response. - * @return */ def statusCode: Int = response.status.intValue @@ -80,52 +105,80 @@ abstract class TestResponse(_response: HttpResponse, awaitAtMost: FiniteDuration response.header(ClassTag(clazz)) .getOrElse(doFail(s"Expected header of type ${clazz.getSimpleName} but wasn't found.")) + /** + * Expects the route to have been rejected, returning the list of rejections, or empty list if the route + * was rejected with an empty rejection list. + * Fails the test if the route completes with a response rather than having been rejected. + */ + def rejections: java.util.List[Rejection] = _rejections.map(_.asJava).asJava + + /** + * Expects the route to have been rejected with a single rejection. + * Fails the test if the route completes with a response, or is rejected with 0 or >1 rejections. + */ + def rejection: Rejection = { + val r = rejections + if (r.size == 1) r.get(0) else doFail("Expected a single rejection but got %s (%s)".format(r.size, r)) + } + /** * Assert on the numeric status code. */ - def assertStatusCode(expected: Int): TestResponse = + def assertStatusCode(expected: Int): TestRouteResult = assertStatusCode(StatusCodes.get(expected)) /** * Assert on the status code. */ - def assertStatusCode(expected: StatusCode): TestResponse = + def assertStatusCode(expected: StatusCode): TestRouteResult = assertEqualsKind(expected, status, "status code") /** * Assert on the media type of the response. */ - def assertMediaType(expected: String): TestResponse = + def assertMediaType(expected: String): TestRouteResult = assertEqualsKind(expected, mediaTypeString, "media type") /** * Assert on the media type of the response. */ - def assertMediaType(expected: MediaType): TestResponse = + def assertMediaType(expected: MediaType): TestRouteResult = assertEqualsKind(expected, mediaType, "media type") + /** + * Assert on the content type of the response. + */ + def assertContentType(expected: String): TestRouteResult = + assertEqualsKind(expected, contentTypeString, "content type") + + /** + * Assert on the content type of the response. + */ + def assertContentType(expected: ContentType): TestRouteResult = + assertEqualsKind(expected, contentType, "content type") + /** * Assert on the response entity to be a UTF8 representation of the given string. */ - def assertEntity(expected: String): TestResponse = - assertEqualsKind(expected, entityAsString, "entity") + def assertEntity(expected: String): TestRouteResult = + assertEqualsKind(expected, entityString, "entity") /** * Assert on the response entity to equal the given bytes. */ - def assertEntityBytes(expected: ByteString): TestResponse = + def assertEntityBytes(expected: ByteString): TestRouteResult = assertEqualsKind(expected, entityBytes, "entity") /** * Assert on the response entity to equal the given object after applying an [[akka.http.javadsl.server.Unmarshaller]]. */ - def assertEntityAs[T <: AnyRef](unmarshaller: Unmarshaller[T], expected: T): TestResponse = - assertEqualsKind(expected, entityAs(unmarshaller), "entity") + def assertEntityAs[T <: AnyRef](unmarshaller: Unmarshaller[HttpEntity,T], expected: T): TestRouteResult = + assertEqualsKind(expected, entity(unmarshaller), "entity") /** * Assert that a given header instance exists in the response. */ - def assertHeaderExists(expected: HttpHeader): TestResponse = { + def assertHeaderExists(expected: HttpHeader): TestRouteResult = { assertTrue(response.headers.exists(_ == expected), s"Header $expected was missing.") this } @@ -133,7 +186,7 @@ abstract class TestResponse(_response: HttpResponse, awaitAtMost: FiniteDuration /** * Assert that a header of the given type exists. */ - def assertHeaderKindExists(name: String): TestResponse = { + def assertHeaderKindExists(name: String): TestRouteResult = { val lowercased = name.toRootLowerCase assertTrue(response.headers.exists(_.is(lowercased)), s"Expected `$name` header was missing.") this @@ -142,7 +195,7 @@ abstract class TestResponse(_response: HttpResponse, awaitAtMost: FiniteDuration /** * Assert that a header of the given name and value exists. */ - def assertHeaderExists(name: String, value: String): TestResponse = { + def assertHeaderExists(name: String, value: String): TestRouteResult = { val lowercased = name.toRootLowerCase val headers = response.headers.filter(_.is(lowercased)) if (headers.isEmpty) fail(s"Expected `$name` header was missing.") @@ -151,16 +204,20 @@ abstract class TestResponse(_response: HttpResponse, awaitAtMost: FiniteDuration this } + + @varargs def assertRejections(expectedRejections: Rejection*): TestRouteResult = { + if (rejections.asScala == expectedRejections.toSeq) { + this + } else { + doFail(s"Expected rejections [${expectedRejections.mkString(",")}], but rejected with [${rejections.asScala.mkString(",")}] instead.") + } + } - private[this] def extractFromResponse[T](f: HttpResponse ⇒ T): T = - if (response eq null) doFail("Request didn't complete with response") - else f(response) - - protected def assertEqualsKind(expected: AnyRef, actual: AnyRef, kind: String): TestResponse = { + protected def assertEqualsKind(expected: AnyRef, actual: AnyRef, kind: String): TestRouteResult = { assertEquals(expected, actual, s"Unexpected $kind!") this } - protected def assertEqualsKind(expected: Int, actual: Int, kind: String): TestResponse = { + protected def assertEqualsKind(expected: Int, actual: Int, kind: String): TestRouteResult = { assertEquals(expected, actual, s"Unexpected $kind!") this } diff --git a/akka-http-tests/src/main/java/akka/http/javadsl/server/examples/petstore/Pet.java b/akka-http-tests/src/main/java/akka/http/javadsl/server/examples/petstore/Pet.java index 76b3ea95ce..c36617fa63 100644 --- a/akka-http-tests/src/main/java/akka/http/javadsl/server/examples/petstore/Pet.java +++ b/akka-http-tests/src/main/java/akka/http/javadsl/server/examples/petstore/Pet.java @@ -4,27 +4,25 @@ package akka.http.javadsl.server.examples.petstore; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + public class Pet { - private int id; - private String name; + private final int id; + private final String name; - private Pet(){} - public Pet(int id, String name) { - this.id = id; - this.name = name; - } + @JsonCreator + public Pet(@JsonProperty("id") int id, @JsonProperty("name") String name) { + this.id = id; + this.name = name; + } - public int getId() { - return id; - } - public void setId(int id) { - this.id = id; - } + public int getId() { + return id; + } + + public String getName() { + return name; + } - public String getName() { - return name; - } - public void setName(String name) { - this.name = name; - } } diff --git a/akka-http-tests/src/main/java/akka/http/javadsl/server/examples/petstore/PetStoreController.java b/akka-http-tests/src/main/java/akka/http/javadsl/server/examples/petstore/PetStoreController.java index 3d2222c723..4132901bf8 100644 --- a/akka-http-tests/src/main/java/akka/http/javadsl/server/examples/petstore/PetStoreController.java +++ b/akka-http-tests/src/main/java/akka/http/javadsl/server/examples/petstore/PetStoreController.java @@ -4,8 +4,10 @@ package akka.http.javadsl.server.examples.petstore; -import akka.http.javadsl.server.RequestContext; -import akka.http.javadsl.server.RouteResult; +import akka.http.javadsl.model.StatusCodes; +import static akka.http.javadsl.server.Directives.*; + +import akka.http.javadsl.server.Route; import java.util.Map; @@ -15,8 +17,9 @@ public class PetStoreController { public PetStoreController(Map dataStore) { this.dataStore = dataStore; } - public RouteResult deletePet(RequestContext ctx, int petId) { + + public Route deletePet(int petId) { dataStore.remove(petId); - return ctx.completeWithStatus(200); + return complete(StatusCodes.OK); } } diff --git a/akka-http-tests/src/main/java/akka/http/javadsl/server/examples/petstore/PetStoreExample.java b/akka-http-tests/src/main/java/akka/http/javadsl/server/examples/petstore/PetStoreExample.java index 6c2799a65f..3d7c13b96e 100644 --- a/akka-http-tests/src/main/java/akka/http/javadsl/server/examples/petstore/PetStoreExample.java +++ b/akka-http-tests/src/main/java/akka/http/javadsl/server/examples/petstore/PetStoreExample.java @@ -4,48 +4,71 @@ package akka.http.javadsl.server.examples.petstore; -import akka.actor.ActorSystem; -import akka.http.javadsl.marshallers.jackson.Jackson; -import akka.http.javadsl.server.*; -import akka.http.javadsl.server.values.PathMatcher; -import akka.http.javadsl.server.values.PathMatchers; +import static akka.http.javadsl.server.Directives.complete; +import static akka.http.javadsl.server.Directives.delete; +import static akka.http.javadsl.server.Directives.entity; +import static akka.http.javadsl.server.Directives.get; +import static akka.http.javadsl.server.Directives.getFromResource; +import static akka.http.javadsl.server.Directives.path; +import static akka.http.javadsl.server.Directives.pathPrefix; +import static akka.http.javadsl.server.Directives.put; +import static akka.http.javadsl.server.Directives.reject; +import static akka.http.javadsl.server.Directives.route; +import static akka.http.javadsl.server.StringUnmarshallers.INTEGER; import java.io.IOException; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; -import static akka.http.javadsl.server.Directives.*; +import akka.actor.ActorSystem; +import akka.http.javadsl.ConnectHttp; +import akka.http.javadsl.Http; +import akka.http.javadsl.marshallers.jackson.Jackson; +import akka.http.javadsl.model.StatusCodes; +import akka.http.javadsl.server.Route; +import akka.http.javadsl.server.examples.simple.SimpleServerApp; +import akka.stream.ActorMaterializer; public class PetStoreExample { - static PathMatcher petId = PathMatchers.intValue(); - static RequestVal petEntity = RequestVals.entityAs(Jackson.jsonAs(Pet.class)); + private static Route putPetHandler(Map pets, Pet thePet) { + pets.put(thePet.getId(), thePet); + return complete(StatusCodes.OK, thePet, Jackson.marshaller()); + } + public static Route appRoute(final Map pets) { PetStoreController controller = new PetStoreController(pets); - final RequestVal existingPet = RequestVals.lookupInMap(petId, Pet.class, pets); - - Handler1 putPetHandler = (Handler1) (ctx, thePet) -> { - pets.put(thePet.getId(), thePet); - return ctx.completeAs(Jackson.json(), thePet); + // Defined as Function in order to refere to [pets], but this could also be an ordinary method. + Function existingPet = petId -> { + Pet pet = pets.get(petId); + return (pet == null) ? reject() : complete(StatusCodes.OK, pet, Jackson.marshaller()); }; - + + // The directives here are statically imported, but you can also inherit from AllDirectives. return route( - path().route( + path("", () -> getFromResource("web/index.html") ), - path("pet", petId).route( - // demonstrates three different ways of handling requests: + pathPrefix("pet", () -> + path(INTEGER, petId -> route( + // demonstrates different ways of handling requests: - // 1. using a predefined route that completes with an extraction - get(extractAndComplete(Jackson.json(), existingPet)), + // 1. using a Function + get(() -> existingPet.apply(petId)), - // 2. using a handler - put(handleWith1(petEntity, putPetHandler)), - - // 3. calling a method of a controller instance reflectively - delete(handleReflectively(controller, "deletePet", petId)) + // 2. using a method + put(() -> + entity(Jackson.unmarshaller(Pet.class), thePet -> + putPetHandler(pets, thePet) + ) + ), + + // 3. calling a method of a controller instance + delete(() -> controller.deletePet(petId)) + )) ) ); } @@ -57,13 +80,14 @@ public class PetStoreExample { pets.put(0, dog); pets.put(1, cat); - ActorSystem system = ActorSystem.create(); - try { - HttpService.bindRoute("localhost", 8080, appRoute(pets), system); - System.out.println("Type RETURN to exit"); - System.in.read(); - } finally { - system.terminate(); - } + final ActorSystem system = ActorSystem.create(); + final ActorMaterializer materializer = ActorMaterializer.create(system); + + final ConnectHttp host = ConnectHttp.toHost("127.0.0.1"); + + Http.get(system).bindAndHandle(appRoute(pets).flow(system, materializer), host, materializer); + + System.console().readLine("Type RETURN to exit..."); + system.terminate(); } } \ No newline at end of file diff --git a/akka-http-tests/src/main/java/akka/http/javadsl/server/examples/simple/SimpleServerApp.java b/akka-http-tests/src/main/java/akka/http/javadsl/server/examples/simple/SimpleServerApp.java index c7e5db8221..7a50ca72c6 100644 --- a/akka-http-tests/src/main/java/akka/http/javadsl/server/examples/simple/SimpleServerApp.java +++ b/akka-http-tests/src/main/java/akka/http/javadsl/server/examples/simple/SimpleServerApp.java @@ -6,96 +6,89 @@ package akka.http.javadsl.server.examples.simple; //#https-http-app +import akka.NotUsed; +import static akka.http.javadsl.server.PathMatchers.segment; + import akka.actor.ActorSystem; +import akka.http.javadsl.ConnectHttp; import akka.http.javadsl.ConnectionContext; import akka.http.javadsl.Http; import akka.http.javadsl.HttpsConnectionContext; +import akka.http.javadsl.model.HttpRequest; +import akka.http.javadsl.model.HttpResponse; import akka.http.javadsl.server.*; -import akka.http.javadsl.server.values.Parameter; -import akka.http.javadsl.server.values.Parameters; -import akka.http.javadsl.server.values.PathMatcher; -import akka.http.javadsl.server.values.PathMatchers; -import com.typesafe.config.ConfigFactory; +import akka.stream.ActorMaterializer; +import akka.stream.javadsl.Flow; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLEngine; import javax.net.ssl.TrustManagerFactory; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.security.*; import java.security.cert.CertificateException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import java.util.concurrent.Executor; +import java.util.function.BiFunction; +import java.util.function.Function; -public class SimpleServerApp extends HttpApp { - static Parameter x = Parameters.intValue("x"); - static Parameter y = Parameters.intValue("y"); +import static akka.http.javadsl.server.PathMatchers.integerSegment; +import static akka.http.javadsl.server.Unmarshaller.entityToString; - static PathMatcher xSegment = PathMatchers.intValue(); - static PathMatcher ySegment = PathMatchers.intValue(); +public class SimpleServerApp extends AllDirectives { // or import Directives.* - static RequestVal bodyAsName = RequestVals.entityAs(Unmarshallers.String()); - - public static RouteResult multiply(RequestContext ctx, int x, int y) { + public Route multiply(int x, int y) { int result = x * y; - return ctx.complete(String.format("%d * %d = %d", x, y, result)); + return complete(String.format("%d * %d = %d", x, y, result)); } - public static CompletionStage multiplyAsync(final RequestContext ctx, final int x, final int y) { - return CompletableFuture.supplyAsync(() -> multiply(ctx, x, y), ctx.executionContext()); + public CompletionStage multiplyAsync(Executor ctx, int x, int y) { + return CompletableFuture.supplyAsync(() -> multiply(x, y), ctx); } - @Override public Route createRoute() { - Handler addHandler = new Handler() { - @Override - public RouteResult apply(RequestContext ctx) { - int xVal = x.get(ctx); - int yVal = y.get(ctx); - int result = xVal + yVal; - return ctx.complete(String.format("%d + %d = %d", xVal, yVal, result)); - } + Route addHandler = parameter(StringUnmarshallers.INTEGER, "x", x -> + parameter(StringUnmarshallers.INTEGER, "y", y -> { + int result = x + y; + return complete(String.format("%d + %d = %d", x, y, result)); + }) + ); + + BiFunction subtractHandler = (x, y) -> { + int result = x - y; + return complete(String.format("%d - %d = %d", x, y, result)); }; - Handler2 subtractHandler = new Handler2() { - public RouteResult apply(RequestContext ctx, Integer xVal, Integer yVal) { - int result = xVal - yVal; - return ctx.complete(String.format("%d - %d = %d", xVal, yVal, result)); - } - }; - Handler1 helloPostHandler = - new Handler1() { - @Override - public RouteResult apply(RequestContext ctx, String s) { - return ctx.complete("Hello " + s + "!"); - } - }; + return route( // matches the empty path - pathSingleSlash().route( + pathSingleSlash(() -> getFromResource("web/calculator.html") ), // matches paths like this: /add?x=42&y=23 - path("add").route( - handleWith(addHandler, x, y) - ), - path("subtract").route( - handleWith2(x, y, subtractHandler) + path("add", () -> addHandler), + path("subtract", () -> + parameter(StringUnmarshallers.INTEGER, "x", x -> + parameter(StringUnmarshallers.INTEGER, "y", y -> + subtractHandler.apply(x, y) + ) + ) ), // matches paths like this: /multiply/{x}/{y} - path("multiply", xSegment, ySegment).route( - // bind handler by reflection - handleReflectively(SimpleServerApp.class, "multiply", xSegment, ySegment) + path(PathMatchers.segment("multiply").slash(integerSegment()).slash(integerSegment()), + this::multiply ), - path("multiplyAsync", xSegment, ySegment).route( - // bind async handler by reflection - handleReflectively(SimpleServerApp.class, "multiplyAsync", xSegment, ySegment) + path(PathMatchers.segment("multiplyAsync").slash(integerSegment()).slash(integerSegment()), (x, y) -> + extractExecutionContext(ctx -> + onSuccess(() -> multiplyAsync(ctx, x, y), Function.identity()) + ) ), - post( - path("hello").route( - handleWith1(bodyAsName, helloPostHandler) + post(() -> + path("hello", () -> + entity(entityToString(), body -> + complete("Hello " + body + "!") + ) ) ) ); @@ -105,12 +98,16 @@ public class SimpleServerApp extends HttpApp { public static void main(String[] args) throws IOException { final ActorSystem system = ActorSystem.create("SimpleServerApp"); + final ActorMaterializer materializer = ActorMaterializer.create(system); final Http http = Http.get(system); boolean useHttps = false; // pick value from anywhere useHttps(system, http, useHttps); - new SimpleServerApp().bindRoute("localhost", 8080, system); + final SimpleServerApp app = new SimpleServerApp(); + final Flow flow = app.createRoute().flow(system, materializer); + + Http.get(system).bindAndHandle(flow, ConnectHttp.toHost("localhost", 8080), materializer); System.out.println("Type RETURN to exit"); System.in.read(); @@ -157,4 +154,4 @@ public class SimpleServerApp extends HttpApp { } } -//# \ No newline at end of file +//# diff --git a/akka-http-tests/src/main/java/akka/http/javadsl/server/examples/simple/SimpleServerApp8.java b/akka-http-tests/src/main/java/akka/http/javadsl/server/examples/simple/SimpleServerApp8.java deleted file mode 100644 index dff50a8dc0..0000000000 --- a/akka-http-tests/src/main/java/akka/http/javadsl/server/examples/simple/SimpleServerApp8.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.javadsl.server.examples.simple; - -import akka.actor.ActorSystem; -import akka.http.javadsl.server.*; -import akka.http.javadsl.server.values.*; - -import java.io.IOException; - -public class SimpleServerApp8 extends HttpApp { - static Parameter x = Parameters.intValue("x"); - static Parameter y = Parameters.intValue("y"); - - static PathMatcher xSegment = PathMatchers.intValue(); - static PathMatcher ySegment = PathMatchers.intValue(); - - public static RouteResult multiply(RequestContext ctx, int x, int y) { - int result = x * y; - return ctx.complete(String.format("%d * %d = %d", x, y, result)); - } - - static class Test { - int constant; - Test(int constant) { - this.constant = constant; - } - RouteResult constantPlusMultiply(RequestContext ctx, int x, int y) { - int result = x * y + constant; - return ctx.complete(String.format("%d * %d + %d = %d", x, y, constant, result)); - } - } - - public void test() { - handleWith2(xSegment, ySegment, SimpleServerApp8::multiply); - } - - @Override - public Route createRoute() { - Handler addHandler = new Handler() { - static final long serialVersionUID = 1L; - @Override - public RouteResult apply(RequestContext ctx) { - int xVal = x.get(ctx); - int yVal = y.get(ctx); - int result = xVal + yVal; - return ctx.complete(String.format("%d + %d = %d", xVal, yVal, result)); - } - }; - Handler2 subtractHandler = new Handler2() { - static final long serialVersionUID = 1L; - public RouteResult apply(RequestContext ctx, Integer xVal, Integer yVal) { - int result = xVal - yVal; - return ctx.complete(String.format("%d - %d = %d", xVal, yVal, result)); - } - }; - - return - route( - // matches the empty path - pathSingleSlash().route( - getFromResource("web/calculator.html") - ), - // matches paths like this: /add?x=42&y=23 - path("add").route( - handleWith(addHandler) - ), - path("subtract").route( - handleWith2(x, y, subtractHandler) - ), - path("divide").route( - handleWith2(x, y, - (ctx, x, y) -> - ctx.complete(String.format("%d / %d = %d", x, y, x / y)) - ) - ), - // matches paths like this: /multiply/{x}/{y} - path("multiply", xSegment, ySegment).route( - // bind handler by reflection - handleWith2(xSegment, ySegment, SimpleServerApp8::multiply) - ), - path("multiply-methodref", xSegment, ySegment).route( - // bind handler by reference to new instance of handler - handleWith2(xSegment, ySegment, new Test(123)::constantPlusMultiply) - ) - ); - } - - public static void main(String[] args) throws IOException { - ActorSystem system = ActorSystem.create(); - new SimpleServerApp8().bindRoute("localhost", 8080, system); - System.out.println("Type RETURN to exit"); - System.in.read(); - system.terminate(); - } -} - diff --git a/akka-http-tests/src/test/java/akka/http/javadsl/client/HttpAPIsTest.java b/akka-http-tests/src/test/java/akka/http/javadsl/HttpAPIsTest.java similarity index 97% rename from akka-http-tests/src/test/java/akka/http/javadsl/client/HttpAPIsTest.java rename to akka-http-tests/src/test/java/akka/http/javadsl/HttpAPIsTest.java index 40b115e2cf..27765b7c5f 100644 --- a/akka-http-tests/src/test/java/akka/http/javadsl/client/HttpAPIsTest.java +++ b/akka-http-tests/src/test/java/akka/http/javadsl/HttpAPIsTest.java @@ -2,7 +2,7 @@ * Copyright (C) 2009-2016 Lightbend Inc. */ -package akka.http.javadsl.client; +package akka.http.javadsl; import akka.event.LoggingAdapter; import akka.http.javadsl.*; @@ -15,17 +15,18 @@ import akka.stream.javadsl.Flow; import org.junit.Test; import javax.net.ssl.SSLContext; - -import static akka.http.javadsl.ConnectHttp.*; -import static akka.http.javadsl.ConnectHttp.toHostHttps; - import java.util.concurrent.CompletionStage; +import static akka.http.javadsl.ConnectHttp.toHost; +import static akka.http.javadsl.ConnectHttp.toHostHttps; + @SuppressWarnings("ConstantConditions") public class HttpAPIsTest extends JUnitRouteTest { @Test - public void compileOnlyTest() {} + public void placeholderCompileTimeOnlyTest() { + // fails if there are no test cases + } @SuppressWarnings("unused") public void compileOnly() throws Exception { diff --git a/akka-http-tests/src/test/java/akka/http/javadsl/server/CompleteTest.java b/akka-http-tests/src/test/java/akka/http/javadsl/server/CompleteTest.java index 45728a2e32..4e6b873cdc 100644 --- a/akka-http-tests/src/test/java/akka/http/javadsl/server/CompleteTest.java +++ b/akka-http-tests/src/test/java/akka/http/javadsl/server/CompleteTest.java @@ -4,16 +4,17 @@ package akka.http.javadsl.server; -import org.junit.Test; +import static akka.http.javadsl.server.StringUnmarshallers.INTEGER; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; -import akka.http.javadsl.testkit.*; +import org.junit.Test; import akka.http.javadsl.marshallers.jackson.Jackson; import akka.http.javadsl.model.HttpRequest; import akka.http.javadsl.model.MediaTypes; -import akka.http.javadsl.server.values.*; +import akka.http.javadsl.testkit.JUnitRouteTest; public class CompleteTest extends JUnitRouteTest { @Test @@ -30,12 +31,14 @@ public class CompleteTest extends JUnitRouteTest { @Test public void completeAsJacksonJson() { + + @SuppressWarnings("unused") // The getters are used reflectively by Jackson class Person { public String getFirstName() { return "Peter"; } public String getLastName() { return "Parker"; } public int getAge() { return 138; } } - Route route = completeAs(Jackson.json(), new Person()); + Route route = completeOK(new Person(), Jackson.marshaller()); HttpRequest request = HttpRequest.create(); @@ -44,22 +47,23 @@ public class CompleteTest extends JUnitRouteTest { .assertMediaType("application/json") .assertEntity("{\"age\":138,\"firstName\":\"Peter\",\"lastName\":\"Parker\"}"); } + + private CompletionStage doSlowCalculation(int x, int y) { + return CompletableFuture.supplyAsync(() -> { + int result = x + y; + return String.format("%d + %d = %d",x, y, result); + }); + } + @Test public void completeWithFuture() { - Parameter x = Parameters.intValue("x"); - Parameter y = Parameters.intValue("y"); + Route route = + parameter(INTEGER, "x", x -> + parameter(INTEGER, "y", y -> + onSuccess(() -> doSlowCalculation(x, y), Directives::complete) + ) + ); - Handler2 slowCalc = new Handler2() { - @Override - public RouteResult apply(final RequestContext ctx, final Integer x, final Integer y) { - return ctx.completeWith(CompletableFuture.supplyAsync(() -> { - int result = x + y; - return ctx.complete(String.format("%d + %d = %d",x, y, result)); - }, ctx.executionContext())); - } - }; - - Route route = handleWith2(x, y, slowCalc); runRoute(route, HttpRequest.GET("add?x=42&y=23")) .assertStatusCode(200) .assertEntity("42 + 23 = 65"); diff --git a/akka-http-tests/src/test/java/akka/http/javadsl/server/HandlerBindingTest.java b/akka-http-tests/src/test/java/akka/http/javadsl/server/HandlerBindingTest.java index 6723d30fd8..3c8c60eef2 100644 --- a/akka-http-tests/src/test/java/akka/http/javadsl/server/HandlerBindingTest.java +++ b/akka-http-tests/src/test/java/akka/http/javadsl/server/HandlerBindingTest.java @@ -4,79 +4,47 @@ package akka.http.javadsl.server; -import akka.http.scaladsl.model.HttpRequest; +import static akka.http.javadsl.server.StringUnmarshallers.INTEGER; + import org.junit.Test; -import akka.http.javadsl.testkit.*; -import akka.http.javadsl.server.values.*; + +import akka.http.javadsl.testkit.JUnitRouteTest; +import akka.http.javadsl.testkit.TestRouteResult; +import akka.http.scaladsl.model.HttpRequest; public class HandlerBindingTest extends JUnitRouteTest { - Parameter aParam = Parameters.intValue("a"); - Parameter bParam = Parameters.intValue("b"); - Parameter cParam = Parameters.intValue("c"); - Parameter dParam = Parameters.intValue("d"); @Test public void testHandlerWithoutExtractions() { - Route route = handleWith(ctx -> ctx.complete("Ok")); - TestResponse response = runRoute(route, HttpRequest.GET("/")); + Route route = complete("Ok"); + TestRouteResult response = runRoute(route, HttpRequest.GET("/")); response.assertEntity("Ok"); } @Test public void testHandler1() { - Route route = handleWith1(aParam, (ctx, a) -> ctx.complete("Ok " + a)); - TestResponse response = runRoute(route, HttpRequest.GET("?a=23")); + Route route = parameter("a", a -> complete("Ok " + a)); + TestRouteResult response = runRoute(route, HttpRequest.GET("?a=23")); response.assertStatusCode(200); response.assertEntity("Ok 23"); } @Test public void testHandler2() { - Route route = - handleWith2( - aParam, - bParam, - (ctx, a, b) -> ctx.complete("Sum: " + (a + b))); - TestResponse response = runRoute(route, HttpRequest.GET("?a=23&b=42")); + Route route = parameter(INTEGER, "a", a -> parameter(INTEGER, "b", b -> complete("Sum: " + (a + b)))); + TestRouteResult response = runRoute(route, HttpRequest.GET("?a=23&b=42")); response.assertStatusCode(200); response.assertEntity("Sum: 65"); } - @Test - public void testHandler3() { - Route route = - handleWith3( - aParam, - bParam, - cParam, - (ctx, a, b, c) -> ctx.complete("Sum: " + (a + b + c))); - TestResponse response = runRoute(route, HttpRequest.GET("?a=23&b=42&c=30")); - response.assertStatusCode(200); - response.assertEntity("Sum: 95"); + + public Route sum(int a, int b, int c, int d) { + return complete("Sum: " + (a + b + c + d)); } @Test - public void testHandler4() { - Route route = - handleWith4( - aParam, - bParam, - cParam, - dParam, - (ctx, a, b, c, d) -> ctx.complete("Sum: " + (a + b + c + d))); - TestResponse response = runRoute(route, HttpRequest.GET("?a=23&b=42&c=30&d=45")); - response.assertStatusCode(200); - response.assertEntity("Sum: 140"); - } - public RouteResult sum(RequestContext ctx, int a, int b, int c, int d) { - return ctx.complete("Sum: "+(a + b + c + d)); - } - @Test - public void testHandler4MethodRef() { - Route route = - handleWith4( - aParam, - bParam, - cParam, - dParam, - this::sum); - TestResponse response = runRoute(route, HttpRequest.GET("?a=23&b=42&c=30&d=45")); + public void testHandlerMethod() { + Route route = parameter(INTEGER, "a", a -> + parameter(INTEGER, "b", b -> + parameter(INTEGER, "c", c -> + parameter(INTEGER, "d", d -> sum(a,b,c,d))))); + TestRouteResult response = runRoute(route, HttpRequest.GET("?a=23&b=42&c=30&d=45")); response.assertStatusCode(200); response.assertEntity("Sum: 140"); } diff --git a/akka-http-tests/src/test/java/akka/http/javadsl/server/JavaRouteTest.java b/akka-http-tests/src/test/java/akka/http/javadsl/server/JavaRouteTest.java new file mode 100644 index 0000000000..74473124c3 --- /dev/null +++ b/akka-http-tests/src/test/java/akka/http/javadsl/server/JavaRouteTest.java @@ -0,0 +1,335 @@ +/* + * Copyright (C) 2009-2016 Typesafe Inc. + */ +package akka.http.javadsl.server; + +import static akka.http.javadsl.server.PathMatchers.*; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import akka.http.javadsl.model.*; +import akka.http.javadsl.model.headers.Accept; +import akka.http.javadsl.model.headers.RawHeader; +import org.junit.Test; + +import akka.http.javadsl.testkit.JUnitRouteTest; +//FIXME discuss how to provide a javadsl.CustomHeader where render() is either pre-implemented or trivial to write in Java +import akka.http.scaladsl.model.headers.CustomHeader; +import akka.japi.pf.PFBuilder; +import akka.util.ByteString; + +public class JavaRouteTest extends JUnitRouteTest { + private final Route route = getRoute(); + private static final Unmarshaller BIG_DECIMAL_PARAM = + Unmarshaller.sync(BigDecimal::new); + + private final Unmarshaller BIG_DECIMAL_BODY = + Unmarshaller.entityToString().thenApply(BigDecimal::new); + + private final Unmarshaller UUID_FROM_JSON_BODY = + Unmarshaller.forMediaType(MediaTypes.APPLICATION_JSON, + Unmarshaller.entityToString()) + .thenApply(s -> { + // just a fake JSON parser, assuming it's {"id":"..."} + // A real implementation could easily invoke Jackson here instead. + Pattern regex = Pattern.compile("\"id\":\"(.+)\""); + Matcher matcher = regex.matcher(s); + matcher.find(); + return UUID.fromString(matcher.group(1)); + }); + + private final Unmarshaller UUID_FROM_XML_BODY = + Unmarshaller.forMediaTypes(Arrays.asList(MediaTypes.TEXT_XML, MediaTypes.APPLICATION_XML), + Unmarshaller.entityToString()) + .thenApply(s -> { + // just a fake XML parser, assuming it's ... + // A real implementation could easily invoke JAXB here instead. + Pattern regex = Pattern.compile("(.+)"); + Matcher matcher = regex.matcher(s); + matcher.find(); + return UUID.fromString(matcher.group(1)); + }); + + private final Unmarshaller UUID_FROM_BODY = + Unmarshaller.firstOf(UUID_FROM_JSON_BODY, UUID_FROM_XML_BODY); + + private final Marshaller UUID_TO_JSON = Marshaller.wrapEntity( + (UUID u) -> "{\"id\":\"" + u + "\"}", + Marshaller.stringToEntity(), + MediaTypes.APPLICATION_JSON + ); + + private Marshaller UUID_TO_XML(ContentType xmlType) { + return Marshaller.byteStringMarshaller(xmlType).compose( + (UUID u) -> ByteString.fromString("" + u + "")); + } + + private final Marshaller UUID_TO_ENTITY = + Marshaller.oneOf( + UUID_TO_JSON, + UUID_TO_XML(MediaTypes.APPLICATION_XML.toContentType(HttpCharsets.UTF_8)), + UUID_TO_XML(MediaTypes.TEXT_XML.toContentType(HttpCharsets.UTF_8)) + ); + + private static boolean isUUID(String s) { + try { + UUID.fromString(s); + return true; + } catch (IllegalArgumentException x) { + return false; + } + } + + private Route uuidHeaderValue(Function inner) { + return headerValueByName("UUID", value -> { + return isUUID(value) ? inner.apply(new UUIDHeader(UUID.fromString(value))) + : reject(Rejections.malformedHeader("UUID", "must be a valid UUID")); + }); + } + + private class UUIDHeader extends CustomHeader { + private final UUID value; + + public UUIDHeader(UUID value) { + this.value = value; + } + + @Override + public String name() { + return "UUID"; + } + + @Override + public String value() { + return value.toString(); + } + + public UUID uuid() { + return value; + } + + @Override + public boolean renderInRequests() { + return true; + } + + @Override + public boolean renderInResponses() { + return true; + } + } + + @Test + public void pathCanMatchUuid() { + runRoute(route, HttpRequest.GET("/documents/359e4920-a6a2-4614-9355-113165d600fb")) + .assertEntity("document 359e4920-a6a2-4614-9355-113165d600fb"); + } + + @Test + public void pathCanMatchElement() { + runRoute(route, HttpRequest.GET("/people/john")) + .assertEntity("person john"); + } + + @Test + public void paramIsExtracted() { + runRoute(route, HttpRequest.GET("/cookies?amount=5")) + .assertEntity("cookies 5"); + } + + @Test + public void requiredParamCausesRejectionWhenMissing() { + // The rejection on "amount" appears twice because we have two route alternatives both requiring it. + runRouteUnSealed(route, HttpRequest.GET("/cookies")) + .assertRejections(Rejections.missingQueryParam("amount"), Rejections.missingQueryParam("amount")); + } + + @Test + public void wrongParamTypeCausesNextRouteToBeEvaluated() { + runRoute(route, HttpRequest.GET("/cookies?amount=one")) + .assertEntity("cookies (string) one"); + } + + @Test + public void requiredParamCauses404OnSealedRoute() { + runRoute(route, HttpRequest.GET("/cookies")) + .assertStatusCode(StatusCodes.NOT_FOUND) + .assertEntity("Request is missing required query parameter 'amount'"); + } + + @Test + public void customParamTypeCanBeExtracted() { + runRoute(route, HttpRequest.GET("/cakes?amount=5")) + .assertEntity("cakes 5"); + } + + @Test + public void entityCanBeUnmarshalled() { + runRoute(route, HttpRequest.POST("/bigdecimal").withEntity("1234")) + .assertEntity("body 1234"); + } + + @Test + public void entityCanBeUnmarshalledWhenPickingJsonUnmarshaller() { + runRoute(route, HttpRequest + .PUT("/uuid") + .withEntity(ContentTypes.create(MediaTypes.APPLICATION_JSON), + "{\"id\":\"76b38659-1dec-4ee6-86d0-9ca787bf578c\"}")) + .assertEntity("uuid 76b38659-1dec-4ee6-86d0-9ca787bf578c"); + } + + @Test + public void entityCanBeUnmarshalledWhenPickingXmlUnmarshaller() { + runRoute(route, HttpRequest + .PUT("/uuid") + .withEntity( + ContentTypes.create(MediaTypes.APPLICATION_XML, HttpCharsets.UTF_8), + "76b38659-1dec-4ee6-86d0-9ca787bf578c")) + .assertEntity("uuid 76b38659-1dec-4ee6-86d0-9ca787bf578c"); + } + + @Test + public void entityCanBeMarshalledWhenJsonIsAccepted() { + runRoute(route, HttpRequest.GET("/uuid").addHeader(Accept.create(MediaRanges.create(MediaTypes.APPLICATION_JSON)))) + .assertEntity("{\"id\":\"80a05eee-652e-4458-9bee-19b69dbe1dee\"}") + .assertContentType(MediaTypes.APPLICATION_JSON.toContentType()); + } + + @Test + public void entityCanBeMarshalledWhenXmlIsAccepted() { + runRoute(route, HttpRequest.GET("/uuid").addHeader(Accept.create(MediaRanges.create(MediaTypes.TEXT_XML)))) + .assertEntity("80a05eee-652e-4458-9bee-19b69dbe1dee") + .assertContentType(MediaTypes.TEXT_XML.toContentType(HttpCharsets.UTF_8)); + } + + @Test + public void requestIsRejectedIfNoMarshallerFitsAcceptedType() { + runRoute(route, HttpRequest.GET("/uuid").addHeader(Accept.create(MediaRanges.create(MediaTypes.TEXT_PLAIN)))) + .assertStatusCode(StatusCodes.NOT_ACCEPTABLE); + } + + @Test + public void firstMarshallerIsPickedAndStatusCodeAppliedIfNoAcceptHeaderPresent() { + runRoute(route, HttpRequest.GET("/uuid")) + .assertContentType(MediaTypes.APPLICATION_JSON.toContentType()) + .assertStatusCode(StatusCodes.FOUND); + } + + @Test + public void exceptionHandlersAreAppliedEvenIfTheRouteThrowsInFuture() { + runRoute(route, HttpRequest.GET("/shouldnotfail")) + .assertEntity("no problem!"); + } + + @Test + public void routeWithRequiredHeaderFailsWhenHeaderIsAbsent() { + runRouteUnSealed(route, HttpRequest.GET("/requiredheader")) + .assertRejections(Rejections.missingHeader("UUID")); + } + + @Test + public void routeWithRequiredHeaderFailsWhenHeaderIsMalformed() { + runRouteUnSealed(route, HttpRequest.GET("/requiredheader").addHeader(RawHeader.create("UUID", "monkeys"))) + .assertRejections(Rejections.malformedHeader("UUID", "must be a valid UUID")); + } + + @Test + public void routeWithRequiredHeaderSucceedsWhenHeaderIsPresentAsRawHeader() { + runRoute(route, HttpRequest.GET("/requiredheader").addHeader(RawHeader.create("UUID", "98610fcb-7b19-4639-8dfa-08db8ac19320"))) + .assertEntity("has header: 98610fcb-7b19-4639-8dfa-08db8ac19320"); + } + + @Test + public void routeWithRequiredHeaderSucceedsWhenHeaderIsPresentAsCustomType() { + runRoute(route, HttpRequest.GET("/requiredheader").addHeader(new UUIDHeader(UUID.fromString("98610fcb-7b19-4639-8dfa-08db8ac19320")))) + .assertEntity("has header: 98610fcb-7b19-4639-8dfa-08db8ac19320"); + } + + private final ExceptionHandler xHandler = ExceptionHandler.of(new PFBuilder() + .match(IllegalArgumentException.class, x -> complete("no problem!")) + .build()); + + private CompletionStage throwExceptionInFuture() { + return CompletableFuture.supplyAsync(() -> { + throw new IllegalArgumentException("always failing"); + }); + } + + + public Route getRoute() { + return route( + path(segment("hello").slash("world"), () -> + complete("hello, world") + ), + path(segment("number").slash(integerSegment()), i -> + complete("you said: " + (i * i)) + ), + pathPrefix("documents", () -> + path(uuidSegment(), id -> + complete("document " + id) + ) + ), + pathPrefix("people", () -> + path(name -> + complete("person " + name) + ) + ), + path("notreally", () -> + reject(Rejections.missingFormField("always failing")) + ), + path("shouldnotfail", () -> + handleExceptions(xHandler, () -> + onSuccess(() -> throwExceptionInFuture(), value -> + complete("never reaches here") + ) + ) + ), + path("cookies", () -> + parameter(StringUnmarshallers.INTEGER, "amount", amount -> + complete("cookies " + amount) + ) + ), + path("cookies", () -> + parameter("amount", (String amount) -> + complete("cookies (string) " + amount) + ) + ), + path("custom_response", () -> + complete(HttpResponse.create().withStatus(StatusCodes.ACCEPTED)) + ), + path("bigdecimal", () -> + entity(BIG_DECIMAL_BODY, value -> + complete("body " + value) + ) + ), + path("uuid", () -> route( + put(() -> + entity(UUID_FROM_BODY, value -> + complete("uuid " + value) + ) + ), + get(() -> { + UUID id = UUID.fromString("80a05eee-652e-4458-9bee-19b69dbe1dee"); + return complete(StatusCodes.FOUND, id, UUID_TO_ENTITY); + }) + )), + path("cakes", () -> + parameter(BIG_DECIMAL_PARAM, "amount", amount -> + complete("cakes " + amount) + ) + ), + path("requiredheader", () -> + uuidHeaderValue(h -> + complete("has header: " + h.uuid()) + ) + ) + ); + } +} \ No newline at end of file diff --git a/akka-http-tests/src/test/java/akka/http/javadsl/server/JavaTestServer.java b/akka-http-tests/src/test/java/akka/http/javadsl/server/JavaTestServer.java new file mode 100644 index 0000000000..171d5c5cdb --- /dev/null +++ b/akka-http-tests/src/test/java/akka/http/javadsl/server/JavaTestServer.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2009-2016 Lightbend Inc. + */ +package akka.http.javadsl.server; + +import akka.actor.ActorSystem; +import akka.http.javadsl.ConnectHttp; +import akka.http.javadsl.Http; +import akka.http.javadsl.ServerBinding; +import akka.http.javadsl.model.HttpRequest; +import akka.http.javadsl.model.HttpResponse; +import akka.http.javadsl.model.StatusCodes; +import akka.stream.ActorMaterializer; +import akka.stream.javadsl.Flow; +import scala.concurrent.duration.Duration; +import scala.runtime.BoxedUnit; + +import java.util.Optional; +import java.util.concurrent.*; +import java.util.function.Function; + +public class JavaTestServer extends AllDirectives { // or import static Directives.*; + + public Route createRoute() { + final Duration timeout = Duration.create(1, TimeUnit.SECONDS); + + final Route index = path("", () -> + withRequestTimeout(timeout, this::mkTimeoutResponse, () -> { + silentSleep(5000); // too long, trigger failure + return complete(index()); + }) + ); + + final Function, Optional> handleAuth = (maybeCreds) -> { + if (maybeCreds.isPresent() && maybeCreds.get().verify("pa$$word")) // some secure hash + check + return Optional.of(maybeCreds.get().identifier()); + else return Optional.empty(); + }; + + final Route secure = path("secure", () -> + authenticateBasic("My basic secure site", handleAuth, (login) -> + complete(String.format("Hello, %s!", login)) + ) + ); + + final Route ping = path("ping", () -> + complete("PONG!") + ); + + final Route crash = path("crash", () -> + path("scala", () -> completeWithFutureString(akka.dispatch.Futures.failed(new Exception("Boom!")))).orElse( + path("java", () -> completeWithFutureString(CompletableFuture.supplyAsync(() -> { throw new RuntimeException("Boom!"); })))) + ); + + final Route inner = path("inner", () -> + getFromResourceDirectory("someDir") + ); + + + return get(() -> + index.orElse(secure).orElse(ping).orElse(crash).orElse(inner) + ); + } + + private void silentSleep(int millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + private HttpResponse mkTimeoutResponse(HttpRequest request) { + return HttpResponse.create() + .withStatus(StatusCodes.ENHANCE_YOUR_CALM) + .withEntity("Unable to serve response within time limit, please enchance your calm."); + } + + private String index() { + return " \n" + + " \n" + + "

Say hello to akka-http-core!

\n" + + "

Defined resources:

\n" + + "
    \n" + + "
  • /ping
  • \n" + + "
  • /secure Use any username and '<username>-password' as credentials
  • \n" + + "
  • /crash
  • \n" + + "
\n" + + " \n" + + " \n"; + } + + public static void main(String[] args) throws InterruptedException { + final JavaTestServer server = new JavaTestServer(); + server.run(); + } + + private void run() throws InterruptedException { + final ActorSystem system = ActorSystem.create(); + final ActorMaterializer mat = ActorMaterializer.create(system); + + final Flow flow = createRoute().flow(system, mat); + final CompletionStage binding = + Http.get(system).bindAndHandle(flow, ConnectHttp.toHost("127.0.0.1"), mat); + + System.console().readLine("Press [ENTER] to quit..."); + shutdown(binding); + } + + private CompletionStage shutdown(CompletionStage binding) { + return binding.thenAccept(b -> { + System.out.println(String.format("Unbinding from %s", b.localAddress())); + + final CompletionStage unbound = b.unbind(); + try { + unbound.toCompletableFuture().get(3, TimeUnit.SECONDS); // block... + } catch (TimeoutException | InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + }); + } +} \ No newline at end of file diff --git a/akka-http-tests/src/test/java/akka/http/javadsl/server/MarshallerTest.java b/akka-http-tests/src/test/java/akka/http/javadsl/server/MarshallerTest.java index 1b8e591c65..6cdbd522bc 100644 --- a/akka-http-tests/src/test/java/akka/http/javadsl/server/MarshallerTest.java +++ b/akka-http-tests/src/test/java/akka/http/javadsl/server/MarshallerTest.java @@ -4,205 +4,190 @@ package akka.http.javadsl.server; + +import java.util.function.Function; + import akka.http.javadsl.model.*; -import akka.http.javadsl.model.headers.Accept; -import akka.http.javadsl.model.headers.AcceptCharset; -import akka.http.javadsl.server.values.Parameters; -import akka.http.javadsl.testkit.JUnitRouteTest; -import akka.http.javadsl.testkit.TestRoute; -import akka.japi.function.Function; -import akka.util.ByteString; +import akka.http.javadsl.model.headers.*; import org.junit.Test; +import akka.http.javadsl.testkit.JUnitRouteTest; +import akka.http.javadsl.testkit.TestRoute; +import akka.util.ByteString; + public class MarshallerTest extends JUnitRouteTest { - RequestVal n = Parameters.intValue("n"); - @Test - public void testCustomToStringMarshaller() { - final Marshaller numberAsNameMarshaller = - Marshallers.toEntityString(MediaTypes.TEXT_X_SPEECH, new Function() { - @Override - public String apply(Integer param) throws Exception { - switch(param) { - case 0: return "null"; - case 1: return "eins"; - case 2: return "zwei"; - case 3: return "drei"; - case 4: return "vier"; - case 5: return "fünf"; - default: return "wat?"; - } - } - }); + @Test + public void testCustomToStringMarshaller() { + final Marshaller numberAsNameMarshaller = + Marshaller.wrapEntity((Integer param) -> { + switch (param) { + case 0: + return "null"; + case 1: + return "eins"; + case 2: + return "zwei"; + case 3: + return "drei"; + case 4: + return "vier"; + case 5: + return "fünf"; + default: + return "wat?"; + } + }, Marshaller.stringToEntity(), MediaTypes.TEXT_X_SPEECH); - Handler1 nummerHandler = new Handler1() { - @Override - public RouteResult apply(RequestContext ctx, Integer integer) { - return ctx.completeAs(numberAsNameMarshaller, integer); - } - }; - TestRoute route = - testRoute( - get( - path("nummer").route( - handleWith1(n, nummerHandler) - ) - ) - ); + final Function nummerHandler = integer -> completeOK(integer, numberAsNameMarshaller); - route.run(HttpRequest.GET("/nummer?n=1")) - .assertStatusCode(200) - .assertMediaType(MediaTypes.TEXT_X_SPEECH) - .assertEntity("eins"); + TestRoute route = + testRoute( + get(() -> + path("nummer", () -> + parameter(StringUnmarshallers.INTEGER, "n", nummerHandler) + ) + ) + ); - route.run(HttpRequest.GET("/nummer?n=6")) - .assertStatusCode(200) - .assertMediaType(MediaTypes.TEXT_X_SPEECH) - .assertEntity("wat?"); + route.run(HttpRequest.GET("/nummer?n=1")) + .assertStatusCode(StatusCodes.OK) + .assertMediaType(MediaTypes.TEXT_X_SPEECH) + .assertEntity("eins"); - route.run(HttpRequest.GET("/nummer?n=5")) - .assertStatusCode(200) - .assertEntityBytes(ByteString.fromString("fünf", "utf8")); + route.run(HttpRequest.GET("/nummer?n=6")) + .assertStatusCode(StatusCodes.OK) + .assertMediaType(MediaTypes.TEXT_X_SPEECH) + .assertEntity("wat?"); - route.run( - HttpRequest.GET("/nummer?n=5") - .addHeader(AcceptCharset.create(HttpCharsets.ISO_8859_1.toRange()))) - .assertStatusCode(200) - .assertEntityBytes(ByteString.fromString("fünf", "ISO-8859-1")); - } + route.run(HttpRequest.GET("/nummer?n=5")) + .assertStatusCode(StatusCodes.OK) + .assertEntityBytes(ByteString.fromString("fünf", "utf8")); - @Test - public void testCustomToByteStringMarshaller() { - final Marshaller numberAsJsonListMarshaller = - Marshallers.toEntityByteString(MediaTypes.APPLICATION_JSON.toContentType(), new Function() { - @Override - public ByteString apply(Integer param) throws Exception { - switch(param) { - case 1: return ByteString.fromString("[1]"); - case 5: return ByteString.fromString("[1,2,3,4,5]"); - default: return ByteString.fromString("[]"); - } - } - }); + route.run( + HttpRequest.GET("/nummer?n=5") + .addHeader(AcceptCharset.create(HttpCharsets.ISO_8859_1.toRange()))) + .assertStatusCode(StatusCodes.OK) + .assertEntityBytes(ByteString.fromString("fünf", "ISO-8859-1")); + } - Handler1 nummerHandler = new Handler1() { - @Override - public RouteResult apply(RequestContext ctx, Integer integer) { - return ctx.completeAs(numberAsJsonListMarshaller, integer); - } - }; + @Test + public void testCustomToByteStringMarshaller() { + final Marshaller numberAsJsonListMarshaller = + Marshaller.wrapEntity((Integer param) -> { + switch (param) { + case 1: + return ByteString.fromString("[1]"); + case 5: + return ByteString.fromString("[1,2,3,4,5]"); + default: + return ByteString.fromString("[]"); + } + }, Marshaller.byteStringToEntity(), MediaTypes.APPLICATION_JSON); - TestRoute route = - testRoute( - get( - path("nummer").route( - handleWith1(n, nummerHandler) - ) - ) - ); + final Function nummerHandler = integer -> completeOK(integer, numberAsJsonListMarshaller); - route.run(HttpRequest.GET("/nummer?n=1")) - .assertStatusCode(200) - .assertMediaType(MediaTypes.APPLICATION_JSON) - .assertEntity("[1]"); + TestRoute route = + testRoute( + get(() -> + path("nummer", () -> + parameter(StringUnmarshallers.INTEGER, "n", nummerHandler) + ) + ) + ); - route.run(HttpRequest.GET("/nummer?n=5")) - .assertStatusCode(200) - .assertEntity("[1,2,3,4,5]"); + route.run(HttpRequest.GET("/nummer?n=1")) + .assertStatusCode(StatusCodes.OK) + .assertMediaType(MediaTypes.APPLICATION_JSON) + .assertEntity("[1]"); - route.run( - HttpRequest.GET("/nummer?n=5").addHeader(Accept.create(MediaTypes.TEXT_PLAIN.toRange()))) - .assertStatusCode(406); - } + route.run(HttpRequest.GET("/nummer?n=5")) + .assertStatusCode(StatusCodes.OK) + .assertEntity("[1,2,3,4,5]"); - @Test - public void testCustomToEntityMarshaller() { - final Marshaller numberAsJsonListMarshaller = - Marshallers.toEntity(MediaTypes.APPLICATION_JSON.toContentType(), new Function() { - @Override - public ResponseEntity apply(Integer param) throws Exception { - switch(param) { - case 1: return HttpEntities.create(MediaTypes.APPLICATION_JSON.toContentType(), "[1]"); - case 5: return HttpEntities.create(MediaTypes.APPLICATION_JSON.toContentType(), "[1,2,3,4,5]"); - default: return HttpEntities.create(MediaTypes.APPLICATION_JSON.toContentType(), "[]"); - } - } - }); + route.run( + HttpRequest.GET("/nummer?n=5").addHeader(Accept.create(MediaTypes.TEXT_PLAIN.toRange()))) + .assertStatusCode(StatusCodes.NOT_ACCEPTABLE); + } - Handler1 nummerHandler = new Handler1() { - @Override - public RouteResult apply(RequestContext ctx, Integer integer) { - return ctx.completeAs(numberAsJsonListMarshaller, integer); - } - }; + @Test + public void testCustomToEntityMarshaller() { + final Marshaller numberAsJsonListMarshaller = + Marshaller.withFixedContentType(MediaTypes.APPLICATION_JSON.toContentType(), (Integer param) -> { + switch (param) { + case 1: + return HttpEntities.create(MediaTypes.APPLICATION_JSON.toContentType(), "[1]"); + case 5: + return HttpEntities.create(MediaTypes.APPLICATION_JSON.toContentType(), "[1,2,3,4,5]"); + default: + return HttpEntities.create(MediaTypes.APPLICATION_JSON.toContentType(), "[]"); + } + }); - TestRoute route = - testRoute( - get( - path("nummer").route( - handleWith1(n, nummerHandler) - ) - ) - ); + final Function nummerHandler = integer -> completeOK(integer, numberAsJsonListMarshaller); - route.run(HttpRequest.GET("/nummer?n=1")) - .assertStatusCode(200) - .assertMediaType(MediaTypes.APPLICATION_JSON) - .assertEntity("[1]"); + TestRoute route = + testRoute( + get(() -> + path("nummer", () -> + parameter(StringUnmarshallers.INTEGER, "n", nummerHandler) + ) + ).seal(system(), materializer()) // needed to get the content negotiation, maybe + ); - route.run(HttpRequest.GET("/nummer?n=5")) - .assertStatusCode(200) - .assertEntity("[1,2,3,4,5]"); + route.run(HttpRequest.GET("/nummer?n=1")) + .assertStatusCode(StatusCodes.OK) + .assertMediaType(MediaTypes.APPLICATION_JSON) + .assertEntity("[1]"); - route.run( - HttpRequest.GET("/nummer?n=5").addHeader(Accept.create(MediaTypes.TEXT_PLAIN.toRange()))) - .assertStatusCode(406); - } + route.run(HttpRequest.GET("/nummer?n=5")) + .assertStatusCode(StatusCodes.OK) + .assertEntity("[1,2,3,4,5]"); - @Test - public void testCustomToResponseMarshaller() { - final Marshaller numberAsJsonListMarshaller = - Marshallers.toResponse(MediaTypes.APPLICATION_JSON.toContentType(), new Function() { - @Override - public HttpResponse apply(Integer param) throws Exception { - switch(param) { - case 1: return HttpResponse.create().withEntity(MediaTypes.APPLICATION_JSON.toContentType(), "[1]"); - case 5: return HttpResponse.create().withEntity(MediaTypes.APPLICATION_JSON.toContentType(), "[1,2,3,4,5]"); - default: return HttpResponse.create().withStatus(404); - } - } - }); + route.run( + HttpRequest.GET("/nummer?n=5").addHeader(Accept.create(MediaTypes.TEXT_PLAIN.toRange()))) + .assertStatusCode(StatusCodes.NOT_ACCEPTABLE); + } - Handler1 nummerHandler = new Handler1() { - @Override - public RouteResult apply(RequestContext ctx, Integer integer) { - return ctx.completeAs(numberAsJsonListMarshaller, integer); - } - }; + @Test + public void testCustomToResponseMarshaller() { + final Marshaller numberAsJsonListMarshaller = + Marshaller.withFixedContentType(MediaTypes.APPLICATION_JSON.toContentType(), (Integer param) -> { + switch (param) { + case 1: + return HttpResponse.create().withEntity(MediaTypes.APPLICATION_JSON.toContentType(), "[1]"); + case 5: + return HttpResponse.create().withEntity(MediaTypes.APPLICATION_JSON.toContentType(), "[1,2,3,4,5]"); + default: + return HttpResponse.create().withStatus(StatusCodes.NOT_FOUND); + } + }); - TestRoute route = - testRoute( - get( - path("nummer").route( - handleWith1(n, nummerHandler) - ) - ) - ); + final Function nummerHandler = integer -> complete(integer, numberAsJsonListMarshaller); - route.run(HttpRequest.GET("/nummer?n=1")) - .assertStatusCode(200) - .assertMediaType(MediaTypes.APPLICATION_JSON) - .assertEntity("[1]"); + TestRoute route = + testRoute( + get(() -> + path("nummer", () -> + parameter(StringUnmarshallers.INTEGER, "n", nummerHandler) + ) + ) + ); - route.run(HttpRequest.GET("/nummer?n=5")) - .assertStatusCode(200) - .assertEntity("[1,2,3,4,5]"); + route.run(HttpRequest.GET("/nummer?n=1")) + .assertStatusCode(StatusCodes.OK) + .assertMediaType(MediaTypes.APPLICATION_JSON) + .assertEntity("[1]"); - route.run(HttpRequest.GET("/nummer?n=6")) - .assertStatusCode(404); + route.run(HttpRequest.GET("/nummer?n=5")) + .assertStatusCode(StatusCodes.OK) + .assertEntity("[1,2,3,4,5]"); - route.run(HttpRequest.GET("/nummer?n=5").addHeader(Accept.create(MediaTypes.TEXT_PLAIN.toRange()))) - .assertStatusCode(406); - } + route.run(HttpRequest.GET("/nummer?n=6")) + .assertStatusCode(StatusCodes.NOT_FOUND); + + route.run(HttpRequest.GET("/nummer?n=5").addHeader(Accept.create(MediaTypes.TEXT_PLAIN.toRange()))) + .assertStatusCode(StatusCodes.NOT_ACCEPTABLE); + } } diff --git a/akka-http-tests/src/test/java/akka/http/javadsl/server/UnmarshallerTest.java b/akka-http-tests/src/test/java/akka/http/javadsl/server/UnmarshallerTest.java new file mode 100644 index 0000000000..7b23e53cf0 --- /dev/null +++ b/akka-http-tests/src/test/java/akka/http/javadsl/server/UnmarshallerTest.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2009-2016 Typesafe Inc. + */ +package akka.http.javadsl.server; + +import akka.http.javadsl.model.*; +import akka.http.javadsl.testkit.JUnitRouteTest; +import org.junit.Test; + +import java.util.Arrays; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertEquals; + +public class UnmarshallerTest extends JUnitRouteTest { + + @Test + public void canChooseOneOfManyUnmarshallers() throws Exception { + Unmarshaller jsonUnmarshaller = + Unmarshaller.forMediaType(MediaTypes.APPLICATION_JSON, Unmarshaller.entityToString()).thenApply((str) -> "json"); + Unmarshaller xmlUnmarshaller = + Unmarshaller.forMediaType(MediaTypes.TEXT_XML, Unmarshaller.entityToString()).thenApply((str) -> "xml"); + + final Unmarshaller both = Unmarshaller.firstOf(jsonUnmarshaller, xmlUnmarshaller); + + { + CompletionStage resultStage = + both.unmarshall( + HttpEntities.create(ContentTypes.TEXT_XML_UTF8, ""), + system().dispatcher(), + materializer()); + + assertEquals("xml", resultStage.toCompletableFuture().get(3, TimeUnit.SECONDS)); + } + + + { + CompletionStage resultStage = + both.unmarshall( + HttpEntities.create(ContentTypes.APPLICATION_JSON, "{}"), + system().dispatcher(), + materializer()); + + assertEquals("json", resultStage.toCompletableFuture().get(3, TimeUnit.SECONDS)); + } + } + + @Test + public void oneMarshallerCanHaveMultipleMediaTypes() throws Exception { + Unmarshaller xmlUnmarshaller = + Unmarshaller.forMediaTypes( + Arrays.asList(MediaTypes.APPLICATION_XML, MediaTypes.TEXT_XML), + Unmarshaller.entityToString()).thenApply((str) -> "xml"); + + { + CompletionStage resultStage = + xmlUnmarshaller.unmarshall( + HttpEntities.create(ContentTypes.TEXT_XML_UTF8, ""), + system().dispatcher(), + materializer()); + + assertEquals("xml", resultStage.toCompletableFuture().get(3, TimeUnit.SECONDS)); + } + + { + CompletionStage resultStage = + xmlUnmarshaller.unmarshall( + HttpEntities.create(ContentTypes.create(MediaTypes.APPLICATION_XML, HttpCharsets.UTF_8), ""), + system().dispatcher(), + materializer()); + + assertEquals("xml", resultStage.toCompletableFuture().get(3, TimeUnit.SECONDS)); + } + } +} diff --git a/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/CodingDirectivesTest.java b/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/CodingDirectivesTest.java index c34ceb54f0..73c13b7dfe 100644 --- a/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/CodingDirectivesTest.java +++ b/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/CodingDirectivesTest.java @@ -9,130 +9,123 @@ import akka.http.javadsl.model.HttpRequest; import akka.http.javadsl.model.headers.AcceptEncoding; import akka.http.javadsl.model.headers.ContentEncoding; import akka.http.javadsl.model.headers.HttpEncodings; -import akka.http.javadsl.server.Coder; import akka.stream.ActorMaterializer; import akka.http.javadsl.server.*; import akka.util.ByteString; + import org.junit.*; + import scala.concurrent.Await; import scala.concurrent.duration.Duration; import akka.http.javadsl.testkit.*; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.concurrent.TimeUnit; public class CodingDirectivesTest extends JUnitRouteTest { - static ActorSystem system; + @Test + public void testAutomaticEncodingWhenNoEncodingRequested() throws Exception { + TestRoute route = + testRoute( + encodeResponse(() -> + complete("TestString") + ) + ); - @BeforeClass - public static void setup() { - system = ActorSystem.create("GraphDSLDocTest"); - } + TestRouteResult response = route.run(HttpRequest.create()); + response + .assertStatusCode(200); - @AfterClass - public static void tearDown() throws Exception { - Await.result(system.terminate(), Duration.Inf()); - system = null; - } + Assert.assertEquals("TestString", response.entityBytes().utf8String()); + } - final ActorMaterializer mat = ActorMaterializer.create(system); + @Test + public void testAutomaticEncodingWhenDeflateRequested() throws Exception { + TestRoute route = + testRoute( + encodeResponse(() -> + complete("tester") + ) + ); - @Test - public void testAutomaticEncodingWhenNoEncodingRequested() throws Exception { - TestRoute route = - testRoute( - encodeResponse( - complete("TestString") - ) - ); + HttpRequest request = HttpRequest.create().addHeader(AcceptEncoding.create(HttpEncodings.DEFLATE)); + TestRouteResult response = route.run(request); + response + .assertStatusCode(200) + .assertHeaderExists(ContentEncoding.create(HttpEncodings.DEFLATE)); - TestResponse response = route.run(HttpRequest.create()); - response - .assertStatusCode(200); + ByteString decompressed = + Coder.Deflate.decode(response.entityBytes(), materializer()).toCompletableFuture().get(3, TimeUnit.SECONDS); + Assert.assertEquals("tester", decompressed.utf8String()); + } - Assert.assertEquals("TestString", response.entityBytes().utf8String()); - } - @Test - public void testAutomaticEncodingWhenDeflateRequested() throws Exception { - TestRoute route = - testRoute( - encodeResponse( - complete("tester") - ) - ); + @Test + public void testEncodingWhenDeflateRequestedAndGzipSupported() { + TestRoute route = + testRoute( + encodeResponseWith(Arrays.asList(Coder.Gzip), () -> + complete("tester") + ) + ); - HttpRequest request = HttpRequest.create().addHeader(AcceptEncoding.create(HttpEncodings.DEFLATE)); - TestResponse response = route.run(request); - response - .assertStatusCode(200) - .assertHeaderExists(ContentEncoding.create(HttpEncodings.DEFLATE)); + HttpRequest request = HttpRequest.create().addHeader(AcceptEncoding.create(HttpEncodings.DEFLATE)); + route.run(request) + .assertStatusCode(406) + .assertEntity("Resource representation is only available with these Content-Encodings:\ngzip"); + } - ByteString decompressed = - Coder.Deflate.decode(response.entityBytes(), mat).toCompletableFuture().get(3, TimeUnit.SECONDS); - Assert.assertEquals("tester", decompressed.utf8String()); - } - @Test - public void testEncodingWhenDeflateRequestedAndGzipSupported() { - TestRoute route = - testRoute( - encodeResponse(Coder.Gzip).route( - complete("tester") - ) - ); + @Test + public void testAutomaticDecoding() { + TestRoute route = + testRoute( + decodeRequest(() -> + extractEntity(entity -> complete(entity)) + ) + ); - HttpRequest request = HttpRequest.create().addHeader(AcceptEncoding.create(HttpEncodings.DEFLATE)); - route.run(request) - .assertStatusCode(406) - .assertEntity("Resource representation is only available with these Content-Encodings:\ngzip"); - } + HttpRequest deflateRequest = + HttpRequest.POST("/") + .addHeader(ContentEncoding.create(HttpEncodings.DEFLATE)) + .withEntity(Coder.Deflate.encode(ByteString.fromString("abcdef"))); + route.run(deflateRequest) + .assertStatusCode(200) + .assertEntity("abcdef"); - @Test - public void testAutomaticDecoding() { - TestRoute route = - testRoute( - decodeRequest( - completeWithValueToString(RequestVals.entityAs(Unmarshallers.String())) - ) - ); + HttpRequest gzipRequest = + HttpRequest.POST("/") + .addHeader(ContentEncoding.create(HttpEncodings.GZIP)) + .withEntity(Coder.Gzip.encode(ByteString.fromString("hijklmnopq"))); + route.run(gzipRequest) + .assertStatusCode(200) + .assertEntity("hijklmnopq"); + } - HttpRequest deflateRequest = - HttpRequest.POST("/") - .addHeader(ContentEncoding.create(HttpEncodings.DEFLATE)) - .withEntity(Coder.Deflate.encode(ByteString.fromString("abcdef"))); - route.run(deflateRequest) - .assertStatusCode(200) - .assertEntity("abcdef"); + @Test + public void testGzipDecoding() { + TestRoute route = + testRoute( + decodeRequestWith(Collections.singleton(Coder.Gzip), () -> + extractEntity(entity -> complete(entity)) + ) + ); - HttpRequest gzipRequest = - HttpRequest.POST("/") - .addHeader(ContentEncoding.create(HttpEncodings.GZIP)) - .withEntity(Coder.Gzip.encode(ByteString.fromString("hijklmnopq"))); - route.run(gzipRequest) - .assertStatusCode(200) - .assertEntity("hijklmnopq"); - } - @Test - public void testGzipDecoding() { - TestRoute route = - testRoute( - decodeRequestWith(Coder.Gzip, - completeWithValueToString(RequestVals.entityAs(Unmarshallers.String())) - ) - ); + HttpRequest gzipRequest = + HttpRequest.POST("/") + .addHeader(ContentEncoding.create(HttpEncodings.GZIP)) + .withEntity(Coder.Gzip.encode(ByteString.fromString("hijklmnopq"))); + route.run(gzipRequest) + .assertStatusCode(200) + .assertEntity("hijklmnopq"); - HttpRequest gzipRequest = - HttpRequest.POST("/") - .addHeader(ContentEncoding.create(HttpEncodings.GZIP)) - .withEntity(Coder.Gzip.encode(ByteString.fromString("hijklmnopq"))); - route.run(gzipRequest) - .assertStatusCode(200) - .assertEntity("hijklmnopq"); - - HttpRequest deflateRequest = - HttpRequest.POST("/") - .addHeader(ContentEncoding.create(HttpEncodings.DEFLATE)) - .withEntity(Coder.Deflate.encode(ByteString.fromString("abcdef"))); - route.run(deflateRequest) - .assertStatusCode(400) - .assertEntity("The request's Content-Encoding is not supported. Expected:\ngzip"); - } + HttpRequest deflateRequest = + HttpRequest.POST("/") + .addHeader(ContentEncoding.create(HttpEncodings.DEFLATE)) + .withEntity(Coder.Deflate.encode(ByteString.fromString("abcdef"))); + route.run(deflateRequest) + .assertStatusCode(400) + .assertEntity("The request's Content-Encoding is not supported. Expected:\ngzip"); + } } diff --git a/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/CookieDirectivesTest.java b/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/CookieDirectivesTest.java new file mode 100644 index 0000000000..d7809e8661 --- /dev/null +++ b/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/CookieDirectivesTest.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2009-2016 Lightbend Inc. + */ + +package akka.http.javadsl.server.directives; + +import akka.http.javadsl.model.HttpRequest; +import akka.http.javadsl.model.headers.HttpCookie; +import akka.http.javadsl.testkit.JUnitRouteTest; +import akka.http.javadsl.testkit.TestRoute; +import org.junit.Test; + +public class CookieDirectivesTest extends JUnitRouteTest { + @Test + public void testCookieValue() { + TestRoute route = + testRoute( + cookie("userId", userId -> complete(userId.value())) + ); + + route.run(HttpRequest.create()) + .assertStatusCode(400) + .assertEntity("Request is missing required cookie 'userId'"); + + route.run(HttpRequest.create().addHeader(akka.http.javadsl.model.headers.Cookie.create("userId", "12345"))) + .assertStatusCode(200) + .assertEntity("12345"); + } + + @Test + public void testCookieOptionalValue() { + TestRoute route = + testRoute( + optionalCookie("userId", opt -> complete(opt.toString())) + ); + + route.run(HttpRequest.create()) + .assertStatusCode(200) + .assertEntity("Optional.empty"); + + route.run(HttpRequest.create().addHeader(akka.http.javadsl.model.headers.Cookie.create("userId", "12345"))) + .assertStatusCode(200) + .assertEntity("Optional[userId=12345]"); + } + + @Test + public void testCookieSet() { + TestRoute route = + testRoute( + setCookie(HttpCookie.create("userId", "12"), () -> complete("OK!")) + ); + + route.run(HttpRequest.create()) + .assertStatusCode(200) + .assertHeaderExists("Set-Cookie", "userId=12") + .assertEntity("OK!"); + } + + @Test + public void testDeleteCookie() { + TestRoute route = + testRoute( + deleteCookie("userId", () -> complete("OK!")) + ); + + route.run(HttpRequest.create()) + .assertStatusCode(200) + .assertHeaderExists("Set-Cookie", "userId=deleted; Expires=Wed, 01 Jan 1800 00:00:00 GMT") + .assertEntity("OK!"); + } +} diff --git a/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/ExecutionDirectivesTest.java b/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/ExecutionDirectivesTest.java index 07f25a2298..2eeee3ad95 100644 --- a/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/ExecutionDirectivesTest.java +++ b/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/ExecutionDirectivesTest.java @@ -6,147 +6,94 @@ package akka.http.javadsl.server.directives; import org.junit.Test; -import akka.http.javadsl.model.*; -import akka.http.javadsl.server.*; -import akka.http.javadsl.server.values.*; -import akka.http.javadsl.testkit.*; +import akka.http.javadsl.model.HttpRequest; +import akka.http.javadsl.model.StatusCodes; +import akka.http.javadsl.server.ExceptionHandler; +import akka.http.javadsl.server.RejectionHandler; +import akka.http.javadsl.server.Route; +import akka.http.javadsl.server.StringUnmarshallers; +import akka.http.javadsl.testkit.JUnitRouteTest; +import akka.http.javadsl.testkit.TestRoute; +import akka.http.scaladsl.server.MethodRejection; +import akka.http.scaladsl.server.Rejection; public class ExecutionDirectivesTest extends JUnitRouteTest { - @Test - public void testCatchExceptionThrownFromHandler() { - Parameter a = Parameters.intValue("a"); - Parameter b = Parameters.intValue("b"); - Handler2 divide = - new Handler2() { - @Override - public RouteResult apply(RequestContext ctx, Integer a, Integer b) { - int result = a / b; - return ctx.complete("The result is: " + result); - } - }; + @Test + public void testCatchExceptionThrownFromHandler() { + Route divide = + path("divide", () -> + parameter(StringUnmarshallers.INTEGER, "a", a -> + parameter(StringUnmarshallers.INTEGER, "b", b -> + complete("The result is: " + (a / b))))); - ExceptionHandler handleDivByZero = - new ExceptionHandler() { - @Override - public Route handle(RuntimeException exception) { - try { - throw exception; - } catch(ArithmeticException t) { - return complete( - HttpResponse.create() - .withStatus(400) - .withEntity("Congratulations you provoked a division by zero!")); - } - } - }; - TestRoute route = - testRoute( - handleExceptions(handleDivByZero, - path("divide").route( - handleWith2(a, b, divide) - ) - ) - ); + ExceptionHandler handleDivByZero = ExceptionHandler.newBuilder() + .match(ArithmeticException.class, t -> complete(StatusCodes.BAD_REQUEST, "Congratulations you provoked a division by zero!")) + .build(); - route.run(HttpRequest.GET("/divide?a=10&b=5")) - .assertEntity("The result is: 2"); + TestRoute route = + testRoute( + handleExceptions(handleDivByZero, () -> divide) + ); - route.run(HttpRequest.GET("/divide?a=10&b=0")) - .assertStatusCode(400) - .assertEntity("Congratulations you provoked a division by zero!"); + route.run(HttpRequest.GET("/divide?a=10&b=5")) + .assertEntity("The result is: 2"); + + route.run(HttpRequest.GET("/divide?a=10&b=0")) + .assertStatusCode(StatusCodes.BAD_REQUEST) + .assertEntity("Congratulations you provoked a division by zero!"); + } + + @Test + public void testHandleMethodRejection() { + RejectionHandler rejectionHandler = RejectionHandler.newBuilder() + .handle(MethodRejection.class, r -> complete(StatusCodes.BAD_REQUEST, "Whoopsie! Unsupported method. Supported would have been " + r.supported().value())) + .build(); + + TestRoute route = + testRoute( + handleRejections(rejectionHandler, () -> + get(() -> complete("Successful!")) + ) + ); + + route.run(HttpRequest.GET("/")) + .assertStatusCode(StatusCodes.OK) + .assertEntity("Successful!"); + + route.run(HttpRequest.POST("/")) + .assertStatusCode(StatusCodes.BAD_REQUEST) + .assertEntity("Whoopsie! Unsupported method. Supported would have been GET"); + } + + public static final class TooManyRequestsRejection implements Rejection { + final public String message; + + TooManyRequestsRejection(String message) { + this.message = message; } + } - @Test - public void testHandleMethodRejection() { - RejectionHandler rejectionHandler = - new RejectionHandler() { - @Override - public RouteResult handleMethodRejection(RequestContext ctx, HttpMethod supported) { - return ctx.complete( - HttpResponse.create() - .withStatus(400) - .withEntity("Whoopsie! Unsupported method. Supported would have been " + supported.value())); - } - }; + private final Route testRoute = extractUri(uri -> { + if (uri.path().startsWith("/test")) + return complete("Successful!"); + else + return reject(new TooManyRequestsRejection("Too many requests for busy path!")); + }); - TestRoute route = - testRoute( - handleRejections(rejectionHandler, - get(complete("Successful!")) - ) - ); + @Test + public void testHandleCustomRejection() { + RejectionHandler rejectionHandler = RejectionHandler.newBuilder() + .handle(TooManyRequestsRejection.class, rej -> complete(StatusCodes.TOO_MANY_REQUESTS, rej.message)) + .build(); - route.run(HttpRequest.GET("/")) - .assertStatusCode(200) - .assertEntity("Successful!"); + TestRoute route = testRoute(handleRejections(rejectionHandler, () -> testRoute)); - route.run(HttpRequest.POST("/")) - .assertStatusCode(400) - .assertEntity("Whoopsie! Unsupported method. Supported would have been GET"); - } + route.run(HttpRequest.GET("/test")) + .assertStatusCode(StatusCodes.OK); - public static final class TooManyRequestsRejection extends CustomRejection { - final public String message; - TooManyRequestsRejection(String message) { - this.message = message; - } - } - - private static Handler testHandler = - new Handler() { - @Override - public RouteResult apply(RequestContext ctx) { - if (ctx.request().getUri().path().startsWith("/test")) - return ctx.complete("Successful!"); - else - return ctx.reject(new TooManyRequestsRejection("Too many requests for busy path!")); - } - }; - - @Test - public void testHandleCustomRejection() { - RejectionHandler rejectionHandler = - new RejectionHandler() { - @Override - public RouteResult handleCustomRejection(RequestContext ctx, CustomRejection rejection) { - if (rejection instanceof TooManyRequestsRejection) { - TooManyRequestsRejection rej = (TooManyRequestsRejection) rejection; - HttpResponse response = - HttpResponse.create() - .withStatus(StatusCodes.TOO_MANY_REQUESTS) - .withEntity(rej.message); - return ctx.complete(response); - } else - return passRejection(); - } - }; - - testRouteWithHandler(handleRejections(rejectionHandler, handleWith(testHandler))); - } - @Test - public void testHandleCustomRejectionByClass() { - Handler1 rejectionHandler = - new Handler1() { - public RouteResult apply(RequestContext ctx, TooManyRequestsRejection rej) { - HttpResponse response = - HttpResponse.create() - .withStatus(StatusCodes.TOO_MANY_REQUESTS) - .withEntity(rej.message); - return ctx.complete(response); - } - }; - testRouteWithHandler(handleRejections(TooManyRequestsRejection.class, rejectionHandler, handleWith(testHandler))); - } - - private void testRouteWithHandler(Route innerRoute) { - TestRoute route = testRoute(innerRoute); - - route.run(HttpRequest.GET("/test")) - .assertStatusCode(200); - - route.run(HttpRequest.GET("/other")) - .assertStatusCode(429) - .assertEntity("Too many requests for busy path!"); - } + route.run(HttpRequest.GET("/other")) + .assertStatusCode(StatusCodes.TOO_MANY_REQUESTS) + .assertEntity("Too many requests for busy path!"); + } } diff --git a/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/HeaderDirectivesTest.java b/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/HeaderDirectivesTest.java new file mode 100644 index 0000000000..79176309b9 --- /dev/null +++ b/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/HeaderDirectivesTest.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2009-2016 Lightbend Inc. + */ +package akka.http.javadsl.server.directives; + +import akka.http.javadsl.model.HttpHeader; +import akka.http.javadsl.model.HttpRequest; +import akka.http.javadsl.model.StatusCodes; +import akka.http.javadsl.model.headers.*; +import akka.http.javadsl.testkit.JUnitRouteTest; +import akka.http.javadsl.testkit.TestRoute; +import akka.japi.pf.PFBuilder; +import org.junit.Test; + +import java.util.Optional; + +public class HeaderDirectivesTest extends JUnitRouteTest { + + @Test + public void testHeaderValue() { + TestRoute route = testRoute(headerValue((header) -> { + if (header.name().equals("X-Test-Header")) { + if (header.value().equals("bad value")) throw new RuntimeException("bad value"); + else return Optional.of(header.value()); + } + else return Optional.empty(); + }, + this::complete)); + + route + .run(HttpRequest.create().addHeader(RawHeader.create("X-Test-Header", "woho!"))) + .assertStatusCode(StatusCodes.OK) + .assertEntity("woho!"); + + route + .run(HttpRequest.create().addHeader(RawHeader.create("X-Test-Header", "bad value"))) + .assertStatusCode(StatusCodes.BAD_REQUEST); + + + route + .run(HttpRequest.create()) + .assertStatusCode(StatusCodes.NOT_FOUND); + } + + @Test + public void testHeaderValuePF() { + TestRoute route = testRoute(headerValuePF( + new PFBuilder().match( + Host.class, Host::value + ).build(), + this::complete)); + + route + .run(HttpRequest.create().addHeader(Host.create("example.com"))) + .assertStatusCode(StatusCodes.OK) + .assertEntity("example.com"); + + route + .run(HttpRequest.create()) + .assertStatusCode(StatusCodes.NOT_FOUND); + } + + @Test + public void testHeaderValueByName() { + TestRoute route = testRoute(headerValueByName("X-Test-Header", this::complete)); + + route + .run(HttpRequest.create().addHeader(RawHeader.create("X-Test-Header", "woho!"))) + .assertStatusCode(StatusCodes.OK) + .assertEntity("woho!"); + + route + .run(HttpRequest.create()) + .assertStatusCode(StatusCodes.BAD_REQUEST); + } + + @Test + public void testHeaderValueByType() { + TestRoute route = testRoute(headerValueByType(Server.class, + (Server s) -> complete(s.getProducts().iterator().next().product()))); + + route + .run(HttpRequest.create().addHeader(Server.create(ProductVersion.create("such-service", "0.6")))) + .assertStatusCode(StatusCodes.OK) + .assertEntity("such-service"); + + route + .run(HttpRequest.create()) + .assertStatusCode(StatusCodes.BAD_REQUEST); + } + + @Test + public void testOptionalHeaderValue() { + TestRoute route = testRoute(optionalHeaderValue((header) -> { + if (header.name().equals("X-Test-Header")) { + if (header.value().equals("bad value")) throw new RuntimeException("bad value"); + else return Optional.of(header.value()); + } + else return Optional.empty(); + }, + opt -> complete(opt.toString()))); + + route + .run(HttpRequest.create().addHeader(RawHeader.create("X-Test-Header", "woho!"))) + .assertStatusCode(StatusCodes.OK) + .assertEntity("Optional[woho!]"); + + route + .run(HttpRequest.create().addHeader(RawHeader.create("X-Test-Header", "bad value"))) + .assertStatusCode(StatusCodes.BAD_REQUEST); + + + route + .run(HttpRequest.create()) + .assertStatusCode(StatusCodes.OK) + .assertEntity("Optional.empty"); + } + + @Test + public void testOptionalHeaderValuePF() { + TestRoute route = testRoute(optionalHeaderValuePF( + new PFBuilder().match( + Host.class, Host::value + ).build(), + (opt) -> complete(opt.toString()))); + + route + .run(HttpRequest.create().addHeader(Host.create("example.com"))) + .assertStatusCode(StatusCodes.OK) + .assertEntity("Optional[example.com]"); + + route + .run(HttpRequest.create()) + .assertStatusCode(StatusCodes.OK) + .assertEntity("Optional.empty"); + } + + + @Test + public void testOptionalHeaderValueByName() { + TestRoute route = testRoute(optionalHeaderValueByName("X-Test-Header", (opt) -> complete(opt.toString()))); + + route + .run(HttpRequest.create().addHeader(RawHeader.create("X-Test-Header", "woho!"))) + .assertStatusCode(StatusCodes.OK) + .assertEntity("Optional[woho!]"); + + route + .run(HttpRequest.create()) + .assertStatusCode(StatusCodes.OK) + .assertEntity("Optional.empty"); + } + + @Test + public void testOptionalHeaderValueByType() { + TestRoute route = testRoute(optionalHeaderValueByType(Server.class, + (Optional s) -> complete(((Boolean)s.isPresent()).toString()))); + + route + .run(HttpRequest.create().addHeader(Server.create(ProductVersion.create("such-service", "0.6")))) + .assertStatusCode(StatusCodes.OK) + .assertEntity("true"); + + route + .run(HttpRequest.create()) + .assertStatusCode(StatusCodes.OK) + .assertEntity("false"); + } + + @Test + public void testValueByTypeHandlesCustomHeaders() { + TestRoute route = testRoute(headerValueByType(SampleCustomHeader.class, + (SampleCustomHeader m) -> complete(m.value()))); + + route + .run(HttpRequest.create().addHeader(RawHeader.create("X-Sample-Custom-Header", "such header"))) + .assertStatusCode(StatusCodes.OK) + .assertEntity("X-Sample-Custom-Header: such header"); + + route + .run(HttpRequest.create()) + .assertStatusCode(StatusCodes.BAD_REQUEST); + } + +} diff --git a/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/HostDirectivesTest.java b/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/HostDirectivesTest.java index 1ba3290f87..7e3c96de71 100644 --- a/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/HostDirectivesTest.java +++ b/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/HostDirectivesTest.java @@ -5,68 +5,91 @@ package akka.http.javadsl.server.directives; import akka.http.javadsl.model.HttpRequest; +import akka.http.javadsl.model.StatusCodes; import akka.http.javadsl.model.Uri; import akka.http.javadsl.testkit.JUnitRouteTest; import akka.http.javadsl.testkit.TestRoute; -import akka.japi.function.Function; import org.junit.Test; import java.util.ArrayList; +import java.util.regex.Pattern; public class HostDirectivesTest extends JUnitRouteTest { - @Test - public void testHostFilterBySingleName() { - TestRoute route = testRoute(host("example.org", complete("OK!"))); + @Test + public void testHostFilterBySingleName() { + TestRoute route = testRoute(host("example.org", () -> complete("OK!"))); - route - .run(HttpRequest.create().withUri(Uri.create("http://example.org"))) - .assertStatusCode(200) - .assertEntity("OK!"); + route + .run(HttpRequest.create().withUri(Uri.create("http://example.org"))) + .assertStatusCode(StatusCodes.OK) + .assertEntity("OK!"); - route - .run(HttpRequest.create().withUri(Uri.create("https://other.org"))) - .assertStatusCode(404); - } - @Test - public void testHostFilterByNames() { - ArrayList hosts = new ArrayList(); - hosts.add("example.org"); - hosts.add("example2.org"); - TestRoute route = testRoute(host(hosts, complete("OK!"))); + route + .run(HttpRequest.create().withUri(Uri.create("https://other.org"))) + .assertStatusCode(StatusCodes.NOT_FOUND); + } - route - .run(HttpRequest.create().withUri(Uri.create("http://example.org"))) - .assertStatusCode(200) - .assertEntity("OK!"); + @Test + public void testHostFilterByNames() { + ArrayList hosts = new ArrayList(); + hosts.add("example.org"); + hosts.add("example2.org"); + TestRoute route = testRoute(host(hosts, () -> complete("OK!"))); - route - .run(HttpRequest.create().withUri(Uri.create("http://example2.org"))) - .assertStatusCode(200) - .assertEntity("OK!"); + route + .run(HttpRequest.create().withUri(Uri.create("http://example.org"))) + .assertStatusCode(StatusCodes.OK) + .assertEntity("OK!"); - route - .run(HttpRequest.create().withUri(Uri.create("https://other.org"))) - .assertStatusCode(404); - } - @Test - public void testHostFilterByPredicate() { - Function predicate = - new Function(){ - @Override - public Boolean apply(String hostName) throws Exception { - return hostName.contains("ample"); - } - }; + route + .run(HttpRequest.create().withUri(Uri.create("http://example2.org"))) + .assertStatusCode(StatusCodes.OK) + .assertEntity("OK!"); - TestRoute route = testRoute(host(predicate, complete("OK!"))); + route + .run(HttpRequest.create().withUri(Uri.create("https://other.org"))) + .assertStatusCode(404); + } - route - .run(HttpRequest.create().withUri(Uri.create("http://example.org"))) - .assertStatusCode(200) - .assertEntity("OK!"); + @Test + public void testHostFilterByPredicate() { + TestRoute route = testRoute(host(hostName -> hostName.contains("ample"), () -> complete("OK!"))); + + route + .run(HttpRequest.create().withUri(Uri.create("http://example.org"))) + .assertStatusCode(StatusCodes.OK) + .assertEntity("OK!"); + + route + .run(HttpRequest.create().withUri(Uri.create("https://other.org"))) + .assertStatusCode(StatusCodes.NOT_FOUND); + } + + + @Test + public void testHostExtraction() { + TestRoute route = testRoute(extractHost(this::complete)); + + route + .run(HttpRequest.create().withUri(Uri.create("http://example.org"))) + .assertStatusCode(StatusCodes.OK) + .assertEntity("example.org"); + } + + @Test + public void testHostPatternExtraction() { + TestRoute route = + testRoute(host(Pattern.compile(".*\\.([^.]*)"), this::complete)); + + route + .run(HttpRequest.create().withUri(Uri.create("http://example.org"))) + .assertStatusCode(StatusCodes.OK) + .assertEntity("org"); + + route + .run(HttpRequest.create().withUri(Uri.create("http://example.de"))) + .assertStatusCode(StatusCodes.OK) + .assertEntity("de"); + } - route - .run(HttpRequest.create().withUri(Uri.create("https://other.org"))) - .assertStatusCode(404); - } } diff --git a/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/MarshallingDirectivesTest.java b/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/MarshallingDirectivesTest.java new file mode 100644 index 0000000000..9c291439a6 --- /dev/null +++ b/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/MarshallingDirectivesTest.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2009-2016 Lightbend Inc. + */ + +package akka.http.javadsl.server.directives; + +import akka.http.javadsl.model.HttpRequest; +import akka.http.javadsl.model.RemoteAddress; +import akka.http.javadsl.model.StatusCodes; +import akka.http.javadsl.model.Uri; +import akka.http.javadsl.model.headers.RawHeader; +import akka.http.javadsl.model.headers.XForwardedFor; +import akka.http.javadsl.testkit.JUnitRouteTest; +import akka.http.javadsl.testkit.TestRoute; + +import org.junit.Test; +import akka.http.javadsl.server.Unmarshaller; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.regex.Pattern; + +public class MarshallingDirectivesTest extends JUnitRouteTest { + + @Test + public void testEntityAsString() { + TestRoute route = + testRoute( + entity(Unmarshaller.entityToString(), this::complete) + ); + + HttpRequest request = + HttpRequest.POST("/") + .withEntity("abcdef"); + route.run(request) + .assertStatusCode(StatusCodes.OK) + .assertEntity("abcdef"); + } +} 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 bf5f01d908..aded1cbad1 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 @@ -5,113 +5,72 @@ package akka.http.javadsl.server.directives; import akka.http.javadsl.model.HttpRequest; +import akka.http.javadsl.model.RemoteAddress; +import akka.http.javadsl.model.StatusCodes; import akka.http.javadsl.model.Uri; -import akka.http.javadsl.server.RequestContext; -import akka.http.javadsl.server.RequestVal; -import akka.http.javadsl.server.RequestVals; -import akka.http.javadsl.server.values.Parameter; -import akka.http.javadsl.server.values.Parameters; -import akka.http.javadsl.server.values.PathMatchers; +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.testkit.JUnitRouteTest; import akka.http.javadsl.testkit.TestRoute; -import akka.japi.function.Function; -import akka.japi.function.Function2; import org.junit.Test; +import java.net.InetAddress; +import java.net.UnknownHostException; + public class MiscDirectivesTest extends JUnitRouteTest { - static Parameter stringParam = Parameters.stringValue("stringParam"); - static Function isShort = - new Function() { - @Override - public Boolean apply(String str) throws Exception { - return str.length() < 5; - } - }; + static boolean isShort(String str) { + return str.length() < 5; + } - @Test - public void testValidateRequestContext() { - Function hasShortPath = - new Function() { - @Override - public Boolean apply(RequestContext ctx) throws Exception { - return ctx.request().getUri().path().toString().length() < 5; - } - }; + static boolean hasShortPath(Uri uri) { + return uri.path().toString().length() < 5; + } - TestRoute route = testRoute(validate(hasShortPath, "Path too long!", complete("OK!"))); + @Test + public void testValidateUri() { + TestRoute route = testRoute( + extractUri(uri -> + validate(() -> hasShortPath(uri), "Path too long!", + () -> complete("OK!") + ) + ) + ); - route - .run(HttpRequest.create().withUri(Uri.create("/abc"))) - .assertStatusCode(200) - .assertEntity("OK!"); + route + .run(HttpRequest.create().withUri(Uri.create("/abc"))) + .assertStatusCode(StatusCodes.OK) + .assertEntity("OK!"); - route - .run(HttpRequest.create().withUri(Uri.create("/abcdefghijkl"))) - .assertStatusCode(400) - .assertEntity("Path too long!"); - } - @Test - public void testValidateOneStandaloneRequestVal() { - TestRoute route = testRoute(validate(stringParam, isShort, "stringParam too long!", complete("OK!"))); + route + .run(HttpRequest.create().withUri(Uri.create("/abcdefghijkl"))) + .assertStatusCode(StatusCodes.BAD_REQUEST) + .assertEntity("Path too long!"); + } - route - .run(HttpRequest.GET("/?stringParam=abcd")) - .assertStatusCode(200) - .assertEntity("OK!"); + @Test + public void testClientIpExtraction() throws UnknownHostException { + TestRoute route = testRoute(extractClientIP(ip -> complete(ip.toString()))); - route - .run(HttpRequest.GET("/?stringParam=abcdefg")) - .assertStatusCode(400) - .assertEntity("stringParam too long!"); - } - @Test - public void testValidateOnePathMatcherRequestVal() { - RequestVal nameSegment = PathMatchers.segment(); + route + .run(HttpRequest.create().addHeader(XForwardedFor.create(RemoteAddress.create(InetAddress.getByName("127.0.0.2"))))) + .assertStatusCode(StatusCodes.OK) + .assertEntity("127.0.0.2"); - TestRoute route = testRoute( - path("people", nameSegment, "address").route( - validate(nameSegment, isShort, "Segment too long!", complete("OK!")) - ) - ); + route + .run(HttpRequest.create().addHeader(akka.http.javadsl.model.headers.RemoteAddress.create(RemoteAddress.create(InetAddress.getByName("127.0.0.3"))))) + .assertStatusCode(StatusCodes.OK) + .assertEntity("127.0.0.3"); - route - .run(HttpRequest.GET("/people/john/address")) - .assertStatusCode(200) - .assertEntity("OK!"); + route + .run(HttpRequest.create().addHeader(XRealIp.create(RemoteAddress.create(InetAddress.getByName("127.0.0.4"))))) + .assertStatusCode(StatusCodes.OK) + .assertEntity("127.0.0.4"); - route - .run(HttpRequest.GET("/people/hermanbaker/address")) - .assertStatusCode(400) - .assertEntity("Segment too long!"); - } - @Test - public void testValidateTwoRequestVals() { - Function2 stringParamEqualsHostName = - new Function2() { - @Override - public Boolean apply(String stringParam, String hostName) throws Exception { - return stringParam.equals(hostName); - } - }; + route + .run(HttpRequest.create()) + .assertStatusCode(StatusCodes.NOT_FOUND); + } - TestRoute route = - testRoute( - validate(stringParam, RequestVals.host(), stringParamEqualsHostName, "stringParam must equal hostName!", - complete("OK!"))); - - route - .run(HttpRequest.GET("http://example.org/?stringParam=example.org")) - .assertStatusCode(200) - .assertEntity("OK!"); - - route - .run(HttpRequest.GET("http://blubber.org/?stringParam=example.org")) - .assertStatusCode(400) - .assertEntity("stringParam must equal hostName!"); - - route - .run(HttpRequest.GET("http://blubber.org/")) - .assertStatusCode(404); - } } diff --git a/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/ParameterDirectivesTest.java b/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/ParameterDirectivesTest.java new file mode 100644 index 0000000000..7fa8a7f7f4 --- /dev/null +++ b/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/ParameterDirectivesTest.java @@ -0,0 +1,374 @@ +/* + * Copyright (C) 2009-2016 Lightbend Inc. + */ + +package akka.http.javadsl.server.directives; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Map; + +import org.junit.Test; + +import akka.http.javadsl.model.HttpRequest; +import akka.http.javadsl.server.StringUnmarshallers; +import akka.http.javadsl.testkit.JUnitRouteTest; +import akka.http.javadsl.testkit.TestRoute; + +public class ParameterDirectivesTest extends JUnitRouteTest { + + @Test + public void testStringParameterExtraction() { + TestRoute route = testRoute(parameter("stringParam", value -> complete(value))); + + route + .run(HttpRequest.create().withUri("/abc")) + .assertStatusCode(404) + .assertEntity("Request is missing required query parameter 'stringParam'"); + + route + .run(HttpRequest.create().withUri("/abc?stringParam=john")) + .assertStatusCode(200) + .assertEntity("john"); + } + + @Test + public void testByteParameterExtraction() { + TestRoute route = testRoute(parameter(StringUnmarshallers.BYTE, "byteParam", value -> complete(value.toString()))); + + route + .run(HttpRequest.create().withUri("/abc")) + .assertStatusCode(404) + .assertEntity("Request is missing required query parameter 'byteParam'"); + + route + .run(HttpRequest.create().withUri("/abc?byteParam=test")) + .assertStatusCode(400) + .assertEntity("The query parameter 'byteParam' was malformed:\n'test' is not a valid 8-bit signed integer value"); + + route + .run(HttpRequest.create().withUri("/abc?byteParam=1000")) + .assertStatusCode(400) + .assertEntity("The query parameter 'byteParam' was malformed:\n'1000' is not a valid 8-bit signed integer value"); + + route + .run(HttpRequest.create().withUri("/abc?byteParam=48")) + .assertStatusCode(200) + .assertEntity("48"); + } + + @Test + public void testShortParameterExtraction() { + TestRoute route = testRoute(parameter(StringUnmarshallers.SHORT, "shortParam", value -> complete(value.toString()))); + + route + .run(HttpRequest.create().withUri("/abc")) + .assertStatusCode(404) + .assertEntity("Request is missing required query parameter 'shortParam'"); + + route + .run(HttpRequest.create().withUri("/abc?shortParam=test")) + .assertStatusCode(400) + .assertEntity("The query parameter 'shortParam' was malformed:\n'test' is not a valid 16-bit signed integer value"); + + route + .run(HttpRequest.create().withUri("/abc?shortParam=100000")) + .assertStatusCode(400) + .assertEntity("The query parameter 'shortParam' was malformed:\n'100000' is not a valid 16-bit signed integer value"); + + route + .run(HttpRequest.create().withUri("/abc?shortParam=1234")) + .assertStatusCode(200) + .assertEntity("1234"); + } + + @Test + public void testIntegerParameterExtraction() { + TestRoute route = testRoute(parameter(StringUnmarshallers.INTEGER, "intParam", value -> complete(value.toString()))); + + route + .run(HttpRequest.create().withUri("/abc")) + .assertStatusCode(404) + .assertEntity("Request is missing required query parameter 'intParam'"); + + route + .run(HttpRequest.create().withUri("/abc?intParam=test")) + .assertStatusCode(400) + .assertEntity("The query parameter 'intParam' was malformed:\n'test' is not a valid 32-bit signed integer value"); + + route + .run(HttpRequest.create().withUri("/abc?intParam=48")) + .assertStatusCode(200) + .assertEntity("48"); + } + + @Test + public void testLongParameterExtraction() { + TestRoute route = testRoute(parameter(StringUnmarshallers.LONG, "longParam", value -> complete(value.toString()))); + + route + .run(HttpRequest.create().withUri("/abc")) + .assertStatusCode(404) + .assertEntity("Request is missing required query parameter 'longParam'"); + + route + .run(HttpRequest.create().withUri("/abc?longParam=test")) + .assertStatusCode(400) + .assertEntity("The query parameter 'longParam' was malformed:\n'test' is not a valid 64-bit signed integer value"); + + route + .run(HttpRequest.create().withUri("/abc?longParam=123456")) + .assertStatusCode(200) + .assertEntity("123456"); + } + + @Test + public void testFloatParameterExtraction() { + TestRoute route = testRoute(parameter(StringUnmarshallers.FLOAT, "floatParam", value -> complete(value.toString()))); + + route + .run(HttpRequest.create().withUri("/abc")) + .assertStatusCode(404) + .assertEntity("Request is missing required query parameter 'floatParam'"); + + route + .run(HttpRequest.create().withUri("/abc?floatParam=test")) + .assertStatusCode(400) + .assertEntity("The query parameter 'floatParam' was malformed:\n'test' is not a valid 32-bit floating point value"); + + route + .run(HttpRequest.create().withUri("/abc?floatParam=48")) + .assertStatusCode(200) + .assertEntity("48.0"); + } + + @Test + public void testDoubleParameterExtraction() { + TestRoute route = testRoute(parameter(StringUnmarshallers.DOUBLE, "doubleParam", value -> complete(value.toString()))); + + route + .run(HttpRequest.create().withUri("/abc")) + .assertStatusCode(404) + .assertEntity("Request is missing required query parameter 'doubleParam'"); + + route + .run(HttpRequest.create().withUri("/abc?doubleParam=test")) + .assertStatusCode(400) + .assertEntity("The query parameter 'doubleParam' was malformed:\n'test' is not a valid 64-bit floating point value"); + + route + .run(HttpRequest.create().withUri("/abc?doubleParam=48")) + .assertStatusCode(200) + .assertEntity("48.0"); + } + + @Test + public void testHexByteParameterExtraction() { + TestRoute route = testRoute(parameter(StringUnmarshallers.BYTE_HEX, "hexByteParam", value -> complete(value.toString()))); + + route + .run(HttpRequest.create().withUri("/abc")) + .assertStatusCode(404) + .assertEntity("Request is missing required query parameter 'hexByteParam'"); + + route + .run(HttpRequest.create().withUri("/abc?hexByteParam=test")) + .assertStatusCode(400) + .assertEntity("The query parameter 'hexByteParam' was malformed:\n'test' is not a valid 8-bit hexadecimal integer value"); + + route + .run(HttpRequest.create().withUri("/abc?hexByteParam=1000")) + .assertStatusCode(400) + .assertEntity("The query parameter 'hexByteParam' was malformed:\n'1000' is not a valid 8-bit hexadecimal integer value"); + + route + .run(HttpRequest.create().withUri("/abc?hexByteParam=48")) + .assertStatusCode(200) + .assertEntity(Integer.toString(0x48)); + } + + @Test + public void testHexShortParameterExtraction() { + TestRoute route = testRoute(parameter(StringUnmarshallers.SHORT_HEX, "hexShortParam", value -> complete(value.toString()))); + + route + .run(HttpRequest.create().withUri("/abc")) + .assertStatusCode(404) + .assertEntity("Request is missing required query parameter 'hexShortParam'"); + + route + .run(HttpRequest.create().withUri("/abc?hexShortParam=test")) + .assertStatusCode(400) + .assertEntity("The query parameter 'hexShortParam' was malformed:\n'test' is not a valid 16-bit hexadecimal integer value"); + + route + .run(HttpRequest.create().withUri("/abc?hexShortParam=100000")) + .assertStatusCode(400) + .assertEntity("The query parameter 'hexShortParam' was malformed:\n'100000' is not a valid 16-bit hexadecimal integer value"); + + route + .run(HttpRequest.create().withUri("/abc?hexShortParam=1234")) + .assertStatusCode(200) + .assertEntity(Integer.toString(0x1234)); + } + + @Test + public void testHexIntegerParameterExtraction() { + TestRoute route = testRoute(parameter(StringUnmarshallers.INTEGER_HEX, "hexIntParam", value -> complete(value.toString()))); + + route + .run(HttpRequest.create().withUri("/abc")) + .assertStatusCode(404) + .assertEntity("Request is missing required query parameter 'hexIntParam'"); + + route + .run(HttpRequest.create().withUri("/abc?hexIntParam=test")) + .assertStatusCode(400) + .assertEntity("The query parameter 'hexIntParam' was malformed:\n'test' is not a valid 32-bit hexadecimal integer value"); + + route + .run(HttpRequest.create().withUri("/abc?hexIntParam=12345678")) + .assertStatusCode(200) + .assertEntity(Integer.toString(0x12345678)); + } + + @Test + public void testHexLongParameterExtraction() { + TestRoute route = testRoute(parameter(StringUnmarshallers.LONG_HEX, "hexLongParam", value -> complete(value.toString()))); + + route + .run(HttpRequest.create().withUri("/abc")) + .assertStatusCode(404) + .assertEntity("Request is missing required query parameter 'hexLongParam'"); + + route + .run(HttpRequest.create().withUri("/abc?hexLongParam=test")) + .assertStatusCode(400) + .assertEntity("The query parameter 'hexLongParam' was malformed:\n'test' is not a valid 64-bit hexadecimal integer value"); + + route + .run(HttpRequest.create().withUri("/abc?hexLongParam=123456789a")) + .assertStatusCode(200) + .assertEntity(Long.toString(0x123456789aL)); + } + + @Test + public void testParametersAsMapExtraction() { + TestRoute route = testRoute( + parameterMap(paramMap -> { + ArrayList keys = new ArrayList(paramMap.keySet()); + Collections.sort(keys); + StringBuilder res = new StringBuilder(); + res.append(paramMap.size()).append(": ["); + for (String key : keys) + res.append(key).append(" -> ").append(paramMap.get(key)).append(", "); + res.append(']'); + return complete(res.toString()); + })); + + route + .run(HttpRequest.create().withUri("/abc")) + .assertStatusCode(200) + .assertEntity("0: []"); + + route + .run(HttpRequest.create().withUri("/abc?a=b")) + .assertStatusCode(200) + .assertEntity("1: [a -> b, ]"); + + route + .run(HttpRequest.create().withUri("/abc?a=b&c=d")) + .assertStatusCode(200) + .assertEntity("2: [a -> b, c -> d, ]"); + } + + @Test + public void testParametersAsMultiMapExtraction() { + TestRoute route = testRoute( + parameterMultiMap(paramMap -> { + ArrayList keys = new ArrayList(paramMap.keySet()); + Collections.sort(keys); + StringBuilder res = new StringBuilder(); + res.append(paramMap.size()).append(": ["); + for (String key : keys) { + res.append(key).append(" -> ["); + ArrayList values = new ArrayList(paramMap.get(key)); + Collections.sort(values); + for (String value : values) + res.append(value).append(", "); + res.append("], "); + } + res.append(']'); + return complete(res.toString()); + })); + + route + .run(HttpRequest.create().withUri("/abc")) + .assertStatusCode(200) + .assertEntity("0: []"); + + route + .run(HttpRequest.create().withUri("/abc?a=b")) + .assertStatusCode(200) + .assertEntity("1: [a -> [b, ], ]"); + + route + .run(HttpRequest.create().withUri("/abc?a=b&c=d&a=a")) + .assertStatusCode(200) + .assertEntity("2: [a -> [a, b, ], c -> [d, ], ]"); + } + + @Test + public void testParametersAsCollectionExtraction() { + TestRoute route = testRoute( + parameterList(paramEntries -> { + ArrayList> entries = new ArrayList>(paramEntries); + Collections.sort(entries, new Comparator>() { + @Override + public int compare(Map.Entry e1, Map.Entry e2) { + int res = e1.getKey().compareTo(e2.getKey()); + return res == 0 ? e1.getValue().compareTo(e2.getValue()) : res; + } + }); + + StringBuilder res = new StringBuilder(); + res.append(paramEntries.size()).append(": ["); + for (Map.Entry entry : entries) + res.append(entry.getKey()).append(" -> ").append(entry.getValue()).append(", "); + res.append(']'); + return complete(res.toString()); + })); + + route + .run(HttpRequest.create().withUri("/abc")) + .assertStatusCode(200) + .assertEntity("0: []"); + + route + .run(HttpRequest.create().withUri("/abc?a=b&e=f&c=d")) + .assertStatusCode(200) + .assertEntity("3: [a -> b, c -> d, e -> f, ]"); + + route + .run(HttpRequest.create().withUri("/abc?a=b&e=f&c=d&a=z")) + .assertStatusCode(200) + .assertEntity("4: [a -> b, a -> z, c -> d, e -> f, ]"); + } + + @Test + public void testOptionalIntParameterExtraction() { + TestRoute route = testRoute(parameterOptional(StringUnmarshallers.INTEGER, "optionalIntParam", value -> complete(value.toString()))); + + route + .run(HttpRequest.create().withUri("/abc")) + .assertStatusCode(200) + .assertEntity("Optional.empty"); + + route + .run(HttpRequest.create().withUri("/abc?optionalIntParam=23")) + .assertStatusCode(200) + .assertEntity("Optional[23]"); + } + +} diff --git a/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/PathDirectivesTest.java b/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/PathDirectivesTest.java index 20e7dcef25..3954d27e3a 100644 --- a/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/PathDirectivesTest.java +++ b/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/PathDirectivesTest.java @@ -4,362 +4,375 @@ package akka.http.javadsl.server.directives; -import akka.http.javadsl.model.StatusCodes; -import akka.http.javadsl.model.headers.Host; -import akka.http.javadsl.server.values.PathMatcher; +import static akka.http.javadsl.server.PathMatchers.*; + import org.junit.Test; -import java.util.List; -import java.util.UUID; - -import akka.http.javadsl.server.*; -import akka.http.javadsl.server.values.*; -import akka.http.javadsl.testkit.*; +import akka.http.javadsl.model.StatusCodes; +import akka.http.javadsl.model.headers.Host; +import akka.http.javadsl.server.PathMatchers; +import akka.http.javadsl.testkit.JUnitRouteTest; +import akka.http.javadsl.testkit.TestRoute; import akka.http.scaladsl.model.HttpRequest; public class PathDirectivesTest extends JUnitRouteTest { - @Test - public void testPathPrefixAndPath() { - TestRoute route = - testRoute( - pathPrefix("pet").route( - path("cat").route(complete("The cat!")), - path("dog").route(complete("The dog!")), - pathSingleSlash().route(complete("Here are only pets.")) - ) - ); - - route.run(HttpRequest.GET("/pet/")) - .assertEntity("Here are only pets."); - - route.run(HttpRequest.GET("/pet")) // missing trailing slash - .assertStatusCode(404); - - route.run(HttpRequest.GET("/pet/cat")) - .assertEntity("The cat!"); - - route.run(HttpRequest.GET("/pet/dog")) - .assertEntity("The dog!"); - } - - @Test - public void testRawPathPrefix() { - TestRoute route1 = - testRoute( - rawPathPrefix(PathMatchers.SLASH(), "pet", PathMatchers.SLASH(), "", PathMatchers.SLASH(), "cat").route( - complete("The cat!") - ) - ); - - route1.run(HttpRequest.GET("/pet//cat")) - .assertEntity("The cat!"); - - // any suffix allowed - route1.run(HttpRequest.GET("/pet//cat/abcdefg")) - .assertEntity("The cat!"); - - TestRoute route2 = - testRoute( - rawPathPrefix(PathMatchers.SLASH(), "pet", PathMatchers.SLASH(), "", PathMatchers.SLASH(), "cat", PathMatchers.END()).route( - complete("The cat!") - ) - ); - - route2.run(HttpRequest.GET("/pet//cat")) - .assertEntity("The cat!"); - - route2.run(HttpRequest.GET("/pet//cat/abcdefg")) - .assertStatusCode(404); - } - - @Test - public void testSegment() { - PathMatcher name = PathMatchers.segment(); - - TestRoute route = - testRoute( - path("hey", name).route(completeWithValueToString(name)) - ); - - route.run(HttpRequest.GET("/hey/jude")) - .assertEntity("jude"); - } - - @Test - public void testPathEnd() { - TestRoute route = - testRoute( - pathPrefix("test").route( - pathEnd().route(complete("end")), - path("abc").route(complete("abc")) - ) - ); - - route.run(HttpRequest.GET("/test")) - .assertEntity("end"); - - route.run(HttpRequest.GET("/test/abc")) - .assertEntity("abc"); - - route.run(HttpRequest.GET("/xyz")) - .assertStatusCode(404); - } - @Test - public void testSingleSlash() { - TestRoute route = - testRoute( - pathPrefix("test").route( - pathSingleSlash().route(complete("Ok")) - ) - ); - - route.run(HttpRequest.GET("/test/")) - .assertEntity("Ok"); - - route.run(HttpRequest.GET("/test")) - .assertStatusCode(404); - } - - @Test - public void testPathEndOrSingleSlash() { - TestRoute route = - testRoute( - pathPrefix("test").route( - pathEndOrSingleSlash().route(complete("Ok")) - ) - ); - - route.run(HttpRequest.GET("/test")) - .assertEntity("Ok"); - - route.run(HttpRequest.GET("/test/")) - .assertEntity("Ok"); - - route.run(HttpRequest.GET("/abc")) - .assertStatusCode(404); - } - - @Test - public void testRawPathPrefixTest() { - TestRoute route = - testRoute( - rawPathPrefixTest(PathMatchers.SLASH(), "abc").route( - completeWithValueToString(RequestVals.unmatchedPath()) - ) - ); - - route.run(HttpRequest.GET("/abc")) - .assertEntity("/abc"); - - route.run(HttpRequest.GET("/abc/def")) - .assertEntity("/abc/def"); - - route.run(HttpRequest.GET("/abcd/ef")) - .assertEntity("/abcd/ef"); - - route.run(HttpRequest.GET("/xyz/def")) - .assertStatusCode(404); - } - @Test - public void testPathPrefixTest() { - TestRoute route = - testRoute( - pathPrefixTest("abc").route(completeWithValueToString(RequestVals.unmatchedPath())) - ); - - route.run(HttpRequest.GET("/abc")) - .assertEntity("/abc"); - - route.run(HttpRequest.GET("/abc/def")) - .assertEntity("/abc/def"); - - route.run(HttpRequest.GET("/abcd/ef")) - .assertEntity("/abcd/ef"); - - route.run(HttpRequest.GET("/xyz/def")) - .assertStatusCode(404); - } - @Test - public void testPathSuffix() { - TestRoute route = - testRoute( - pathSuffix(PathMatchers.SLASH(), "abc").route(completeWithValueToString(RequestVals.unmatchedPath())) - ); - - route.run(HttpRequest.GET("/test/abc/")) - .assertEntity("/test/"); - - route.run(HttpRequest.GET("/abc/")) - .assertEntity("/"); - - route.run(HttpRequest.GET("/abc/def")) - .assertStatusCode(404); - - route.run(HttpRequest.GET("/abc")) - .assertStatusCode(404); - } - @Test - public void testPathSuffixTest() { - TestRoute route = - testRoute( - pathSuffixTest("abc").route(completeWithValueToString(RequestVals.unmatchedPath())) - ); - - route.run(HttpRequest.GET("/test/abc")) - .assertEntity("/test/abc"); - - route.run(HttpRequest.GET("/abc")) - .assertEntity("/abc"); - - route.run(HttpRequest.GET("/abc/def")) - .assertStatusCode(404); - } - - @Test - public void testIntegerMatcher() { - PathMatcher age = PathMatchers.intValue(); - - TestRoute route = - testRoute( - path("age", age).route(completeWithValueToString(age)) - ); - - route.run(HttpRequest.GET("/age/38")) - .assertEntity("38"); - - route.run(HttpRequest.GET("/age/abc")) - .assertStatusCode(404); - } - @Test - public void testTwoVals() { - // tests that `x` and `y` have different identities which is important for - // retrieving the values - PathMatcher x = PathMatchers.intValue(); - PathMatcher y = PathMatchers.intValue(); - - TestRoute route = - testRoute( - path("multiply", x, "with", y).route( - handleWith2(x, y, new Handler2() { - @Override - public RouteResult apply(RequestContext ctx, Integer x, Integer y) { - return ctx.complete(String.format("%d * %d = %d", x, y, x * y)); - } - }) - ) - ); - - route.run(HttpRequest.GET("/multiply/3/with/6")) - .assertEntity("3 * 6 = 18"); - } - - @Test - public void testHexIntegerMatcher() { - PathMatcher color = PathMatchers.hexIntValue(); - - TestRoute route = - testRoute( - path("color", color).route(completeWithValueToString(color)) - ); - - route.run(HttpRequest.GET("/color/a0c2ef")) - .assertEntity(Integer.toString(0xa0c2ef)); - } - - @Test - public void testLongMatcher() { - PathMatcher bigAge = PathMatchers.longValue(); - - TestRoute route = - testRoute( - path("bigage", bigAge).route(completeWithValueToString(bigAge)) - ); - - route.run(HttpRequest.GET("/bigage/12345678901")) - .assertEntity("12345678901"); - } - - @Test - public void testHexLongMatcher() { - PathMatcher code = PathMatchers.hexLongValue(); - - TestRoute route = - testRoute( - path("code", code).route(completeWithValueToString(code)) - ); - - route.run(HttpRequest.GET("/code/a0b1c2d3e4f5")) - .assertEntity(Long.toString(0xa0b1c2d3e4f5L)); - } - - @Test - public void testRestMatcher() { - PathMatcher rest = PathMatchers.rest(); - - TestRoute route = - testRoute( - path("pets", rest).route(completeWithValueToString(rest)) - ); - - route.run(HttpRequest.GET("/pets/afdaoisd/asda/sfasfasf/asf")) - .assertEntity("afdaoisd/asda/sfasfasf/asf"); - } - - @Test - public void testUUIDMatcher() { - PathMatcher uuid = PathMatchers.uuid(); - - TestRoute route = - testRoute( - path("by-uuid", uuid).route(completeWithValueToString(uuid)) - ); - - route.run(HttpRequest.GET("/by-uuid/6ba7b811-9dad-11d1-80b4-00c04fd430c8")) - .assertEntity("6ba7b811-9dad-11d1-80b4-00c04fd430c8"); - } - - @Test - public void testSegmentsMatcher() { - PathMatcher> segments = PathMatchers.segments(); - - TestRoute route = - testRoute( - path("pets", segments).route(completeWithValueToString(segments)) - ); - - route.run(HttpRequest.GET("/pets/cat/dog")) - .assertEntity("[cat, dog]"); - } - - @Test - public void testRedirectToTrailingSlashIfMissing() { - TestRoute route = - testRoute( - redirectToTrailingSlashIfMissing(StatusCodes.FOUND, complete("Ok")) - ); - - route.run(HttpRequest.GET("/home").addHeader(Host.create("example.com"))) - .assertStatusCode(302) - .assertHeaderExists("Location", "http://example.com/home/"); - - route.run(HttpRequest.GET("/home/")) - .assertStatusCode(200) - .assertEntity("Ok"); - } - - @Test - public void testRedirectToNoTrailingSlashIfPresent() { - TestRoute route = - testRoute( - redirectToNoTrailingSlashIfPresent(StatusCodes.FOUND, complete("Ok")) - ); - - route.run(HttpRequest.GET("/home/").addHeader(Host.create("example.com"))) - .assertStatusCode(302) - .assertHeaderExists("Location", "http://example.com/home"); - - route.run(HttpRequest.GET("/home")) - .assertStatusCode(200) - .assertEntity("Ok"); - } + @Test + public void testPathPrefixAndPath() { + TestRoute route = testRoute( + pathPrefix("pet", () -> route( + path("cat", () -> complete("The cat!")), + path("dog", () -> complete("The dog!")), + pathSingleSlash(() -> complete("Here are only pets.")) + )) + ); + + route.run(HttpRequest.GET("/pet/")) + .assertEntity("Here are only pets."); + + route.run(HttpRequest.GET("/pet")) // missing trailing slash + .assertStatusCode(404); + + route.run(HttpRequest.GET("/pet/cat")) + .assertEntity("The cat!"); + + route.run(HttpRequest.GET("/pet/dog")) + .assertEntity("The dog!"); + } + + @Test + public void testRawPathPrefix() { + TestRoute route1 = testRoute( + rawPathPrefix(separateOnSlashes("/pet//cat"), () -> complete("The cat!")) + ); + + route1.run(HttpRequest.GET("/pet//cat")) + .assertEntity("The cat!"); + + // any suffix allowed + route1.run(HttpRequest.GET("/pet//cat/abcdefg")) + .assertEntity("The cat!"); + + TestRoute route2 = testRoute( + rawPathPrefix(separateOnSlashes("/pet//cat"), () -> + pathEnd(() -> complete("The cat!")) + ) + ); + + route2.run(HttpRequest.GET("/pet//cat")) + .assertEntity("The cat!"); + + route2.run(HttpRequest.GET("/pet//cat/abcdefg")) + .assertStatusCode(404); + } + + @Test + public void testSegment() { + TestRoute route = + testRoute( + pathPrefix("hey", () -> path(name -> complete(name) ) ) + ); + + route.run(HttpRequest.GET("/hey/jude")) + .assertEntity("jude"); + } + + @Test + public void testPathEnd() { + TestRoute route = + testRoute( + pathPrefix("test", () -> route( + pathEnd(() -> complete("end")), + path("abc", () -> complete("abc")) + )) + ); + + route.run(HttpRequest.GET("/test")) + .assertEntity("end"); + + route.run(HttpRequest.GET("/test/abc")) + .assertEntity("abc"); + + route.run(HttpRequest.GET("/xyz")) + .assertStatusCode(404); + } + + @Test + public void testSingleSlash() { + TestRoute route = + testRoute( + pathPrefix("test", () -> + pathSingleSlash(() -> complete("Ok")) + ) + ); + + route.run(HttpRequest.GET("/test/")) + .assertEntity("Ok"); + + route.run(HttpRequest.GET("/test")) + .assertStatusCode(404); + } + + @Test + public void testPathEndOrSingleSlash() { + TestRoute route = + testRoute( + pathPrefix("test", () -> + pathEndOrSingleSlash(() -> complete("Ok")) + ) + ); + + route.run(HttpRequest.GET("/test")) + .assertEntity("Ok"); + + route.run(HttpRequest.GET("/test/")) + .assertEntity("Ok"); + + route.run(HttpRequest.GET("/abc")) + .assertStatusCode(404); + } + + @Test + public void testRawPathPrefixTest() { + TestRoute route = + testRoute( + rawPathPrefixTest(slash().concat(segment("abc")), () -> + extractUnmatchedPath(s -> complete(s)) + ) + ); + + route.run(HttpRequest.GET("/abc")) + .assertEntity("/abc"); + + route.run(HttpRequest.GET("/abc/def")) + .assertEntity("/abc/def"); + + route.run(HttpRequest.GET("/abcd/ef")) + .assertEntity("/abcd/ef"); + + route.run(HttpRequest.GET("/xyz/def")) + .assertStatusCode(404); + } + + @Test + public void testPathPrefixTest() { + TestRoute route = + testRoute( + pathPrefixTest("abc", () -> extractUnmatchedPath(s -> complete(s))) + ); + + route.run(HttpRequest.GET("/abc")) + .assertEntity("/abc"); + + route.run(HttpRequest.GET("/abc/def")) + .assertEntity("/abc/def"); + + route.run(HttpRequest.GET("/abcd/ef")) + .assertEntity("/abcd/ef"); + + route.run(HttpRequest.GET("/xyz/def")) + .assertStatusCode(404); + } + + @Test + public void testPathSuffix() { + TestRoute route = + testRoute( + pathSuffix(PathMatchers.slash().concat(segment("abc")), () -> extractUnmatchedPath(s -> complete(s))) + ); + + route.run(HttpRequest.GET("/test/abc/")) + .assertEntity("/test/"); + + route.run(HttpRequest.GET("/abc/")) + .assertEntity("/"); + + route.run(HttpRequest.GET("/abc/def")) + .assertStatusCode(404); + + route.run(HttpRequest.GET("/abc")) + .assertStatusCode(404); + } + + @Test + public void testPathSuffixTest() { + TestRoute route = + testRoute( + pathSuffixTest("abc", () -> extractUnmatchedPath(s -> complete(s))) + ); + + route.run(HttpRequest.GET("/test/abc")) + .assertEntity("/test/abc"); + + route.run(HttpRequest.GET("/abc")) + .assertEntity("/abc"); + + route.run(HttpRequest.GET("/abc/def")) + .assertStatusCode(404); + } + + @Test + public void testIntegerMatcher() { + TestRoute route = + testRoute( + path(segment("age").slash(integerSegment()), value -> complete(value.toString())) + ); + + route.run(HttpRequest.GET("/age/38")) + .assertEntity("38"); + + route.run(HttpRequest.GET("/age/abc")) + .assertStatusCode(404); + } + + @Test + public void testIntegerConcatMatcher() { + TestRoute route = + testRoute( + // this is testing that we can express the same path using slash() and concat() as with nexting inside slash() + path(segment("age").slash().concat(integerSegment()), value -> complete(value.toString())) + ); + + route.run(HttpRequest.GET("/age/38")) + .assertEntity("38"); + + route.run(HttpRequest.GET("/age/abc")) + .assertStatusCode(404); + } + + @Test + public void testTwoVals() { + // tests that `x` and `y` have different identities which is important for + // retrieving the values + + TestRoute route = + testRoute( + path(segment("multiply").slash(integerSegment()).slash("with").slash(integerSegment()), (x, y) -> + complete(String.format("%d * %d = %d", x, y, x * y)) + ) + ); + + route.run(HttpRequest.GET("/multiply/3/with/6")) + .assertEntity("3 * 6 = 18"); + } + + @Test + public void testHexIntegerMatcher() { + TestRoute route = + testRoute( + path(segment("color").slash(hexIntegerSegment()), color -> complete(color.toString())) + ); + + route.run(HttpRequest.GET("/color/a0c2ef")) + .assertEntity(Integer.toString(0xa0c2ef)); + } + + @Test + public void testLongMatcher() { + TestRoute route = + testRoute( + path(segment("bigage").slash(longSegment()), bigAge -> complete(bigAge.toString())) + ); + + route.run(HttpRequest.GET("/bigage/12345678901")) + .assertEntity("12345678901"); + } + + @Test + public void testSegmentMatcher() { + TestRoute route = + testRoute( + path(segment("string").slash(segment()), bigAge -> complete(bigAge)) + ); + + route.run(HttpRequest.GET("/string/hello-it-is-me")) + .assertEntity("hello-it-is-me"); + } + + @Test + public void testHexLongMatcher() { + TestRoute route = + testRoute( + path(segment("code").slash(hexLongSegment()), code -> complete(code.toString())) + ); + + route.run(HttpRequest.GET("/code/a0b1c2d3e4f5")) + .assertEntity(Long.toString(0xa0b1c2d3e4f5L)); + } + + @Test + public void testRemainingMatcher() { + TestRoute route = + testRoute( + path(remaining(), remainingPath -> complete(remainingPath)) + ); + + route.run(HttpRequest.GET("/pets/afdaoisd/asda/sfasfasf/asf")) + .assertEntity("pets/afdaoisd/asda/sfasfasf/asf"); + } + + @Test + public void testRemainingMatcherInsidePath() { + TestRoute route = + testRoute( + pathPrefix("pets", () -> + path(remaining(), remainingPath -> complete(remainingPath))) + ); + + route.run(HttpRequest.GET("/pets/afdaoisd/asda/sfasfasf/asf")) + .assertEntity("afdaoisd/asda/sfasfasf/asf"); + } + + @Test + public void testUUIDMatcher() { + TestRoute route = + testRoute( + path(segment("by-uuid").slash(uuidSegment()), uuid -> complete(uuid.toString())) + ); + + route.run(HttpRequest.GET("/by-uuid/6ba7b811-9dad-11d1-80b4-00c04fd430c8")) + .assertEntity("6ba7b811-9dad-11d1-80b4-00c04fd430c8"); + } + + @Test + public void testSegmentsMatcher() { + TestRoute route = + testRoute( + path(segment("pets").slash(segments()), segments -> complete(segments.toString())) + ); + + route.run(HttpRequest.GET("/pets/cat/dog")) + .assertEntity("[cat, dog]"); + } + + @Test + public void testRedirectToTrailingSlashIfMissing() { + TestRoute route = + testRoute( + redirectToTrailingSlashIfMissing(StatusCodes.FOUND, () -> complete("Ok")) + ); + + route.run(HttpRequest.GET("/home").addHeader(Host.create("example.com"))) + .assertStatusCode(302) + .assertHeaderExists("Location", "http://example.com/home/"); + + route.run(HttpRequest.GET("/home/")) + .assertStatusCode(200) + .assertEntity("Ok"); + } + + @Test + public void testRedirectToNoTrailingSlashIfPresent() { + TestRoute route = + testRoute( + redirectToNoTrailingSlashIfPresent(StatusCodes.FOUND, () -> complete("Ok")) + ); + + route.run(HttpRequest.GET("/home/").addHeader(Host.create("example.com"))) + .assertStatusCode(302) + .assertHeaderExists("Location", "http://example.com/home"); + + route.run(HttpRequest.GET("/home")) + .assertStatusCode(200) + .assertEntity("Ok"); + } } diff --git a/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/RouteDirectivesTest.java b/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/RouteDirectivesTest.java index 5d5438c736..0b7da8b6d0 100644 --- a/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/RouteDirectivesTest.java +++ b/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/RouteDirectivesTest.java @@ -4,25 +4,18 @@ package akka.http.javadsl.server.directives; -import akka.dispatch.Futures; -import akka.dispatch.Mapper; import akka.http.javadsl.model.HttpRequest; -import akka.http.javadsl.model.RequestEntity; import akka.http.javadsl.model.StatusCodes; import akka.http.javadsl.model.Uri; import akka.http.javadsl.model.headers.Location; -import akka.http.javadsl.server.RequestContext; -import akka.http.javadsl.server.RouteResult; import akka.http.javadsl.testkit.JUnitRouteTest; import akka.http.javadsl.testkit.TestRoute; -import akka.japi.function.Function; import akka.stream.javadsl.Sink; import akka.util.ByteString; import org.junit.Test; -import scala.concurrent.ExecutionContext$; -import scala.concurrent.forkjoin.ForkJoinPool; public class RouteDirectivesTest extends JUnitRouteTest { + @Test public void testRedirection() { Uri targetUri = Uri.create("http://example.com"); @@ -38,48 +31,63 @@ public class RouteDirectivesTest extends JUnitRouteTest { } @Test - public void testEntitySizeLimit() { + public void testEntitySizeNoLimit() { TestRoute route = testRoute( - path("no-limit") - .route( - handleWith(new Function() { - @Override - public RouteResult apply(final RequestContext ctx) throws Exception { - final RequestEntity entity = ctx.request().entity(); - return ctx.completeWith( - entity - .withoutSizeLimit() - .getDataBytes() - .runWith(Sink.head(), ctx.materializer()) - .thenApplyAsync(s -> ctx.complete(s.utf8String()), ctx.executionContext())); - } - })), - path("limit-5") - .route( - handleWith(ctx -> { - final RequestEntity entity = ctx.request().entity(); - return ctx.completeWith( - entity - .withSizeLimit(5) - .getDataBytes() - .runWith(Sink.head(), ctx.materializer()) - .thenApplyAsync(s -> ctx.complete(s.utf8String()), ctx.executionContext())); - })) + path("no-limit", () -> + extractEntity(entity -> + extractMaterializer(mat -> + onSuccess(() -> entity + .withoutSizeLimit() + .getDataBytes() + .runWith(Sink.head(), mat), + bytes -> complete(bytes.utf8String()) + ) + ) + ) + ) ); route .run(HttpRequest.create("/no-limit").withEntity("1234567890")) - .assertStatusCode(200) + .assertStatusCode(StatusCodes.OK) .assertEntity("1234567890"); + } + + private TestRoute routeWithLimit() { + return testRoute( + path("limit-5", () -> + extractEntity(entity -> + extractMaterializer(mat -> + onSuccess(() -> entity + .withSizeLimit(5) + .getDataBytes() + .runWith(Sink.head(), mat), + bytes -> complete(bytes.utf8String()) + ) + ) + ) + ) + ); + } + + @Test + public void testEntitySizeWithinLimit() { + TestRoute route = routeWithLimit(); route .run(HttpRequest.create("/limit-5").withEntity("12345")) - .assertStatusCode(200) + .assertStatusCode(StatusCodes.OK) .assertEntity("12345"); + } + + @Test + public void testEntitySizeLargerThanLimit() { + TestRoute route = routeWithLimit(); + route .run(HttpRequest.create("/limit-5").withEntity("1234567890")) - .assertStatusCode(500) - .assertEntity("There was an internal server error."); + .assertStatusCode(StatusCodes.INTERNAL_SERVER_ERROR) + .assertEntity("There was an internal server error."); } } diff --git a/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/SchemeDirectivesTest.java b/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/SchemeDirectivesTest.java index 2fc0aee5eb..e017d0a8b4 100644 --- a/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/SchemeDirectivesTest.java +++ b/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/SchemeDirectivesTest.java @@ -5,24 +5,42 @@ package akka.http.javadsl.server.directives; import akka.http.javadsl.model.HttpRequest; +import akka.http.javadsl.model.StatusCodes; import akka.http.javadsl.model.Uri; import akka.http.javadsl.testkit.JUnitRouteTest; import akka.http.javadsl.testkit.TestRoute; import org.junit.Test; public class SchemeDirectivesTest extends JUnitRouteTest { - @Test - public void testSchemeFilter() { - TestRoute route = testRoute(scheme("http", complete("OK!"))); + @Test + public void testSchemeFilter() { + TestRoute route = testRoute(scheme("http", () -> complete("OK!"))); + + route + .run(HttpRequest.create().withUri(Uri.create("http://example.org"))) + .assertStatusCode(StatusCodes.OK) + .assertEntity("OK!"); + + route + .run(HttpRequest.create().withUri(Uri.create("https://example.org"))) + .assertStatusCode(StatusCodes.BAD_REQUEST) + .assertEntity("Uri scheme not allowed, supported schemes: http"); + } + + @Test + public void testSchemeExtraction() { + TestRoute route = testRoute(extractScheme(this::complete)); + + route + .run(HttpRequest.create().withUri(Uri.create("http://example.org"))) + .assertStatusCode(StatusCodes.OK) + .assertEntity("http"); + + route + .run(HttpRequest.create().withUri(Uri.create("https://example.org"))) + .assertStatusCode(StatusCodes.OK) + .assertEntity("https"); + } - route - .run(HttpRequest.create().withUri(Uri.create("http://example.org"))) - .assertStatusCode(200) - .assertEntity("OK!"); - route - .run(HttpRequest.create().withUri(Uri.create("https://example.org"))) - .assertStatusCode(400) - .assertEntity("Uri scheme not allowed, supported schemes: http"); - } } diff --git a/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/SecurityDirectivesTest.java b/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/SecurityDirectivesTest.java new file mode 100644 index 0000000000..659ac718bc --- /dev/null +++ b/akka-http-tests/src/test/java/akka/http/javadsl/server/directives/SecurityDirectivesTest.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2009-2016 Lightbend Inc. + */ + +package akka.http.javadsl.server.directives; + +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import akka.http.javadsl.model.StatusCodes; +import org.junit.Test; + +import scala.util.Left; +import scala.util.Right; +import akka.http.javadsl.server.*; +import akka.http.javadsl.model.HttpRequest; +import akka.http.javadsl.model.headers.Authorization; +import akka.http.javadsl.model.headers.BasicHttpCredentials; +import akka.http.javadsl.model.headers.HttpChallenge; +import akka.http.javadsl.testkit.*; + +import static akka.http.javadsl.server.PathMatchers.*; + +public class SecurityDirectivesTest extends JUnitRouteTest { + + // These authenticators don't have to be async; they're just written that way to test the API. + private CompletionStage> authenticateUser(Optional creds) { + return CompletableFuture.completedFuture( + creds.filter(c -> + c.identifier().equals("sina") && c.verify("1234") + ).map(c -> + "sina" + )); + } + + private CompletionStage> authenticateToken(Optional creds) { + System.out.println(creds); + + return CompletableFuture.completedFuture( + creds.filter(c -> + c.verify("myToken") + ).map(c -> + "myToken" + )); + } + + public Route securedRoute(String identifier) { + return complete("Identified as " + identifier + "!"); + } + + TestRoute route = + testRoute( + path("basicSecure", () -> + authenticateBasicAsync("test-realm", this::authenticateUser, this::securedRoute) + ), + path("oauthSecure", () -> + authenticateOAuth2Async("test-realm", this::authenticateToken, this::securedRoute) + ), + path(segment("authorize").slash(integerSegment()), (n) -> + authorize(() -> n == 1, () -> complete("authorized")) + ), + path(segment("authorizeAsync").slash(integerSegment()), (n) -> + authorizeAsync(() -> CompletableFuture.completedFuture(n == 1), () -> complete("authorized")) + ), + path(segment("authorizeWithRequestContext").slash(integerSegment()), (n) -> + authorizeWithRequestContext((ctx) -> n == 1, () -> complete("authorized")) + ), + path(segment("authorizeAsyncWithRequestContext").slash(integerSegment()), (n) -> + authorizeAsyncWithRequestContext((ctx) -> CompletableFuture.completedFuture(n == 1), () -> complete("authorized")) + ) + ); + + @Test + public void testCorrectUser() { + HttpRequest authenticatedRequest = + HttpRequest.GET("/basicSecure") + .addHeader(Authorization.basic("sina", "1234")); + + route.run(authenticatedRequest) + .assertStatusCode(StatusCodes.OK) + .assertEntity("Identified as sina!"); + } + + @Test + public void testCorrectToken() { + HttpRequest authenticatedRequest = + HttpRequest.GET("/oauthSecure") + .addHeader(Authorization.oauth2("myToken")); + + route.run(authenticatedRequest) + .assertStatusCode(StatusCodes.OK) + .assertEntity("Identified as myToken!"); + } + + @Test + public void testRejectAnonymousAccess() { + route.run(HttpRequest.GET("/basicSecure")) + .assertStatusCode(StatusCodes.UNAUTHORIZED) + .assertEntity("The resource requires authentication, which was not supplied with the request") + .assertHeaderExists("WWW-Authenticate", "Basic realm=\"test-realm\""); + } + + @Test + public void testRejectUnknownUser() { + HttpRequest authenticatedRequest = + HttpRequest.GET("/basicSecure") + .addHeader(Authorization.basic("joe", "0000")); + + route.run(authenticatedRequest) + .assertStatusCode(StatusCodes.UNAUTHORIZED) + .assertEntity("The supplied authentication is invalid"); + } + + @Test + public void testRejectWrongPassword() { + HttpRequest authenticatedRequest = + HttpRequest.GET("/basicSecure") + .addHeader(Authorization.basic("sina", "1235")); + + route.run(authenticatedRequest) + .assertStatusCode(StatusCodes.UNAUTHORIZED) + .assertEntity("The supplied authentication is invalid"); + } + + @Test + public void testAuthenticateOrRejectWithChallenge() { + TestRoute route = testRoute( + path("basicSecure", () -> + authenticateOrRejectWithChallenge(BasicHttpCredentials.class, cred -> { + if (cred.isPresent()) { + return CompletableFuture.completedFuture(Right.apply(cred.get().token())); + } else { + return CompletableFuture.completedFuture(Left.apply(HttpChallenge.create("Basic", "test-realm"))); + } + }, this::securedRoute) + ) + ); + + Authorization auth = Authorization.basic("sina", "1234"); + HttpRequest authenticatedRequest = + HttpRequest.GET("/basicSecure") + .addHeader(auth); + + route.run(authenticatedRequest) + .assertStatusCode(StatusCodes.OK) + .assertEntity("Identified as " + auth.credentials().token() + "!"); + } + + @Test + public void testAuthorize() { + route.run(HttpRequest.GET("/authorize/1")) + .assertStatusCode(StatusCodes.OK) + .assertEntity("authorized"); + + route.run(HttpRequest.GET("/authorize/0")) + .assertStatusCode(StatusCodes.FORBIDDEN); + } + + @Test + public void testAuthorizeAsync() { + route.run(HttpRequest.GET("/authorizeAsync/1")) + .assertStatusCode(StatusCodes.OK) + .assertEntity("authorized"); + + route.run(HttpRequest.GET("/authorizeAsync/0")) + .assertStatusCode(StatusCodes.FORBIDDEN); + } + + @Test + public void testAuthorizeWithRequestContext() { + route.run(HttpRequest.GET("/authorizeWithRequestContext/1")) + .assertStatusCode(StatusCodes.OK) + .assertEntity("authorized"); + + route.run(HttpRequest.GET("/authorizeWithRequestContext/0")) + .assertStatusCode(StatusCodes.FORBIDDEN); + } + + + @Test + public void testAuthorizeAsyncWithRequestContext() { + route.run(HttpRequest.GET("/authorizeAsyncWithRequestContext/1")) + .assertStatusCode(StatusCodes.OK) + .assertEntity("authorized"); + + route.run(HttpRequest.GET("/authorizeAsyncWithRequestContext/0")) + .assertStatusCode(StatusCodes.FORBIDDEN); + + } + +} diff --git a/akka-http-tests/src/test/java/akka/http/javadsl/server/examples/petstore/PetStoreAPITest.java b/akka-http-tests/src/test/java/akka/http/javadsl/server/examples/petstore/PetStoreAPITest.java index 7e33359b80..6488824ba9 100644 --- a/akka-http-tests/src/test/java/akka/http/javadsl/server/examples/petstore/PetStoreAPITest.java +++ b/akka-http-tests/src/test/java/akka/http/javadsl/server/examples/petstore/PetStoreAPITest.java @@ -7,73 +7,81 @@ package akka.http.javadsl.server.examples.petstore; import akka.http.javadsl.marshallers.jackson.Jackson; import akka.http.javadsl.model.HttpRequest; import akka.http.javadsl.model.MediaTypes; +import akka.http.javadsl.model.StatusCodes; import akka.http.javadsl.testkit.*; + import static org.junit.Assert.*; import akka.http.javadsl.testkit.TestRoute; + import org.junit.Test; import java.util.HashMap; import java.util.Map; public class PetStoreAPITest extends JUnitRouteTest { - @Test - public void testGetPet() { - TestResponse response = createRoute().run(HttpRequest.GET("/pet/1")); + @Test + public void testGetPet() { + TestRouteResult response = createRoute().run(HttpRequest.GET("/pet/1")); - response - .assertStatusCode(200) - .assertMediaType("application/json"); + response + .assertStatusCode(StatusCodes.OK) + .assertMediaType("application/json"); - Pet pet = response.entityAs(Jackson.jsonAs(Pet.class)); - assertEquals("cat", pet.getName()); - assertEquals(1, pet.getId()); - } - @Test - public void testGetMissingPet() { - createRoute().run(HttpRequest.GET("/pet/999")) - .assertStatusCode(404); - } - @Test - public void testPutPet() { - HttpRequest request = - HttpRequest.PUT("/pet/1") - .withEntity(MediaTypes.APPLICATION_JSON.toContentType(), "{\"id\": 1, \"name\": \"giraffe\"}"); + Pet pet = response.entity(Jackson.unmarshaller(Pet.class)); + assertEquals("cat", pet.getName()); + assertEquals(1, pet.getId()); + } - TestResponse response = createRoute().run(request); + @Test + public void testGetMissingPet() { + createRoute().run(HttpRequest.GET("/pet/999")) + .assertStatusCode(StatusCodes.NOT_FOUND); + } - response.assertStatusCode(200); + @Test + public void testPutPet() { + HttpRequest request = + HttpRequest.PUT("/pet/1") + .withEntity(MediaTypes.APPLICATION_JSON.toContentType(), "{\"id\": 1, \"name\": \"giraffe\"}"); - Pet pet = response.entityAs(Jackson.jsonAs(Pet.class)); - assertEquals("giraffe", pet.getName()); - assertEquals(1, pet.getId()); - } - @Test - public void testDeletePet() { - Map data = createData(); + TestRouteResult response = createRoute().run(request); - HttpRequest request = HttpRequest.DELETE("/pet/0"); + response.assertStatusCode(StatusCodes.OK); - createRoute(data).run(request) - .assertStatusCode(200); + Pet pet = response.entity(Jackson.unmarshaller(Pet.class)); + assertEquals("giraffe", pet.getName()); + assertEquals(1, pet.getId()); + } - // test actual deletion from data store - assertFalse(data.containsKey(0)); - } + @Test + public void testDeletePet() { + Map data = createData(); - private TestRoute createRoute() { - return createRoute(createData()); - } - private TestRoute createRoute(Map pets) { - return testRoute(PetStoreExample.appRoute(pets)); - } - private Map createData() { - Map pets = new HashMap(); - Pet dog = new Pet(0, "dog"); - Pet cat = new Pet(1, "cat"); - pets.put(0, dog); - pets.put(1, cat); + HttpRequest request = HttpRequest.DELETE("/pet/0"); - return pets; - } + createRoute(data).run(request) + .assertStatusCode(StatusCodes.OK); + + // test actual deletion from data store + assertFalse(data.containsKey(0)); + } + + private TestRoute createRoute() { + return createRoute(createData()); + } + + private TestRoute createRoute(Map pets) { + return testRoute(PetStoreExample.appRoute(pets)); + } + + private Map createData() { + Map pets = new HashMap(); + Pet dog = new Pet(0, "dog"); + Pet cat = new Pet(1, "cat"); + pets.put(0, dog); + pets.put(1, cat); + + return pets; + } } diff --git a/akka-http-tests/src/test/java/akka/http/javadsl/server/examples/simple/SimpleServerTest.java b/akka-http-tests/src/test/java/akka/http/javadsl/server/examples/simple/SimpleServerTest.java index 2319c37cb9..3ca43c73ba 100644 --- a/akka-http-tests/src/test/java/akka/http/javadsl/server/examples/simple/SimpleServerTest.java +++ b/akka-http-tests/src/test/java/akka/http/javadsl/server/examples/simple/SimpleServerTest.java @@ -6,6 +6,7 @@ package akka.http.javadsl.server.examples.simple; import akka.http.javadsl.model.HttpRequest; import akka.http.javadsl.testkit.*; + import org.junit.Test; public class SimpleServerTest extends JUnitRouteTest { @@ -13,7 +14,7 @@ public class SimpleServerTest extends JUnitRouteTest { @Test public void testAdd() { - TestResponse response = route.run(HttpRequest.GET("/add?x=42&y=23")); + TestRouteResult response = route.run(HttpRequest.GET("/add?x=42&y=23")); response .assertStatusCode(200) @@ -22,7 +23,7 @@ public class SimpleServerTest extends JUnitRouteTest { @Test public void testMultiplyAsync() { - TestResponse response = route.run(HttpRequest.GET("/multiplyAsync/42/23")); + TestRouteResult response = route.run(HttpRequest.GET("/multiplyAsync/42/23")); response .assertStatusCode(200) @@ -31,7 +32,7 @@ public class SimpleServerTest extends JUnitRouteTest { @Test public void testPostWithBody() { - TestResponse response = route.run(HttpRequest.POST("/hello").withEntity("John")); + TestRouteResult response = route.run(HttpRequest.POST("/hello").withEntity("John")); response .assertStatusCode(200) diff --git a/akka-http-tests/src/test/java/akka/http/javadsl/server/values/CookiesTest.java b/akka-http-tests/src/test/java/akka/http/javadsl/server/values/CookiesTest.java deleted file mode 100644 index da318df9fe..0000000000 --- a/akka-http-tests/src/test/java/akka/http/javadsl/server/values/CookiesTest.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.javadsl.server.values; - -import akka.http.javadsl.model.DateTime; -import akka.http.javadsl.model.HttpRequest; -import akka.http.javadsl.model.headers.HttpCookie; -import akka.http.javadsl.testkit.JUnitRouteTest; -import akka.http.javadsl.testkit.TestRoute; -import org.junit.Test; - -public class CookiesTest extends JUnitRouteTest { - Cookie userIdCookie = Cookies.create("userId").withDomain("example.com").withPath("/admin"); - - @Test - public void testCookieValue() { - TestRoute route = - testRoute(completeWithValueToString(userIdCookie.value())); - - route.run(HttpRequest.create()) - .assertStatusCode(400) - .assertEntity("Request is missing required cookie 'userId'"); - - route.run(HttpRequest.create().addHeader(akka.http.javadsl.model.headers.Cookie.create("userId", "12345"))) - .assertStatusCode(200) - .assertEntity("12345"); - } - @Test - public void testCookieOptionalValue() { - TestRoute route = - testRoute(completeWithValueToString(userIdCookie.optionalValue())); - - route.run(HttpRequest.create()) - .assertStatusCode(200) - .assertEntity("None"); - - route.run(HttpRequest.create().addHeader(akka.http.javadsl.model.headers.Cookie.create("userId", "12345"))) - .assertStatusCode(200) - .assertEntity("Some(12345)"); - } - @Test - public void testCookieSet() { - TestRoute route = - testRoute( - userIdCookie.set("12").route( - complete("OK!") - ) - ); - - route.run(HttpRequest.create()) - .assertStatusCode(200) - .assertHeaderExists("Set-Cookie", "userId=12; Domain=example.com; Path=/admin") - .assertEntity("OK!"); - } - @Test - public void testDeleteCookie() { - TestRoute route = - testRoute( - userIdCookie.delete( - complete("OK!") - ) - ); - - route.run(HttpRequest.create()) - .assertStatusCode(200) - .assertHeaderExists("Set-Cookie", "userId=deleted; Expires=Wed, 01 Jan 1800 00:00:00 GMT; Domain=example.com; Path=/admin") - .assertEntity("OK!"); - } -} diff --git a/akka-http-tests/src/test/java/akka/http/javadsl/server/values/FormFieldsTest.java b/akka-http-tests/src/test/java/akka/http/javadsl/server/values/FormFieldsTest.java index b4833ca823..a13eb5cc61 100644 --- a/akka-http-tests/src/test/java/akka/http/javadsl/server/values/FormFieldsTest.java +++ b/akka-http-tests/src/test/java/akka/http/javadsl/server/values/FormFieldsTest.java @@ -4,33 +4,17 @@ package akka.http.javadsl.server.values; +import org.junit.Test; + import akka.http.javadsl.model.HttpCharsets; import akka.http.javadsl.model.HttpRequest; import akka.http.javadsl.model.MediaTypes; -import akka.http.javadsl.server.RequestVal; +import akka.http.javadsl.server.StringUnmarshallers; import akka.http.javadsl.testkit.JUnitRouteTest; import akka.http.javadsl.testkit.TestRoute; import akka.japi.Pair; -import org.junit.Test; - -import java.util.Optional; public class FormFieldsTest extends JUnitRouteTest { - static FormField stringParam = FormFields.stringValue("stringParam"); - static FormField byteParam = FormFields.byteValue("byteParam"); - static FormField shortParam = FormFields.shortValue("shortParam"); - static FormField intParam = FormFields.intValue("intParam"); - static FormField longParam = FormFields.longValue("longParam"); - static FormField floatParam = FormFields.floatValue("floatParam"); - static FormField doubleParam = FormFields.doubleValue("doubleParam"); - - static FormField hexByteParam = FormFields.hexByteValue("hexByteParam"); - static FormField hexShortParam = FormFields.hexShortValue("hexShortParam"); - static FormField hexIntParam = FormFields.hexIntValue("hexIntParam"); - static FormField hexLongParam = FormFields.hexLongValue("hexLongParam"); - - static RequestVal nameWithDefault = FormFields.stringValue("nameWithDefault").withDefault("John Doe"); - static RequestVal> optionalIntParam = FormFields.intValue("optionalIntParam").optional(); private Pair param(String name, String value) { return Pair.create(name, value); @@ -59,7 +43,7 @@ public class FormFieldsTest extends JUnitRouteTest { @Test public void testStringFormFieldExtraction() { - TestRoute route = testRoute(completeWithValueToString(stringParam)); + TestRoute route = testRoute(formField("stringParam", value -> complete(value))); route .run(HttpRequest.create().withUri("/abc")) @@ -74,7 +58,7 @@ public class FormFieldsTest extends JUnitRouteTest { @Test public void testByteFormFieldExtraction() { - TestRoute route = testRoute(completeWithValueToString(byteParam)); + TestRoute route = testRoute(formField(StringUnmarshallers.BYTE, "byteParam", value -> complete(value.toString()))); route .run(HttpRequest.create().withUri("/abc")) @@ -97,228 +81,19 @@ public class FormFieldsTest extends JUnitRouteTest { .assertEntity("48"); } - @Test - public void testShortFormFieldExtraction() { - TestRoute route = testRoute(completeWithValueToString(shortParam)); - - route - .run(HttpRequest.create().withUri("/abc")) - .assertStatusCode(400) - .assertEntity("Request is missing required form field 'shortParam'"); - - route - .run(singleParameterUrlEncodedRequest("shortParam", "test")) - .assertStatusCode(400) - .assertEntity("The form field 'shortParam' was malformed:\n'test' is not a valid 16-bit signed integer value"); - - route - .run(singleParameterUrlEncodedRequest("shortParam", "100000")) - .assertStatusCode(400) - .assertEntity("The form field 'shortParam' was malformed:\n'100000' is not a valid 16-bit signed integer value"); - - route - .run(singleParameterUrlEncodedRequest("shortParam", "1234")) - .assertStatusCode(200) - .assertEntity("1234"); - } - - @Test - public void testIntegerFormFieldExtraction() { - TestRoute route = testRoute(completeWithValueToString(intParam)); - - route - .run(HttpRequest.create().withUri("/abc")) - .assertStatusCode(400) - .assertEntity("Request is missing required form field 'intParam'"); - - route - .run(singleParameterUrlEncodedRequest("intParam", "test")) - .assertStatusCode(400) - .assertEntity("The form field 'intParam' was malformed:\n'test' is not a valid 32-bit signed integer value"); - - route - .run(singleParameterUrlEncodedRequest("intParam", "48")) - .assertStatusCode(200) - .assertEntity("48"); - } - - @Test - public void testLongFormFieldExtraction() { - TestRoute route = testRoute(completeWithValueToString(longParam)); - - route - .run(HttpRequest.create().withUri("/abc")) - .assertStatusCode(400) - .assertEntity("Request is missing required form field 'longParam'"); - - route - .run(singleParameterUrlEncodedRequest("longParam", "test")) - .assertStatusCode(400) - .assertEntity("The form field 'longParam' was malformed:\n'test' is not a valid 64-bit signed integer value"); - - route - .run(singleParameterUrlEncodedRequest("longParam", "123456")) - .assertStatusCode(200) - .assertEntity("123456"); - } - - @Test - public void testFloatFormFieldExtraction() { - TestRoute route = testRoute(completeWithValueToString(floatParam)); - - route - .run(HttpRequest.create().withUri("/abc")) - .assertStatusCode(400) - .assertEntity("Request is missing required form field 'floatParam'"); - - route - .run(singleParameterUrlEncodedRequest("floatParam", "test")) - .assertStatusCode(400) - .assertEntity("The form field 'floatParam' was malformed:\n'test' is not a valid 32-bit floating point value"); - - route - .run(singleParameterUrlEncodedRequest("floatParam", "48.123")) - .assertStatusCode(200) - .assertEntity("48.123"); - } - - @Test - public void testDoubleFormFieldExtraction() { - TestRoute route = testRoute(completeWithValueToString(doubleParam)); - - route - .run(HttpRequest.create().withUri("/abc")) - .assertStatusCode(400) - .assertEntity("Request is missing required form field 'doubleParam'"); - - route - .run(singleParameterUrlEncodedRequest("doubleParam", "test")) - .assertStatusCode(400) - .assertEntity("The form field 'doubleParam' was malformed:\n'test' is not a valid 64-bit floating point value"); - - route - .run(singleParameterUrlEncodedRequest("doubleParam", "0.234213235987")) - .assertStatusCode(200) - .assertEntity("0.234213235987"); - } - - @Test - public void testHexByteFormFieldExtraction() { - TestRoute route = testRoute(completeWithValueToString(hexByteParam)); - - route - .run(HttpRequest.create().withUri("/abc")) - .assertStatusCode(400) - .assertEntity("Request is missing required form field 'hexByteParam'"); - - route - .run(singleParameterUrlEncodedRequest("hexByteParam", "test")) - .assertStatusCode(400) - .assertEntity("The form field 'hexByteParam' was malformed:\n'test' is not a valid 8-bit hexadecimal integer value"); - - route - .run(singleParameterUrlEncodedRequest("hexByteParam", "1000")) - .assertStatusCode(400) - .assertEntity("The form field 'hexByteParam' was malformed:\n'1000' is not a valid 8-bit hexadecimal integer value"); - - route - .run(singleParameterUrlEncodedRequest("hexByteParam", "48")) - .assertStatusCode(200) - .assertEntity(Integer.toString(0x48)); - } - - @Test - public void testHexShortFormFieldExtraction() { - TestRoute route = testRoute(completeWithValueToString(hexShortParam)); - - route - .run(HttpRequest.create().withUri("/abc")) - .assertStatusCode(400) - .assertEntity("Request is missing required form field 'hexShortParam'"); - - route - .run(singleParameterUrlEncodedRequest("hexShortParam", "test")) - .assertStatusCode(400) - .assertEntity("The form field 'hexShortParam' was malformed:\n'test' is not a valid 16-bit hexadecimal integer value"); - - route - .run(singleParameterUrlEncodedRequest("hexShortParam", "100000")) - .assertStatusCode(400) - .assertEntity("The form field 'hexShortParam' was malformed:\n'100000' is not a valid 16-bit hexadecimal integer value"); - - route - .run(singleParameterUrlEncodedRequest("hexShortParam", "1234")) - .assertStatusCode(200) - .assertEntity(Integer.toString(0x1234)); - } - - @Test - public void testHexIntegerFormFieldExtraction() { - TestRoute route = testRoute(completeWithValueToString(hexIntParam)); - - route - .run(HttpRequest.create().withUri("/abc")) - .assertStatusCode(400) - .assertEntity("Request is missing required form field 'hexIntParam'"); - - route - .run(singleParameterUrlEncodedRequest("hexIntParam", "test")) - .assertStatusCode(400) - .assertEntity("The form field 'hexIntParam' was malformed:\n'test' is not a valid 32-bit hexadecimal integer value"); - - route - .run(singleParameterUrlEncodedRequest("hexIntParam", "12345678")) - .assertStatusCode(200) - .assertEntity(Integer.toString(0x12345678)); - } - - @Test - public void testHexLongFormFieldExtraction() { - TestRoute route = testRoute(completeWithValueToString(hexLongParam)); - - route - .run(HttpRequest.create().withUri("/abc")) - .assertStatusCode(400) - .assertEntity("Request is missing required form field 'hexLongParam'"); - - route - .run(singleParameterUrlEncodedRequest("hexLongParam", "test")) - .assertStatusCode(400) - .assertEntity("The form field 'hexLongParam' was malformed:\n'test' is not a valid 64-bit hexadecimal integer value"); - - route - .run(singleParameterUrlEncodedRequest("hexLongParam", "123456789a")) - .assertStatusCode(200) - .assertEntity(Long.toString(0x123456789aL)); - } - @Test public void testOptionalIntFormFieldExtraction() { - TestRoute route = testRoute(completeWithValueToString(optionalIntParam)); + TestRoute route = testRoute(formFieldOptional(StringUnmarshallers.INTEGER, "optionalIntParam", value -> complete(value.toString()))); route .run(HttpRequest.create().withUri("/abc")) .assertStatusCode(200) - .assertEntity("None"); + .assertEntity("Optional.empty"); route .run(singleParameterUrlEncodedRequest("optionalIntParam", "23")) .assertStatusCode(200) - .assertEntity("Some(23)"); + .assertEntity("Optional[23]"); } - @Test - public void testStringFormFieldExtractionWithDefaultValue() { - TestRoute route = testRoute(completeWithValueToString(nameWithDefault)); - - route - .run(HttpRequest.create().withUri("/abc")) - .assertStatusCode(200) - .assertEntity("John Doe"); - - route - .run(singleParameterUrlEncodedRequest("nameWithDefault", "paul")) - .assertStatusCode(200) - .assertEntity("paul"); - } } diff --git a/akka-http-tests/src/test/java/akka/http/javadsl/server/values/HeadersTest.java b/akka-http-tests/src/test/java/akka/http/javadsl/server/values/HeadersTest.java index a8bdd55796..514597b3f8 100644 --- a/akka-http-tests/src/test/java/akka/http/javadsl/server/values/HeadersTest.java +++ b/akka-http-tests/src/test/java/akka/http/javadsl/server/values/HeadersTest.java @@ -4,6 +4,8 @@ package akka.http.javadsl.server.values; +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; import org.junit.Test; import akka.http.javadsl.testkit.JUnitRouteTest; @@ -12,125 +14,68 @@ import akka.http.javadsl.testkit.TestRoute; import akka.http.javadsl.model.*; import akka.http.javadsl.model.headers.Age; import akka.http.javadsl.model.headers.RawHeader; -import akka.http.javadsl.server.values.*; public class HeadersTest extends JUnitRouteTest { - Header XTestHeader = Headers.byName("X-Test-Header"); - Header AgeHeader = Headers.byClass(Age.class); + final RawHeader testHeaderInstance = RawHeader.create("X-Test-Header", "abcdef-test"); + final Age ageHeaderInstance = Age.create(1000); - RawHeader testHeaderInstance = RawHeader.create("X-Test-Header", "abcdef-test"); - Age ageHeaderInstance = Age.create(1000); + @Test + public void testValueByName() { + TestRoute route = testRoute(headerValueByName("X-Test-Header", value -> complete(value))); - @Test - public void testValueByName() { - TestRoute route = testRoute(completeWithValueToString(XTestHeader.value())); + route + .run(HttpRequest.create()) + .assertStatusCode(400) + .assertEntity("Request is missing required HTTP header 'X-Test-Header'"); - route - .run(HttpRequest.create()) - .assertStatusCode(400) - .assertEntity("Request is missing required HTTP header 'X-Test-Header'"); + route + .run(HttpRequest.create().addHeader(testHeaderInstance)) + .assertStatusCode(200) + .assertEntity("abcdef-test"); + } - route - .run(HttpRequest.create().addHeader(testHeaderInstance)) - .assertStatusCode(200) - .assertEntity("abcdef-test"); - } - @Test - public void testOptionalValueByName() { - TestRoute route = testRoute(completeWithValueToString(XTestHeader.optionalValue())); + @Test + public void testOptionalValueByName() { + TestRoute route = testRoute(optionalHeaderValueByName("X-Test-Header", value -> complete(value.toString()))); - route - .run(HttpRequest.create()) - .assertStatusCode(200) - .assertEntity("None"); + route + .run(HttpRequest.create()) + .assertStatusCode(200) + .assertEntity("Optional.empty"); - route - .run(HttpRequest.create().addHeader(testHeaderInstance)) - .assertStatusCode(200) - .assertEntity("Some(abcdef-test)"); - } - @Test - public void testInstanceByName() { - TestRoute route = testRoute(completeWithValueToString(XTestHeader.instance())); + route + .run(HttpRequest.create().addHeader(testHeaderInstance)) + .assertStatusCode(200) + .assertEntity("Optional[abcdef-test]"); + } - route - .run(HttpRequest.create()) - .assertStatusCode(400) - .assertEntity("Request is missing required HTTP header 'X-Test-Header'"); + @Test + public void testValueByClass() { + TestRoute route = testRoute(headerValueByType(Age.class, age -> complete(age.value()))); - route - .run(HttpRequest.create().addHeader(testHeaderInstance)) - .assertStatusCode(200) - .assertEntity("X-Test-Header: abcdef-test"); - } - @Test - public void testOptionalInstanceByName() { - TestRoute route = testRoute(completeWithValueToString(XTestHeader.optionalInstance())); + route + .run(HttpRequest.create()) + .assertStatusCode(400) + .assertEntity("Request is missing required HTTP header 'Age'"); - route - .run(HttpRequest.create()) - .assertStatusCode(200) - .assertEntity("None"); + route + .run(HttpRequest.create().addHeader(ageHeaderInstance)) + .assertStatusCode(200) + .assertEntity("1000"); + } - route - .run(HttpRequest.create().addHeader(testHeaderInstance)) - .assertStatusCode(200) - .assertEntity("Some(X-Test-Header: abcdef-test)"); - } - @Test - public void testValueByClass() { - TestRoute route = testRoute(completeWithValueToString(AgeHeader.value())); + @Test + public void testOptionalValueByClass() { + TestRoute route = testRoute(optionalHeaderValueByType(Age.class, age -> complete(age.toString()))); - route - .run(HttpRequest.create()) - .assertStatusCode(400) - .assertEntity("Request is missing required HTTP header 'Age'"); + route + .run(HttpRequest.create()) + .assertStatusCode(200) + .assertEntity("Optional.empty"); - route - .run(HttpRequest.create().addHeader(ageHeaderInstance)) - .assertStatusCode(200) - .assertEntity("1000"); - } - @Test - public void testOptionalValueByClass() { - TestRoute route = testRoute(completeWithValueToString(AgeHeader.optionalValue())); - - route - .run(HttpRequest.create()) - .assertStatusCode(200) - .assertEntity("None"); - - route - .run(HttpRequest.create().addHeader(ageHeaderInstance)) - .assertStatusCode(200) - .assertEntity("Some(1000)"); - } - @Test - public void testInstanceByClass() { - TestRoute route = testRoute(completeWithValueToString(AgeHeader.instance())); - - route - .run(HttpRequest.create()) - .assertStatusCode(400) - .assertEntity("Request is missing required HTTP header 'Age'"); - - route - .run(HttpRequest.create().addHeader(ageHeaderInstance)) - .assertStatusCode(200) - .assertEntity("Age: 1000"); - } - @Test - public void testOptionalInstanceByClass() { - TestRoute route = testRoute(completeWithValueToString(AgeHeader.optionalInstance())); - - route - .run(HttpRequest.create()) - .assertStatusCode(200) - .assertEntity("None"); - - route - .run(HttpRequest.create().addHeader(ageHeaderInstance)) - .assertStatusCode(200) - .assertEntity("Some(Age: 1000)"); - } + route + .run(HttpRequest.create().addHeader(ageHeaderInstance)) + .assertStatusCode(200) + .assertEntity("Optional[Age: 1000]"); + } } diff --git a/akka-http-tests/src/test/java/akka/http/javadsl/server/values/HttpBasicAuthenticationTest.java b/akka-http-tests/src/test/java/akka/http/javadsl/server/values/HttpBasicAuthenticationTest.java deleted file mode 100644 index 3550ed8aeb..0000000000 --- a/akka-http-tests/src/test/java/akka/http/javadsl/server/values/HttpBasicAuthenticationTest.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.javadsl.server.values; - -import java.util.Optional; -import java.util.concurrent.CompletionStage; - -import org.junit.Test; - -import akka.http.javadsl.server.*; -import akka.http.javadsl.model.HttpRequest; -import akka.http.javadsl.model.headers.Authorization; -import akka.http.javadsl.testkit.*; - -public class HttpBasicAuthenticationTest extends JUnitRouteTest { - HttpBasicAuthenticator authenticatedUser = - new HttpBasicAuthenticator("test-realm") { - @Override - public CompletionStage> authenticate(BasicCredentials credentials) { - if (credentials.available() && // no anonymous access - credentials.identifier().equals("sina") && - credentials.verify("1234")) - return authenticateAs("Sina"); - else return refuseAccess(); - } - }; - - OAuth2Authenticator authenticatedToken = - new OAuth2Authenticator("test-realm") { - @Override - public CompletionStage> authenticate(OAuth2Credentials credentials) { - if (credentials.available() && // no anonymous access - credentials.identifier().equals("myToken") && - credentials.verify("myToken")) - return authenticateAs("myToken"); - else return refuseAccess(); - } - }; - - Handler1 helloWorldHandler = - new Handler1() { - @Override - public RouteResult apply(RequestContext ctx, String identifier) { - return ctx.complete("Identified as "+identifier+"!"); - } - }; - - TestRoute route = - testRoute( - path("basicSecure").route( - authenticatedUser.route( - handleWith1(authenticatedUser, helloWorldHandler) - ) - ), - path("oauthSecure").route( - authenticatedToken.route( - handleWith1(authenticatedToken, helloWorldHandler) - ) - ) - ); - - @Test - public void testCorrectUser() { - HttpRequest authenticatedRequest = - HttpRequest.GET("/basicSecure") - .addHeader(Authorization.basic("sina", "1234")); - - route.run(authenticatedRequest) - .assertStatusCode(200) - .assertEntity("Identified as Sina!"); - } - @Test - public void testCorrectToken() { - HttpRequest authenticatedRequest = - HttpRequest.GET("/oauthSecure") - .addHeader(Authorization.oauth2("myToken")); - - route.run(authenticatedRequest) - .assertStatusCode(200) - .assertEntity("Identified as myToken!"); - } - @Test - public void testRejectAnonymousAccess() { - route.run(HttpRequest.GET("/basicSecure")) - .assertStatusCode(401) - .assertEntity("The resource requires authentication, which was not supplied with the request") - .assertHeaderExists("WWW-Authenticate", "Basic realm=\"test-realm\""); - } - @Test - public void testRejectUnknownUser() { - HttpRequest authenticatedRequest = - HttpRequest.GET("/basicSecure") - .addHeader(Authorization.basic("joe", "0000")); - - route.run(authenticatedRequest) - .assertStatusCode(401) - .assertEntity("The supplied authentication is invalid"); - } - @Test - public void testRejectWrongPassword() { - HttpRequest authenticatedRequest = - HttpRequest.GET("/basicSecure") - .addHeader(Authorization.basic("sina", "1235")); - - route.run(authenticatedRequest) - .assertStatusCode(401) - .assertEntity("The supplied authentication is invalid"); - } -} diff --git a/akka-http-tests/src/test/java/akka/http/javadsl/server/values/ParametersTest.java b/akka-http-tests/src/test/java/akka/http/javadsl/server/values/ParametersTest.java deleted file mode 100644 index 907c5c7e54..0000000000 --- a/akka-http-tests/src/test/java/akka/http/javadsl/server/values/ParametersTest.java +++ /dev/null @@ -1,410 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.javadsl.server.values; - -import akka.http.javadsl.model.HttpRequest; -import akka.http.javadsl.server.Handler1; -import akka.http.javadsl.server.RequestContext; -import akka.http.javadsl.server.RequestVal; -import akka.http.javadsl.server.RouteResult; -import akka.http.javadsl.testkit.JUnitRouteTest; -import akka.http.javadsl.testkit.TestRoute; -import org.junit.Test; - -import java.util.*; - -public class ParametersTest extends JUnitRouteTest { - static Parameter stringParam = Parameters.stringValue("stringParam"); - static Parameter byteParam = Parameters.byteValue("byteParam"); - static Parameter shortParam = Parameters.shortValue("shortParam"); - static Parameter intParam = Parameters.intValue("intParam"); - static Parameter longParam = Parameters.longValue("longParam"); - static Parameter floatParam = Parameters.floatValue("floatParam"); - static Parameter doubleParam = Parameters.doubleValue("doubleParam"); - - static Parameter hexByteParam = Parameters.hexByteValue("hexByteParam"); - static Parameter hexShortParam = Parameters.hexShortValue("hexShortParam"); - static Parameter hexIntParam = Parameters.hexIntValue("hexIntParam"); - static Parameter hexLongParam = Parameters.hexLongValue("hexLongParam"); - - static RequestVal nameWithDefault = Parameters.stringValue("nameWithDefault").withDefault("John Doe"); - static RequestVal> optionalIntParam = Parameters.intValue("optionalIntParam").optional(); - - static RequestVal> paramMap = Parameters.asMap(); - static RequestVal>> paramMultiMap = Parameters.asMultiMap(); - static RequestVal>> paramEntries = Parameters.asCollection(); - - @Test - public void testStringParameterExtraction() { - TestRoute route = testRoute(completeWithValueToString(stringParam)); - - route - .run(HttpRequest.create().withUri("/abc")) - .assertStatusCode(404) - .assertEntity("Request is missing required query parameter 'stringParam'"); - - route - .run(HttpRequest.create().withUri("/abc?stringParam=john")) - .assertStatusCode(200) - .assertEntity("john"); - } - - @Test - public void testByteParameterExtraction() { - TestRoute route = testRoute(completeWithValueToString(byteParam)); - - route - .run(HttpRequest.create().withUri("/abc")) - .assertStatusCode(404) - .assertEntity("Request is missing required query parameter 'byteParam'"); - - route - .run(HttpRequest.create().withUri("/abc?byteParam=test")) - .assertStatusCode(400) - .assertEntity("The query parameter 'byteParam' was malformed:\n'test' is not a valid 8-bit signed integer value"); - - route - .run(HttpRequest.create().withUri("/abc?byteParam=1000")) - .assertStatusCode(400) - .assertEntity("The query parameter 'byteParam' was malformed:\n'1000' is not a valid 8-bit signed integer value"); - - route - .run(HttpRequest.create().withUri("/abc?byteParam=48")) - .assertStatusCode(200) - .assertEntity("48"); - } - - @Test - public void testShortParameterExtraction() { - TestRoute route = testRoute(completeWithValueToString(shortParam)); - - route - .run(HttpRequest.create().withUri("/abc")) - .assertStatusCode(404) - .assertEntity("Request is missing required query parameter 'shortParam'"); - - route - .run(HttpRequest.create().withUri("/abc?shortParam=test")) - .assertStatusCode(400) - .assertEntity("The query parameter 'shortParam' was malformed:\n'test' is not a valid 16-bit signed integer value"); - - route - .run(HttpRequest.create().withUri("/abc?shortParam=100000")) - .assertStatusCode(400) - .assertEntity("The query parameter 'shortParam' was malformed:\n'100000' is not a valid 16-bit signed integer value"); - - route - .run(HttpRequest.create().withUri("/abc?shortParam=1234")) - .assertStatusCode(200) - .assertEntity("1234"); - } - - @Test - public void testIntegerParameterExtraction() { - TestRoute route = testRoute(completeWithValueToString(intParam)); - - route - .run(HttpRequest.create().withUri("/abc")) - .assertStatusCode(404) - .assertEntity("Request is missing required query parameter 'intParam'"); - - route - .run(HttpRequest.create().withUri("/abc?intParam=test")) - .assertStatusCode(400) - .assertEntity("The query parameter 'intParam' was malformed:\n'test' is not a valid 32-bit signed integer value"); - - route - .run(HttpRequest.create().withUri("/abc?intParam=48")) - .assertStatusCode(200) - .assertEntity("48"); - } - - @Test - public void testLongParameterExtraction() { - TestRoute route = testRoute(completeWithValueToString(longParam)); - - route - .run(HttpRequest.create().withUri("/abc")) - .assertStatusCode(404) - .assertEntity("Request is missing required query parameter 'longParam'"); - - route - .run(HttpRequest.create().withUri("/abc?longParam=test")) - .assertStatusCode(400) - .assertEntity("The query parameter 'longParam' was malformed:\n'test' is not a valid 64-bit signed integer value"); - - route - .run(HttpRequest.create().withUri("/abc?longParam=123456")) - .assertStatusCode(200) - .assertEntity("123456"); - } - - @Test - public void testFloatParameterExtraction() { - TestRoute route = testRoute(completeWithValueToString(floatParam)); - - route - .run(HttpRequest.create().withUri("/abc")) - .assertStatusCode(404) - .assertEntity("Request is missing required query parameter 'floatParam'"); - - route - .run(HttpRequest.create().withUri("/abc?floatParam=test")) - .assertStatusCode(400) - .assertEntity("The query parameter 'floatParam' was malformed:\n'test' is not a valid 32-bit floating point value"); - - route - .run(HttpRequest.create().withUri("/abc?floatParam=48")) - .assertStatusCode(200) - .assertEntity("48.0"); - } - - @Test - public void testDoubleParameterExtraction() { - TestRoute route = testRoute(completeWithValueToString(doubleParam)); - - route - .run(HttpRequest.create().withUri("/abc")) - .assertStatusCode(404) - .assertEntity("Request is missing required query parameter 'doubleParam'"); - - route - .run(HttpRequest.create().withUri("/abc?doubleParam=test")) - .assertStatusCode(400) - .assertEntity("The query parameter 'doubleParam' was malformed:\n'test' is not a valid 64-bit floating point value"); - - route - .run(HttpRequest.create().withUri("/abc?doubleParam=48")) - .assertStatusCode(200) - .assertEntity("48.0"); - } - - @Test - public void testHexByteParameterExtraction() { - TestRoute route = testRoute(completeWithValueToString(hexByteParam)); - - route - .run(HttpRequest.create().withUri("/abc")) - .assertStatusCode(404) - .assertEntity("Request is missing required query parameter 'hexByteParam'"); - - route - .run(HttpRequest.create().withUri("/abc?hexByteParam=test")) - .assertStatusCode(400) - .assertEntity("The query parameter 'hexByteParam' was malformed:\n'test' is not a valid 8-bit hexadecimal integer value"); - - route - .run(HttpRequest.create().withUri("/abc?hexByteParam=1000")) - .assertStatusCode(400) - .assertEntity("The query parameter 'hexByteParam' was malformed:\n'1000' is not a valid 8-bit hexadecimal integer value"); - - route - .run(HttpRequest.create().withUri("/abc?hexByteParam=48")) - .assertStatusCode(200) - .assertEntity(Integer.toString(0x48)); - } - - @Test - public void testHexShortParameterExtraction() { - TestRoute route = testRoute(completeWithValueToString(hexShortParam)); - - route - .run(HttpRequest.create().withUri("/abc")) - .assertStatusCode(404) - .assertEntity("Request is missing required query parameter 'hexShortParam'"); - - route - .run(HttpRequest.create().withUri("/abc?hexShortParam=test")) - .assertStatusCode(400) - .assertEntity("The query parameter 'hexShortParam' was malformed:\n'test' is not a valid 16-bit hexadecimal integer value"); - - route - .run(HttpRequest.create().withUri("/abc?hexShortParam=100000")) - .assertStatusCode(400) - .assertEntity("The query parameter 'hexShortParam' was malformed:\n'100000' is not a valid 16-bit hexadecimal integer value"); - - route - .run(HttpRequest.create().withUri("/abc?hexShortParam=1234")) - .assertStatusCode(200) - .assertEntity(Integer.toString(0x1234)); - } - - @Test - public void testHexIntegerParameterExtraction() { - TestRoute route = testRoute(completeWithValueToString(hexIntParam)); - - route - .run(HttpRequest.create().withUri("/abc")) - .assertStatusCode(404) - .assertEntity("Request is missing required query parameter 'hexIntParam'"); - - route - .run(HttpRequest.create().withUri("/abc?hexIntParam=test")) - .assertStatusCode(400) - .assertEntity("The query parameter 'hexIntParam' was malformed:\n'test' is not a valid 32-bit hexadecimal integer value"); - - route - .run(HttpRequest.create().withUri("/abc?hexIntParam=12345678")) - .assertStatusCode(200) - .assertEntity(Integer.toString(0x12345678)); - } - - @Test - public void testHexLongParameterExtraction() { - TestRoute route = testRoute(completeWithValueToString(hexLongParam)); - - route - .run(HttpRequest.create().withUri("/abc")) - .assertStatusCode(404) - .assertEntity("Request is missing required query parameter 'hexLongParam'"); - - route - .run(HttpRequest.create().withUri("/abc?hexLongParam=test")) - .assertStatusCode(400) - .assertEntity("The query parameter 'hexLongParam' was malformed:\n'test' is not a valid 64-bit hexadecimal integer value"); - - route - .run(HttpRequest.create().withUri("/abc?hexLongParam=123456789a")) - .assertStatusCode(200) - .assertEntity(Long.toString(0x123456789aL)); - } - - @Test - public void testParametersAsMapExtraction() { - TestRoute route = testRoute(handleWith1(paramMap, new Handler1>(){ - @Override - public RouteResult apply(RequestContext ctx, Map paramMap) { - ArrayList keys = new ArrayList(paramMap.keySet()); - Collections.sort(keys); - StringBuilder res = new StringBuilder(); - res.append(paramMap.size()).append(": ["); - for (String key: keys) - res.append(key).append(" -> ").append(paramMap.get(key)).append(", "); - res.append(']'); - return ctx.complete(res.toString()); - } - })); - - route - .run(HttpRequest.create().withUri("/abc")) - .assertStatusCode(200) - .assertEntity("0: []"); - - route - .run(HttpRequest.create().withUri("/abc?a=b")) - .assertStatusCode(200) - .assertEntity("1: [a -> b, ]"); - - route - .run(HttpRequest.create().withUri("/abc?a=b&c=d")) - .assertStatusCode(200) - .assertEntity("2: [a -> b, c -> d, ]"); - } - @Test - public void testParametersAsMultiMapExtraction() { - TestRoute route = testRoute(handleWith1(paramMultiMap, new Handler1>>(){ - @Override - public RouteResult apply(RequestContext ctx, Map> paramMap) { - ArrayList keys = new ArrayList(paramMap.keySet()); - Collections.sort(keys); - StringBuilder res = new StringBuilder(); - res.append(paramMap.size()).append(": ["); - for (String key: keys) { - res.append(key).append(" -> ["); - ArrayList values = new ArrayList(paramMap.get(key)); - Collections.sort(values); - for (String value: values) - res.append(value).append(", "); - res.append("], "); - } - res.append(']'); - return ctx.complete(res.toString()); - } - })); - - route - .run(HttpRequest.create().withUri("/abc")) - .assertStatusCode(200) - .assertEntity("0: []"); - - route - .run(HttpRequest.create().withUri("/abc?a=b")) - .assertStatusCode(200) - .assertEntity("1: [a -> [b, ], ]"); - - route - .run(HttpRequest.create().withUri("/abc?a=b&c=d&a=a")) - .assertStatusCode(200) - .assertEntity("2: [a -> [a, b, ], c -> [d, ], ]"); - } - @Test - public void testParametersAsCollectionExtraction() { - TestRoute route = testRoute(handleWith1(paramEntries, new Handler1>>(){ - @Override - public RouteResult apply(RequestContext ctx, Collection> paramEntries) { - ArrayList> entries = new ArrayList>(paramEntries); - Collections.sort(entries, new Comparator>() { - @Override - public int compare(Map.Entry e1, Map.Entry e2) { - int res = e1.getKey().compareTo(e2.getKey()); - return res == 0 ? e1.getValue().compareTo(e2.getValue()) : res; - } - }); - - StringBuilder res = new StringBuilder(); - res.append(paramEntries.size()).append(": ["); - for (Map.Entry entry: entries) - res.append(entry.getKey()).append(" -> ").append(entry.getValue()).append(", "); - res.append(']'); - return ctx.complete(res.toString()); - } - })); - - route - .run(HttpRequest.create().withUri("/abc")) - .assertStatusCode(200) - .assertEntity("0: []"); - - route - .run(HttpRequest.create().withUri("/abc?a=b&e=f&c=d")) - .assertStatusCode(200) - .assertEntity("3: [a -> b, c -> d, e -> f, ]"); - - route - .run(HttpRequest.create().withUri("/abc?a=b&e=f&c=d&a=z")) - .assertStatusCode(200) - .assertEntity("4: [a -> b, a -> z, c -> d, e -> f, ]"); - } - - @Test - public void testOptionalIntParameterExtraction() { - TestRoute route = testRoute(completeWithValueToString(optionalIntParam)); - - route - .run(HttpRequest.create().withUri("/abc")) - .assertStatusCode(200) - .assertEntity("None"); - - route - .run(HttpRequest.create().withUri("/abc?optionalIntParam=23")) - .assertStatusCode(200) - .assertEntity("Some(23)"); - } - - @Test - public void testStringParameterExtractionWithDefaultValue() { - TestRoute route = testRoute(completeWithValueToString(nameWithDefault)); - - route - .run(HttpRequest.create().withUri("/abc")) - .assertStatusCode(200) - .assertEntity("John Doe"); - - route - .run(HttpRequest.create().withUri("/abc?nameWithDefault=paul")) - .assertStatusCode(200) - .assertEntity("paul"); - } -} diff --git a/akka-http-tests/src/test/java/akka/http/javadsl/server/values/RequestValTest.java b/akka-http-tests/src/test/java/akka/http/javadsl/server/values/RequestValTest.java deleted file mode 100644 index 5f5d2fb228..0000000000 --- a/akka-http-tests/src/test/java/akka/http/javadsl/server/values/RequestValTest.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.javadsl.server.values; - -import akka.http.javadsl.model.HttpRequest; -import akka.http.javadsl.model.RemoteAddress; -import akka.http.javadsl.model.Uri; -import akka.http.javadsl.model.headers.RawHeader; -import akka.http.javadsl.model.headers.XForwardedFor; -import akka.http.javadsl.server.RequestVals; -import akka.http.javadsl.server.Unmarshallers; -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.regex.Pattern; - -public class RequestValTest extends JUnitRouteTest { - @Test - public void testSchemeExtraction() { - TestRoute route = testRoute(completeWithValueToString(RequestVals.scheme())); - - route - .run(HttpRequest.create().withUri(Uri.create("http://example.org"))) - .assertStatusCode(200) - .assertEntity("http"); - - route - .run(HttpRequest.create().withUri(Uri.create("https://example.org"))) - .assertStatusCode(200) - .assertEntity("https"); - } - - @Test - public void testHostExtraction() { - TestRoute route = testRoute(completeWithValueToString(RequestVals.host())); - - route - .run(HttpRequest.create().withUri(Uri.create("http://example.org"))) - .assertStatusCode(200) - .assertEntity("example.org"); - } - - @Test - public void testHostPatternExtraction() { - TestRoute route = - testRoute( - completeWithValueToString( - RequestVals.matchAndExtractHost(Pattern.compile(".*\\.([^.]*)")))); - - route - .run(HttpRequest.create().withUri(Uri.create("http://example.org"))) - .assertStatusCode(200) - .assertEntity("org"); - - route - .run(HttpRequest.create().withUri(Uri.create("http://example.de"))) - .assertStatusCode(200) - .assertEntity("de"); - } - - @Test - public void testClientIpExtraction() throws UnknownHostException{ - TestRoute route = testRoute(completeWithValueToString(RequestVals.clientIP())); - - route - .run(HttpRequest.create().addHeader(XForwardedFor.create(RemoteAddress.create(InetAddress.getByName("127.0.0.2"))))) - .assertStatusCode(200) - .assertEntity("127.0.0.2"); - - route - .run(HttpRequest.create().addHeader(akka.http.javadsl.model.headers.RemoteAddress.create(RemoteAddress.create(InetAddress.getByName("127.0.0.3"))))) - .assertStatusCode(200) - .assertEntity("127.0.0.3"); - - route - .run(HttpRequest.create().addHeader(RawHeader.create("X-Real-IP", "127.0.0.4"))) - .assertStatusCode(200) - .assertEntity("127.0.0.4"); - - route - .run(HttpRequest.create()) - .assertStatusCode(404); - } - - @Test - public void testEntityAsString() { - TestRoute route = - testRoute( - completeWithValueToString(RequestVals.entityAs(Unmarshallers.String())) - ); - - HttpRequest request = - HttpRequest.POST("/") - .withEntity("abcdef"); - route.run(request) - .assertStatusCode(200) - .assertEntity("abcdef"); - } -} diff --git a/akka-http-tests/src/test/java/docs/http/javadsl/server/HandlerExampleDocTest.java b/akka-http-tests/src/test/java/docs/http/javadsl/server/HandlerExampleDocTest.java index 9f0959c41e..cf6fea705d 100644 --- a/akka-http-tests/src/test/java/docs/http/javadsl/server/HandlerExampleDocTest.java +++ b/akka-http-tests/src/test/java/docs/http/javadsl/server/HandlerExampleDocTest.java @@ -4,301 +4,218 @@ package docs.http.javadsl.server; -import akka.dispatch.Mapper; -import akka.http.javadsl.model.HttpRequest; -import akka.http.javadsl.model.HttpResponse; -import akka.http.javadsl.model.StatusCodes; -import akka.http.javadsl.server.*; -import akka.http.javadsl.server.values.Parameters; -import akka.http.javadsl.server.values.PathMatchers; -import akka.http.javadsl.testkit.JUnitRouteTest; -import akka.http.javadsl.testkit.TestRoute; +import static akka.http.javadsl.server.PathMatchers.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import java.util.concurrent.Executor; +import java.util.function.BiFunction; +import java.util.function.Function; import org.junit.Test; +import akka.http.javadsl.model.HttpRequest; +import akka.http.javadsl.model.HttpResponse; +import akka.http.javadsl.model.StatusCodes; +import akka.http.javadsl.server.Route; +import akka.http.javadsl.server.StringUnmarshallers; +import akka.http.javadsl.testkit.JUnitRouteTest; +import akka.http.javadsl.testkit.TestRoute; + public class HandlerExampleDocTest extends JUnitRouteTest { - @Test - public void testSimpleHandler() { - //#simple-handler-example-full - class TestHandler extends akka.http.javadsl.server.AllDirectives { - //#simple-handler - Handler handlerString = new Handler() { - static final long serialVersionUID = 1L; - @Override - public RouteResult apply(RequestContext ctx) { - return ctx.complete(String.format("This was a %s request to %s", - ctx.request().method().value(), ctx.request().getUri())); - } - }; - Handler handlerResponse = new Handler() { - static final long serialVersionUID = 1L; - @Override - public RouteResult apply(RequestContext ctx) { - // with full control over the returned HttpResponse: - final HttpResponse response = HttpResponse.create() - .withEntity(String.format("Accepted %s request to %s", - ctx.request().method().value(), ctx.request().getUri())) - .withStatus(StatusCodes.ACCEPTED); - return ctx.complete(response); - } - }; - //#simple-handler + @Test + public void testSimpleHandler() { + //#simple-handler-example-full + class TestHandler extends akka.http.javadsl.server.AllDirectives { + //#simple-handler + Route handlerString = extractMethod(method -> + extractUri(uri -> + complete(String.format("This was a %s request to %s", method.name(), uri)) + ) + ); - Route createRoute() { - return route( - get( - handleWith(handlerString) - ), - post( - path("abc").route( - handleWith(handlerResponse) - ) - ) - ); - } - } + Route handlerResponse = extractMethod(method -> + extractUri(uri -> { + // with full control over the returned HttpResponse: + final HttpResponse response = HttpResponse.create() + .withEntity(String.format("Accepted %s request to %s", method.name(), uri)) + .withStatus(StatusCodes.ACCEPTED); + return complete(response); + }) + ); + //#simple-handler - // actual testing code - TestRoute r = testRoute(new TestHandler().createRoute()); - r.run(HttpRequest.GET("/test")) - .assertStatusCode(200) - .assertEntity("This was a GET request to http://example.com/test"); - - r.run(HttpRequest.POST("/test")) - .assertStatusCode(404); - - r.run(HttpRequest.POST("/abc")) - .assertStatusCode(202) - .assertEntity("Accepted POST request to http://example.com/abc"); - //#simple-handler-example-full + Route createRoute() { + return route( + get(() -> + handlerString + ), + post(() -> + path("abc", () -> + handlerResponse + ) + ) + ); + } } - @Test - public void testCalculator() { - //#handler2-example-full - class TestHandler extends akka.http.javadsl.server.AllDirectives { - final RequestVal xParam = Parameters.intValue("x"); - final RequestVal yParam = Parameters.intValue("y"); + // actual testing code + TestRoute r = testRoute(new TestHandler().createRoute()); + r.run(HttpRequest.GET("/test")) + .assertStatusCode(StatusCodes.OK) + .assertEntity("This was a GET request to http://example.com/test"); - final RequestVal xSegment = PathMatchers.intValue(); - final RequestVal ySegment = PathMatchers.intValue(); + r.run(HttpRequest.POST("/test")) + .assertStatusCode(StatusCodes.NOT_FOUND); - //#handler2 - final Handler2 multiply = - new Handler2() { - static final long serialVersionUID = 1L; - @Override - public RouteResult apply(RequestContext ctx, Integer x, Integer y) { - int result = x * y; - return ctx.complete("x * y = " + result); - } - }; + r.run(HttpRequest.POST("/abc")) + .assertStatusCode(StatusCodes.ACCEPTED) + .assertEntity("Accepted POST request to http://example.com/abc"); + //#simple-handler-example-full + } - final Route multiplyXAndYParam = handleWith2(xParam, yParam, multiply); - //#handler2 + @Test + public void testCalculator() { + //#handler2-example-full + class TestHandler extends akka.http.javadsl.server.AllDirectives { - Route createRoute() { - return route( - get( - pathPrefix("calculator").route( - path("multiply").route( - multiplyXAndYParam - ), - path("path-multiply", xSegment, ySegment).route( - handleWith2(xSegment, ySegment, multiply) - ) - ) - ) - ); - } - } + final Route multiplyXAndYParam = + parameter(StringUnmarshallers.INTEGER, "x", x -> + parameter(StringUnmarshallers.INTEGER, "y", y -> + complete("x * y = " + (x * y)) + ) + ); - // actual testing code - TestRoute r = testRoute(new TestHandler().createRoute()); - r.run(HttpRequest.GET("/calculator/multiply?x=12&y=42")) - .assertStatusCode(200) - .assertEntity("x * y = 504"); + final Route pathMultiply = + path(integerSegment().slash(integerSegment()), (x, y) -> + complete("x * y = " + (x * y)) + ); - r.run(HttpRequest.GET("/calculator/path-multiply/23/5")) - .assertStatusCode(200) - .assertEntity("x * y = 115"); - //#handler2-example-full + Route subtract(int x, int y) { + return complete("x - y = " + (x - y)); + } + + //#handler2 + + Route createRoute() { + return route( + get(() -> + pathPrefix("calculator", () -> route( + path("multiply", () -> + multiplyXAndYParam + ), + pathPrefix("path-multiply", () -> + pathMultiply + ), + // handle by lifting method + path(segment("subtract").slash(integerSegment()).slash(integerSegment()), this::subtract) + )) + ) + ); + } } - @Test - public void testCalculatorJava8() { - //#handler2-java8-example-full - class TestHandler extends akka.http.javadsl.server.AllDirectives { - final RequestVal xParam = Parameters.intValue("x"); - final RequestVal yParam = Parameters.intValue("y"); + // actual testing code + TestRoute r = testRoute(new TestHandler().createRoute()); + r.run(HttpRequest.GET("/calculator/multiply?x=12&y=42")) + .assertStatusCode(StatusCodes.OK) + .assertEntity("x * y = 504"); - //#handler2-java8 - final Handler2 multiply = - (ctx, x, y) -> ctx.complete("x * y = " + (x * y)); + r.run(HttpRequest.GET("/calculator/path-multiply/23/5")) + .assertStatusCode(StatusCodes.OK) + .assertEntity("x * y = 115"); - final Route multiplyXAndYParam = handleWith2(xParam, yParam, multiply); - //#handler2-java8 + r.run(HttpRequest.GET("/calculator/subtract/42/12")) + .assertStatusCode(StatusCodes.OK) + .assertEntity("x - y = 30"); + //#handler2-example-full + } - RouteResult subtract(RequestContext ctx, int x, int y) { - return ctx.complete("x - y = " + (x - y)); - } + @Test + public void testDeferredResultAsyncHandler() { + //#async-example-full + //#async-service-definition + class CalculatorService { + public CompletionStage multiply(final int x, final int y) { + return CompletableFuture.supplyAsync(() -> x * y); + } - Route createRoute() { - return route( - get( - pathPrefix("calculator").route( - path("multiply").route( - // use Handler explicitly - multiplyXAndYParam - ), - path("add").route( - // create Handler as lambda expression - handleWith2(xParam, yParam, - (ctx, x, y) -> ctx.complete("x + y = " + (x + y))) - ), - path("subtract").route( - // create handler by lifting method - handleWith2(xParam, yParam, this::subtract) - ) - ) - ) - ); - } - } + public CompletionStage add(final int x, final int y) { + return CompletableFuture.supplyAsync(() -> x + y); + } + } + //#async-service-definition - // actual testing code - TestRoute r = testRoute(new TestHandler().createRoute()); - r.run(HttpRequest.GET("/calculator/multiply?x=12&y=42")) - .assertStatusCode(200) - .assertEntity("x * y = 504"); + class TestHandler extends akka.http.javadsl.server.AllDirectives { - r.run(HttpRequest.GET("/calculator/add?x=12&y=42")) - .assertStatusCode(200) - .assertEntity("x + y = 54"); + /** + * Returns a route that applies the (required) request parameters "x" and "y", as integers, to + * the inner function. + */ + Route paramXY(BiFunction inner) { + return + parameter(StringUnmarshallers.INTEGER, "x", x -> + parameter(StringUnmarshallers.INTEGER, "y", y -> + inner.apply(x, y) + ) + ); + } - r.run(HttpRequest.GET("/calculator/subtract?x=42&y=12")) - .assertStatusCode(200) - .assertEntity("x - y = 30"); - //#handler2-java8-example-full + + //#async-handler-1 + // would probably be injected or passed at construction time in real code + CalculatorService calculatorService = new CalculatorService(); + + public CompletionStage multiplyAsync(Executor ctx, int x, int y) { + CompletionStage result = calculatorService.multiply(x, y); + return result.thenApplyAsync(product -> complete("x * y = " + product), ctx); + } + + Route multiplyAsyncRoute = + extractExecutionContext(ctx -> + path("multiply", () -> + paramXY((x, y) -> + onSuccess(() -> multiplyAsync(ctx, x, y), Function.identity()) + ) + ) + ); + //#async-handler-1 + + //#async-handler-2 + + public Route addAsync(int x, int y) { + CompletionStage result = calculatorService.add(x, y); + + return onSuccess(() -> result, sum -> complete("x + y = " + sum)); + } + + Route addAsyncRoute = + path("add", () -> + paramXY(this::addAsync) + ); + //#async-handler-2 + + Route createRoute() { + return route( + get(() -> + pathPrefix("calculator", () -> route( + multiplyAsyncRoute, + addAsyncRoute + )) + ) + ); + } } - @Test - public void testCalculatorReflective() { - //#reflective-example-full - class TestHandler extends akka.http.javadsl.server.AllDirectives { - RequestVal xParam = Parameters.intValue("x"); - RequestVal yParam = Parameters.intValue("y"); + // testing code + TestRoute r = testRoute(new TestHandler().createRoute()); + r.run(HttpRequest.GET("/calculator/multiply?x=12&y=42")) + .assertStatusCode(StatusCodes.OK) + .assertEntity("x * y = 504"); - RequestVal xSegment = PathMatchers.intValue(); - RequestVal ySegment = PathMatchers.intValue(); - - - //#reflective - public RouteResult multiply(RequestContext ctx, Integer x, Integer y) { - int result = x * y; - return ctx.complete("x * y = " + result); - } - - Route multiplyXAndYParam = handleReflectively(this, "multiply", xParam, yParam); - //#reflective - - Route createRoute() { - return route( - get( - pathPrefix("calculator").route( - path("multiply").route( - multiplyXAndYParam - ), - path("path-multiply", xSegment, ySegment).route( - handleWith2(xSegment, ySegment, this::multiply) - ) - ) - ) - ); - } - } - - // actual testing code - TestRoute r = testRoute(new TestHandler().createRoute()); - r.run(HttpRequest.GET("/calculator/multiply?x=12&y=42")) - .assertStatusCode(200) - .assertEntity("x * y = 504"); - - r.run(HttpRequest.GET("/calculator/path-multiply/23/5")) - .assertStatusCode(200) - .assertEntity("x * y = 115"); - //#reflective-example-full - } - - @Test - public void testDeferredResultAsyncHandler() { - //#async-example-full - //#async-service-definition - class CalculatorService { - public CompletionStage multiply(final int x, final int y) { - return CompletableFuture.supplyAsync(() -> x * y); - } - - public CompletionStage add(final int x, final int y) { - return CompletableFuture.supplyAsync(() -> x + y); - } - } - //#async-service-definition - - class TestHandler extends akka.http.javadsl.server.AllDirectives { - RequestVal xParam = Parameters.intValue("x"); - RequestVal yParam = Parameters.intValue("y"); - - //#async-handler-1 - // would probably be injected or passed at construction time in real code - CalculatorService calculatorService = new CalculatorService(); - public CompletionStage multiplyAsync(final RequestContext ctx, int x, int y) { - CompletionStage result = calculatorService.multiply(x, y); - return result.thenApplyAsync(product -> ctx.complete("x * y = " + product), - ctx.executionContext()); - } - Route multiplyAsyncRoute = - path("multiply").route( - handleWithAsync2(xParam, yParam, this::multiplyAsync) - ); - //#async-handler-1 - - //#async-handler-2 - public RouteResult addAsync(final RequestContext ctx, int x, int y) { - CompletionStage result = calculatorService.add(x, y); - return ctx.completeWith(result.thenApplyAsync(sum -> ctx.complete("x + y = " + sum), - ctx.executionContext())); - } - Route addAsyncRoute = - path("add").route( - handleWith2(xParam, yParam, this::addAsync) - ); - //#async-handler-2 - - Route createRoute() { - return route( - get( - pathPrefix("calculator").route( - multiplyAsyncRoute, - addAsyncRoute - ) - ) - ); - } - } - - // testing code - TestRoute r = testRoute(new TestHandler().createRoute()); - r.run(HttpRequest.GET("/calculator/multiply?x=12&y=42")) - .assertStatusCode(200) - .assertEntity("x * y = 504"); - - r.run(HttpRequest.GET("/calculator/add?x=23&y=5")) - .assertStatusCode(200) - .assertEntity("x + y = 28"); - //#async-example-full - } + r.run(HttpRequest.GET("/calculator/add?x=23&y=5")) + .assertStatusCode(StatusCodes.OK) + .assertEntity("x + y = 28"); + //#async-example-full + } } diff --git a/akka-http-tests/src/test/resources/reference.conf b/akka-http-tests/src/test/resources/reference.conf index 67a8010d7d..429ce1804e 100644 --- a/akka-http-tests/src/test/resources/reference.conf +++ b/akka-http-tests/src/test/resources/reference.conf @@ -5,5 +5,5 @@ akka { serialize-messages = off default-dispatcher.throughput = 1 } - stream.materializer.debug.fuzzing-mode=on + stream.materializer.debug.fuzzing-mode = on } \ No newline at end of file diff --git a/akka-http-tests/src/test/scala/akka/http/javadsl/DirectivesConsistencySpec.scala b/akka-http-tests/src/test/scala/akka/http/javadsl/DirectivesConsistencySpec.scala new file mode 100644 index 0000000000..57fc95c57b --- /dev/null +++ b/akka-http-tests/src/test/scala/akka/http/javadsl/DirectivesConsistencySpec.scala @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2009-2016 Lightbend Inc. + */ + +package akka.http.javadsl + +import java.lang.reflect.{ Modifier, Method } + +import akka.http.javadsl.server.AllDirectives +import akka.http.javadsl.server.directives.CorrespondsTo +import org.scalatest.exceptions.TestPendingException +import org.scalatest.{ Matchers, WordSpec } +import scala.reflect.runtime.{ universe ⇒ ru } + +import scala.util.control.NoStackTrace + +class DirectivesConsistencySpec extends WordSpec with Matchers { + + val scalaDirectivesClazz = classOf[akka.http.scaladsl.server.Directives] + val javaDirectivesClazz = classOf[akka.http.javadsl.server.AllDirectives] + + val ignore = + Set("equals", "hashCode", "notify", "notifyAll", "wait", "toString", "getClass") ++ + Set("productArity", "canEqual", "productPrefix", "copy", "productIterator", "productElement", + "concat", "route") ++ // TODO this fails on jenkins but not locally, no idea why, disabling to get Java DSL in + // param extractions in ScalaDSL + Set("DoubleNumber", "HexIntNumber", "HexLongNumber", "IntNumber", "JavaUUID", "LongNumber", + "Neutral", "PathEnd", "Remaining", "Segment", "Segments", "Slash", "RemainingPath") // TODO do we cover these? + + def prepareDirectivesList(in: Array[Method]): List[Method] = { + in.toSet[Method] + .toList + .foldLeft[List[Method]](Nil) { + (l, s) ⇒ + { + val test = l find { _.getName.toLowerCase == s.getName.toLowerCase } + if (test.isEmpty) s :: l else l + } + } + .sortBy(_.getName) + .iterator + .filterNot(m ⇒ Modifier.isStatic(m.getModifiers)) + .filterNot(m ⇒ ignore(m.getName)) + .filterNot(m ⇒ m.getName.contains("$")) + .filterNot(m ⇒ m.getName.startsWith("_")) + .toList + } + + val scalaDirectives = { + prepareDirectivesList(scalaDirectivesClazz.getMethods) + } + val javaDirectives = { + prepareDirectivesList(javaDirectivesClazz.getMethods) + } + + val correspondingScalaMethods = { + val javaToScalaMappings = + for { + // using Scala annotations - Java annotations were magically not present in certain places... + d ← javaDirectives + if d.isAnnotationPresent(classOf[CorrespondsTo]) + annot = d.getAnnotation(classOf[CorrespondsTo]) + } yield d.getName -> annot.value() + + Map(javaToScalaMappings.toList: _*) + } + + val correspondingJavaMethods = Map() ++ correspondingScalaMethods.map(_.swap) + + /** Left(@CorrespondsTo(...) or Right(normal name) */ + def correspondingScalaMethodName(m: Method): Either[String, String] = + correspondingScalaMethods.get(m.getName) match { + case Some(correspondent) ⇒ Left(correspondent) + case _ ⇒ Right(m.getName) + } + + /** Left(@CorrespondsTo(...) or Right(normal name) */ + def correspondingJavaMethodName(m: Method): Either[String, String] = + correspondingJavaMethods.get(m.getName) match { + case Some(correspondent) ⇒ Left(correspondent) + case _ ⇒ Right(m.getName) + } + + val allowMissing: Map[Class[_], Set[String]] = Map( + scalaDirectivesClazz -> Set( + "route", "request", + "completeOK", // solved by raw complete() in Scala + "defaultDirectoryRenderer", "defaultContentTypeResolver" // solved by implicits in Scala + ), + javaDirectivesClazz -> Set( + "as", + "instanceOf", + "pass", + + // TODO PENDING -> + "extractRequestContext", "nothingMatcher", "separateOnSlashes", + "textract", "tprovide", "withExecutionContext", "withRequestTimeoutResponse", + "withSettings", + "provide", "withMaterializer", "recoverRejectionsWith", + "mapSettings", "mapRequestContext", "mapInnerRoute", "mapRouteResultFuture", + "mapRouteResultWith", + "mapRouteResult", "handleWith", + "mapRouteResultWithPF", "mapRouteResultPF", + "route", "request", + "completeOrRecoverWith", "completeWith", + // TODO <- END OF PENDING + "parameters", "formFields", // since we can't do magnet-style "arbitrary arity" + + "authenticateOAuth2PF", "authenticateOAuth2PFAsync", + "authenticateBasicPF", "authenticateBasicPFAsync")) + + def assertHasMethod(c: Class[_], name: String, alternativeName: String): Unit = { + // include class name to get better error message + if (!allowMissing.getOrElse(c, Set.empty).exists(n ⇒ n == name || n == alternativeName)) { + val methods = c.getMethods.collect { case m if !ignore(m.getName) ⇒ c.getName + "." + m.getName } + + def originClazz = { + // look in the "opposite" class + // traversal is different in scala/java - in scala its traits, so we need to look at interfaces + // in hava we have a huge inheritance chain so we unfold it + c match { + case `javaDirectivesClazz` ⇒ + val all = scalaDirectivesClazz + (for { + i ← all.getInterfaces + m ← i.getDeclaredMethods + if m.getName == name || m.getName == alternativeName + } yield i).headOption + .map(_.getName) + .getOrElse(throw new Exception(s"Unable to locate method [$name] on source class $all")) + + case `scalaDirectivesClazz` ⇒ + val all = javaDirectivesClazz + + var is = List.empty[Class[_]] + var c: Class[_] = all + while (c != classOf[java.lang.Object]) { + is = c :: is + c = c.getSuperclass + } + + (for { + i ← is + m ← i.getDeclaredMethods + if m.getName == name || m.getName == alternativeName + } yield i).headOption + .map(_.getName) + .getOrElse(throw new Exception(s"Unable to locate method [$name] on source class $all")) + } + } + + if (methods.contains(c.getName + "." + name) && name == alternativeName) () + else if (methods.contains(c.getName + "." + alternativeName)) () + else throw new AssertionError(s"Method [$originClazz#$name] was not defined on class: ${c.getName}") with NoStackTrace + } else { + // allowed missing - we mark as pending, perhaps we'll want that method eventually + throw new TestPendingException + } + } + + "DSL Stats" should { + info("Scala Directives: ~" + scalaDirectives.map(_.getName).filterNot(ignore).size) + info("Java Directives: ~" + javaDirectives.map(_.getName).filterNot(ignore).size) + } + + "Directive aliases" should { + info("Aliases: ") + correspondingScalaMethods.foreach { case (k, v) ⇒ info(s" $k => $v") } + } + + "Consistency scaladsl -> javadsl" should { + for { + m ← scalaDirectives + name = m.getName + targetName = correspondingJavaMethodName(m) match { case Left(l) ⇒ l case Right(r) ⇒ r } + text = if (name == targetName) name else s"$name (alias: $targetName)" + } s"""define Scala directive [$text] for JavaDSL too""" in { + assertHasMethod(javaDirectivesClazz, name, targetName) + } + } + + "Consistency javadsl -> scaladsl" should { + for { + m ← javaDirectives + name = m.getName + targetName = correspondingScalaMethodName(m) match { case Left(l) ⇒ l case Right(r) ⇒ r } + text = if (name == targetName) name else s"$name (alias for: $targetName)" + } s"""define Java directive [$text] for ScalaDSL too""" in { + assertHasMethod(scalaDirectivesClazz, name, targetName) + } + } + +} diff --git a/akka-http-tests/src/test/scala/akka/http/javadsl/server/directives/SampleCustomHeader.scala b/akka-http-tests/src/test/scala/akka/http/javadsl/server/directives/SampleCustomHeader.scala new file mode 100644 index 0000000000..e03359cd3e --- /dev/null +++ b/akka-http-tests/src/test/scala/akka/http/javadsl/server/directives/SampleCustomHeader.scala @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2009-2016 Lightbend Inc. + */ +package akka.http.javadsl.server.directives + +import akka.http.scaladsl.model.headers.{ ModeledCustomHeader, ModeledCustomHeaderCompanion } + +import scala.util.{ Success, Try } + +// no support for modeled headers in the Java DSL yet, so this has to live here + +object SampleCustomHeader extends ModeledCustomHeaderCompanion[SampleCustomHeader] { + override def name: String = "X-Sample-Custom-Header" + override def parse(value: String): Try[SampleCustomHeader] = Success(new SampleCustomHeader(value)) +} + +class SampleCustomHeader(val value: String) extends ModeledCustomHeader[SampleCustomHeader] { + override def companion: ModeledCustomHeaderCompanion[SampleCustomHeader] = SampleCustomHeader + override def renderInResponses(): Boolean = true + override def renderInRequests(): Boolean = true +} diff --git a/akka-http-tests/src/test/scala/akka/http/scaladsl/TestSingleRequest.scala b/akka-http-tests/src/test/scala/akka/http/scaladsl/TestSingleRequest.scala new file mode 100644 index 0000000000..c7b4c0eb62 --- /dev/null +++ b/akka-http-tests/src/test/scala/akka/http/scaladsl/TestSingleRequest.scala @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2009-2016 Lightbend Inc. + */ + +package akka.http.scaladsl + +import akka.http.scaladsl.model.{ HttpRequest, StatusCodes, HttpResponse } +import akka.util.ByteString +import com.typesafe.config.{ ConfigFactory, Config } +import akka.actor.ActorSystem +import akka.stream._ +import scala.concurrent.Await +import scala.concurrent.duration._ +import scala.io.StdIn + +object TestSingleRequest extends App { + val testConf: Config = ConfigFactory.parseString(""" + akka.loglevel = INFO + akka.log-dead-letters = off + akka.stream.materializer.debug.fuzzing-mode = off + """) + implicit val system = ActorSystem("ServerTest", testConf) + implicit val materializer = ActorMaterializer() + import system.dispatcher + + val url = StdIn.readLine("url? ") + + val x = Http().singleRequest(HttpRequest(uri = url)) + + val res = Await.result(x, 10.seconds) + + val response = res.entity.dataBytes.runFold(ByteString.empty)(_ ++ _).map(_.utf8String) + + println(" ------------ RESPONSE ------------") + println(Await.result(response, 10.seconds)) + println(" -------- END OF RESPONSE ---------") + + system.terminate() +} 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 48fa0d9a47..39d37885c7 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 @@ -9,9 +9,11 @@ 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.stream._ +import akka.stream.scaladsl._ import akka.http.scaladsl.Http import scala.concurrent.duration._ +import scala.io.StdIn object TestServer extends App { val testConf: Config = ConfigFactory.parseString(""" @@ -54,7 +56,7 @@ object TestServer extends App { }, interface = "localhost", port = 8080) println(s"Server online at http://localhost:8080/\nPress RETURN to stop...") - Console.readLine() + StdIn.readLine() bindingFuture.flatMap(_.unbind()).onComplete(_ ⇒ system.terminate()) diff --git a/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/CacheConditionDirectivesSpec.scala b/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/CacheConditionDirectivesSpec.scala index 3af77214e3..4493e9793c 100644 --- a/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/CacheConditionDirectivesSpec.scala +++ b/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/CacheConditionDirectivesSpec.scala @@ -146,7 +146,7 @@ class CacheConditionDirectivesSpec extends RoutingSpec { "not filter out a `Range` header if `If-Range` does match the timestamp" in { Get() ~> `If-Range`(timestamp) ~> Range(ByteRange(0, 10)) ~> { - (conditional(tag, timestamp) & optionalHeaderValueByType[Range](())) { echoComplete } + (conditional(tag, timestamp) & optionalHeaderValueByType[Range]()) { echoComplete } } ~> check { status shouldEqual OK responseAs[String] should startWith("Some") @@ -155,7 +155,7 @@ class CacheConditionDirectivesSpec extends RoutingSpec { "filter out a `Range` header if `If-Range` doesn't match the timestamp" in { Get() ~> `If-Range`(timestamp - 1000) ~> Range(ByteRange(0, 10)) ~> { - (conditional(tag, timestamp) & optionalHeaderValueByType[Range](())) { echoComplete } + (conditional(tag, timestamp) & optionalHeaderValueByType[Range]()) { echoComplete } } ~> check { status shouldEqual OK responseAs[String] shouldEqual "None" @@ -164,7 +164,7 @@ class CacheConditionDirectivesSpec extends RoutingSpec { "not filter out a `Range` header if `If-Range` does match the ETag" in { Get() ~> `If-Range`(tag) ~> Range(ByteRange(0, 10)) ~> { - (conditional(tag, timestamp) & optionalHeaderValueByType[Range](())) { echoComplete } + (conditional(tag, timestamp) & optionalHeaderValueByType[Range]()) { echoComplete } } ~> check { status shouldEqual OK responseAs[String] should startWith("Some") @@ -173,7 +173,7 @@ class CacheConditionDirectivesSpec extends RoutingSpec { "filter out a `Range` header if `If-Range` doesn't match the ETag" in { Get() ~> `If-Range`(EntityTag("other")) ~> Range(ByteRange(0, 10)) ~> { - (conditional(tag, timestamp) & optionalHeaderValueByType[Range](())) { echoComplete } + (conditional(tag, timestamp) & optionalHeaderValueByType[Range]()) { echoComplete } } ~> check { status shouldEqual OK responseAs[String] shouldEqual "None" diff --git a/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/PathDirectivesSpec.scala b/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/PathDirectivesSpec.scala index 3624d3f220..1b3021c951 100644 --- a/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/PathDirectivesSpec.scala +++ b/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/PathDirectivesSpec.scala @@ -259,20 +259,20 @@ class PathDirectivesSpec extends RoutingSpec with Inside { "PathMatchers" should { { - val test = testFor(path(Rest.tmap { case Tuple1(s) ⇒ Tuple1(s.split('-').toList) }) { echoComplete }) + val test = testFor(path(Remaining.tmap { case Tuple1(s) ⇒ Tuple1(s.split('-').toList) }) { echoComplete }) "support the hmap modifier in accept [/yes-no]" in test("List(yes, no)") } { - val test = testFor(path(Rest.map(_.split('-').toList)) { echoComplete }) + val test = testFor(path(Remaining.map(_.split('-').toList)) { echoComplete }) "support the map modifier in accept [/yes-no]" in test("List(yes, no)") } { - val test = testFor(path(Rest.tflatMap { case Tuple1(s) ⇒ Some(s).filter("yes" ==).map(x ⇒ Tuple1(x)) }) { echoComplete }) + val test = testFor(path(Remaining.tflatMap { case Tuple1(s) ⇒ Some(s).filter("yes" ==).map(x ⇒ Tuple1(x)) }) { echoComplete }) "support the hflatMap modifier in accept [/yes]" in test("yes") "support the hflatMap modifier in reject [/blub]" in test() } { - val test = testFor(path(Rest.flatMap(s ⇒ Some(s).filter("yes" ==))) { echoComplete }) + val test = testFor(path(Remaining.flatMap(s ⇒ Some(s).filter("yes" ==))) { echoComplete }) "support the flatMap modifier in accept [/yes]" in test("yes") "support the flatMap modifier reject [/blub]" in test() } @@ -305,6 +305,17 @@ class PathDirectivesSpec extends RoutingSpec with Inside { import akka.http.scaladsl.model.StatusCodes._ + "the Remaining path matcher" should { + "extract complete path if nothing previously consumed" in { + val route = path(Remaining) { echoComplete } + Get("/pets/afdaoisd/asda/sfasfasf/asf") ~> route ~> check { responseAs[String] shouldEqual "pets/afdaoisd/asda/sfasfasf/asf" } + } + "extract remaining path when parts of path already matched" in { + val route = path("pets" / Remaining) { echoComplete } + Get("/pets/afdaoisd/asda/sfasfasf/asf") ~> route ~> check { responseAs[String] shouldEqual "afdaoisd/asda/sfasfasf/asf" } + } + } + "the `redirectToTrailingSlashIfMissing` directive" should { val route = redirectToTrailingSlashIfMissing(Found) { completeOk } diff --git a/akka-http/build.sbt b/akka-http/build.sbt index 7fe8670788..54f1a31a9d 100644 --- a/akka-http/build.sbt +++ b/akka-http/build.sbt @@ -8,5 +8,4 @@ Dependencies.http disablePlugins(MimaPlugin) // still experimental enablePlugins(spray.boilerplate.BoilerplatePlugin) -disablePlugins(MimaPlugin) scalacOptions in Compile += "-language:_" diff --git a/akka-http/src/main/boilerplate/akka/http/javadsl/server/Handlers.scala.template b/akka-http/src/main/boilerplate/akka/http/javadsl/server/Handlers.scala.template deleted file mode 100644 index 2f8719ff0c..0000000000 --- a/akka-http/src/main/boilerplate/akka/http/javadsl/server/Handlers.scala.template +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2009-2015 Lightbend Inc. - */ -package akka.http.javadsl.server - -import java.util.concurrent.CompletionStage - -[..21#/** - * A route Handler that handles a request (that is encapsulated in a [[RequestContext]]) - * and returns a [[RouteResult]] with the response (or the rejection). - * - * A route `Handler1` is a convenience class that extends Function of arity `N+1`, - * since it needs to pass along the [[RequestContext]] as well, yet for readability - * purposes we can see it as "handles 1 route arguments". - * - * Use the methods in [[RequestContext]] to create a [[RouteResult]]. - * A handler MUST NOT return `null` as the result. - */ -trait Handler1[[#T1#]] extends akka.japi.function.Function2[RequestContext, [#T1#], RouteResult] { - override def apply(ctx: RequestContext, [#t1: T1#]): RouteResult -} -/** - * A route Handler that handles a request (that is encapsulated in a [[RequestContext]]) - * and returns a [[java.util.concurrent.CompletionStage]] of [[RouteResult]] with the response (or the rejection). - * - * A route `Handler1` is a convenience class that extends Function of arity `N+1`, - * since it needs to pass along the [[RequestContext]] as well, yet for readability - * purposes we can see it as "handles 1 route arguments". - * - * Use the methods in [[RequestContext]] to create a [[RouteResult]]. - * A handler MUST NOT return `null` as the result. - */ -trait AsyncHandler1[[#T1#]] extends akka.japi.function.Function2[RequestContext, [#T1#], CompletionStage[RouteResult]] { - override def apply(ctx: RequestContext, [#t1: T1#]): CompletionStage[RouteResult] -}# - -] diff --git a/akka-http/src/main/boilerplate/akka/http/javadsl/server/JavaPathMatchers.scala.template b/akka-http/src/main/boilerplate/akka/http/javadsl/server/JavaPathMatchers.scala.template new file mode 100644 index 0000000000..cac37f262e --- /dev/null +++ b/akka-http/src/main/boilerplate/akka/http/javadsl/server/JavaPathMatchers.scala.template @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2009-2016 Lightbend Inc. + */ +package akka.http.javadsl.server + +import java.util.List +import java.util.function.{BiFunction, Function} +import akka.http.scaladsl.server.{ PathMatcher ⇒ SPathMatcher } +import akka.http.scaladsl.server.{ PathMatchers ⇒ SPathMatchers } +import akka.http.scaladsl.server.PathMatcher +import java.util.{ List ⇒ JList } +import java.util.UUID +import java.util.function.BiFunction +import java.util.function.{ Function ⇒ JFunction } +import akka.japi.function._ +import java.util.regex.Pattern +import scala.collection.JavaConverters._ + +import akka.http.scaladsl.server.PathMatcher +import java.util.{List => JList} + +import scala.language.implicitConversions + +object JavaPathMatchers { + + /* This is a workaround for sbt-boilerplate being limited with 2 as the max distance between number and generated number */ + type PathMatcherTwoMoreThan1[A, B, T1] = PathMatcher3[A, B, T1] + type PathMatcherTwoMoreThan2[A, B, T1, T2] = PathMatcher4[A, B, T1, T2] + type PathMatcherTwoMoreThan3[A, B, T1, T2, T3] = PathMatcher5[A, B, T1, T2, T3] + type PathMatcherTwoMoreThan4[A, B, T1, T2, T3, T4] = PathMatcher6[A, B, T1, T2, T3, T4] + type PathMatcherTwoMoreThan5[A, B, T1, T2, T3, T4, T5] = PathMatcher7[A, B, T1, T2, T3, T4, T5] + type PathMatcherTwoMoreThan6[A, B, T1, T2, T3, T4, T5, T6] = PathMatcher8[A, B, T1, T2, T3, T4, T5, T6] + type PathMatcherTwoMoreThan7[A, B, T1, T2, T3, T4, T5, T6, T7] = PathMatcher9[A, B, T1, T2, T3, T4, T5, T6, T7] + type PathMatcherTwoMoreThan8[A, B, T1, T2, T3, T4, T5, T6, T7, T8] = PathMatcher10[A, B, T1, T2, T3, T4, T5, T6, T7, T8] + type PathMatcherTwoMoreThan9[A, B, T1, T2, T3, T4, T5, T6, T7, T8, T9] = PathMatcher11[A, B, T1, T2, T3, T4, T5, T6, T7, T8, T9] + type PathMatcherTwoMoreThan10[A, B, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10] = PathMatcher12[A, B, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10] + type PathMatcherTwoMoreThan11[A, B, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11] = PathMatcher13[A, B, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11] + type PathMatcherTwoMoreThan12[A, B, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12] = PathMatcher14[A, B, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12] + type PathMatcherTwoMoreThan13[A, B, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13] = PathMatcher15[A, B, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13] + type PathMatcherTwoMoreThan14[A, B, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14] = PathMatcher16[A, B, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14] + type PathMatcherTwoMoreThan15[A, B, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15] = PathMatcher17[A, B, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15] + type PathMatcherTwoMoreThan16[A, B, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16] = PathMatcher18[A, B, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16] + type PathMatcherTwoMoreThan17[A, B, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17] = PathMatcher19[A, B, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17] + type PathMatcherTwoMoreThan18[A, B, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18] = PathMatcher20[A, B, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18] + type PathMatcherTwoMoreThan19[A, B, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19] = PathMatcher21[A, B, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19] + type PathMatcherTwoMoreThan20[A, B, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20] = PathMatcher22[A, B, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20] + + /** INTERNAL API */ + private[server] implicit def fromScala0(scalaMatcher: SPathMatcher[Unit]): PathMatcher0 = + new PathMatcher0(scalaMatcher) + + [..22#/** INTERNAL API */ + private[server] def fromScala1[[#T1#]](scalaMatcher: SPathMatcher[Tuple1[[#T1#]]]): PathMatcher1[[#T1#]] = + new PathMatcher1(scalaMatcher) + # + ] + + + [..20#/** INTERNAL API */ + private[server] def fromScalaTwoMoreThan1[A, B, [#T1#]](scalaMatcher: SPathMatcher[(A, B, [#T1#])]) = + new PathMatcherTwoMoreThan1[A, B, [#T1#]](scalaMatcher) + # + ] + +} + +/** + * A PathMatcher tries to match a prefix of a given string and returns either a PathMatcher.Matched instance + * if matched, otherwise PathMatchers.Unmatched. + */ +// Generated class, do not edit in place, but edit template instead. +final class PathMatcher0(val toScala: SPathMatcher[Unit]) { + import JavaPathMatchers._ + + def slash() = fromScala0(toScala./) + + def slash(segment: String) : PathMatcher0 = fromScala0(toScala / segment) + def slash(next: PathMatcher0) : PathMatcher0 = fromScala0(toScala / next.toScala) + def slash[T](next: PathMatcher1[T]) : PathMatcher1[T] = fromScala1(toScala / next.toScala) + def slash[T1, T2](next: PathMatcher2[T1, T2]): PathMatcher2[T1, T2] = fromScala2(toScala / next.toScala) + + def concat(segment: String) : PathMatcher0 = fromScala0(toScala ~ segment) + def concat(next: PathMatcher0) : PathMatcher0 = fromScala0(toScala ~ next.toScala) + def concat[T](next: PathMatcher1[T]) : PathMatcher1[T] = fromScala1(toScala ~ next.toScala) + def concat[T1, T2](next: PathMatcher2[T1, T2]): PathMatcher2[T1, T2] = fromScala2(toScala ~ next.toScala) + + def orElse(segment: String) = fromScala0(toScala | segment) + def orElse(alternative: PathMatcher0) = fromScala0(toScala | alternative.toScala) + + def invert = fromScala0(!toScala) + + def repeat(min: Int, max: Int): PathMatcher0 = repeat(min, max, SPathMatchers.Neutral) + def repeat(min: Int, max: Int, separator: PathMatcher0): PathMatcher0 = fromScala0(toScala.repeat(min, max, separator.toScala)) + +} + +/** + * A PathMatcher tries to match a prefix of a given string and returns either a PathMatcher.Matched instance + * if matched, otherwise PathMatchers.Unmatched. + */ +// Generated class, do not edit in place, but edit template instead. +final class PathMatcher1[T1](val toScala: SPathMatcher[Tuple1[T1]]) { + import JavaPathMatchers._ + + def slash() : PathMatcher1[T1] = fromScala1(toScala./) + def slash(segment: String) : PathMatcher1[T1] = fromScala1(toScala / segment) + def slash(next: PathMatcher0) : PathMatcher1[T1] = fromScala1(toScala./(next.toScala)) + def slash[N](next: PathMatcher1[N]) = fromScala2(toScala / next.toScala) + def slash[N1, N2](next: PathMatcher2[N1, N2]) = fromScalaTwoMoreThan1(toScala / next.toScala) + + def concat(segment: String) : PathMatcher1[T1] = fromScala1(toScala ~ segment) + def concat(next: PathMatcher0) : PathMatcher1[T1] = fromScala1(toScala ~ next.toScala) + def concat[N](next: PathMatcher1[N]) = fromScala2(toScala ~ next.toScala) + def concat[N1, N2](next: PathMatcher2[N1, N2]) = fromScalaTwoMoreThan1(toScala ~ next.toScala) + + def orElse(alternative: PathMatcher1[T1]) = fromScala1(toScala | alternative.toScala) + + def invert = fromScala0(!toScala) + + def repeat(min: Int, max: Int): PathMatcher1[JList[T1]] = repeat(min, max, SPathMatchers.Neutral) + def repeat(min: Int, max: Int, separator: PathMatcher0): PathMatcher1[JList[T1]] = + fromScala1(toScala.repeat(min, max, separator.toScala).map(_.asJava)) + + def map[U](f: JFunction[T1, U]) = + fromScala1(toScala.map(t ⇒ f.apply(t))) +} + +[2..20#/** + * A PathMatcher tries to match a prefix of a given string and returns either a PathMatcher.Matched instance + * if matched, otherwise PathMatchers.Unmatched. + */ +// Generated class, do not edit in place, but edit template instead. +final class PathMatcher1[[#T1#]](val toScala: SPathMatcher[Tuple1[[#T1#]]]) { + import JavaPathMatchers._ + + def slash() : PathMatcher1[[#T1#]] = fromScala1(toScala./) + def slash(segment: String) : PathMatcher1[[#T1#]] = fromScala1(toScala / segment) + def slash(next: PathMatcher##0) : PathMatcher1[[#T1#]] = fromScala1(toScala./(next.toScala)) + def slash[N](next: PathMatcher##1[N]) = fromScalaTwoMoreThan0(toScala / next.toScala) + def slash[[..2#N1#]](next: PathMatcher##2[[..2#N1#]]) = fromScalaTwoMoreThan1(toScala / next.toScala) + + def concat(segment: String) : PathMatcher1[[#T1#]] = fromScala1(toScala ~ segment) + def concat(next: PathMatcher##0) : PathMatcher1[[#T1#]] = fromScala1(toScala ~ next.toScala) + def concat[N](next: PathMatcher##1[N]) : PathMatcher2[[#T1#], N] = fromScalaTwoMoreThan0(toScala ~ next.toScala) + def concat[[..2#N1#]](next: PathMatcher##2[[..2#N1#]]) = fromScalaTwoMoreThan1(toScala ~ next.toScala) + + def orElse(alternative: PathMatcher1[[#T1#]]) = fromScala1(toScala | alternative.toScala) + + def invert = fromScala##0(!toScala) + +} +# +] + +/* The last 21 and 22 are special as we are not able to generate slash/concat for them (would need to add more params) */ + +/** + * A PathMatcher tries to match a prefix of a given string and returns either a PathMatcher.Matched instance + * if matched, otherwise PathMatchers.Unmatched. + * + * It is not possible to append more matchers with keeping their values as parameters (due to limit of Tuple22). + */ +// Generated class, do not edit in place, but edit template instead. +final class PathMatcher21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21](val toScala: SPathMatcher[Tuple21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21]]) { + import JavaPathMatchers._ + + def slash() : PathMatcher21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21] = fromScala21(toScala./) + def slash(segment: String) : PathMatcher21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21] = fromScala21(toScala / segment) + def slash(next: PathMatcher0) : PathMatcher21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21] = fromScala21(toScala./(next.toScala)) + def slash[N](next: PathMatcher1[N]) = fromScala22(toScala / next.toScala) + + def concat(segment: String) : PathMatcher21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21] = fromScala21(toScala ~ segment) + def concat(next: PathMatcher0) : PathMatcher21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21] = fromScala21(toScala ~ next.toScala) + def concat[N](next: PathMatcher1[N]) = fromScala22(toScala ~ next.toScala) + + def orElse(alternative: PathMatcher21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21]) = fromScala21(toScala | alternative.toScala) + + def invert = fromScala0(!toScala) + +} + + +/** + * A PathMatcher tries to match a prefix of a given string and returns either a PathMatcher.Matched instance + * if matched, otherwise PathMatchers.Unmatched. + * + * It is not possible to append more matchers with keeping their values as parameters (due to limit of Tuple22). + */ +// Generated class, do not edit in place, but edit template instead. +final class PathMatcher22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22](val toScala: SPathMatcher[Tuple22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22]]) { + import JavaPathMatchers._ + + def slash() : PathMatcher22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22] = fromScala22(toScala./) + def slash(segment: String) : PathMatcher22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22] = fromScala22(toScala / segment) + def slash(next: PathMatcher0) : PathMatcher22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22] = fromScala22(toScala./(next.toScala)) + + def concat(segment: String) : PathMatcher22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22] = fromScala22(toScala ~ segment) + def concat(next: PathMatcher0) : PathMatcher22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22] = fromScala22(toScala ~ next.toScala) + + def orElse(alternative: PathMatcher22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22]) = fromScala22(toScala | alternative.toScala) + + def invert = fromScala0(!toScala) + +} diff --git a/akka-http/src/main/boilerplate/akka/http/javadsl/server/directives/BasicDirectivesBase.scala.template b/akka-http/src/main/boilerplate/akka/http/javadsl/server/directives/BasicDirectivesBase.scala.template deleted file mode 100644 index 5492ea8a26..0000000000 --- a/akka-http/src/main/boilerplate/akka/http/javadsl/server/directives/BasicDirectivesBase.scala.template +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2009-2015 Lightbend Inc. - */ -package akka.http.javadsl.server.directives - -import akka.http.javadsl.server.Route -import akka.http.javadsl.server.RouteResult -import akka.http.javadsl.server.RequestVal -import akka.http.javadsl.server.RequestContext -import scala.annotation.varargs -import java.util.concurrent.CompletionStage - -abstract class BasicDirectivesBase { - /** INTERNAL API */ - private[http] def handle(extractions: RequestVal[_]*)(f: RequestContext ⇒ RouteResult): Route - - /** - * Handles the route using the given function. - * The function MUST NOT return `null`. - * - * If the `handler` is accessing request values these must be passed to this method in order for extraction to be performed. - */ - @varargs def handleWith(handler: akka.japi.function.Function[RequestContext, RouteResult], extractions: RequestVal[_]*): Route = - handle(extractions: _*)(ctx => handler(ctx)) - - /** - * Handles the route using the given function, completing the route once the returned [[java.util.concurrent.CompletionStage]] completes. - * The function MUST NOT return `null`. - * - * If the `handler` is accessing request values these must be passed to this method in order for extraction to be performed. - */ - @varargs def handleWithAsync(handler: akka.japi.function.Function[RequestContext, CompletionStage[RouteResult]], extractions: RequestVal[_]*): Route = - handle(extractions: _*)(ctx => ctx.completeWith(handler(ctx))) - - - [..21#/** - * Handles the route using the given function. The function MUST NOT return `null`. - * - * For convenience, using Java 8 lambda expressions as the `handler` function is recommended. - * For Java 6 or 7 users the convenience [[akka.http.javadsl.server.Handler1]] class (which itself extends - * [[akka.japi.function.Function2]] should prove to be useful, as it matches naming-wise with the number of - * handled request values. - */ - def handleWith1[[#T1#]]([#v1: RequestVal[T1]#], handler: akka.japi.function.Function2[RequestContext, [#T1#], RouteResult]): Route = - handle([#v1#])(ctx => handler(ctx, [#v1.get(ctx)#])) - - /** - * Handles the route using the given function, completing the route once the returned [[java.util.concurrent.CompletionStage]] completes. - * The function MUST NOT return `null`. - * - * For convenience, using Java 8 lambda expressions as the `handler` function is recommended. - * For Java 6 or 7 users the convenience [[akka.http.javadsl.server.Handler1]] class (which itself extends - * [[akka.japi.function.Function2]] should prove to be useful, as it matches naming-wise with the number of - * handled request values. - */ - def handleWithAsync1[[#T1#]]([#v1: RequestVal[T1]#], handler: akka.japi.function.Function2[RequestContext, [#T1#], CompletionStage[RouteResult]]): Route = - handle([#v1#])(ctx => ctx.completeWith(handler(ctx, [#v1.get(ctx)#])))# - - ] - -} diff --git a/akka-http/src/main/java/akka/http/javadsl/server/AbstractDirective.java b/akka-http/src/main/java/akka/http/javadsl/server/AbstractDirective.java deleted file mode 100644 index a08007d910..0000000000 --- a/akka-http/src/main/java/akka/http/javadsl/server/AbstractDirective.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.javadsl.server; - -/** - * Helper class to steer around SI-9013. - * - * It's currently impossible to implement a trait containing @varargs methods - * if the trait is written in Scala. Therefore, derive from this class and - * implement the method without varargs. - * FIXME: remove once SI-9013 is fixed. - * - * See https://issues.scala-lang.org/browse/SI-9013 - */ -public abstract class AbstractDirective implements Directive { - @Override - public Route route(Route first, Route... others) { - return createRoute(first, others); - } - - protected abstract Route createRoute(Route first, Route[] others); -} diff --git a/akka-http/src/main/java/akka/http/javadsl/server/Directive.java b/akka-http/src/main/java/akka/http/javadsl/server/Directive.java deleted file mode 100644 index d71a6fcaca..0000000000 --- a/akka-http/src/main/java/akka/http/javadsl/server/Directive.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.javadsl.server; - - -/** - * A directive is the basic building block for building routes by composing - * any kind of request or response processing into the main route a request - * flows through. It is a factory that creates a route when given a sequence of - * route alternatives to be augmented with the function the directive - * represents. - * - * The `path`-Directive, for example, filters incoming requests by checking if - * the URI of the incoming request matches the pattern and only invokes its inner - * routes for those requests. - */ -public interface Directive { - /** - * Creates the Route given a sequence of inner route alternatives. - */ - Route route(Route first, Route... others); -} diff --git a/akka-http/src/main/java/akka/http/javadsl/server/ExceptionHandlerBuilder.java b/akka-http/src/main/java/akka/http/javadsl/server/ExceptionHandlerBuilder.java new file mode 100644 index 0000000000..b12520ac0a --- /dev/null +++ b/akka-http/src/main/java/akka/http/javadsl/server/ExceptionHandlerBuilder.java @@ -0,0 +1,71 @@ +package akka.http.javadsl.server; + +import akka.japi.pf.FI; +import akka.japi.pf.PFBuilder; + +public class ExceptionHandlerBuilder { + private final PFBuilder delegate; + + public ExceptionHandlerBuilder() { + this(new PFBuilder<>()); + } + + private ExceptionHandlerBuilder(PFBuilder delegate) { + this.delegate = delegate; + } + + /** + * Add a new case statement to this builder. + * + * @param type a type to match the argument against + * @param apply an action to apply to the argument if the type matches + * @return a builder with the case statement added + */ + public

ExceptionHandlerBuilder match(final Class

type, FI.Apply apply) { + delegate.match(type, apply); + return this; + } + + /** + * Add a new case statement to this builder. + * + * @param type a type to match the argument against + * @param predicate a predicate that will be evaluated on the argument if the type matches + * @param apply an action to apply to the argument if the type matches and the predicate returns true + * @return a builder with the case statement added + */ + public

ExceptionHandlerBuilder match(final Class

type, + final FI.TypedPredicate

predicate, + final FI.Apply apply) { + delegate.match(type, predicate, apply); + return this; + } + + /** + * Add a new case statement to this builder. + * + * @param object the object to compare equals with + * @param apply an action to apply to the argument if the object compares equal + * @return a builder with the case statement added + */ + public

ExceptionHandlerBuilder matchEquals(final P object, + final FI.Apply apply) { + delegate.matchEquals(object, apply); + return this; + } + + /** + * Add a new case statement to this builder, that matches any argument. + * + * @param apply an action to apply to the argument + * @return a builder with the case statement added + */ + public ExceptionHandlerBuilder matchAny(final FI.Apply apply) { + delegate.matchAny(apply); + return this; + } + + public ExceptionHandler build() { + return ExceptionHandler.of(delegate.build()); + } +} diff --git a/akka-http/src/main/java/akka/http/javadsl/server/RegexConverters.java b/akka-http/src/main/java/akka/http/javadsl/server/RegexConverters.java new file mode 100644 index 0000000000..6d0ff6c5b2 --- /dev/null +++ b/akka-http/src/main/java/akka/http/javadsl/server/RegexConverters.java @@ -0,0 +1,18 @@ +package akka.http.javadsl.server; + +import java.util.regex.Pattern; + +import scala.collection.Seq; +import scala.collection.immutable.VectorBuilder; +import scala.util.matching.Regex; + +public class RegexConverters { + private static final Seq empty = new VectorBuilder().result(); + + /** + * Converts the given Java Pattern into a scala Regex, without recompiling it. + */ + public static Regex toScala(Pattern p) { + return new Regex(p, empty); + } +} diff --git a/akka-http/src/main/java/akka/http/javadsl/server/StringUnmarshallers.java b/akka-http/src/main/java/akka/http/javadsl/server/StringUnmarshallers.java new file mode 100644 index 0000000000..af9f905cc6 --- /dev/null +++ b/akka-http/src/main/java/akka/http/javadsl/server/StringUnmarshallers.java @@ -0,0 +1,78 @@ +package akka.http.javadsl.server; + +import java.util.function.Function; + +public class StringUnmarshallers { + /** + * An unmarshaller that returns the input String unchanged. + */ + public static final Unmarshaller STRING = StringUnmarshaller.sync(Function.identity()); + + /** + * An unmarshaller that parses the input String as a Byte in decimal notation. + */ + public static final Unmarshaller BYTE = assumeBoxed(StringUnmarshallerPredef.byteFromStringUnmarshaller()); + + /** + * An unmarshaller that parses the input String as a Short in decimal notation. + */ + public static final Unmarshaller SHORT = assumeBoxed(StringUnmarshallerPredef.shortFromStringUnmarshaller()); + + /** + * An unmarshaller that parses the input String as an Integer in decimal notation. + */ + public static final Unmarshaller INTEGER = assumeBoxed(StringUnmarshallerPredef.intFromStringUnmarshaller()); + + /** + * An unmarshaller that parses the input String as a Long in decimal notation. + */ + public static final Unmarshaller LONG = assumeBoxed(StringUnmarshallerPredef.longFromStringUnmarshaller()); + + /** + * An unmarshaller that parses the input String as a Byte in hexadecimal notation. + */ + public static final Unmarshaller BYTE_HEX = assumeBoxed(StringUnmarshallerPredef.HexByte()); + + /** + * An unmarshaller that parses the input String as a Short in hexadecimal notation. + */ + public static final Unmarshaller SHORT_HEX = assumeBoxed(StringUnmarshallerPredef.HexShort()); + + /** + * An unmarshaller that parses the input String as an Integer in hexadecimal notation. + */ + public static final Unmarshaller INTEGER_HEX = assumeBoxed(StringUnmarshallerPredef.HexInt()); + + /** + * An unmarshaller that parses the input String as a Long in hexadecimal notation. + */ + public static final Unmarshaller LONG_HEX = assumeBoxed(StringUnmarshallerPredef.HexLong()); + + /** + * An unmarshaller that parses the input String as a Float in decimal notation. + */ + public static final Unmarshaller FLOAT = assumeBoxed(StringUnmarshallerPredef.floatFromStringUnmarshaller()); + + /** + * An unmarshaller that parses the input String as a Double in decimal notation. + */ + public static final Unmarshaller DOUBLE = assumeBoxed(StringUnmarshallerPredef.doubleFromStringUnmarshaller()); + + /** + * An unmarshaller that parses the input String as a Boolean, matching "true", "yes", "on" as true, and "false", "no", "off" as false. + */ + public static final Unmarshaller BOOLEAN = assumeBoxed(StringUnmarshallerPredef.booleanFromStringUnmarshaller()); + + /** + * An unmarshaller that parses the input String as a UUID. + */ + public static final Unmarshaller UUID = StringUnmarshaller.sync(java.util.UUID::fromString); + + /** + * Assume that the given [src] is a marshaller to a Scala boxed primitive type, and coerces into the given Java boxed type T. + */ + @SuppressWarnings("unchecked") + private static Unmarshaller assumeBoxed(akka.http.scaladsl.unmarshalling.Unmarshaller src) { + return (Unmarshaller) src; + } +} diff --git a/akka-http/src/main/java/akka/http/javadsl/server/directives/CorrespondsTo.java b/akka-http/src/main/java/akka/http/javadsl/server/directives/CorrespondsTo.java new file mode 100644 index 0000000000..a44d09283c --- /dev/null +++ b/akka-http/src/main/java/akka/http/javadsl/server/directives/CorrespondsTo.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2009-2016 Lightbend Inc. + */ +package akka.http.javadsl.server.directives; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * INTERNAL API – used for consistency specs + * + * Used to hint at consistency spec implementations that a given JavaDSL method corresponds + * to a method of given name in ScalaDSL. + * + * E.g. a Java method paramsList could be hinted using @CorrespondsTo("paramsSeq"). + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface CorrespondsTo { + String value(); +} diff --git a/akka-http/src/main/scala/akka/http/impl/server/CookieImpl.scala b/akka-http/src/main/scala/akka/http/impl/server/CookieImpl.scala deleted file mode 100644 index ccd0be8685..0000000000 --- a/akka-http/src/main/scala/akka/http/impl/server/CookieImpl.scala +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.impl.server - -import java.util.Optional - -import akka.http.javadsl.model.headers.HttpCookie -import akka.http.javadsl.server.values.Cookie -import akka.http.javadsl.server.{ Directive, Directives, RequestVal } -import akka.http.scaladsl.server.Directive1 -import akka.http.scaladsl.server.directives.CookieDirectives._ -import akka.http.impl.util.JavaMapping.Implicits._ - -case class CookieImpl(name: String, domain: Optional[String] = Optional.empty[String], path: Optional[String] = Optional.empty[String]) extends Cookie { - def withDomain(domain: String): Cookie = copy(domain = Optional.of(domain)) - def withPath(path: String): Cookie = copy(path = Optional.of(path)) - - val value: RequestVal[String] = - new StandaloneExtractionImpl[String] { - def directive: Directive1[String] = cookie(name).map(_.value) - } - - def optionalValue(): RequestVal[Optional[String]] = - new StandaloneExtractionImpl[Optional[String]] { - def directive: Directive1[Optional[String]] = optionalCookie(name).map(_.map(_.value).asJava) - } - - def set(value: String): Directive = - Directives.custom(Directives.setCookie( - HttpCookie.create(name, value, domain, path), _, _: _*)) -} diff --git a/akka-http/src/main/scala/akka/http/impl/server/CustomRejectionWrapper.scala b/akka-http/src/main/scala/akka/http/impl/server/CustomRejectionWrapper.scala deleted file mode 100644 index e01254f4f8..0000000000 --- a/akka-http/src/main/scala/akka/http/impl/server/CustomRejectionWrapper.scala +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.impl.server - -import akka.http.javadsl.server.CustomRejection -import akka.http.scaladsl.server.Rejection - -/** - * A wrapper that packs a Java custom rejection into a Scala Rejection. - * - * INTERNAL API - */ -private[http] case class CustomRejectionWrapper(customRejection: CustomRejection) extends Rejection diff --git a/akka-http/src/main/scala/akka/http/impl/server/ExtractionImpl.scala b/akka-http/src/main/scala/akka/http/impl/server/ExtractionImpl.scala deleted file mode 100644 index e69fd2f381..0000000000 --- a/akka-http/src/main/scala/akka/http/impl/server/ExtractionImpl.scala +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.impl.server - -import scala.reflect.ClassTag -import akka.http.javadsl.server.{ RequestContext, RequestVal } -import akka.http.impl.util.JavaMapping.Implicits._ - -/** - * INTERNAL API - */ -private[http] trait ExtractionImplBase[T] extends RequestVal[T] { - protected[http] implicit def classTag: ClassTag[T] - def resultClass: Class[T] = classTag.runtimeClass.asInstanceOf[Class[T]] - - def get(ctx: RequestContext): T = - ctx.request.asScala.header[ExtractionMap].flatMap(_.get(this)) - .getOrElse(throw new RuntimeException(s"Value wasn't extracted! $this")) -} - -private[http] abstract class ExtractionImpl[T](implicit val classTag: ClassTag[T]) extends ExtractionImplBase[T] \ No newline at end of file diff --git a/akka-http/src/main/scala/akka/http/impl/server/FormFieldImpl.scala b/akka-http/src/main/scala/akka/http/impl/server/FormFieldImpl.scala deleted file mode 100644 index 01ff7d21c2..0000000000 --- a/akka-http/src/main/scala/akka/http/impl/server/FormFieldImpl.scala +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.impl.server - -import java.util.Optional - -import akka.http.javadsl.server.RequestVal -import akka.http.javadsl.server.values.FormField -import akka.http.scaladsl.common.{ StrictForm, NameUnmarshallerReceptacle, NameReceptacle } -import akka.http.scaladsl.unmarshalling._ - -import scala.reflect.ClassTag -import akka.http.scaladsl.server.{ Directives, Directive1 } - -import scala.compat.java8.OptionConverters._ - -/** - * INTERNAL API - */ -private[http] class FormFieldImpl[T, U](receptacle: NameReceptacle[T])( - implicit fu: FromStrictFormFieldUnmarshaller[T], tTag: ClassTag[U], conv: T ⇒ U) - extends StandaloneExtractionImpl[U] with FormField[U] { - import Directives._ - - def directive: Directive1[U] = - extractMaterializer.flatMap { implicit fm ⇒ - formField(receptacle).map(conv) - } - - def optional: RequestVal[Optional[U]] = - new StandaloneExtractionImpl[Optional[U]] { - def directive: Directive1[Optional[U]] = optionalDirective - } - - private def optionalDirective: Directive1[Optional[U]] = - extractMaterializer.flatMap { implicit fm ⇒ - formField(receptacle.?).map(v ⇒ v.map(conv).asJava) - } - - def withDefault(defaultValue: U): RequestVal[U] = - new StandaloneExtractionImpl[U] { - def directive: Directive1[U] = optionalDirective.map(_.orElse(defaultValue)) - } -} -object FormFieldImpl { - def apply[T, U](receptacle: NameReceptacle[T])(implicit fu: FromStrictFormFieldUnmarshaller[T], tTag: ClassTag[U], conv: T ⇒ U): FormField[U] = - new FormFieldImpl[T, U](receptacle)(fu, tTag, conv) - - def apply[T, U](receptacle: NameUnmarshallerReceptacle[T])(implicit tTag: ClassTag[U], conv: T ⇒ U): FormField[U] = - apply(new NameReceptacle[T](receptacle.name))(StrictForm.Field.unmarshallerFromFSU(receptacle.um), tTag, conv) -} diff --git a/akka-http/src/main/scala/akka/http/impl/server/HeaderImpl.scala b/akka-http/src/main/scala/akka/http/impl/server/HeaderImpl.scala deleted file mode 100644 index e504359bf5..0000000000 --- a/akka-http/src/main/scala/akka/http/impl/server/HeaderImpl.scala +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.impl.server - -import java.util.Optional - -import akka.http.javadsl.model.HttpHeader -import akka.http.javadsl.server.RequestVal -import akka.http.javadsl.server.values.Header -import akka.http.scaladsl -import akka.http.scaladsl.server._ -import akka.http.scaladsl.server.directives.BasicDirectives._ -import akka.http.scaladsl.server.directives.RouteDirectives._ - -import scala.compat.java8.OptionConverters._ -import scala.reflect.ClassTag - -/** - * Internal API - */ -private[http] object HeaderImpl { - def apply[T <: HttpHeader]( - name: String, - optionalDirective: ClassTag[T with scaladsl.model.HttpHeader] ⇒ Directive1[Option[T with scaladsl.model.HttpHeader]], tClassTag: ClassTag[T]): Header[T] = { - type U = T with scaladsl.model.HttpHeader - - // cast is safe because creation of javadsl.model.HttpHeader that are not <: scaladsl.model.HttpHeader is forbidden - implicit def uClassTag: ClassTag[U] = tClassTag.asInstanceOf[ClassTag[U]] - - new Header[U] { - val instanceDirective: Directive1[U] = - optionalDirective(uClassTag).flatMap { - case Some(v) ⇒ provide(v) - case None ⇒ reject(MissingHeaderRejection(name)) - } - - def instance(): RequestVal[U] = - new StandaloneExtractionImpl[U] { - def directive: Directive1[U] = instanceDirective - } - - def optionalInstance(): RequestVal[Optional[U]] = - new StandaloneExtractionImpl[Optional[U]] { - def directive: Directive1[Optional[U]] = optionalDirective(uClassTag).map(_.asJava) - } - - def value(): RequestVal[String] = - new StandaloneExtractionImpl[String] { - def directive: Directive1[String] = instanceDirective.map(_.value) - } - - def optionalValue(): RequestVal[Optional[String]] = - new StandaloneExtractionImpl[Optional[String]] { - def directive: Directive1[Optional[String]] = optionalDirective(uClassTag).map(_.map(_.value).asJava) - } - }.asInstanceOf[Header[T]] // undeclared covariance - } -} diff --git a/akka-http/src/main/scala/akka/http/impl/server/MarshallerImpl.scala b/akka-http/src/main/scala/akka/http/impl/server/MarshallerImpl.scala deleted file mode 100644 index b0b741cf8b..0000000000 --- a/akka-http/src/main/scala/akka/http/impl/server/MarshallerImpl.scala +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.impl.server - -import scala.concurrent.ExecutionContext -import akka.http.javadsl.server.Marshaller -import akka.http.scaladsl.marshalling - -/** - * INTERNAL API - */ -// FIXME: too lenient visibility, currently used to implement Java marshallers, needs proper API, see #16439 -case class MarshallerImpl[T](scalaMarshaller: ExecutionContext ⇒ marshalling.ToResponseMarshaller[T]) extends Marshaller[T] diff --git a/akka-http/src/main/scala/akka/http/impl/server/ParameterImpl.scala b/akka-http/src/main/scala/akka/http/impl/server/ParameterImpl.scala deleted file mode 100644 index 7a2c6b32c8..0000000000 --- a/akka-http/src/main/scala/akka/http/impl/server/ParameterImpl.scala +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.impl.server - -import java.util.Optional - -import akka.http.javadsl.server.RequestVal -import akka.http.javadsl.server.values.Parameter -import akka.http.scaladsl.common.{ NameUnmarshallerReceptacle, NameReceptacle } -import akka.http.scaladsl.server.Directives._ -import akka.http.scaladsl.unmarshalling._ - -import scala.reflect.ClassTag -import akka.http.scaladsl.server.directives.ParameterDirectives -import akka.http.scaladsl.server.Directive1 - -import scala.compat.java8.OptionConverters._ - -/** - * INTERNAL API - */ -private[http] class ParameterImpl[T, U](receptacle: NameReceptacle[T])( - implicit fu: FromStringUnmarshaller[T], tTag: ClassTag[U], conv: T ⇒ U) - extends StandaloneExtractionImpl[U] with Parameter[U] { - - import ParameterDirectives._ - def directive: Directive1[U] = parameter(receptacle).map(conv) - - def optional: RequestVal[Optional[U]] = - new StandaloneExtractionImpl[Optional[U]] { - def directive: Directive1[Optional[U]] = optionalDirective - } - - private def optionalDirective: Directive1[Optional[U]] = - extractMaterializer.flatMap { implicit fm ⇒ - parameter(receptacle.?).map(v ⇒ v.map(conv).asJava) - } - - def withDefault(defaultValue: U): RequestVal[U] = - new StandaloneExtractionImpl[U] { - def directive: Directive1[U] = optionalDirective.map(_.orElse(defaultValue)) - } -} -private[http] object ParameterImpl { - def apply[T, U](receptacle: NameReceptacle[T])(implicit fu: FromStringUnmarshaller[T], tTag: ClassTag[U], conv: T ⇒ U): Parameter[U] = - new ParameterImpl(receptacle)(fu, tTag, conv) - - def apply[T, U](receptacle: NameUnmarshallerReceptacle[T])(implicit tTag: ClassTag[U], conv: T ⇒ U): Parameter[U] = - new ParameterImpl(new NameReceptacle(receptacle.name))(receptacle.um, tTag, conv) -} \ No newline at end of file diff --git a/akka-http/src/main/scala/akka/http/impl/server/PathMatcherImpl.scala b/akka-http/src/main/scala/akka/http/impl/server/PathMatcherImpl.scala deleted file mode 100644 index b22a0e2b2d..0000000000 --- a/akka-http/src/main/scala/akka/http/impl/server/PathMatcherImpl.scala +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.impl.server - -import java.util.Optional - -import akka.http.javadsl.server.values.PathMatcher - -import scala.reflect.ClassTag -import akka.http.scaladsl.server.{ PathMatcher ⇒ ScalaPathMatcher } - -import scala.compat.java8.OptionConverters - -/** - * INTERNAL API - */ -private[http] class PathMatcherImpl[T: ClassTag](val matcher: ScalaPathMatcher[Tuple1[T]]) - extends ExtractionImpl[T] with PathMatcher[T] { - def optional: PathMatcher[Optional[T]] = new PathMatcherImpl[Optional[T]](matcher.?.map(OptionConverters.toJava)) -} \ No newline at end of file diff --git a/akka-http/src/main/scala/akka/http/impl/server/RejectionHandlerWrapper.scala b/akka-http/src/main/scala/akka/http/impl/server/RejectionHandlerWrapper.scala deleted file mode 100644 index 0a4fbeaa56..0000000000 --- a/akka-http/src/main/scala/akka/http/impl/server/RejectionHandlerWrapper.scala +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.impl.server - -import scala.collection.immutable - -import akka.http.javadsl.server -import akka.http.javadsl.server.RouteResult -import akka.http.scaladsl.server._ - -import akka.http.impl.util.JavaMapping.Implicits._ - -/** - * INTERNAL API - */ -private[http] class RejectionHandlerWrapper(javaHandler: server.RejectionHandler) extends RejectionHandler { - def apply(rejs: immutable.Seq[Rejection]): Option[Route] = Some { scalaCtx ⇒ - val ctx = new RequestContextImpl(scalaCtx) - - import javaHandler._ - def handle(): RouteResult = - if (rejs.isEmpty) handleEmptyRejection(ctx) - else rejs.head match { - case MethodRejection(supported) ⇒ - handleMethodRejection(ctx, supported.asJava) - case SchemeRejection(supported) ⇒ - handleSchemeRejection(ctx, supported) - case MissingQueryParamRejection(parameterName) ⇒ - handleMissingQueryParamRejection(ctx, parameterName) - case MalformedQueryParamRejection(parameterName, errorMsg, cause) ⇒ - handleMalformedQueryParamRejection(ctx, parameterName, errorMsg, cause.orNull) - case MissingFormFieldRejection(fieldName) ⇒ - handleMissingFormFieldRejection(ctx, fieldName) - case MalformedFormFieldRejection(fieldName, errorMsg, cause) ⇒ - handleMalformedFormFieldRejection(ctx, fieldName, errorMsg, cause.orNull) - case MissingHeaderRejection(headerName) ⇒ - handleMissingHeaderRejection(ctx, headerName) - case MalformedHeaderRejection(headerName, errorMsg, cause) ⇒ - handleMalformedHeaderRejection(ctx, headerName, errorMsg, cause.orNull) - case UnsupportedRequestContentTypeRejection(supported) ⇒ - handleUnsupportedRequestContentTypeRejection(ctx, supported.toList.toSeq.asJava) - case UnsupportedRequestEncodingRejection(supported) ⇒ - handleUnsupportedRequestEncodingRejection(ctx, supported.asJava) - case UnsatisfiableRangeRejection(unsatisfiableRanges, actualEntityLength) ⇒ - handleUnsatisfiableRangeRejection(ctx, unsatisfiableRanges.asJava, actualEntityLength) - case TooManyRangesRejection(maxRanges) ⇒ - handleTooManyRangesRejection(ctx, maxRanges) - case MalformedRequestContentRejection(message, cause) ⇒ - handleMalformedRequestContentRejection(ctx, message, cause) - case RequestEntityExpectedRejection ⇒ - handleRequestEntityExpectedRejection(ctx) - case UnacceptedResponseContentTypeRejection(supported) ⇒ - handleUnacceptedResponseContentTypeRejection(ctx, supported.toList.map(_.format).toSeq.asJava) - case UnacceptedResponseEncodingRejection(supported) ⇒ - handleUnacceptedResponseEncodingRejection(ctx, supported.toList.toSeq.asJava) - case AuthenticationFailedRejection(cause, challenge) ⇒ - handleAuthenticationFailedRejection(ctx, cause == AuthenticationFailedRejection.CredentialsMissing, challenge) - case AuthorizationFailedRejection ⇒ - handleAuthorizationFailedRejection(ctx) - case MissingCookieRejection(cookieName) ⇒ - handleMissingCookieRejection(ctx, cookieName) - case ExpectedWebSocketRequestRejection ⇒ - handleExpectedWebSocketRequestRejection(ctx) - case UnsupportedWebSocketSubprotocolRejection(supportedProtocol) ⇒ - handleUnsupportedWebSocketSubprotocolRejection(ctx, supportedProtocol) - case ValidationRejection(message, cause) ⇒ - handleValidationRejection(ctx, message, cause.orNull) - - case CustomRejectionWrapper(custom) ⇒ handleCustomRejection(ctx, custom) - case o ⇒ handleCustomScalaRejection(ctx, o) - } - - handle() match { - case r: RouteResultImpl ⇒ r.underlying - case PassRejectionRouteResult ⇒ scalaCtx.reject(rejs: _*) - } - } -} diff --git a/akka-http/src/main/scala/akka/http/impl/server/RequestContextImpl.scala b/akka-http/src/main/scala/akka/http/impl/server/RequestContextImpl.scala deleted file mode 100644 index 8e1118c96e..0000000000 --- a/akka-http/src/main/scala/akka/http/impl/server/RequestContextImpl.scala +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.impl.server - -import akka.http.javadsl.model.ContentType -import akka.http.javadsl.settings.{ RoutingSettings, ParserSettings } -import akka.http.scaladsl.model.HttpEntity -import akka.stream.Materializer -import scala.concurrent.{ ExecutionContextExecutor, Future } -import akka.http.javadsl.{ model ⇒ jm } -import akka.http.impl.util.JavaMapping.Implicits._ -import akka.http.scaladsl.server.{ RequestContext ⇒ ScalaRequestContext } -import akka.http.javadsl.server._ -import java.util.concurrent.CompletionStage -import scala.compat.java8.FutureConverters._ - -/** - * INTERNAL API - */ -private[http] final case class RequestContextImpl(underlying: ScalaRequestContext) extends RequestContext { - // provides auto-conversion to japi.RouteResult - import RouteResultImpl._ - - def request: jm.HttpRequest = underlying.request - def unmatchedPath: String = underlying.unmatchedPath.toString - - def completeWith(futureResult: Future[RouteResult]): RouteResult = - futureResult.flatMap { - case r: RouteResultImpl ⇒ r.underlying - }(executionContext()) - def completeWith(futureResult: CompletionStage[RouteResult]): RouteResult = completeWith(futureResult.toScala) - def complete(text: String): RouteResult = underlying.complete(text) - def complete(contentType: ContentType.NonBinary, text: String): RouteResult = - underlying.complete(HttpEntity(contentType.asScala, text)) - - def completeWithStatus(statusCode: Int): RouteResult = - completeWithStatus(jm.StatusCodes.get(statusCode)) - def completeWithStatus(statusCode: jm.StatusCode): RouteResult = - underlying.complete(statusCode.asScala) - def completeAs[T](marshaller: Marshaller[T], value: T): RouteResult = marshaller match { - case MarshallerImpl(m) ⇒ - implicit val marshaller = m(underlying.executionContext) - underlying.complete(value) - case _ ⇒ throw new IllegalArgumentException(s"Unsupported marshaller: $marshaller") - } - def complete(response: jm.HttpResponse): RouteResult = underlying.complete(response.asScala) - - def notFound(): RouteResult = underlying.reject() - - def reject(customRejection: CustomRejection): RouteResult = underlying.reject(CustomRejectionWrapper(customRejection)) - - def executionContext(): ExecutionContextExecutor = underlying.executionContext - def materializer(): Materializer = underlying.materializer - - override def settings: RoutingSettings = underlying.settings - override def parserSettings: ParserSettings = underlying.parserSettings -} diff --git a/akka-http/src/main/scala/akka/http/impl/server/RouteImplementation.scala b/akka-http/src/main/scala/akka/http/impl/server/RouteImplementation.scala deleted file mode 100644 index 2a8003fec9..0000000000 --- a/akka-http/src/main/scala/akka/http/impl/server/RouteImplementation.scala +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.impl.server - -import akka.http.impl.util.JavaMapping -import akka.http.javadsl.server.values.{ PathMatcher, BasicCredentials, OAuth2Credentials } -import akka.http.scaladsl.model.StatusCodes.Redirection -import akka.http.scaladsl.server.util.TupleOps.Join -import scala.language.implicitConversions -import scala.annotation.tailrec -import scala.collection.immutable -import akka.http.javadsl.model.ContentType -import akka.http.scaladsl.server.directives.{ Credentials, ContentTypeResolver } -import akka.http.scaladsl.server.directives.FileAndResourceDirectives.DirectoryRenderer -import akka.http.scaladsl.model.HttpHeader -import akka.http.scaladsl.model.headers.{ HttpCookie, CustomHeader } -import akka.http.scaladsl.server.{ Route ⇒ ScalaRoute, Directive ⇒ ScalaDirective, PathMatcher ⇒ ScalaPathMatcher, PathMatcher1, Directive0, Directive1, Directives } -import akka.http.impl.util.JavaMapping.Implicits._ -import akka.http.scaladsl.server -import akka.http.javadsl.server._ -import RouteStructure._ - -import scala.compat.java8.FutureConverters._ -import scala.compat.java8.OptionConverters._ -import akka.dispatch.ExecutionContexts.sameThreadExecutionContext - -/** - * INTERNAL API - */ -private[http] trait ExtractionMap extends CustomHeader { - def get[T](key: RequestVal[T]): Option[T] - def set[T](key: RequestVal[T], value: T): ExtractionMap - def addAll(values: Map[RequestVal[_], Any]): ExtractionMap -} -/** - * INTERNAL API - */ -private[http] object ExtractionMap { - val Empty = ExtractionMap(Map.empty) - implicit def apply(map: Map[RequestVal[_], Any]): ExtractionMap = - new ExtractionMap { - def get[T](key: RequestVal[T]): Option[T] = - map.get(key).asInstanceOf[Option[T]] - - def set[T](key: RequestVal[T], value: T): ExtractionMap = - ExtractionMap(map.updated(key, value)) - - def addAll(values: Map[RequestVal[_], Any]): ExtractionMap = - ExtractionMap(map ++ values) - - def renderInRequests = false - def renderInResponses = false - def name(): String = "ExtractedValues" - def value(): String = "" - } -} - -/** - * INTERNAL API - */ -private[http] object RouteImplementation extends Directives with server.RouteConcatenation { - def apply(route: Route): ScalaRoute = { - def directiveFor(route: DirectiveRoute): Directive0 = route match { - case RouteAlternatives() ⇒ ScalaDirective.Empty - case RawPathPrefix(elements) ⇒ pathMatcherDirective[String](elements, rawPathPrefix) - case RawPathPrefixTest(elements) ⇒ pathMatcherDirective[String](elements, rawPathPrefixTest) - case PathSuffix(elements) ⇒ pathMatcherDirective[String](elements, pathSuffix) - case PathSuffixTest(elements) ⇒ pathMatcherDirective[String](elements, pathSuffixTest) - case RedirectToTrailingSlashIfMissing(code) ⇒ redirectToTrailingSlashIfMissing(code.asScala.asInstanceOf[Redirection]) - case RedirectToNoTrailingSlashIfPresent(code) ⇒ redirectToNoTrailingSlashIfPresent(code.asScala.asInstanceOf[Redirection]) - - case MethodFilter(m) ⇒ method(m.asScala) - case Extract(extractions) ⇒ - extractRequestContext.flatMap { ctx ⇒ - extractions.map { e ⇒ - e.directive.flatMap(addExtraction(e.asInstanceOf[RequestVal[Any]], _)) - }.reduce(_ & _) - } - - case BasicAuthentication(authenticator) ⇒ - authenticateBasicAsync(authenticator.realm, { creds ⇒ - val javaCreds = - creds match { - case Credentials.Missing ⇒ - new BasicCredentials { - def available: Boolean = false - def identifier: String = throw new IllegalStateException("Credentials missing") - def verify(secret: String): Boolean = throw new IllegalStateException("Credentials missing") - } - case p @ Credentials.Provided(name) ⇒ - new BasicCredentials { - def available: Boolean = true - def identifier: String = name - def verify(secret: String): Boolean = p.verify(secret) - } - } - - authenticator.authenticate(javaCreds).toScala.map(_.asScala)(akka.dispatch.ExecutionContexts.sameThreadExecutionContext) - }).flatMap { user ⇒ - addExtraction(authenticator.asInstanceOf[RequestVal[Any]], user) - } - - case OAuth2Authentication(authenticator) ⇒ - authenticateOAuth2Async(authenticator.realm, { creds ⇒ - val javaCreds = - creds match { - case Credentials.Missing ⇒ - new OAuth2Credentials { - def available: Boolean = false - def identifier: String = throw new IllegalStateException("Credentials missing") - def verify(secret: String): Boolean = throw new IllegalStateException("Credentials missing") - } - case p @ Credentials.Provided(name) ⇒ - new OAuth2Credentials { - def available: Boolean = true - def identifier: String = name - def verify(secret: String): Boolean = p.verify(secret) - } - } - - authenticator.authenticate(javaCreds).toScala.map(_.asScala)(sameThreadExecutionContext) - }).flatMap { user ⇒ - addExtraction(authenticator.asInstanceOf[RequestVal[Any]], user) - } - - case EncodeResponse(coders) ⇒ - val scalaCoders = coders.map(_._underlyingScalaCoder()) - encodeResponseWith(scalaCoders.head, scalaCoders.tail: _*) - - case DecodeRequest(coders) ⇒ decodeRequestWith(coders.map(_._underlyingScalaCoder()): _*) - case Conditional(eTag, lastModified) ⇒ conditional(eTag.map(_.asScala), lastModified.map(_.asScala)) - case h: HostFilter ⇒ host(h.filter _) - case SchemeFilter(schemeName) ⇒ scheme(schemeName) - - case HandleExceptions(handler) ⇒ - val pf: akka.http.scaladsl.server.ExceptionHandler = akka.http.scaladsl.server.ExceptionHandler { - case e: RuntimeException ⇒ apply(handler.handle(e)) - } - handleExceptions(pf) - - case HandleRejections(handler) ⇒ handleRejections(new RejectionHandlerWrapper(handler)) - case Validated(isValid, errorMsg) ⇒ validate(isValid, errorMsg) - case RangeSupport() ⇒ withRangeSupport - case SetCookie(cookie) ⇒ setCookie(cookie.asScala) - case DeleteCookie(name, domain, path) ⇒ deleteCookie(HttpCookie(name, domain = domain, path = path, value = "deleted")) - } - - route match { - case route: DirectiveRoute ⇒ directiveFor(route).apply(fromAlternatives(route.children)) - case GetFromResource(path, contentType, classLoader) ⇒ getFromResource(path, contentType.asScala, classLoader) - case GetFromResourceDirectory(path, classLoader, resolver) ⇒ getFromResourceDirectory(path, classLoader)(scalaResolver(resolver)) - case GetFromFile(file, contentType) ⇒ getFromFile(file, contentType.asScala) - case GetFromDirectory(directory, true, resolver) ⇒ - extractExecutionContext { implicit ec ⇒ - getFromBrowseableDirectory(directory.getPath)(DirectoryRenderer.defaultDirectoryRenderer, scalaResolver(resolver)) - } - case FileAndResourceRouteWithDefaultResolver(constructor) ⇒ - RouteImplementation(constructor(new directives.ContentTypeResolver { - def resolve(fileName: String): ContentType = ContentTypeResolver.Default(fileName) - })) - - case HandleWebSocketMessages(handler) ⇒ handleWebSocketMessages(JavaMapping.toScala(handler)) - case Redirect(uri, code) ⇒ redirect(uri.asScala, code.asScala.asInstanceOf[Redirection]) // guarded by require in Redirect - - case dyn: DynamicDirectiveRoute1[t1Type] ⇒ - def runToRoute(t1: t1Type): ScalaRoute = - apply(dyn.createDirective(t1).route(dyn.innerRoute, dyn.moreInnerRoutes: _*)) - - requestValToDirective(dyn.value1)(runToRoute) - - case dyn: DynamicDirectiveRoute2[t1Type, t2Type] ⇒ - def runToRoute(t1: t1Type, t2: t2Type): ScalaRoute = - apply(dyn.createDirective(t1, t2).route(dyn.innerRoute, dyn.moreInnerRoutes: _*)) - - (requestValToDirective(dyn.value1) & requestValToDirective(dyn.value2))(runToRoute) - - case o: OpaqueRoute ⇒ (ctx ⇒ o.handle(new RequestContextImpl(ctx)).asInstanceOf[RouteResultImpl].underlying) - case p: Product ⇒ extractExecutionContext { implicit ec ⇒ complete((500, s"Not implemented: ${p.productPrefix}")) } - } - } - def pathMatcherDirective[T](matchers: immutable.Seq[PathMatcher[_]], - directive: PathMatcher1[T] ⇒ Directive1[T] // this type is too specific and only a placeholder for a proper polymorphic function - ): Directive0 = { - // Concatenating PathMatchers is a bit complicated as we don't want to build up a tuple - // but something which we can later split all the separate values and add them to the - // ExtractionMap. - // - // This is achieved by providing a specialized `Join` instance to use with PathMatcher - // which provides the desired behavior. - - type ValMap = Tuple1[Map[RequestVal[_], Any]] - object AddToMapJoin extends Join[ValMap, ValMap] { - type Out = ValMap - def apply(prefix: ValMap, suffix: ValMap): AddToMapJoin.Out = - Tuple1(prefix._1 ++ suffix._1) - } - def toScala(matcher: PathMatcher[_]): ScalaPathMatcher[ValMap] = - matcher.asInstanceOf[PathMatcherImpl[_]].matcher.transform(_.map(v ⇒ Tuple1(Map(matcher -> v._1)))) - def addExtractions(valMap: T): Directive0 = transformExtractionMap(_.addAll(valMap.asInstanceOf[Map[RequestVal[_], Any]])) - val reduced: ScalaPathMatcher[ValMap] = matchers.map(toScala).reduce(_.~(_)(AddToMapJoin)) - directive(reduced.asInstanceOf[PathMatcher1[T]]).flatMap(addExtractions) - } - - def fromAlternatives(alternatives: Seq[Route]): ScalaRoute = - alternatives.map(RouteImplementation.apply).reduce(_ ~ _) - - def addExtraction[T](key: RequestVal[T], value: T): Directive0 = - transformExtractionMap(_.set(key, value)) - - def transformExtractionMap(f: ExtractionMap ⇒ ExtractionMap): Directive0 = { - @tailrec def updateExtractionMap(headers: immutable.Seq[HttpHeader], prefix: Vector[HttpHeader] = Vector.empty): immutable.Seq[HttpHeader] = - headers match { - case (m: ExtractionMap) +: rest ⇒ f(m) +: (prefix ++ rest) - case other +: rest ⇒ updateExtractionMap(rest, prefix :+ other) - case Nil ⇒ f(ExtractionMap.Empty) +: prefix - } - mapRequest(_.mapHeaders(updateExtractionMap(_))) - } - - private def scalaResolver(resolver: directives.ContentTypeResolver): ContentTypeResolver = - ContentTypeResolver(f ⇒ resolver.resolve(f).asScala) - - def requestValToDirective[T](value: RequestVal[T]): Directive1[T] = - value match { - case s: StandaloneExtractionImpl[_] ⇒ s.directive - case v: RequestVal[_] ⇒ extract(ctx ⇒ v.get(new RequestContextImpl(ctx))) - } -} diff --git a/akka-http/src/main/scala/akka/http/impl/server/RouteResultImpl.scala b/akka-http/src/main/scala/akka/http/impl/server/RouteResultImpl.scala deleted file mode 100644 index 406c67c873..0000000000 --- a/akka-http/src/main/scala/akka/http/impl/server/RouteResultImpl.scala +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.impl.server - -import scala.language.implicitConversions -import scala.concurrent.Future -import akka.http.javadsl.{ server ⇒ js } -import akka.http.scaladsl.{ server ⇒ ss } - -/** - * INTERNAL API - */ -private[http] class RouteResultImpl(val underlying: Future[ss.RouteResult]) extends js.RouteResult - -/** - * INTERNAL API - */ -private[http] object RouteResultImpl { - implicit def autoConvert(result: Future[ss.RouteResult]): js.RouteResult = - new RouteResultImpl(result) -} - -/** - * Internal result that flags that a rejection was not handled by a rejection handler. - * - * INTERNAL API - */ -private[http] case object PassRejectionRouteResult extends js.RouteResult \ No newline at end of file diff --git a/akka-http/src/main/scala/akka/http/impl/server/RouteStructure.scala b/akka-http/src/main/scala/akka/http/impl/server/RouteStructure.scala deleted file mode 100644 index dee3b17121..0000000000 --- a/akka-http/src/main/scala/akka/http/impl/server/RouteStructure.scala +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.impl.server - -import java.io.File -import akka.http.javadsl.model.ws.Message -import akka.http.javadsl.server.values.{ PathMatcher, HttpBasicAuthenticator, OAuth2Authenticator } -import akka.stream.javadsl.Flow - -import scala.language.existentials -import scala.collection.immutable -import akka.http.javadsl.model._ -import akka.http.javadsl.model.headers.{ HttpCookie, EntityTag } -import akka.http.javadsl.server.directives.ContentTypeResolver -import akka.http.javadsl.server._ - -/** - * INTERNAL API - */ -private[http] object RouteStructure { - trait DirectiveRoute extends Route { - def innerRoute: Route - def moreInnerRoutes: immutable.Seq[Route] - - def children: immutable.Seq[Route] = innerRoute +: moreInnerRoutes - - require(children.nonEmpty) - } - case class RouteAlternatives()(val innerRoute: Route, val moreInnerRoutes: immutable.Seq[Route]) extends DirectiveRoute - case class MethodFilter(method: HttpMethod)(val innerRoute: Route, val moreInnerRoutes: immutable.Seq[Route]) extends DirectiveRoute { - def filter(ctx: RequestContext): Boolean = ctx.request.method == method - } - - abstract case class FileAndResourceRouteWithDefaultResolver(routeConstructor: ContentTypeResolver ⇒ Route) extends Route - case class GetFromResource(resourcePath: String, contentType: ContentType, classLoader: ClassLoader) extends Route - case class GetFromResourceDirectory(resourceDirectory: String, classLoader: ClassLoader, resolver: ContentTypeResolver) extends Route - case class GetFromFile(file: File, contentType: ContentType) extends Route - case class GetFromDirectory(directory: File, browseable: Boolean, resolver: ContentTypeResolver) extends Route - case class Redirect(uri: Uri, redirectionType: StatusCode) extends Route { - require(redirectionType.isRedirection, s"`redirectionType` must be a redirection status code but was $redirectionType") - } - - case class PathSuffix(pathElements: immutable.Seq[PathMatcher[_]])(val innerRoute: Route, val moreInnerRoutes: immutable.Seq[Route]) extends DirectiveRoute - case class PathSuffixTest(pathElements: immutable.Seq[PathMatcher[_]])(val innerRoute: Route, val moreInnerRoutes: immutable.Seq[Route]) extends DirectiveRoute - case class RawPathPrefix(pathElements: immutable.Seq[PathMatcher[_]])(val innerRoute: Route, val moreInnerRoutes: immutable.Seq[Route]) extends DirectiveRoute - case class RawPathPrefixTest(pathElements: immutable.Seq[PathMatcher[_]])(val innerRoute: Route, val moreInnerRoutes: immutable.Seq[Route]) extends DirectiveRoute - case class RedirectToTrailingSlashIfMissing(redirectionType: StatusCode)(val innerRoute: Route, val moreInnerRoutes: immutable.Seq[Route]) extends DirectiveRoute { - require(redirectionType.isRedirection, s"`redirectionType` must be a redirection status code but was $redirectionType") - } - case class RedirectToNoTrailingSlashIfPresent(redirectionType: StatusCode)(val innerRoute: Route, val moreInnerRoutes: immutable.Seq[Route]) extends DirectiveRoute { - require(redirectionType.isRedirection, s"`redirectionType` must be a redirection status code but was $redirectionType") - } - case class Extract(extractions: Seq[StandaloneExtractionImpl[_]])(val innerRoute: Route, val moreInnerRoutes: immutable.Seq[Route]) extends DirectiveRoute - case class BasicAuthentication(authenticator: HttpBasicAuthenticator[_])(val innerRoute: Route, val moreInnerRoutes: immutable.Seq[Route]) extends DirectiveRoute - case class OAuth2Authentication(authenticator: OAuth2Authenticator[_])(val innerRoute: Route, val moreInnerRoutes: immutable.Seq[Route]) extends DirectiveRoute - case class EncodeResponse(coders: immutable.Seq[Coder])(val innerRoute: Route, val moreInnerRoutes: immutable.Seq[Route]) extends DirectiveRoute - case class DecodeRequest(coders: immutable.Seq[Coder])(val innerRoute: Route, val moreInnerRoutes: immutable.Seq[Route]) extends DirectiveRoute - - case class Conditional(entityTag: Option[EntityTag] = None, lastModified: Option[DateTime] = None)(val innerRoute: Route, val moreInnerRoutes: immutable.Seq[Route]) extends DirectiveRoute { - require(entityTag.isDefined || lastModified.isDefined) - } - - abstract class DynamicDirectiveRoute1[T1](val value1: RequestVal[T1])(val innerRoute: Route, val moreInnerRoutes: immutable.Seq[Route]) extends Route { - def createDirective(t1: T1): Directive - } - abstract class DynamicDirectiveRoute2[T1, T2](val value1: RequestVal[T1], val value2: RequestVal[T2])(val innerRoute: Route, val moreInnerRoutes: immutable.Seq[Route]) extends Route { - def createDirective(t1: T1, t2: T2): Directive - } - case class Validated(isValid: Boolean, errorMsg: String)(val innerRoute: Route, val moreInnerRoutes: immutable.Seq[Route]) extends DirectiveRoute - - case class HandleExceptions(handler: ExceptionHandler)(val innerRoute: Route, val moreInnerRoutes: immutable.Seq[Route]) extends DirectiveRoute - case class HandleRejections(handler: RejectionHandler)(val innerRoute: Route, val moreInnerRoutes: immutable.Seq[Route]) extends DirectiveRoute - - sealed abstract class HostFilter extends DirectiveRoute { - def filter(hostName: String): Boolean - } - case class HostNameFilter(hostWhiteList: immutable.Seq[String])(val innerRoute: Route, val moreInnerRoutes: immutable.Seq[Route]) extends HostFilter { - def filter(hostName: String): Boolean = hostWhiteList.contains(hostName) - } - abstract class GenericHostFilter(val innerRoute: Route, val moreInnerRoutes: immutable.Seq[Route]) extends HostFilter - case class SchemeFilter(scheme: String)(val innerRoute: Route, val moreInnerRoutes: immutable.Seq[Route]) extends DirectiveRoute - - case class RangeSupport()(val innerRoute: Route, val moreInnerRoutes: immutable.Seq[Route]) extends DirectiveRoute - - case class HandleWebSocketMessages(handler: Flow[Message, Message, Any]) extends Route - - case class SetCookie(cookie: HttpCookie)(val innerRoute: Route, val moreInnerRoutes: immutable.Seq[Route]) extends DirectiveRoute - case class DeleteCookie(name: String, domain: Option[String], path: Option[String])(val innerRoute: Route, val moreInnerRoutes: immutable.Seq[Route]) extends DirectiveRoute - - abstract class OpaqueRoute(extractions: RequestVal[_]*) extends Route { - def handle(ctx: RequestContext): RouteResult - } -} - diff --git a/akka-http/src/main/scala/akka/http/impl/server/StandaloneExtractionImpl.scala b/akka-http/src/main/scala/akka/http/impl/server/StandaloneExtractionImpl.scala deleted file mode 100644 index 7d692d7d60..0000000000 --- a/akka-http/src/main/scala/akka/http/impl/server/StandaloneExtractionImpl.scala +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.impl.server - -import scala.concurrent.Future -import scala.reflect.ClassTag -import akka.http.javadsl.server.RequestVal -import akka.http.scaladsl.server._ - -/** - * INTERNAL API - */ -private[http] abstract class StandaloneExtractionImpl[T: ClassTag] extends ExtractionImpl[T] with RequestVal[T] { - def directive: Directive1[T] -} -private[http] object StandaloneExtractionImpl { - def apply[T: ClassTag](extractionDirective: Directive1[T]): RequestVal[T] = - new StandaloneExtractionImpl[T] { - def directive: Directive1[T] = extractionDirective - } -} - -/** - * INTERNAL API - */ -private[http] abstract class ExtractingStandaloneExtractionImpl[T: ClassTag] extends StandaloneExtractionImpl[T] { - def directive: Directive1[T] = Directives.extract(extract).flatMap(Directives.onSuccess(_)) - - def extract(ctx: RequestContext): Future[T] -} diff --git a/akka-http/src/main/scala/akka/http/impl/server/UnmarshallerImpl.scala b/akka-http/src/main/scala/akka/http/impl/server/UnmarshallerImpl.scala deleted file mode 100644 index 9f09a5c31e..0000000000 --- a/akka-http/src/main/scala/akka/http/impl/server/UnmarshallerImpl.scala +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.impl.server - -import scala.reflect.ClassTag -import akka.http.javadsl.server.Unmarshaller -import akka.http.scaladsl.unmarshalling.FromMessageUnmarshaller - -/** - * INTERNAL API - * - */ -private[http] case class UnmarshallerImpl[T](scalaUnmarshaller: FromMessageUnmarshaller[T])(implicit val classTag: ClassTag[T]) - extends Unmarshaller[T] diff --git a/akka-http/src/main/scala/akka/http/impl/server/Util.scala b/akka-http/src/main/scala/akka/http/impl/server/Util.scala deleted file mode 100644 index f415e11051..0000000000 --- a/akka-http/src/main/scala/akka/http/impl/server/Util.scala +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.impl.server - -import akka.http.scaladsl.unmarshalling.{ Unmarshaller, FromStringUnmarshaller } -import akka.http.scaladsl.util.FastFuture -import akka.japi.function -import akka.stream.Materializer - -import scala.concurrent.{ Future, ExecutionContext } -import scala.util.Try - -object Util { - def fromStringUnmarshallerFromFunction[T](convert: function.Function[String, T]): FromStringUnmarshaller[T] = - scalaUnmarshallerFromFunction(convert) - - def scalaUnmarshallerFromFunction[T, U](convert: function.Function[T, U]): Unmarshaller[T, U] = - new Unmarshaller[T, U] { - def apply(value: T)(implicit ec: ExecutionContext, mat: Materializer): Future[U] = FastFuture(Try(convert(value))) - } - - implicit class JApiFunctionAndThen[T, U](f1: function.Function[T, U]) { - def andThen[V](f2: U ⇒ V): function.Function[T, V] = - new function.Function[T, V] { - def apply(param: T): V = f2(f1(param)) - } - } -} diff --git a/akka-http/src/main/scala/akka/http/javadsl/RoutingJavaMapping.scala b/akka-http/src/main/scala/akka/http/javadsl/RoutingJavaMapping.scala new file mode 100644 index 0000000000..f63fcd6e3c --- /dev/null +++ b/akka-http/src/main/scala/akka/http/javadsl/RoutingJavaMapping.scala @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2009-2016 Lightbend Inc. + */ + +package akka.http.javadsl + +import java.util.concurrent.CompletionStage + +import akka.http.impl.util._ +import akka.http.impl.util.JavaMapping._ +import akka.http.javadsl +import akka.http.scaladsl +import javadsl.server.{ directives ⇒ jdirectives } +import akka.http.scaladsl.server.{ directives ⇒ sdirectives, RequestContext } + +import scala.collection.immutable + +/** + * INTERNAL API + */ +private[http] object RoutingJavaMapping { + + object Implicits { + import scala.language.implicitConversions + + implicit def convertToScala[J](j: J)(implicit mapping: J2SMapping[J]): mapping.S = mapping.toScala(j) + implicit def convertSeqToScala[J](j: Seq[J])(implicit mapping: J2SMapping[J]): immutable.Seq[mapping.S] = + j.map(mapping.toScala(_)).toList + + implicit def AddAsScala[J](javaObject: J)(implicit mapping: J2SMapping[J]): AsScala[mapping.S] = new AsScala[mapping.S] { + def asScala = convertToScala(javaObject) + } + implicit def AddAsJava[S](scalaObject: S)(implicit mapping: S2JMapping[S]): AsJava[mapping.J] = new AsJava[mapping.J] { + def asJava = mapping.toJava(scalaObject) + } + } + + implicit object Rejection extends Inherited[javadsl.server.Rejection, scaladsl.server.Rejection] + + implicit object RequestContext extends JavaMapping[javadsl.server.RequestContext, scaladsl.server.RequestContext] { + // TODO make it inhierit + // extends Inherited[javadsl.server.RequestContext, scaladsl.server.RequestContext] + override def toScala(javaObject: javadsl.server.RequestContext): RequestContext = javaObject.delegate + override def toJava(scalaObject: RequestContext): server.RequestContext = javadsl.server.RequestContext.wrap(scalaObject) + } + implicit object convertRouteResult extends Inherited[javadsl.server.RouteResult, scaladsl.server.RouteResult] + + implicit object convertDirectoryRenderer extends Inherited[jdirectives.DirectoryRenderer, sdirectives.FileAndResourceDirectives.DirectoryRenderer] + implicit object convertContentTypeResolver extends Inherited[jdirectives.ContentTypeResolver, sdirectives.ContentTypeResolver] + implicit object convertDirectoryListing extends Inherited[jdirectives.DirectoryListing, sdirectives.DirectoryListing] + + // implicit object javaToScalaMediaType extends Inherited[javadsl.model.MediaType, scaladsl.model.MediaType] + // implicit object javaToScalaContentType extends Inherited[javadsl.model.ContentType, scaladsl.model.ContentType] + // implicit object javaToScalaHttpMethod extends Inherited[javadsl.model.HttpMethod, scaladsl.model.HttpMethod] + // implicit object javaToScalaHttpRequest extends Inherited[javadsl.model.HttpRequest, scaladsl.model.HttpRequest] + // implicit object javaToScalaRequestEntity extends Inherited[javadsl.model.RequestEntity, scaladsl.model.RequestEntity] + // implicit object javaToScalaHttpResponse extends Inherited[javadsl.model.HttpResponse, scaladsl.model.HttpResponse] + // implicit object javaToScalaStatusCode extends Inherited[javadsl.model.StatusCode, scaladsl.model.StatusCode] + // implicit object javaToScalaHttpEncoding extends Inherited[javadsl.model.headers.HttpEncoding, scaladsl.model.headers.HttpEncoding] + // implicit object javaToScalaByteRange extends Inherited[javadsl.model.headers.ByteRange, scaladsl.model.headers.ByteRange] + // implicit object javaToScalaHttpChallenge extends Inherited[javadsl.model.headers.HttpChallenge, scaladsl.model.headers.HttpChallenge] + // implicit object javaToScalaHttpHeader extends Inherited[javadsl.model.HttpHeader, scaladsl.model.HttpHeader] + // implicit object javaToScalaLanguage extends Inherited[javadsl.model.headers.Language, scaladsl.model.headers.Language] + // implicit object javaToScalaHttpCookiePair extends Inherited[javadsl.model.headers.HttpCookiePair, scaladsl.model.headers.HttpCookiePair] + // implicit object javaToScalaHttpCookie extends Inherited[javadsl.model.headers.HttpCookie, scaladsl.model.headers.HttpCookie] + // implicit object javaToScalaHttpCredentials extends Inherited[javadsl.model.headers.HttpCredentials, scaladsl.model.headers.HttpCredentials] + // implicit object javaToScalaMessage extends Inherited[javadsl.model.ws.Message, scaladsl.model.ws.Message] + // implicit object javaToScalaEntityTag extends Inherited[javadsl.model.headers.EntityTag, scaladsl.model.headers.EntityTag] + // implicit object javaToScalaDateTime extends Inherited[javadsl.model.DateTime, scaladsl.model.DateTime] + implicit object convertRouteSettings extends Inherited[javadsl.settings.RoutingSettings, scaladsl.settings.RoutingSettings] + implicit object convertParserSettings extends Inherited[javadsl.settings.ParserSettings, scaladsl.settings.ParserSettings] + implicit object convertLogEntry extends Inherited[javadsl.server.directives.LogEntry, scaladsl.server.directives.LogEntry] + + // // not made implicit since these are subtypes of RequestEntity + // val javaToScalaHttpEntity extends Inherited[javadsl.model.HttpEntity, scaladsl.model.HttpEntity] + // val javaToScalaResponseEntity extends Inherited[javadsl.model.ResponseEntity, scaladsl.model.ResponseEntity] + + implicit final class ConvertCompletionStage[T](val stage: CompletionStage[T]) extends AnyVal { + import scala.compat.java8.FutureConverters._ + def asScala = stage.toScala + } +} + diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/CustomRejection.scala b/akka-http/src/main/scala/akka/http/javadsl/server/CustomRejection.scala deleted file mode 100644 index 304a3b926d..0000000000 --- a/akka-http/src/main/scala/akka/http/javadsl/server/CustomRejection.scala +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.javadsl.server - -/** - * Base class for application defined rejections. - */ -abstract class CustomRejection diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/CustomRequestVal.scala b/akka-http/src/main/scala/akka/http/javadsl/server/CustomRequestVal.scala deleted file mode 100644 index 90616b093b..0000000000 --- a/akka-http/src/main/scala/akka/http/javadsl/server/CustomRequestVal.scala +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.javadsl.server - -import akka.http.impl.server.{ RequestContextImpl, StandaloneExtractionImpl } -import akka.http.scaladsl.server.Directive1 -import akka.http.scaladsl.server.directives.BasicDirectives._ - -import scala.reflect.ClassTag - -/** - * Extend from this class and implement `extractValue` to create a custom request val. - */ -abstract class CustomRequestVal[T](clazz: Class[T]) extends StandaloneExtractionImpl[T]()(ClassTag(clazz)) { - final def directive: Directive1[T] = extract(ctx ⇒ extractValue(RequestContextImpl(ctx))) - - protected def extractValue(ctx: RequestContext): T -} diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/Directives.scala b/akka-http/src/main/scala/akka/http/javadsl/server/Directives.scala index aeea437ffd..4fc6fe5e1b 100644 --- a/akka-http/src/main/scala/akka/http/javadsl/server/Directives.scala +++ b/akka-http/src/main/scala/akka/http/javadsl/server/Directives.scala @@ -4,20 +4,26 @@ package akka.http.javadsl.server -import akka.http.javadsl.server.directives._ -import scala.collection.immutable +import akka.http.impl.util.JavaMapping +import akka.http.javadsl.server.directives.TimeoutDirectives -abstract class AllDirectives extends WebSocketDirectives +import scala.annotation.varargs + +abstract class AllDirectives extends TimeoutDirectives /** - * + * INTERNAL API */ object Directives extends AllDirectives { - /** - * INTERNAL API - */ - private[http] def custom(f: (Route, immutable.Seq[Route]) ⇒ Route): Directive = - new AbstractDirective { - def createRoute(first: Route, others: Array[Route]): Route = f(first, others.toList) - } + import JavaMapping.Implicits._ + import akka.http.javadsl.RoutingJavaMapping._ + + // These are repeated here since sometimes (?) the Scala compiler won't actually generate java-compatible + // signatures for varargs methods, making them show up as Seq instead of T... in Java. + + @varargs override def route(alternatives: Route*): Route = + super.route(alternatives: _*) + + @varargs override def getFromBrowseableDirectories(directories: String*): Route = + super.getFromBrowseableDirectories(directories: _*) } diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/ExceptionHandler.scala b/akka-http/src/main/scala/akka/http/javadsl/server/ExceptionHandler.scala index 4286121980..555faeaffb 100644 --- a/akka-http/src/main/scala/akka/http/javadsl/server/ExceptionHandler.scala +++ b/akka-http/src/main/scala/akka/http/javadsl/server/ExceptionHandler.scala @@ -4,6 +4,39 @@ package akka.http.javadsl.server -trait ExceptionHandler { - def handle(exception: RuntimeException): Route +import akka.http.scaladsl.server +import akka.japi.pf.PFBuilder +import akka.http.javadsl.settings.RoutingSettings +import akka.http.impl.util.JavaMapping.Implicits._ +import akka.http.javadsl.RoutingJavaMapping._ + +object ExceptionHandler { + /** + * Creates a new builder DSL for creating an ExceptionHandler + */ + def newBuilder: ExceptionHandlerBuilder = new ExceptionHandlerBuilder() + + /** INTERNAL API */ + def of(pf: PartialFunction[Throwable, Route]) = new ExceptionHandler(server.ExceptionHandler(pf.andThen(_.delegate))) +} + +/** + * Handles exceptions by turning them into routes. You can create an exception handler in Java code like the following example: + *
+ *     ExceptionHandler myHandler = ExceptionHandler.of (ExceptionHandler.newPFBuilder()
+ *         .match(IllegalArgumentException.class, x -> Directives.complete(StatusCodes.BAD_REQUEST))
+ *         .build()
+ *     ));
+ * 
+ */ +final class ExceptionHandler private (val asScala: server.ExceptionHandler) { + /** + * Creates a new [[ExceptionHandler]] which uses the given one as fallback for this one. + */ + def withFallback(that: ExceptionHandler): ExceptionHandler = new ExceptionHandler(asScala.withFallback(that.asScala)) + + /** + * "Seals" this handler by attaching a default handler as fallback if necessary. + */ + def seal(settings: RoutingSettings): ExceptionHandler = new ExceptionHandler(asScala.seal(settings.asScala)) } diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/Handler.scala b/akka-http/src/main/scala/akka/http/javadsl/server/Handler.scala deleted file mode 100644 index 4d69a80a24..0000000000 --- a/akka-http/src/main/scala/akka/http/javadsl/server/Handler.scala +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ -package akka.http.javadsl.server - -import java.util.concurrent.CompletionStage - -/** - * A route Handler that handles a request (that is encapsulated in a [[RequestContext]]) - * and returns a [[RouteResult]] with the response (or the rejection). - * - * Use the methods in [[RequestContext]] to create a [[RouteResult]]. - * A handler MUST NOT return `null` as the result. - * - * See also [[Handler1]], [[Handler2]], ..., until [[Handler21]] for handling `N` request values. - */ -//#handler -trait Handler extends akka.japi.function.Function[RequestContext, RouteResult] { - override def apply(ctx: RequestContext): RouteResult -} -//#handler - -/** - * A route Handler that handles a request (that is encapsulated in a [[RequestContext]]) - * and returns a [[java.util.concurrent.CompletionStage]] of [[RouteResult]] with the response (or the rejection). - * - * Use the methods in [[RequestContext]] to create a [[RouteResult]]. - * A handler MUST NOT return `null` as the result. - */ -trait AsyncHandler extends akka.japi.function.Function[RequestContext, CompletionStage[RouteResult]] { - override def apply(ctx: RequestContext): CompletionStage[RouteResult] -} diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/HttpApp.scala b/akka-http/src/main/scala/akka/http/javadsl/server/HttpApp.scala deleted file mode 100644 index ae348ca0b7..0000000000 --- a/akka-http/src/main/scala/akka/http/javadsl/server/HttpApp.scala +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.javadsl.server - -import akka.actor.ActorSystem -import akka.http.scaladsl.Http.ServerBinding -import java.util.concurrent.CompletionStage - -/** - * A convenience class to derive from to get everything from HttpService and Directives into scope. - * Implement the [[#createRoute]] method to provide the Route and then call [[#bindRoute]] - * to start the server on the specified interface. - */ -abstract class HttpApp - extends AllDirectives - with HttpServiceBase { - def createRoute(): Route - - /** - * Starts an HTTP server on the given interface and port. Creates the route by calling the - * user-implemented [[#createRoute]] method and uses the route to handle requests of the server. - */ - def bindRoute(interface: String, port: Int, system: ActorSystem): CompletionStage[ServerBinding] = - bindRoute(interface, port, createRoute(), system) -} diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/HttpService.scala b/akka-http/src/main/scala/akka/http/javadsl/server/HttpService.scala deleted file mode 100644 index 13bfc167b2..0000000000 --- a/akka-http/src/main/scala/akka/http/javadsl/server/HttpService.scala +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.javadsl.server - -import akka.actor.ActorSystem -import akka.http.scaladsl.{ server, Http } -import akka.http.scaladsl.Http.ServerBinding -import akka.http.impl.server.RouteImplementation -import akka.stream.{ ActorMaterializer, Materializer } -import akka.stream.scaladsl.{ Keep, Sink } -import java.util.concurrent.CompletionStage -import scala.compat.java8.FutureConverters._ - -trait HttpServiceBase { - /** - * Starts a server on the given interface and port and uses the route to handle incoming requests. - */ - def bindRoute(interface: String, port: Int, route: Route, system: ActorSystem): CompletionStage[ServerBinding] = { - implicit val sys = system - implicit val materializer = ActorMaterializer() - handleConnectionsWithRoute(interface, port, route, system, materializer) - } - - /** - * Starts a server on the given interface and port and uses the route to handle incoming requests. - */ - def bindRoute(interface: String, port: Int, route: Route, system: ActorSystem, materializer: Materializer): CompletionStage[ServerBinding] = - handleConnectionsWithRoute(interface, port, route, system, materializer) - - /** - * Uses the route to handle incoming connections and requests for the ServerBinding. - */ - def handleConnectionsWithRoute(interface: String, port: Int, route: Route, system: ActorSystem, materializer: Materializer): CompletionStage[ServerBinding] = { - implicit val s = system - implicit val m = materializer - - import system.dispatcher - val r: server.Route = RouteImplementation(route) - Http(system).bind(interface, port).toMat(Sink.foreach(_.handleWith(akka.http.scaladsl.server.RouteResult.route2HandlerFlow(r))))(Keep.left).run()(materializer).toJava - } -} - -/** - * Provides the entrypoints to create an Http server from a route. - */ -object HttpService extends HttpServiceBase diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/Marshaller.scala b/akka-http/src/main/scala/akka/http/javadsl/server/Marshaller.scala index b322a85d45..179f5d05a3 100644 --- a/akka-http/src/main/scala/akka/http/javadsl/server/Marshaller.scala +++ b/akka-http/src/main/scala/akka/http/javadsl/server/Marshaller.scala @@ -4,8 +4,164 @@ package akka.http.javadsl.server -/** - * A marker trait for a marshaller that converts a value of type `T` to an - * HttpResponse. - */ -trait Marshaller[T] \ No newline at end of file +import java.util.function + +import akka.http.impl.util.JavaMapping +import akka.http.scaladsl.marshalling +import akka.japi.Util + +import scala.concurrent.ExecutionContext +import akka.http.javadsl.model.ContentType +import akka.http.javadsl.model.MediaType +import akka.http.scaladsl +import akka.http.javadsl.model.HttpEntity +import akka.http.scaladsl.marshalling._ +import akka.http.javadsl.model.HttpResponse +import akka.http.javadsl.model.HttpRequest +import akka.http.javadsl.model.RequestEntity +import akka.util.ByteString +import akka.http.scaladsl.model.{FormData, HttpCharset} +import akka.http.javadsl.model.StatusCode +import akka.http.javadsl.model.HttpHeader + +import scala.collection.JavaConverters._ +import akka.http.impl.util.JavaMapping.Implicits._ + +import scala.language.implicitConversions + +object Marshaller { + + import JavaMapping.Implicits._ + + def fromScala[A, B](scalaMarshaller: marshalling.Marshaller[A, B]): Marshaller[A, B] = new Marshaller()(scalaMarshaller) + + /** + * Safe downcasting of the output type of the marshaller to a superclass. + * + * Marshaller is covariant in B, i.e. if B2 is a subclass of B1, + * then Marshaller[X,B2] is OK to use where Marshaller[X,B1] is expected. + */ + def downcast[A, B1, B2 <: B1](m: Marshaller[A, B2]): Marshaller[A, B1] = m.asInstanceOf[Marshaller[A, B1]] + + /** + * Safe downcasting of the output type of the marshaller to a superclass. + * + * Marshaller is covariant in B, i.e. if B2 is a subclass of B1, + * then Marshaller[X,B2] is OK to use where Marshaller[X,B1] is expected. + */ + def downcast[A, B1, B2 <: B1](m: Marshaller[A, B2], target: Class[B1]): Marshaller[A, B1] = m.asInstanceOf[Marshaller[A, B1]] + + def stringToEntity: Marshaller[String, RequestEntity] = fromScala(marshalling.Marshaller.StringMarshaller) + + def byteArrayToEntity: Marshaller[Array[Byte], RequestEntity] = fromScala(marshalling.Marshaller.ByteArrayMarshaller) + + def charArrayToEntity: Marshaller[Array[Char], RequestEntity] = fromScala(marshalling.Marshaller.CharArrayMarshaller) + + def byteStringToEntity: Marshaller[ByteString, RequestEntity] = fromScala(marshalling.Marshaller.ByteStringMarshaller) + + def fromDataToEntity: Marshaller[FormData, RequestEntity] = fromScala(marshalling.Marshaller.FormDataMarshaller) + + def byteStringMarshaller(t: ContentType): Marshaller[ByteString, RequestEntity] = + fromScala(scaladsl.marshalling.Marshaller.byteStringMarshaller(t.asScala)) + + + // TODO make sure these are actually usable in a sane way + + def wrapEntity[A, C](f: function.BiFunction[ExecutionContext, C, A], m: Marshaller[A, RequestEntity], mediaType: MediaType): Marshaller[C, RequestEntity] = { + val scalaMarshaller = m.asScalaToEntityMarshaller + fromScala(scalaMarshaller.wrapWithEC(mediaType.asScala) { ctx => c: C => f(ctx, c) } (ContentTypeOverrider.forEntity)) + } + + def wrapEntity[A, C, E <: RequestEntity](f: function.Function[C, A], m: Marshaller[A, E], mediaType: MediaType): Marshaller[C, RequestEntity] = { + val scalaMarshaller = m.asScalaToEntityMarshaller + fromScala(scalaMarshaller.wrap(mediaType.asScala)((in: C) => f.apply(in))(ContentTypeOverrider.forEntity)) + } + + + def entityToOKResponse[A](m: Marshaller[A, _ <: RequestEntity]): Marshaller[A, HttpResponse] = { + fromScala(marshalling.Marshaller.fromToEntityMarshaller[A]()(m.asScalaToEntityMarshaller)) + } + + def entityToResponse[A, R <: RequestEntity](status: StatusCode, m: Marshaller[A, R]): Marshaller[A, HttpResponse] = { + fromScala(marshalling.Marshaller.fromToEntityMarshaller[A](status.asScala)(m.asScalaToEntityMarshaller)) + } + + def entityToResponse[A](status: StatusCode, headers: java.lang.Iterable[HttpHeader], m: Marshaller[A, _ <: RequestEntity]): Marshaller[A, HttpResponse] = { + fromScala(marshalling.Marshaller.fromToEntityMarshaller[A](status.asScala, Util.immutableSeq(headers).map(_.asScala))(m.asScalaToEntityMarshaller)) // TODO can we avoid the map() ? + } + + def entityToOKResponse[A](headers: java.lang.Iterable[HttpHeader], m: Marshaller[A, _ <: RequestEntity]): Marshaller[A, HttpResponse] = { + fromScala(marshalling.Marshaller.fromToEntityMarshaller[A](headers = Util.immutableSeq(headers).map(_.asScala))(m.asScalaToEntityMarshaller)) // TODO avoid the map() + } + + // these are methods not varargs to avoid call site warning about unchecked type params + + /** + * Helper for creating a "super-marshaller" from a number of "sub-marshallers". + * Content-negotiation determines, which "sub-marshaller" eventually gets to do the job. + */ + def oneOf[A, B](ms: Marshaller[A, B]*): Marshaller[A, B] = { + fromScala(marshalling.Marshaller.oneOf[A, B](ms.map(_.asScala): _*)) + } + + /** + * Helper for creating a "super-marshaller" from a number of "sub-marshallers". + * Content-negotiation determines, which "sub-marshaller" eventually gets to do the job. + */ + def oneOf[A, B](m1: Marshaller[A, B], m2: Marshaller[A, B], m3: Marshaller[A, B]): Marshaller[A, B] = { + fromScala(marshalling.Marshaller.oneOf(m1.asScala, m2.asScala, m3.asScala)) + } + + /** + * Helper for creating a "super-marshaller" from a number of "sub-marshallers". + * Content-negotiation determines, which "sub-marshaller" eventually gets to do the job. + */ + def oneOf[A, B](m1: Marshaller[A, B], m2: Marshaller[A, B], m3: Marshaller[A, B], m4: Marshaller[A, B]): Marshaller[A, B] = { + fromScala(marshalling.Marshaller.oneOf(m1.asScala, m2.asScala, m3.asScala, m4.asScala)) + } + + /** + * Helper for creating a "super-marshaller" from a number of "sub-marshallers". + * Content-negotiation determines, which "sub-marshaller" eventually gets to do the job. + */ + def oneOf[A, B](m1: Marshaller[A, B], m2: Marshaller[A, B], m3: Marshaller[A, B], m4: Marshaller[A, B], m5: Marshaller[A, B]): Marshaller[A, B] = { + fromScala(marshalling.Marshaller.oneOf(m1.asScala, m2.asScala, m3.asScala, m4.asScala, m5.asScala)) + } + + /** + * Helper for creating a synchronous [[Marshaller]] to content with a fixed charset from the given function. + */ + def withFixedContentType[A, B](contentType: ContentType, f: java.util.function.Function[A, B]): Marshaller[A, B] = + fromScala(marshalling.Marshaller.withFixedContentType(contentType.asScala)(f.apply)) + + /** + * Helper for creating a synchronous [[Marshaller]] to content with a negotiable charset from the given function. + */ + def withOpenCharset[A, B](mediaType: MediaType.WithOpenCharset, f: java.util.function.BiFunction[A, HttpCharset, B]): Marshaller[A, B] = + fromScala(marshalling.Marshaller.withOpenCharset(mediaType.asScala)(f.apply)) + + /** + * Helper for creating a synchronous [[Marshaller]] to non-negotiable content from the given function. + */ + def opaque[A, B](f: function.Function[A, B]): Marshaller[A, B] = + fromScala(scaladsl.marshalling.Marshaller.opaque[A, B] { a => f.apply(a) }) + + + + implicit def asScalaToResponseMarshaller[T](m: Marshaller[T, akka.http.javadsl.model.HttpResponse]): ToResponseMarshaller[T] = + m.asScala.map(_.asScala) + + implicit def asScalaEntityMarshaller[T](m: Marshaller[T, akka.http.javadsl.model.RequestEntity]): akka.http.scaladsl.marshalling.Marshaller[T, akka.http.scaladsl.model.RequestEntity] = + m.asScala.map(_.asScala) +} + +class Marshaller[A, B] private(implicit val asScala: marshalling.Marshaller[A, B]) { + import Marshaller.fromScala + + // TODO would be nice to not need this special case + def asScalaToEntityMarshaller[C]: marshalling.Marshaller[A, C] = asScala.asInstanceOf[marshalling.Marshaller[A, C]] + + def map[C](f: function.Function[B, C]): Marshaller[A, C] = fromScala(asScala.map(f.apply)) + + def compose[C](f: function.Function[C, A]): Marshaller[C, B] = fromScala(asScala.compose(f.apply)) +} diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/Marshallers.scala b/akka-http/src/main/scala/akka/http/javadsl/server/Marshallers.scala deleted file mode 100644 index f65f9c2b1f..0000000000 --- a/akka-http/src/main/scala/akka/http/javadsl/server/Marshallers.scala +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.javadsl.server - -import akka.http.javadsl.model._ -import akka.http.scaladsl.marshalling.{ ToResponseMarshaller, Marshaller ⇒ ScalaMarshaller } -import akka.http.impl.server.MarshallerImpl -import akka.http.scaladsl -import akka.japi.function -import akka.util.ByteString - -/** - * A collection of predefined marshallers. - */ -object Marshallers { - /** - * A marshaller that marshals a String to a `text/plain` using a charset as negotiated with the - * peer. - */ - def String: Marshaller[String] = MarshallerImpl(implicit ctx ⇒ implicitly[ToResponseMarshaller[String]]) - - import akka.http.impl.util.JavaMapping.Implicits._ - import akka.http.impl.server.Util._ - - /** - * Creates a marshaller by specifying a media type and conversion function from `T` to String. - * The charset for encoding the response will be negotiated with the client. - */ - def toEntityString[T](mediaType: MediaType.WithOpenCharset, convert: function.Function[T, String]): Marshaller[T] = - MarshallerImpl(_ ⇒ ScalaMarshaller.stringMarshaller(mediaType.asScala).compose[T](convert(_))) - - /** - * Creates a marshaller by specifying a media type and conversion function from `T` to String. - * The charset for encoding the response will be negotiated with the client. - */ - def toEntityString[T](mediaType: MediaType.WithFixedCharset, convert: function.Function[T, String]): Marshaller[T] = - MarshallerImpl(_ ⇒ ScalaMarshaller.stringMarshaller(mediaType.asScala).compose[T](convert(_))) - - /** - * Creates a marshaller from a ContentType and a conversion function from `T` to a `Array[Byte]`. - */ - def toEntityBytes[T](contentType: ContentType, convert: function.Function[T, Array[Byte]]): Marshaller[T] = - toEntity(contentType, convert.andThen(scaladsl.model.HttpEntity(contentType.asScala, _))) - - /** - * Creates a marshaller from a ContentType and a conversion function from `T` to a `ByteString`. - */ - def toEntityByteString[T](contentType: ContentType, convert: function.Function[T, ByteString]): Marshaller[T] = - toEntity(contentType, convert.andThen(scaladsl.model.HttpEntity(contentType.asScala, _))) - - /** - * Creates a marshaller from a ContentType and a conversion function from `T` to a `ResponseEntity`. - */ - def toEntity[T](contentType: ContentType, convert: function.Function[T, ResponseEntity]): Marshaller[T] = - MarshallerImpl { _ ⇒ - ScalaMarshaller.withFixedContentType(contentType.asScala)(t ⇒ - HttpResponse.create().withStatus(200).withEntity(convert(t)).asScala) - } - - /** - * Creates a marshaller from a ContentType and a conversion function from `T` to an `HttpResponse`. - */ - def toResponse[T](contentType: ContentType, convert: function.Function[T, HttpResponse]): Marshaller[T] = - MarshallerImpl { _ ⇒ - ScalaMarshaller.withFixedContentType(contentType.asScala)(t ⇒ convert(t).asScala) - } -} diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/PathMatchers.scala b/akka-http/src/main/scala/akka/http/javadsl/server/PathMatchers.scala new file mode 100644 index 0000000000..de29561079 --- /dev/null +++ b/akka-http/src/main/scala/akka/http/javadsl/server/PathMatchers.scala @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2009-2016 Lightbend Inc. + */ +package akka.http.javadsl.server + +import java.util.UUID +import java.util.regex.Pattern + +import akka.http.scaladsl.model.Uri.Path + +import scala.collection.JavaConverters._ + +import akka.http.scaladsl.server.{ PathMatcher ⇒ SPathMatcher } +import akka.http.scaladsl.server.{ PathMatchers ⇒ SPathMatchers } +import akka.http.javadsl.server.RegexConverters.toScala + +final class PathMatchers + +object PathMatchers { + import JavaPathMatchers._ + + private[this] val IntegerSegment: PathMatcher1[java.lang.Integer] = fromScala1(SPathMatchers.IntNumber.map { i ⇒ i: java.lang.Integer }) + private[this] val LongSegment: PathMatcher1[java.lang.Long] = fromScala1(SPathMatchers.LongNumber.map { i ⇒ i: java.lang.Long }) + private[this] val HexIntegerSegment: PathMatcher1[java.lang.Integer] = fromScala1(SPathMatchers.HexIntNumber.map { i ⇒ i: java.lang.Integer }) + private[this] val HexLongSegment: PathMatcher1[java.lang.Long] = fromScala1(SPathMatchers.HexLongNumber.map { i ⇒ i: java.lang.Long }) + private[this] val DoubleSegment: PathMatcher1[java.lang.Double] = fromScala1(SPathMatchers.DoubleNumber.map { i ⇒ i: java.lang.Double }) + private[this] val UUIDSegment: PathMatcher1[UUID] = fromScala1(SPathMatchers.JavaUUID) + + private[this] val Neutral = fromScala0(SPathMatchers.Neutral) + private[this] val Slash = new PathMatcher0(SPathMatchers.Slash) + private[this] val PathEnd = new PathMatcher0(SPathMatchers.PathEnd) + private[this] val Remaining = new PathMatcher1[String](SPathMatchers.Remaining) + private[this] val RemainingPath = new PathMatcher1[Path](SPathMatchers.RemainingPath) + private[this] val Segment = new PathMatcher1[String](SPathMatchers.Segment) + private[this] val Segments = new PathMatcher1[java.util.List[String]](SPathMatchers.Segments.map(_.asJava)) + + /** + * Converts a path string containing slashes into a PathMatcher that interprets slashes as + * path segment separators. + */ + def separateOnSlashes(segments: String): PathMatcher0 = fromScala0(SPathMatchers.separateOnSlashes(segments)) + + /** + * A PathMatcher that matches a single slash character ('/'). + */ + def slash(): PathMatcher0 = Slash + + /** + * Creates a PathMatcher that consumes (a prefix of) the first path segment + * (if the path begins with a segment). + */ + def segment(segment: String): PathMatcher0 = new PathMatcher0(SPathMatcher(segment)) + + /** + * Creates a PathMatcher that consumes (a prefix of) the first path segment + * if the path begins with a segment (a prefix of) which matches the given regex. + * Extracts either the complete match (if the regex doesn't contain a capture group) or + * the capture group (if the regex contains exactly one). + * If the regex contains more than one capture group the method throws an IllegalArgumentException. + */ + def segment(regex: Pattern): PathMatcher1[String] = new PathMatcher1[String](SPathMatcher[Tuple1[String]](toScala(regex))) + + /** + * A PathMatcher that matches between `min` and `max` (both inclusively) path segments (separated by slashes) + * as a List[String]. If there are more than `count` segments present the remaining ones will be left unmatched. + * If the path has a trailing slash this slash will *not* be matched. + */ + def segments(min: Int, max: Int): PathMatcher1[java.util.List[String]] = new PathMatcher1[java.util.List[String]](SPathMatchers.Segments(min, max).map(_.asJava)) + + /** + * A PathMatcher that matches the given number of path segments (separated by slashes) as a List[String]. + * If there are more than `count` segments present the remaining ones will be left unmatched. + * If the path has a trailing slash this slash will *not* be matched. + */ + def segments(count: Int) = new PathMatcher1[java.util.List[String]](SPathMatchers.Segments(count).map(_.asJava)) + + /** + * A PathMatcher that efficiently matches a number of digits and extracts their (non-negative) Int value. + * The matcher will not match 0 digits or a sequence of digits that would represent an Int value larger + * than Int.MaxValue. + */ + def integerSegment: PathMatcher1[java.lang.Integer] = IntegerSegment + + /** + * A PathMatcher that efficiently matches a number of digits and extracts their (non-negative) Long value. + * The matcher will not match 0 digits or a sequence of digits that would represent an Long value larger + * than Long.MaxValue. + */ + def longSegment: PathMatcher1[java.lang.Long] = LongSegment + + /** + * A PathMatcher that efficiently matches a number of hex-digits and extracts their (non-negative) Int value. + * The matcher will not match 0 digits or a sequence of digits that would represent an Int value larger + * than Int.MaxValue. + */ + def hexIntegerSegment: PathMatcher1[java.lang.Integer] = HexIntegerSegment + + /** + * A PathMatcher that efficiently matches a number of hex-digits and extracts their (non-negative) Long value. + * The matcher will not match 0 digits or a sequence of digits that would represent an Long value larger + * than Long.MaxValue. + */ + def hexLongSegment: PathMatcher1[java.lang.Long] = HexLongSegment + + /** + * A PathMatcher that matches and extracts a Double value. The matched string representation is the pure decimal, + * optionally signed form of a double value, i.e. without exponent. + */ + def doubleSegment: PathMatcher1[java.lang.Double] = DoubleSegment + + /** + * A PathMatcher that matches and extracts a java.util.UUID instance. + */ + def uuidSegment: PathMatcher1[UUID] = UUIDSegment + + /** + * A PathMatcher that always matches, doesn't consume anything and extracts nothing. + * Serves mainly as a neutral element in PathMatcher composition. + */ + def neutral: PathMatcher0 = Neutral + + /** + * A PathMatcher that matches the very end of the requests URI path. + */ + def pathEnd: PathMatcher0 = PathEnd + + /** + * A PathMatcher that matches and extracts the complete remaining, + * unmatched part of the request's URI path as an (encoded!) String. + */ + def remaining: PathMatcher1[String] = Remaining + + /** + * A PathMatcher that matches and extracts the complete remaining, + * unmatched part of the request's URI path. + */ + def remainingPath: PathMatcher1[Path] = RemainingPath + + /** + * A PathMatcher that matches if the unmatched path starts with a path segment. + * If so the path segment is extracted as a String. + */ + def segment: PathMatcher1[String] = Segment + + /** + * A PathMatcher that matches up to 128 remaining segments as a List[String]. + * This can also be no segments resulting in the empty list. + * If the path has a trailing slash this slash will *not* be matched. + */ + def segments: PathMatcher1[java.util.List[String]] = Segments + +} + diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/RejectionHandler.scala b/akka-http/src/main/scala/akka/http/javadsl/server/RejectionHandler.scala index 2aceb8f266..363fc00c8c 100644 --- a/akka-http/src/main/scala/akka/http/javadsl/server/RejectionHandler.scala +++ b/akka-http/src/main/scala/akka/http/javadsl/server/RejectionHandler.scala @@ -1,190 +1,59 @@ /* * Copyright (C) 2009-2016 Lightbend Inc. */ - package akka.http.javadsl.server -import java.{ lang ⇒ jl } +import akka.http.scaladsl.server +import java.util.function +import scala.reflect.ClassTag +import scala.collection.JavaConverters._ -import akka.http.impl.server.PassRejectionRouteResult -import akka.http.javadsl.model.{ ContentTypeRange, HttpMethod } -import akka.http.javadsl.model.headers.{ HttpChallenge, ByteRange, HttpEncoding } -import akka.http.scaladsl.server.Rejection - -/** - * The base class for defining a RejectionHandler to be used with the `handleRejection` directive. - * Override one of the handler methods to define a route to be used in case the inner route - * rejects a request with the given rejection. - * - * Default implementations pass the rejection to outer handlers. - */ -abstract class RejectionHandler { +object RejectionHandler { /** - * Callback called to handle the empty rejection which represents the - * "Not Found" condition. + * Creates a new [[RejectionHandler]] builder. */ - def handleEmptyRejection(ctx: RequestContext): RouteResult = passRejection() - - /** - * Callback called to handle rejection created by method filters. - * Signals that the request was rejected because the HTTP method is unsupported. - * - * The default implementation does not handle the rejection. - */ - def handleMethodRejection(ctx: RequestContext, supported: HttpMethod): RouteResult = passRejection() - - /** - * Callback called to handle rejection created by scheme filters. - * Signals that the request was rejected because the Uri scheme is unsupported. - */ - def handleSchemeRejection(ctx: RequestContext, supported: String): RouteResult = passRejection() - - /** - * Callback called to handle rejection created by parameter filters. - * Signals that the request was rejected because a query parameter was not found. - */ - def handleMissingQueryParamRejection(ctx: RequestContext, parameterName: String): RouteResult = passRejection() - - /** - * Callback called to handle rejection created by parameter filters. - * Signals that the request was rejected because a query parameter could not be interpreted. - */ - def handleMalformedQueryParamRejection(ctx: RequestContext, parameterName: String, errorMsg: String, - cause: Throwable): RouteResult = passRejection() - - /** - * Callback called to handle rejection created by form field filters. - * Signals that the request was rejected because a form field was not found. - */ - def handleMissingFormFieldRejection(ctx: RequestContext, fieldName: String): RouteResult = passRejection() - - /** - * Callback called to handle rejection created by form field filters. - * Signals that the request was rejected because a form field could not be interpreted. - */ - def handleMalformedFormFieldRejection(ctx: RequestContext, fieldName: String, errorMsg: String, - cause: Throwable): RouteResult = passRejection() - - /** - * Callback called to handle rejection created by header directives. - * Signals that the request was rejected because a required header could not be found. - */ - def handleMissingHeaderRejection(ctx: RequestContext, headerName: String): RouteResult = passRejection() - - /** - * Callback called to handle rejection created by header directives. - * Signals that the request was rejected because a header value is malformed. - */ - def handleMalformedHeaderRejection(ctx: RequestContext, headerName: String, errorMsg: String, - cause: Throwable): RouteResult = passRejection() - - /** - * Callback called to handle rejection created by unmarshallers. - * Signals that the request was rejected because the requests content-type is unsupported. - */ - def handleUnsupportedRequestContentTypeRejection(ctx: RequestContext, supported: jl.Iterable[ContentTypeRange]): RouteResult = passRejection() - - /** - * Callback called to handle rejection created by decoding filters. - * Signals that the request was rejected because the requests content encoding is unsupported. - */ - def handleUnsupportedRequestEncodingRejection(ctx: RequestContext, supported: HttpEncoding): RouteResult = passRejection() - - /** - * Callback called to handle rejection created by range directives. - * Signals that the request was rejected because the requests contains only unsatisfiable ByteRanges. - * The actualEntityLength gives the client a hint to create satisfiable ByteRanges. - */ - def handleUnsatisfiableRangeRejection(ctx: RequestContext, unsatisfiableRanges: jl.Iterable[ByteRange], actualEntityLength: Long): RouteResult = passRejection() - - /** - * Callback called to handle rejection created by range directives. - * Signals that the request contains too many ranges. An irregular high number of ranges - * indicates a broken client or a denial of service attack. - */ - def handleTooManyRangesRejection(ctx: RequestContext, maxRanges: Int): RouteResult = passRejection() - - /** - * Callback called to handle rejection created by unmarshallers. - * Signals that the request was rejected because unmarshalling failed with an error that wasn't - * an `IllegalArgumentException`. Usually that means that the request content was not of the expected format. - * Note that semantic issues with the request content (e.g. because some parameter was out of range) - * will usually trigger a `ValidationRejection` instead. - */ - def handleMalformedRequestContentRejection(ctx: RequestContext, message: String, cause: Throwable): RouteResult = passRejection() - - /** - * Callback called to handle rejection created by unmarshallers. - * Signals that the request was rejected because an message body entity was expected but not supplied. - */ - def handleRequestEntityExpectedRejection(ctx: RequestContext): RouteResult = passRejection() - - /** - * Callback called to handle rejection created by marshallers. - * Signals that the request was rejected because the service is not capable of producing a response entity whose - * content type is accepted by the client. - */ - def handleUnacceptedResponseContentTypeRejection(ctx: RequestContext, supported: jl.Iterable[String]): RouteResult = passRejection() - - /** - * Callback called to handle rejection created by encoding filters. - * Signals that the request was rejected because the service is not capable of producing a response entity whose - * content encoding is accepted by the client - */ - def handleUnacceptedResponseEncodingRejection(ctx: RequestContext, supported: jl.Iterable[HttpEncoding]): RouteResult = passRejection() - - /** - * Callback called to handle rejection created by an [[akka.http.javadsl.server.values.HttpBasicAuthenticator]]. - * Signals that the request was rejected because the user could not be authenticated. The reason for the rejection is - * specified in the cause. - * - * If credentialsMissing is false, existing credentials were rejected. - */ - def handleAuthenticationFailedRejection(ctx: RequestContext, credentialsMissing: Boolean, challenge: HttpChallenge): RouteResult = passRejection() - - /** - * Callback called to handle rejection created by the 'authorize' directive. - * Signals that the request was rejected because the user is not authorized. - */ - def handleAuthorizationFailedRejection(ctx: RequestContext): RouteResult = passRejection() - - /** - * Callback called to handle rejection created by the `cookie` directive. - * Signals that the request was rejected because a cookie was not found. - */ - def handleMissingCookieRejection(ctx: RequestContext, cookieName: String): RouteResult = passRejection() - - /** - * Callback called to handle rejection created when a websocket request was expected but none was found. - */ - def handleExpectedWebSocketRequestRejection(ctx: RequestContext): RouteResult = passRejection() - - /** - * Callback called to handle rejection created when a websocket request was not handled because none - * of the given subprotocols was supported. - */ - def handleUnsupportedWebSocketSubprotocolRejection(ctx: RequestContext, supportedProtocol: String): RouteResult = passRejection() - - /** - * Callback called to handle rejection created by the `validation` directive as well as for `IllegalArgumentExceptions` - * thrown by domain model constructors (e.g. via `require`). - * It signals that an expected value was semantically invalid. - */ - def handleValidationRejection(ctx: RequestContext, message: String, cause: Throwable): RouteResult = passRejection() - - /** - * Callback called to handle any custom rejection defined by the application. - */ - def handleCustomRejection(ctx: RequestContext, rejection: CustomRejection): RouteResult = passRejection() - - /** - * Callback called to handle any other Scala rejection that is not covered by this class. - */ - def handleCustomScalaRejection(ctx: RequestContext, rejection: Rejection): RouteResult = passRejection() - - /** - * Use the RouteResult returned by this method in handler implementations to signal that a rejection was not handled - * and should be passed to an outer rejection handler. - */ - protected final def passRejection(): RouteResult = PassRejectionRouteResult + def newBuilder = new RejectionHandlerBuilder(server.RejectionHandler.newBuilder) + + def defaultHandler = new RejectionHandler(server.RejectionHandler.default) +} + +final class RejectionHandler(val asScala: server.RejectionHandler) { + /** + * Creates a new [[RejectionHandler]] which uses the given one as fallback for this one. + */ + def withFallback(fallback: RejectionHandler) = new RejectionHandler(asScala.withFallback(fallback.asScala)) + + /** + * "Seals" this handler by attaching a default handler as fallback if necessary. + */ + def seal = new RejectionHandler(asScala.seal) +} + +class RejectionHandlerBuilder(asScala: server.RejectionHandler.Builder) { + def build = new RejectionHandler(asScala.result()) + + /** + * Handles a single [[Rejection]] with the given partial function. + */ + def handle[T <: Rejection](t: Class[T], handler: function.Function[T, Route]): RejectionHandlerBuilder = { + asScala.handle { case r if t.isInstance(r) => handler.apply(t.cast(r)).delegate } + this + } + + /** + * Handles several Rejections of the same type at the same time. + * The list passed to the given function is guaranteed to be non-empty. + */ + def handleAll[T <: Rejection](t: Class[T], handler: function.Function[java.util.List[T], Route]): RejectionHandlerBuilder = { + asScala.handleAll { rejections:collection.immutable.Seq[T] => handler.apply(rejections.asJava).delegate } (ClassTag(t)) + this + } + + /** + * Handles the special "not found" case using the given [[Route]]. + */ + def handleNotFound(route: Route): RejectionHandlerBuilder = { + asScala.handleNotFound(route.delegate) + this + } } diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/Rejections.scala b/akka-http/src/main/scala/akka/http/javadsl/server/Rejections.scala new file mode 100644 index 0000000000..6480fed2b1 --- /dev/null +++ b/akka-http/src/main/scala/akka/http/javadsl/server/Rejections.scala @@ -0,0 +1,374 @@ +/* + * Copyright (C) 2009-2016 Lightbend Inc. + */ +package akka.http.javadsl.server + +import akka.http.impl.util.JavaMapping +import akka.http.javadsl.RoutingJavaMapping +import akka.http.javadsl.model.ContentTypeRange +import akka.http.scaladsl.model +import akka.http.scaladsl.server.ContentNegotiator.Alternative +import akka.http.scaladsl.server._ +import akka.http.javadsl.model._ +import akka.http.javadsl.model.headers.HttpEncoding +import akka.http.javadsl.model.headers.ByteRange +import akka.http.javadsl.model.headers.HttpChallenge +import java.util.Optional +import java.util.function.{ Function ⇒ JFunction } +import java.lang.{ Iterable ⇒ JIterable } +import akka.http.scaladsl +import akka.japi.Util + +import scala.compat.java8.OptionConverters._ +import scala.collection.immutable +import scala.collection.JavaConverters._ + +/** + * A rejection encapsulates a specific reason why a Route was not able to handle a request. Rejections are gathered + * up over the course of a Route evaluation and finally converted to [[akka.http.scaladsl.model.HttpResponse]]s by the + * `handleRejections` directive, if there was no way for the request to be completed. + */ +trait Rejection + +/** + * Rejection created by method filters. + * Signals that the request was rejected because the HTTP method is unsupported. + */ +trait MethodRejection extends Rejection { + def supported: HttpMethod +} + +/** + * Rejection created by scheme filters. + * Signals that the request was rejected because the Uri scheme is unsupported. + */ +trait SchemeRejection extends Rejection { + def supported: String +} + +/** + * Rejection created by parameter filters. + * Signals that the request was rejected because a query parameter was not found. + */ +trait MissingQueryParamRejection extends Rejection { + def parameterName: String +} + +/** + * Rejection created by parameter filters. + * Signals that the request was rejected because a query parameter could not be interpreted. + */ +trait MalformedQueryParamRejection extends Rejection { + def parameterName: String + def errorMsg: String + def getCause: Optional[Throwable] +} + +/** + * Rejection created by form field filters. + * Signals that the request was rejected because a form field was not found. + */ +trait MissingFormFieldRejection extends Rejection { + def fieldName: String +} + +/** + * Rejection created by form field filters. + * Signals that the request was rejected because a form field could not be interpreted. + */ +trait MalformedFormFieldRejection extends Rejection { + def fieldName: String + def errorMsg: String + def getCause: Optional[Throwable] +} + +/** + * Rejection created by header directives. + * Signals that the request was rejected because a required header could not be found. + */ +trait MissingHeaderRejection extends Rejection { + def headerName: String +} + +/** + * Rejection created by header directives. + * Signals that the request was rejected because a header value is malformed. + */ +trait MalformedHeaderRejection extends Rejection { + def headerName: String + def errorMsg: String + def getCause: Optional[Throwable] +} + +/** + * Rejection created by unmarshallers. + * Signals that the request was rejected because the requests content-type is unsupported. + */ +trait UnsupportedRequestContentTypeRejection extends Rejection { + def getSupported: java.util.Set[akka.http.javadsl.model.ContentTypeRange] +} + +/** + * Rejection created by decoding filters. + * Signals that the request was rejected because the requests content encoding is unsupported. + */ +trait UnsupportedRequestEncodingRejection extends Rejection { + def supported: HttpEncoding +} + +/** + * Rejection created by range directives. + * Signals that the request was rejected because the requests contains only unsatisfiable ByteRanges. + * The actualEntityLength gives the client a hint to create satisfiable ByteRanges. + */ +trait UnsatisfiableRangeRejection extends Rejection { + def getUnsatisfiableRanges: JIterable[ByteRange] + def actualEntityLength: Long +} + +/** + * Rejection created by range directives. + * Signals that the request contains too many ranges. An irregular high number of ranges + * indicates a broken client or a denial of service attack. + */ +trait TooManyRangesRejection extends Rejection { + def maxRanges: Int +} + +/** + * Rejection created by unmarshallers. + * Signals that the request was rejected because unmarshalling failed with an error that wasn't + * an `IllegalArgumentException`. Usually that means that the request content was not of the expected format. + * Note that semantic issues with the request content (e.g. because some parameter was out of range) + * will usually trigger a `ValidationRejection` instead. + */ +trait MalformedRequestContentRejection extends Rejection { + def message: String + def getCause: Throwable +} + +/** + * Rejection created by unmarshallers. + * Signals that the request was rejected because an message body entity was expected but not supplied. + */ +abstract class RequestEntityExpectedRejection extends Rejection +object RequestEntityExpectedRejection { + def get: RequestEntityExpectedRejection = scaladsl.server.RequestEntityExpectedRejection +} + +/** + * Rejection created by marshallers. + * Signals that the request was rejected because the service is not capable of producing a response entity whose + * content type is accepted by the client + */ +trait UnacceptedResponseContentTypeRejection extends Rejection { + def supported: immutable.Set[ContentNegotiator.Alternative] +} + +/** + * Rejection created by encoding filters. + * Signals that the request was rejected because the service is not capable of producing a response entity whose + * content encoding is accepted by the client + */ +trait UnacceptedResponseEncodingRejection extends Rejection { + def getSupported: java.util.Set[HttpEncoding] +} +object UnacceptedResponseEncodingRejection { + def create(supported: HttpEncoding): UnacceptedResponseEncodingRejection = + scaladsl.server.UnacceptedResponseEncodingRejection(JavaMapping.toScala(supported)) +} + +/** + * Rejection created by the various [[akka.http.javadsl.server.directives.SecurityDirectives]]. + * Signals that the request was rejected because the user could not be authenticated. The reason for the rejection is + * specified in the cause. + */ +trait AuthenticationFailedRejection extends Rejection { + def cause: AuthenticationFailedRejection.Cause + def challenge: HttpChallenge +} + +object AuthenticationFailedRejection { + /** + * Signals the cause of the failed authentication. + */ + trait Cause + + /** + * Signals the cause of the rejecting was that the user could not be authenticated, because the `WWW-Authenticate` + * header was not supplied. + */ + trait CredentialsMissing extends Cause + + /** + * Signals the cause of the rejecting was that the user could not be authenticated, because the supplied credentials + * are invalid. + */ + trait CredentialsRejected extends Cause +} + +/** + * Rejection created by the 'authorize' directive. + * Signals that the request was rejected because the user is not authorized. + */ +trait AuthorizationFailedRejection extends Rejection +object AuthorizationFailedRejection { + def get = scaladsl.server.AuthorizationFailedRejection +} + +/** + * Rejection created by the `cookie` directive. + * Signals that the request was rejected because a cookie was not found. + */ +trait MissingCookieRejection extends Rejection { + def cookieName: String +} + +/** + * Rejection created when a websocket request was expected but none was found. + */ +trait ExpectedWebSocketRequestRejection extends Rejection +object ExpectedWebSocketRequestRejection { + def get: ExpectedWebSocketRequestRejection = scaladsl.server.ExpectedWebSocketRequestRejection +} + +/** + * Rejection created when a websocket request was not handled because none of the given subprotocols + * was supported. + */ +trait UnsupportedWebSocketSubprotocolRejection extends Rejection { + def supportedProtocol: String +} + +/** + * Rejection created by the `validation` directive as well as for `IllegalArgumentExceptions` + * thrown by domain model constructors (e.g. via `require`). + * It signals that an expected value was semantically invalid. + */ +trait ValidationRejection extends Rejection { + def message: String + def getCause: Optional[Throwable] +} + +/** + * A special Rejection that serves as a container for a transformation function on rejections. + * It is used by some directives to "cancel" rejections that are added by later directives of a similar type. + * + * Consider this route structure for example: + * + * put { reject(ValidationRejection("no") } ~ get { ... } + * + * If this structure is applied to a PUT request the list of rejections coming back contains three elements: + * + * 1. A ValidationRejection + * 2. A MethodRejection + * 3. A TransformationRejection holding a function filtering out the MethodRejection + * + * so that in the end the RejectionHandler will only see one rejection (the ValidationRejection), because the + * MethodRejection added by the `get` directive is canceled by the `put` directive (since the HTTP method + * did indeed match eventually). + */ +trait TransformationRejection extends Rejection { + def getTransform: JFunction[JIterable[Rejection], JIterable[Rejection]] +} + +/** + * A Throwable wrapping a Rejection. + * Can be used for marshalling `Future[T]` or `Try[T]` instances, whose failure side is supposed to trigger a route + * rejection rather than an Exception that is handled by the nearest ExceptionHandler. + * (Custom marshallers can of course use it as well.) + */ +trait RejectionError extends RuntimeException { + def rejection: Rejection +} + +object Rejections { + import akka.http.scaladsl.{ model ⇒ m } + import akka.http.scaladsl.{ server ⇒ s } + import scala.language.implicitConversions + import JavaMapping.Implicits._ + import RoutingJavaMapping._ + + def method(supported: HttpMethod): MethodRejection = + s.MethodRejection(JavaMapping.toScala(supported)) + + def scheme(supported: String): SchemeRejection = + s.SchemeRejection(supported) + + def missingQueryParam(parameterName: String): MissingQueryParamRejection = + s.MissingQueryParamRejection(parameterName) + + def malformedQueryParam(parameterName: String, errorMsg: String): MalformedQueryParamRejection = + s.MalformedQueryParamRejection(parameterName, errorMsg) + def malformedQueryParam(parameterName: String, errorMsg: String, cause: Optional[Throwable]): MalformedQueryParamRejection = + s.MalformedQueryParamRejection(parameterName, errorMsg, cause.asScala) + + def missingFormField(fieldName: String): MissingFormFieldRejection = + s.MissingFormFieldRejection(fieldName) + + def malformedFormField(fieldName: String, errorMsg: String): MalformedFormFieldRejection = + s.MalformedFormFieldRejection(fieldName, errorMsg) + def malformedFormField(fieldName: String, errorMsg: String, cause: Optional[Throwable]): s.MalformedFormFieldRejection = + s.MalformedFormFieldRejection(fieldName, errorMsg, cause.asScala) + + def missingHeader(headerName: String): MissingHeaderRejection = + s.MissingHeaderRejection(headerName) + + def malformedHeader(headerName: String, errorMsg: String): MalformedHeaderRejection = + s.MalformedHeaderRejection(headerName, errorMsg) + def malformedHeader(headerName: String, errorMsg: String, cause: Optional[Throwable]): s.MalformedHeaderRejection = + s.MalformedHeaderRejection(headerName, errorMsg, cause.asScala) + + def unsupportedRequestContentType(supported: java.lang.Iterable[MediaType]): UnsupportedRequestContentTypeRejection = + s.UnsupportedRequestContentTypeRejection(supported.asScala.map(m ⇒ scaladsl.model.ContentTypeRange(m.asScala)).toSet) + + def unsupportedRequestEncoding(supported: HttpEncoding): UnsupportedRequestEncodingRejection = + s.UnsupportedRequestEncodingRejection(supported.asScala) + + def unsatisfiableRange(unsatisfiableRanges: java.lang.Iterable[ByteRange], actualEntityLength: Long) = + UnsatisfiableRangeRejection(Util.immutableSeq(unsatisfiableRanges).map(_.asScala), actualEntityLength) + + def tooManyRanges(maxRanges: Int) = TooManyRangesRejection(maxRanges) + + def malformedRequestContent(message: String, cause: Throwable) = + MalformedRequestContentRejection(message, cause) + + def requestEntityExpected = RequestEntityExpectedRejection + + def unacceptedResponseContentType(supportedContentTypes: java.lang.Iterable[ContentType], + supportedMediaTypes: java.lang.Iterable[MediaType]): UnacceptedResponseContentTypeRejection = { + val s1: Set[Alternative] = supportedContentTypes.asScala.map(_.asScala).map(ct ⇒ ContentNegotiator.Alternative(ct)).toSet + val s2: Set[Alternative] = supportedMediaTypes.asScala.map(_.asScala).map(mt ⇒ ContentNegotiator.Alternative(mt)).toSet + s.UnacceptedResponseContentTypeRejection(s1 ++ s2) + } + + def unacceptedResponseEncoding(supported: HttpEncoding) = + s.UnacceptedResponseEncodingRejection(supported.asScala) + def unacceptedResponseEncoding(supported: java.lang.Iterable[HttpEncoding]) = + s.UnacceptedResponseEncodingRejection(supported.asScala.map(_.asScala).toSet) + + def authenticationCredentialsMissing(challenge: HttpChallenge): AuthenticationFailedRejection = + s.AuthenticationFailedRejection(s.AuthenticationFailedRejection.CredentialsMissing, challenge.asScala) + def authenticationCredentialsRejected(challenge: HttpChallenge): AuthenticationFailedRejection = + s.AuthenticationFailedRejection(s.AuthenticationFailedRejection.CredentialsRejected, challenge.asScala) + + def authorizationFailed = + s.AuthorizationFailedRejection + + def missingCookie(cookieName: String) = + s.MissingCookieRejection(cookieName) + + def expectedWebSocketRequest = + s.ExpectedWebSocketRequestRejection + + def validationRejection(message: String) = + s.ValidationRejection(message) + def validationRejection(message: String, cause: Optional[Throwable]) = + s.ValidationRejection(message, cause.asScala) + + def transformationRejection(f: java.util.function.Function[java.util.List[Rejection], java.util.List[Rejection]]) = + s.TransformationRejection(rejections ⇒ f.apply(rejections.map(_.asJava).asJava).asScala.toVector.map(_.asScala)) // TODO this is maddness + + def rejectionError(rejection: Rejection) = + s.RejectionError(convertToScala(rejection)) +} diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/RequestContext.scala b/akka-http/src/main/scala/akka/http/javadsl/server/RequestContext.scala index 7c3b4b72c2..10186a087f 100644 --- a/akka-http/src/main/scala/akka/http/javadsl/server/RequestContext.scala +++ b/akka-http/src/main/scala/akka/http/javadsl/server/RequestContext.scala @@ -4,87 +4,70 @@ package akka.http.javadsl.server +import akka.http.javadsl.model.HttpRequest +import akka.http.scaladsl.util.FastFuture._ import scala.concurrent.ExecutionContextExecutor -import akka.http.javadsl.model._ -import akka.http.javadsl.settings.{ RoutingSettings, ParserSettings } import akka.stream.Materializer +import akka.event.LoggingAdapter +import akka.http.javadsl.settings.RoutingSettings +import akka.http.javadsl.settings.ParserSettings +import akka.http.javadsl.model.HttpResponse import java.util.concurrent.CompletionStage +import java.util.function.{ Function ⇒ JFunction } +import akka.http.scaladsl +import akka.http.impl.util.JavaMapping.Implicits._ +import akka.http.scaladsl.marshalling.ToResponseMarshallable +import scala.compat.java8.FutureConverters._ +import scala.annotation.varargs +import akka.http.scaladsl.model.Uri.Path -/** - * The RequestContext represents the state of the request while it is routed through - * the route structure. - */ -trait RequestContext { - /** - * The incoming request. - */ - def request: HttpRequest +class RequestContext private (val delegate: scaladsl.server.RequestContext) { + import RequestContext._ + import akka.http.javadsl.RoutingJavaMapping._ - /** - * The still unmatched path of the request. - */ - def unmatchedPath: String + def getRequest: HttpRequest = delegate.request + def getUnmatchedPath: String = delegate.unmatchedPath.toString() + def getExecutionContext: ExecutionContextExecutor = delegate.executionContext + def getMaterializer: Materializer = delegate.materializer + def getLog: LoggingAdapter = delegate.log + def getSettings: RoutingSettings = delegate.settings + def getParserSettings: ParserSettings = delegate.parserSettings - /** Returns the ExecutionContext of this RequestContext */ - def executionContext(): ExecutionContextExecutor + def reconfigure( + executionContext: ExecutionContextExecutor, + materializer: Materializer, + log: LoggingAdapter, + settings: RoutingSettings): RequestContext = wrap(delegate.reconfigure(executionContext, materializer, log, settings.asScala)) - /** Returns the Materializer of this RequestContext */ - def materializer(): Materializer + def complete[T](value: T, marshaller: Marshaller[T, HttpResponse]): CompletionStage[RouteResult] = { + delegate.complete(ToResponseMarshallable(value)(marshaller)) + .fast.map(r ⇒ r: RouteResult)(akka.dispatch.ExecutionContexts.sameThreadExecutionContext).toJava + } - /** - * The default RoutingSettings to be used for configuring directives. - */ - def settings: RoutingSettings + @varargs def reject(rejections: Rejection*): CompletionStage[RouteResult] = { + val scalaRejections = rejections.map(_.asScala) + delegate.reject(scalaRejections: _*) + .fast.map(r ⇒ r.asJava: RouteResult)(akka.dispatch.ExecutionContexts.sameThreadExecutionContext).toJava + } - /** - * The default ParserSettings to be used for configuring directives. - */ - def parserSettings: ParserSettings + def fail(error: Throwable): CompletionStage[RouteResult] = + delegate.fail(error) + .fast.map(r ⇒ r: RouteResult)(akka.dispatch.ExecutionContexts.sameThreadExecutionContext).toJava - /** - * Completes the request with a value of type T and marshals it using the given - * marshaller. - */ - def completeAs[T](marshaller: Marshaller[T], value: T): RouteResult + def withRequest(req: HttpRequest): RequestContext = wrap(delegate.withRequest(req.asScala)) + def withExecutionContext(ec: ExecutionContextExecutor): RequestContext = wrap(delegate.withExecutionContext(ec)) + def withMaterializer(materializer: Materializer): RequestContext = wrap(delegate.withMaterializer(materializer)) + def withLog(log: LoggingAdapter): RequestContext = wrap(delegate.withLog(log)) + def withRoutingSettings(settings: RoutingSettings): RequestContext = wrap(delegate.withRoutingSettings(settings.asScala)) + def withParserSettings(settings: ParserSettings): RequestContext = wrap(delegate.withParserSettings(settings.asScala)) - /** - * Completes the request with the given response. - */ - def complete(response: HttpResponse): RouteResult - - /** - * Completes the request with the given string as an entity of type `text/plain`. - */ - def complete(text: String): RouteResult - - /** - * Completes the request with the given string as an entity of the given type. - */ - def complete(contentType: ContentType.NonBinary, text: String): RouteResult - - /** - * Completes the request with the given status code and no entity. - */ - def completeWithStatus(statusCode: StatusCode): RouteResult - - /** - * Completes the request with the given status code and no entity. - */ - def completeWithStatus(statusCode: Int): RouteResult - - /** - * Defers completion of the request - */ - def completeWith(futureResult: CompletionStage[RouteResult]): RouteResult - - /** - * Explicitly rejects the request as not found. Other route alternatives - * may still be able provide a response. - */ - def notFound(): RouteResult - - /** - * Reject this request with an application-defined CustomRejection. - */ - def reject(customRejection: CustomRejection): RouteResult + def mapRequest(f: JFunction[HttpRequest, HttpRequest]): RequestContext = wrap(delegate.mapRequest(r ⇒ f.apply(r.asJava).asScala)) + def withUnmatchedPath(path: String): RequestContext = wrap(delegate.withUnmatchedPath(Path(path))) + def mapUnmatchedPath(f: JFunction[String, String]): RequestContext = wrap(delegate.mapUnmatchedPath(p ⇒ Path(f.apply(p.toString())))) + def withAcceptAll: RequestContext = wrap(delegate.withAcceptAll) +} + +object RequestContext { + /** INTERNAL API */ + private[http] def wrap(delegate: scaladsl.server.RequestContext) = new RequestContext(delegate) } diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/RequestVal.scala b/akka-http/src/main/scala/akka/http/javadsl/server/RequestVal.scala deleted file mode 100644 index 98f1efe0a0..0000000000 --- a/akka-http/src/main/scala/akka/http/javadsl/server/RequestVal.scala +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.javadsl.server - -/** - * Represents a value that can be extracted from a request. - */ -trait RequestVal[T] { outer ⇒ - /** - * An accessor for the value given the [[RequestContext]]. - * - * Note, that some RequestVals need to be actively specified in the route structure to - * be extracted at a particular point during routing. One example is a [[akka.http.javadsl.server.values.PathMatcher]] - * that needs to used with a [[directives.PathDirectives]] to specify which part of the - * path should actually be extracted. Another example is an [[akka.http.javadsl.server.values.HttpBasicAuthenticator]] - * that needs to be used in the route explicitly to be activated. - */ - def get(ctx: RequestContext): T - - /** - * The runtime type of the extracted value. - */ - def resultClass: Class[T] -} diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/RequestVals.scala b/akka-http/src/main/scala/akka/http/javadsl/server/RequestVals.scala deleted file mode 100644 index fdc46ac559..0000000000 --- a/akka-http/src/main/scala/akka/http/javadsl/server/RequestVals.scala +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.javadsl.server - -import java.util.regex.Pattern -import java.{ util ⇒ ju } -import scala.concurrent.Future -import scala.reflect.ClassTag -import akka.http.javadsl.model.{ RemoteAddress, HttpMethod } -import akka.http.scaladsl.server -import akka.http.scaladsl.server._ -import akka.http.scaladsl.server.directives._ -import akka.http.impl.server.{ UnmarshallerImpl, ExtractingStandaloneExtractionImpl, RequestContextImpl, StandaloneExtractionImpl } -import akka.http.scaladsl.util.FastFuture -import akka.http.impl.util.JavaMapping.Implicits._ - -/** - * A collection of predefined [[RequestVals]]. - */ -object RequestVals { - /** - * Creates an extraction that extracts the request body using the supplied Unmarshaller. - */ - def entityAs[T](unmarshaller: Unmarshaller[T]): RequestVal[T] = - new ExtractingStandaloneExtractionImpl[T]()(unmarshaller.classTag) { - def extract(ctx: server.RequestContext): Future[T] = { - val u = unmarshaller.asInstanceOf[UnmarshallerImpl[T]].scalaUnmarshaller - u(ctx.request)(ctx.executionContext, ctx.materializer) - } - } - - /** - * Extracts the request method. - */ - def requestMethod: RequestVal[HttpMethod] = - new ExtractingStandaloneExtractionImpl[HttpMethod] { - def extract(ctx: server.RequestContext): Future[HttpMethod] = FastFuture.successful(ctx.request.method.asJava) - } - - /** - * Extracts the scheme used for this request. - */ - def requestContext: RequestVal[RequestContext] = - new StandaloneExtractionImpl[RequestContext] { - def directive: Directive1[RequestContext] = BasicDirectives.extractRequestContext.map(RequestContextImpl(_): RequestContext) - } - - /** - * Extracts the unmatched path of the request context. - */ - def unmatchedPath: RequestVal[String] = - new ExtractingStandaloneExtractionImpl[String] { - def extract(ctx: server.RequestContext): Future[String] = FastFuture.successful(ctx.unmatchedPath.toString) - } - - /** - * Extracts the scheme used for this request. - */ - def scheme: RequestVal[String] = - new StandaloneExtractionImpl[String] { - def directive: Directive1[String] = SchemeDirectives.extractScheme - } - - /** - * Extracts the host name this request targeted. - */ - def host: RequestVal[String] = - new StandaloneExtractionImpl[String] { - def directive: Directive1[String] = HostDirectives.extractHost - } - - /** - * Extracts the host name this request targeted. - */ - def matchAndExtractHost(regex: Pattern): RequestVal[String] = - new StandaloneExtractionImpl[String] { - // important to use a val here so that invalid patterns are - // detected at construction and `IllegalArgumentException` is thrown - override val directive: Directive1[String] = HostDirectives.host(regex.pattern().r) - } - - /** - * Directive extracting the IP of the client from either the X-Forwarded-For, Remote-Address or X-Real-IP header - * (in that order of priority). - * - * TODO: add link to the configuration entry that would add a remote-address header - */ - def clientIP(): RequestVal[RemoteAddress] = - new StandaloneExtractionImpl[RemoteAddress] { - def directive: Directive1[RemoteAddress] = - MiscDirectives.extractClientIP.map(x ⇒ x: RemoteAddress) // missing covariance of Directive - } - - /** - * Creates a new [[RequestVal]] given a [[java.util.Map]] and a [[RequestVal]] that represents the key. - * The new RequestVal represents the existing value as looked up in the map. If the key doesn't - * exist the request is rejected. - */ - def lookupInMap[T, U](key: RequestVal[T], clazz: Class[U], map: ju.Map[T, U]): RequestVal[U] = - new StandaloneExtractionImpl[U]()(ClassTag(clazz)) { - import BasicDirectives._ - import RouteDirectives._ - - def directive: Directive1[U] = - extract(ctx ⇒ key.get(RequestContextImpl(ctx))).flatMap { - case key if map.containsKey(key) ⇒ provide(map.get(key)) - case _ ⇒ reject() - } - } -} diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/Route.scala b/akka-http/src/main/scala/akka/http/javadsl/server/Route.scala index 877217e315..009fc2926c 100644 --- a/akka-http/src/main/scala/akka/http/javadsl/server/Route.scala +++ b/akka-http/src/main/scala/akka/http/javadsl/server/Route.scala @@ -4,11 +4,55 @@ package akka.http.javadsl.server +import akka.stream.javadsl.Flow +import akka.http.javadsl.model.HttpRequest +import akka.http.javadsl.model.HttpResponse +import akka.http.scaladsl +import akka.actor.ActorSystem +import akka.stream.Materializer +import akka.NotUsed +import akka.http.javadsl.settings.{ ParserSettings, RoutingSettings } + /** - * A marker interface to denote an element that handles a request. + * In the Java DSL, a Route can only consist of combinations of the built-in directives. A Route can not be + * instantiated directly. * - * This is an opaque interface that cannot be implemented manually. - * Instead, see the predefined routes in [[Directives]] and use the [[Directives#handleWith]] - * method to create custom routes. + * However, the built-in directives may be combined methods like: + * + *
+ * Route myDirective(String test, Supplier inner) {
+ *   return
+ *     path("fixed", () ->
+ *       path(test),
+ *         inner
+ *       )
+ *     );
+ * }
+ * 
+ * + * The above example will invoke [inner] whenever the path "fixed/{test}" is matched, where "{test}" + * is the actual String that was given as method argument. */ -trait Route \ No newline at end of file +trait Route { + /** INTERNAL API */ + private[http] def delegate: scaladsl.server.Route + + def flow(system: ActorSystem, materializer: Materializer): Flow[HttpRequest, HttpResponse, NotUsed] + + /** + * "Seals" a route by wrapping it with default exception handling and rejection conversion. + */ + def seal(system: ActorSystem, materializer: Materializer): Route + + /** + * "Seals" a route by wrapping it with explicit exception handling and rejection conversion. + */ + def seal(routingSettings: RoutingSettings, + parserSettings: ParserSettings, + rejectionHandler: RejectionHandler, + exceptionHandler: ExceptionHandler, + system: ActorSystem, + materializer: Materializer): Route + + def orElse(alternative: Route): Route +} diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/RouteResult.scala b/akka-http/src/main/scala/akka/http/javadsl/server/RouteResult.scala index 221a073990..7a3a5642ec 100644 --- a/akka-http/src/main/scala/akka/http/javadsl/server/RouteResult.scala +++ b/akka-http/src/main/scala/akka/http/javadsl/server/RouteResult.scala @@ -1,11 +1,13 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - package akka.http.javadsl.server -/** - * A marker trait to denote the result of handling a request. Use the methods in [[RequestContext]] - * to create instances of results. - */ -trait RouteResult +import akka.http.javadsl.model.HttpResponse + +trait RouteResult {} + +trait Complete extends RouteResult { + def getResponse: HttpResponse +} + +trait Rejected extends RouteResult { + def getRejections: java.lang.Iterable[Rejection] +} diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/StringUnmarshaller.scala b/akka-http/src/main/scala/akka/http/javadsl/server/StringUnmarshaller.scala new file mode 100644 index 0000000000..c64fef0b6f --- /dev/null +++ b/akka-http/src/main/scala/akka/http/javadsl/server/StringUnmarshaller.scala @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2009-2016 Lightbend Inc. + */ + +package akka.http.javadsl.server + +import java.util.concurrent.CompletionStage + +object StringUnmarshaller { + /** + * Turns the given asynchronous function into an unmarshaller from String to B. + */ + def async[B](f: java.util.function.Function[String, CompletionStage[B]]): Unmarshaller[String, B] = Unmarshaller.async(f) + + /** + * Turns the given function into an unmarshaller from String to B. + */ + def sync[B](f: java.util.function.Function[String, B]): Unmarshaller[String, B] = Unmarshaller.sync(f) +} + +/** + * INTERNAL API + */ +private[server] object StringUnmarshallerPredef extends akka.http.scaladsl.unmarshalling.PredefinedFromStringUnmarshallers { + +} + diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/Unmarshaller.scala b/akka-http/src/main/scala/akka/http/javadsl/server/Unmarshaller.scala index 820d103746..b44d380cdb 100644 --- a/akka-http/src/main/scala/akka/http/javadsl/server/Unmarshaller.scala +++ b/akka-http/src/main/scala/akka/http/javadsl/server/Unmarshaller.scala @@ -4,11 +4,141 @@ package akka.http.javadsl.server -import scala.reflect.ClassTag +import akka.http.impl.util.JavaMapping +import akka.http.javadsl.RoutingJavaMapping +import akka.http.scaladsl.marshalling._ +import akka.http.scaladsl.unmarshalling.{ FromEntityUnmarshaller, FromRequestUnmarshaller } +import akka.http.scaladsl.unmarshalling.Unmarshaller.{ EnhancedFromEntityUnmarshaller, EnhancedUnmarshaller, UnsupportedContentTypeException } +import akka.http.scaladsl.{ marshalling, model, unmarshalling } +import akka.util.ByteString +import akka.http.scaladsl.util.FastFuture +import akka.http.scaladsl.util.FastFuture._ +import scala.concurrent.ExecutionContext +import scala.annotation.varargs +import akka.http.javadsl.model.HttpEntity +import akka.http.scaladsl.model.{ ContentTypeRange, ContentTypes, FormData } +import akka.http.scaladsl +import akka.http.javadsl.model.ContentType +import akka.http.javadsl.model.HttpRequest +import akka.http.javadsl.model.RequestEntity +import akka.http.javadsl.model.MediaType +import java.util.concurrent.CompletionStage + +import scala.compat.java8.FutureConverters._ +import scala.collection.JavaConverters._ +import akka.http.impl.util.JavaMapping.Implicits._ +import akka.http.javadsl.RoutingJavaMapping._ +import akka.http.scaladsl.util.FastFuture +import akka.stream.Materializer + +import scala.language.implicitConversions + +object Unmarshaller { + implicit def fromScala[A, B](scalaUnmarshaller: unmarshalling.Unmarshaller[A, B]): Unmarshaller[A, B] = + scalaUnmarshaller + + /** + * Creates an unmarshaller from an asynchronous Java function. + */ + def async[A, B](f: java.util.function.Function[A, CompletionStage[B]]): Unmarshaller[A, B] = + unmarshalling.Unmarshaller[A, B] { + ctx ⇒ a ⇒ f(a).toScala + } + + /** + * Creates an unmarshaller from a Java function. + */ + def sync[A, B](f: java.util.function.Function[A, B]): Unmarshaller[A, B] = + unmarshalling.Unmarshaller[A, B] { + ctx ⇒ a ⇒ scala.concurrent.Future.successful(f.apply(a)) + } + + // format: OFF + def entityToByteString: Unmarshaller[HttpEntity, ByteString] = unmarshalling.Unmarshaller.byteStringUnmarshaller + def entityToByteArray: Unmarshaller[HttpEntity, Array[Byte]] = unmarshalling.Unmarshaller.byteArrayUnmarshaller + def entityToCharArray: Unmarshaller[HttpEntity, Array[Char]] = unmarshalling.Unmarshaller.charArrayUnmarshaller + def entityToString: Unmarshaller[HttpEntity, String] = unmarshalling.Unmarshaller.stringUnmarshaller + def entityToUrlEncodedFormData: Unmarshaller[HttpEntity, FormData] = unmarshalling.Unmarshaller.defaultUrlEncodedFormDataUnmarshaller + // format: ON + + val requestToEntity: Unmarshaller[HttpRequest, RequestEntity] = + unmarshalling.Unmarshaller.strict[HttpRequest, RequestEntity](_.entity) + + def forMediaType[B](t: MediaType, um: Unmarshaller[HttpEntity, B]): Unmarshaller[HttpEntity, B] = { + unmarshalling.Unmarshaller.withMaterializer[HttpEntity, B] { implicit ex ⇒ + implicit mat ⇒ jEntity ⇒ { + val entity = jEntity.asScala + val mediaType = t.asScala + if (entity.contentType == ContentTypes.NoContentType || mediaType.matches(entity.contentType.mediaType)) { + um.asScala(entity) + } else FastFuture.failed(UnsupportedContentTypeException(ContentTypeRange(t.toRange.asScala))) + } + } + } + + def forMediaTypes[B](types: java.lang.Iterable[MediaType], um: Unmarshaller[HttpEntity, B]): Unmarshaller[HttpEntity, B] = { + val u: FromEntityUnmarshaller[B] = um.asScala + val theTypes: Seq[akka.http.scaladsl.model.ContentTypeRange] = types.asScala.toSeq.map { media ⇒ + akka.http.scaladsl.model.ContentTypeRange(media.asScala) + } + u.forContentTypes(theTypes: _*) + } + + def firstOf[A, B](u1: Unmarshaller[A, B], u2: Unmarshaller[A, B]): Unmarshaller[A, B] = { + unmarshalling.Unmarshaller.firstOf(u1.asScala, u2.asScala) + } + + def firstOf[A, B](u1: Unmarshaller[A, B], u2: Unmarshaller[A, B], u3: Unmarshaller[A, B]): Unmarshaller[A, B] = { + unmarshalling.Unmarshaller.firstOf(u1.asScala, u2.asScala, u3.asScala) + } + + def firstOf[A, B](u1: Unmarshaller[A, B], u2: Unmarshaller[A, B], u3: Unmarshaller[A, B], u4: Unmarshaller[A, B]): Unmarshaller[A, B] = { + unmarshalling.Unmarshaller.firstOf(u1.asScala, u2.asScala, u3.asScala, u4.asScala) + } + + def firstOf[A, B](u1: Unmarshaller[A, B], u2: Unmarshaller[A, B], u3: Unmarshaller[A, B], u4: Unmarshaller[A, B], u5: Unmarshaller[A, B]): Unmarshaller[A, B] = { + unmarshalling.Unmarshaller.firstOf(u1.asScala, u2.asScala, u3.asScala, u4.asScala, u5.asScala) + } + + // implicit def asScalaToResponseMarshaller[T](um: Unmarshaller[akka.http.javadsl.model.HttpRequest, T]): FromRequestUnmarshaller[T] = + // um.asScala.contramap[akka.http.scaladsl.model.HttpRequest](_.asJava) + // + // implicit def asScalaEntityMarshaller[T](um: Unmarshaller[akka.http.javadsl.model.RequestEntity, T]): akka.http.scaladsl.marshalling.Marshaller[T, akka.http.scaladsl.model.RequestEntity] = + // um.asScala.map(_.asJava) + + private implicit def adaptInputToJava[JI, SI, O](um: unmarshalling.Unmarshaller[SI, O])(implicit mi: JavaMapping[JI, SI]): unmarshalling.Unmarshaller[JI, O] = + um.asInstanceOf[unmarshalling.Unmarshaller[JI, O]] // since guarantee provided by existence of `mi` + +} + +trait UnmarshallerBase[-A, B] /** - * A marker trait for an unmarshaller that converts an HttpRequest to a value of type T. + * An unmarshaller transforms values of type A into type B. */ -trait Unmarshaller[T] { - def classTag: ClassTag[T] +abstract class Unmarshaller[-A, B] extends UnmarshallerBase[A, B] { + import unmarshalling.Unmarshaller._ + + implicit def asScala: akka.http.scaladsl.unmarshalling.Unmarshaller[A, B] + + def unmarshall(a: A, ec: ExecutionContext, mat: Materializer): CompletionStage[B] = asScala.apply(a)(ec, mat).toJava + + /** + * Transform the result `B` of this unmarshaller to a `C` producing a marshaller that turns `A`s into `C`s + * @return A new marshaller that can unmarshall instances of `A` into instances of `C` + */ + def thenApply[C](f: java.util.function.Function[B, C]): Unmarshaller[A, C] = asScala.map(f.apply) + + def flatMap[C](f: java.util.function.Function[B, CompletionStage[C]]): Unmarshaller[A, C] = + asScala.flatMap { ctx ⇒ mat ⇒ b ⇒ f.apply(b).toScala } + + def flatMap[C](u: Unmarshaller[_ >: B, C]): Unmarshaller[A, C] = + asScala.flatMap { ctx ⇒ mat ⇒ b ⇒ u.asScala.apply(b)(ctx, mat) } + + // TODO not exposed for Java yet + // def mapWithInput[C](f: java.util.function.BiFunction[A, B, C]): Unmarshaller[A, C] = + // asScala.mapWithInput { case (a, b) ⇒ f.apply(a, b) } + // + // def flatMapWithInput[C](f: java.util.function.BiFunction[A, B, CompletionStage[C]]): Unmarshaller[A, C] = + // asScala.flatMapWithInput { case (a, b) ⇒ f.apply(a, b).toScala } } diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/Unmarshallers.scala b/akka-http/src/main/scala/akka/http/javadsl/server/Unmarshallers.scala deleted file mode 100644 index 2b185cf5ec..0000000000 --- a/akka-http/src/main/scala/akka/http/javadsl/server/Unmarshallers.scala +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.javadsl.server - -import akka.http.impl.server.{ Util, UnmarshallerImpl } -import akka.http.javadsl.model.{ HttpEntity, HttpMessage } -import akka.http.scaladsl.unmarshalling.{ Unmarshaller ⇒ ScalaUnmarshaller, FromMessageUnmarshaller } -import akka.japi.function.Function -import akka.util.ByteString - -import scala.reflect.ClassTag - -object Unmarshallers { - def String: Unmarshaller[String] = implicitInstance - def ByteString: Unmarshaller[ByteString] = implicitInstance - def ByteArray: Unmarshaller[Array[Byte]] = implicitInstance - def CharArray: Unmarshaller[Array[Char]] = implicitInstance - - def fromMessage[T](convert: Function[HttpMessage, T], clazz: Class[T]): Unmarshaller[T] = - new UnmarshallerImpl[T](Util.scalaUnmarshallerFromFunction(convert))(ClassTag(clazz)) - - def fromEntity[T](convert: Function[HttpEntity, T], clazz: Class[T]): Unmarshaller[T] = - new UnmarshallerImpl[T]( - ScalaUnmarshaller.messageUnmarshallerFromEntityUnmarshaller(Util.scalaUnmarshallerFromFunction[HttpEntity, T](convert)))(ClassTag(clazz)) - - private def implicitInstance[T: ClassTag](implicit um: FromMessageUnmarshaller[T]): Unmarshaller[T] = - new UnmarshallerImpl[T](um) -} diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/directives/BasicDirectives.scala b/akka-http/src/main/scala/akka/http/javadsl/server/directives/BasicDirectives.scala index 3f2316deda..c1d17e98dc 100644 --- a/akka-http/src/main/scala/akka/http/javadsl/server/directives/BasicDirectives.scala +++ b/akka-http/src/main/scala/akka/http/javadsl/server/directives/BasicDirectives.scala @@ -4,200 +4,253 @@ package akka.http.javadsl.server.directives -import java.lang.reflect.{ Method, ParameterizedType } +import java.util.function.{ Function ⇒ JFunction } -import akka.http.impl.server.RouteStructure._ -import akka.http.impl.server._ -import akka.http.javadsl.model.{ ContentType, HttpResponse, StatusCode, Uri } +import akka.http.impl.util.JavaMapping +import akka.http.javadsl.settings.ParserSettings +import akka.http.scaladsl.settings.RoutingSettings +import akka.japi.Util + +import scala.concurrent.ExecutionContextExecutor +import akka.http.impl.model.JavaUri +import akka.http.javadsl.model.HttpRequest +import akka.http.javadsl.model.RequestEntity +import akka.http.javadsl.model.Uri import akka.http.javadsl.server._ +import akka.http.scaladsl.server.{ Directives ⇒ D } +import akka.http.scaladsl +import akka.stream.Materializer +import java.util.function.Supplier +import java.util.{ List ⇒ JList } -import scala.annotation.varargs -import scala.concurrent.Future +import akka.http.javadsl.model.HttpResponse +import akka.http.javadsl.model.ResponseEntity +import akka.http.javadsl.model.HttpHeader +import akka.http.scaladsl.util.FastFuture._ +import java.lang.{ Iterable ⇒ JIterable } import java.util.concurrent.CompletionStage +import java.util.function.Predicate + +import akka.dispatch.ExecutionContexts +import akka.event.LoggingAdapter + import scala.compat.java8.FutureConverters._ -abstract class BasicDirectives extends BasicDirectivesBase { - /** - * Tries the given route alternatives in sequence until the first one matches. - */ - @varargs - def route(innerRoute: Route, moreInnerRoutes: Route*): Route = - RouteAlternatives()(innerRoute, moreInnerRoutes.toList) +abstract class BasicDirectives { + import akka.http.impl.util.JavaMapping.Implicits._ + import akka.http.javadsl.RoutingJavaMapping._ - /** - * A route that completes the request with a static text - */ - def complete(text: String): Route = - new OpaqueRoute() { - def handle(ctx: RequestContext): RouteResult = ctx.complete(text) + def mapRequest(f: JFunction[HttpRequest, HttpRequest], inner: Supplier[Route]): Route = RouteAdapter { + D.mapRequest(rq ⇒ f.apply(rq.asJava).asScala) { inner.get.delegate } + } + + def mapRequestContext(f: JFunction[RequestContext, RequestContext], inner: Supplier[Route]): Route = RouteAdapter { + D.mapRequestContext(rq ⇒ f.apply(RequestContext.toJava(rq)).asScala) { inner.get.delegate } + } + + def mapRejections(f: JFunction[JList[Rejection], JList[Rejection]], inner: Supplier[Route]): Route = RouteAdapter { + D.mapRejections(rejections ⇒ Util.immutableSeq(f.apply(Util.javaArrayList(rejections.map(_.asJava)))).map(_.asScala)) { inner.get.delegate } + } + + def mapResponse(f: JFunction[HttpResponse, HttpResponse], inner: Supplier[Route]): Route = RouteAdapter { + D.mapResponse(resp ⇒ f.apply(resp.asJava).asScala) { inner.get.delegate } + } + + def mapResponseEntity(f: JFunction[ResponseEntity, ResponseEntity], inner: Supplier[Route]): Route = RouteAdapter { + D.mapResponseEntity(e ⇒ f.apply(e.asJava).asScala) { inner.get.delegate } + } + + def mapResponseHeaders(f: JFunction[JList[HttpHeader], JList[HttpHeader]], inner: Supplier[Route]): Route = RouteAdapter { + D.mapResponseHeaders(l ⇒ Util.immutableSeq(f.apply(Util.javaArrayList(l))).map(_.asScala)) { inner.get.delegate } // TODO try to remove map() + } + + def mapInnerRoute(f: JFunction[Route, Route], inner: Supplier[Route]): Route = RouteAdapter { + D.mapInnerRoute(route ⇒ f(RouteAdapter(route)).delegate) { inner.get.delegate } + } + + def mapRouteResult(f: JFunction[RouteResult, RouteResult], inner: Supplier[Route]): Route = RouteAdapter { + D.mapRouteResult(route ⇒ f(route.asJava).asScala) { inner.get.delegate } + } + + def mapRouteResultFuture(f: JFunction[CompletionStage[RouteResult], CompletionStage[RouteResult]], inner: Supplier[Route]): Route = RouteAdapter { + D.mapRouteResultFuture(stage ⇒ + f(toJava(stage.fast.map(_.asJava)(ExecutionContexts.sameThreadExecutionContext))).toScala.fast.map(_.asScala)(ExecutionContexts.sameThreadExecutionContext)) { + inner.get.delegate } + } - /** - * A route that completes the request with a static text - */ - def complete(contentType: ContentType.NonBinary, text: String): Route = - new OpaqueRoute() { - def handle(ctx: RequestContext): RouteResult = - ctx.complete(contentType, text) - } - - /** - * A route that completes the request with a static text - */ - def complete(response: HttpResponse): Route = - new OpaqueRoute() { - def handle(ctx: RequestContext): RouteResult = ctx.complete(response) - } - - /** - * A route that completes the request with a status code. - */ - def completeWithStatus(code: StatusCode): Route = - new OpaqueRoute() { - def handle(ctx: RequestContext): RouteResult = ctx.completeWithStatus(code) - } - - /** - * A route that completes the request using the given marshaller and value. - */ - def completeAs[T](marshaller: Marshaller[T], value: T): Route = - new OpaqueRoute() { - def handle(ctx: RequestContext): RouteResult = ctx.completeAs(marshaller, value) - } - - /** - * Completes the request with redirection response of the given type to the given URI. - * - * The `redirectionType` must be a StatusCode for which `isRedirection` returns true. - */ - def redirect(uri: Uri, redirectionType: StatusCode): Route = Redirect(uri, redirectionType) - - /** - * A route that extracts a value and completes the request with it. - */ - def extractAndComplete[T](marshaller: Marshaller[T], extraction: RequestVal[T]): Route = - handle(extraction)(ctx ⇒ ctx.completeAs(marshaller, extraction.get(ctx))) - - /** - * A directive that makes sure that all the standalone extractions have been - * executed and validated. - */ - @varargs - def extractHere(extractions: RequestVal[_]*): Directive = - Directives.custom(Extract(extractions.map(_.asInstanceOf[StandaloneExtractionImpl[_ <: AnyRef]]))) - - private[http] def handle(extractions: RequestVal[_]*)(f: RequestContext ⇒ RouteResult): Route = { - val route = - new OpaqueRoute() { - def handle(ctx: RequestContext): RouteResult = f(ctx) - } - val saExtractions = extractions.collect { case sa: StandaloneExtractionImpl[_] ⇒ sa } - if (saExtractions.isEmpty) route - else extractHere(saExtractions: _*).route(route) + def mapRouteResultWith(f: JFunction[RouteResult, CompletionStage[RouteResult]], inner: Supplier[Route]): Route = RouteAdapter { + D.mapRouteResultWith(r ⇒ f(r.asJava).toScala.fast.map(_.asScala)(ExecutionContexts.sameThreadExecutionContext)) { inner.get.delegate } } /** - * Handles the route by reflectively calling the instance method specified by `instance`, and `methodName`. - * Additionally, the value of all extractions will be passed to the function. - * - * For extraction types `Extraction[T1]`, `Extraction[T2]`, ... the shape of the method must match this pattern: - * - * public static RouteResult methodName(RequestContext ctx, T1 t1, T2 t2, ...) + * Runs the inner route with settings mapped by the given function. */ - @varargs - def handleReflectively(instance: AnyRef, methodName: String, extractions: RequestVal[_]*): Route = - handleReflectively(instance.getClass, instance, methodName, extractions: _*) - - /** - * Handles the route by reflectively calling the static method specified by `clazz`, and `methodName`. - * Additionally, the value of all extractions will be passed to the function. - * - * For extraction types `Extraction[T1]`, `Extraction[T2]`, ... the shape of the method must match this pattern: - * - * public static RouteResult methodName(RequestContext ctx, T1 t1, T2 t2, ...) - */ - @varargs - def handleReflectively(clazz: Class[_], methodName: String, extractions: RequestVal[_]*): Route = - handleReflectively(clazz, null, methodName, extractions: _*) - - /** - * Handles the route by calling the method specified by `clazz`, `instance`, and `methodName`. Additionally, the value - * of all extractions will be passed to the function. - * - * For extraction types `Extraction[T1]`, `Extraction[T2]`, ... the shape of the method must match this pattern: - * - * public static RouteResult methodName(RequestContext ctx, T1 t1, T2 t2, ...) - */ - @varargs - def handleReflectively(clazz: Class[_], instance: AnyRef, methodName: String, extractions: RequestVal[_]*): Route = { - def chooseOverload(methods: Seq[Method]): (RequestContext, Seq[Any]) ⇒ RouteResult = { - val extractionTypes = extractions.map(_.resultClass).toList - val RequestContextClass = classOf[RequestContext] - - import java.{ lang ⇒ jl } - def paramMatches(expected: Class[_], actual: Class[_]): Boolean = expected match { - case e if e isAssignableFrom actual ⇒ true - case jl.Long.TYPE if actual == classOf[jl.Long] ⇒ true - case jl.Integer.TYPE if actual == classOf[jl.Integer] ⇒ true - case jl.Short.TYPE if actual == classOf[jl.Short] ⇒ true - case jl.Character.TYPE if actual == classOf[jl.Character] ⇒ true - case jl.Byte.TYPE if actual == classOf[jl.Byte] ⇒ true - case jl.Double.TYPE if actual == classOf[jl.Double] ⇒ true - case jl.Float.TYPE if actual == classOf[jl.Float] ⇒ true - case _ ⇒ false - } - def paramsMatch(params: Seq[Class[_]]): Boolean = { - val res = - params.size == extractionTypes.size && - (params, extractionTypes).zipped.forall(paramMatches) - - res - } - def returnTypeMatches(method: Method): Boolean = - method.getReturnType == classOf[RouteResult] || returnsFuture(method) || returnsCompletionStage(method) - - def returnsFuture(method: Method): Boolean = - method.getReturnType == classOf[Future[_]] && - method.getGenericReturnType.isInstanceOf[ParameterizedType] && - method.getGenericReturnType.asInstanceOf[ParameterizedType].getActualTypeArguments()(0) == classOf[RouteResult] - - def returnsCompletionStage(method: Method): Boolean = - method.getReturnType == classOf[CompletionStage[_]] && - method.getGenericReturnType.isInstanceOf[ParameterizedType] && - method.getGenericReturnType.asInstanceOf[ParameterizedType].getActualTypeArguments()(0) == classOf[RouteResult] - - /** Makes sure both RouteResult and Future[RouteResult] are acceptable result types. */ - def adaptResult(method: Method): (RequestContext, AnyRef) ⇒ RouteResult = - if (returnsFuture(method)) (ctx, v) ⇒ ctx.completeWith(v.asInstanceOf[Future[RouteResult]].toJava) - else if (returnsCompletionStage(method)) (ctx, v) => ctx.completeWith(v.asInstanceOf[CompletionStage[RouteResult]]) - else (_, v) ⇒ v.asInstanceOf[RouteResult] - - val IdentityAdaptor: (RequestContext, Seq[Any]) ⇒ Seq[Any] = (_, ps) ⇒ ps - def methodInvocator(method: Method, adaptParams: (RequestContext, Seq[Any]) ⇒ Seq[Any]): (RequestContext, Seq[Any]) ⇒ RouteResult = { - val resultAdaptor = adaptResult(method) - if (!method.isAccessible) method.setAccessible(true) - if (adaptParams == IdentityAdaptor) - (ctx, params) ⇒ resultAdaptor(ctx, method.invoke(instance, params.toArray.asInstanceOf[Array[AnyRef]]: _*)) - else - (ctx, params) ⇒ resultAdaptor(ctx, method.invoke(instance, adaptParams(ctx, params).toArray.asInstanceOf[Array[AnyRef]]: _*)) - } - - object ParameterTypes { - def unapply(method: Method): Option[List[Class[_]]] = Some(method.getParameterTypes.toList) - } - - methods.filter(returnTypeMatches).collectFirst { - case method @ ParameterTypes(RequestContextClass :: rest) if paramsMatch(rest) ⇒ methodInvocator(method, _ +: _) - case method @ ParameterTypes(rest) if paramsMatch(rest) ⇒ methodInvocator(method, IdentityAdaptor) - }.getOrElse(throw new RuntimeException("No suitable method found")) - } - def lookupMethod() = { - val candidateMethods = clazz.getMethods.filter(_.getName == methodName) - chooseOverload(candidateMethods) - } - - val method = lookupMethod() - - handle(extractions: _*)(ctx ⇒ method(ctx, extractions.map(_.get(ctx)))) + def mapSettings(f: JFunction[RoutingSettings, RoutingSettings], inner: Supplier[Route]): Route = RouteAdapter { + D.mapSettings(rs ⇒ f(rs)) { inner.get.delegate } } + + /** + * Always passes the request on to its inner route + * (i.e. does nothing with the request or the response). + */ + def pass(inner: Supplier[Route]): Route = RouteAdapter { + D.pass { inner.get.delegate } + } + + /** + * Injects the given value into a directive. + */ + def provide[T](t: T, inner: JFunction[T, Route]): Route = RouteAdapter { + D.provide(t) { t ⇒ inner.apply(t).delegate } + } + + /** + * Adds a TransformationRejection cancelling all rejections equal to the given one + * to the list of rejections potentially coming back from the inner route. + */ + def cancelRejection(rejection: Rejection, inner: Supplier[Route]): Route = RouteAdapter { + D.cancelRejection(rejection.asScala) { inner.get.delegate } + } + + /** + * Adds a TransformationRejection cancelling all rejections of one of the given classes + * to the list of rejections potentially coming back from the inner route. + */ + def cancelRejections(classes: JIterable[Class[_]], inner: Supplier[Route]): Route = RouteAdapter { + D.cancelRejections(Util.immutableSeq(classes): _*) { inner.get.delegate } + } + + /** + * Adds a TransformationRejection cancelling all rejections for which the given filter function returns true + * to the list of rejections potentially coming back from the inner route. + */ + def cancelRejections(filter: Predicate[Rejection], inner: Supplier[Route]): Route = RouteAdapter { + D.cancelRejections(r ⇒ filter.test(r)) { inner.get.delegate } + } + + def recoverRejections(f: JFunction[JIterable[Rejection], RouteResult], inner: Supplier[Route]): Route = RouteAdapter { + D.recoverRejections(rs ⇒ f.apply(Util.javaArrayList(rs.map(_.asJava))).asScala) { inner.get.delegate } + } + + def recoverRejectionsWith(f: JFunction[JIterable[Rejection], CompletionStage[RouteResult]], inner: Supplier[Route]): Route = RouteAdapter { + D.recoverRejectionsWith(rs ⇒ f.apply(Util.javaArrayList(rs.map(_.asJava))).toScala.fast.map(_.asScala)(ExecutionContexts.sameThreadExecutionContext)) { inner.get.delegate } + } + + /** + * Transforms the unmatchedPath of the RequestContext using the given function. + */ + def mapUnmatchedPath(f: JFunction[String, String], inner: Supplier[Route]): Route = RouteAdapter { + D.mapUnmatchedPath(path ⇒ scaladsl.model.Uri.Path(f.apply(path.toString))) { inner.get.delegate } + } + + /** + * Extracts the yet unmatched path from the RequestContext. + */ + def extractUnmatchedPath(inner: JFunction[String, Route]) = RouteAdapter { + D.extractUnmatchedPath { path ⇒ + inner.apply(path.toString).delegate + } + } + + /** + * Extracts the current [[HttpRequest]] instance. + */ + def extractRequest(inner: JFunction[HttpRequest, Route]) = RouteAdapter { + D.extractRequest { rq ⇒ + inner.apply(rq).delegate + } + } + + /** + * Extracts the complete request URI. + */ + def extractUri(inner: JFunction[Uri, Route]) = RouteAdapter { + D.extractUri { uri ⇒ + inner.apply(JavaUri(uri)).delegate + } + } + + /** + * Extracts the current http request entity. + */ + @CorrespondsTo("extract") + def extractEntity(inner: java.util.function.Function[RequestEntity, Route]): Route = RouteAdapter { + D.extractRequest { rq ⇒ + inner.apply(rq.entity).delegate + } + } + + /** + * Extracts the [[Materializer]] from the [[RequestContext]]. + */ + def extractMaterializer(inner: JFunction[Materializer, Route]): Route = RouteAdapter( + D.extractMaterializer { m ⇒ inner.apply(m).delegate }) + + /** + * Extracts the [[ExecutionContextExecutor]] from the [[RequestContext]]. + */ + def extractExecutionContext(inner: JFunction[ExecutionContextExecutor, Route]): Route = RouteAdapter( + D.extractExecutionContext { c ⇒ inner.apply(c).delegate }) + + /** + * Extracts a single value using the given function. + */ + def extract[T](extract: JFunction[RequestContext, T], inner: JFunction[T, Route]): Route = RouteAdapter { + D.extract(sc ⇒ extract.apply(JavaMapping.toJava(sc)(akka.http.javadsl.RoutingJavaMapping.RequestContext))) { c ⇒ inner.apply(c).delegate } + } + + /** + * Runs its inner route with the given alternative [[LoggingAdapter]]. + */ + def withLog(log: LoggingAdapter, inner: Supplier[Route]): Route = RouteAdapter { + D.withLog(log) { inner.get.delegate } + } + + /** + * Runs its inner route with the given alternative [[scala.concurrent.ExecutionContextExecutor]]. + */ + def withExecutionContext(ec: ExecutionContextExecutor, inner: Supplier[Route]): Route = RouteAdapter { + D.withExecutionContext(ec) { inner.get.delegate } + } + + /** + * Runs its inner route with the given alternative [[RoutingSettings]]. + */ + def withSettings(s: RoutingSettings, inner: Supplier[Route]): Route = RouteAdapter { + D.withSettings(s) { inner.get.delegate } + } + + /** + * Extracts the [[LoggingAdapter]] + */ + def extractLog(inner: JFunction[LoggingAdapter, Route]): Route = RouteAdapter { + D.extractLog { log ⇒ inner.apply(log).delegate } + } + + /** + * Extracts the [[akka.http.javadsl.settings.ParserSettings]] from the [[akka.http.javadsl.server.RequestContext]]. + */ + def extractParserSettings(inner: JFunction[ParserSettings, Route]) = RouteAdapter { + D.extractParserSettings { settings ⇒ + inner.apply(settings).delegate + } + } + + /** + * Extracts the [[RoutingSettings]] from the [[akka.http.javadsl.server.RequestContext]]. + */ + def extractSettings(inner: JFunction[RoutingSettings, Route]) = RouteAdapter { + D.extractSettings { settings ⇒ + inner.apply(settings).delegate + } + } + + /** + * Extracts the [[akka.http.javadsl.server.RequestContext]] itself. + */ + def extractRequestContext(inner: JFunction[RequestContext, Route]) = RouteAdapter { + D.extractRequestContext { ctx ⇒ inner.apply(JavaMapping.toJava(ctx)(akka.http.javadsl.RoutingJavaMapping.RequestContext)).delegate } + } + } diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/directives/CacheConditionDirectives.scala b/akka-http/src/main/scala/akka/http/javadsl/server/directives/CacheConditionDirectives.scala index 3075975dfc..ca92b4013e 100644 --- a/akka-http/src/main/scala/akka/http/javadsl/server/directives/CacheConditionDirectives.scala +++ b/akka-http/src/main/scala/akka/http/javadsl/server/directives/CacheConditionDirectives.scala @@ -5,27 +5,17 @@ package akka.http.javadsl.server package directives +import java.util.Optional +import java.util.function.Supplier + +import scala.compat.java8.OptionConverters._ + import akka.http.javadsl.model.DateTime import akka.http.javadsl.model.headers.EntityTag -import akka.http.impl.server.RouteStructure - -import scala.annotation.varargs +import akka.http.scaladsl.server.{ Directives ⇒ D } abstract class CacheConditionDirectives extends BasicDirectives { - /** - * Wraps its inner route with support for Conditional Requests as defined - * by http://tools.ietf.org/html/rfc7232 - * - * In particular the algorithm defined by http://tools.ietf.org/html/rfc7232#section-6 - * is implemented by this directive. - * - * Note: if you want to combine this directive with `withRangeSupport(...)` you need to put - * it on the *outside* of the `withRangeSupport(...)` directive, i.e. `withRangeSupport(...)` - * must be on a deeper level in your route structure in order to function correctly. - */ - @varargs - def conditional(entityTag: EntityTag, innerRoute: Route, moreInnerRoutes: Route*): Route = - RouteStructure.Conditional(entityTag = Some(entityTag))(innerRoute, moreInnerRoutes.toList) + import akka.http.impl.util.JavaMapping.Implicits._ /** * Wraps its inner route with support for Conditional Requests as defined @@ -38,9 +28,9 @@ abstract class CacheConditionDirectives extends BasicDirectives { * it on the *outside* of the `withRangeSupport(...)` directive, i.e. `withRangeSupport(...)` * must be on a deeper level in your route structure in order to function correctly. */ - @varargs - def conditional(lastModified: DateTime, innerRoute: Route, moreInnerRoutes: Route*): Route = - RouteStructure.Conditional(lastModified = Some(lastModified))(innerRoute, moreInnerRoutes.toList) + def conditional(eTag: EntityTag, inner: Supplier[Route]): Route = RouteAdapter { + D.conditional(eTag.asScala) { inner.get.delegate } + } /** * Wraps its inner route with support for Conditional Requests as defined @@ -53,7 +43,38 @@ abstract class CacheConditionDirectives extends BasicDirectives { * it on the *outside* of the `withRangeSupport(...)` directive, i.e. `withRangeSupport(...)` * must be on a deeper level in your route structure in order to function correctly. */ - @varargs - def conditional(entityTag: EntityTag, lastModified: DateTime, innerRoute: Route, moreInnerRoutes: Route*): Route = - RouteStructure.Conditional(Some(entityTag), Some(lastModified))(innerRoute, moreInnerRoutes.toList) + def conditional(lastModified: DateTime, inner: Supplier[Route]): Route = RouteAdapter { + D.conditional(lastModified.asScala) { inner.get.delegate } + } + + /** + * Wraps its inner route with support for Conditional Requests as defined + * by http://tools.ietf.org/html/rfc7232 + * + * In particular the algorithm defined by http://tools.ietf.org/html/rfc7232#section-6 + * is implemented by this directive. + * + * Note: if you want to combine this directive with `withRangeSupport(...)` you need to put + * it on the *outside* of the `withRangeSupport(...)` directive, i.e. `withRangeSupport(...)` + * must be on a deeper level in your route structure in order to function correctly. + */ + def conditional(eTag: EntityTag, lastModified: DateTime, inner: Supplier[Route]): Route = RouteAdapter { + D.conditional(eTag.asScala, lastModified.asScala) { inner.get.delegate } + } + + /** + * Wraps its inner route with support for Conditional Requests as defined + * by http://tools.ietf.org/html/rfc7232 + * + * In particular the algorithm defined by http://tools.ietf.org/html/rfc7232#section-6 + * is implemented by this directive. + * + * Note: if you want to combine this directive with `withRangeSupport(...)` you need to put + * it on the *outside* of the `withRangeSupport(...)` directive, i.e. `withRangeSupport(...)` + * must be on a deeper level in your route structure in order to function correctly. + */ + def conditional(eTag: Optional[EntityTag], lastModified: Optional[DateTime], inner: Supplier[Route]): Route = RouteAdapter { + D.conditional(eTag.asScala.map(_.asScala), lastModified.asScala.map(_.asScala)) { inner.get.delegate } + } + } diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/directives/CodingDirectives.scala b/akka-http/src/main/scala/akka/http/javadsl/server/directives/CodingDirectives.scala index 2799b948bd..339b956154 100644 --- a/akka-http/src/main/scala/akka/http/javadsl/server/directives/CodingDirectives.scala +++ b/akka-http/src/main/scala/akka/http/javadsl/server/directives/CodingDirectives.scala @@ -5,41 +5,82 @@ package akka.http.javadsl.server package directives -import akka.http.javadsl.server.Directive -import akka.http.javadsl.server.Directives -import akka.http.javadsl.server.Route -import akka.http.scaladsl.server._ +import java.util.function.Supplier -import scala.annotation.varargs -import akka.http.scaladsl -import akka.http.impl.server.RouteStructure +import scala.collection.JavaConverters._ +import akka.http.impl.util.JavaMapping.Implicits._ +import akka.http.javadsl.RoutingJavaMapping._ +import akka.http.javadsl.model.headers.HttpEncoding +import akka.http.javadsl.server.Route +import akka.http.scaladsl.server.{ Directives ⇒ D } abstract class CodingDirectives extends CacheConditionDirectives { /** - * Wraps the inner routes with encoding support. The response will be encoded - * using one of the predefined coders, `Gzip`, `Deflate`, or `NoCoding` depending on - * a potential [[akka.http.javadsl.model.headers.AcceptEncoding]] header from the client. + * Rejects the request with an UnacceptedResponseEncodingRejection + * if the given response encoding is not accepted by the client. */ - @varargs def encodeResponse(innerRoute: Route, moreInnerRoutes: Route*): Route = - RouteStructure.EncodeResponse(CodingDirectives._DefaultCodersToEncodeResponse)(innerRoute, moreInnerRoutes.toList) + def responseEncodingAccepted(encoding: HttpEncoding, inner: Supplier[Route]): Route = RouteAdapter { + D.responseEncodingAccepted(encoding.asScala) { + inner.get.delegate + } + } /** - * A directive that Wraps its inner routes with encoding support. - * The response will be encoded using one of the given coders with the precedence given - * by the order of the coders in this call. + * Encodes the response with the encoding that is requested by the client via the `Accept- + * Encoding` header. The response encoding is determined by the rules specified in + * http://tools.ietf.org/html/rfc7231#section-5.3.4. * - * In any case, a potential [[akka.http.javadsl.model.headers.AcceptEncoding]] header from the client - * will be respected (or otherwise, if no matching . + * If the `Accept-Encoding` header is missing or empty or specifies an encoding other than + * identity, gzip or deflate then no encoding is used. */ - @varargs def encodeResponse(coders: Coder*): Directive = - Directives.custom(RouteStructure.EncodeResponse(coders.toList)) + def encodeResponse(inner: Supplier[Route]): Route = RouteAdapter { + D.encodeResponse { + inner.get.delegate + } + } + + /** + * Encodes the response with the encoding that is requested by the client via the `Accept- + * Encoding` header. The response encoding is determined by the rules specified in + * http://tools.ietf.org/html/rfc7231#section-5.3.4. + * + * If the `Accept-Encoding` header is missing then the response is encoded using the `first` + * encoder. + * + * If the `Accept-Encoding` header is empty and `NoCoding` is part of the encoders then no + * response encoding is used. Otherwise the request is rejected. + * + * If [encoders] is empty, no encoding is performed. + */ + def encodeResponseWith(coders: java.lang.Iterable[Coder], inner: Supplier[Route]): Route = RouteAdapter { + coders.asScala.toList match { + case head :: tail ⇒ + D.encodeResponseWith(head._underlyingScalaCoder, tail.toSeq.map(_._underlyingScalaCoder): _*) { + inner.get.delegate + } + case _ ⇒ + inner.get.delegate + } + } /** * Decodes the incoming request using the given Decoder. * If the request encoding doesn't match the request is rejected with an `UnsupportedRequestEncodingRejection`. */ - @varargs def decodeRequestWith(decoder: Coder, innerRoute: Route, moreInnerRoutes: Route*): Route = - RouteStructure.DecodeRequest(decoder :: Nil)(innerRoute, moreInnerRoutes.toList) + def decodeRequestWith(coder: Coder, inner: Supplier[Route]): Route = RouteAdapter { + D.decodeRequestWith(coder._underlyingScalaCoder) { + inner.get.delegate + } + } + + /** + * Rejects the request with an UnsupportedRequestEncodingRejection if its encoding doesn't match the given one. + */ + def requestEncodedWith(encoding: HttpEncoding, inner: Supplier[Route]): Route = RouteAdapter { + D.requestEncodedWith(encoding.asScala) { + inner.get.delegate + } + } /** * Decodes the incoming request if it is encoded with one of the given @@ -47,27 +88,31 @@ abstract class CodingDirectives extends CacheConditionDirectives { * the request is rejected with an `UnsupportedRequestEncodingRejection`. * If no decoders are given the default encoders (`Gzip`, `Deflate`, `NoCoding`) are used. */ - @varargs def decodeRequestWith(decoders: Coder*): Directive = - Directives.custom(RouteStructure.DecodeRequest(decoders.toList)) + def decodeRequestWith(coders: java.lang.Iterable[Coder], inner: Supplier[Route]): Route = RouteAdapter { + D.decodeRequestWith(coders.asScala.map(_._underlyingScalaCoder).toSeq: _*) { + inner.get.delegate + } + } /** * Decompresses the incoming request if it is `gzip` or `deflate` compressed. * Uncompressed requests are passed through untouched. * If the request encoded with another encoding the request is rejected with an `UnsupportedRequestEncodingRejection`. */ - @varargs def decodeRequest(innerRoute: Route, moreInnerRoutes: Route*): Route = - RouteStructure.DecodeRequest(CodingDirectives._DefaultCodersToDecodeRequest)(innerRoute, moreInnerRoutes.toList) + def decodeRequest(inner: Supplier[Route]): Route = RouteAdapter { + D.decodeRequest { + inner.get.delegate + } + } + + /** + * Inspects the response entity and adds a `Content-Encoding: gzip` response header if + * the entities media-type is precompressed with gzip and no `Content-Encoding` header is present yet. + */ + def withPrecompressedMediaTypeSupport(inner: Supplier[Route]): Route = RouteAdapter { + D.withPrecompressedMediaTypeSupport { + inner.get.delegate + } + } } -/** - * Internal API - */ -private[http] object CodingDirectives { - private[http] val _DefaultCodersToEncodeResponse = - scaladsl.server.directives.CodingDirectives.DefaultEncodeResponseEncoders - .map(c ⇒ Coder.values().find(_._underlyingScalaCoder() == c).get) - - private[http] val _DefaultCodersToDecodeRequest = - scaladsl.server.directives.CodingDirectives.DefaultCoders - .map(c ⇒ Coder.values().find(_._underlyingScalaCoder() == c).get) -} diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/directives/CookieDirectives.scala b/akka-http/src/main/scala/akka/http/javadsl/server/directives/CookieDirectives.scala index 4c0561c3c2..dc5088b3a1 100644 --- a/akka-http/src/main/scala/akka/http/javadsl/server/directives/CookieDirectives.scala +++ b/akka-http/src/main/scala/akka/http/javadsl/server/directives/CookieDirectives.scala @@ -4,16 +4,105 @@ package akka.http.javadsl.server.directives -import akka.http.impl.server.RouteStructure -import akka.http.javadsl.model.headers.HttpCookie -import akka.http.javadsl.server.Route +import java.lang.{ Iterable ⇒ JIterable } +import java.util.Optional +import java.util.function.{ Function ⇒ JFunction } +import java.util.function.Supplier -import scala.annotation.varargs +import scala.collection.JavaConverters._ +import akka.http.impl.util.JavaMapping.Implicits._ + +import akka.http.javadsl.model.headers.HttpCookie +import akka.http.javadsl.model.headers.HttpCookiePair +import akka.http.javadsl.server.Route +import akka.http.scaladsl +import akka.http.scaladsl.server.{ Directives ⇒ D } abstract class CookieDirectives extends CodingDirectives { /** - * Adds a Set-Cookie header with the given cookies to all responses of its inner route. + * Extracts the [[HttpCookiePair]] with the given name. If the cookie is not present the + * request is rejected with a respective [[akka.http.javadsl.server.MissingCookieRejection]]. */ - @varargs def setCookie(cookie: HttpCookie, innerRoute: Route, moreInnerRoutes: Route*): Route = - RouteStructure.SetCookie(cookie)(innerRoute, moreInnerRoutes.toList) + def cookie(name: String, inner: JFunction[HttpCookiePair, Route]): Route = RouteAdapter { + D.cookie(name) { c ⇒ inner.apply(c).delegate } + } + + /** + * Extracts the [[HttpCookiePair]] with the given name as an `Option[HttpCookiePair]`. + * If the cookie is not present a value of `None` is extracted. + */ + def optionalCookie(name: String, inner: JFunction[Optional[HttpCookiePair], Route]): Route = RouteAdapter { + D.optionalCookie(name) { c ⇒ inner.apply(c.asJava).delegate } + } + + /** + * Adds a [[Set-Cookie]] response header with the given cookie. + */ + def setCookie(cookie: HttpCookie, inner: Supplier[Route]): Route = RouteAdapter { + D.setCookie(cookie.asScala) { inner.get.delegate } + } + + /** + * Adds a [[Set-Cookie]] response header with the given cookies. + */ + def setCookie(cookies: JIterable[HttpCookie], inner: Supplier[Route]): Route = RouteAdapter { + cookies.asScala.toList match { + case head :: tail ⇒ + D.setCookie(head.asScala, tail.map(_.asScala).toVector: _*) { + inner.get.delegate + } + case _ ⇒ + inner.get.delegate + } + } + + /** + * Adds a [[Set-Cookie]] response header expiring the given cookie. + */ + def deleteCookie(cookie: HttpCookie, inner: Supplier[Route]): Route = RouteAdapter { + D.deleteCookie(cookie.asScala) { inner.get.delegate } + } + + /** + * Adds a [[Set-Cookie]] response header expiring the given cookies. + */ + def deleteCookie(cookies: JIterable[HttpCookie], inner: Supplier[Route]): Route = RouteAdapter { + cookies.asScala.toList match { + case head :: tail ⇒ + D.deleteCookie(head.asScala, tail.map(_.asScala).toSeq: _*) { + inner.get.delegate + } + case _ ⇒ + inner.get.delegate + } + } + + /** + * Adds a [[Set-Cookie]] response header expiring the cookie with the given properties. + * + * @param name Name of the cookie to match + */ + def deleteCookie(name: String, inner: Supplier[Route]): Route = deleteCookie(name, "", "", inner) + + /** + * Adds a [[Set-Cookie]] response header expiring the cookie with the given properties. + * + * @param name Name of the cookie to match + * @param domain Domain of the cookie to match, or empty string to match any domain + */ + def deleteCookie(name: String, domain: String, inner: Supplier[Route]): Route = deleteCookie(name, domain, "", inner) + + /** + * Adds a [[Set-Cookie]] response header expiring the cookie with the given properties. + * + * @param name Name of the cookie to match + * @param domain Domain of the cookie to match, or empty string to match any domain + * @param path Path of the cookie to match, or empty string to match any path + */ + def deleteCookie(name: String, domain: String, path: String, inner: Supplier[Route]): Route = RouteAdapter { + D.deleteCookie(name, domain, path) { + inner.get.delegate + } + } + } diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/directives/DebuggingDirectives.scala b/akka-http/src/main/scala/akka/http/javadsl/server/directives/DebuggingDirectives.scala new file mode 100644 index 0000000000..762c1bc3c8 --- /dev/null +++ b/akka-http/src/main/scala/akka/http/javadsl/server/directives/DebuggingDirectives.scala @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2009-2016 Lightbend Inc. + */ +package akka.http.javadsl.server.directives + +import java.lang.{ Iterable ⇒ JIterable } +import java.util.function.{ BiFunction, Function ⇒ JFunction, Supplier } +import java.util.{ List ⇒ JList, Optional } + +import akka.event.Logging +import akka.event.Logging.LogLevel +import akka.http.javadsl.RoutingJavaMapping +import akka.http.javadsl.model.{ HttpRequest, HttpResponse } +import akka.http.javadsl.server.Route +import akka.http.scaladsl +import akka.http.scaladsl.server.directives.LoggingMagnet +import akka.http.scaladsl.server.{ Directives ⇒ D, Rejection, RouteResult } + +import scala.collection.JavaConverters._ + +abstract class DebuggingDirectives extends CookieDirectives { + import akka.http.impl.util.JavaMapping.Implicits._ + import RoutingJavaMapping._ + + /** + * Produces a log entry for every incoming request. + */ + def logRequest(marker: String, inner: Supplier[Route]): Route = RouteAdapter { + D.logRequest(marker) { inner.get.delegate } + } + + /** + * Produces a log entry for every incoming request. + * + * @param level One of the log levels defined in akka.event.Logging + */ + def logRequest(marker: String, level: LogLevel, inner: Supplier[Route]): Route = RouteAdapter { + D.logRequest(marker) { inner.get.delegate } + } + + /** + * Produces a log entry for every incoming request. + */ + def logRequest(show: JFunction[HttpRequest, LogEntry], inner: Supplier[Route]): Route = RouteAdapter { + D.logRequest(LoggingMagnet.forMessageFromFullShow(rq ⇒ show.apply(rq).asScala)) { inner.get.delegate } + } + + /** + * Produces a log entry for every route result. + */ + def logResult(marker: String, inner: Supplier[Route]): Route = RouteAdapter { + D.logResult(marker) { inner.get.delegate } + } + + /** + * Produces a log entry for every route result. + * + * @param level One of the log levels defined in akka.event.Logging + */ + def logResult(marker: String, level: LogLevel, inner: Supplier[Route]): Route = RouteAdapter { + D.logResult(marker) { inner.get.delegate } + } + + /** + * Produces a log entry for every route result. + * + * @param showSuccess Function invoked when the route result was successful and yielded an HTTP response + * @param showRejection Function invoked when the route yielded a rejection + */ + def logResult(showSuccess: JFunction[HttpResponse, LogEntry], + showRejection: JFunction[JList[Rejection], LogEntry], + inner: Supplier[Route]) = RouteAdapter { + D.logResult(LoggingMagnet.forMessageFromFullShow { + case RouteResult.Complete(response) ⇒ showSuccess.apply(response).asScala + case RouteResult.Rejected(rejections) ⇒ showRejection.apply(rejections.asJava).asScala + }) { + inner.get.delegate + } + } + + /** + * Produces a log entry for every request/response combination. + * + * @param showSuccess Function invoked when the route result was successful and yielded an HTTP response + * @param showRejection Function invoked when the route yielded a rejection + */ + def logRequestResult(showSuccess: BiFunction[HttpRequest, HttpResponse, LogEntry], + showRejection: BiFunction[HttpRequest, JList[Rejection], LogEntry], + inner: Supplier[Route]) = RouteAdapter { + D.logRequestResult(LoggingMagnet.forRequestResponseFromFullShow(request ⇒ { + case RouteResult.Complete(response) ⇒ Some(showSuccess.apply(request, response).asScala) + case RouteResult.Rejected(rejections) ⇒ Some(showRejection.apply(request, rejections.asJava).asScala) + })) { + inner.get.delegate + } + } + + /** + * Optionally produces a log entry for every request/response combination. + * + * @param showSuccess Function invoked when the route result was successful and yielded an HTTP response + * @param showRejection Function invoked when the route yielded a rejection + */ + @CorrespondsTo("logRequestResult") + def logRequestResultOptional(showSuccess: BiFunction[HttpRequest, HttpResponse, Optional[LogEntry]], + showRejection: BiFunction[HttpRequest, JList[Rejection], Optional[LogEntry]], + inner: Supplier[Route]) = RouteAdapter { + D.logRequestResult(LoggingMagnet.forRequestResponseFromFullShow(request ⇒ { + case RouteResult.Complete(response) ⇒ showSuccess.apply(request, response).asScala + case RouteResult.Rejected(rejections) ⇒ showRejection.apply(request, rejections.asJava).asScala + })) { + inner.get.delegate + } + } +} + +abstract class LogEntry { + def getObj: Any + def getLevel: LogLevel +} + +object LogEntry { + def create(obj: Any, level: LogLevel): LogEntry = scaladsl.server.directives.LogEntry(obj, level) + def debug(obj: Any): LogEntry = scaladsl.server.directives.LogEntry(obj, Logging.DebugLevel) + def info(obj: Any): LogEntry = scaladsl.server.directives.LogEntry(obj, Logging.InfoLevel) + def warning(obj: Any): LogEntry = scaladsl.server.directives.LogEntry(obj, Logging.WarningLevel) + def error(obj: Any): LogEntry = scaladsl.server.directives.LogEntry(obj, Logging.ErrorLevel) +} \ No newline at end of file diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/directives/ExecutionDirectives.scala b/akka-http/src/main/scala/akka/http/javadsl/server/directives/ExecutionDirectives.scala index 63194da4ed..480a1acccc 100644 --- a/akka-http/src/main/scala/akka/http/javadsl/server/directives/ExecutionDirectives.scala +++ b/akka-http/src/main/scala/akka/http/javadsl/server/directives/ExecutionDirectives.scala @@ -1,41 +1,31 @@ /* * Copyright (C) 2009-2016 Lightbend Inc. */ +package akka.http.javadsl.server.directives -package akka.http.javadsl.server -package directives +import akka.http.javadsl.server.ExceptionHandler +import akka.http.javadsl.server.RejectionHandler +import akka.http.javadsl.server.Route +import akka.http.scaladsl.server.directives.{ ExecutionDirectives ⇒ D } -import akka.http.impl.server.RouteStructure - -import scala.annotation.varargs -import scala.reflect.ClassTag - -abstract class ExecutionDirectives extends CookieDirectives { - /** - * Handles exceptions in the inner routes using the specified handler. - */ - @varargs - def handleExceptions(handler: ExceptionHandler, innerRoute: Route, moreInnerRoutes: Route*): Route = - RouteStructure.HandleExceptions(handler)(innerRoute, moreInnerRoutes.toList) +abstract class ExecutionDirectives extends DebuggingDirectives { /** - * Handles rejections in the inner routes using the specified handler. + * Transforms exceptions thrown during evaluation of its inner route using the given + * [[akka.http.javadsl.server.ExceptionHandler]]. */ - @varargs - def handleRejections(handler: RejectionHandler, innerRoute: Route, moreInnerRoutes: Route*): Route = - RouteStructure.HandleRejections(handler)(innerRoute, moreInnerRoutes.toList) + def handleExceptions(handler: ExceptionHandler, inner: java.util.function.Supplier[Route]) = RouteAdapter( + D.handleExceptions(handler.asScala) { + inner.get.delegate + }) /** - * Handles rejections of the given type in the inner routes using the specified handler. + * Transforms rejections produced by its inner route using the given + * [[akka.http.scaladsl.server.RejectionHandler]]. */ - @varargs - def handleRejections[T](tClass: Class[T], handler: Handler1[T], innerRoute: Route, moreInnerRoutes: Route*): Route = - RouteStructure.HandleRejections(new RejectionHandler { - implicit def tTag: ClassTag[T] = ClassTag(tClass) - override def handleCustomRejection(ctx: RequestContext, rejection: CustomRejection): RouteResult = - rejection match { - case t: T ⇒ handler.apply(ctx, t) - case _ ⇒ passRejection() - } - })(innerRoute, moreInnerRoutes.toList) + def handleRejections(handler: RejectionHandler, inner: java.util.function.Supplier[Route]) = RouteAdapter( + D.handleRejections(handler.asScala) { + inner.get.delegate + }) + } diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/directives/FileAndResourceDirectives.scala b/akka-http/src/main/scala/akka/http/javadsl/server/directives/FileAndResourceDirectives.scala index 74f949d8d8..9ba922d479 100644 --- a/akka-http/src/main/scala/akka/http/javadsl/server/directives/FileAndResourceDirectives.scala +++ b/akka-http/src/main/scala/akka/http/javadsl/server/directives/FileAndResourceDirectives.scala @@ -5,10 +5,14 @@ package akka.http.javadsl.server.directives import java.io.File -import akka.http.javadsl.model.{ ContentType } + +import scala.annotation.varargs +import scala.collection.JavaConverters._ + +import akka.http.javadsl.model.ContentType +import akka.http.javadsl.model.RequestEntity import akka.http.javadsl.server.Route -import akka.http.scaladsl.server -import akka.http.impl.server.RouteStructure._ +import akka.http.scaladsl.server.{ Directives ⇒ D } /** * Implement this interface to provide a custom mapping from a file name to a [[akka.http.javadsl.model.ContentType]]. @@ -17,112 +21,242 @@ trait ContentTypeResolver { def resolve(fileName: String): ContentType } -/** - * A resolver that assumes the given constant [[akka.http.javadsl.model.ContentType]] for all files. - */ -case class StaticContentTypeResolver(contentType: ContentType) extends ContentTypeResolver { - def resolve(fileName: String): ContentType = contentType +abstract class DirectoryListing { + def getPath: String + def isRoot: Boolean + def getFiles: java.util.List[File] +} + +trait DirectoryRenderer { + def directoryMarshaller(renderVanityFooter: Boolean): akka.http.javadsl.server.Marshaller[DirectoryListing, RequestEntity] } /** - * Allows to customize one of the predefined routes of [[FileAndResourceRoute]] to respond - * with a particular content type. + * Directives that load files and resources. * - * The default behavior is to determine the content type by file extension. + * For the directives in this class, the "default classloader" is defined as the classloader that has loaded + * the akka.actor.ActorSystem class. */ -trait FileAndResourceRoute extends Route { - /** - * Returns a variant of this route that responds with the given constant [[akka.http.javadsl.model.ContentType]]. - */ - def withContentType(contentType: ContentType): Route - - /** - * Returns a variant of this route that uses the specified [[ContentTypeResolver]] to determine - * which [[akka.http.javadsl.model.ContentType]] to respond with by file name. - */ - def resolveContentTypeWith(resolver: ContentTypeResolver): Route -} - -object FileAndResourceRoute { - /** - * INTERNAL API - */ - private[http] def apply(f: ContentTypeResolver ⇒ Route): FileAndResourceRoute = - new FileAndResourceRouteWithDefaultResolver(f) with FileAndResourceRoute { - def withContentType(contentType: ContentType): Route = resolveContentTypeWith(StaticContentTypeResolver(contentType)) - def resolveContentTypeWith(resolver: ContentTypeResolver): Route = f(resolver) - } - - /** - * INTERNAL API - */ - private[http] def forFixedName(fileName: String)(f: ContentType ⇒ Route): FileAndResourceRoute = - new FileAndResourceRouteWithDefaultResolver(resolver ⇒ f(resolver.resolve(fileName))) with FileAndResourceRoute { - def withContentType(contentType: ContentType): Route = resolveContentTypeWith(StaticContentTypeResolver(contentType)) - def resolveContentTypeWith(resolver: ContentTypeResolver): Route = f(resolver.resolve(fileName)) - } -} - abstract class FileAndResourceDirectives extends ExecutionDirectives { + import akka.http.impl.util.JavaMapping.Implicits._ + import akka.http.javadsl.RoutingJavaMapping._ + /** - * Completes GET requests with the content of the given resource loaded from the default ClassLoader. + * Completes GET requests with the content of the given resource loaded from the default ClassLoader, + * using the default content type resolver. * If the resource cannot be found or read the Route rejects the request. */ - def getFromResource(path: String): Route = - getFromResource(path, defaultClassLoader) + def getFromResource(path: String): Route = RouteAdapter { + D.getFromResource(path) + } /** - * Completes GET requests with the content of the given resource loaded from the given ClassLoader. + * Completes GET requests with the content of the given resource loaded from the default ClassLoader, + * using the given content type resolver. * If the resource cannot be found or read the Route rejects the request. */ - def getFromResource(path: String, classLoader: ClassLoader): Route = - FileAndResourceRoute.forFixedName(path)(GetFromResource(path, _, classLoader)) + def getFromResource(path: String, resolver: ContentTypeResolver): Route = RouteAdapter { + D.getFromResource(path)(resolver.asScala) + } /** - * Completes GET requests with the content from the resource identified by the given - * directoryPath and the unmatched path. + * Completes GET requests with the content of the given resource loaded from the default ClassLoader, + * with the given content type. + * If the resource cannot be found or read the Route rejects the request. */ - def getFromResourceDirectory(directoryPath: String): FileAndResourceRoute = - getFromResourceDirectory(directoryPath, defaultClassLoader) + def getFromResource(path: String, contentType: ContentType): Route = RouteAdapter { + D.getFromResource(path, contentType.asScala) + } /** - * Completes GET requests with the content from the resource identified by the given - * directoryPath and the unmatched path from the given ClassLoader. + * Completes GET requests with the content of the given resource loaded from the given ClassLoader, + * with the given content type. + * If the resource cannot be found or read the Route rejects the request. */ - def getFromResourceDirectory(directoryPath: String, classLoader: ClassLoader): FileAndResourceRoute = - FileAndResourceRoute(GetFromResourceDirectory(directoryPath, classLoader, _)) + def getFromResource(path: String, contentType: ContentType, classLoader: ClassLoader): Route = RouteAdapter { + D.getFromResource(path, contentType.asScala, classLoader) + } /** - * Completes GET requests with the content of the given file. + * Same as "getFromDirectory" except that the file is not fetched from the file system but rather from a + * "resource directory", using the default ClassLoader, resolving content type using the default content type + * resolver. + * + * If the requested resource is itself a directory or cannot be found or read the Route rejects the request. */ - def getFromFile(file: File): FileAndResourceRoute = FileAndResourceRoute.forFixedName(file.getPath)(GetFromFile(file, _)) + def getFromResourceDirectory(directoryName: String): Route = RouteAdapter { + D.getFromResourceDirectory(directoryName) + } /** - * Completes GET requests with the content of the file at the path. + * Same as "getFromDirectory" except that the file is not fetched from the file system but rather from a + * "resource directory", using the given ClassLoader, resolving content type using the default content type + * resolver. + * + * If the requested resource is itself a directory or cannot be found or read the Route rejects the request. */ - def getFromFile(path: String): FileAndResourceRoute = getFromFile(new File(path)) + def getFromResourceDirectory(directoryName: String, classLoader: ClassLoader): Route = RouteAdapter { + D.getFromResourceDirectory(directoryName, classLoader) + } /** - * Completes GET requests with the content from the file identified by the given - * directory and the unmatched path of the request. + * Same as "getFromDirectory" except that the file is not fetched from the file system but rather from a + * "resource directory", using the default ClassLoader, resolving content type using the given content type + * resolver. + * + * If the requested resource is itself a directory or cannot be found or read the Route rejects the request. */ - def getFromDirectory(directory: File): FileAndResourceRoute = FileAndResourceRoute(GetFromDirectory(directory, browseable = false, _)) + def getFromResourceDirectory(directoryName: String, resolver: ContentTypeResolver): Route = RouteAdapter { + D.getFromResourceDirectory(directoryName)(resolver.asScala) + } /** - * Completes GET requests with the content from the file identified by the given - * directoryPath and the unmatched path of the request. + * Same as "getFromDirectory" except that the file is not fetched from the file system but rather from a + * "resource directory", using the given ClassLoader, resolving content type using the given content type + * resolver. + * + * If the requested resource is itself a directory or cannot be found or read the Route rejects the request. */ - def getFromDirectory(directoryPath: String): FileAndResourceRoute = getFromDirectory(new File(directoryPath)) + def getFromResourceDirectory(directoryName: String, resolver: ContentTypeResolver, classLoader: ClassLoader): Route = RouteAdapter { + D.getFromResourceDirectory(directoryName, classLoader)(resolver.asScala) + } /** - * Same as [[#getFromDirectory]] but generates a listing of files if the path is a directory. + * Completes GET requests with the content of the given file, resolving the content type using the default resolver. + * If the file cannot be found or read the request is rejected. */ - def getFromBrowseableDirectory(directory: File): FileAndResourceRoute = FileAndResourceRoute(GetFromDirectory(directory, browseable = true, _)) + def getFromFile(file: File): Route = RouteAdapter { + D.getFromFile(file) + } /** - * Same as [[#getFromDirectory]] but generates a listing of files if the path is a directory. + * Completes GET requests with the content of the given file, resolving the content type using the given resolver. + * If the file cannot be found or read the request is rejected. */ - def getFromBrowseableDirectory(directoryPath: String): FileAndResourceRoute = FileAndResourceRoute(GetFromDirectory(new File(directoryPath), browseable = true, _)) + def getFromFile(file: File, resolver: ContentTypeResolver): Route = RouteAdapter { + D.getFromFile(file)(resolver.asScala) + } - protected def defaultClassLoader: ClassLoader = server.directives.FileAndResourceDirectives.defaultClassLoader + /** + * Completes GET requests with the content of the given file, using the content type. + * If the file cannot be found or read the request is rejected. + */ + def getFromFile(file: File, contentType: ContentType): Route = RouteAdapter { + D.getFromFile(file, contentType.asScala) + } + + /** + * Completes GET requests with the content of the given file, resolving the content type using the default resolver. + * If the file cannot be found or read the request is rejected. + */ + def getFromFile(file: String): Route = RouteAdapter { + D.getFromFile(file) + } + + /** + * Completes GET requests with the content of the given file, resolving the content type using the given resolver. + * If the file cannot be found or read the request is rejected. + */ + def getFromFile(file: String, resolver: ContentTypeResolver): Route = RouteAdapter { + D.getFromFile(file)(resolver.asScala) + } + + /** + * Completes GET requests with the content of a file underneath the given directory, using the default content-type resolver. + * If the file cannot be read the Route rejects the request. + */ + def getFromDirectory(directoryPath: String): Route = RouteAdapter { + D.getFromDirectory(directoryPath) + } + + /** + * Completes GET requests with the content of a file underneath the given directory, using the given content-type resolver. + * If the file cannot be read the Route rejects the request. + */ + def getFromDirectory(directoryPath: String, resolver: ContentTypeResolver): Route = RouteAdapter { + D.getFromDirectory(directoryPath)(resolver.asScala) + } + + /** + * Same as `getFromBrowseableDirectories` with only one directory. + */ + def getFromBrowseableDirectory(directory: String, renderer: DirectoryRenderer, resolver: ContentTypeResolver): Route = RouteAdapter { + D.getFromBrowseableDirectory(directory)(renderer.asScala, resolver.asScala) + } + + /** + * Same as `getFromBrowseableDirectories` with only one directory. + */ + def getFromBrowseableDirectory(directory: String, renderer: DirectoryRenderer): Route = RouteAdapter { + D.getFromBrowseableDirectory(directory)(renderer.asScala, defaultContentTypeResolver.asScala) + } + + /** + * Same as `getFromBrowseableDirectories` with only one directory. + */ + def getFromBrowseableDirectory(directory: String, resolver: ContentTypeResolver): Route = RouteAdapter { + D.getFromBrowseableDirectory(directory)(defaultDirectoryRenderer.asScala, resolver.asScala) + } + + /** + * Same as `getFromBrowseableDirectories` with only one directory. + */ + def getFromBrowseableDirectory(directory: String): Route = RouteAdapter { + D.getFromBrowseableDirectory(directory) + } + + /** + * Serves the content of the given directories as a file system browser, i.e. files are sent and directories + * served as browseable listings. + */ + def getFromBrowseableDirectories(directories: java.lang.Iterable[String], renderer: DirectoryRenderer, resolver: ContentTypeResolver): Route = RouteAdapter { + D.getFromBrowseableDirectories(directories.asScala.toSeq: _*)(renderer.asScala, resolver.asScala) + } + + /** + * Serves the content of the given directories as a file system browser, i.e. files are sent and directories + * served as browseable listings. + */ + def getFromBrowseableDirectories(directories: java.lang.Iterable[String], renderer: DirectoryRenderer): Route = RouteAdapter { + D.getFromBrowseableDirectories(directories.asScala.toSeq: _*)(renderer.asScala, defaultContentTypeResolver.asScala) + } + + /** + * Serves the content of the given directories as a file system browser, i.e. files are sent and directories + * served as browseable listings. + */ + def getFromBrowseableDirectories(directories: java.lang.Iterable[String], resolver: ContentTypeResolver): Route = RouteAdapter { + D.getFromBrowseableDirectories(directories.asScala.toSeq: _*)(defaultDirectoryRenderer.asScala, resolver.asScala) + } + + /** + * Serves the content of the given directories as a file system browser, i.e. files are sent and directories + * served as browseable listings. + */ + @varargs def getFromBrowseableDirectories(directories: String*): Route = RouteAdapter { + D.getFromBrowseableDirectories(directories: _*) + } + + /** + * Completes GET requests with a unified listing of the contents of all given directories. + * The actual rendering of the directory contents is performed by the in-scope `Marshaller[DirectoryListing]`. + */ + @varargs def listDirectoryContents(directories: String*): Route = RouteAdapter { + D.listDirectoryContents(directories: _*)(defaultDirectoryRenderer.asScala) + } + /** + * Completes GET requests with a unified listing of the contents of all given directories. + * The actual rendering of the directory contents is performed by the in-scope `Marshaller[DirectoryListing]`. + */ + @varargs def listDirectoryContents(directoryRenderer: DirectoryRenderer, directories: String*): Route = RouteAdapter { + D.listDirectoryContents(directories: _*)(directoryRenderer.asScala) + } + + /** Default [[DirectoryRenderer]] to be used with directory listing directives. */ + def defaultDirectoryRenderer: DirectoryRenderer = + akka.http.scaladsl.server.directives.FileAndResourceDirectives.DirectoryRenderer.defaultDirectoryRenderer + + /** Default [[ContentTypeResolver]]. */ + def defaultContentTypeResolver: ContentTypeResolver = + akka.http.scaladsl.server.directives.ContentTypeResolver.Default } diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/directives/FileUploadDirectives.scala b/akka-http/src/main/scala/akka/http/javadsl/server/directives/FileUploadDirectives.scala new file mode 100644 index 0000000000..7c656eba4d --- /dev/null +++ b/akka-http/src/main/scala/akka/http/javadsl/server/directives/FileUploadDirectives.scala @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2009-2016 Lightbend Inc. + */ +package akka.http.javadsl.server.directives + +import java.io.File +import java.util.function.BiFunction + +import akka.http.impl.util.JavaMapping.Implicits._ + +import akka.http.javadsl.model.ContentType +import akka.http.javadsl.server.Route +import akka.http.scaladsl.server.{ Directives ⇒ D } +import akka.stream.javadsl.Source +import akka.util.ByteString + +abstract class FileUploadDirectives extends FileAndResourceDirectives { + /** + * Streams the bytes of the file submitted using multipart with the given file name into a temporary file on disk. + * If there is an error writing to disk the request will be failed with the thrown exception, if there is no such + * field the request will be rejected, if there are multiple file parts with the same name, the first one will be + * used and the subsequent ones ignored. + */ + def uploadedFile(fieldName: String, inner: BiFunction[FileInfo, File, Route]): Route = RouteAdapter { + D.uploadedFile(fieldName) { case (info, file) ⇒ inner.apply(info, file).delegate } + } + + /** + * Collects each body part that is a multipart file as a tuple containing metadata and a `Source` + * for streaming the file contents somewhere. If there is no such field the request will be rejected, + * if there are multiple file parts with the same name, the first one will be used and the subsequent + * ones ignored. + */ + def fileUpload(fieldName: String, inner: BiFunction[FileInfo, Source[ByteString, Any], Route]): Route = RouteAdapter { + D.fileUpload(fieldName) { case (info, src) ⇒ inner.apply(info, src.asJava).delegate } + } +} + +/** + * Additional metadata about the file being uploaded/that was uploaded using the [[FileUploadDirectives]] + */ +abstract class FileInfo { + /** + * Name of the form field the file was uploaded in + */ + def getFieldName: String + + /** + * User specified name of the uploaded file + */ + def getFileName: String + + /** + * Content type of the file + */ + def getContentType: ContentType +} \ No newline at end of file diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/directives/FormFieldDirectives.scala b/akka-http/src/main/scala/akka/http/javadsl/server/directives/FormFieldDirectives.scala new file mode 100644 index 0000000000..214f191c58 --- /dev/null +++ b/akka-http/src/main/scala/akka/http/javadsl/server/directives/FormFieldDirectives.scala @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2009-2016 Lightbend Inc. + */ +package akka.http.javadsl.server.directives + +import java.util.{ Map ⇒ JMap, List ⇒ JList } +import java.util.AbstractMap.SimpleImmutableEntry +import java.util.Optional +import java.util.function.{ Function ⇒ JFunction } + +import scala.collection.JavaConverters._ +import akka.http.impl.util.JavaMapping.Implicits._ + +import akka.http.javadsl.server.{ Route, Unmarshaller } + +import akka.http.scaladsl.server.{ Directives ⇒ D } +import akka.http.scaladsl.server.directives.ParameterDirectives._ + +import scala.compat.java8.OptionConverters + +abstract class FormFieldDirectives extends FileUploadDirectives { + + def formField(name: String, inner: JFunction[String, Route]): Route = RouteAdapter( + D.formField(name) { value ⇒ + inner.apply(value).delegate + }) + + @CorrespondsTo("formField") + def formFieldOptional(name: String, inner: JFunction[Optional[String], Route]): Route = RouteAdapter( + D.formField(name.?) { value ⇒ + inner.apply(value.asJava).delegate + }) + + @CorrespondsTo("formFieldSeq") + def formFieldList(name: String, inner: JFunction[java.util.List[String], Route]): Route = RouteAdapter( + D.formField(_string2NR(name).*) { values ⇒ + inner.apply(values.toSeq.asJava).delegate + }) + + def formField[T](t: Unmarshaller[String, T], name: String, inner: JFunction[T, Route]): Route = { + import t.asScala + RouteAdapter( + D.formField(name.as[T]) { value ⇒ + inner.apply(value).delegate + }) + } + + @CorrespondsTo("formField") + def formFieldOptional[T](t: Unmarshaller[String, T], name: String, inner: JFunction[Optional[T], Route]): Route = { + import t.asScala + RouteAdapter( + D.formField(name.as[T].?) { value ⇒ + inner.apply(OptionConverters.toJava(value)).delegate + }) + } + + @CorrespondsTo("formFieldSeq") + def formFieldList[T](t: Unmarshaller[String, T], name: String, inner: JFunction[java.util.List[T], Route]): Route = { + import t.asScala + RouteAdapter( + D.formField(name.as[T].*) { values ⇒ + inner.apply(values.toSeq.asJava).delegate + }) + } + + /** + * Extracts HTTP form fields from the request as a ``Map``. + */ + def formFieldMap(inner: JFunction[JMap[String, String], Route]): Route = RouteAdapter { + D.formFieldMap { map ⇒ inner.apply(map.asJava).delegate } + } + + /** + * Extracts HTTP form fields from the request as a ``Map>``. + */ + def formFieldMultiMap(inner: JFunction[JMap[String, JList[String]], Route]): Route = RouteAdapter { + D.formFieldMultiMap { map ⇒ inner.apply(map.mapValues { l ⇒ l.asJava }.asJava).delegate } + } + + /** + * Extracts HTTP form fields from the request as a ``Map.Entry>``. + */ + @CorrespondsTo("formFieldSeq") + def formFieldList(inner: JFunction[JList[JMap.Entry[String, String]], Route]): Route = RouteAdapter { + D.formFieldSeq { list ⇒ + val entries: Seq[JMap.Entry[String, String]] = list.map { e ⇒ new SimpleImmutableEntry(e._1, e._2) } + inner.apply(entries.asJava).delegate + } + } + +} \ No newline at end of file diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/directives/FutureDirectives.scala b/akka-http/src/main/scala/akka/http/javadsl/server/directives/FutureDirectives.scala new file mode 100644 index 0000000000..4c83e2a192 --- /dev/null +++ b/akka-http/src/main/scala/akka/http/javadsl/server/directives/FutureDirectives.scala @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2009-2016 Lightbend Inc. + */ +package akka.http.javadsl.server.directives + +import java.util.concurrent.CompletionException +import java.util.concurrent.CompletionStage +import java.util.function.{ Function ⇒ JFunction } +import java.util.function.Supplier + +import akka.http.javadsl.model.RequestEntity + +import scala.compat.java8.FutureConverters._ +import scala.concurrent.ExecutionContext.Implicits.global +import scala.util.Try +import akka.http.javadsl.server.{ Marshaller, Route } +import akka.http.scaladsl.server.directives.{ CompleteOrRecoverWithMagnet, FutureDirectives ⇒ D } + +abstract class FutureDirectives extends FormFieldDirectives { + + /** + * "Unwraps" a `CompletionStage` and runs the inner route after future + * completion with the future's value as an extraction of type `Try`. + */ + def onComplete[T](f: Supplier[CompletionStage[T]], inner: JFunction[Try[T], Route]) = RouteAdapter { + D.onComplete(f.get.toScala.recover(unwrapCompletionException)) { value ⇒ + inner(value).delegate + } + } + + /** + * "Unwraps" a `CompletionStage` and runs the inner route after stage + * completion with the stage's value as an extraction of type `T`. + * If the stage fails its failure Throwable is bubbled up to the nearest + * ExceptionHandler. + */ + def onSuccess[T](f: Supplier[CompletionStage[T]], inner: JFunction[T, Route]) = RouteAdapter { + D.onSuccess(f.get.toScala.recover(unwrapCompletionException)) { value ⇒ + inner(value).delegate + } + } + + /** + * "Unwraps" a `CompletionStage` and runs the inner route when the stage has failed + * with the stage's failure exception as an extraction of type `Throwable`. + * If the completion stage succeeds the request is completed using the values marshaller + * (This directive therefore requires a marshaller for the completion stage value type to be + * provided.) + */ + def completeOrRecoverWith[T](f: Supplier[CompletionStage[T]], marshaller: Marshaller[T, RequestEntity], inner: JFunction[Throwable, Route]): Route = RouteAdapter { + val magnet = CompleteOrRecoverWithMagnet(f.get.toScala)(Marshaller.asScalaEntityMarshaller(marshaller)) + D.completeOrRecoverWith(magnet) { ex ⇒ inner(ex).delegate } + } + + // TODO: This might need to be raised as an issue to scala-java8-compat instead. + // Right now, having this in Java: + // CompletableFuture.supplyAsync(() -> { throw new IllegalArgumentException("always failing"); }) + // will in fact fail the future with CompletionException. + private def unwrapCompletionException[T]: PartialFunction[Throwable, T] = { + case x: CompletionException if x.getCause ne null ⇒ + throw x.getCause + } + +} diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/directives/HeaderDirectives.scala b/akka-http/src/main/scala/akka/http/javadsl/server/directives/HeaderDirectives.scala new file mode 100644 index 0000000000..e0f2fa9b71 --- /dev/null +++ b/akka-http/src/main/scala/akka/http/javadsl/server/directives/HeaderDirectives.scala @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2009-2016 Lightbend Inc. + */ +package akka.http.javadsl.server.directives + +import java.util.Optional +import java.util.{function => jf} + +import akka.actor.ReflectiveDynamicAccess + +import scala.compat.java8.OptionConverters +import scala.compat.java8.OptionConverters._ +import akka.http.impl.util.JavaMapping.Implicits._ +import akka.http.javadsl.model.HttpHeader +import akka.http.javadsl.server.Route +import akka.http.scaladsl.model.headers.{ModeledCustomHeader, ModeledCustomHeaderCompanion} +import akka.http.scaladsl.server.directives.{HeaderMagnet, BasicDirectives => B, HeaderDirectives => D} +import akka.stream.ActorMaterializer + +import scala.reflect.ClassTag +import scala.util.{Failure, Success} + +abstract class HeaderDirectives extends FutureDirectives { + + private type ScalaHeaderMagnet = HeaderMagnet[akka.http.scaladsl.model.HttpHeader] + + /** + * Extracts an HTTP header value using the given function. If the function result is undefined for all headers the + * request is rejected with an empty rejection set. If the given function throws an exception the request is rejected + * with a [[akka.http.javadsl.server.MalformedHeaderRejection]]. + */ + def headerValue[T](f: jf.Function[HttpHeader, Optional[T]], inner: jf.Function[T, Route]) = RouteAdapter { + D.headerValue(h => f.apply(h).asScala) { value => + inner.apply(value).delegate + } + } + + /** + * Extracts an HTTP header value using the given partial function. If the function is undefined for all headers the + * request is rejected with an empty rejection set. + */ + def headerValuePF[T](pf: PartialFunction[HttpHeader, T], inner: jf.Function[T, Route]) = RouteAdapter { + D.headerValuePF(pf) { value => + inner.apply(value).delegate + } + } + + /** + * Extracts the value of the first HTTP request header with the given name. + * If no header with a matching name is found the request is rejected with a [[akka.http.javadsl.server.MissingHeaderRejection]]. + */ + def headerValueByName(headerName: String, inner: jf.Function[String, Route]) = RouteAdapter { + D.headerValueByName(headerName) { value => + inner.apply(value).delegate + } + } + + /** + * Extracts the first HTTP request header of the given type. + * If no header with a matching type is found the request is rejected with a [[akka.http.javadsl.server.MissingHeaderRejection]]. + */ + def headerValueByType[T <: HttpHeader](t: Class[T], inner: jf.Function[T, Route]) = RouteAdapter { + + def magnetForModeledCustomHeader(clazz: Class[T]): HeaderMagnet[T] = { + // figure out the modeled header companion and use that to parse the header + val refl = new ReflectiveDynamicAccess(getClass.getClassLoader) + refl.getObjectFor[ModeledCustomHeaderCompanion[_]](t.getName) match { + case Success(companion) => + new HeaderMagnet[T] { + override def classTag = ClassTag(t) + override def runtimeClass = t + override def extractPF = { + case h if h.is(companion.lowercaseName) => companion.apply(h.toString).asInstanceOf[T] + } + } + case Failure(ex) => throw new RuntimeException(s"Failed to find or access the ModeledCustomHeaderCompanion for [${t.getName}]", ex) + } + } + + val magnet: HeaderMagnet[T] = + if (classOf[ModeledCustomHeader[_]].isAssignableFrom(t)) magnetForModeledCustomHeader(t) + else HeaderMagnet.fromClassNormalJavaHeader(t) + + D.headerValueByType(magnet) { value => + inner.apply(value).delegate + } + + } + + /** + * Extracts an optional HTTP header value using the given function. + * If the given function throws an exception the request is rejected + * with a [[akka.http.javadsl.server.MalformedHeaderRejection]]. + */ + def optionalHeaderValue[T](f: jf.Function[HttpHeader, Optional[T]], inner: jf.Function[Optional[T], Route]) = RouteAdapter { + D.optionalHeaderValue(h => f.apply(h).asScala) { value => + inner.apply(value.asJava).delegate + } + } + + /** + * Extracts an optional HTTP header value using the given partial function. + * If the given function throws an exception the request is rejected + * with a [[akka.http.javadsl.server.MalformedHeaderRejection]]. + */ + def optionalHeaderValuePF[T](pf: PartialFunction[HttpHeader, T], inner: jf.Function[Optional[T], Route]) = RouteAdapter { + D.optionalHeaderValuePF(pf) { value => + inner.apply(value.asJava).delegate + } + } + + /** + * Extracts the value of the optional HTTP request header with the given name. + */ + def optionalHeaderValueByName(headerName: String, inner: jf.Function[Optional[String], Route]) = RouteAdapter { + D.optionalHeaderValueByName(headerName) { value => + inner.apply(value.asJava).delegate + } + } + + /** + * FIXME: WARNING: Custom headers don't work yet with this directive! + * + * Extract the header value of the optional HTTP request header with the given type. + */ + def optionalHeaderValueByType[T <: HttpHeader](t: Class[T], inner: jf.Function[Optional[T], Route]) = RouteAdapter { + // TODO custom headers don't work yet + // TODO needs instance of check if it's a modeled header and then magically locate companion + D.optionalHeaderValueByType(HeaderMagnet.fromClassNormalJavaHeader(t).asInstanceOf[ScalaHeaderMagnet]) { value => + val valueT = value.asInstanceOf[Option[T]] // we know this is safe because T <: HttpHeader + inner.apply(OptionConverters.toJava[T](valueT)).delegate + } + } + +} diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/directives/HostDirectives.scala b/akka-http/src/main/scala/akka/http/javadsl/server/directives/HostDirectives.scala index 6919d821e2..6ae18a6d57 100644 --- a/akka-http/src/main/scala/akka/http/javadsl/server/directives/HostDirectives.scala +++ b/akka-http/src/main/scala/akka/http/javadsl/server/directives/HostDirectives.scala @@ -5,34 +5,54 @@ package akka.http.javadsl.server package directives -import java.lang.{ Iterable ⇒ JIterable, Boolean ⇒ JBoolean } +import java.lang.{ Iterable ⇒ JIterable } +import java.util.function.{ Function ⇒ JFunction } +import java.util.function.Predicate +import java.util.function.Supplier +import java.util.regex.Pattern -import scala.annotation.varargs import scala.collection.JavaConverters._ -import akka.http.impl.server.RouteStructure.{ GenericHostFilter, HostNameFilter } +import akka.http.javadsl.server.RegexConverters.toScala +import akka.http.scaladsl.server.{ Directives ⇒ D } -abstract class HostDirectives extends FileAndResourceDirectives { +abstract class HostDirectives extends HeaderDirectives { /** - * Rejects all requests with a host name different from the given one. + * Extracts the hostname part of the Host request header value. */ - @varargs - def host(hostName: String, innerRoute: Route, moreInnerRoutes: Route*): Route = - HostNameFilter(hostName :: Nil)(innerRoute, moreInnerRoutes.toList) + def extractHost(inner: JFunction[String, Route]): Route = RouteAdapter { + D.extractHost { host ⇒ inner.apply(host).delegate } + } /** * Rejects all requests with a host name different from the given ones. */ - @varargs - def host(hostNames: JIterable[String], innerRoute: Route, moreInnerRoutes: Route*): Route = - HostNameFilter(hostNames.asScala.toList)(innerRoute, moreInnerRoutes.toList) + def host(hostNames: JIterable[String], inner: Supplier[Route]): Route = RouteAdapter { + D.host(hostNames.asScala.toSeq: _*) { inner.get().delegate } + } + + /** + * Rejects all requests with a host name different from the given one. + */ + def host(hostName: String, inner: Supplier[Route]): Route = RouteAdapter { + D.host(hostName) { inner.get().delegate } + } /** * Rejects all requests for whose host name the given predicate function returns false. */ - @varargs - def host(predicate: akka.japi.function.Function[String, JBoolean], innerRoute: Route, moreInnerRoutes: Route*): Route = - new GenericHostFilter(innerRoute, moreInnerRoutes.toList) { - def filter(hostName: String): Boolean = predicate.apply(hostName) - } + def host(predicate: Predicate[String], inner: Supplier[Route]): Route = RouteAdapter { + D.host(s ⇒ predicate.test(s)) { inner.get().delegate } + } + + /** + * Rejects all requests with a host name that doesn't have a prefix matching the given regular expression. + * For all matching requests the prefix string matching the regex is extracted and passed to the inner route. + * If the regex contains a capturing group only the string matched by this group is extracted. + * If the regex contains more than one capturing group an IllegalArgumentException is thrown. + */ + def host(regex: Pattern, inner: JFunction[String, Route]): Route = RouteAdapter { + D.host(toScala(regex)) { s ⇒ inner.apply(s).delegate } + } + } diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/directives/MarshallingDirectives.scala b/akka-http/src/main/scala/akka/http/javadsl/server/directives/MarshallingDirectives.scala new file mode 100644 index 0000000000..b358173db0 --- /dev/null +++ b/akka-http/src/main/scala/akka/http/javadsl/server/directives/MarshallingDirectives.scala @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2009-2016 Lightbend Inc. + */ +package akka.http.javadsl.server.directives + +import akka.http.javadsl.model.HttpRequest +import akka.http.javadsl.model.HttpEntity +import akka.http.javadsl.server.Route +import akka.http.javadsl.server.Unmarshaller + +import akka.http.scaladsl.server.directives.{ MarshallingDirectives ⇒ D } + +abstract class MarshallingDirectives extends HostDirectives { + /** + * Unmarshalls the request using the given unmarshaller, and passes the result to [inner]. + * If there is a problem with unmarshalling the request is rejected with the [[akka.http.javadsl.server.Rejection]] + * produced by the unmarshaller. + */ + def request[T](unmarshaller: Unmarshaller[_ >: HttpRequest, T], + inner: java.util.function.Function[T, Route]): Route = RouteAdapter { + D.entity(unmarshaller.asScala) { value ⇒ + inner.apply(value).delegate + } + } + + /** + * Unmarshalls the requests entity using the given unmarshaller, and passes the result to [inner]. + * If there is a problem with unmarshalling the request is rejected with the [[akka.http.javadsl.server.Rejection]] + * produced by the unmarshaller. + */ + def entity[T](unmarshaller: Unmarshaller[_ >: HttpEntity, T], + inner: java.util.function.Function[T, Route]): Route = RouteAdapter { + D.entity(Unmarshaller.requestToEntity.flatMap(unmarshaller).asScala) { value ⇒ + inner.apply(value).delegate + } + } + + // If you want the raw entity, use BasicDirectives.extractEntity +} diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/directives/MethodDirectives.scala b/akka-http/src/main/scala/akka/http/javadsl/server/directives/MethodDirectives.scala index 572776186b..adabe62f74 100644 --- a/akka-http/src/main/scala/akka/http/javadsl/server/directives/MethodDirectives.scala +++ b/akka-http/src/main/scala/akka/http/javadsl/server/directives/MethodDirectives.scala @@ -4,42 +4,62 @@ package akka.http.javadsl.server.directives -import akka.http.javadsl.model.{ HttpMethods, HttpMethod } +import java.util.function + +import akka.http.javadsl.model.HttpMethod import akka.http.javadsl.server.Route -import akka.http.impl.server.RouteStructure +import akka.http.impl.util.JavaMapping.Implicits._ +import akka.http.javadsl.RoutingJavaMapping._ -import scala.annotation.varargs +import akka.http.scaladsl.server.directives.{ MethodDirectives ⇒ D } -abstract class MethodDirectives extends HostDirectives { - /** Handles the inner routes if the incoming request is a GET request, rejects the request otherwise */ - @varargs - def get(innerRoute: Route, moreInnerRoutes: Route*): Route = method(HttpMethods.GET, innerRoute, moreInnerRoutes: _*) +abstract class MethodDirectives extends MarshallingDirectives { + def delete(inner: function.Supplier[Route]): Route = RouteAdapter { + D.delete { inner.get.delegate } + } - /** Handles the inner routes if the incoming request is a POST request, rejects the request otherwise */ - @varargs - def post(innerRoute: Route, moreInnerRoutes: Route*): Route = method(HttpMethods.POST, innerRoute, moreInnerRoutes: _*) + def get(inner: function.Supplier[Route]): Route = RouteAdapter { + D.get { inner.get.delegate } + } - /** Handles the inner routes if the incoming request is a PUT request, rejects the request otherwise */ - @varargs - def put(innerRoute: Route, moreInnerRoutes: Route*): Route = method(HttpMethods.PUT, innerRoute, moreInnerRoutes: _*) + def head(inner: function.Supplier[Route]): Route = RouteAdapter { + D.head { inner.get.delegate } + } - /** Handles the inner routes if the incoming request is a DELETE request, rejects the request otherwise */ - @varargs - def delete(innerRoute: Route, moreInnerRoutes: Route*): Route = method(HttpMethods.DELETE, innerRoute, moreInnerRoutes: _*) + def options(inner: function.Supplier[Route]): Route = RouteAdapter { + D.options { inner.get.delegate } + } + def patch(inner: function.Supplier[Route]): Route = RouteAdapter { + D.patch { inner.get.delegate } + } + def post(inner: function.Supplier[Route]): Route = RouteAdapter { + D.post { inner.get.delegate } + } + def put(inner: function.Supplier[Route]): Route = RouteAdapter { + D.put { inner.get.delegate } + } - /** Handles the inner routes if the incoming request is a HEAD request, rejects the request otherwise */ - @varargs - def head(innerRoute: Route, moreInnerRoutes: Route*): Route = method(HttpMethods.HEAD, innerRoute, moreInnerRoutes: _*) + def extractMethod(inner: function.Function[HttpMethod, Route]) = RouteAdapter { + D.extractMethod { m ⇒ + inner.apply(m).delegate + } + } - /** Handles the inner routes if the incoming request is a OPTIONS request, rejects the request otherwise */ - @varargs - def options(innerRoute: Route, moreInnerRoutes: Route*): Route = method(HttpMethods.OPTIONS, innerRoute, moreInnerRoutes: _*) + def method(method: HttpMethod, inner: function.Supplier[Route]): Route = RouteAdapter { + D.method(method.asScala) { inner.get.delegate } + } - /** Handles the inner routes if the incoming request is a PATCH request, rejects the request otherwise */ - @varargs - def patch(innerRoute: Route, moreInnerRoutes: Route*): Route = method(HttpMethods.PATCH, innerRoute, moreInnerRoutes: _*) + /** + * Changes the HTTP method of the request to the value of the specified query string parameter. If the query string + * parameter is not specified this directive has no effect. If the query string is specified as something that is not + * a HTTP method, then this directive completes the request with a `501 Not Implemented` response. + * + * This directive is useful for: + * - Use in combination with JSONP (JSONP only supports GET) + * - Supporting older browsers that lack support for certain HTTP methods. E.g. IE8 does not support PATCH + */ + def overrideMethodWithParameter(paramName: String, inner: function.Supplier[Route]): Route = RouteAdapter { + D.overrideMethodWithParameter(paramName) { inner.get.delegate } + } - /** Handles the inner routes if the incoming request is a request with the given method, rejects the request otherwise */ - @varargs - def method(method: HttpMethod, innerRoute: Route, moreInnerRoutes: Route*): Route = RouteStructure.MethodFilter(method)(innerRoute, moreInnerRoutes.toList) -} \ No newline at end of file +} 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 b505eb55e4..22620214c7 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 @@ -5,43 +5,80 @@ package akka.http.javadsl.server package directives -import java.lang.{ Boolean ⇒ JBoolean } +import java.lang.{ Iterable ⇒ JIterable } +import java.util.function.BooleanSupplier +import java.util.function.{ Function ⇒ JFunction } +import java.util.function.Supplier -import akka.http.impl.server.RouteStructure.{ DynamicDirectiveRoute2, Validated, DynamicDirectiveRoute1 } +import scala.collection.JavaConverters._ -import scala.annotation.varargs -import akka.japi.function.{ Function2, Function } +import akka.http.javadsl.model.RemoteAddress +import akka.http.javadsl.model.headers.Language +import akka.http.impl.util.JavaMapping.Implicits._ +import akka.http.javadsl.RoutingJavaMapping._ + +import akka.http.scaladsl.server.{ Directives ⇒ D } abstract class MiscDirectives extends MethodDirectives { - /** - * Returns a Route which checks the given condition on the request context before running its inner Route. - * If the condition fails the route is rejected with a [[akka.http.scaladsl.server.ValidationRejection]]. - */ - @varargs - def validate(check: Function[RequestContext, JBoolean], errorMsg: String, innerRoute: Route, moreInnerRoutes: Route*): Route = - validate(RequestVals.requestContext, check, errorMsg, innerRoute, moreInnerRoutes: _*) /** - * Returns a Route which checks the given condition before running its inner Route. If the condition fails the - * route is rejected with a [[akka.http.scaladsl.server.ValidationRejection]]. + * Checks the given condition before running its inner route. + * If the condition fails the route is rejected with a [[ValidationRejection]]. */ - @varargs - def validate[T](value: RequestVal[T], check: Function[T, JBoolean], errorMsg: String, innerRoute: Route, moreInnerRoutes: Route*): Route = - new DynamicDirectiveRoute1[T](value)(innerRoute, moreInnerRoutes.toList) { - def createDirective(t1: T): Directive = Directives.custom(Validated(check.apply(t1), errorMsg)) - } + def validate(check: BooleanSupplier, errorMsg: String, inner: Supplier[Route]): Route = RouteAdapter { + D.validate(check.getAsBoolean(), errorMsg) { inner.get.delegate } + } /** - * Returns a Route which checks the given condition before running its inner Route. If the condition fails the - * route is rejected with a [[akka.http.scaladsl.server.ValidationRejection]]. + * Extracts the client's IP from either the X-Forwarded-For, Remote-Address or X-Real-IP header + * (in that order of priority). */ - @varargs - def validate[T1, T2](value1: RequestVal[T1], - value2: RequestVal[T2], - check: Function2[T1, T2, JBoolean], - errorMsg: String, - innerRoute: Route, moreInnerRoutes: Route*): Route = - new DynamicDirectiveRoute2[T1, T2](value1, value2)(innerRoute, moreInnerRoutes.toList) { - def createDirective(t1: T1, t2: T2): Directive = Directives.custom(Validated(check.apply(t1, t2), errorMsg)) + def extractClientIP(inner: JFunction[RemoteAddress, Route]): Route = RouteAdapter { + D.extractClientIP { ip ⇒ inner.apply(ip).delegate } + } + + /** + * Rejects if the request entity is non-empty. + */ + def requestEntityEmpty(inner: Supplier[Route]): Route = RouteAdapter { + D.requestEntityEmpty { inner.get.delegate } + } + + /** + * Rejects with a [[RequestEntityExpectedRejection]] if the request entity is empty. + * Non-empty requests are passed on unchanged to the inner route. + */ + def requestEntityPresent(inner: Supplier[Route]): Route = RouteAdapter { + D.requestEntityPresent { inner.get.delegate } + } + + /** + * Converts responses with an empty entity into (empty) rejections. + * This way you can, for example, have the marshalling of a ''None'' option + * be treated as if the request could not be matched. + */ + def rejectEmptyResponse(inner: Supplier[Route]): Route = RouteAdapter { + D.rejectEmptyResponse { inner.get.delegate } + } + + /** + * Inspects the request's `Accept-Language` header and determines, + * which of the given language alternatives is preferred by the client. + * (See http://tools.ietf.org/html/rfc7231#section-5.3.5 for more details on the + * negotiation logic.) + * If there are several best language alternatives that the client + * has equal preference for (even if this preference is zero!) + * the order of the arguments is used as a tie breaker (First one wins). + * + * If [languages] is empty, the route is rejected. + */ + def selectPreferredLanguage(languages: JIterable[Language], inner: JFunction[Language, Route]): Route = RouteAdapter { + languages.asScala.toList match { + case head :: tail ⇒ + D.selectPreferredLanguage(head.asScala, tail.map(_.asScala).toSeq: _*) { lang ⇒ inner.apply(lang).delegate } + case _ ⇒ + D.reject() } + } + } diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/directives/ParameterDirectives.scala b/akka-http/src/main/scala/akka/http/javadsl/server/directives/ParameterDirectives.scala new file mode 100644 index 0000000000..25e16859c2 --- /dev/null +++ b/akka-http/src/main/scala/akka/http/javadsl/server/directives/ParameterDirectives.scala @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2009-2016 Lightbend Inc. + */ +package akka.http.javadsl.server.directives + +import java.util.{ Map ⇒ JMap, List ⇒ JList } +import java.util.AbstractMap.SimpleImmutableEntry +import java.util.Optional +import java.util.function.{ Function ⇒ JFunction } + +import scala.collection.JavaConverters._ +import scala.compat.java8.OptionConverters._ + +import akka.http.javadsl.server.{ Route, Unmarshaller } +import akka.http.scaladsl.server.directives.{ ParameterDirectives ⇒ D } +import akka.http.scaladsl.server.directives.ParameterDirectives._ +import akka.http.scaladsl.unmarshalling.PredefinedFromStringUnmarshallers._ + +abstract class ParameterDirectives extends MiscDirectives { + + def parameter(name: String, inner: java.util.function.Function[String, Route]): Route = RouteAdapter( + D.parameter(name) { value ⇒ + inner.apply(value).delegate + }) + + @CorrespondsTo("parameter") + def parameterOptional(name: String, inner: java.util.function.Function[Optional[String], Route]): Route = RouteAdapter( + D.parameter(name.?) { value ⇒ + inner.apply(value.asJava).delegate + }) + + @CorrespondsTo("parameterSeq") + def parameterList(name: String, inner: java.util.function.Function[java.util.List[String], Route]): Route = RouteAdapter( + D.parameter(_string2NR(name).*) { values ⇒ + inner.apply(values.toSeq.asJava).delegate + }) + + def parameter[T](t: Unmarshaller[String, T], name: String, inner: java.util.function.Function[T, Route]): Route = { + import t.asScala + RouteAdapter( + D.parameter(name.as[T]) { value ⇒ + inner.apply(value).delegate + }) + } + + @CorrespondsTo("parameter") + def parameterOptional[T](t: Unmarshaller[String, T], name: String, inner: java.util.function.Function[Optional[T], Route]): Route = { + import t.asScala + RouteAdapter( + D.parameter(name.as[T].?) { value ⇒ + inner.apply(value.asJava).delegate + }) + } + + @CorrespondsTo("parameter") + def parameterOrDefault[T](t: Unmarshaller[String, T], defaultValue: T, name: String, inner: java.util.function.Function[T, Route]): Route = { + import t.asScala + RouteAdapter( + D.parameter(name.as[T].?(defaultValue)) { value ⇒ + inner.apply(value).delegate + }) + } + + @CorrespondsTo("parameterSeq") + def parameterList[T](t: Unmarshaller[String, T], name: String, inner: java.util.function.Function[java.util.List[T], Route]): Route = { + import t.asScala + RouteAdapter( + D.parameter(name.as[T].*) { values ⇒ + inner.apply(values.toSeq.asJava).delegate + }) + } + + def parameterMap(inner: JFunction[JMap[String, String], Route]): Route = RouteAdapter { + D.parameterMap { map ⇒ inner.apply(map.asJava).delegate } + } + + def parameterMultiMap(inner: JFunction[JMap[String, JList[String]], Route]): Route = RouteAdapter { + D.parameterMultiMap { map ⇒ inner.apply(map.mapValues { l ⇒ l.asJava }.asJava).delegate } + } + + @CorrespondsTo("parameterSeq") + def parameterList(inner: JFunction[JList[JMap.Entry[String, String]], Route]): Route = RouteAdapter { + D.parameterSeq { list ⇒ + val entries: Seq[JMap.Entry[String, String]] = list.map { e ⇒ new SimpleImmutableEntry(e._1, e._2) } + inner.apply(entries.asJava).delegate + } + } + +} diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/directives/PathDirectives.scala b/akka-http/src/main/scala/akka/http/javadsl/server/directives/PathDirectives.scala index a0b8180de6..695833a593 100644 --- a/akka-http/src/main/scala/akka/http/javadsl/server/directives/PathDirectives.scala +++ b/akka-http/src/main/scala/akka/http/javadsl/server/directives/PathDirectives.scala @@ -1,112 +1,42 @@ /* * Copyright (C) 2009-2016 Lightbend Inc. */ +package akka.http.javadsl.server.directives -package akka.http.javadsl.server -package directives +import java.util.function.BiFunction +import java.util.function.{ Function ⇒ JFunction } +import java.util.function.Supplier + +import scala.util.Failure +import scala.util.Success -import akka.http.impl.server.RouteStructure -import akka.http.impl.server.RouteStructure.{ RedirectToNoTrailingSlashIfPresent, RedirectToTrailingSlashIfMissing } import akka.http.javadsl.model.StatusCode -import akka.http.javadsl.server.values.{ PathMatchers, PathMatcher } +import akka.http.javadsl.server.PathMatcher0 +import akka.http.javadsl.server.PathMatcher1 +import akka.http.javadsl.server.PathMatcher2 +import akka.http.javadsl.server.Route +import akka.http.javadsl.server.Unmarshaller +import akka.http.scaladsl.model.StatusCodes.Redirection +import akka.http.scaladsl.server.{ Directives ⇒ D } -import scala.annotation.varargs -import scala.collection.immutable +import akka.http.scaladsl.server.PathMatchers -abstract class PathDirectives extends MiscDirectives { - /** - * Applies the given PathMatchers to the remaining unmatched path after consuming a leading slash. - * The matcher has to match the remaining path completely. - * If matched the value extracted by the PathMatchers is extracted on the directive level. - * - * Each of Each of the arguments s must either be an instance of [[akka.http.javadsl.server.values.PathMatcher]] or a constant String - * that will be automatically converted using `PathMatcher.segment`. - */ - @varargs - def path(matchers: AnyRef*): Directive = - RawPathPrefixForMatchers(joinWithSlash(convertMatchers(matchers)) :+ PathMatchers.END) - - /** - * Applies the given PathMatchers to a prefix of the remaining unmatched path after consuming a leading slash. - * The matcher has to match a prefix of the remaining path. - * If matched the value extracted by the PathMatcher is extracted on the directive level. - * - * Each of the arguments must either be an instance of [[akka.http.javadsl.server.values.PathMatcher]] or a constant String - * that will be automatically converted using `PathMatcher.segment`. - */ - @varargs - def pathPrefix(matchers: AnyRef*): Directive = - RawPathPrefixForMatchers(joinWithSlash(convertMatchers(matchers))) - - /** - * Checks whether the unmatchedPath of the [[akka.http.javadsl.server.RequestContext]] has a prefix matched by the - * given PathMatcher. In analogy to the `pathPrefix` directive a leading slash is implied. - * - * Each of the arguments must either be an instance of [[akka.http.javadsl.server.values.PathMatcher]] or a constant String - * that will be automatically converted using `PathMatcher.segment`. - */ - @varargs - def pathPrefixTest(matchers: AnyRef*): Directive = - RawPathPrefixTestForMatchers(joinWithSlash(convertMatchers(matchers))) - - /** - * Applies the given matcher directly to a prefix of the unmatched path of the - * [[akka.http.javadsl.server.RequestContext]] (i.e. without implicitly consuming a leading slash). - * The matcher has to match a prefix of the remaining path. - * If matched the value extracted by the PathMatcher is extracted on the directive level. - * - * Each of the arguments must either be an instance of [[akka.http.javadsl.server.values.PathMatcher]] or a constant String - * that will be automatically converted using `PathMatcher.segment`. - */ - @varargs - def rawPathPrefix(matchers: AnyRef*): Directive = - RawPathPrefixForMatchers(convertMatchers(matchers)) - - /** - * Checks whether the unmatchedPath of the [[akka.http.javadsl.server.RequestContext]] has a prefix matched by the - * given PathMatcher. However, as opposed to the `pathPrefix` directive the matched path is not - * actually "consumed". - * - * Each of the arguments must either be an instance of [[akka.http.javadsl.server.values.PathMatcher]] or a constant String - * that will be automatically converted using `PathMatcher.segment`. - */ - @varargs - def rawPathPrefixTest(matchers: AnyRef*): Directive = - RawPathPrefixTestForMatchers(convertMatchers(matchers)) - - /** - * Applies the given PathMatchers to a suffix of the remaining unmatchedPath of the [[akka.http.javadsl.server.RequestContext]]. - * If matched the value extracted by the PathMatchers is extracted and the matched parts of the path are consumed. - * Note that, for efficiency reasons, the given PathMatchers must match the desired suffix in reversed-segment - * order, i.e. `pathSuffix("baz" / "bar")` would match `/foo/bar/baz`! - * - * Each of the arguments must either be an instance of [[akka.http.javadsl.server.values.PathMatcher]] or a constant String - * that will be automatically converted using `PathMatcher.segment`. - */ - @varargs - def pathSuffix(matchers: AnyRef*): Directive = - Directives.custom(RouteStructure.PathSuffix(convertMatchers(matchers))) - - /** - * Checks whether the unmatchedPath of the [[akka.http.javadsl.server.RequestContext]] has a suffix matched by the - * given PathMatcher. However, as opposed to the pathSuffix directive the matched path is not - * actually "consumed". - * Note that, for efficiency reasons, the given PathMatcher must match the desired suffix in reversed-segment - * order, i.e. `pathSuffixTest("baz" / "bar")` would match `/foo/bar/baz`! - * - * Each of the arguments must either be an instance of [[akka.http.javadsl.server.values.PathMatcher]] or a constant String - * that will be automatically converted using `PathMatcher.segment`. - */ - @varargs - def pathSuffixTest(matchers: AnyRef*): Directive = - Directives.custom(RouteStructure.PathSuffixTest(convertMatchers(matchers))) +/** + * Only path prefixes are matched by these methods, since any kind of chaining path extractions + * in Java would just look cumbersome without operator overloads; hence, no PathMatcher for Java. + * + * Just nest path() directives with the required types, ending in pathEnd() if you want to fail + * further paths. + */ +abstract class PathDirectives extends ParameterDirectives { /** * Rejects the request if the unmatchedPath of the [[akka.http.javadsl.server.RequestContext]] is non-empty, * or said differently: only passes on the request to its inner route if the request path * has been matched completely. */ - def pathEnd: Directive = RawPathPrefixForMatchers(PathMatchers.END :: Nil) + def pathEnd(inner: Supplier[Route]): Route = RouteAdapter( + D.pathEnd { inner.get.delegate }) /** * Only passes on the request to its inner route if the request path has been matched @@ -135,52 +65,225 @@ abstract class PathDirectives extends MiscDirectives { * * For further information, refer to: http://googlewebmastercentral.blogspot.de/2010/04/to-slash-or-not-to-slash.html */ - def pathEndOrSingleSlash: Directive = RawPathPrefixForMatchers(List(PathMatchers.SLASH.optional, PathMatchers.END)) + def pathEndOrSingleSlash(inner: Supplier[Route]): Route = RouteAdapter { + D.pathEndOrSingleSlash { inner.get.delegate } + } /** * Only passes on the request to its inner route if the request path * consists of exactly one remaining slash. */ - def pathSingleSlash: Directive = RawPathPrefixForMatchers(List(PathMatchers.SLASH, PathMatchers.END)) + def pathSingleSlash(inner: Supplier[Route]): Route = RouteAdapter { + D.pathSingleSlash { inner.get.delegate } + } + + /** + * Matches a prefix to the remaining unmatched path after consuming a leading slash. + * The matcher has to match the remaining path completely. + * If matched the value matching the prefix is extracted on the directive level. + */ + def path(segment: String, inner: Supplier[Route]): Route = RouteAdapter { + D.path(segment) { inner.get.delegate } + } + def path(inner: java.util.function.Function[String, Route]): Route = RouteAdapter { + D.path(PathMatchers.Segment) { element ⇒ inner.apply(element).delegate } + } + + /** + * Applies the given [[PathMatcher0]] to the remaining unmatched path after consuming a leading slash. + * The matcher has to match the remaining path completely. + * If matched the value extracted by the [[PathMatcher0]] is extracted on the directive level. + */ + def path(p: PathMatcher0, inner: Supplier[Route]): Route = RouteAdapter { + D.path(p.toScala) { inner.get.delegate } + } + def path[T](p: PathMatcher1[T], inner: JFunction[T, Route]): Route = RouteAdapter { + D.path(p.toScala) { t1 ⇒ inner.apply(t1).delegate } + } + def path[T1, T2](p: PathMatcher2[T1, T2], inner: BiFunction[T1, T2, Route]): Route = RouteAdapter { + D.path(p.toScala) { (t1, t2) ⇒ inner.apply(t1, t2).delegate } + } + + /** + * Matches a prefix to the remaining unmatched path after consuming a leading slash. + * The matcher has to match a prefix of the remaining path. + * If matched the value matching the prefix is extracted on the directive level. + */ + def pathPrefix(segment: String, inner: Supplier[Route]): Route = RouteAdapter { + D.pathPrefix(segment) { inner.get.delegate } + } + def pathPrefix(inner: java.util.function.Function[String, Route]): Route = RouteAdapter { + D.pathPrefix(PathMatchers.Segment) { element ⇒ inner.apply(element).delegate } + } + + /** + * Applies the given [[PathMatcher0]] to the remaining unmatched path after consuming a leading slash. + * The matcher has to match a prefix of the remaining path. + * If matched the value extracted by the PathMatcher is extracted on the directive level. + */ + def pathPrefix(p: PathMatcher0, inner: Supplier[Route]): Route = RouteAdapter { + D.pathPrefix(p.toScala) { inner.get.delegate } + } + def pathPrefix[T](p: PathMatcher1[T], inner: JFunction[T, Route]): Route = RouteAdapter { + D.pathPrefix(p.toScala) { t1 ⇒ inner.apply(t1).delegate } + } + def pathPrefix[T1, T2](p: PathMatcher2[T1, T2], inner: BiFunction[T1, T2, Route]): Route = RouteAdapter { + D.pathPrefix(p.toScala) { (t1, t2) ⇒ inner.apply(t1, t2).delegate } + } + + /** + * Applies the given matcher directly to a prefix of the unmatched path of the + * [[akka.http.javadsl.server.RequestContext]] (i.e. without implicitly consuming a leading slash). + * The matcher has to match a prefix of the remaining path. + * If matched the value extracted by the PathMatcher is extracted on the directive level. + */ + def rawPathPrefix(segment: String, inner: Supplier[Route]): Route = RouteAdapter { + D.rawPathPrefix(segment) { inner.get().delegate } + } + def rawPathPrefix(pm: PathMatcher0, inner: Supplier[Route]): Route = RouteAdapter { + D.rawPathPrefix(pm.toScala) { inner.get().delegate } + } + def rawPathPrefix[T1](pm: PathMatcher1[T1], inner: Function[T1, Route]): Route = RouteAdapter { + D.rawPathPrefix(pm.toScala) { t1 ⇒ inner.apply(t1).delegate } + } + def rawPathPrefix[T1, T2](pm: PathMatcher2[T1, T2], inner: BiFunction[T1, T2, Route]): Route = RouteAdapter { + D.rawPathPrefix(pm.toScala) { case (t1, t2) ⇒ inner.apply(t1, t2).delegate } + } + + /** + * Checks whether the unmatchedPath of the [[akka.http.javadsl.server.RequestContext]] has a prefix matched by the + * given PathMatcher. In analogy to the `pathPrefix` directive a leading slash is implied. + */ + def pathPrefixTest(segment: String, inner: Supplier[Route]): Route = RouteAdapter { + D.pathPrefixTest(segment) { inner.get().delegate } + } + def pathPrefixTest(pm: PathMatcher0, inner: Supplier[Route]): Route = RouteAdapter { + D.pathPrefixTest(pm.toScala) { inner.get().delegate } + } + def pathPrefixTest[T1](pm: PathMatcher1[T1], inner: Function[T1, Route]): Route = RouteAdapter { + D.pathPrefixTest(pm.toScala) { t1 ⇒ inner.apply(t1).delegate } + } + def pathPrefixTest[T1, T2](pm: PathMatcher2[T1, T2], inner: BiFunction[T1, T2, Route]): Route = RouteAdapter { + D.pathPrefixTest(pm.toScala) { case (t1, t2) ⇒ inner.apply(t1, t2).delegate } + } + + /** + * Checks whether the unmatchedPath of the [[akka.http.javadsl.server.RequestContext]] has a prefix matched by the + * given PathMatcher. However, as opposed to the `pathPrefix` directive the matched path is not + * actually "consumed". + */ + def rawPathPrefixTest(segment: String, inner: Supplier[Route]): Route = RouteAdapter { + D.rawPathPrefixTest(segment) { inner.get().delegate } + } + def rawPathPrefixTest(pm: PathMatcher0, inner: Supplier[Route]): Route = RouteAdapter { + D.rawPathPrefixTest(pm.toScala) { inner.get().delegate } + } + def rawPathPrefixTest[T1](pm: PathMatcher1[T1], inner: Function[T1, Route]): Route = RouteAdapter { + D.rawPathPrefixTest(pm.toScala) { t1 ⇒ inner.apply(t1).delegate } + } + def rawPathPrefixTest[T1, T2](pm: PathMatcher2[T1, T2], inner: BiFunction[T1, T2, Route]): Route = RouteAdapter { + D.rawPathPrefixTest(pm.toScala) { case (t1, t2) ⇒ inner.apply(t1, t2).delegate } + } + + /** + * Applies the given [[akka.http.scaladsl.server.PathMatcher]] to a suffix of the remaining unmatchedPath of the [[akka.http.javadsl.server.RequestContext]]. + * If matched the value extracted by the [[akka.http.javadsl.server.PathMatcher0]] is extracted and the matched parts of the path are consumed. + * Note that, for efficiency reasons, the given [[akka.http.javadsl.server.PathMatcher0]] must match the desired suffix in reversed-segment + * order, i.e. `pathSuffix("baz" / "bar")` would match `/foo/bar/baz`! + */ + def pathSuffix(segment: String, inner: Supplier[Route]): Route = RouteAdapter { + D.pathSuffix(segment) { inner.get().delegate } + } + def pathSuffix(pm: PathMatcher0, inner: Supplier[Route]): Route = RouteAdapter { + D.pathSuffix(pm.toScala) { inner.get().delegate } + } + def pathSuffix[T1](pm: PathMatcher1[T1], inner: Function[T1, Route]): Route = RouteAdapter { + D.pathSuffix(pm.toScala) { t1 ⇒ inner.apply(t1).delegate } + } + def pathSuffix[T1, T2](pm: PathMatcher2[T1, T2], inner: BiFunction[T1, T2, Route]): Route = RouteAdapter { + D.pathSuffix(pm.toScala) { case (t1, t2) ⇒ inner.apply(t1, t2).delegate } + } + + /** + * Checks whether the unmatchedPath of the [[akka.http.javadsl.server.RequestContext]] has a suffix matched by the + * given PathMatcher. However, as opposed to the pathSuffix directive the matched path is not + * actually "consumed". + * Note that, for efficiency reasons, the given PathMatcher must match the desired suffix in reversed-segment + * order, i.e. `pathSuffixTest("baz" / "bar")` would match `/foo/bar/baz`! + */ + def pathSuffixTest(segment: String, inner: Supplier[Route]): Route = RouteAdapter { + D.pathSuffixTest(segment) { inner.get().delegate } + } + def pathSuffixTest(pm: PathMatcher0, inner: Supplier[Route]): Route = RouteAdapter { + D.pathSuffixTest(pm.toScala) { inner.get().delegate } + } + def pathSuffixTest[T1](pm: PathMatcher1[T1], inner: Function[T1, Route]): Route = RouteAdapter { + D.pathSuffixTest(pm.toScala) { t1 ⇒ inner.apply(t1).delegate } + } + def pathSuffixTest[T1, T2](pm: PathMatcher2[T1, T2], inner: BiFunction[T1, T2, Route]): Route = RouteAdapter { + D.pathSuffixTest(pm.toScala) { case (t1, t2) ⇒ inner.apply(t1, t2).delegate } + } /** * If the request path doesn't end with a slash, redirect to the same uri with trailing slash in the path. * * '''Caveat''': [[#path]] without trailing slash and [[#pathEnd]] directives will not match inside of this directive. + * + * @param redirectionType A status code from StatusCodes, which must be a redirection type. */ - @varargs - def redirectToTrailingSlashIfMissing(redirectionStatusCode: StatusCode, innerRoute: Route, moreInnerRoutes: Route*): Route = - RedirectToTrailingSlashIfMissing(redirectionStatusCode)(innerRoute, moreInnerRoutes.toList) + def redirectToTrailingSlashIfMissing(redirectionType: StatusCode, inner: Supplier[Route]): Route = RouteAdapter { + redirectionType match { + case r: Redirection ⇒ D.redirectToTrailingSlashIfMissing(r) { inner.get().delegate } + case _ ⇒ throw new IllegalArgumentException("Not a valid redirection status code: " + redirectionType) + } + } /** * If the request path ends with a slash, redirect to the same uri without trailing slash in the path. * * '''Caveat''': [[#pathSingleSlash]] directive will not match inside of this directive. + * + * @param redirectionType A status code from StatusCodes, which must be a redirection type. */ - @varargs - def redirectToNoTrailingSlashIfPresent(redirectionStatusCode: StatusCode, innerRoute: Route, moreInnerRoutes: Route*): Route = - RedirectToNoTrailingSlashIfPresent(redirectionStatusCode)(innerRoute, moreInnerRoutes.toList) - - private def RawPathPrefixForMatchers(matchers: immutable.Seq[PathMatcher[_]]): Directive = - Directives.custom(RouteStructure.RawPathPrefix(matchers)) - - private def RawPathPrefixTestForMatchers(matchers: immutable.Seq[PathMatcher[_]]): Directive = - Directives.custom(RouteStructure.RawPathPrefixTest(matchers)) - - private def joinWithSlash(matchers: immutable.Seq[PathMatcher[_]]): immutable.Seq[PathMatcher[_]] = { - def join(result: immutable.Seq[PathMatcher[_]], next: PathMatcher[_]): immutable.Seq[PathMatcher[_]] = - result :+ PathMatchers.SLASH :+ next - - matchers.foldLeft(immutable.Seq.empty[PathMatcher[_]])(join) - } - - private def convertMatchers(matchers: Seq[AnyRef]): immutable.Seq[PathMatcher[_]] = { - def parse(matcher: AnyRef): PathMatcher[_] = matcher match { - case p: PathMatcher[_] ⇒ p - case name: String ⇒ PathMatchers.segment(name) - case x ⇒ throw new IllegalArgumentException(s"Matcher of class ${x.getClass} is unsupported for PathDirectives") + def redirectToNoTrailingSlashIfPresent(redirectionType: StatusCode, inner: Supplier[Route]): Route = RouteAdapter { + redirectionType match { + case r: Redirection ⇒ D.redirectToNoTrailingSlashIfPresent(r) { inner.get().delegate } + case _ ⇒ throw new IllegalArgumentException("Not a valid redirection status code: " + redirectionType) } - - matchers.map(parse).toVector } -} \ No newline at end of file + + //------ extra java-specific methods + + // Java-specific since there's no Java API to create custom PathMatchers. And that's because there's no Path model in Java. + /** + * Consumes a leading slash and extracts the next path segment, unmarshalling it and passing the result to the inner function. + */ + def pathPrefix[T](t: Unmarshaller[String, T], inner: java.util.function.Function[T, Route]): Route = RouteAdapter { + D.pathPrefix(PathMatchers.Segment)(unmarshal(t, inner)) + } + + /** + * Consumes a leading slash and extracts the next path segment, unmarshalling it and passing the result to the inner function, + * expecting the full path to have been consumed then. + */ + def path[T](t: Unmarshaller[String, T], inner: java.util.function.Function[T, Route]): Route = RouteAdapter { + D.path(PathMatchers.Segment)(unmarshal(t, inner)) + } + + private def unmarshal[T](t: Unmarshaller[String, T], inner: java.util.function.Function[T, Route]) = { element: String ⇒ + D.extractRequestContext { ctx ⇒ + import ctx.executionContext + import ctx.materializer + + D.onComplete(t.asScala.apply(element)) { + case Success(value) ⇒ + inner.apply(value).delegate + case Failure(x: IllegalArgumentException) ⇒ + D.reject() + case Failure(x) ⇒ + D.failWith(x) + } + } + } +} + diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/directives/RangeDirectives.scala b/akka-http/src/main/scala/akka/http/javadsl/server/directives/RangeDirectives.scala index d56fe442a8..59d65c0bac 100644 --- a/akka-http/src/main/scala/akka/http/javadsl/server/directives/RangeDirectives.scala +++ b/akka-http/src/main/scala/akka/http/javadsl/server/directives/RangeDirectives.scala @@ -5,9 +5,8 @@ package akka.http.javadsl.server package directives -import akka.http.impl.server.RouteStructure.RangeSupport - -import scala.annotation.varargs +import java.util.function.Supplier +import akka.http.scaladsl.server.{ Directives ⇒ D } abstract class RangeDirectives extends PathDirectives { /** @@ -24,5 +23,7 @@ abstract class RangeDirectives extends PathDirectives { * * For more information, see: https://tools.ietf.org/html/rfc7233 */ - @varargs def withRangeSupport(innerRoute: Route, moreInnerRoutes: Route*): Route = RangeSupport()(innerRoute, moreInnerRoutes.toList) + def withRangeSupport(inner: Supplier[Route]): Route = RouteAdapter { + D.withRangeSupport { inner.get.delegate } + } } diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/directives/RespondWithDirectives.scala b/akka-http/src/main/scala/akka/http/javadsl/server/directives/RespondWithDirectives.scala new file mode 100644 index 0000000000..90809d489c --- /dev/null +++ b/akka-http/src/main/scala/akka/http/javadsl/server/directives/RespondWithDirectives.scala @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2009-2016 Lightbend Inc. + */ +package akka.http.javadsl.server.directives + +import java.lang.{ Iterable ⇒ JIterable } +import java.util.function.Supplier +import java.util.{ List ⇒ JList } + +import akka.http.javadsl.model.HttpHeader +import akka.http.javadsl.server.Route +import akka.http.scaladsl.server.{ Directives ⇒ D } + +abstract class RespondWithDirectives extends RangeDirectives { + import akka.http.impl.util.JavaMapping.Implicits._ + + /** + * Unconditionally adds the given response header to all HTTP responses of its inner Route. + */ + def respondWithHeader(responseHeader: HttpHeader, inner: Supplier[Route]): Route = RouteAdapter { + D.respondWithHeader(responseHeader.asScala) { inner.get.delegate } + } + + /** + * Adds the given response header to all HTTP responses of its inner Route, + * if the response from the inner Route doesn't already contain a header with the same name. + */ + def respondWithDefaultHeader(responseHeader: HttpHeader, inner: Supplier[Route]): Route = RouteAdapter { + D.respondWithDefaultHeader(responseHeader.asScala) { inner.get.delegate } + } + + /** + * Unconditionally adds the given response headers to all HTTP responses of its inner Route. + */ + def respondWithHeaders(responseHeaders: JIterable[HttpHeader], inner: Supplier[Route]): Route = RouteAdapter { + D.respondWithHeaders(responseHeaders.asScala) { inner.get.delegate } + } + + /** + * Adds the given response headers to all HTTP responses of its inner Route, + * if a header already exists it is not added again. + */ + def respondWithDefaultHeaders(responseHeaders: JIterable[HttpHeader], inner: Supplier[Route]): Route = RouteAdapter { + D.respondWithDefaultHeaders(responseHeaders.asScala.toVector) { inner.get.delegate } + } + +} \ No newline at end of file diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/directives/RouteAdapter.scala b/akka-http/src/main/scala/akka/http/javadsl/server/directives/RouteAdapter.scala new file mode 100644 index 0000000000..7bcb7ccf2e --- /dev/null +++ b/akka-http/src/main/scala/akka/http/javadsl/server/directives/RouteAdapter.scala @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2009-2016 Lightbend Inc. + */ +package akka.http.javadsl.server.directives + +import akka.NotUsed +import akka.actor.ActorSystem +import akka.http.javadsl.model.HttpRequest +import akka.http.javadsl.model.HttpResponse +import akka.http.impl.util.JavaMapping.Implicits._ +import akka.http.javadsl.RoutingJavaMapping._ +import akka.http.javadsl.server.{ ExceptionHandler, RejectionHandler, Route } +import akka.http.javadsl.settings.{ ParserSettings, RoutingSettings } +import akka.http.scaladsl +import akka.http.scaladsl.server.RouteConcatenation._ +import akka.stream.{ Materializer, javadsl } +import akka.stream.scaladsl.Flow + +/** INTERNAL API */ +final class RouteAdapter(val delegate: akka.http.scaladsl.server.Route) extends Route { + import RouteAdapter._ + + override def flow(system: ActorSystem, materializer: Materializer): javadsl.Flow[HttpRequest, HttpResponse, NotUsed] = + scalaFlow(system, materializer).asJava + + private def scalaFlow(system: ActorSystem, materializer: Materializer): Flow[HttpRequest, HttpResponse, NotUsed] = { + implicit val s = system + implicit val m = materializer + Flow[HttpRequest].map(_.asScala).via(delegate).map(_.asJava) + } + + override def orElse(alternative: Route): Route = + alternative match { + case adapt: RouteAdapter ⇒ + RouteAdapter(delegate ~ adapt.delegate) + } + + override def seal(system: ActorSystem, materializer: Materializer): Route = { + implicit val s = system + implicit val m = materializer + + RouteAdapter(scaladsl.server.Route.seal(delegate)) + } + + override def seal(routingSettings: RoutingSettings, parserSettings: ParserSettings, rejectionHandler: RejectionHandler, exceptionHandler: ExceptionHandler, system: ActorSystem, materializer: Materializer): Route = { + implicit val s = system + implicit val m = materializer + + RouteAdapter(scaladsl.server.Route.seal(delegate)( + routingSettings.asScala, + parserSettings.asScala, + rejectionHandler.asScala, + exceptionHandler.asScala)) + } + + override def toString = s"akka.http.javadsl.server.Route($delegate)" + +} + +object RouteAdapter { + def apply(delegate: akka.http.scaladsl.server.Route) = new RouteAdapter(delegate) +} \ No newline at end of file diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/directives/RouteDirectives.scala b/akka-http/src/main/scala/akka/http/javadsl/server/directives/RouteDirectives.scala new file mode 100644 index 0000000000..2a0af6144f --- /dev/null +++ b/akka-http/src/main/scala/akka/http/javadsl/server/directives/RouteDirectives.scala @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2009-2016 Lightbend Inc. + */ +package akka.http.javadsl.server.directives + +import java.util.concurrent.CompletionStage + +import akka.dispatch.ExecutionContexts +import akka.http.javadsl.RoutingJavaMapping +import akka.http.scaladsl.server._ +import akka.japi.Util + +import scala.collection.immutable.Seq +import scala.annotation.varargs +import scala.collection.JavaConverters._ + +import akka.http.impl.model.JavaUri +import akka.http.javadsl.model.HttpHeader +import akka.http.javadsl.model.HttpResponse +import akka.http.javadsl.model.RequestEntity +import akka.http.javadsl.model.StatusCode +import akka.http.javadsl.model.Uri +import akka.http.javadsl.server.{ Rejection, Marshaller, Route } +import akka.http.scaladsl +import akka.http.scaladsl.marshalling.Marshaller._ +import akka.http.scaladsl.marshalling.ToResponseMarshallable +import akka.http.scaladsl.model.StatusCodes.Redirection +import akka.http.javadsl.RoutingJavaMapping._ +import akka.http.scaladsl.server.directives.{ RouteDirectives ⇒ D } +import akka.http.javadsl.server.Rejection + +import akka.http.scaladsl.util.FastFuture._ + +abstract class RouteDirectives extends RespondWithDirectives { + import RoutingJavaMapping.Implicits._ + + // Don't try this at home – we only use it here for the java -> scala conversions + private implicit val conversionExecutionContext = ExecutionContexts.sameThreadExecutionContext + + /** + * Java-specific call added so you can chain together multiple alternate routes using comma, + * rather than having to explicitly call route1.orElse(route2).orElse(route3). + */ + @CorrespondsTo("concat") + @varargs def route(alternatives: Route*): Route = RouteAdapter { + import akka.http.scaladsl.server.Directives._ + + alternatives.map(_.delegate).reduce(_ ~ _) + } + + /** + * Rejects the request with the given rejections, or with an empty set of rejections if no rejections are given. + */ + @varargs def reject(rejection: Rejection, rejections: Rejection*): Route = RouteAdapter { + D.reject((rejection +: rejections).map(_.asScala): _*) + } + + /** + * Rejects the request with an empty rejection (usualy used for "no directive matched"). + */ + def reject(): Route = RouteAdapter { + D.reject() + } + + /** + * Completes the request with redirection response of the given type to the given URI. + * + * @param redirectionType A status code from StatusCodes, which must be a redirection type. + */ + def redirect(uri: Uri, redirectionType: StatusCode): Route = RouteAdapter { + redirectionType match { + case r: Redirection ⇒ D.redirect(uri.asInstanceOf[JavaUri].uri, r) + case _ ⇒ throw new IllegalArgumentException("Not a valid redirection status code: " + redirectionType) + } + } + + /** + * Bubbles the given error up the response chain, where it is dealt with by the closest `handleExceptions` + * directive and its ExceptionHandler. + */ + def failWith(error: Throwable): Route = RouteAdapter(D.failWith(error)) + + /** + * Completes the request using an HTTP 200 OK status code and the given body as UTF-8 entity. + */ + def complete(body: String): Route = RouteAdapter( + D.complete(body)) + + /** + * Completes the request using the given http response. + */ + def complete(response: HttpResponse): Route = RouteAdapter( + D.complete(response.asScala)) + + /** + * Completes the request using the given status code. + */ + def complete(status: StatusCode): Route = RouteAdapter( + D.complete(status.asScala)) + + /** + * Completes the request by marshalling the given value into an http response. + */ + def complete[T](value: T, marshaller: Marshaller[T, HttpResponse]) = RouteAdapter { + D.complete(ToResponseMarshallable(value)(marshaller)) + } + + /** + * Completes the request using the given status code and headers, marshalling the given value as response entity. + */ + def complete[T](status: StatusCode, headers: java.lang.Iterable[HttpHeader], value: T, marshaller: Marshaller[T, RequestEntity]) = RouteAdapter { + D.complete(ToResponseMarshallable(value)(fromToEntityMarshaller(status.asScala, Util.immutableSeq(headers).map(_.asScala))(marshaller))) // TODO avoid the map() + } + + /** + * Completes the request using the given status code, headers, and response entity. + */ + def complete(status: StatusCode, headers: java.lang.Iterable[HttpHeader], entity: RequestEntity) = RouteAdapter { + D.complete(scaladsl.model.HttpResponse(status = status.asScala, entity = entity.asScala, headers = Util.immutableSeq(headers).map(_.asScala))) // TODO avoid the map() + } + + /** + * Completes the request using the given status code, marshalling the given value as response entity. + */ + def complete[T](status: StatusCode, value: T, marshaller: Marshaller[T, RequestEntity]) = RouteAdapter { + D.complete(ToResponseMarshallable(value)(fromToEntityMarshaller(status.asScala)(marshaller))) + } + + /** + * Completes the request using the given status code and response entity. + */ + def complete(status: StatusCode, entity: RequestEntity) = RouteAdapter { + D.complete(scaladsl.model.HttpResponse(status = status.asScala, entity = entity.asScala)) + } + + /** + * Completes the request using the given status code and the given body as UTF-8. + */ + def complete(status: StatusCode, entity: String) = RouteAdapter { + D.complete(scaladsl.model.HttpResponse(status = status.asScala, entity = entity)) + } + + /** + * Completes the request as HTTP 200 OK, adding the given headers, and marshalling the given value as response entity. + */ + def complete[T](headers: java.lang.Iterable[HttpHeader], value: T, marshaller: Marshaller[T, RequestEntity]) = RouteAdapter { + D.complete(ToResponseMarshallable(value)(fromToEntityMarshaller(headers = Util.immutableSeq(headers).map(_.asScala))(marshaller))) // TODO can we avoid the map() ? + } + + /** + * Completes the request as HTTP 200 OK, adding the given headers and response entity. + */ + def complete(headers: java.lang.Iterable[HttpHeader], entity: RequestEntity) = RouteAdapter { + D.complete(scaladsl.model.HttpResponse(headers = headers.asScala.toVector.map(_.asScala), entity = entity.asScala)) // TODO can we avoid the map() ? + } + + /** + * Completes the request as HTTP 200 OK, marshalling the given value as response entity. + */ + def completeOK[T](value: T, marshaller: Marshaller[T, RequestEntity]) = RouteAdapter { + D.complete(ToResponseMarshallable(value)(fromToEntityMarshaller()(marshaller))) + } + + /** + * Completes the request as HTTP 200 OK with the given value as response entity. + */ + def complete(entity: RequestEntity) = RouteAdapter { + D.complete(scaladsl.model.HttpResponse(entity = entity.asScala)) + } + + // --- manual "magnet" for Scala Future --- + + /** + * Completes the request by marshalling the given future value into an http response. + */ + @CorrespondsTo("complete") + def completeWithFutureResponse(value: scala.concurrent.Future[HttpResponse]) = RouteAdapter { + D.complete(value.fast.map(_.asScala)) + } + + /** + * Completes the request by marshalling the given future value into an http response. + */ + @CorrespondsTo("complete") + def completeWithFutureString(value: scala.concurrent.Future[String]) = RouteAdapter { + D.complete(value) + } + + /** + * Completes the request using the given future status code. + */ + @CorrespondsTo("complete") + def completeWithFutureStatus(status: scala.concurrent.Future[StatusCode]): Route = RouteAdapter { + D.complete(status.fast.map(_.asScala)) + } + + /** + * Completes the request by marshalling the given value into an http response. + */ + @CorrespondsTo("complete") + def completeWithFuture[T](value: scala.concurrent.Future[T], marshaller: Marshaller[T, HttpResponse]) = RouteAdapter { + D.complete(value.fast.map(v ⇒ ToResponseMarshallable(v)(marshaller))) + } + + // --- manual "magnet" for CompletionStage --- + + /** + * Completes the request by marshalling the given future value into an http response. + */ + @CorrespondsTo("complete") + def completeWithFutureResponse(value: CompletionStage[HttpResponse]) = RouteAdapter { + D.complete(value.asScala.fast.map(_.asScala)) + } + + /** + * Completes the request by marshalling the given future value into an http response. + */ + @CorrespondsTo("complete") + def completeWithFutureString(value: CompletionStage[String]) = RouteAdapter { + D.complete(value.asScala) + } + + /** + * Completes the request using the given future status code. + */ + @CorrespondsTo("complete") + def completeWithFutureStatus(status: CompletionStage[StatusCode]): Route = RouteAdapter { + D.complete(status.asScala.fast.map(_.asScala)) + } + + /** + * Completes the request by marshalling the given value into an http response. + */ + @CorrespondsTo("complete") + def completeWithFuture[T](value: CompletionStage[T], marshaller: Marshaller[T, HttpResponse]) = RouteAdapter { + D.complete(value.asScala.fast.map(v ⇒ ToResponseMarshallable(v)(marshaller))) + } + +} diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/directives/SchemeDirectives.scala b/akka-http/src/main/scala/akka/http/javadsl/server/directives/SchemeDirectives.scala index 4c05bfa318..97ff656177 100644 --- a/akka-http/src/main/scala/akka/http/javadsl/server/directives/SchemeDirectives.scala +++ b/akka-http/src/main/scala/akka/http/javadsl/server/directives/SchemeDirectives.scala @@ -4,15 +4,24 @@ package akka.http.javadsl.server.directives -import akka.http.impl.server.RouteStructure.SchemeFilter +import java.util.function.{ Function ⇒ JFunction } +import java.util.function.Supplier + import akka.http.javadsl.server.Route +import akka.http.scaladsl.server.{ Directives ⇒ D } -import scala.annotation.varargs +abstract class SchemeDirectives extends RouteDirectives { + /** + * Extracts the Uri scheme from the request. + */ + def extractScheme(inner: JFunction[String, Route]): Route = RouteAdapter { + D.extractScheme { s ⇒ inner.apply(s).delegate } + } -abstract class SchemeDirectives extends RangeDirectives { /** * Rejects all requests whose Uri scheme does not match the given one. */ - @varargs - def scheme(scheme: String, innerRoute: Route, moreInnerRoutes: Route*): Route = SchemeFilter(scheme)(innerRoute, moreInnerRoutes.toList) + def scheme(name: String, inner: Supplier[Route]): Route = RouteAdapter { + D.scheme(name) { inner.get().delegate } + } } diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/directives/SecurityDirectives.scala b/akka-http/src/main/scala/akka/http/javadsl/server/directives/SecurityDirectives.scala new file mode 100644 index 0000000000..1500da97e5 --- /dev/null +++ b/akka-http/src/main/scala/akka/http/javadsl/server/directives/SecurityDirectives.scala @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2009-2016 Lightbend Inc. + */ +package akka.http.javadsl.server.directives + +import java.util.Optional +import java.util.concurrent.CompletionStage +import java.util.function.{Function => JFunction} +import java.util.function.Supplier + +import scala.compat.java8.FutureConverters._ +import scala.compat.java8.OptionConverters._ +import akka.http.javadsl.model.headers.HttpChallenge +import akka.http.javadsl.model.headers.HttpCredentials +import akka.http.javadsl.server.{RequestContext, Route} +import akka.http.scaladsl +import akka.http.scaladsl.server.{AuthorizationFailedRejection, Directives => D} + +object SecurityDirectives { + /** + * Represents HTTP Basic or OAuth2 authentication credentials supplied with a request. + */ + case class ProvidedCredentials(private val asScala: scaladsl.server.directives.Credentials.Provided) { + /** + * The username or token provided with the credentials + */ + def identifier: String = asScala.identifier + + /** + * Safely compares the passed in `secret` with the received secret part of the Credentials. + * Use of this method instead of manual String equality testing is recommended in order to guard against timing attacks. + * + * See also [[akka.http.impl.util.EnhancedString#secure_==]], for more information. + */ + def verify(secret: String): Boolean = asScala.verify(secret) + } + + private def toJava(cred: scaladsl.server.directives.Credentials): Optional[ProvidedCredentials] = cred match { + case provided: scaladsl.server.directives.Credentials.Provided => Optional.of(ProvidedCredentials(provided)) + case _ => Optional.empty() + } +} + +abstract class SecurityDirectives extends SchemeDirectives { + import SecurityDirectives._ + import akka.http.impl.util.JavaMapping.Implicits._ + + /** + * Extracts the potentially present [[HttpCredentials]] provided with the request's [[akka.http.javadsl.model.headers.Authorization]] header. + */ + def extractCredentials(inner: JFunction[Optional[HttpCredentials], Route]): Route = RouteAdapter { + D.extractCredentials { cred => + inner.apply(cred.map(_.asJava).asJava).delegate // TODO attempt to not need map() + } + } + + /** + * Wraps the inner route with Http Basic authentication support using a given `Authenticator[T]`. + * The given authenticator determines whether the credentials in the request are valid + * and, if so, which user object to supply to the inner route. + * + * Authentication is required in this variant, i.e. the request is rejected if [authenticator] returns Optional.empty. + */ + def authenticateBasic[T](realm: String, authenticator: JFunction[Optional[ProvidedCredentials], Optional[T]], + inner: JFunction[T, Route]): Route = RouteAdapter { + D.authenticateBasic(realm, c => authenticator.apply(toJava(c)).asScala) { t => + inner.apply(t).delegate + } + } + + /** + * Wraps the inner route with Http Basic authentication support using a given `Authenticator[T]`. + * The given authenticator determines whether the credentials in the request are valid + * and, if so, which user object to supply to the inner route. + * + * Authentication is optional in this variant. + */ + @CorrespondsTo("authenticateBasic") + def authenticateBasicOptional[T](realm: String, authenticator: JFunction[Optional[ProvidedCredentials], Optional[T]], + inner: JFunction[Optional[T], Route]): Route = RouteAdapter { + D.authenticateBasic(realm, c => authenticator.apply(toJava(c)).asScala).optional { t => + inner.apply(t.asJava).delegate + } + } + + /** + * Wraps the inner route with Http Basic authentication support. + * The given authenticator determines whether the credentials in the request are valid + * and, if so, which user object to supply to the inner route. + * + * Authentication is required in this variant, i.e. the request is rejected if [authenticator] returns Optional.empty. + */ + def authenticateBasicAsync[T](realm: String, authenticator: JFunction[Optional[ProvidedCredentials], CompletionStage[Optional[T]]], + inner: JFunction[T, Route]): Route = RouteAdapter { + D.extractExecutionContext { implicit ctx => + D.authenticateBasicAsync(realm, c => authenticator.apply(toJava(c)).toScala.map(_.asScala)) { t => + inner.apply(t).delegate + } + } + } + + /** + * Wraps the inner route with Http Basic authentication support. + * The given authenticator determines whether the credentials in the request are valid + * and, if so, which user object to supply to the inner route. + * + * Authentication is optional in this variant. + */ + @CorrespondsTo("authenticateBasicAsync") + def authenticateBasicAsyncOptional[T](realm: String, authenticator: JFunction[Optional[ProvidedCredentials], CompletionStage[Optional[T]]], + inner: JFunction[Optional[T], Route]): Route = RouteAdapter { + D.extractExecutionContext { implicit ctx => + D.authenticateBasicAsync(realm, c => authenticator.apply(toJava(c)).toScala.map(_.asScala)).optional { t => + inner.apply(t.asJava).delegate + } + } + } + + /** + * A directive that wraps the inner route with OAuth2 Bearer Token authentication support. + * The given authenticator determines whether the credentials in the request are valid + * and, if so, which user object to supply to the inner route. + * + * Authentication is required in this variant, i.e. the request is rejected if [authenticator] returns Optional.empty. + */ + def authenticateOAuth2[T](realm: String, authenticator: JFunction[Optional[ProvidedCredentials], Optional[T]], + inner: JFunction[T, Route]): Route = RouteAdapter { + D.authenticateOAuth2(realm, c => authenticator.apply(toJava(c)).asScala) { t => + inner.apply(t).delegate + } + } + + /** + * A directive that wraps the inner route with OAuth2 Bearer Token authentication support. + * The given authenticator determines whether the credentials in the request are valid + * and, if so, which user object to supply to the inner route. + * + * Authentication is optional in this variant. + */ + @CorrespondsTo("authenticateOAuth2") + def authenticateOAuth2Optional[T](realm: String, authenticator: JFunction[Optional[ProvidedCredentials], Optional[T]], + inner: JFunction[Optional[T], Route]): Route = RouteAdapter { + D.authenticateOAuth2(realm, c => authenticator.apply(toJava(c)).asScala).optional { t => + inner.apply(t.asJava).delegate + } + } + + /** + * A directive that wraps the inner route with OAuth2 Bearer Token authentication support. + * The given authenticator determines whether the credentials in the request are valid + * and, if so, which user object to supply to the inner route. + * + * Authentication is required in this variant, i.e. the request is rejected if [authenticator] returns Optional.empty. + */ + def authenticateOAuth2Async[T](realm: String, authenticator: JFunction[Optional[ProvidedCredentials], CompletionStage[Optional[T]]], + inner: JFunction[T, Route]): Route = RouteAdapter { + D.extractExecutionContext { implicit ctx => + D.authenticateOAuth2Async(realm, c => authenticator.apply(toJava(c)).toScala.map(_.asScala)) { t => + inner.apply(t).delegate + } + } + } + + /** + * A directive that wraps the inner route with OAuth2 Bearer Token authentication support. + * The given authenticator determines whether the credentials in the request are valid + * and, if so, which user object to supply to the inner route. + * + * Authentication is optional in this variant. + */ + @CorrespondsTo("authenticateOAuth2Async") + def authenticateOAuth2AsyncOptional[T](realm: String, authenticator: JFunction[Optional[ProvidedCredentials], CompletionStage[Optional[T]]], + inner: JFunction[Optional[T], Route]): Route = RouteAdapter { + D.extractExecutionContext { implicit ctx => + D.authenticateOAuth2Async(realm, c => authenticator.apply(toJava(c)).toScala.map(_.asScala)).optional { t => + inner.apply(t.asJava).delegate + } + } + } + + /** + * Lifts an authenticator function into a directive. The authenticator function gets passed in credentials from the + * [[akka.http.javadsl.model.headers.Authorization]] header of the request. If the function returns `Right(user)` the user object is provided + * to the inner route. If the function returns `Left(challenge)` the request is rejected with an + * [[akka.http.javadsl.server.AuthenticationFailedRejection]] that contains this challenge to be added to the response. + */ + def authenticateOrRejectWithChallenge[T](authenticator: JFunction[Optional[HttpCredentials], CompletionStage[Either[HttpChallenge,T]]], + inner: JFunction[T, Route]): Route = RouteAdapter { + D.extractExecutionContext { implicit ctx => + val scalaAuthenticator = { cred: Option[scaladsl.model.headers.HttpCredentials] => + authenticator.apply(cred.map(_.asJava).asJava).toScala.map(_.left.map(_.asScala)) + } + + D.authenticateOrRejectWithChallenge(scalaAuthenticator) { t => + inner.apply(t).delegate + } + } + } + + /** + * Lifts an authenticator function into a directive. Same as `authenticateOrRejectWithChallenge` + * but only applies the authenticator function with a certain type of credentials. + */ + def authenticateOrRejectWithChallenge[C <: HttpCredentials, T](c: Class[C], + authenticator: JFunction[Optional[C], CompletionStage[Either[HttpChallenge,T]]], + inner: JFunction[T, Route]): Route = RouteAdapter { + D.extractExecutionContext { implicit ctx => + val scalaAuthenticator = { cred: Option[scaladsl.model.headers.HttpCredentials] => + authenticator.apply(cred.filter(c.isInstance).map(_.asJava).asJava.asInstanceOf[Optional[C]]).toScala.map(_.left.map(_.asScala)) // TODO make sure cast is safe + } + + D.authenticateOrRejectWithChallenge(scalaAuthenticator) { t => + inner.apply(t).delegate + } + } + } + + /** + * Applies the given authorization check to the request. + * If the check fails the route is rejected with an [[akka.http.javadsl.server.AuthorizationFailedRejection]]. + */ + def authorize(check: Supplier[Boolean], inner: Supplier[Route]): Route = RouteAdapter { + D.authorize(check.get()) { + inner.get().delegate + } + } + + /** + * Applies the given authorization check to the request. + * If the check fails the route is rejected with an [[akka.http.javadsl.server.AuthorizationFailedRejection]]. + */ + @CorrespondsTo("authorize") + def authorizeWithRequestContext(check: akka.japi.function.Function[RequestContext, Boolean], inner: Supplier[Route]): Route = RouteAdapter { + D.authorize(rc => check(RequestContext.wrap(rc)))(inner.get().delegate) + } + + /** + * Applies the given authorization check to the request. + * If the check fails the route is rejected with an [[akka.http.javadsl.server.AuthorizationFailedRejection]]. + */ + def authorizeAsync(check: Supplier[CompletionStage[Boolean]], inner: Supplier[Route]): Route = RouteAdapter { + D.authorizeAsync(check.get().toScala) { + inner.get().delegate + } + } + + /** + * Asynchronous version of [[authorize]]. + * If the [[CompletionStage]] fails or is completed with `false` + * authorization fails and the route is rejected with an [[akka.http.javadsl.server.AuthorizationFailedRejection]]. + */ + @CorrespondsTo("authorizeAsync") + def authorizeAsyncWithRequestContext(check: akka.japi.function.Function[RequestContext, CompletionStage[Boolean]], inner: Supplier[Route]): Route = RouteAdapter { + D.authorizeAsync(rc => check(RequestContext.wrap(rc)).toScala)(inner.get().delegate) + } + + /** + * Creates a `Basic` [[HttpChallenge]] for the given realm. + */ + def challengeFor(realm: String): HttpChallenge = HttpChallenge.create("Basic", realm) + +} \ No newline at end of file diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/directives/TimeoutDirectives.scala b/akka-http/src/main/scala/akka/http/javadsl/server/directives/TimeoutDirectives.scala new file mode 100644 index 0000000000..68ca4b9f52 --- /dev/null +++ b/akka-http/src/main/scala/akka/http/javadsl/server/directives/TimeoutDirectives.scala @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2009-2016 Lightbend Inc. + */ + +package akka.http.javadsl.server.directives + +import java.lang.{ Iterable ⇒ JIterable } +import java.util.function.{ BooleanSupplier, Function ⇒ JFunction, Supplier } + +import akka.http.javadsl.model.{ HttpRequest, HttpResponse, RemoteAddress } +import akka.http.javadsl.model.headers.Language +import akka.http.javadsl.server.Route +import akka.http.scaladsl.server.{ Directives ⇒ D } + +import akka.http.impl.util.JavaMapping +import akka.http.impl.util.JavaMapping.Implicits._ +import scala.collection.JavaConverters._ + +abstract class TimeoutDirectives extends WebSocketDirectives { + + /** + * 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: scala.concurrent.duration.Duration, inner: Supplier[Route]): RouteAdapter = RouteAdapter { + D.withRequestTimeout(timeout) { inner.get.delegate } + } + + /** + * 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: scala.concurrent.duration.Duration, timeoutHandler: JFunction[HttpRequest, HttpResponse], + inner: Supplier[Route]): RouteAdapter = RouteAdapter { + D.withRequestTimeout(timeout, in ⇒ timeoutHandler(in.asJava).asScala) { inner.get.delegate } + } + + def withoutRequestTimeout(inner: Supplier[Route]): RouteAdapter = RouteAdapter { + D.withoutRequestTimeout { inner.get.delegate } + } + +} diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/directives/WebSocketDirectives.scala b/akka-http/src/main/scala/akka/http/javadsl/server/directives/WebSocketDirectives.scala index e866bd3624..79b78f9071 100644 --- a/akka-http/src/main/scala/akka/http/javadsl/server/directives/WebSocketDirectives.scala +++ b/akka-http/src/main/scala/akka/http/javadsl/server/directives/WebSocketDirectives.scala @@ -5,15 +5,76 @@ package akka.http.javadsl.server package directives -import akka.http.impl.server.RouteStructure -import akka.http.javadsl.model.ws.Message -import akka.stream.javadsl.Flow +import java.util.{ List ⇒ JList } +import java.util.Optional +import java.util.function.{ Function ⇒ JFunction } + +import akka.NotUsed +import scala.collection.JavaConverters._ +import akka.http.scaladsl.model.{ ws ⇒ s } +import akka.http.javadsl.model.ws.Message +import akka.http.javadsl.model.ws.UpgradeToWebSocket +import akka.http.scaladsl.server.{ Directives ⇒ D } +import akka.stream.javadsl.Flow +import akka.stream.scaladsl + +abstract class WebSocketDirectives extends SecurityDirectives { + import akka.http.impl.util.JavaMapping.Implicits._ -abstract class WebSocketDirectives extends SchemeDirectives { /** - * Handles websocket requests with the given handler and rejects other requests with a - * [[akka.http.scaladsl.server.ExpectedWebSocketRequestRejection]]. + * Extract the [[UpgradeToWebSocket]] header if existent. Rejects with an [[ExpectedWebSocketRequestRejection]], otherwise. */ - def handleWebSocketMessages(handler: Flow[Message, Message, _]): Route = - RouteStructure.HandleWebSocketMessages(handler) + def extractUpgradeToWebSocket(inner: JFunction[UpgradeToWebSocket, Route]): Route = RouteAdapter { + D.extractUpgradeToWebSocket { header ⇒ + inner.apply(header).delegate + } + } + + /** + * Extract the list of WebSocket subprotocols as offered by the client in the [[Sec-WebSocket-Protocol]] header if + * this is a WebSocket request. Rejects with an [[ExpectedWebSocketRequestRejection]], otherwise. + */ + def extractOfferedWsProtocols(inner: JFunction[JList[String], Route]): Route = RouteAdapter { + D.extractOfferedWsProtocols { list ⇒ + inner.apply(list.asJava).delegate + } + } + + /** + * Handles WebSocket requests with the given handler and rejects other requests with an + * [[ExpectedWebSocketRequestRejection]]. + */ + def handleWebSocketMessages[T](handler: Flow[Message, Message, T]): Route = RouteAdapter { + D.handleWebSocketMessages(adapt(handler)) + } + + /** + * Handles WebSocket requests with the given handler if the given subprotocol is offered in the request and + * rejects other requests with an [[ExpectedWebSocketRequestRejection]] or an [[UnsupportedWebSocketSubprotocolRejection]]. + */ + def handleWebSocketMessagesForProtocol(handler: Flow[Message, Message, Any], subprotocol: String): Route = RouteAdapter { + val adapted = scaladsl.Flow[s.Message].map(_.asJava).via(handler).map(_.asScala) + D.handleWebSocketMessagesForProtocol(adapted, subprotocol) + } + + /** + * Handles WebSocket requests with the given handler and rejects other requests with an + * [[ExpectedWebSocketRequestRejection]]. + * + * If the `subprotocol` parameter is None any WebSocket request is accepted. If the `subprotocol` parameter is + * `Some(protocol)` a WebSocket request is only accepted if the list of subprotocols supported by the client (as + * announced in the WebSocket request) contains `protocol`. If the client did not offer the protocol in question + * the request is rejected with an [[UnsupportedWebSocketSubprotocolRejection]] rejection. + * + * To support several subprotocols you may chain several `handleWebSocketMessage` Routes. + */ + def handleWebSocketMessagesForOptionalProtocol(handler: Flow[Message, Message, Any], subprotocol: Optional[String]): Route = RouteAdapter { + val adapted = scaladsl.Flow[s.Message].map(_.asJava).via(handler).map(_.asScala) + D.handleWebSocketMessagesForOptionalProtocol(adapted, subprotocol.asScala) + } + + // TODO this is because scala Message does not extend java Message - we could fix that, but http-core is stable + private def adapt[T](handler: Flow[Message, Message, T]): scaladsl.Flow[s.Message, s.Message, NotUsed] = { + scaladsl.Flow[s.Message].map(_.asJava).via(handler).map(_.asScala) + } } diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/values/Cookie.scala b/akka-http/src/main/scala/akka/http/javadsl/server/values/Cookie.scala deleted file mode 100644 index 030a703bfa..0000000000 --- a/akka-http/src/main/scala/akka/http/javadsl/server/values/Cookie.scala +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.javadsl.server.values - -import akka.http.impl.server.{ RouteStructure, CookieImpl } -import akka.http.javadsl.server.{ Directive, RequestVal, Route } -import java.util.Optional - -import scala.annotation.varargs -import scala.compat.java8.OptionConverters._ - -abstract class Cookie { - def name(): String - def domain(): Optional[String] - def path(): Optional[String] - - def withDomain(domain: String): Cookie - def withPath(path: String): Cookie - - def value(): RequestVal[String] - def optionalValue(): RequestVal[Optional[String]] - - def set(value: String): Directive - - @varargs - def delete(innerRoute: Route, moreInnerRoutes: Route*): Route = - RouteStructure.DeleteCookie(name(), domain().asScala, path().asScala)(innerRoute, moreInnerRoutes.toList) -} -object Cookies { - def create(name: String): Cookie = new CookieImpl(name) -} \ No newline at end of file diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/values/FormField.scala b/akka-http/src/main/scala/akka/http/javadsl/server/values/FormField.scala deleted file mode 100644 index 4bdaf4ab7e..0000000000 --- a/akka-http/src/main/scala/akka/http/javadsl/server/values/FormField.scala +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.javadsl.server -package values - -import java.util.Optional -import java.{ lang ⇒ jl } - -import akka.http.impl.server.{ FormFieldImpl, Util } -import akka.http.scaladsl.unmarshalling._ -import akka.japi.function.Function - -import scala.reflect.ClassTag - -trait FormField[T] extends RequestVal[T] { - def optional: RequestVal[Optional[T]] - def withDefault(defaultValue: T): RequestVal[T] -} - -object FormFields { - import akka.http.scaladsl.common.ToNameReceptacleEnhancements._ - - def stringValue(name: String): FormField[String] = FormFieldImpl(name) - def intValue(name: String): FormField[jl.Integer] = FormFieldImpl(name.as[Int]) - def byteValue(name: String): FormField[jl.Byte] = FormFieldImpl(name.as[Byte]) - def shortValue(name: String): FormField[jl.Short] = FormFieldImpl(name.as[Short]) - def longValue(name: String): FormField[jl.Long] = FormFieldImpl(name.as[Long]) - def floatValue(name: String): FormField[jl.Float] = FormFieldImpl(name.as[Float]) - def doubleValue(name: String): FormField[jl.Double] = FormFieldImpl(name.as[Double]) - def booleanValue(name: String): FormField[jl.Boolean] = FormFieldImpl(name.as[Boolean]) - - def hexByteValue(name: String): FormField[jl.Byte] = FormFieldImpl(name.as(Unmarshaller.HexByte)) - def hexShortValue(name: String): FormField[jl.Short] = FormFieldImpl(name.as(Unmarshaller.HexShort)) - def hexIntValue(name: String): FormField[jl.Integer] = FormFieldImpl(name.as(Unmarshaller.HexInt)) - def hexLongValue(name: String): FormField[jl.Long] = FormFieldImpl(name.as(Unmarshaller.HexLong)) - - /** Unmarshals the `name` field using the provided `convert` function. */ - def fromString[T](name: String, clazz: Class[T], convert: Function[String, T]): FormField[T] = { - implicit val tTag: ClassTag[T] = ClassTag(clazz) - FormFieldImpl(name.as(Util.fromStringUnmarshallerFromFunction(convert))) - } -} \ No newline at end of file diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/values/Header.scala b/akka-http/src/main/scala/akka/http/javadsl/server/values/Header.scala deleted file mode 100644 index b0ca5bb3ab..0000000000 --- a/akka-http/src/main/scala/akka/http/javadsl/server/values/Header.scala +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.javadsl.server.values - -import java.util.Optional - -import akka.http.impl.server.HeaderImpl -import akka.http.javadsl.model.HttpHeader -import akka.http.javadsl.server.RequestVal -import akka.http.scaladsl.model -import akka.http.scaladsl.server.Directive1 -import akka.http.scaladsl.server.directives.HeaderMagnet - -import scala.compat.java8.OptionConverters._ -import scala.reflect.{ ClassTag, classTag } - -trait Header[T <: HttpHeader] { - def instance(): RequestVal[T] - def optionalInstance(): RequestVal[Optional[T]] - - def value(): RequestVal[String] - def optionalValue(): RequestVal[Optional[String]] -} -object Headers { - import akka.http.scaladsl.server.directives.BasicDirectives._ - import akka.http.scaladsl.server.directives.HeaderDirectives._ - - def byName(name: String): Header[HttpHeader] = - HeaderImpl[HttpHeader](name, _ ⇒ optionalHeaderInstanceByName(name.toLowerCase()).map(_.asScala), classTag[HttpHeader]) - - def byClass[T <: HttpHeader](clazz: Class[T]): Header[T] = - HeaderImpl[T](clazz.getSimpleName, ct ⇒ optionalHeaderValueByType(HeaderMagnet.fromUnit(())(ct)), ClassTag(clazz)) - - private def optionalHeaderInstanceByName(lowercaseName: String): Directive1[Optional[model.HttpHeader]] = - extract(_.request.headers.collectFirst { - case h @ model.HttpHeader(`lowercaseName`, _) ⇒ h - }.asJava) -} diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/values/HttpBasicAuthenticator.scala b/akka-http/src/main/scala/akka/http/javadsl/server/values/HttpBasicAuthenticator.scala deleted file mode 100644 index 912f5087fb..0000000000 --- a/akka-http/src/main/scala/akka/http/javadsl/server/values/HttpBasicAuthenticator.scala +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.javadsl.server.values - -import akka.http.impl.server.{ ExtractionImplBase, RouteStructure } -import akka.http.javadsl.server.{ AbstractDirective, RequestVal, Route } -import scala.reflect.ClassTag -import java.util.concurrent.CompletionStage -import java.util.Optional -import java.util.concurrent.CompletableFuture - -/** - * Represents existing or missing Http Basic authentication credentials. - */ -trait BasicCredentials { - /** - * Were credentials provided in the request? - */ - def available: Boolean - - /** - * The identifier as sent in the request. - */ - def identifier: String - - /** - * Verifies the given secret against the one sent in the request. - */ - def verify(secret: String): Boolean -} - -/** - * Implement this class to provide an HTTP Basic authentication check. The [[#authenticate]] method needs to be implemented - * to check if the supplied or missing credentials are authenticated and provide a domain level object representing - * the user as a [[akka.http.javadsl.server.RequestVal]]. - */ -abstract class HttpBasicAuthenticator[T](val realm: String) extends AbstractDirective with ExtractionImplBase[T] with RequestVal[T] { - protected[http] implicit def classTag: ClassTag[T] = reflect.classTag[AnyRef].asInstanceOf[ClassTag[T]] - def authenticate(credentials: BasicCredentials): CompletionStage[Optional[T]] - - /** - * Creates a return value for use in [[#authenticate]] that successfully authenticates the requests and provides - * the given user. - */ - def authenticateAs(user: T): CompletionStage[Optional[T]] = CompletableFuture.completedFuture(Optional.of(user)) - - /** - * Refuses access for this user. - */ - def refuseAccess(): CompletionStage[Optional[T]] = CompletableFuture.completedFuture(Optional.empty()) - - /** - * INTERNAL API - */ - protected[http] final def createRoute(first: Route, others: Array[Route]): Route = - RouteStructure.BasicAuthentication(this)(first, others.toList) -} diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/values/OAuth2Authenticator.scala b/akka-http/src/main/scala/akka/http/javadsl/server/values/OAuth2Authenticator.scala deleted file mode 100644 index b7c28a663a..0000000000 --- a/akka-http/src/main/scala/akka/http/javadsl/server/values/OAuth2Authenticator.scala +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.javadsl.server.values - -import akka.http.impl.server.{ ExtractionImplBase, RouteStructure } -import akka.http.javadsl.server.{ AbstractDirective, RequestVal, Route } -import scala.reflect.ClassTag -import java.util.concurrent.CompletionStage -import java.util.Optional -import java.util.concurrent.CompletableFuture - -/** - * Represents existing or missing OAuth 2 authentication credentials. - */ -trait OAuth2Credentials { - /** - * Were credentials provided in the request? - */ - def available: Boolean - - /** - * The identifier as sent in the request. - */ - def identifier: String - - /** - * Verifies the given secret against the one sent in the request. - */ - def verify(secret: String): Boolean -} - -/** - * Implement this class to provide an OAuth 2 Bearer Token authentication check. The [[#authenticate]] method needs to be implemented - * to check if the supplied or missing credentials are authenticated and provide a domain level object representing - * the user as a [[akka.http.javadsl.server.RequestVal]]. - */ -abstract class OAuth2Authenticator[T](val realm: String) extends AbstractDirective with ExtractionImplBase[T] with RequestVal[T] { - protected[http] implicit def classTag: ClassTag[T] = reflect.classTag[AnyRef].asInstanceOf[ClassTag[T]] - def authenticate(credentials: OAuth2Credentials): CompletionStage[Optional[T]] - - /** - * Creates a return value for use in [[#authenticate]] that successfully authenticates the requests and provides - * the given user. - */ - def authenticateAs(user: T): CompletionStage[Optional[T]] = CompletableFuture.completedFuture(Optional.of(user)) - - /** - * Refuses access for this user. - */ - def refuseAccess(): CompletionStage[Optional[T]] = CompletableFuture.completedFuture(Optional.empty()) - - /** - * INTERNAL API - */ - protected[http] final def createRoute(first: Route, others: Array[Route]): Route = - RouteStructure.OAuth2Authentication(this)(first, others.toList) -} diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/values/Parameter.scala b/akka-http/src/main/scala/akka/http/javadsl/server/values/Parameter.scala deleted file mode 100644 index bdf924e40a..0000000000 --- a/akka-http/src/main/scala/akka/http/javadsl/server/values/Parameter.scala +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.javadsl.server.values - -import java.util.AbstractMap.SimpleEntry -import java.util.{ Collection ⇒ JCollection, Map ⇒ JMap, Optional } -import java.{ lang ⇒ jl } - -import akka.http.impl.server.{ ParameterImpl, StandaloneExtractionImpl, Util } -import akka.http.javadsl.server.RequestVal -import akka.http.scaladsl.server.directives.ParameterDirectives -import akka.http.scaladsl.unmarshalling.Unmarshaller -import akka.japi.function.Function - -import scala.reflect.ClassTag - -/** - * A RequestVal representing a query parameter of type T. - */ -trait Parameter[T] extends RequestVal[T] { - def optional: RequestVal[Optional[T]] - def withDefault(defaultValue: T): RequestVal[T] -} - -/** - * A collection of predefined parameters. - */ -object Parameters { - import akka.http.scaladsl.common.ToNameReceptacleEnhancements._ - - /** - * A string query parameter. - */ - def stringValue(name: String): Parameter[String] = ParameterImpl(name) - - /** - * An integer query parameter. - */ - def intValue(name: String): Parameter[jl.Integer] = ParameterImpl(name.as[Int]) - def byteValue(name: String): Parameter[jl.Byte] = ParameterImpl(name.as[Byte]) - def shortValue(name: String): Parameter[jl.Short] = ParameterImpl(name.as[Short]) - def longValue(name: String): Parameter[jl.Long] = ParameterImpl(name.as[Long]) - def floatValue(name: String): Parameter[jl.Float] = ParameterImpl(name.as[Float]) - def doubleValue(name: String): Parameter[jl.Double] = ParameterImpl(name.as[Double]) - def booleanValue(name: String): Parameter[jl.Boolean] = ParameterImpl(name.as[Boolean]) - - def hexByteValue(name: String): Parameter[jl.Byte] = ParameterImpl(name.as(Unmarshaller.HexByte)) - def hexShortValue(name: String): Parameter[jl.Short] = ParameterImpl(name.as(Unmarshaller.HexShort)) - def hexIntValue(name: String): Parameter[jl.Integer] = ParameterImpl(name.as(Unmarshaller.HexInt)) - def hexLongValue(name: String): Parameter[jl.Long] = ParameterImpl(name.as(Unmarshaller.HexLong)) - - import scala.collection.JavaConverters._ - def asMap: RequestVal[JMap[String, String]] = StandaloneExtractionImpl(ParameterDirectives.parameterMap.map(_.asJava)) - def asMultiMap: RequestVal[JMap[String, JCollection[String]]] = - StandaloneExtractionImpl(ParameterDirectives.parameterMultiMap.map(_.mapValues(_.asJavaCollection).asJava)) - def asCollection: RequestVal[JCollection[JMap.Entry[String, String]]] = - StandaloneExtractionImpl(ParameterDirectives.parameterSeq.map(_.map(e ⇒ new SimpleEntry(e._1, e._2): JMap.Entry[String, String]).asJavaCollection)) - - /** Unmarshals the `name` field using the provided `convert` function. */ - def fromString[T](name: String, clazz: Class[T], convert: Function[String, T]): Parameter[T] = { - implicit val tTag: ClassTag[T] = ClassTag(clazz) - ParameterImpl(name.as(Util.fromStringUnmarshallerFromFunction(convert))) - } -} - diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/values/PathMatchers.scala b/akka-http/src/main/scala/akka/http/javadsl/server/values/PathMatchers.scala deleted file mode 100644 index d6b26d9aeb..0000000000 --- a/akka-http/src/main/scala/akka/http/javadsl/server/values/PathMatchers.scala +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2009-2016 Lightbend Inc. - */ - -package akka.http.javadsl.server.values - -import java.util.Optional -import java.{ lang ⇒ jl, util ⇒ ju } - -import akka.http.impl.server.PathMatcherImpl -import akka.http.javadsl.server.RequestVal -import akka.http.scaladsl.server.{ PathMatcher0, PathMatcher1, PathMatchers ⇒ ScalaPathMatchers } -import akka.japi.function.Function - -import scala.collection.JavaConverters._ -import scala.reflect.ClassTag - -/** - * A PathMatcher is used to match the (yet unmatched) URI path of incoming requests. - * It is also a RequestVal that allows to access dynamic parts of the part in a - * handler. - * - * Using a PathMatcher with the [[akka.http.javadsl.server.Directives#path]] or [[akka.http.javadsl.server.Directives#pathPrefix]] directives - * "consumes" a part of the path which is recorded in [[akka.http.javadsl.server.RequestContext#unmatchedPath]]. - */ -trait PathMatcher[T] extends RequestVal[T] { - def optional: PathMatcher[Optional[T]] -} - -/** - * A collection of predefined path matchers. - */ -object PathMatchers { - /** - * A PathMatcher that always matches, doesn't consume anything and extracts nothing. - * Serves mainly as a neutral element in PathMatcher composition. - */ - val NEUTRAL: PathMatcher[Void] = matcher0(_.Neutral) - - /** - * A PathMatcher that matches a single slash character ('/'). - */ - val SLASH: PathMatcher[Void] = matcher0(_.Slash) - - /** - * A PathMatcher that matches the very end of the requests URI path. - */ - val END: PathMatcher[Void] = matcher0(_.PathEnd) - - /** - * Creates a PathMatcher that consumes (a prefix of) the first path segment - * (if the path begins with a segment) and extracts a given value. - */ - def segment(name: String): PathMatcher[String] = matcher(_ ⇒ name -> name) - - /** - * A PathMatcher that efficiently matches a number of digits and extracts their (non-negative) Int value. - * The matcher will not match 0 digits or a sequence of digits that would represent an Int value larger - * than [[java.lang.Integer.MAX_VALUE]]. - */ - def intValue: PathMatcher[jl.Integer] = matcher(_.IntNumber.asInstanceOf[PathMatcher1[jl.Integer]]) - - /** - * A PathMatcher that efficiently matches a number of hex-digits and extracts their (non-negative) Int value. - * The matcher will not match 0 digits or a sequence of digits that would represent an Int value larger - * than [[java.lang.Integer.MAX_VALUE]]. - */ - def hexIntValue: PathMatcher[jl.Integer] = matcher(_.HexIntNumber.asInstanceOf[PathMatcher1[jl.Integer]]) - - /** - * A PathMatcher that efficiently matches a number of digits and extracts their (non-negative) Long value. - * The matcher will not match 0 digits or a sequence of digits that would represent an Long value larger - * than [[java.lang.Long.MAX_VALUE]]. - */ - def longValue: PathMatcher[jl.Long] = matcher(_.LongNumber.asInstanceOf[PathMatcher1[jl.Long]]) - - /** - * A PathMatcher that efficiently matches a number of hex-digits and extracts their (non-negative) Long value. - * The matcher will not match 0 digits or a sequence of digits that would represent an Long value larger - * than [[java.lang.Long.MAX_VALUE]]. - */ - def hexLongValue: PathMatcher[jl.Long] = matcher(_.HexLongNumber.asInstanceOf[PathMatcher1[jl.Long]]) - - /** - * Creates a PathMatcher that consumes (a prefix of) the first path segment - * if the path begins with a segment (a prefix of) which matches the given regex. - * Extracts either the complete match (if the regex doesn't contain a capture group) or - * the capture group (if the regex contains exactly one). - * If the regex contains more than one capture group the method throws an IllegalArgumentException. - */ - def regex(regex: String): PathMatcher[String] = matcher(_ ⇒ regex.r) - - /** - * A PathMatcher that matches and extracts a java.util.UUID instance. - */ - def uuid: PathMatcher[ju.UUID] = matcher(_.JavaUUID) - - /** - * A PathMatcher that matches if the unmatched path starts with a path segment. - * If so the path segment is extracted as a String. - */ - def segment: PathMatcher[String] = matcher(_.Segment) - - /** - * A PathMatcher that matches up to 128 remaining segments as a List[String]. - * This can also be no segments resulting in the empty list. - * If the path has a trailing slash this slash will *not* be matched. - */ - def segments: PathMatcher[ju.List[String]] = matcher(_.Segments.map(_.asJava)) - - /** - * A PathMatcher that matches the given number of path segments (separated by slashes) as a List[String]. - * If there are more than `count` segments present the remaining ones will be left unmatched. - * If the path has a trailing slash this slash will *not* be matched. - */ - def segments(maxNumber: Int): PathMatcher[ju.List[String]] = matcher(_.Segments(maxNumber).map(_.asJava)) - - /** - * A PathMatcher that matches and extracts the complete remaining, - * unmatched part of the request's URI path as an (encoded!) String. - * If you need access to the remaining unencoded elements of the path - * use the `RestPath` matcher! - */ - def rest: PathMatcher[String] = matcher(_.Rest) - - def segmentFromString[T](convert: Function[String, T], clazz: Class[T]): PathMatcher[T] = - matcher(_ ⇒ ScalaPathMatchers.Segment.map(convert(_)))(ClassTag(clazz)) - - private def matcher[T: ClassTag](scalaMatcher: ScalaPathMatchers.type ⇒ PathMatcher1[T]): PathMatcher[T] = - new PathMatcherImpl[T](scalaMatcher(ScalaPathMatchers)) - private def matcher0(scalaMatcher: ScalaPathMatchers.type ⇒ PathMatcher0): PathMatcher[Void] = - new PathMatcherImpl[Void](scalaMatcher(ScalaPathMatchers).tmap(_ ⇒ Tuple1(null))) -} diff --git a/akka-http/src/main/scala/akka/http/scaladsl/common/NameReceptacle.scala b/akka-http/src/main/scala/akka/http/scaladsl/common/NameReceptacle.scala index 5e5cc8ec7c..fb8c0546f4 100644 --- a/akka-http/src/main/scala/akka/http/scaladsl/common/NameReceptacle.scala +++ b/akka-http/src/main/scala/akka/http/scaladsl/common/NameReceptacle.scala @@ -7,8 +7,8 @@ package akka.http.scaladsl.common import akka.http.scaladsl.unmarshalling.{ FromStringUnmarshaller ⇒ FSU } private[http] trait ToNameReceptacleEnhancements { - implicit def symbol2NR(symbol: Symbol): NameReceptacle[String] = new NameReceptacle[String](symbol.name) - implicit def string2NR(string: String): NameReceptacle[String] = new NameReceptacle[String](string) + implicit def _symbol2NR(symbol: Symbol): NameReceptacle[String] = new NameReceptacle[String](symbol.name) + implicit def _string2NR(string: String): NameReceptacle[String] = new NameReceptacle[String](string) } object ToNameReceptacleEnhancements extends ToNameReceptacleEnhancements diff --git a/akka-http/src/main/scala/akka/http/scaladsl/marshalling/Marshaller.scala b/akka-http/src/main/scala/akka/http/scaladsl/marshalling/Marshaller.scala index 8719aa9e1a..1974bc8000 100644 --- a/akka-http/src/main/scala/akka/http/scaladsl/marshalling/Marshaller.scala +++ b/akka-http/src/main/scala/akka/http/scaladsl/marshalling/Marshaller.scala @@ -10,6 +10,7 @@ import akka.http.scaladsl.model._ import akka.http.scaladsl.util.FastFuture import akka.http.scaladsl.util.FastFuture._ +// TODO make it extend JavaDSL sealed abstract class Marshaller[-A, +B] { def apply(value: A)(implicit ec: ExecutionContext): Future[List[Marshalling[B]]] @@ -138,7 +139,7 @@ object Marshaller } //# -//# marshalling +//#marshalling /** * Describes one possible option for marshalling a given value. */ diff --git a/akka-http/src/main/scala/akka/http/scaladsl/server/ContentNegotation.scala b/akka-http/src/main/scala/akka/http/scaladsl/server/ContentNegotation.scala index d3a2166a12..ceec0ffe47 100644 --- a/akka-http/src/main/scala/akka/http/scaladsl/server/ContentNegotation.scala +++ b/akka-http/src/main/scala/akka/http/scaladsl/server/ContentNegotation.scala @@ -139,11 +139,11 @@ object ContentNegotiator { case x: model.MediaType.WithOpenCharset ⇒ MediaType(x) } - case class ContentType(contentType: model.ContentType) extends Alternative { + final case class ContentType(contentType: model.ContentType) extends Alternative { def mediaType = contentType.mediaType def format = contentType.toString } - case class MediaType(mediaType: model.MediaType.WithOpenCharset) extends Alternative { + final case class MediaType(mediaType: model.MediaType.WithOpenCharset) extends Alternative { def format = mediaType.toString } } 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 b5af98cfb5..b92bb8d97a 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 @@ -6,6 +6,11 @@ package akka.http.scaladsl.server import directives._ +/** + * Collects all default directives into one trait for simple importing. + * + * See [[akka.http.javadsl.server.AllDirectives]] for JavaDSL equivalent of this trait. + */ trait Directives extends RouteConcatenation with BasicDirectives with CacheConditionDirectives diff --git a/akka-http/src/main/scala/akka/http/scaladsl/server/PathMatcher.scala b/akka-http/src/main/scala/akka/http/scaladsl/server/PathMatcher.scala index 51c3ee97de..2ca514b0bc 100644 --- a/akka-http/src/main/scala/akka/http/scaladsl/server/PathMatcher.scala +++ b/akka-http/src/main/scala/akka/http/scaladsl/server/PathMatcher.scala @@ -236,7 +236,7 @@ trait ImplicitPathMatcherConstruction { * * @group pathmatcherimpl */ - implicit def stringExtractionPair2PathMatcher[T](tuple: (String, T)): PathMatcher1[T] = + implicit def _stringExtractionPair2PathMatcher[T](tuple: (String, T)): PathMatcher1[T] = PathMatcher(tuple._1 :: Path.Empty, Tuple1(tuple._2)) /** @@ -245,13 +245,13 @@ trait ImplicitPathMatcherConstruction { * * @group pathmatcherimpl */ - implicit def segmentStringToPathMatcher(segment: String): PathMatcher0 = + implicit def _segmentStringToPathMatcher(segment: String): PathMatcher0 = PathMatcher(segment :: Path.Empty, ()) /** * @group pathmatcherimpl */ - implicit def stringNameOptionReceptacle2PathMatcher(nr: NameOptionReceptacle[String]): PathMatcher0 = + implicit def _stringNameOptionReceptacle2PathMatcher(nr: NameOptionReceptacle[String]): PathMatcher0 = PathMatcher(nr.name).? /** @@ -263,7 +263,7 @@ trait ImplicitPathMatcherConstruction { * * @group pathmatcherimpl */ - implicit def regex2PathMatcher(regex: Regex): PathMatcher1[String] = regex.groupCount match { + implicit def _regex2PathMatcher(regex: Regex): PathMatcher1[String] = regex.groupCount match { case 0 ⇒ new PathMatcher1[String] { def apply(path: Path) = path match { case Path.Segment(segment, tail) ⇒ regex findPrefixOf segment match { @@ -292,9 +292,9 @@ trait ImplicitPathMatcherConstruction { * * @group pathmatcherimpl */ - implicit def valueMap2PathMatcher[T](valueMap: Map[String, T]): PathMatcher1[T] = + implicit def _valueMap2PathMatcher[T](valueMap: Map[String, T]): PathMatcher1[T] = if (valueMap.isEmpty) PathMatchers.nothingMatcher - else valueMap.map { case (prefix, value) ⇒ stringExtractionPair2PathMatcher((prefix, value)) }.reduceLeft(_ | _) + else valueMap.map { case (prefix, value) ⇒ _stringExtractionPair2PathMatcher((prefix, value)) }.reduceLeft(_ | _) } /** @@ -348,11 +348,11 @@ trait PathMatchers { * A PathMatcher that matches and extracts the complete remaining, * unmatched part of the request's URI path as an (encoded!) String. * If you need access to the remaining unencoded elements of the path - * use the `RestPath` matcher! + * use the `RemainingPath` matcher! * * @group pathmatcher */ - object Rest extends PathMatcher1[String] { + object Remaining extends PathMatcher1[String] { def apply(path: Path) = Matched(Path.Empty, Tuple1(path.toString)) } @@ -362,7 +362,7 @@ trait PathMatchers { * * @group pathmatcher */ - object RestPath extends PathMatcher1[Path] { + object RemainingPath extends PathMatcher1[Path] { def apply(path: Path) = Matched(Path.Empty, Tuple1(path)) } diff --git a/akka-http/src/main/scala/akka/http/scaladsl/server/Rejection.scala b/akka-http/src/main/scala/akka/http/scaladsl/server/Rejection.scala index 951cd31ae1..15fc491669 100644 --- a/akka-http/src/main/scala/akka/http/scaladsl/server/Rejection.scala +++ b/akka-http/src/main/scala/akka/http/scaladsl/server/Rejection.scala @@ -4,93 +4,126 @@ package akka.http.scaladsl.server +import java.lang.Iterable +import java.util.Optional +import java.util.function.Function + +import akka.japi.Util + import scala.collection.immutable import akka.http.scaladsl.model._ +import akka.http.javadsl +import akka.http.javadsl.{ server ⇒ jserver, model } import headers._ +import akka.http.impl.util.JavaMapping._ +import akka.http.impl.util.JavaMapping.Implicits._ +import akka.http.javadsl.RoutingJavaMapping._ +import scala.collection.JavaConverters._ +import scala.compat.java8.OptionConverters + /** * A rejection encapsulates a specific reason why a Route was not able to handle a request. Rejections are gathered * up over the course of a Route evaluation and finally converted to [[akka.http.scaladsl.model.HttpResponse]]s by the * `handleRejections` directive, if there was no way for the request to be completed. */ -trait Rejection +trait Rejection extends akka.http.javadsl.server.Rejection + +trait RejectionWithOptionalCause extends Rejection { + final def getCause: Optional[Throwable] = OptionConverters.toJava(cause) + def cause: Option[Throwable] +} /** * Rejection created by method filters. * Signals that the request was rejected because the HTTP method is unsupported. */ -case class MethodRejection(supported: HttpMethod) extends Rejection +final case class MethodRejection(supported: HttpMethod) + extends jserver.MethodRejection with Rejection /** * Rejection created by scheme filters. * Signals that the request was rejected because the Uri scheme is unsupported. */ -case class SchemeRejection(supported: String) extends Rejection +final case class SchemeRejection(supported: String) + extends jserver.SchemeRejection with Rejection /** * Rejection created by parameter filters. * Signals that the request was rejected because a query parameter was not found. */ -case class MissingQueryParamRejection(parameterName: String) extends Rejection +final case class MissingQueryParamRejection(parameterName: String) + extends jserver.MissingQueryParamRejection with Rejection /** * Rejection created by parameter filters. * Signals that the request was rejected because a query parameter could not be interpreted. */ -case class MalformedQueryParamRejection(parameterName: String, errorMsg: String, - cause: Option[Throwable] = None) extends Rejection +final case class MalformedQueryParamRejection(parameterName: String, errorMsg: String, cause: Option[Throwable] = None) + extends jserver.MalformedQueryParamRejection with RejectionWithOptionalCause /** * Rejection created by form field filters. * Signals that the request was rejected because a form field was not found. */ -case class MissingFormFieldRejection(fieldName: String) extends Rejection +final case class MissingFormFieldRejection(fieldName: String) + extends jserver.MissingFormFieldRejection with Rejection /** * Rejection created by form field filters. * Signals that the request was rejected because a form field could not be interpreted. */ -case class MalformedFormFieldRejection(fieldName: String, errorMsg: String, - cause: Option[Throwable] = None) extends Rejection +final case class MalformedFormFieldRejection(fieldName: String, errorMsg: String, cause: Option[Throwable] = None) + extends jserver.MalformedFormFieldRejection with RejectionWithOptionalCause /** * Rejection created by header directives. * Signals that the request was rejected because a required header could not be found. */ -case class MissingHeaderRejection(headerName: String) extends Rejection +final case class MissingHeaderRejection(headerName: String) + extends jserver.MissingHeaderRejection with Rejection /** * Rejection created by header directives. * Signals that the request was rejected because a header value is malformed. */ -case class MalformedHeaderRejection(headerName: String, errorMsg: String, - cause: Option[Throwable] = None) extends Rejection +final case class MalformedHeaderRejection(headerName: String, errorMsg: String, cause: Option[Throwable] = None) + extends jserver.MalformedHeaderRejection with RejectionWithOptionalCause /** * Rejection created by unmarshallers. * Signals that the request was rejected because the requests content-type is unsupported. */ -case class UnsupportedRequestContentTypeRejection(supported: immutable.Set[ContentTypeRange]) extends Rejection +final case class UnsupportedRequestContentTypeRejection(supported: immutable.Set[ContentTypeRange]) + extends jserver.UnsupportedRequestContentTypeRejection with Rejection { + override def getSupported: java.util.Set[model.ContentTypeRange] = + scala.collection.mutable.Set(supported.map(_.asJava).toVector: _*).asJava // TODO optimise +} /** * Rejection created by decoding filters. * Signals that the request was rejected because the requests content encoding is unsupported. */ -case class UnsupportedRequestEncodingRejection(supported: HttpEncoding) extends Rejection +final case class UnsupportedRequestEncodingRejection(supported: HttpEncoding) + extends jserver.UnsupportedRequestEncodingRejection with Rejection /** * Rejection created by range directives. * Signals that the request was rejected because the requests contains only unsatisfiable ByteRanges. * The actualEntityLength gives the client a hint to create satisfiable ByteRanges. */ -case class UnsatisfiableRangeRejection(unsatisfiableRanges: immutable.Seq[ByteRange], actualEntityLength: Long) extends Rejection +final case class UnsatisfiableRangeRejection(unsatisfiableRanges: immutable.Seq[ByteRange], actualEntityLength: Long) + extends jserver.UnsatisfiableRangeRejection with Rejection { + override def getUnsatisfiableRanges: Iterable[model.headers.ByteRange] = unsatisfiableRanges.map(_.asJava).asJava +} /** * Rejection created by range directives. * Signals that the request contains too many ranges. An irregular high number of ranges * indicates a broken client or a denial of service attack. */ -case class TooManyRangesRejection(maxRanges: Int) extends Rejection +final case class TooManyRangesRejection(maxRanges: Int) + extends jserver.TooManyRangesRejection with Rejection /** * Rejection created by unmarshallers. @@ -99,87 +132,99 @@ case class TooManyRangesRejection(maxRanges: Int) extends Rejection * Note that semantic issues with the request content (e.g. because some parameter was out of range) * will usually trigger a `ValidationRejection` instead. */ -case class MalformedRequestContentRejection(message: String, cause: Throwable) extends Rejection +final case class MalformedRequestContentRejection(message: String, cause: Throwable) + extends jserver.MalformedRequestContentRejection with Rejection { override def getCause: Throwable = cause } /** * Rejection created by unmarshallers. * Signals that the request was rejected because an message body entity was expected but not supplied. */ -case object RequestEntityExpectedRejection extends Rejection +case object RequestEntityExpectedRejection + extends jserver.RequestEntityExpectedRejection with Rejection /** * Rejection created by marshallers. * Signals that the request was rejected because the service is not capable of producing a response entity whose * content type is accepted by the client */ -case class UnacceptedResponseContentTypeRejection(supported: immutable.Set[ContentNegotiator.Alternative]) extends Rejection +final case class UnacceptedResponseContentTypeRejection(supported: immutable.Set[ContentNegotiator.Alternative]) + extends jserver.UnacceptedResponseContentTypeRejection with Rejection /** * Rejection created by encoding filters. * Signals that the request was rejected because the service is not capable of producing a response entity whose * content encoding is accepted by the client */ -case class UnacceptedResponseEncodingRejection(supported: immutable.Set[HttpEncoding]) extends Rejection +final case class UnacceptedResponseEncodingRejection(supported: immutable.Set[HttpEncoding]) + extends jserver.UnacceptedResponseEncodingRejection with Rejection { + override def getSupported: java.util.Set[model.headers.HttpEncoding] = + scala.collection.mutable.Set(supported.map(_.asJava).toVector: _*).asJava // TODO optimise +} object UnacceptedResponseEncodingRejection { def apply(supported: HttpEncoding): UnacceptedResponseEncodingRejection = UnacceptedResponseEncodingRejection(Set(supported)) } /** - * Rejection created by an [[akka.http.javadsl.server.values.HttpBasicAuthenticator]]. + * Rejection created by the various [[akka.http.scaladsl.server.directives.SecurityDirectives]]. * Signals that the request was rejected because the user could not be authenticated. The reason for the rejection is * specified in the cause. */ -case class AuthenticationFailedRejection(cause: AuthenticationFailedRejection.Cause, - challenge: HttpChallenge) extends Rejection +final case class AuthenticationFailedRejection(cause: AuthenticationFailedRejection.Cause, challenge: HttpChallenge) + extends jserver.AuthenticationFailedRejection with Rejection object AuthenticationFailedRejection { /** * Signals the cause of the failed authentication. */ - sealed trait Cause + sealed trait Cause extends jserver.AuthenticationFailedRejection.Cause /** * Signals the cause of the rejecting was that the user could not be authenticated, because the `WWW-Authenticate` * header was not supplied. */ - case object CredentialsMissing extends Cause + case object CredentialsMissing extends jserver.AuthenticationFailedRejection.CredentialsMissing with Cause /** * Signals the cause of the rejecting was that the user could not be authenticated, because the supplied credentials * are invalid. */ - case object CredentialsRejected extends Cause + case object CredentialsRejected extends jserver.AuthenticationFailedRejection.CredentialsRejected with Cause } /** * Rejection created by the 'authorize' directive. * Signals that the request was rejected because the user is not authorized. */ -case object AuthorizationFailedRejection extends Rejection +case object AuthorizationFailedRejection + extends jserver.AuthorizationFailedRejection with Rejection /** * Rejection created by the `cookie` directive. * Signals that the request was rejected because a cookie was not found. */ -case class MissingCookieRejection(cookieName: String) extends Rejection +final case class MissingCookieRejection(cookieName: String) + extends jserver.MissingCookieRejection with Rejection /** * Rejection created when a websocket request was expected but none was found. */ -case object ExpectedWebSocketRequestRejection extends Rejection +case object ExpectedWebSocketRequestRejection + extends jserver.ExpectedWebSocketRequestRejection with Rejection /** * Rejection created when a websocket request was not handled because none of the given subprotocols * was supported. */ -case class UnsupportedWebSocketSubprotocolRejection(supportedProtocol: String) extends Rejection +final case class UnsupportedWebSocketSubprotocolRejection(supportedProtocol: String) + extends jserver.UnsupportedWebSocketSubprotocolRejection with Rejection /** * Rejection created by the `validation` directive as well as for `IllegalArgumentExceptions` * thrown by domain model constructors (e.g. via `require`). * It signals that an expected value was semantically invalid. */ -case class ValidationRejection(message: String, cause: Option[Throwable] = None) extends Rejection +final case class ValidationRejection(message: String, cause: Option[Throwable] = None) + extends jserver.ValidationRejection with RejectionWithOptionalCause /** * A special Rejection that serves as a container for a transformation function on rejections. @@ -187,7 +232,9 @@ case class ValidationRejection(message: String, cause: Option[Throwable] = None) * * Consider this route structure for example: * + * {{{ * put { reject(ValidationRejection("no") } ~ get { ... } + * }}} * * If this structure is applied to a PUT request the list of rejections coming back contains three elements: * @@ -196,10 +243,16 @@ case class ValidationRejection(message: String, cause: Option[Throwable] = None) * 3. A TransformationRejection holding a function filtering out the MethodRejection * * so that in the end the RejectionHandler will only see one rejection (the ValidationRejection), because the - * MethodRejection added by the `get` directive is canceled by the `put` directive (since the HTTP method + * MethodRejection added by the get` directive is canceled by the `put` directive (since the HTTP method * did indeed match eventually). */ -case class TransformationRejection(transform: immutable.Seq[Rejection] ⇒ immutable.Seq[Rejection]) extends Rejection +final case class TransformationRejection(transform: immutable.Seq[Rejection] ⇒ immutable.Seq[Rejection]) + extends jserver.TransformationRejection with Rejection { + override def getTransform = new Function[Iterable[jserver.Rejection], Iterable[jserver.Rejection]] { + override def apply(t: Iterable[jserver.Rejection]): Iterable[jserver.Rejection] = + transform(Util.immutableSeq(t).map(x ⇒ x.asScala)).map(_.asJava).asJava // TODO "asJavaDeep" and optimise? + } +} /** * A Throwable wrapping a Rejection. @@ -207,4 +260,4 @@ case class TransformationRejection(transform: immutable.Seq[Rejection] ⇒ immut * rejection rather than an Exception that is handled by the nearest ExceptionHandler. * (Custom marshallers can of course use it as well.) */ -case class RejectionError(rejection: Rejection) extends RuntimeException +final case class RejectionError(rejection: Rejection) extends RuntimeException diff --git a/akka-http/src/main/scala/akka/http/scaladsl/server/RouteConcatenation.scala b/akka-http/src/main/scala/akka/http/scaladsl/server/RouteConcatenation.scala index 0e7cd19bd8..9d39ffff2b 100644 --- a/akka-http/src/main/scala/akka/http/scaladsl/server/RouteConcatenation.scala +++ b/akka-http/src/main/scala/akka/http/scaladsl/server/RouteConcatenation.scala @@ -17,7 +17,7 @@ trait RouteConcatenation { /** * @group concat */ - implicit def enhanceRouteWithConcatenation(route: Route): RouteConcatenation.RouteWithConcatenation = + implicit def _enhanceRouteWithConcatenation(route: Route): RouteConcatenation.RouteWithConcatenation = new RouteConcatenation.RouteWithConcatenation(route: Route) /** diff --git a/akka-http/src/main/scala/akka/http/scaladsl/server/RouteResult.scala b/akka-http/src/main/scala/akka/http/scaladsl/server/RouteResult.scala index d871e068c2..39e18c4034 100644 --- a/akka-http/src/main/scala/akka/http/scaladsl/server/RouteResult.scala +++ b/akka-http/src/main/scala/akka/http/scaladsl/server/RouteResult.scala @@ -11,6 +11,8 @@ import akka.http.scaladsl.settings.{ RoutingSettings, ParserSettings } import akka.stream.Materializer import akka.stream.scaladsl.Flow import akka.http.scaladsl.model.{ HttpRequest, HttpResponse } +import akka.http.javadsl +import scala.collection.JavaConverters._ /** * The result of handling a request. @@ -18,11 +20,15 @@ import akka.http.scaladsl.model.{ HttpRequest, HttpResponse } * As a user you typically don't create RouteResult instances directly. * Instead, use the methods on the [[RequestContext]] to achieve the desired effect. */ -sealed trait RouteResult +sealed trait RouteResult extends javadsl.server.RouteResult object RouteResult { - final case class Complete(response: HttpResponse) extends RouteResult - final case class Rejected(rejections: immutable.Seq[Rejection]) extends RouteResult + final case class Complete(response: HttpResponse) extends javadsl.server.Complete with RouteResult { + override def getResponse = response + } + final case class Rejected(rejections: immutable.Seq[Rejection]) extends javadsl.server.Rejected with RouteResult { + override def getRejections = rejections.map(r ⇒ r: javadsl.server.Rejection).toIterable.asJava + } implicit def route2HandlerFlow(route: Route)(implicit routingSettings: RoutingSettings, parserSettings: ParserSettings, diff --git a/akka-http/src/main/scala/akka/http/scaladsl/server/directives/DebuggingDirectives.scala b/akka-http/src/main/scala/akka/http/scaladsl/server/directives/DebuggingDirectives.scala index 03805f04e0..6d4ac5cf37 100644 --- a/akka-http/src/main/scala/akka/http/scaladsl/server/directives/DebuggingDirectives.scala +++ b/akka-http/src/main/scala/akka/http/scaladsl/server/directives/DebuggingDirectives.scala @@ -8,6 +8,7 @@ package directives import akka.event.Logging._ import akka.event.LoggingAdapter import akka.http.scaladsl.model._ +import akka.http.javadsl /** * @groupname debugging Debugging directives @@ -93,10 +94,12 @@ object LoggingMagnet { } } -case class LogEntry(obj: Any, level: LogLevel = DebugLevel) { +case class LogEntry(obj: Any, level: LogLevel = DebugLevel) extends javadsl.server.directives.LogEntry { def logTo(log: LoggingAdapter): Unit = { log.log(level, obj.toString) } + override def getObj: Any = obj + override def getLevel: LogLevel = level } object LogEntry { diff --git a/akka-http/src/main/scala/akka/http/scaladsl/server/directives/FileAndResourceDirectives.scala b/akka-http/src/main/scala/akka/http/scaladsl/server/directives/FileAndResourceDirectives.scala index 23c7d43466..ef954566dd 100644 --- a/akka-http/src/main/scala/akka/http/scaladsl/server/directives/FileAndResourceDirectives.scala +++ b/akka-http/src/main/scala/akka/http/scaladsl/server/directives/FileAndResourceDirectives.scala @@ -8,16 +8,22 @@ package directives import java.io.File import java.net.{ URI, URL } +import akka.http.javadsl.{ RoutingJavaMapping, model } +import akka.http.javadsl.model.RequestEntity import akka.stream.ActorAttributes import akka.stream.scaladsl.{ FileIO, StreamConverters } import scala.annotation.tailrec import akka.actor.ActorSystem import akka.event.LoggingAdapter -import akka.http.scaladsl.marshalling.{ Marshaller, ToEntityMarshaller } +import akka.http.scaladsl.marshalling.{ Marshalling, Marshaller, ToEntityMarshaller } import akka.http.scaladsl.model._ import akka.http.scaladsl.model.headers._ import akka.http.impl.util._ +import akka.http.javadsl +import scala.collection.JavaConverters._ +import JavaMapping.Implicits._ +import akka.http.javadsl.RoutingJavaMapping._ /** * @groupname fileandresource File and resource directives @@ -94,7 +100,7 @@ trait FileAndResourceDirectives { * * @group fileandresource */ - def getFromResource(resourceName: String, contentType: ContentType, classLoader: ClassLoader = defaultClassLoader): Route = + def getFromResource(resourceName: String, contentType: ContentType, classLoader: ClassLoader = _defaultClassLoader): Route = if (!resourceName.endsWith("/")) get { Option(classLoader.getResource(resourceName)) flatMap ResourceFile.apply match { @@ -188,7 +194,7 @@ trait FileAndResourceDirectives { * * @group fileandresource */ - def getFromResourceDirectory(directoryName: String, classLoader: ClassLoader = defaultClassLoader)(implicit resolver: ContentTypeResolver): Route = { + def getFromResourceDirectory(directoryName: String, classLoader: ClassLoader = _defaultClassLoader)(implicit resolver: ContentTypeResolver): Route = { val base = if (directoryName.isEmpty) "" else withTrailingSlash(directoryName) extractUnmatchedPath { path ⇒ @@ -201,7 +207,7 @@ trait FileAndResourceDirectives { } } - protected[http] def defaultClassLoader: ClassLoader = classOf[ActorSystem].getClassLoader + protected[http] def _defaultClassLoader: ClassLoader = classOf[ActorSystem].getClassLoader } object FileAndResourceDirectives extends FileAndResourceDirectives { @@ -258,8 +264,20 @@ object FileAndResourceDirectives extends FileAndResourceDirectives { } case class ResourceFile(url: URL, length: Long, lastModified: Long) - trait DirectoryRenderer { + trait DirectoryRenderer extends akka.http.javadsl.server.directives.DirectoryRenderer { + type JDL = akka.http.javadsl.server.directives.DirectoryListing + type SDL = akka.http.scaladsl.server.directives.DirectoryListing + type SRE = akka.http.scaladsl.model.RequestEntity + type JRE = akka.http.javadsl.model.RequestEntity + def marshaller(renderVanityFooter: Boolean): ToEntityMarshaller[DirectoryListing] + + final override def directoryMarshaller(renderVanityFooter: Boolean): akka.http.javadsl.server.Marshaller[JDL, JRE] = { + val combined = Marshaller.combined[JDL, SDL, SRE](x ⇒ JavaMapping.toScala(x)(RoutingJavaMapping.convertDirectoryListing))(marshaller(renderVanityFooter)) + .map(_.asJava) + akka.http.javadsl.server.Marshaller.fromScala(combined) + } + } trait LowLevelDirectoryRenderer { implicit def defaultDirectoryRenderer: DirectoryRenderer = @@ -276,8 +294,9 @@ object FileAndResourceDirectives extends FileAndResourceDirectives { } } -trait ContentTypeResolver { +trait ContentTypeResolver extends akka.http.javadsl.server.directives.ContentTypeResolver { def apply(fileName: String): ContentType + final override def resolve(fileName: String): model.ContentType = apply(fileName) } object ContentTypeResolver { @@ -311,7 +330,10 @@ object ContentTypeResolver { } } -case class DirectoryListing(path: String, isRoot: Boolean, files: Seq[File]) +final case class DirectoryListing(path: String, isRoot: Boolean, files: Seq[File]) extends javadsl.server.directives.DirectoryListing { + override def getPath: String = path + override def getFiles: java.util.List[File] = files.asJava +} object DirectoryListing { diff --git a/akka-http/src/main/scala/akka/http/scaladsl/server/directives/FileUploadDirectives.scala b/akka-http/src/main/scala/akka/http/scaladsl/server/directives/FileUploadDirectives.scala index b7ba4718c4..36045a88f7 100644 --- a/akka-http/src/main/scala/akka/http/scaladsl/server/directives/FileUploadDirectives.scala +++ b/akka-http/src/main/scala/akka/http/scaladsl/server/directives/FileUploadDirectives.scala @@ -11,6 +11,7 @@ import akka.util.ByteString import scala.concurrent.Future import scala.util.{ Failure, Success } import akka.stream.scaladsl._ +import akka.http.javadsl /** * @groupname fileupload File upload directives @@ -95,4 +96,8 @@ object FileUploadDirectives extends FileUploadDirectives * @param fileName User specified name of the uploaded file * @param contentType Content type of the file */ -final case class FileInfo(fieldName: String, fileName: String, contentType: ContentType) +final case class FileInfo(fieldName: String, fileName: String, contentType: ContentType) extends javadsl.server.directives.FileInfo { + override def getFieldName = fieldName + override def getFileName = fileName + override def getContentType = contentType +} diff --git a/akka-http/src/main/scala/akka/http/scaladsl/server/directives/HeaderDirectives.scala b/akka-http/src/main/scala/akka/http/scaladsl/server/directives/HeaderDirectives.scala index e4a2756f98..cf33994316 100644 --- a/akka-http/src/main/scala/akka/http/scaladsl/server/directives/HeaderDirectives.scala +++ b/akka-http/src/main/scala/akka/http/scaladsl/server/directives/HeaderDirectives.scala @@ -163,6 +163,15 @@ object HeaderMagnet extends LowPriorityHeaderMagnetImplicits { */ implicit def fromUnitForModeledCustomHeader[T <: ModeledCustomHeader[T], H <: ModeledCustomHeaderCompanion[T]] (u: Unit)(implicit tag: ClassTag[T], companion: ModeledCustomHeaderCompanion[T]): HeaderMagnet[T] = + fromClassTagForModeledCustomHeader[T, H](tag, companion) + + + implicit def fromClassForModeledCustomHeader[T <: ModeledCustomHeader[T], H <: ModeledCustomHeaderCompanion[T]] + (clazz: Class[T], companion: ModeledCustomHeaderCompanion[T]): HeaderMagnet[T] = + fromClassTagForModeledCustomHeader(ClassTag(clazz), companion) + + implicit def fromClassTagForModeledCustomHeader[T <: ModeledCustomHeader[T], H <: ModeledCustomHeaderCompanion[T]] + (tag: ClassTag[T], companion: ModeledCustomHeaderCompanion[T]): HeaderMagnet[T] = new HeaderMagnet[T] { override def runtimeClass = tag.runtimeClass.asInstanceOf[Class[T]] override def classTag = tag @@ -174,10 +183,24 @@ object HeaderMagnet extends LowPriorityHeaderMagnetImplicits { } trait LowPriorityHeaderMagnetImplicits { - implicit def fromUnit[T <: HttpHeader](u: Unit)(implicit tag: ClassTag[T]): HeaderMagnet[T] = + implicit def fromClassNormalHeader[T <: HttpHeader](clazz: Class[T]): HeaderMagnet[T] = + fromClassTagNormalHeader(ClassTag(clazz)) + + // TODO DRY? + implicit def fromClassNormalJavaHeader[T <: akka.http.javadsl.model.HttpHeader](clazz: Class[T]): HeaderMagnet[T] = + new HeaderMagnet[T] { + override def classTag: ClassTag[T] = ClassTag(clazz) + override def runtimeClass: Class[T] = clazz + override def extractPF: PartialFunction[HttpHeader, T] = { case x if runtimeClass.isAssignableFrom(x.getClass) => x.asInstanceOf[T] } + } + + implicit def fromUnitNormalHeader[T <: HttpHeader](u: Unit)(implicit tag: ClassTag[T]): HeaderMagnet[T] = + fromClassTagNormalHeader(tag) + + implicit def fromClassTagNormalHeader[T <: HttpHeader](tag: ClassTag[T]): HeaderMagnet[T] = new HeaderMagnet[T] { val classTag: ClassTag[T] = tag val runtimeClass: Class[T] = tag.runtimeClass.asInstanceOf[Class[T]] - val extractPF: PartialFunction[Any, T] = { case x: T ⇒ x } + val extractPF: PartialFunction[Any, T] = { case x if runtimeClass.isAssignableFrom(x.getClass) ⇒ x.asInstanceOf[T] } } } diff --git a/akka-http/src/main/scala/akka/http/scaladsl/server/directives/ParameterDirectives.scala b/akka-http/src/main/scala/akka/http/scaladsl/server/directives/ParameterDirectives.scala index 041e4c9573..de711ed89e 100644 --- a/akka-http/src/main/scala/akka/http/scaladsl/server/directives/ParameterDirectives.scala +++ b/akka-http/src/main/scala/akka/http/scaladsl/server/directives/ParameterDirectives.scala @@ -5,6 +5,8 @@ package akka.http.scaladsl.server package directives +import akka.http.javadsl.server.directives.CorrespondsTo + import scala.collection.immutable import scala.concurrent.{ ExecutionContext, Future } import scala.util.{ Failure, Success } diff --git a/akka-http/src/main/scala/akka/http/scaladsl/server/package.scala b/akka-http/src/main/scala/akka/http/scaladsl/server/package.scala index c332367a97..2b82ed0dd9 100644 --- a/akka-http/src/main/scala/akka/http/scaladsl/server/package.scala +++ b/akka-http/src/main/scala/akka/http/scaladsl/server/package.scala @@ -9,6 +9,7 @@ import scala.concurrent.Future package object server { type Route = RequestContext ⇒ Future[RouteResult] + type RouteGenerator[T] = T ⇒ Route type Directive0 = Directive[Unit] type Directive1[T] = Directive[Tuple1[T]] @@ -16,4 +17,4 @@ package object server { type PathMatcher1[T] = PathMatcher[Tuple1[T]] def FIXME = throw new RuntimeException("Not yet implemented") -} \ No newline at end of file +} diff --git a/akka-http/src/main/scala/akka/http/scaladsl/unmarshalling/Unmarshaller.scala b/akka-http/src/main/scala/akka/http/scaladsl/unmarshalling/Unmarshaller.scala index dd1a7984b5..289c5dcd87 100644 --- a/akka-http/src/main/scala/akka/http/scaladsl/unmarshalling/Unmarshaller.scala +++ b/akka-http/src/main/scala/akka/http/scaladsl/unmarshalling/Unmarshaller.scala @@ -13,13 +13,18 @@ import akka.http.scaladsl.util.FastFuture import akka.http.scaladsl.util.FastFuture._ import akka.http.scaladsl.model._ -trait Unmarshaller[-A, B] { +trait Unmarshaller[-A, B] extends akka.http.javadsl.server.Unmarshaller[A, B] { + + implicit final def asScala: Unmarshaller[A, B] = this def apply(value: A)(implicit ec: ExecutionContext, materializer: Materializer): Future[B] def transform[C](f: ExecutionContext ⇒ Materializer ⇒ Future[B] ⇒ Future[C]): Unmarshaller[A, C] = Unmarshaller.withMaterializer { implicit ec ⇒ implicit mat ⇒ a ⇒ f(ec)(mat)(this(a)) } + // def contramap[I](f: I ⇒ A): Unmarshaller[I, B] = + // Unmarshaller.strict(i ⇒ f(i)).flatMap(ec ⇒ mat ⇒ apply(_)(ec, mat)) + def map[C](f: B ⇒ C): Unmarshaller[A, C] = transform(implicit ec ⇒ _ ⇒ _.fast map f) @@ -110,8 +115,6 @@ object Unmarshaller } else FastFuture.failed(UnsupportedContentTypeException(ranges: _*)) } - // TODO: move back into the [[EnhancedFromEntityUnmarshaller]] value class after the upgrade to Scala 2.11, - // Scala 2.10 suffers from this bug: https://issues.scala-lang.org/browse/SI-8018 private def barkAtUnsupportedContentTypeException(ranges: Seq[ContentTypeRange], newContentType: ContentType): PartialFunction[Throwable, Nothing] = { case UnsupportedContentTypeException(supported) ⇒ throw new IllegalStateException( diff --git a/async-client/build.sbt b/async-client/build.sbt new file mode 100644 index 0000000000..c098a28f39 --- /dev/null +++ b/async-client/build.sbt @@ -0,0 +1,15 @@ +name := "async-client" + +organization := "async-client" + +version := "0.1-SNAPSHOT" + +scalaVersion := "2.11.7" + +val AkkaVersion = "2.4.0" + +resolvers += Resolver.typesafeRepo("releases") + +libraryDependencies += "com.typesafe.akka" %% "akka-actor" % AkkaVersion + +libraryDependencies += "org.asynchttpclient" % "async-http-client" % "2.0.2" diff --git a/async-client/src/main/scala/Main.scala b/async-client/src/main/scala/Main.scala new file mode 100644 index 0000000000..bda8d1ea20 --- /dev/null +++ b/async-client/src/main/scala/Main.scala @@ -0,0 +1,22 @@ +import org.asynchttpclient._ +import java.util.concurrent.Future +import scala.concurrent.duration._ +import scala.concurrent.Await + +object Run extends App { + + val asyncHttpClient = new DefaultAsyncHttpClient(); + + + val t = System.currentTimeMillis + asyncHttpClient.prepareGet("http://www.example.com/").execute().get(); + println("(System.currentTimeMillis() - t) = " + (System.currentTimeMillis() - t) + "ms") + + val t1 = System.currentTimeMillis + asyncHttpClient.prepareGet("http://www.example.com/").execute().get(); + println("(System.currentTimeMillis() - t) = " + (System.currentTimeMillis() - t1) + "ms") + + val t2 = System.currentTimeMillis + asyncHttpClient.prepareGet("http://www.example.com/").execute().get(); + println("(System.currentTimeMillis() - t) = " + (System.currentTimeMillis() - t2) + "ms") +} diff --git a/project/MiMa.scala b/project/MiMa.scala index 4bd50b314d..b9c2b17b04 100644 --- a/project/MiMa.scala +++ b/project/MiMa.scala @@ -33,13 +33,16 @@ object MiMa extends AutoPlugin { ) val akka242NewArtifacts = Seq( "akka-stream", - "akka-stream-testkit", "akka-http-core", - "akka-http-experimental", + "akka-http-testkit", - "akka-http-jackson-experimental", - "akka-http-spray-json-experimental", - "akka-http-xml-experimental" + "akka-stream-testkit" + + // TODO enable once not experimental anymore + // "akka-http-experimental", + // "akka-http-jackson-experimental", + // "akka-http-spray-json-experimental", + // "akka-http-xml-experimental" ) scalaBinaryVersion match { case "2.11" if !(akka24NewArtifacts ++ akka242NewArtifacts).contains(projectName) => akka23Versions ++ akka24NoStreamVersions ++ akka24StreamVersions @@ -705,20 +708,38 @@ object MiMa extends AutoPlugin { // #19849 content negotiation fixes ProblemFilters.exclude[FinalClassProblem]("akka.http.scaladsl.marshalling.Marshal$UnacceptableResponseContentTypeException"), + // #20009 internal and shouldn't have been public + ProblemFilters.exclude[DirectMissingMethodProblem]("akka.stream.impl.QueueSource.completion"), + + // #20015 simplify materialized value computation tree + ProblemFilters.exclude[FinalMethodProblem]("akka.stream.impl.StreamLayout#AtomicModule.subModules"), + ProblemFilters.exclude[FinalMethodProblem]("akka.stream.impl.StreamLayout#AtomicModule.downstreams"), + ProblemFilters.exclude[FinalMethodProblem]("akka.stream.impl.StreamLayout#AtomicModule.upstreams"), + ProblemFilters.exclude[FinalMethodProblem]("akka.stream.impl.Stages#DirectProcessor.toString"), + ProblemFilters.exclude[IncompatibleMethTypeProblem]("akka.stream.impl.MaterializerSession.materializeAtomic"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.stream.impl.MaterializerSession.materializeAtomic"), + ProblemFilters.exclude[MissingTypesProblem]("akka.stream.impl.Stages$StageModule"), + ProblemFilters.exclude[FinalMethodProblem]("akka.stream.impl.Stages#GroupBy.toString"), + ProblemFilters.exclude[MissingTypesProblem]("akka.stream.impl.FlowModule"), + ProblemFilters.exclude[DirectMissingMethodProblem]("akka.stream.impl.FlowModule.subModules"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.stream.impl.FlowModule.label"), + ProblemFilters.exclude[FinalClassProblem]("akka.stream.impl.fusing.GraphModule"), + // #15947 catch mailbox creation failures ProblemFilters.exclude[DirectMissingMethodProblem]("akka.actor.RepointableActorRef.point"), ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.actor.dungeon.Dispatch.initWithFailure"), + // #19877 Source.queue termination support + ProblemFilters.exclude[IncompatibleMethTypeProblem]("akka.stream.impl.SourceQueueAdapter.this"), + // #19828 ProblemFilters.exclude[DirectAbstractMethodProblem]("akka.persistence.Eventsourced#ProcessingState.onWriteMessageComplete"), ProblemFilters.exclude[ReversedAbstractMethodProblem]("akka.persistence.Eventsourced#ProcessingState.onWriteMessageComplete"), // #19390 Add flow monitor - ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.stream.scaladsl.FlowOpsMat.monitor") - ), - "2.4.3" -> Seq( - // internal api - FilterAnyProblemStartingWith("akka.stream.impl"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.stream.scaladsl.FlowOpsMat.monitor"), + ProblemFilters.exclude[MissingClassProblem]("akka.stream.impl.fusing.GraphStages$TickSource$"), + FilterAnyProblemStartingWith("akka.http.impl"), // #20214 @@ -794,6 +815,14 @@ object MiMa extends AutoPlugin { // #20131 - flow combinator ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.stream.scaladsl.FlowOps.backpressureTimeout"), + + // #20470 - new JavaDSL for Akka HTTP + ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.javadsl.model.DateTime.plus"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.javadsl.model.DateTime.minus"), + + // #20214 + ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.scaladsl.DefaultSSLContextCreation.createClientHttpsContext"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.scaladsl.DefaultSSLContextCreation.validateAndWarnAboutLooseSettings"), // #20257 Snapshots with PersistentFSM (experimental feature) ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.persistence.serialization.MessageFormats#PersistentStateChangeEventOrBuilder.getTimeoutNanos"),