概述
最近在接手一个 Web 推送的网关,主要功能其实就是通过 Socket.IO 协议接收前端的连接,然后推送消息给对应的前端,但是,在调试的时候,遇到了一个不能成功连接的问题,这里做一个总结。
本文使用的 golang lib 为:https://github.com/googollee/go-socket.io
node lib 为:https://github.com/socketio/socket.io
问题重现
具体的问题就是,当我用 Postman (是的,没错,Postman 开始支持 Websocket/Socket.IO 的功能了)连接我们的 Gateway 时,发现处于一直 Connecting 的状态,如图:
图 1:Socket.IO 处于 Connecting 状态 |
---|
然后同事发现,如果我去掉 URL 中的 query param 就可以成功连接了:
图 2:去除 query param 后成功连接 |
---|
问题定位
发现去除 query param 可以正常连接之后,我就感觉不应该是我的问题,但是,出于严谨,还是得一步一步地来,首先需要排除是我的业务代码有问题,于是我就用对应版本的 Golang Socket.IO 库写了一个最简单的 Hello World 程序,然后发现能复现问题,ok 这个就排除了我的业务代码的问题。
然后我看了一下,目前项目使用的版本还比较旧(1.0.1),然后就升级到最新版本(1.7.0),发现接口都变了(这个值得吐槽一下啊,同个大版本你居然变接口?),但是没关系,就修改一下 Hello World 的代码,然后再用 Postman 试一遍,发现不能复现,也就是说最新的版本是正常的。
处于对同个大版本修改接口的不信任,我尝试用官方原生的代码来尝试一下,看下标准的协议是如何实现的(Socket.IO 官方没有明确的相关文档说明),于是我就用了 Node Socket.IO 的 2.5.0 版本,发现同样的也是无法复现,也就是正常连接,那么最终可以认定是项目中使用的这个版本有 bug。
问题解析
既然知道是使用的版本代码有问题,那么就直接跟踪一下代码,发现问题出在 Connect 的数据包处理阶段,Server 端看上去是正常处理了,但是,返回给 Postman 之后,Postman 却不认为正常,从而忽略了这个 Connect 的响应包,从而导致连接的状态一直处于 Connecting 中。
从代码中,可以看到问题代码在这里:
[root@liqiang.io]# cat parser.go
if next[0] == '/' {
path, err := reader.ReadBytes(',')
if err != nil && err != io.EOF {
return err
}
pathLen := len(path)
if pathLen == 0 {
return fmt.Errorf("invalid packet")
}
if err == nil {
path = path[:pathLen-1]
}
v.NSP = string(path)
if err == io.EOF {
return nil
}
}
Golang 的这个实现在解析 Connect 数据包的时候,Namespace 直接就使用请求的 Path,注意,这里的 Path 是原始未处理的,所以它是带 query param 的,也就是说它将 query param 也作为 namespace 的一部分;但是对于标准的 Node 实现,它的 Namespace 却是解析后的 URL Path,并且去除了 query param 的,源码为这里:
[root@liqiang.io]# cat lib/client.js
Client.prototype.ondecoded = function(packet) {
if (parser.CONNECT == packet.type) {
this.connect(url.parse(packet.nsp).pathname, url.parse(packet.nsp, true).query);
} else {
var socket = this.nsps[packet.nsp];
if (socket) {
process.nextTick(function() {
socket.onpacket(packet);
});
} else {
debug('no socket for namespace %s', packet.nsp);
}
}
};
可以发现这里解析 Namespace 的方式为:url.parse(packet.nsp).pathname
,不包含 Query Param。所以 Golang 和 Node 的差异就在这里了。
一些疑问
首先我的第一个疑问就是我们的前端是可以正常连接带 Query Param 的,如果后端有这个问题的话,那么前端是不是也是可以处理这个问题?还是得从代码中寻找原因:源码,然后事实就是 Client 段的代码也是有特殊处理这段逻辑的。
那么 Postman 为什么不能正确处理?这个因为 Postman 的源码没有开源,从文档中也没有看到它使用的是开源的 Library 还是自己实现的,但是从问题上来看,应该也是不能正确地处理 Namespace 的问题。所以我创建了一个 ticket 来跟踪后续。
关键词
- 关键词:Socket.IO; Golang; Namespace; Query Param; Postman; Connecting; 无法连接