diff --git a/akka-docs-dev/rst/java/code/docs/http/javadsl/HandlerExampleSpec.java b/akka-docs-dev/rst/java/code/docs/http/javadsl/HandlerExampleSpec.java index 79ce476add..22c61fb79c 100644 --- a/akka-docs-dev/rst/java/code/docs/http/javadsl/HandlerExampleSpec.java +++ b/akka-docs-dev/rst/java/code/docs/http/javadsl/HandlerExampleSpec.java @@ -63,8 +63,8 @@ public class HandlerExampleSpec extends JUnitRouteTest { RequestVal xParam = Parameters.intValue("x"); RequestVal yParam = Parameters.intValue("y"); - RequestVal xSegment = PathMatchers.integerNumber(); - RequestVal ySegment = PathMatchers.integerNumber(); + RequestVal xSegment = PathMatchers.intValue(); + RequestVal ySegment = PathMatchers.intValue(); //#handler2 Handler2 multiply = @@ -114,8 +114,8 @@ public class HandlerExampleSpec extends JUnitRouteTest { RequestVal xParam = Parameters.intValue("x"); RequestVal yParam = Parameters.intValue("y"); - RequestVal xSegment = PathMatchers.integerNumber(); - RequestVal ySegment = PathMatchers.integerNumber(); + RequestVal xSegment = PathMatchers.intValue(); + RequestVal ySegment = PathMatchers.intValue(); //#reflective diff --git a/akka-docs-dev/rst/java/code/docs/http/javadsl/PathDirectiveExampleTest.java b/akka-docs-dev/rst/java/code/docs/http/javadsl/PathDirectiveExampleTest.java new file mode 100644 index 0000000000..de41f7ace4 --- /dev/null +++ b/akka-docs-dev/rst/java/code/docs/http/javadsl/PathDirectiveExampleTest.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2009-2015 Typesafe Inc. + */ + +package docs.http.javadsl; + +import akka.http.javadsl.model.HttpRequest; +import akka.http.javadsl.model.StatusCodes; +import akka.http.javadsl.server.*; +import akka.http.javadsl.server.values.Parameters; +import akka.http.javadsl.server.values.PathMatcher; +import akka.http.javadsl.server.values.PathMatchers; +import akka.http.javadsl.testkit.JUnitRouteTest; +import akka.http.javadsl.testkit.TestRoute; +import org.junit.Test; + +public class PathDirectiveExampleTest extends JUnitRouteTest { + @Test + public void testPathPrefix() { + //#path-examples + // matches "/test" + path("test").route( + completeWithStatus(StatusCodes.OK) + ); + + // matches "/test", as well + path(PathMatchers.segment("test")).route( + completeWithStatus(StatusCodes.OK) + ); + + // matches "/admin/user" + path("admin", "user").route( + completeWithStatus(StatusCodes.OK) + ); + + // matches "/admin/user", as well + pathPrefix("admin").route( + path("user").route( + completeWithStatus(StatusCodes.OK) + ) + ); + + // matches "/admin/user/" + Handler1 completeWithUserId = + new Handler1() { + @Override + public RouteResult handle(RequestContext ctx, Integer userId) { + return ctx.complete("Hello user " + userId); + } + }; + PathMatcher userId = PathMatchers.intValue(); + pathPrefix("admin", "user").route( + path(userId).route( + handleWith1(userId, completeWithUserId) + ) + ); + + // matches "/admin/user/", as well + path("admin", "user", userId).route( + handleWith1(userId, completeWithUserId) + ); + + // never matches + path("admin").route( // oops this only matches "/admin" + path("user").route( + completeWithStatus(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 + } + + // FIXME: remove once #17988 is merged + public static Route handleWith1(RequestVal val, Object o) { + return null; + } +} \ No newline at end of file diff --git a/akka-docs-dev/rst/java/http/index.rst b/akka-docs-dev/rst/java/http/index.rst index a3547ea710..d2094d0701 100644 --- a/akka-docs-dev/rst/java/http/index.rst +++ b/akka-docs-dev/rst/java/http/index.rst @@ -3,13 +3,39 @@ Akka HTTP ========= +The Akka HTTP modules implement a full server- and client-side HTTP stack on top of *akka-actor* and *akka-stream*. It's +not a web-framework but rather a more general toolkit for providing and consuming HTTP-based services. While interaction +with a browser is of course also in scope it is not the primary focus of Akka HTTP. + +Akka HTTP follows a rather open design and many times offers several different API levels for "doing the same thing". +You get to pick the API level of abstraction that is most suitable for your application. +This means that, if you have trouble achieving something using a high-level API, there's a good chance that you can get +it done with a low-level API, which offers more flexibility but might require you to write more application code. + +Akka HTTP is structured into several modules: + +akka-http-core + A complete, mostly low-level, server- and client-side implementation of HTTP (incl. WebSockets). + Includes a model of all things HTTP. + +akka-http + Higher-level functionality, like (un)marshalling, (de)compression as well as a powerful DSL + for defining HTTP-based APIs on the server-side + +akka-http-testkit + A test harness and set of utilities for verifying server-side service implementations + +akka-http-jackson + Predefined glue-code for (de)serializing custom types from/to JSON with jackson_ + .. toctree:: :maxdepth: 2 - introduction configuration http-model server-side/low-level-server-side-api server-side/websocket-support routing-dsl/index - client-side/index \ No newline at end of file + client-side/index + +.. _jackson: https://github.com/FasterXML/jackson \ No newline at end of file diff --git a/akka-docs-dev/rst/java/http/introduction.rst b/akka-docs-dev/rst/java/http/introduction.rst deleted file mode 100644 index d7080f2a1c..0000000000 --- a/akka-docs-dev/rst/java/http/introduction.rst +++ /dev/null @@ -1,28 +0,0 @@ -Introduction -============ - -The Akka HTTP modules implement a full server- and client-side HTTP stack on top of *akka-actor* and *akka-stream*. It's -not a web-framework but rather a more general toolkit for providing and consuming HTTP-based services. While interaction -with a browser is of course also in scope it is not the primary focus of Akka HTTP. - -Akka HTTP follows a rather open design and many times offers several different API levels for "doing the same thing". -You get to pick the API level of abstraction that is most suitable for your application. -This means that, if you have trouble achieving something using a high-level API, there's a good chance that you can get -it done with a low-level API, which offers more flexibility but might require you to write more application code. - -Akka HTTP is structured into several modules: - -akka-http-core - A complete, mostly low-level, server- and client-side implementation of HTTP (incl. WebSockets) - -akka-http - Higher-level functionality, like (un)marshalling, (de)compression as well as a powerful DSL - for defining HTTP-based APIs on the server-side - -akka-http-testkit - A test harness and set of utilities for verifying server-side service implementations - -akka-http-jackson - Predefined glue-code for (de)serializing custom types from/to JSON with jackson_ - -.. _jackson: https://github.com/FasterXML/jackson diff --git a/akka-docs-dev/rst/java/http/routing-dsl/directives/index.rst b/akka-docs-dev/rst/java/http/routing-dsl/directives/index.rst index c72ee6c145..846e1d1756 100644 --- a/akka-docs-dev/rst/java/http/routing-dsl/directives/index.rst +++ b/akka-docs-dev/rst/java/http/routing-dsl/directives/index.rst @@ -3,4 +3,64 @@ Directives ========== -TODO \ No newline at end of file +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:`PathDirectives-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. + +.. toctree:: + :maxdepth: 1 + + path-directives + +.. _`RFC 7234`: http://tools.ietf.org/html/rfc7234 \ No newline at end of file diff --git a/akka-docs-dev/rst/java/http/routing-dsl/directives/path-directives.rst b/akka-docs-dev/rst/java/http/routing-dsl/directives/path-directives.rst new file mode 100644 index 0000000000..1530003fae --- /dev/null +++ b/akka-docs-dev/rst/java/http/routing-dsl/directives/path-directives.rst @@ -0,0 +1,95 @@ +.. _PathDirectives-java: + +PathDirectives +============== + +Path directives are the most basic building blocks for routing requests depending on the URI path. + +When a request (or rather the respective ``RequestContext`` instance) enters the route structure it has an +"unmatched path" that is identical to the ``request.uri.path``. As it descends the routing tree and passes through one +or more ``pathPrefix`` or ``path`` directives the "unmatched path" progressively gets "eaten into" from the +left until, in most cases, it eventually has been consumed completely. + +The two main directives are ``path`` and ``pathPrefix``. The ``path`` directive tries to match the complete remaining +unmatched path against the specified "path matchers", the ``pathPrefix`` directive only matches a prefix and passes the +remaining unmatched path to nested directives. Both directives automatically match a slash from the beginning, so +that matching slashes in a hierarchy of nested ``pathPrefix`` and ``path`` directives is usually not needed. + +Path directives take a variable amount of arguments. Each argument must be a ``PathMatcher`` or a string (which is +automatically converted to a path matcher using ``PathMatchers.segment``). In the case of ``path`` and ``pathPrefix``, +if multiple arguments are supplied, a slash is assumed between any of the supplied path matchers. The ``rawPathX`` +variants of those directives on the other side do no such preprocessing, so that slashes must be matched manually. + +Path Matchers +------------- + +A path matcher is a description of a part of a path to match. The simplest path matcher is ``PathMatcher.segment`` which +matches exactly one path segment against the supplied constant string. + +Other path matchers defined in ``PathMatchers`` match the end of the path (``PathMatchers.END``), a single slash +(``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. + +Predefined path matchers allow extraction of various types of values: + +``PathMatchers.segment(String)`` + Strings simply match themselves and extract no value. + Note that strings are interpreted as the decoded representation of the path, so if they include a '/' character + this character will match "%2F" in the encoded raw URI! + +``PathMatchers.regex`` + You can use a regular expression instance as a path matcher, which matches whatever the regex matches and extracts + one ``String`` value. A ``PathMatcher`` created from a regular expression extracts either the complete match (if the + regex doesn't contain a capture group) or the capture group (if the regex contains exactly one capture group). + If the regex contains more than one capture group an ``IllegalArgumentException`` will be thrown. + +``PathMatchers.SLASH`` + Matches exactly one path-separating slash (``/``) character. + +``PathMatchers.END`` + Matches the very end of the path, similar to ``$`` in regular expressions. + +``PathMatchers.Segment`` + Matches if the unmatched path starts with a path segment (i.e. not a slash). + If so the path segment is extracted as a ``String`` instance. + +``PathMatchers.Rest`` + 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 *decoded* elements of the path use ``RestPath`` instead. + +``PathMatchers.intValue`` + Efficiently matches a number of decimal digits (unsigned) and extracts their (non-negative) ``Int`` value. The matcher + will not match zero digits or a sequence of digits that would represent an ``Int`` value larger than ``Integer.MAX_VALUE``. + +``PathMatchers.longValue`` + Efficiently matches a number of decimal digits (unsigned) and extracts their (non-negative) ``Long`` value. The matcher + will not match zero digits or a sequence of digits that would represent an ``Long`` value larger than ``Long.MAX_VALUE``. + +``PathMatchers.hexIntValue`` + Efficiently matches a number of hex digits and extracts their (non-negative) ``Int`` value. The matcher will not match + zero digits or a sequence of digits that would represent an ``Int`` value larger than ``Integer.MAX_VALUE``. + +``PathMatchers.hexLongValue`` + Efficiently matches a number of hex digits and extracts their (non-negative) ``Long`` value. The matcher will not + match zero digits or a sequence of digits that would represent an ``Long`` value larger than ``Long.MAX_VALUE``. + +``PathMatchers.uuid`` + Matches and extracts a ``java.util.UUID`` instance. + +``PathMatchers.NEUTRAL`` + A matcher that always matches, doesn't consume anything and extracts nothing. + Serves mainly as a neutral element in ``PathMatcher`` composition. + +``PathMatchers.segments`` + Matches all remaining segments as a list of strings. Note that this can also be "no segments" resulting in the empty + list. If the path has a trailing slash this slash will *not* be matched, i.e. remain unmatched and to be consumed by + potentially nested directives. + +Here's a collection of path matching examples: + +.. includecode:: ../../../code/docs/http/javadsl/PathDirectiveExampleTest.java + :include: path-examples \ No newline at end of file diff --git a/akka-docs-dev/rst/java/http/routing-dsl/handlers.rst b/akka-docs-dev/rst/java/http/routing-dsl/handlers.rst index 57b0710f3d..205199db52 100644 --- a/akka-docs-dev/rst/java/http/routing-dsl/handlers.rst +++ b/akka-docs-dev/rst/java/http/routing-dsl/handlers.rst @@ -59,7 +59,7 @@ route structure, this time representing segments from the URI path. Handlers in Java 8 ------------------ -In Java 8 handlers can be provided as function literals. The example from before then looks like this: +In Java 8 handlers can be provided as function literals. The previous example can then be written like this: .. includecode:: /../../akka-http-tests-java8/src/test/java/docs/http/javadsl/server/HandlerExampleSpec.java :include: handler2-example-full diff --git a/akka-docs-dev/rst/java/http/routing-dsl/index.rst b/akka-docs-dev/rst/java/http/routing-dsl/index.rst index 2ec80185ba..913f2dfc64 100644 --- a/akka-docs-dev/rst/java/http/routing-dsl/index.rst +++ b/akka-docs-dev/rst/java/http/routing-dsl/index.rst @@ -3,7 +3,7 @@ High-level Server-Side API ========================== -... +To use the high-level API you need to add a dependency to the ``akka-http-experimental`` module. .. toctree:: :maxdepth: 1 @@ -13,5 +13,5 @@ High-level Server-Side API directives/index request-vals/index handlers - path-matchers marshalling + json-jackson-support \ No newline at end of file diff --git a/akka-docs-dev/rst/java/http/routing-dsl/json-jackson-support.rst b/akka-docs-dev/rst/java/http/routing-dsl/json-jackson-support.rst new file mode 100644 index 0000000000..13bde7fb36 --- /dev/null +++ b/akka-docs-dev/rst/java/http/routing-dsl/json-jackson-support.rst @@ -0,0 +1,19 @@ +.. _json-jackson-support-java: + +Json Support via Jackson +======================== + +akka-http provides support to convert application-domain objects from and to JSON using 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. + +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. + + +.. _jackson: https://github.com/FasterXML/jackson +__ @github@/akka-http-tests/src/main/java/akka/http/javadsl/server/examples/petstore/PetStoreExample.java \ No newline at end of file diff --git a/akka-docs-dev/rst/java/http/routing-dsl/marshalling.rst b/akka-docs-dev/rst/java/http/routing-dsl/marshalling.rst index fd1c2ec9b9..a12a1cfc7c 100644 --- a/akka-docs-dev/rst/java/http/routing-dsl/marshalling.rst +++ b/akka-docs-dev/rst/java/http/routing-dsl/marshalling.rst @@ -3,4 +3,41 @@ Marshalling & Unmarshalling =========================== -TODO \ No newline at end of file +"Marshalling" is the process of converting a higher-level (object) structure into some kind of lower-level +representation (and vice versa), often a binary 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 an HttpEntity, which forms the entity body +of an HTTP request or response (depending on whether used on the client or server side). + +Marshalling +----------- + +On the server-side marshalling is used to convert a application-domain object to a response (entity). Requests can +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. + +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. + +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``: + + * 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. + +.. _jackson: https://github.com/FasterXML/jackson \ No newline at end of file diff --git a/akka-docs-dev/rst/java/http/routing-dsl/path-matchers.rst b/akka-docs-dev/rst/java/http/routing-dsl/path-matchers.rst deleted file mode 100644 index 59a3834976..0000000000 --- a/akka-docs-dev/rst/java/http/routing-dsl/path-matchers.rst +++ /dev/null @@ -1,6 +0,0 @@ -.. _pathmatcher-dsl-java: - -The PathMatcher DSL -=================== - -TODO \ No newline at end of file diff --git a/akka-docs-dev/rst/java/http/routing-dsl/request-vals/index.rst b/akka-docs-dev/rst/java/http/routing-dsl/request-vals/index.rst index 678db867e3..e808599e77 100644 --- a/akka-docs-dev/rst/java/http/routing-dsl/request-vals/index.rst +++ b/akka-docs-dev/rst/java/http/routing-dsl/request-vals/index.rst @@ -27,11 +27,19 @@ service. These request values are defined: - * in ``RequestVals``: request values for basic data like URI components, request method, peer address, or the entity data - * in ``Cookies``: request values representing cookies - * in ``FormFields``: request values to access form fields unmarshalled to various primitive Java types - * in ``Headers``:: request values to access request headers or header values - * ``HttpBasicAuthenticator``: an abstract class to implement to create a request value representing a HTTP basic authenticated principal - * in ``Parameters``: request values to access URI paramaters unmarshalled to various primitive Java types - * in ``PathMatchers``: request values to match and access URI path segments - * ``CustomRequestVal``: an abstract class to implement arbitrary custom request values +RequestVals + Contains request values for basic data like URI components, request method, peer address, or the entity data. +Cookies + Contains request values representing cookies. +FormFields + Contains request values to access form fields unmarshalled to various primitive Java types. +Headers + Contains request values to access request headers or header values. +HttpBasicAuthenticator + An abstract class to implement to create a request value representing a HTTP basic authenticated principal. +Parameters + Contains request values to access URI paramaters unmarshalled to various primitive Java types. +PathMatchers + Contains request values to match and access URI path segments. +CustomRequestVal + An abstract class to implement arbitrary custom request values. diff --git a/akka-http-tests-java8/src/main/java/akka/http/javadsl/server/examples/simple/SimpleServerApp8.java b/akka-http-tests-java8/src/main/java/akka/http/javadsl/server/examples/simple/SimpleServerApp8.java index 5b23a230f3..d223cadc24 100644 --- a/akka-http-tests-java8/src/main/java/akka/http/javadsl/server/examples/simple/SimpleServerApp8.java +++ b/akka-http-tests-java8/src/main/java/akka/http/javadsl/server/examples/simple/SimpleServerApp8.java @@ -14,8 +14,8 @@ public class SimpleServerApp8 extends HttpApp { static Parameter x = Parameters.intValue("x"); static Parameter y = Parameters.intValue("y"); - static PathMatcher xSegment = PathMatchers.integerNumber(); - static PathMatcher ySegment = PathMatchers.integerNumber(); + static PathMatcher xSegment = PathMatchers.intValue(); + static PathMatcher ySegment = PathMatchers.intValue(); public static RouteResult multiply(RequestContext ctx, int x, int y) { int result = x * y; 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 c01dccfe35..9adaae6d01 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 @@ -17,7 +17,7 @@ import java.util.concurrent.ConcurrentHashMap; import static akka.http.javadsl.server.Directives.*; public class PetStoreExample { - static PathMatcher petId = PathMatchers.integerNumber(); + static PathMatcher petId = PathMatchers.intValue(); static RequestVal petEntity = RequestVals.entityAs(Jackson.jsonAs(Pet.class)); public static Route appRoute(final Map pets) { 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 983382b727..b17e6fbd43 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 @@ -20,8 +20,8 @@ public class SimpleServerApp extends HttpApp { static Parameter x = Parameters.intValue("x"); static Parameter y = Parameters.intValue("y"); - static PathMatcher xSegment = PathMatchers.integerNumber(); - static PathMatcher ySegment = PathMatchers.integerNumber(); + static PathMatcher xSegment = PathMatchers.intValue(); + static PathMatcher ySegment = PathMatchers.intValue(); static RequestVal bodyAsName = RequestVals.entityAs(Unmarshallers.String()); 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 new file mode 100644 index 0000000000..45b09cd6e4 --- /dev/null +++ b/akka-http-tests/src/test/java/akka/http/javadsl/server/MarshallerTest.java @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2009-2015 Typesafe Inc. + */ + +package akka.http.javadsl.server; + +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 org.junit.Test; + +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?"; + } + } + }); + + Handler1 nummerHandler = new Handler1() { + @Override + public RouteResult handle(RequestContext ctx, Integer integer) { + return ctx.completeAs(numberAsNameMarshaller, integer); + } + }; + + TestRoute route = + testRoute( + get( + path("nummer").route( + handleWith(n, nummerHandler) + ) + ) + ); + + route.run(HttpRequest.GET("/nummer?n=1")) + .assertStatusCode(200) + .assertMediaType(MediaTypes.TEXT_X_SPEECH) + .assertEntity("eins"); + + route.run(HttpRequest.GET("/nummer?n=6")) + .assertStatusCode(200) + .assertMediaType(MediaTypes.TEXT_X_SPEECH) + .assertEntity("wat?"); + + route.run(HttpRequest.GET("/nummer?n=5")) + .assertStatusCode(200) + .assertEntityBytes(ByteString.fromString("fünf", "utf8")); + + 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")); + } + + @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("[]"); + } + } + }); + + Handler1 nummerHandler = new Handler1() { + @Override + public RouteResult handle(RequestContext ctx, Integer integer) { + return ctx.completeAs(numberAsJsonListMarshaller, integer); + } + }; + + TestRoute route = + testRoute( + get( + path("nummer").route( + handleWith(n, nummerHandler) + ) + ) + ); + + route.run(HttpRequest.GET("/nummer?n=1")) + .assertStatusCode(200) + .assertMediaType(MediaTypes.APPLICATION_JSON) + .assertEntity("[1]"); + + route.run(HttpRequest.GET("/nummer?n=5")) + .assertStatusCode(200) + .assertEntity("[1,2,3,4,5]"); + + route.run( + HttpRequest.GET("/nummer?n=5").addHeader(Accept.create(MediaTypes.TEXT_PLAIN.toRange()))) + .assertStatusCode(406); + } + + @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(), "[]"); + } + } + }); + + Handler1 nummerHandler = new Handler1() { + @Override + public RouteResult handle(RequestContext ctx, Integer integer) { + return ctx.completeAs(numberAsJsonListMarshaller, integer); + } + }; + + TestRoute route = + testRoute( + get( + path("nummer").route( + handleWith(n, nummerHandler) + ) + ) + ); + + route.run(HttpRequest.GET("/nummer?n=1")) + .assertStatusCode(200) + .assertMediaType(MediaTypes.APPLICATION_JSON) + .assertEntity("[1]"); + + route.run(HttpRequest.GET("/nummer?n=5")) + .assertStatusCode(200) + .assertEntity("[1,2,3,4,5]"); + + route.run( + HttpRequest.GET("/nummer?n=5").addHeader(Accept.create(MediaTypes.TEXT_PLAIN.toRange()))) + .assertStatusCode(406); + } + + @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); + } + } + }); + + Handler1 nummerHandler = new Handler1() { + @Override + public RouteResult handle(RequestContext ctx, Integer integer) { + return ctx.completeAs(numberAsJsonListMarshaller, integer); + } + }; + + TestRoute route = + testRoute( + get( + path("nummer").route( + handleWith(n, nummerHandler) + ) + ) + ); + + route.run(HttpRequest.GET("/nummer?n=1")) + .assertStatusCode(200) + .assertMediaType(MediaTypes.APPLICATION_JSON) + .assertEntity("[1]"); + + route.run(HttpRequest.GET("/nummer?n=5")) + .assertStatusCode(200) + .assertEntity("[1,2,3,4,5]"); + + route.run(HttpRequest.GET("/nummer?n=6")) + .assertStatusCode(404); + + route.run(HttpRequest.GET("/nummer?n=5").addHeader(Accept.create(MediaTypes.TEXT_PLAIN.toRange()))) + .assertStatusCode(406); + } +} 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 54ffc0a955..ce46bdac60 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 @@ -216,7 +216,7 @@ public class PathDirectivesTest extends JUnitRouteTest { @Test public void testIntegerMatcher() { - PathMatcher age = PathMatchers.integerNumber(); + PathMatcher age = PathMatchers.intValue(); TestRoute route = testRoute( @@ -233,8 +233,8 @@ public class PathDirectivesTest extends JUnitRouteTest { public void testTwoVals() { // tests that `x` and `y` have different identities which is important for // retrieving the values - PathMatcher x = PathMatchers.integerNumber(); - PathMatcher y = PathMatchers.integerNumber(); + PathMatcher x = PathMatchers.intValue(); + PathMatcher y = PathMatchers.intValue(); TestRoute route = testRoute( @@ -254,7 +254,7 @@ public class PathDirectivesTest extends JUnitRouteTest { @Test public void testHexIntegerMatcher() { - PathMatcher color = PathMatchers.hexIntegerNumber(); + PathMatcher color = PathMatchers.hexIntValue(); TestRoute route = testRoute( @@ -267,7 +267,7 @@ public class PathDirectivesTest extends JUnitRouteTest { @Test public void testLongMatcher() { - PathMatcher bigAge = PathMatchers.longNumber(); + PathMatcher bigAge = PathMatchers.longValue(); TestRoute route = testRoute( @@ -280,7 +280,7 @@ public class PathDirectivesTest extends JUnitRouteTest { @Test public void testHexLongMatcher() { - PathMatcher code = PathMatchers.hexLongNumber(); + PathMatcher code = PathMatchers.hexLongValue(); TestRoute route = testRoute( 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 index 9c8ab9615f..96c4390756 100644 --- a/akka-http/src/main/scala/akka/http/impl/server/Util.scala +++ b/akka-http/src/main/scala/akka/http/impl/server/Util.scala @@ -6,16 +6,24 @@ package akka.http.impl.server import akka.http.scaladsl.unmarshalling.{ Unmarshaller, FromStringUnmarshaller } import akka.http.scaladsl.util.FastFuture -import akka.japi.function.Function +import akka.japi.function import scala.concurrent.{ Future, ExecutionContext } import scala.util.Try object Util { - def fromStringUnmarshallerFromFunction[T](convert: Function[String, T]): FromStringUnmarshaller[T] = + def fromStringUnmarshallerFromFunction[T](convert: function.Function[String, T]): FromStringUnmarshaller[T] = scalaUnmarshallerFromFunction(convert) - def scalaUnmarshallerFromFunction[T, U](convert: Function[T, U]): Unmarshaller[T, U] = + + def scalaUnmarshallerFromFunction[T, U](convert: function.Function[T, U]): Unmarshaller[T, U] = new Unmarshaller[T, U] { def apply(value: T)(implicit ec: ExecutionContext): 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/server/Marshallers.scala b/akka-http/src/main/scala/akka/http/javadsl/server/Marshallers.scala index 11c3930ecd..c9f04f4f43 100644 --- a/akka-http/src/main/scala/akka/http/javadsl/server/Marshallers.scala +++ b/akka-http/src/main/scala/akka/http/javadsl/server/Marshallers.scala @@ -4,12 +4,60 @@ package akka.http.javadsl.server -import akka.http.scaladsl.marshalling.ToResponseMarshaller +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 { - def STRING: Marshaller[String] = MarshallerImpl(implicit ctx ⇒ implicitly[ToResponseMarshaller[String]]) + /** + * 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, 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.withFixedCharset(contentType.mediaType().asScala, contentType.charset().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.withFixedCharset(contentType.mediaType().asScala, contentType.charset().asScala)(t ⇒ + convert(t).asScala) + } } 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 d8589b2dc0..419f976107 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 @@ -7,7 +7,7 @@ package akka.http.javadsl.server.directives import scala.annotation.varargs import java.lang.reflect.{ ParameterizedType, Method } -import akka.http.javadsl.model.{ ContentType, StatusCode, HttpResponse } +import akka.http.javadsl.model.{ Uri, ContentType, StatusCode, HttpResponse } import akka.http.javadsl.server._ import akka.http.impl.server.RouteStructure._ import akka.http.impl.server._ @@ -16,7 +16,7 @@ import scala.concurrent.Future abstract class BasicDirectives { /** - * Tries the given routes in sequence until the first one matches. + * Tries the given route alternatives in sequence until the first one matches. */ @varargs def route(innerRoute: Route, moreInnerRoutes: Route*): Route = @@ -63,6 +63,13 @@ abstract class BasicDirectives { 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. */ 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 2ac704df21..12be8a3e22 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 @@ -15,46 +15,149 @@ import scala.collection.immutable abstract class PathDirectives extends MiscDirectives { /** - * Tries to consumes the complete unmatched path given a number of PathMatchers. Between each - * of the matchers a `/` will be matched automatically. + * 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. * - * A matcher can either be a matcher of type `PathMatcher`, or a literal string. + * Each of Each of the arguments s must either be an instance of [[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 [[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 [[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 [[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 + * [[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 [[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 [[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 [[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 [[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 [[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 [[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 [[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))) + /** + * Rejects the request if the unmatchedPath of the [[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 pathSingleSlash: Directive = RawPathPrefixForMatchers(List(PathMatchers.SLASH, PathMatchers.END)) + + /** + * 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. + * + * Note that trailing slash and non-trailing slash URLs are '''not''' the same, although they often serve + * the same content. It is recommended to serve only one URL version and make the other redirect to it using + * [[redirectToTrailingSlashIfMissing]] or [[redirectToNoTrailingSlashIfPresent]] directive. + * + * For example: + * {{{ + * def route = { + * // redirect '/users/' to '/users', '/users/:userId/' to '/users/:userId' + * redirectToNoTrailingSlashIfPresent(Found) { + * pathPrefix("users") { + * pathEnd { + * // user list ... + * } ~ + * path(UUID) { userId => + * // user profile ... + * } + * } + * } + * } + * }}} + * + * 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)) + /** + * 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)) + + /** + * 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. + */ @varargs def redirectToTrailingSlashIfMissing(redirectionStatusCode: StatusCode, innerRoute: Route, moreInnerRoutes: Route*): Route = RedirectToTrailingSlashIfMissing(redirectionStatusCode)(innerRoute, moreInnerRoutes.toList) + + /** + * 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. + */ @varargs def redirectToNoTrailingSlashIfPresent(redirectionStatusCode: StatusCode, innerRoute: Route, moreInnerRoutes: Route*): Route = RedirectToNoTrailingSlashIfPresent(redirectionStatusCode)(innerRoute, moreInnerRoutes.toList) 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 deleted file mode 100644 index 4e005935c9..0000000000 --- a/akka-http/src/main/scala/akka/http/javadsl/server/directives/RouteDirectives.scala +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (C) 2009-2015 Typesafe Inc. - */ - -package akka.http.javadsl.server -package directives - -import akka.http.impl.server.RouteStructure.Redirect -import akka.http.javadsl.model.{ StatusCode, Uri } - -abstract class RouteDirectives extends RangeDirectives { - /** - * 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) -} 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 36fbca898e..7678ae04f3 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 @@ -9,7 +9,7 @@ import akka.http.javadsl.server.Route import scala.annotation.varargs -abstract class SchemeDirectives extends RouteDirectives { +abstract class SchemeDirectives extends RangeDirectives { /** * Rejects all requests whose Uri scheme does not match the given one. */ 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 index 5bcf35e56e..4f874dfbfb 100644 --- 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 @@ -4,6 +4,7 @@ package akka.http.javadsl.server.values +import java.util.regex.Pattern import java.{ lang ⇒ jl, util ⇒ ju } import akka.http.impl.server.PathMatcherImpl @@ -14,6 +15,7 @@ import akka.japi.function.Function import scala.collection.JavaConverters._ import scala.reflect.ClassTag +import scala.util.matching.Regex /** * A PathMatcher is used to match the (yet unmatched) URI path of incoming requests. @@ -31,24 +33,96 @@ trait PathMatcher[T] extends RequestVal[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) - def integerNumber: PathMatcher[jl.Integer] = matcher(_.IntNumber.asInstanceOf[PathMatcher1[jl.Integer]]) - def hexIntegerNumber: PathMatcher[jl.Integer] = matcher(_.HexIntNumber.asInstanceOf[PathMatcher1[jl.Integer]]) + /** + * 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]]) - def longNumber: PathMatcher[jl.Long] = matcher(_.LongNumber.asInstanceOf[PathMatcher1[jl.Long]]) - def hexLongNumber: PathMatcher[jl.Long] = matcher(_.HexLongNumber.asInstanceOf[PathMatcher1[jl.Long]]) + /** + * 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] =