上篇提到, 按当前对 web-service 功能需要, 我们需要完成数据转换 marshalling, 服务接口 routing 这两部分的调研和示范. 上篇已经完成了对序列化 marshalling 的讨论, 这篇就介绍一下 routing 了. akka-http 提供了一套功能强大, 使用又很方便的 Routing DSL.Route 是个类型:
type Route = RequestContext Future[RouteResult]
实际上就是个把 HttpRequest 转换成 HttpResponse 的函数. 举个例子:
- val route: Flow[HttpRequest, HttpResponse, NotUsed]=
- get {
- pathSingleSlash {
- complete(HttpEntity(ContentTypes.`text/html(UTF-8)`,"<html><body>Hello world!</body></html>"))
- } ~
- path("ping") {
- complete("PONG!")
- } ~
- path("crash") {
- sys.error("BOOM!")
- }
- }
这个 route 是个 handler Flow, 但 Route 可以用 RouteResult.route2HandlerFlow 转换成 Flow:
- /**
- * Turns a `Route` into a server flow.
- *
- * This conversion is also implicitly available through [[RouteResult#route2HandlerFlow]].
- */
- def handlerFlow(route: Route)(implicit
- routingSettings: RoutingSettings,
- parserSettings: ParserSettings,
- materializer: Materializer,
- routingLog: RoutingLog,
- executionContext: ExecutionContextExecutor = null,
- rejectionHandler: RejectionHandler = RejectionHandler.default,
- exceptionHandler: ExceptionHandler = null): Flow[HttpRequest, HttpResponse, NotUsed] =
- Flow[HttpRequest].mapAsync(1)(asyncHandler(route))
- ...
- implicit def route2HandlerFlow(route: Route)(
- implicit
- routingSettings: RoutingSettings,
- parserSettings: ParserSettings,
- materializer: Materializer,
- routingLog: RoutingLog,
- executionContext: ExecutionContext = null,
- rejectionHandler: RejectionHandler = RejectionHandler.default,
- exceptionHandler: ExceptionHandler = null
- ): Flow[HttpRequest, HttpResponse, NotUsed] =
- Route.handlerFlow(route)
route 是由 Directive 类组合而成的一个决策树 decision-tree.get,path,pathSingleSlash 等都是 Directive, 如:
def path[L](pm: PathMatcher[L]): Directive[L] = pathPrefix(pm ~ PathEnd)
然后 complete 返回 Route 类:
- def complete(m: ToResponseMarshallable): StandardRoute =
- StandardRoute(_.complete(m))
- ...
- abstract class StandardRoute extends Route {
- def toDirective[L: Tuple]: Directive[L] = StandardRoute.toDirective(this)
- }
Directive 的主要功能就是对 HttpRequest 的 Uri 进行解析, 找出具体的服务接口点, 已经对 entity 里的数据进行调取.
Route 是一种可组合组件. 我们可以用简单的 Route 组合成更多层次的 Route. 下面是组合 Route 的几种方式:
1,Route 转化: 对输入的 request, 输出的 response 进行转化处理后把实际运算托付给下一层内部 (inner)Route
2, 筛选 Route: 只容许符合某种条件的 Route 通过并拒绝其它不符合条件的 Route
3, 链接 Route: 假如一个 Route 被拒绝, 尝试下一个 Route. 这个是通过 ~ 操作符号实现的
在 Akka-http 的 routing DSL 里这些 Route 组合操作是通过 Directive 实现的. Akka-http 提供了大量现成的 Directive, 我们也可以自定义一些特殊功能的 Directive, 详情可以查询官方文件或者 API 文件.
Directive 的表达形式如下:
- dirname(arguments) { extractions =>
- ... // 内层 inner route
- }
下面是 Directive 的一些用例:
下面的三个 route 效果相等:
- val route: Route = { ctx =>
- if (ctx.request.method == HttpMethods.GET)
- ctx.complete("Received GET")
- else
- ctx.complete("Received something else")
- }
- val route =
- get {
- complete("Received GET")
- } ~
- complete("Received something else")
- val route =
- get { ctx =>
- ctx.complete("Received GET")
- } ~
- complete("Received something else")
下面列出一些 Directive 的组合例子:
- val route: Route =
- path("order" / IntNumber) { id =>
- get {
- complete {
- "Received GET request for order" + id
- }
- } ~
- put {
- complete {
- "Received PUT request for order" + id
- }
- }
- }
- def innerRoute(id: Int): Route =
- get {
- complete {
- "Received GET request for order" + id
- }
- } ~
- put {
- complete {
- "Received PUT request for order" + id
- }
- }
- val route: Route = path("order" / IntNumber) { id => innerRoute(id) }
- val route =
- path("order" / IntNumber) { id =>
- (get | put) { ctx =>
- ctx.complete(s"Received ${ctx.request.method.name} request for order $id")
- }
- }
- val route =
- path("order" / IntNumber) { id =>
- (get | put) {
- extractMethod { m =>
- complete(s"Received ${m.name} request for order $id")
- }
- }
- }
- val getOrPut = get | put
- val route =
- path("order" / IntNumber) { id =>
- getOrPut {
- extractMethod { m =>
- complete(s"Received ${m.name} request for order $id")
- }
- }
- }
- val route =
- (path("order" / IntNumber) & getOrPut & extractMethod) { (id, m) =>
- complete(s"Received ${m.name} request for order $id")
- }
- val orderGetOrPutWithMethod =
- path("order" / IntNumber) & (get | put) & extractMethod
- val route =
- orderGetOrPutWithMethod { (id, m) =>
- complete(s"Received ${m.name} request for order $id")
- }
我们可以从上面这些示范例子得出结论: Directive 的组合能力是 routing DSL 的核心. 来看看 Directive 的组合能力是如何实现的. Directive 类定义如下:
- //#basic
- abstract class Directive[L](implicit val ev: Tuple[L]) {
- /**
- * Calls the inner route with a tuple of extracted values of type `L`.
- *
- * `tapply` is short for "tuple-apply". Usually, you will use the regular `apply` method instead,
- * which is added by an implicit conversion (see `Directive.addDirectiveApply`).
- */
- def tapply(f: L Route): Route
- ...
- }
- /**
- * Constructs a directive from a function literal.
- */
- def apply[T: Tuple](f: (T Route) Route): Directive[T] =
- new Directive[T] { def tapply(inner: T Route) = f(inner) }
- /**
- * A Directive that always passes the request on to its inner route (i.e. does nothing).
- */
- val Empty: Directive0 = Directive(_(()))
- ...
- implicit class SingleValueModifiers[T](underlying: Directive1[T]) extends AnyRef {
- def map[R](f: T R)(implicit tupler: Tupler[R]): Directive[tupler.Out] =
- underlying.tmap { case Tuple1(value) f(value) }
- def flatMap[R: Tuple](f: T Directive[R]): Directive[R] =
- underlying.tflatMap { case Tuple1(value) f(value) }
- def require(predicate: T Boolean, rejections: Rejection*): Directive0 =
- underlying.filter(predicate, rejections: _*).tflatMap(_ Empty)
- def filter(predicate: T Boolean, rejections: Rejection*): Directive1[T] =
- underlying.tfilter({ case Tuple1(value) predicate(value) }, rejections: _*)
- }
- }
注意 Directive.apply 参数 f: (T =>Route)=>Route), 代表 dirname (args){extractions => ...} 这样的构建函数款式. 还有 implicit ev: Tuple[L] 是给 compiler 的证例, 它要求 Tuple[L] 存在于可视域. Akka-http 提供了所有 22 个 TupleXX[L] 的隐形实例. 再注意 implicit class singleValueModifiers[T]: 它提供了多层 Directive 的自动展平, 能够实现下面的自动转换结果:
- Directive1[T] = Directive[Tuple1[T]]
- Directive1[Tuple2[M,N]] = Directive[Tuple1[Tuple2[M,N]]] = Directive[Tuple2[M,N]]
- Directive1[Tuple3[M,N,G]] = ... = Directive[Tuple3[M,N,G]]
- Directive1[Tuple4[M1,M2,M3,M4]] = ... = Directive[Tuple4[M1,M2,M3,M4]]
- ...
- Directive1[Unit] = Directive0
- Directive1,Directive0:
- type Directive0 = Directive[Unit]
- type Directive1[T] = Directive[Tuple1[T]]
下面是这几种 Directive 的使用模式:
- dirname { route } //Directive0
- dirname[L] { L => route } //Directive1[L]
- dirname[T] { (T1,T2...) => route} //Directive[T]
任何类型值到 Tuple 的自动转换是通过 Tupler 类实现的:
- /**
- * Provides a way to convert a value into an Tuple.
- * If the value is already a Tuple then it is returned unchanged, otherwise it's wrapped in a Tuple1 instance.
- */
- trait Tupler[T] {
- type Out
- def OutIsTuple: Tuple[Out]
- def apply(value: T): Out
- }
- object Tupler extends LowerPriorityTupler {
- implicit def forTuple[T: Tuple]: Tupler[T] { type Out = T } =
- new Tupler[T] {
- type Out = T
- def OutIsTuple = implicitly[Tuple[Out]]
- def apply(value: T) = value
- }
- }
- private[server] abstract class LowerPriorityTupler {
- implicit def forAnyRef[T]: Tupler[T] { type Out = Tuple1[T] } =
- new Tupler[T] {
- type Out = Tuple1[T]
- def OutIsTuple = implicitly[Tuple[Out]]
- def apply(value: T) = Tuple1(value)
- }
- }
好了, 还是回到具体的 Uri 解析上来吧. 在 POS 例子里需要上传的指令款式如下:
- http://192.168.11.189:2588/pos/logon?shopid=1101&opr=1010
- http://192.168.11.189:2588/pos/logoff?shopid=1101
- http://192.168.11.189:2588/pos/logsales?shopid=1101&acct=001&dpt=01&code=978111&qty=3&price=1200
- http://192.168.11.189:2588/pos/shopid=1101&subtotal?level=0
- http://192.168.11.189:2588/pos/shopid=1101&discount?disctype=2&grouped=true&code=481&percent=20
基本上全部是 Uri Path 解析的工作. 下面是具体的 Route 示范:
- val route =
- (pathPrefix("pos-on-cloud") & get) {
- ((pathSuffix("logon") & parameters('shopid.as[Int],'opr)){ (id, op) =>
- complete(s"logon: shopid=$id and opr=$op")
- }
- ~ (pathSuffix("logoff") & parameter('shopid.as[Int])){ id =>
- complete(s"logoff: shopid=$id")
- }
- ~ (pathSuffix("logsales") & parameters(
- 'shopid.as[Int],
- 'acct,
- 'dpt,
- 'code,
- 'qty.as[Int],
- 'price.as[Int]
- )){ (id,acct,dpt,code,qty,price) =>
- complete(s"logsales: shopid=$id,$acct,$dpt,$code,$qty,$price")
- }
- ~ (pathSuffix("subtotal") & parameters('shopid.as[Int],'level)){ (id,l) =>
- complete(s"subtotal: shopid=$id, level=$l")
- }
- )
- }
用 browser 来测试:
- http://192.168.11.189:8011/pos-on-cloud/logsales?shopid=1101&acct=001&dpt=01&code=978111&qty=3&price=1200
- logsales: shopid=1101,001,01,978111,3,1200
没错, 解析正确!
来源: https://www.cnblogs.com/tiger-xc/p/11056657.html