spray.io的文档太晦涩,啃起来太痛苦了。还有,屋里真冷。路由
“路由”是spray-routing的中心概念,因为所有你用DSL创建的结构都是Route的子类型。在spray-routing中一个路由需要如下方式定义:
type Route = RequestContext => Unit
它是一个把RequestContext作为参数的函数的别名。
不同于你最初所期望的,一个路由不返回任何东西。相反,所有响应处理(在路由处理完一个请求后需要完成的所有事情)是通过 RequestContext的响应器来执行的(in “continuation-style”)。如果你不知道这意味着什么的话,别担心。这些很快就会变得清晰。关键是,这种设计的优点是完全非阻塞以及 Actor友好的,因为这种方式,它可以简单地把一个RequestContext发送给另一个Actor(以“fire-and-forget”的方 式),而不必担心结果处理。
通常当一个路由接收到一个请求(或者说是一个RequestContext)它可以做这三件事中的一个:
- 通过调用requestContext.complete(...)完成这个请求
- 通过调用requestContext.reject(...)拒绝这个请求
- 忽略这个请求(既不完成也不拒绝)
第一种情况相当清楚,通过调用complete将一个给定的响应发送到客户端作为这个请求的反应。第二种情况中,“拒绝”意味着路由不想处理这个请 求。You’ll see further down in the section about route composition what this is good for。第三种情况通常是一个错误。如果一个路由对请求不做任何事情,它会简单的不对它产生作用。这意味着客户端将接收不到响应,直到请求超时,此时会生 成一个“500 Internal Server Error ”响应。因此你的路由通常以完成或拒绝请求来结束。
构造路由
路由是普通的函数(RequestContext => Unit),最简单的路由是:
ctx => ctx.complete("Response")
更简短的:
_.complete("Response")
还有更短的(用complete指令):
complete("Response")
这些都是不同的方式定义相同的东西,即一个路由用一个静态响应简单地完成所有请求。
虽然你可以把所有应用逻辑写到一个函数里,来检查RequestContext并根据它的属性来完成它,但这种设计很难阅读,维护和重用。因此spray-routing允许你通过组合简单的路由来构造更复杂的路由。
组合路由
用简单路由构造复杂路由有三个基本的操作:
- 路由转换,which delegates processing to another, “inner” route but in the process changes some properties of either the incoming request, the outgoing response or both(什么意思???)
- 路由过滤,只允许满足给定过滤器条件的请求通过,并拒绝其他所有的
- 路由链,如果第一个路由拒绝了,它会尝试第二个
最后一点通过简单的~操作来完成,它对所有路由都有效,例如一个隐式的“扩展”。前两点由Directives来提供,spray-routing已经预定义了大多数,你也可以自己简单地创建。Directives给予spray-routing大部分能力和灵活性。
路由树
基本上,当你通过嵌套和~操作来结合指令和自定义路由时,你构造了一个树形路由结构。当一个请求到来,它被注入到树的根,并以深度优先的方式向下流经所有分支,直到有节点完成,或全部拒绝。
考虑这个例子:
val route =
a {
b {
c {
... // route 1
} ~
d {
... // route 2
} ~
... // route 3
} ~
e {
... // route 4
}
}
有5个构成路由树的指令。
- 路由1仅在指令a,b和c都通过时可达。
- 如果a,b通过,c拒绝并且d通过,将运行路由2。
- 如果a和b通过,但c和d拒绝,将运行路由3。
路由3因此可以被视为一种“全方位”路由(如果路由链接前面的位置拒绝)。这种机制可以使复杂的过滤逻辑很容易实现:简单地把最具体的情况提前并把一般情况放后面。