路由 DSL 概述
Routing DSL Overview
The Akka HTTP Core Server API provides a Flow
Flow
- or Function
-level interface that allows an application to respond to incoming HTTP requests by simply mapping requests to responses (excerpt from Low-level server side example):
Akka HTTP 核心服务器 API 提供 Flow
Flow
- 或 Function
-级的接口, 允许应用程序通过简单的把请求映射到响应来回应传入的 HTTP 请求(节选自 低级服务器端示例):
- Scala
-
import akka.actor.ActorSystem import akka.http.scaladsl.Http import akka.http.scaladsl.model.HttpMethods._ import akka.http.scaladsl.model._ import akka.stream.ActorMaterializer import scala.io.StdIn object WebServer { def main(args: Array[String]) { implicit val system = ActorSystem() implicit val materializer = ActorMaterializer() // needed for the future map/flatmap in the end implicit val executionContext = system.dispatcher val requestHandler: HttpRequest => HttpResponse = { case HttpRequest(GET, Uri.Path("/"), _, _, _) => HttpResponse(entity = HttpEntity( ContentTypes.`text/html(UTF-8)`, "<html><body>Hello world!</body></html>")) case HttpRequest(GET, Uri.Path("/ping"), _, _, _) => HttpResponse(entity = "PONG!") case HttpRequest(GET, Uri.Path("/crash"), _, _, _) => sys.error("BOOM!") case r: HttpRequest => r.discardEntityBytes() // important to drain incoming HTTP Entity stream HttpResponse(404, entity = "Unknown resource!") } val bindingFuture = Http().bindAndHandleSync(requestHandler, "localhost", 8080) println(s"Server online at http://localhost:8080/\nPress RETURN to stop...") StdIn.readLine() // let it run until user presses return bindingFuture .flatMap(_.unbind()) // trigger unbinding from the port .onComplete(_ => system.terminate()) // and shutdown when done } }
- Java
-
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(ContentTypes.TEXT_HTML_UTF8, "<html><body>Hello world!</body></html>"); } else if (uri.path().equals("/hello")) { String name = uri.query().get("name").orElse("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 pattern-matching againstinspecting the incoming HttpRequest
HttpRequest
(maybe with the help of a few extractors in the way of Unfiltered) this approach becomes somewhat unwieldy for larger services due to the amount of syntax “ceremony” required. Also, it doesn’t help in keeping your service definition as DRY as you might like.
虽然完全可以通过 模式匹配检查 传入的 HttpRequest
HttpRequest
(允许,在一此抽取器的帮助下以 Unfiltered 的方式) 来定义一个完整、纯粹的 REST API 服务, 但由于所需语法“仪式”的数量,该方法对于大量服务显得笨重。 而且,它不能帮助服务定义保持 DRY 。
As an alternative Akka HTTP provides a flexible DSL for expressing your service behavior as a structure of composable elements (called Directives) in a concise and readable way. Directives are assembled into a so called route structure which, at its top-level, can be used to create a handler Flow
Flow
or async handler function that can be directly supplied to a bind
call. The conversion from Route
Route
Route
to flow can either be invoked explicitly using Route.handlerFlow
or, otherwise, the conversion is also provided implicitly by RouteResult.route2HandlerFlow
[1].
作为替代,Akka HTTP 提供了一种灵活的 DSL 以简洁易读的方式表达服务行为表示为可组合元素(称为 指令 )的结构。 指令被组装成 路由结构 ,在它的顶层可用于创建处理程序 Flow
Flow
或异步处理函数,该处理函数可以直接提供给 bind
调用。 @scala[从 Route
转换Route
Route
到 flow 可以显示调用 Route.handlerFlow
,或者也可以由 RouteResult.route2HandlerFlow
隐式提供。
Here’s the complete example rewritten using the composable high-level API:
这里是使用可组合的高级 API 重写的完整示例:
- Scala
-
import akka.actor.ActorSystem import akka.http.scaladsl.Http import akka.http.scaladsl.model.{ ContentTypes, HttpEntity } import akka.http.scaladsl.server.Directives._ import akka.stream.ActorMaterializer import scala.io.StdIn object WebServer { def main(args: Array[String]) { implicit val system = ActorSystem() implicit val materializer = ActorMaterializer() // needed for the future flatMap/onComplete in the end implicit val executionContext = system.dispatcher val route = get { concat( pathSingleSlash { complete(HttpEntity(ContentTypes.`text/html(UTF-8)`, "<html><body>Hello world!</body></html>")) }, path("ping") { complete("PONG!") }, path("crash") { sys.error("BOOM!") } ) } // `route` will be implicitly converted to `Flow` using `RouteResult.route2HandlerFlow` val bindingFuture = Http().bindAndHandle(route, "localhost", 8080) println(s"Server online at http://localhost:8080/\nPress RETURN to stop...") StdIn.readLine() // let it run until user presses return bindingFuture .flatMap(_.unbind()) // trigger unbinding from the port .onComplete(_ => system.terminate()) // and shutdown when done } }
- Java
-
import akka.NotUsed; import akka.actor.ActorSystem; import akka.http.javadsl.ConnectHttp; import akka.http.javadsl.Http; import akka.http.javadsl.ServerBinding; import akka.http.javadsl.model.ContentTypes; import akka.http.javadsl.model.HttpEntities; import akka.http.javadsl.model.HttpRequest; import akka.http.javadsl.model.HttpResponse; import akka.http.javadsl.server.AllDirectives; import akka.http.javadsl.server.Route; import akka.stream.ActorMaterializer; import akka.stream.javadsl.Flow; import java.io.IOException; import java.util.concurrent.CompletionStage; public class HighLevelServerExample extends AllDirectives { public static void main(String[] args) throws IOException { // boot up server using the route as defined below ActorSystem system = ActorSystem.create(); final HighLevelServerExample app = new HighLevelServerExample(); final Http http = Http.get(system); final ActorMaterializer materializer = ActorMaterializer.create(system); final Flow<HttpRequest, HttpResponse, NotUsed> routeFlow = app.createRoute().flow(system, materializer); final CompletionStage<ServerBinding> binding = http.bindAndHandle(routeFlow, ConnectHttp.toHost("localhost", 8080), materializer); System.out.println("Type RETURN to exit"); System.in.read(); binding .thenCompose(ServerBinding::unbind) .thenAccept(unbound -> system.terminate()); } public Route createRoute() { // This handler generates responses to `/hello?name=XXX` requests Route helloRoute = parameterOptional("name", optName -> { String name = optName.orElse("Mister X"); return complete("Hello " + name + "!"); }); return // here the complete behavior for this server is defined // only handle GET requests get(() -> concat( // matches the empty path pathSingleSlash(() -> // return a constant string with a certain content type complete(HttpEntities.create(ContentTypes.TEXT_HTML_UTF8, "<html><body>Hello world!</body></html>")) ), path("ping", () -> // return a simple `text/plain` response complete("PONG!") ), path("hello", () -> // uses the route defined above helloRoute ) )); } }
The core of the Routing DSL becomes available with a single import:
路由 DSL 的核心可用单个导入语句:
- Scala
-
import akka.http.scaladsl.server.Directives._
- Java
-
import static akka.http.javadsl.server.Directives.*;
Or by extending the akka.http.javadsl.server.AllDirectives
class which brings together all directives into a single class for easier access:
或者继承 akka.http.javadsl.server.AllDirectives
类,它将所有指令一起放入单个类中,以便于访问:
extends AllDirectives
Of course it is possible to directly import only the directives you need (i.e. WebSocketDirectives
WebSocketDirectives
etc).
当然,可以直接导入只需要的指令(例如: WebSocketDirectives
WebSocketDirectives
等)。
This example also relies on the pre-defined support for Scala XML with:
这个示例还依赖预定义的 Scala XML 支持:
import akka.http.scaladsl.marshallers.xml.ScalaXmlSupport._
The very short example shown here is certainly not the best for illustrating the savings in “ceremony” and improvements in conciseness and readability that the Routing DSL promises. The Long Example might do a better job in this regard.
这里显示的非常简短的例子当然不是路由 DSL 所承诺节省“仪式”、提高简洁性和可读性的最好的说明。 长的示例 在这方面也放做的更好。
For learning how to work with the Routing DSL you should first understand the concept of Routes.
要学习如何使用路由 DSL,你应该首先了解 路由 的概念。
[1] To be picked up automatically, the implicit conversion needs to be provided in the companion object of the source type. However, as
Route
Route
Route
is just a type alias forRequestContext => Future[RouteResult]
, there’s no companion object forRoute
Route
Route
. Fortunately, the implicit scope for finding an implicit conversion also includes all types that are “associated with any part” of the source type which in this case means that the implicit conversion will also be picked up fromRouteResult.route2HandlerFlow
automatically.
为了自动获取,隐式转换需要在源类型的伴身对象里提供。但是,因为 Route
@apidocRoute 只是 RequestContext => Future[RouteResult]
的类型别名,所以不存在 Route
@apidocRoute 伴身对象。 好在,查找隐式转换的 隐式范围 也包含在与源类型“任何部分相关”所有类型,在这种情况下,这意味着隐式转换将自动从 RouteResult.route2HandlerFlow
获取。