SockJS - Web Messaging 并非易事
“实时 Web”或使用 Web 浏览器进行消息传递的想法已经存在一段时间了。最初被称为“长轮询”,然后是“Comet”,最新的形式叫做“WebSockets”。毫无疑问,它正朝着正确的方向发展,WebSockets 是一项很棒的技术。
但在争取实时功能的过程中,我们忽略了真正重要的事情——如何实际使用消息传递。在 Web 上下文中,一切都是请求-响应驱动的,将典型的 Web 堆栈与异步消息传递相结合并不容易。
现状
多年来,人们曾尝试创建一种通用的组件,能够接入 Web 技术栈,并像数据库处理数据一样处理“消息”。
但存在一个问题:消息的异步性以及浏览器跨域限制意味着,要想让“Comet”工作正常,你需要使用特定的 Web 技术栈。这是一种典型的供应商锁定。
新方法
至少过去是这样。你可以有一个 Web 消息框架,但你基本上就被它束缚住了。
直到不久前,Socket.io 项目出现了。它构建在 Node.js 之上,这赋予了它独特的地位,不仅因为它在 Node.js 中的一切都已经是完全异步的(几乎不可能搞砸),而且它还使用了 Web 环境中已有的通用语言。
早期 Socket.io 的开发专注于消息传递,换句话说,是如何将消息发送到 Web 浏览器以及从 Web 浏览器接收消息。与 Web 开发者的约定很简单:这里有一个异步传输层,你在此之上用 JavaScript 构建你的应用程序逻辑。
开发者需要编写一个薄薄的 JavaScript 层,将 Socket.io 连接与应用程序连接起来,具体的实现细节留给开发者。应用程序可以是 Node.js,也可以使用任何其他技术栈。它可以通过任何方式连接,例如 Redis、RabbitMQ 或 HTTP 回调。
但最近 Socket.io 改变了焦点,它不再仅仅是一个简单的传输层,而是一个完整消息栈,具有:
- 消息确认
- 广播 / “房间”
- 多路复用
- 不稳定消息
没有银弹
但这里有个问题:如果你构建消息语义,消息库就开始变成一个框架,并且很快就会被绑定到一个平台。
想想看,即使你试图构建一个简单的“广播”抽象,你也不可避免地需要回答许多不明显的问题:
- 应用程序将如何部署?(仅限于一台服务器,还是使用底层的消息总线进行扩展?使用什么消息总线?它如何与 HTTP 负载均衡器配合?)
- 谁有权“订阅”该广播数据?是公开的吗?如何设置权限?
- 谁可以发布消息?订阅者如何才能可靠地识别消息的作者?
请记住,“广播”是一个非常简单的抽象,实际上每个人使用消息传递的方式都不同,创建一个通用的消息传递框架非常困难。消息传递框架做出的许多决定实际上都是应用程序特定的。
- 授权(谁能听到什么,以及在哪里发布)
- 数据理解(如何进行值 + 更新?差分算法是什么?)
- 在线状态(所有应用程序都需要对它有略微不同的理解)
我很高兴 Socket.io 的开发进展顺利,并为它祈祷。但我的看法是,它关注的是错误的问题:我不需要另一个有固定想法、并且被绑定到特定平台的消息传递框架。
相反,我只需要一个稳定的传输层。
下一步
Socket.io 指明了方向——有一个简单的、稳定的库可以解决消息传递问题,直到原生实现广泛部署,它就可以实现类 WebSocket 的 API。所有这一切,请不要定义消息模型。
这是我的回应:SockJS——一个具有类 WebSocket API 的库,它只专注于传输层。尽管项目尚处年轻,但我认为它已经比其他同类库要好。
该项目分为两部分:
- 浏览器 JavaScript 库:SockJS-client
- Node 的服务器端组件:SockJS-node
如果您想看到它的运行效果,这里有一些实时部署的 QUnit 测试:
- http://sockjs.popcnt.org/(欧洲托管)
- http://sockjs.cloudfoundry.com/(CloudFoundry,已禁用 WebSockets)
- https://sockjs.cloudfoundry.com/(CloudFoundry SSL,已禁用 WebSockets)
- http://sockjs.herokuapp.com/(Heroku,已禁用 WebSockets)
SockJS 背后的主要假设是:
- API 应尽可能接近 WebSocket API。
- 服务器端部分应尽可能简单,所有复杂性都应由浏览器库处理。(假设只有一个浏览器库,并且会有许多服务器端实现。我们有一个 SockJS Node 服务器,并且我们希望至少做一个 Erlang 的。)
- 内部不使用 Flash,仅使用 JavaScript。
- 回退到缓慢、低效的轮询传输,当客户端位于公司防火墙和代理后面时很有用。
- 所有传输都必须是跨域的——开发人员必须能够将 SockJS 服务器作为其基础设施的独立部分进行托管。
- 支持常见的负载均衡策略:使用基于 JSESSIONID Cookie 的粘性会话或基于前缀的负载均衡。
未来
解决消息传递问题只是第一步。最终目标是为 Web 应用程序创建一个通用的消息传递框架,但这并不容易。这需要大量的工作和许多失败的尝试。这就是为什么拥有一个稳定、设计良好、可重用的传输层至关重要。
(文章也可用在 github pages)