+doc add more detailed PathDirectives documentation

This commit is contained in:
Johannes Rudolph 2015-07-14 19:00:32 +02:00
parent 7291d0ccab
commit ed76b14447
9 changed files with 284 additions and 20 deletions

View file

@ -63,8 +63,8 @@ public class HandlerExampleSpec extends JUnitRouteTest {
RequestVal<Integer> xParam = Parameters.intValue("x"); RequestVal<Integer> xParam = Parameters.intValue("x");
RequestVal<Integer> yParam = Parameters.intValue("y"); RequestVal<Integer> yParam = Parameters.intValue("y");
RequestVal<Integer> xSegment = PathMatchers.integerNumber(); RequestVal<Integer> xSegment = PathMatchers.intValue();
RequestVal<Integer> ySegment = PathMatchers.integerNumber(); RequestVal<Integer> ySegment = PathMatchers.intValue();
//#handler2 //#handler2
Handler2<Integer, Integer> multiply = Handler2<Integer, Integer> multiply =
@ -114,8 +114,8 @@ public class HandlerExampleSpec extends JUnitRouteTest {
RequestVal<Integer> xParam = Parameters.intValue("x"); RequestVal<Integer> xParam = Parameters.intValue("x");
RequestVal<Integer> yParam = Parameters.intValue("y"); RequestVal<Integer> yParam = Parameters.intValue("y");
RequestVal<Integer> xSegment = PathMatchers.integerNumber(); RequestVal<Integer> xSegment = PathMatchers.intValue();
RequestVal<Integer> ySegment = PathMatchers.integerNumber(); RequestVal<Integer> ySegment = PathMatchers.intValue();
//#reflective //#reflective

View file

@ -0,0 +1,90 @@
/*
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
*/
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/<user-id>"
Handler1<Integer> completeWithUserId =
new Handler1<Integer>() {
@Override
public RouteResult handle(RequestContext ctx, Integer userId) {
return ctx.complete("Hello user " + userId);
}
};
PathMatcher<Integer> userId = PathMatchers.intValue();
pathPrefix("admin", "user").route(
path(userId).route(
handleWith1(userId, completeWithUserId)
)
);
// matches "/admin/user/<user-id>", 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/<user-id>" 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 <T> Route handleWith1(RequestVal<T> val, Object o) {
return null;
}
}

View file

@ -46,7 +46,7 @@ MethodDirectives
MiscDirectives MiscDirectives
Contains directives that validate a request by user-defined logic. Contains directives that validate a request by user-defined logic.
PathDirectives :ref:`PathDirectives-java`
Contains directives to match and filter on the URI path of the incoming request. Contains directives to match and filter on the URI path of the incoming request.
RangeDirectives RangeDirectives
@ -58,4 +58,9 @@ SchemeDirectives
WebsocketDirectives WebsocketDirectives
Contains directives to support answering Websocket requests. Contains directives to support answering Websocket requests.
.. toctree::
:maxdepth: 1
path-directives
.. _`RFC 7234`: http://tools.ietf.org/html/rfc7234 .. _`RFC 7234`: http://tools.ietf.org/html/rfc7234

View file

@ -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

View file

@ -14,8 +14,8 @@ public class SimpleServerApp8 extends HttpApp {
static Parameter<Integer> x = Parameters.intValue("x"); static Parameter<Integer> x = Parameters.intValue("x");
static Parameter<Integer> y = Parameters.intValue("y"); static Parameter<Integer> y = Parameters.intValue("y");
static PathMatcher<Integer> xSegment = PathMatchers.integerNumber(); static PathMatcher<Integer> xSegment = PathMatchers.intValue();
static PathMatcher<Integer> ySegment = PathMatchers.integerNumber(); static PathMatcher<Integer> ySegment = PathMatchers.intValue();
public static RouteResult multiply(RequestContext ctx, int x, int y) { public static RouteResult multiply(RequestContext ctx, int x, int y) {
int result = x * y; int result = x * y;

View file

@ -17,7 +17,7 @@ import java.util.concurrent.ConcurrentHashMap;
import static akka.http.javadsl.server.Directives.*; import static akka.http.javadsl.server.Directives.*;
public class PetStoreExample { public class PetStoreExample {
static PathMatcher<Integer> petId = PathMatchers.integerNumber(); static PathMatcher<Integer> petId = PathMatchers.intValue();
static RequestVal<Pet> petEntity = RequestVals.entityAs(Jackson.jsonAs(Pet.class)); static RequestVal<Pet> petEntity = RequestVals.entityAs(Jackson.jsonAs(Pet.class));
public static Route appRoute(final Map<Integer, Pet> pets) { public static Route appRoute(final Map<Integer, Pet> pets) {

View file

@ -20,8 +20,8 @@ public class SimpleServerApp extends HttpApp {
static Parameter<Integer> x = Parameters.intValue("x"); static Parameter<Integer> x = Parameters.intValue("x");
static Parameter<Integer> y = Parameters.intValue("y"); static Parameter<Integer> y = Parameters.intValue("y");
static PathMatcher<Integer> xSegment = PathMatchers.integerNumber(); static PathMatcher<Integer> xSegment = PathMatchers.intValue();
static PathMatcher<Integer> ySegment = PathMatchers.integerNumber(); static PathMatcher<Integer> ySegment = PathMatchers.intValue();
static RequestVal<String> bodyAsName = RequestVals.entityAs(Unmarshallers.String()); static RequestVal<String> bodyAsName = RequestVals.entityAs(Unmarshallers.String());

View file

@ -216,7 +216,7 @@ public class PathDirectivesTest extends JUnitRouteTest {
@Test @Test
public void testIntegerMatcher() { public void testIntegerMatcher() {
PathMatcher<Integer> age = PathMatchers.integerNumber(); PathMatcher<Integer> age = PathMatchers.intValue();
TestRoute route = TestRoute route =
testRoute( testRoute(
@ -233,8 +233,8 @@ public class PathDirectivesTest extends JUnitRouteTest {
public void testTwoVals() { public void testTwoVals() {
// tests that `x` and `y` have different identities which is important for // tests that `x` and `y` have different identities which is important for
// retrieving the values // retrieving the values
PathMatcher<Integer> x = PathMatchers.integerNumber(); PathMatcher<Integer> x = PathMatchers.intValue();
PathMatcher<Integer> y = PathMatchers.integerNumber(); PathMatcher<Integer> y = PathMatchers.intValue();
TestRoute route = TestRoute route =
testRoute( testRoute(
@ -254,7 +254,7 @@ public class PathDirectivesTest extends JUnitRouteTest {
@Test @Test
public void testHexIntegerMatcher() { public void testHexIntegerMatcher() {
PathMatcher<Integer> color = PathMatchers.hexIntegerNumber(); PathMatcher<Integer> color = PathMatchers.hexIntValue();
TestRoute route = TestRoute route =
testRoute( testRoute(
@ -267,7 +267,7 @@ public class PathDirectivesTest extends JUnitRouteTest {
@Test @Test
public void testLongMatcher() { public void testLongMatcher() {
PathMatcher<Long> bigAge = PathMatchers.longNumber(); PathMatcher<Long> bigAge = PathMatchers.longValue();
TestRoute route = TestRoute route =
testRoute( testRoute(
@ -280,7 +280,7 @@ public class PathDirectivesTest extends JUnitRouteTest {
@Test @Test
public void testHexLongMatcher() { public void testHexLongMatcher() {
PathMatcher<Long> code = PathMatchers.hexLongNumber(); PathMatcher<Long> code = PathMatchers.hexLongValue();
TestRoute route = TestRoute route =
testRoute( testRoute(

View file

@ -4,6 +4,7 @@
package akka.http.javadsl.server.values package akka.http.javadsl.server.values
import java.util.regex.Pattern
import java.{ lang jl, util ju } import java.{ lang jl, util ju }
import akka.http.impl.server.PathMatcherImpl import akka.http.impl.server.PathMatcherImpl
@ -14,6 +15,7 @@ import akka.japi.function.Function
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
import scala.reflect.ClassTag import scala.reflect.ClassTag
import scala.util.matching.Regex
/** /**
* A PathMatcher is used to match the (yet unmatched) URI path of incoming requests. * 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. * A collection of predefined path matchers.
*/ */
object PathMatchers { 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) val NEUTRAL: PathMatcher[Void] = matcher0(_.Neutral)
/**
* A PathMatcher that matches a single slash character ('/').
*/
val SLASH: PathMatcher[Void] = matcher0(_.Slash) val SLASH: PathMatcher[Void] = matcher0(_.Slash)
/**
* A PathMatcher that matches the very end of the requests URI path.
*/
val END: PathMatcher[Void] = matcher0(_.PathEnd) 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 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) 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) 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)) 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)) 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 rest: PathMatcher[String] = matcher(_.Rest)
def segmentFromString[T](convert: Function[String, T], clazz: Class[T]): PathMatcher[T] = def segmentFromString[T](convert: Function[String, T], clazz: Class[T]): PathMatcher[T] =