框架 | spray-routing的核心流程

最近我们在一个项目上使用spray来发布restful service。

spray是个性能很好而且功能非常完整的service框架,包含很多组件,从底层http服务器到高层的rest路由DSL都有。一般简单的应用就使用和掌握好最高层的spray-routing就够用。本文主要讲spray-routing,不及其余。 spray整体的设计理念,spray和akka的关系留待以后的博客。

spray-routing上手很容易,但是有一些比较独特的概念和设计。如果没有一定的理解,就会发现当系统复杂到一定程度时对于有些需求不知道该怎么实现了。为方便大家掌握使用,本文主要解释了spray的核心流程,而不涉及深入讲解最核心的Directive(指令)。

spray发布http service的流程如下:

整个流程由spray框架控制,http连接处理由spray-can或spray-servlet负责,大部分情况下,开发人员只要定义路由——url和业务服务的映射——以及对应的业务服务即可,注意这个路由定义并不是一个配置文件,而是spray-routing定义的一套scala的DSL。

请求到达时,spray会先查找路由定义,如果请求的URL没有找到最终能完成请求的服务则会拒绝(reject)。 如果找到,则spray会根据你在路由定义里的配置,把请求参数转成业务对象(比如用json4s把json请求转换成scala对象,需要用Entity指令来定义),然后调用业务服务。调用可能有三种结果:

  • 业务处理正常返回,则将返回的业务对象根据配置的转换方式转换回HttpResponse,再返回给客户端
  • 调用业务服务超时,则交由一个可覆盖的超时处理器处理,默认实现是返回500内部服务器错,并带一个默认的错误信息
        def timeoutRoute: Route = complete(
              InternalServerError,
              "The server was not able to produce a timely response to your request.")
  • 业务服务抛异常,跟超时处理一样会被交给一个可自定义的异常处理块去统一处理

我们的路由服务一般继承HttpService,HttpService继承自HttpServiceBase,其中提供了runRoute方法,也就是路由的入口。

def runRoute(route: Route)(implicit eh: ExceptionHandler, rh: RejectionHandler, ac: ActorContext,rs: RoutingSettings, log: LoggingContext): Actor.Receive

runRoute的参数是Route:

type Route = RequestContext ⇒ Unit

是RequestContext => Unit的类型别名

我们按照spray例子写的path("xx")...其实就是定义了一个RequestContext => Unit的函数,也就是如何从请求上下文里解析请求内容,调用业务服务。 比较奇怪的是返回类型是Unit,spray会调用RequestContext里包含的responder成员来负责将响应返回给客户端。 据spray-routing文档里说是为了”non-blocking"和"actor friendly",但实际上在spray的后续版本,也就是akka-http里把这个返回类型改成了RouteResult,也就是RequestContext => RouteResult,这样感觉更合理,更对应于HttpRequest => HttpResponse的本质。

我们完全可以定义一个RequestContext ⇒ Unit类型的路由,然后自己从RequestContext里解析出请求数据,自己做数据转换,自己决定应该调用什么服务(实际上有些时候我们确实要这么做)。 但大部分时候我们可以用spray-routing通过一组Directive——翻译成中文就是指令——提供的路由DSL来定义我们的路由。这也是spray-routing提供的最核心的功能。

比如官方文档里的路由例子:

import spray.routing._
import Directives._
val route: Route =
  path("order" / IntNumber) { id =>
    get {
      complete {
        "Received GET request for order " + id
      }
    } ~
    put {
      complete {
        "Received PUT request for order " + id
      }
    }
  }

path、get、put、complete都是指令,spray的指令实现用了一种比较复杂的模式叫做磁铁模式(maget pattern)。

拿上面代码里的path为例,directive的一般形式为:

name(arguments) { extractions =>
  ... // inner Route
}

spray对RequestContext => Unit要完成的事情通过directive做了很好的抽象:

  • 转换——将RequestContext做一些转换再传给下一级路由
  • 过滤——拒绝不符合条件的请求
  • 抽取——从RequestContext里抽取一些信息,使之在下级路由中可用,比如上例中的id =>
  • 完成请求——比如上例中的complete{ }

对于过滤功能而言,还需要能“并联”——如果这个路径与请求不匹配,spray要去尝试下一个路径,有点像嵌套的模式匹配。 在spray-routing里并联用的是操作符 “~” 在前例中的get和put分支的并联可以看得很清楚。 但”~“不是唯一的把directive组合起来的方法,当路由定义变得庞大的时候,我们会需要某种方法把大量类似的结构抽取出来免得写出一棵巨大无比的路由树。

再回头看一下前面的流程图,除了正常路由、正常处理外还有拒绝,异常,超时三个分支。看上去好像我们只定义了正常处理的逻辑,实际上是我们的spray路由的入口runRoute这个方法偷偷做了默认处理:

def runRoute(route: Route)(implicit eh: ExceptionHandler, rh: RejectionHandler, ac: ActorContext,rs: RoutingSettings, log: LoggingContext): Actor.Receive

里面有隐式参数ExceptionHandler和RejectionHandler,以及一个隐藏的超时处理:

case Timedout(request: HttpRequest) ⇒ runRoute(timeoutRoute)(eh, rh, ac, rs, log)(request)

默认的拒绝实现对于常见的拒绝原因都给出正确的错误码和不错的返回信息,比如

case AuthorizationFailedRejection :: _ ⇒

complete(Forbidden, "The supplied authentication is not authorized to access this resource")

异常处理器和超时处理器也一样,如果你需要定制也可以定制自己的处理器,具体方法可以查阅Spray官方文档介绍的Reject Handler,Exception Handler和Timeout Handler。

原文发布于微信公众号 - 逸言(YiYan_OneWord)

原文发表时间:2015-01-23

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏落影的专栏

求职笔记-iOS篇

前言 今年年初求职时,整理、回顾了学习iOS开发以来收获的知识,此篇为当时的笔记。 插一段我对面试的看法。 公司要在短短的几个小时内要详细了解求职者,并且求...

3466
来自专栏我杨某人的青春满是悔恨

Swift API 设计指南(上)

本文翻译自苹果官方文档:Swift API Design Guidelines,如有错漏,欢迎指出。

843
来自专栏我杨某人的青春满是悔恨

走进 RxSwift 之观察者模式

RxSwift 是 ReactiveX 系列的 Swift 版本,如果你之前用过 ReactiveCocoa(RAC) 的话,想必对 Functional Re...

1032
来自专栏高性能服务器开发

API设计原则 – QT官网的设计实践总结

原文链接:API Design Principles – Qt Wiki 链接:(http://wiki.qt.io/API_Design_Principles...

922
来自专栏java工会

Java 8 最佳技巧

21012
来自专栏更流畅、简洁的软件开发方式

【自然框架】之数据访问 —— 再小的类库也需要设计。

  以前也写过几篇关于数据访问的,这里是最新的总结。麻雀虽小五脏俱全,数据访问也许不起眼,但是也要好好的设计一翻。从2004年开始用自己的数据访问,一直到现在,...

1899
来自专栏cmazxiaoma的架构师之路

Listener、Filter、Interceptor的那些事

1344
来自专栏我杨某人的青春满是悔恨

设计模式之结构型模式(下)

上篇已经介绍了适配器模式、桥接模式和组合模式,这篇将介绍装饰者模式、外观模式、享元模式和代理模式。

935
来自专栏梧雨北辰的开发录

iOS面试知识总结之代码片段

凡经历过iOS面试的我们总会发觉,即使实际开发中做过许多项目,也难免为一个普通的面试题受挫。这也许不是因为我们技术不过关,而是因为在平时我们忽略了怎样将用到的知...

2766
来自专栏双十二技术哥

Android性能优化(六)之卡顿那些事

对普通用户而言,类如内存占用高、耗流量、耗电量等性能问题可能不会轻易发现,但是卡顿问题用户一定会立马直观的感受到。本文就带你一览卡顿的发生、检测、及优化。

652

扫码关注云+社区