RequestContext.reject如何工作?

时间:2014-11-18 19:05:31

标签: scala akka spray

查看spray API,RequestContext是不可变的,RequestContext.reject返回Unit - 所以Spray路由如何知道"请求"被拒绝了?

http://spray.io/documentation/1.2.2/spray-routing/key-concepts/routes/

I.e。:假设我们有路线:a-> b

如果b拒绝该请求(通过致电RequestContext.reject),a如何通知?

我想我不确定"响应者" (请参阅RequestContext的文档中的The Responder Chain)。 b的响应者会a吗?或者响应者是发起http请求的原始Actor吗?

2 个答案:

答案 0 :(得分:5)

它是如何工作的:

  1. spray-can层HTTP连接actor向服务发送一个HttpRequest对象,并期望一个HttpResponse
  2. HttpService接收此消息并将原始发件人(即喷涂层连接参与者)保存为根"响应者"
  3. 它创建RequestContext,它将请求和响应者都保存为需要向其发送响应的ActorRef。
  4. RequestContext上的基本操作如complete只是向响应者发送消息以完成请求
  5. 此外,对于reject,会向响应者发送一条消息。但是,由于原始响应者只是喷雾罐级别Actor的ActorRef,并且拒绝消息不是由喷雾罐处理的,因此路由层必须挂钩到消息处理中以使用RejectionHandler转换拒绝一个普通的HttpResponse。这可以通过例如完成。通过使用RequestContext.withRouteResponseMapped返回一个新的RequestContext ,其中包含一个新的响应者,它将旧的应用程序应用到响应者收到的所有消息上,然后将结果转发给原始响应者。
  6. 这样可以实现RequestContext.withRejectionHandling:它返回一个新的RequestContext ,其中包含一个新的包装响应器,它将给定的函数应用于拒绝消息,然后将结果转发给包装的响应者。 / LI>
  7. 现在,a ~ b的例子是如何工作的(我理解你的意思是" a-> b")?如果您查看implementation of ~,则只会使用withRejectionHandling
  8. def ~(otherRoute: Route): Route = { ctx ⇒
      firstRoute {
        ctx.withRejectionHandling { rejections ⇒
          otherRoute(ctx.withRejectionsMapped(rejections ++ _))
        }
      }
    }
    

    它传递给第一个路径(a)一个RequestContext,它注册了一个拒绝处理程序,如果第一个路由被拒绝,它将运行第二个路由(b)。然后将使用另一个RequestContext调用第二个路径,该RequestContext也是从原始路径派生的,在另一个拒绝的情况下,将聚合来自两个路由的拒绝。

    您还可以通过查看堆栈跟踪来了解发生了什么。对于这些路线定义

    val a = (ctx: RequestContext) => ctx.reject()
    val b = { (ctx: RequestContext) =>
      Thread.dumpStack()
      ctx.complete("hello world")
    }
    val demoRoute =  a ~ b
    

    打印此堆栈跟踪(需要从下往上读取):

      java.lang.Exception: Stack trace
        at java.lang.Thread.dumpStack(Thread.java:1365)
    
        // arrived at `b`
        at spray.examples.DemoService$$anonfun$3.apply(DemoService.scala:42)
        at spray.examples.DemoService$$anonfun$3.apply(DemoService.scala:41)
    
        // `~` running the second route
        at spray.routing.RouteConcatenation$RouteConcatenation$$anonfun$$tilde$1$$anonfun$apply$1.apply(RouteConcatenation.scala:32)
        at spray.routing.RouteConcatenation$RouteConcatenation$$anonfun$$tilde$1$$anonfun$apply$1.apply(RouteConcatenation.scala:31)
    
        // `RequestContext.withRejectionHandling` handling the Rejection
        at spray.routing.RequestContext$$anonfun$withRejectionHandling$1.applyOrElse(RequestContext.scala:130)
        at scala.runtime.AbstractPartialFunction$mcVL$sp.apply$mcVL$sp(AbstractPartialFunction.scala:33)
        at scala.runtime.AbstractPartialFunction$mcVL$sp.apply(AbstractPartialFunction.scala:33)
        at scala.runtime.AbstractPartialFunction$mcVL$sp.apply(AbstractPartialFunction.scala:25)
    
        // `RequestContext.withRouteResponseHandling` doing its thing
        at spray.routing.RequestContext$$anon$1.handle(RequestContext.scala:84)
        at akka.spray.UnregisteredActorRefBase.$bang(UnregisteredActorRefBase.scala:72)
        at spray.routing.RequestContext.reject(RequestContext.scala:202)
    
        // `a` rejecting the request
        at spray.examples.DemoService$$anonfun$2.apply(DemoService.scala:40)
        at spray.examples.DemoService$$anonfun$2.apply(DemoService.scala:40)
    
        // `~` running route `a`
        at spray.routing.RouteConcatenation$RouteConcatenation$$anonfun$$tilde$1.apply(RouteConcatenation.scala:30)
        at spray.routing.RouteConcatenation$RouteConcatenation$$anonfun$$tilde$1.apply(RouteConcatenation.scala:29)
    
        // HttpService infrastructure
        at spray.routing.directives.BasicDirectives$$anonfun$mapRequestContext$1$$anonfun$apply$1.apply(BasicDirectives.scala:30)
        at spray.routing.directives.BasicDirectives$$anonfun$mapRequestContext$1$$anonfun$apply$1.apply(BasicDirectives.scala:30)
        at spray.routing.directives.ExecutionDirectives$$anonfun$handleExceptions$1$$anonfun$apply$4.apply(ExecutionDirectives.scala:35)
        at spray.routing.directives.ExecutionDirectives$$anonfun$handleExceptions$1$$anonfun$apply$4.apply(ExecutionDirectives.scala:33)
        at spray.routing.HttpServiceBase$class.runSealedRoute$1(HttpService.scala:36)
        at spray.routing.HttpServiceBase$$anonfun$runRoute$1.applyOrElse(HttpService.scala:46)
    
        // HttpService receiving the request
        at akka.actor.ActorCell.receiveMessage(ActorCell.scala:425)
        at akka.actor.ActorCell.invoke(ActorCell.scala:386)
        at akka.dispatch.Mailbox.processMailbox(Mailbox.scala:230)
        at akka.dispatch.Mailbox.run(Mailbox.scala:212)
        at akka.dispatch.ForkJoinExecutorConfigurator$MailboxExecutionTask.exec(AbstractDispatcher.scala:506)
        at scala.concurrent.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)
        at scala.concurrent.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339)
        at scala.concurrent.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)
        at scala.concurrent.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)
    

    作为旁注:大多数复杂性来自喷雾通过副作用完成响应,即通过向ActorRef发送消息。在即将推出的喷雾继承者中,akka-http,而不是Route will simply return a RouteResult,这使得控制流程更容易理解。例如。比较Route.~的新实现,它现在只对第一条路线的结果进行模式匹配,看它是否拒绝了请求,在这种情况下运行第二条路线。

答案 1 :(得分:2)

虽然RequestContext本身可能是不可变的,但它包含的值清楚地表明它会执行副作用。在不查看代码的情况下,我认为它与responder: ActorRef值有关。在github上找到reject的实现可能并不困难。

case class RequestContext(
  request: HttpRequest, 
  responder: ActorRef, 
  unmatchedPath: Path) extends Product with Serializable

修改:对reject的来电确实会向responder发送消息(通过@ j-keck的评论)https://github.com/spray/spray/blob/1ce512cbd17380655fe1756b524c7f19dc9a3de3/spray-routing/src/main/scala/spray/routing/RequestContext.scala#L195

编辑:我刚刚注意到你问题的最后一部分。根据我在代码中看到的内容,可能在一些自定义指令中替换了响应者。我不知道这是否真的在实践中发生,抱歉。根据指令文档,原始responder最终应该间接收到响应。我的猜测是,当Future调用创建的?完成后,原始请求就会关闭。如果未向原始?中的responder发送任何回复,则此RequestContext的来电最终会超时。

http://spray.io/documentation/1.2.2/spray-routing/key-concepts/directives/#the-responder-chain

  

原始RequestContext的响应者,即发送者   HttpRequest的ActorRef接收响应并将其发送给   客户。