分享 Rack Middleware 新的理解

redemption · 2016年11月29日 · 最后由 redemption 回复于 2016年12月01日 · 5051 次阅读
本帖已被管理员设置为精华贴

疑惑

最近用到一个 gem 叫做 rest-client-component,它的主要作用就是让 restclient 能够添加中间件,从而使得在发出 http 请求之前或者在收到 http 应答之后对请求和应答做相关处理。但是在使用的时候,我发现它所添加的中间件是 rack 的中间件。这就引起了我的好奇。

分析

rack 本身是定义的 web server 与 web framework 之间的接口,那么在我印象中 rack 相关的中间件也一定是用于服务端的,这怎么可以用于客户端呢?于是就仔细研究了相关资料和源代码,才发现自己的之前的“认为“过于肤浅。

我们首先来看一个图,这个图描述了一次对基于 rack 的 web app 的请求应答的过程图:

从这个图中我们可以看到当一个 http 请求来临的时候,这个请求中的相关信息通过 server 和 rack handler 转换为了 env 中相关的内容,然后再经过 rack middleware 相关处理,最后经由 rack adapter 变为 web app 能够识别的信息。当不存在 rack middleware 的时候,那么传入 web app 的 env 的本身其实就是 client 发出的 request,他们之间的差别只是格式不同,但所表达的信息是一样的(当然这里说法并不准确,后面有解释)。

同样,当 web app 处理请求完成后,其处理的结果由 rack adapter 转换成了 [status, headers, [body]] 的形式,然后经过 rack middleware 处理,最后通过 rack handler 和 server 的处理,将这些信息变成一个 response 返回给客户端。那么当不存在 middleware 的时候,web app 返回的 [status, headers, [body]] 本质上与最后返回给客户端的 response 是一样的。

所以我们就可以将 env 和 [status, headers, [body]] 与 request 和 response 等同起来。那么从这里我们就可以看出来,rack middle 本身的作用其实就是处理 request 和 response。rack middleware 的作用就像这篇文章说的:

The fundamental idea behind Rack middleware is – come between the calling client and the server, process the HTTP request before sending it to the server, and processing the HTTP response before returning it to the client.

这句话改成这样或许更准确一点

The fundamental idea behind Rack middleware is – come between the calling client and the server, process the HTTP request before sending it to the server web application, and processing the HTTP response before returning it to the client.

这句话说的是 rack middleware 本身出现的目的。但是我们可以将 rack middleware 理解得更加广一点。rack middleware 的作用是处理 request 和 response 的,它就像是一根电线,电线里面有火线和零线,而火线和零线分别对应这里的 request 的通道和 response 的通道,而每个完整的请求应答信息 (env 与 [status, header, [body]]) 就像电流。就如下图一样:(当然这个电线也可能会短路。。)

电线可以加入进电流通路的任何地方。那么同样的 middleware 为什么不能够加入到 http 请求应答路径所经过的任何地方呢?

客户端本身就是每一个 http 请求应答所会经过的必经之路,那么当然 rack middleware 也就能够放在客户端使用了。当然由于 rack middleware 处理的 request 和 response 的格式并不一样,所以我们就要自己写格式相关的代码。当 rack middleware 放在客户端的时候,其结构就如下图 (以 restclient 为例):

当然,由于没有了 rack 的支持,client 还要实现 middleware 栈的调用机制。

补充

上面说当不存在 middleware 的时候,request 内容等同于 env。其实现实情况不一定,因为服务器也可能会对 request 做一些处理(比如 webrick 可以调用一些 callback),当然这并不损 env 代表一个 request 的事实。

其他想法

从 rack middleware 这个问题,我突然意识到,任何东西我们都要尽量要去掌握它的本质,而不要只看到它的表面或者它出现的背景。不然我们很可能无法广泛灵活的去应用这些东西。突然感觉我们日常生活中其实处处存在这个道理。大到核能的应用,核能最早出现也是用于战争,但是理解了它的本质,它也能用于人类的生产生活。小到日常生活中,我们会用凳子当做梯子使用,用废弃的饮料瓶作为花瓶。所以既然在生活中能够将一种东西按照他本身的特性去使用它,而并不只是局限于它出现的直接目的去使用它。那么在技术中,我们或许也可以视情况用这种思维去使用它们,或许有不同的惊喜。

结论

  1. rack middleware 本身存在的意义就是处理应用逻辑之外针对 http 本身的相关处理,既然针对 http 本身的处理,那么只要 middleware 本身的功能合适,那么这个 middleware 就可以放在整个 http 请求应答路径上的任何地方,也就包括这里的客户端。
  2. 任何东西我们都要尽量要去掌握它的本质,可以根据它的本质去灵活使用,而不只是根据它的表面或者它出现的直接目的去使用它。这样或许我们能看到另一篇天地。

还有就是我是菜鸟,请大家多多指教:)

缺了一栏结论,直接总结分析的内容会好些,参考一些文章的组织结构,容易让读者更快速地获取你的核心观点

jasl 将本帖设为了精华贴。 11月30日 17:01

@leiz_me 嗯,明白了。我去学习一下:)

写的不错,现在很多都是“能用主义”,只要“it works”就万事大吉了

@uestc_bird 其实我觉得你可能是误解我的意思了。我觉得这里其实并不是能用主义。我想表达的意思是。当我们要解决一个问题时,如果身边没有一个完完全全刚刚合适的解决方法时,如果身边有一些方法能解决这个问题,但是用起来又不是那么直接的时候,这种情况下,我们或许能考虑稍微将其改造一下使用。

比如说,我本身需要一个梯子去拿高处的东西,但是身边刚好没有梯子,但是有几个凳子,我就可以去拿这个凳子来帮助我去解决增加我高度的问题。

这个思维的核心其实在于一个权衡,在完全从头搞出一个解决这个问题所需要的消耗与改造这些并不完全合适的方法所需要消耗之间做出权衡。当然还要考虑改造后的这个方法本身是否会出现各种问题。

还是刚刚梯子的例子,我如果要重新做一个梯子的话,我要去买木材,做测量,做切割等等,如果我不太会木工,可能还得学学怎么加工木头。而用凳子去做一个梯子的话,可能很快就能搭建起一个高台,但是单纯用凳子搭建出来的高台可能并不稳定,特别如果是搭建的高度还比较高的话,我或许要需要做一些其他工作来保证高台的安全性。所以到底怎么选,我就要考虑本身的需求(增加我能够到的高度),目前可用方法的特性(凳子的特性),另辟蹊径的代价(重新造梯子)等等。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号