• Akka-CQRS(12)- akka-http for http-web-service: Routing-服务项目接口


       上篇提到,按当前对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

    没错,解析正确!

  • 相关阅读:
    重构第四天 : 用多态替换条件语句(if else & switch)
    MSBuild 教程(2)
    为什么Nhibernate中属性和方法必须Virtual的
    重构第三天:提升方法&下移方法
    重构第二天:移动方法
    重构第一天:封装集合
    MSbuild 教程
    工程经验总结之吹水"管理大境界"
    呕心沥血之作:完美解决Informix的中文乱码问题
    万事开头难——我的蛮荒时代
  • 原文地址:https://www.cnblogs.com/tiger-xc/p/11056657.html
Copyright © 2020-2023  润新知