Merge pull request #17990 from spray/w/java-side-documentation

A few last minute Java-side changes + documentation
This commit is contained in:
Konrad Malawski 2015-07-14 19:38:43 +02:00
commit 75229b0711
24 changed files with 829 additions and 98 deletions

View file

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

@ -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
.. _jackson: https://github.com/FasterXML/jackson

View file

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

View file

@ -3,4 +3,64 @@
Directives
==========
TODO
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

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

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

View file

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

View file

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

View file

@ -3,4 +3,41 @@
Marshalling & Unmarshalling
===========================
TODO
"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

View file

@ -1,6 +0,0 @@
.. _pathmatcher-dsl-java:
The PathMatcher DSL
===================
TODO

View file

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

View file

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

View file

@ -17,7 +17,7 @@ import java.util.concurrent.ConcurrentHashMap;
import static akka.http.javadsl.server.Directives.*;
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));
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> y = Parameters.intValue("y");
static PathMatcher<Integer> xSegment = PathMatchers.integerNumber();
static PathMatcher<Integer> ySegment = PathMatchers.integerNumber();
static PathMatcher<Integer> xSegment = PathMatchers.intValue();
static PathMatcher<Integer> ySegment = PathMatchers.intValue();
static RequestVal<String> bodyAsName = RequestVals.entityAs(Unmarshallers.String());

View file

@ -0,0 +1,208 @@
/*
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
*/
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<Integer> n = Parameters.intValue("n");
@Test
public void testCustomToStringMarshaller() {
final Marshaller<Integer> numberAsNameMarshaller =
Marshallers.toEntityString(MediaTypes.TEXT_X_SPEECH, new Function<Integer, String>() {
@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<Integer> nummerHandler = new Handler1<Integer>() {
@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<Integer> numberAsJsonListMarshaller =
Marshallers.toEntityByteString(MediaTypes.APPLICATION_JSON.toContentType(), new Function<Integer, ByteString>() {
@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<Integer> nummerHandler = new Handler1<Integer>() {
@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<Integer> numberAsJsonListMarshaller =
Marshallers.toEntity(MediaTypes.APPLICATION_JSON.toContentType(), new Function<Integer, ResponseEntity>() {
@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<Integer> nummerHandler = new Handler1<Integer>() {
@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<Integer> numberAsJsonListMarshaller =
Marshallers.toResponse(MediaTypes.APPLICATION_JSON.toContentType(), new Function<Integer, HttpResponse>() {
@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<Integer> nummerHandler = new Handler1<Integer>() {
@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);
}
}

View file

@ -216,7 +216,7 @@ public class PathDirectivesTest extends JUnitRouteTest {
@Test
public void testIntegerMatcher() {
PathMatcher<Integer> age = PathMatchers.integerNumber();
PathMatcher<Integer> 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<Integer> x = PathMatchers.integerNumber();
PathMatcher<Integer> y = PathMatchers.integerNumber();
PathMatcher<Integer> x = PathMatchers.intValue();
PathMatcher<Integer> y = PathMatchers.intValue();
TestRoute route =
testRoute(
@ -254,7 +254,7 @@ public class PathDirectivesTest extends JUnitRouteTest {
@Test
public void testHexIntegerMatcher() {
PathMatcher<Integer> color = PathMatchers.hexIntegerNumber();
PathMatcher<Integer> color = PathMatchers.hexIntValue();
TestRoute route =
testRoute(
@ -267,7 +267,7 @@ public class PathDirectivesTest extends JUnitRouteTest {
@Test
public void testLongMatcher() {
PathMatcher<Long> bigAge = PathMatchers.longNumber();
PathMatcher<Long> bigAge = PathMatchers.longValue();
TestRoute route =
testRoute(
@ -280,7 +280,7 @@ public class PathDirectivesTest extends JUnitRouteTest {
@Test
public void testHexLongMatcher() {
PathMatcher<Long> code = PathMatchers.hexLongNumber();
PathMatcher<Long> code = PathMatchers.hexLongValue();
TestRoute route =
testRoute(

View file

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

View file

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

View file

@ -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.
*/

View file

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

View file

@ -1,18 +0,0 @@
/*
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
*/
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)
}

View file

@ -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.
*/

View file

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