To use the high-level API you need to add a dependency to theakka-http-experimentalmodule.
2.5.1 Routing DSL Overview
The Akka HTTPLow-Level Server-Side APIprovides aFlow- orFunction-level interface that allows an appli- cation to respond to incoming HTTP requests by simply mapping requests to responses (excerpt fromLow-level server side example):
final Function<HttpRequest, HttpResponse> requestHandler = new Function<HttpRequest, HttpResponse>() {
private final HttpResponse NOT_FOUND = HttpResponse.create()
.withStatus(404)
.withEntity("Unknown resource!");
@Override
public HttpResponse apply(HttpRequest request) throws Exception { Uri uri = request.getUri();
if (request.method() == HttpMethods.GET) { if (uri.path().equals("/")) return HttpResponse.create() .withEntity(MediaTypes.TEXT_HTML.toContentType(), "<html><body>Hello world!</body></html>"); else if (uri.path().equals("/hello")) {
String name = Util.getOrElse(uri.parameter("name"), "Mister X");
return HttpResponse.create() .withEntity("Hello " + name + "!"); } else if (uri.path().equals("/ping")) return HttpResponse.create().withEntity("PONG!"); else return NOT_FOUND; }
else return NOT_FOUND; }
};
While it’d be perfectly possible to define a complete REST API service purely by inspecting the incoming
HttpRequestthis approach becomes somewhat unwieldy for larger services due to the amount of syntax “cer- emony” required. Also, it doesn’t help in keeping your service definition asDRYas you might like.
As an alternative Akka HTTP provides a flexible DSL for expressing your service behavior as a structure of composable elements (calledDirectives) in a concise and readable way. Directives are assembled into a so called route structurewhich, at its top-level, can be used to create a handlerFlow(or, alternatively, an async handler function) that can be directly supplied to abindcall.
Here’s the complete example rewritten using the composable high-level API: import akka.actor.ActorSystem;
import akka.http.javadsl.model.MediaTypes; import akka.http.javadsl.server.*;
import akka.http.javadsl.server.values.Parameters;
import java.io.IOException;
public static void main(String[] args) throws IOException { // boot up server using the route as defined below ActorSystem system = ActorSystem.create();
// HttpApp.bindRoute expects a route being provided by HttpApp.createRoute new HighLevelServerExample().bindRoute("localhost", 8080, system);
System.out.println("Type RETURN to exit"); System.in.read();
system.shutdown(); }
// A RequestVal is a type-safe representation of some aspect of the request. // In this case it represents the `name` URI parameter of type String.
private RequestVal<String> name = Parameters.stringValue("name").withDefault("Mister X");
@Override
public Route createRoute() {
// This handler generates responses to `/hello?name=XXX` requests Route helloRoute =
handleWith1(name,
// in Java 8 the following becomes simply
// (ctx, name) -> ctx.complete("Hello " + name + "!") new Handler1<String>() {
@Override
public RouteResult apply(RequestContext ctx, String name) { return ctx.complete("Hello " + name + "!");
} });
return
// here the complete behavior for this server is defined route(
// only handle GET requests get(
// matches the empty path pathSingleSlash().route(
// return a constant string with a certain content type complete(MediaTypes.TEXT_HTML.toContentType(),
"<html><body>Hello world!</body></html>") ),
path("ping").route(
// return a simple `text/plain` response complete("PONG!")
),
path("hello").route(
// uses the route defined above helloRoute ) ) ); } }
Heart of the high-level architecture is the route tree. It is a big expression of typeRoutethat is evaluated only once during startup time of your service. It completely describes how your service should react to any request. The typeRouteis the basic building block of the route tree. It defines if and a how a request should be handled. Routes are composed to form the route tree in the following two ways.
A route can be wrapped by a “Directive” which adds some behavioral aspect to its wrapped “inner route”.
path("ping")is such a directive that implements a path filter, i.e. it only passes control to its inner route when the unmatched path matches"ping". Directives can be more versatile than this: A directive can also trans- form the request before passing it into its inner route or transform a response that comes out of its inner route. It’s
a general and powerful abstraction that allows to package any kind of HTTP processing into well-defined blocks that can be freely combined. akka-http defines a library of predefined directives and routes for all the various aspects of dealing with HTTP requests and responses.
Read more aboutDirectives.
The other way of composition is defining a list ofRoutealternatives. Alternative routes are tried one after the other until one route “accepts” the request and provides a response. Otherwise, a route can also “reject” a request, in which case further alternatives are explored. Alternatives are specified by passing a list of routes either to
Directive.route()as inpathSingleSlash().route()or to directives that directly take a variable number of inner routes as argument likeget()here.
Read more aboutRoutes.
Another important building block is a RequestVal<T>. It represents a value that can be extracted from a request (like the URI parameterParameters.stringValue("name")in the example) and which is then interpreted as a value of typeT. Examples of HTTP aspects represented by aRequestValare URI parameters, HTTP form fields, details of the request like headers, URI, the entity, or authentication data.
Read more aboutRequest values.
The actual application-defined processing of a request is defined with aHandlerinstance or by specifying a handling method with reflection. A handler can receive the value of any request values and is converted into a
Routeby using one of theBasicDirectives.handleWithdirectives. Read more aboutHandlers.
Requests or responses often contain data that needs to be interpreted or rendered in some way. Akka-http provides the abstraction of MarshallerandUnmarshaller that define how domain model objects map to HTTP entities.
Read more aboutMarshalling & Unmarshalling.
akka-http contains a testkit that simplifies testing routes. It allows to run test-requests against (sub-)routes quickly without running them over the network and helps with writing assertions on HTTP response properties.
Read more aboutRoute Testkit.
2.5.2 Routes
A Route itself is a function that operates on a RequestContext and returns a RouteResult. The
RequestContextis a data structure that contains the current request and auxiliary data like the so far un- matched path of the request URI that gets passed through the route structure. It also contains the current
ExecutionContext andakka.stream.Materializer, so that these don’t have to be passed around manually.
RequestContext
TheRequestContextachieves two goals: it allows access to request data and it is a factory for creating a
RouteResult. A user-defined handler (seeHandlers) that is usually used at the leaf position of the route tree receives aRequestContext, evaluates its content and then returns a result generated by one of the methods of the context.
RouteResult
The RouteResult is an opaque structure that represents possible results of evaluating a route. A
RouteResultcan only be created by using one of the methods of the RequestContext. A result can either be a response, if it was generated by one of thecompleteXmethods, it can be an eventual result, i.e. a
Future<RouteResultifcompleteWithwas used or a rejection that contains information about why the route could not handle the request.
Composing Routes
Routes are composed to form the route tree in two principle ways.
A route can be wrapped by a “Directive” which adds some behavioral aspect to its wrapped “inner route”. Such an aspect can be
• filtering requests to decide which requests will get to the inner route • transforming the request before passing it to the inner route
• transforming the response (or more generally the route result) received from the inner route
• applying side-effects around inner route processing, such as measuring the time taken to run the inner route akka-http defines a library of predefinedDirectivesand routes for all the various aspects of dealing with HTTP requests and responses.
The other way of composition is defining a list ofRoutealternatives. Alternative routes are tried one after the other until one route “accepts” the request and provides a response. Otherwise, a route can also “reject” a request, in which case further alternatives are explored. Alternatives are specified by passing a list of routes either to
Directive.route()as inpath("xyz").route()or to directives that directly take a variable number of inner routes as argument likeget().
The Routing Tree
Essentially, when you combine routes via nesting and alternative, you build a routing structure that forms a tree. When a request comes in it is injected into this tree at the root and flows down through all the branches in a depth-first manner until either some node completes it or it is fully rejected.
Consider this schematic example: val route = a.route( b.route( c.route( ... // route 1 ), d.route( ... // route 2 ), ... // route 3 ), e.route( ... // route 4 ) )
Here five directives form a routing tree.
• Route 1 will only be reached if directivesa,bandcall let the request pass through. • Route 2 will run ifaandbpass,crejects anddpasses.
• Route 3 will run ifaandbpass, butcanddreject.
Route 3 can therefore be seen as a “catch-all” route that only kicks in, if routes chained into preceding positions reject. This mechanism can make complex filtering logic quite easy to implement: simply put the most specific cases up front and the most general cases in the back.
2.5.3 Directives
A directive is a wrapper for a route or a list of alternative routes that adds one or more of the following functionality to its nested route(s):
• it filters the request and lets only matching requests pass (e.g. thegetdirective lets only GET-requests pass) • it modifies the request or theRequestContext(e.g. thepathdirectives filters on the unmatched path
and then passes an updatedRequestContextunmatched 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.AllDirectivesor by importing them statically withimport 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 specifyingHandlers to process a request.
CacheConditionDirectives Contains a single directiveconditionalthat wraps its inner route with support for Conditional Requests as defined byRFC 7234.
CodingDirectives Contains directives to decode compressed requests and encode responses. CookieDirectives Contains a single directivesetCookieto 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 class- path.
HostDirectives Contains directives to filter on theHostheader 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.
PathDirectives Contains directives to match and filter on the URI path of the incoming request.
RangeDirectives Contains a single directivewithRangeSupportthat adds support for retrieving partial re- sponses.
SchemeDirectives Contains a single directiveschemeto filter requests based on the URI scheme (http vs. https). WebsocketDirectives Contains directives to support answering Websocket requests.
PathDirectives
Path directives are the most basic building blocks for routing requests depending on the URI path.
When a request (or rather the respectiveRequestContextinstance) enters the route structure it has an “un- matched path” that is identical to therequest.uri.path. As it descends the routing tree and passes through one or morepathPrefixorpathdirectives the “unmatched path” progressively gets “eaten into” from the left until, in most cases, it eventually has been consumed completely.
The two main directives arepathandpathPrefix. Thepathdirective tries to match the complete remaining unmatched path against the specified “path matchers”, thepathPrefixdirective 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 nestedpathPrefixandpathdirectives is usually not needed.
Path directives take a variable amount of arguments. Each argument must be aPathMatcheror a string (which is automatically converted to a path matcher using PathMatchers.segment). In the case of pathand
pathPrefix, if multiple arguments are supplied, a slash is assumed between any of the supplied path matchers. TherawPathXvariants 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.segmentwhich matches exactly one path segment against the supplied constant string. Other path matchers defined inPathMatchersmatch 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 areRequest values. Extracting a path matcher value (i.e. using it withhandleWithX) 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 char- acter 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 Stringvalue. A PathMatchercreated from a regular expres- sion 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
IllegalArgumentExceptionwill 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 aStringinstance.
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 remainingdecodedelements of the path useRestPath
instead.
PathMatchers.intValue Efficiently matches a number of decimal digits (unsigned) and extracts their (non- negative)Intvalue. The matcher will not match zero digits or a sequence of digits that would represent an
Intvalue larger thanInteger.MAX_VALUE.
PathMatchers.longValue Efficiently matches a number of decimal digits (unsigned) and extracts their (non-negative) Longvalue. The matcher will not match zero digits or a sequence of digits that would represent anLongvalue larger thanLong.MAX_VALUE.
PathMatchers.hexIntValue Efficiently matches a number of hex digits and extracts their (non-negative)
Intvalue. The matcher will not match zero digits or a sequence of digits that would represent anInt
value larger thanInteger.MAX_VALUE.
PathMatchers.hexLongValue Efficiently matches a number of hex digits and extracts their (non-negative)
Longvalue. The matcher will not match zero digits or a sequence of digits that would represent anLong
value larger thanLong.MAX_VALUE.
PathMatchers.uuid Matches and extracts ajava.util.UUIDinstance.
PathMatchers.NEUTRAL A matcher that always matches, doesn’t consume anything and extracts nothing. Serves mainly as a neutral element inPathMatchercomposition.
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 willnotbe matched, i.e. remain unmatched and to be consumed by potentially nested directives.
Here’s a collection of path matching 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 apply(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) ) );
2.5.4 Request values
A request value of typeRequestVal<T>is a typed structure that represents some aspect of the request that can be interpreted as a value of typeT. ARequestValinstance abstracts the knowledge about how to extract a certain value from the request and interpret it as aT. It is used in combination withHandlers.
The advantage of representing a request detail as aRequestValinstead of performing ad-hoc analysis of a request are:
• you can define an “inventory” of HTTP primitives for your application that you can reuse in many places of your application
• automatic handling of errors when an expected value was not found in a request or if it could not be inter- preted as the expected Java type
Note, that the Scala version of the routing DSL has no direct correspondent to RequestVals. Instead, a Scala-side
Directivecan have “extractions” that are reflected in the type of theDirective. Predefined Request values
akka-http provides a set of predefined request values for request data commonly accessed in a web service. These request values are defined:
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.
2.5.5 Handlers
Handlers implement the actual application-defined logic for a certain trace in the routing tree. Most of the leaves