路由

Routes

The “Route” is the central concept of Akka HTTP’s Routing DSL. All the structures you build with the DSL, no matter whether they consists of a single line or span several hundred lines, are typefunction turning a RequestContextRequestContext into a Future[RouteResult]CompletionStage<RouteResult>.

“Route”是 Akka HTTP 路由 DSL 的中心概念。使用 DSL 构建的所有结构,不管是否包含在单行或跨几百行,都是将 RequestContextRequestContext 转换为 Future[RouteResult]CompletionStage<RouteResult> 类型函数

type Route = RequestContext => Future[RouteResult]

It’s a simple alias for a function turning a RequestContextRequestContext into a Future[RouteResult].

RequestContextRequestContext 转换为 Future[RouteResult] 的函数的简单别名。

A RouteRouteRoute itself is a function that operates on a RequestContextRequestContext and returns a RouteResultRouteResult. The RequestContextRequestContext is a data structure that contains the current request and auxiliary data like the so far unmatched path of the request URI that gets passed through the route structure. It also contains the current ExecutionContext and akka.stream.Materializer, so that these don’t have to be passed around manually.

RouteRouteRoute 本向是一个函数,它操作一个 RequestContextRequestContext 并返回一个 RouteResultRouteResultRequestContextRequestContext 是一个数据结果,包含当前请求和辅助数据,例如:到目前为止通过路由结构传递的请求 URI 的未匹配路径。 也包含当前的 ExecutionContextakka.stream.Materializer,因此,这些不需要手动传递。

Generally when a route receives a request (or rather a RequestContextRequestContext for it) it can do one of these things:

通常当路由收到一个请求(更确切的说是 RequestContextRequestContext ),它可以做这些事情之一:

  • Complete the request by returning the value of requestContext.complete(...)
  • Reject the request by returning the value of requestContext.reject(...) (see Rejections)
  • Fail the request by returning the value of requestContext.fail(...) or by just throwing an exception (see Exception Handling)
  • Do any kind of asynchronous processing and instantly return a Future[RouteResult]CompletionStage<RouteResult> to be eventually completed later
  • 通过返回 requestContext.complete(...) 的值完成请求

  • 通过返回 requestContext.reject(...) 的值拒绝请求(见 Rejections
  • 通过返回 requestContext.fail(...) 的值或通过抛出一个异常使请求失败(见 Exception Handling
  • 进行任何类型的异步处理,并即刻返回 Future[RouteResult]CompletionStage<RouteResult>,以便最终在以后完成

The first case is pretty clear, by calling complete a given response is sent to the client as reaction to the request. In the second case “reject” means that the route does not want to handle the request. You’ll see further down in the section about route composition what this is good for.

第一种情况很清楚,通过调用 complete 将指定响应作为对请求的响应发送到客户端。 在第二种情况里,“reject”意味着路由不愿处理该请求。 你将进一步在关于路由组合部分了解这样做的好处。

A RouteRouteRoute can be “sealed” using Route.seal, which relies on the in-scope RejectionHandler and ExceptionHandlerExceptionHandler instances to convert rejections and exceptions into appropriate HTTP responses for the client. Sealing a Route is described more in detail later.

可以使用 Route.seal “密封” RouteRouteRoute ,路由依赖范围内的 RejectionHandlerExceptionHandlerExceptionHandler 实例将拒绝和异常转换为对客户端合适的 HTTP 响应。 后面 密封路由 更详细地介绍。

Using Route.handlerFlow or Route.asyncHandler a RouteRouteRoute can be lifted into a handler FlowFlow or async handler function to be used with a bindAndHandleXXX call from the Core Server API.

使用 Route.handlerFlowRoute.asyncHandler,可以将 RouteRouteRoute 提升为 FlowFlow 或异步处理函数,它们可用于 @核心服务器 APIbindAndHandleXXX 调用。

Note: There is also an implicit conversion from RouteRouteRoute to Flow<HttpRequest, HttpResponse, Unit>Flow[HttpRequest, HttpResponse, Unit] defined in the RouteResultRouteResult companion, which relies on Route.handlerFlow.

注意:也有从 RouteRouteRouteFlow<HttpRequest, HttpResponse, Unit>Flow[HttpRequest, HttpResponse, Unit] 的隐式转换定义在 RouteResultRouteResult 伴身对象,它依赖 Route.handlerFlow

RequestContext

请求上下文

The request context wraps an HttpRequestHttpRequest instance to enrich it with additional information that are typically required by the routing logic, like an ExecutionContext, MaterializerMaterializer, LoggingAdapterLoggingAdapter and the configured RoutingSettingsRoutingSettings. It also contains the unmatchedPath, a value that describes how much of the request URI has not yet been matched by a Path Directive.

请求上下文包装了 HttpRequestHttpRequest 实例,通过路由逻辑通常需要的使用附加信息来丰富它,例如:ExecutionContextMaterializerMaterializerLoggingAdapterLoggingAdapter 和 已配置的 RoutingSettingsRoutingSettings 。它还包含 unmatchedPath,该值描述 Path 指令 还未匹配多少请求 URI。

The RequestContextRequestContext itself is immutable but contains several helper methods which allow for convenient creation of modified copies.

RequestContextRequestContext 本身是不可变的,但是它包含一些辅助方法来方便创建修改后的复本。

RouteResult

路由结果

RouteResultRouteResult is a simple algebraic data type (ADT) that models the possible non-error results of a RouteRouteRoute. It is defined as such:

RouteResultRouteResult 是一种简单代数数据类型(ADT),它对 RouteRouteRoute 可能的非错误结果建模。

它是这样定义的:

sealed trait RouteResult

object RouteResult {
  final case class Complete(response: HttpResponse) extends RouteResult
  final case class Rejected(rejections: immutable.Seq[Rejection]) extends RouteResult
}

Usually you don’t create any RouteResultRouteResult instances yourself, but rather rely on the pre-defined RouteDirectives (like complete, reject or redirect) or the respective methods on the RequestContext instead.

通常你自己不会创建任何 RouteResultRouteResult 实例,面是依赖预定义的 RouteDirectives (例如: completerejectredirect )或者 RequestContext 上的相应方法。

Composing Routes

组合路由

There are three basic operations we need for building more complex routes from simpler ones:

我们需要三个基本操作符来从简单路由构建更复杂的路由

  • Route transformation, which delegates processing to another, “inner” route but in the process changes some properties of either the incoming request, the outgoing response or both
  • Route filtering, which only lets requests satisfying a given filter condition pass and rejects all others
  • Route chaining, which tries a second route if a given first one was rejected
  • 路由转换,将处理委托给另一个“内部”路由,但在此过程中改变传入请求或外出响应或者两者的一些属性

  • 路由过滤,将只让满足指定过滤条件的通过,并拒绝所有其它的
  • 路由链,如果指定的第一个路由被拒绝,将尝试第二个路由

The last point is achieved with the concatenation operator ~, which is an extension method that becomes available when you import akka.http.scaladsl.server.Directives._. The first two points are provided by so-called Directives of which a large number is already predefined by Akka HTTP and which you can also easily create yourself. Directives deliver most of Akka HTTP’s power and flexibility.

最后一点是使用连接操作符 ~ 达成的,操作符是一个扩展方法,在 import akka.http.scaladsl.server.Directives._ 语句可用。 第一、二两点由 Directives 提供,Akka HTTP 已经预定义了大量的指令,你也可以很容易的定义自己的指令。 Directives 提供了 Akka HTTP 的大部分强大功能和灵活性。

The Routing Tree

路由树

Essentially, when you combine directives and custom routes via the concat method, 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.

实质上,当你通过 concat 方法结合指令和自定义路由时,构建的路由建构形成一颗树。 当一个请求进来时,它在根被注入,并以深度优先的方式向下流径所有分支,直到某个节点完成或者被完全拒绝。

Consider this schematic example:

考虑这个示意图例子:

val route =
  a {
    concat(
      b {
        concat(
          c {
            ... // route 1
          },
          d {
            ... // route 2
          },
          ... // route 3
        )
      },
      e {
        ... // route 4
      }
    )
  }
import static akka.http.javadsl.server.Directives.*;

Route route =
  directiveA(concat(() ->
    directiveB(concat(() ->
      directiveC(
        ... // route 1
      ),
      directiveD(
        ... // route 2
      ),
      ... // route 3
    )),
    directiveE(
      ... // route 4
    )
  ));

Here five directives form a routing tree.

这里五个指令形成一颗路由树。

  • Route 1 will only be reached if directives a, b and c all let the request pass through.
  • Route 2 will run if a and b pass, c rejects and d passes.
  • Route 3 will run if a and b pass, but c and d reject.
  • 如果指令 abc 都让请求通过,路由 1 才能到达。

  • 如果 ab 通过,c 拒绝且 d 通过,路由 2 将运行。
  • 如果 ab 通过,但 cd 拒绝,路由 3 将运行。

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.

因此如果路由进入前面的路径被拒绝,则路由 3 可以看作“捕获-所有”路由。这种机制可以使复杂过滤逻辑非常容易实现:简单地把最特殊情况放在前面,而把最一般的情况放在后面。

Sealing a Route

密封路由

As described in Rejections and Exception Handling, there are generally two ways to handle rejections and exceptions.

拒绝异常处理 所述,通常有两种方法来处理拒绝和异常。

In the first case your handlers will be “sealed”, (which means that it will receive the default handler as a fallback for all cases your handler doesn’t handle itself) and used for all rejections/exceptions that are not handled within the route structure itself.

在第一种情况,你的处理程序将被“密封”(这意味着它将接收默认处理程序,作为对于你的处理程序本身不处理的所有情况的后备), 并且用于路由结构本身不处理的所有拒绝/异常。

Route.seal() method to modify HttpResponse

Route.seal() 方法修改 HttpResponse

In application code, unlike test code, you don’t need to use the Route.seal() method to seal a route. As long as you bring implicit rejection and/or exception handlers to the top-level scope, your route is sealed.

在应用程序代码中,不像 测试代码 那样,你不需要使用 Route.seal() 方法密封路由。 只要在顶层提供了隐式拒绝和/或异常处理器,你的路由将被密闭。

However, you can use Route.seal() to perform modification on HttpResponse from the route. For example, if you want to add a special header, but still use the default rejection handler, then you can do the following. In the below case, the special header is added to rejected responses which did not match the route, as well as successful responses which matched the route.

但是,你可以使用 Route.seal() 从路由对 HttpResponse 进行修改。 例如,你想添加一个特殊的头域,但仍然想使用默认拒绝处理程序,那么你可以向下面这样做。 特殊的头域被添加到不匹配路由的拒绝响应以及匹配路由的成功响应里。

Scala
val route = respondWithHeader(RawHeader("special-header", "you always have this even in 404")) {
  Route.seal(
    get {
      pathSingleSlash {
        complete {
          "Captain on the bridge!"
        }
      }
    }
  )
}
Java
public class RouteSealExample extends AllDirectives {

  public static void main(String [] args) throws IOException {
    RouteSealExample app = new RouteSealExample();
    app.runServer();
  }

  public void runServer(){
    ActorSystem system = ActorSystem.create();
    final ActorMaterializer materializer = ActorMaterializer.create(system);

    Route sealedRoute = get(
      () -> pathSingleSlash( () ->
        complete("Captain on the bridge!")
      )
    ).seal();

    Route route = respondWithHeader(
      RawHeader.create("special-header", "you always have this even in 404"),
      () -> sealedRoute
    );

    final Http http = Http.get(system);
    final Flow<HttpRequest, HttpResponse, NotUsed> routeFlow = route.flow(system, materializer);
    final CompletionStage<ServerBinding> binding = http.bindAndHandle(routeFlow, ConnectHttp.toHost("localhost", 8080), materializer);
  }
}

Converting routes between Java and Scala DSLs

在 Java 和 Scala DSL 之间转换路由

In some cases when building reusable libraries that expose routes, it may be useful to be able to convert routes between their Java and Scala DSL representations. You can do so using the asScala method on a Java DSL route, or by using an RouteAdapter to wrap an Scala DSL route.

在某些情况中,在构建路由的可复用库时,能够在 Java 和 Scala DSL 之间转换路由可能是有用的。 你可以通过在 Java DSL 路由上使用 asScala 方法,或者使用 RouteAdapter 包装一个 Scala DSL 来做到。

Converting Scala DSL routes to Java DSL:

转换 Scala DSL 路由到 Java DSL:

Scala
val scalaRoute: akka.http.scaladsl.server.Route =
  akka.http.scaladsl.server.Directives.get {
    akka.http.scaladsl.server.Directives.complete("OK")
  }

val javaRoute: akka.http.javadsl.server.Route =
  akka.http.javadsl.server.directives.RouteAdapter.asJava(scalaRoute)
Java
scala.Function1<
    akka.http.scaladsl.server.RequestContext,
    scala.concurrent.Future<akka.http.scaladsl.server.RouteResult>> scalaRoute = someRoute();

akka.http.javadsl.server.Route javaRoute =
    RouteAdapter.asJava(scalaRoute);

Converting Java DSL routes to Scala DSL:

转换 Java DSL 路由到 Scala DSL:

Scala
val javaRoute =
  akka.http.javadsl.server.Directives.get(new Supplier[akka.http.javadsl.server.Route] {
    override def get(): Route = akka.http.javadsl.server.Directives.complete("ok")
  })

// Remember that Route in Scala is just a type alias:
//   type Route = RequestContext => Future[RouteResult]
val scalaRoute: akka.http.scaladsl.server.Route = javaRoute.asScala
Java
Route javaRoute = Directives.get(() ->
    Directives.complete("okey")
);

scala.Function1<RequestContext, Future<RouteResult>> scalaRoute =
    javaRoute.asScala();
在此文档中发现错误?该页面的源代码可以在 这里 找到。欢迎随时编辑并提交 Pull Request。