英文:
Letting socket.io client version lag behind server version
问题
情况
我们正在使用socket.io进行移动端和服务器之间的通信。由于我们无法强制升级用户的设备,如果我们想要升级到不向后兼容的版本1,我们必须在服务器上一段时间内处理两个版本。
问题
有哪些选择?
我目前最喜欢的选择是在多路复用器中同时包装旧版本和新版本。它根据请求的头部和查询参数检测传入请求的版本,并知道要调用哪些函数。
另一个(较差的)选择是将新版本包装在一个模块中,该模块可以在必要时将旧版本的协议转换为新版本(反之亦然)。这种方法存在一个严重的缺点。要确保我已经正确确定和处理了所有微小差异,这将是耗时且不确定的工作。有些差异可能需要大量调整。
(如果你好奇或者有帮助的话,我们是用Go语言实现的。)
英文:
Situation
We're using socket.io for mobile-server communications. Since we can't force-upgrade users' devices, if we want to upgrade to version 1 (non-back-compatible), we have to handle both versions on the server for a while.
Question
What are the options?
My current favourite is to wrap both the old version and the new version in a multiplexer. It detects the version of the incoming request based on headers and query parameters and thereby knows which functions to invoke.
Another (shittier) option is to wrap the new version in a module that can translate the old version of the protocol into the new version (and back again) when necessary. This suffers from a serious drawback. It would be time-consuming and uncertain work to ensure I've properly determined and handled all the tiny differences. Some differences might take some serious massaging.
(In case you're curious or it's helpful to know, we're doing this in Go.)
答案1
得分: 2
看起来你可以在服务器上运行两个独立的socket.io版本。由于这两个版本没有唯一的模块文件名,你可能需要从不同的路径加载一个版本。当加载模块并初始化它们时,你需要将它们分配给不同命名的变量。例如:
var io_old = require('old/socket.io');
var io = require('socket.io');
在服务器上加载了这两个版本之后,我认为有两种不同的方法可以运行它们。
- 为每个版本使用不同的端口。 较旧的版本将使用默认端口80(无需对其进行配置更改),该端口与node.js web服务器共享。较新的版本将在不同的端口上运行(例如端口3000)。然后,你将为每个版本的socket.io初始化其自己的端口。你的较新版本客户端将连接到较新版本运行的端口。
对于在端口80上运行的旧socket.io服务器,你将使用你已经有的初始化方法,该方法可能与你现有的http服务器连接。
对于在其他端口上运行的新socket.io服务器,你将像这样单独初始化它:
var io_old = require('old/socket.io')(server);
var io = require('socket.io')(3000);
然后,在新版本的客户端中,你将在连接时指定端口3000。
var socket = io("http://yourdomain.com:3000");
- 为每个版本使用不同的HTTP请求路径。 默认情况下,每个socket.io连接都以类似这样的HTTP请求开始:
http://yourdomain.com/socket.io?EIO=xx&transport=xxx?t=xxx
。但是,该请求的/socket.io
部分是可配置的,两个独立的socket.io版本可以使用不同的路径名。在服务器端,启动socket.io监听的.listen()
方法接受一个可选的选项对象,可以配置自定义路径,例如path: "/socket.io-v2"
。同样,客户端中的.connect()
方法也接受该选项对象。这个选项有点难以找到文档,因为它实际上是一个engine.io选项(socket.io使用的),但是socket.io会将这些选项传递给engine.io。
我自己没有尝试过这两种方法,但我研究了客户端和服务器端如何初始化socket.io连接,看起来底层引擎支持这个功能,我看不出为什么它不会起作用。
以下是在服务器上更改路径的方法:
var io = require('socket.io')(server, {path: "/socket.io.v1"});
然后,在新版本的客户端代码中,你将这样连接:
var socket = io({path: "/socket.io.v1"});
这将导致初始连接请求发送到类似以下的HTTP URL:
http://yourdomain.com/socket.io.v1?EIO=xx&transport=xxx?t=xxx
这将由你的HTTP服务器上的不同请求处理程序处理,从而分离这两个版本。
另外,可能EIO=3
查询参数在socket.io连接URL中实际上是一个engine.io版本号,可以用来区分客户端版本并根据该值采取相应的操作。我没有找到关于它如何工作的任何文档,甚至找不到在engine.io或socket.io源代码中查看该查询参数的位置,所以这需要更多的调查作为另一种可能性。
英文:
It appears that you could run two separate versions of socket.io on the server. Since the two versions don't have unique module filenames you would probably need to load one version from a different path. And, then obviously when loading the modules and initializing them you'd assign them to differently named variables. For example:
var io_old = require('old/socket.io');
var io = require('socket.io);
Once you have the two versions loaded on the server, I think there are two different approaches for how they could be run.
- Use a different port for each version. The older version would use the default port 80 (no configuration change required for that) which is shared with the node.js web server. The newer version would be run on a different port (say port 3000). You would then initialize each version of socket.io to its own port. Your newer version clients would then connect to the the port the newer version was running on.
For the old socket.io server running on port 80, you would use whatever initialization you already have which probably hooks into your existing http server.
For the new socket.io server running on some other port, you would initialize it separately like this:
var io_old = require('old/socket.io')(server);
var io = require('socket.io')(3000);
Then, in the new version client, you would specify port 3000 when connecting.
var socket = io("http://yourdomain.com:3000");
- Use a different HTTP request path for each version. By default, each socket.io connection starts with an HTTP request that looks like this:
http://yourdomain.com/socket.io?EIO=xx&transport=xxx?t=xxx
. But, the/socket.io
portion of that request is configurable and two separate versions of socket.io could each be using a different path name. On the server, the.listen()
method that starts socket.io listening takes an optional options object which can be configured with a custom path as inpath: "/socket.io-v2"
. And similarly, the.connect()
method in the client also accepts that options object. It's kind of hard to find the documentation for this option because it's actually an engine.io option (which socket.io uses), but socket.io passes the options through to engine.io.
I have not tried either of these myself, but I've studied how socket.io connections are initiated from client and server and it looks like the underlying engine supports this capability and I can see no reason why it should not work.
Here's how you'd change the path on the server:
var io = require('socket.io')(server, {path: "/socket.io.v1"});
Then, in the client code for the new version, you'd connect like this:
var socket = io({path: "/socket.io.v1"});
This would then result in the initial connection request being made to an HTTP URL like this:
http://yourdomain.com/socket.io.v1?EIO=xx&transport=xxx?t=xxx
Which would be handled by a different request handler on your HTTP server, thus separating the two version.
FYI, it is also possible that the EIO=3
query parameter in the socket.io connection URL is actually an engine.io version number and that can also be used to discern client version and "do the right thing" based on that value. I have not found any documentation on how that works and could not even find where that query parameter was looked at in the engine.io or socket.io source code so that would take more investigation as a another possibility.
答案2
得分: 0
我对这个问题没有立即的解决方案,但我有一些建议。我想你可以用它来节省很多时间。
- 首先,我在一家初创公司工作,几乎所有的东西都使用socketIo。
- 我们知道这个问题会发生,所以我们最初的设计是使一切可插拔,这意味着我们可以将socketio替换为sockjs,它仍然可以工作。
- 它的实现方式是通过定义在系统中很少改变的一组常见API。我们称之为“管理器”。管理器可以仅暴露开发人员需要使用的API,而不会弄乱任何东西。这样可以加快速度。
- 管理器的实现在后台进行更改,但API保持不变,因此核心开发人员可以自信地进行更改。
- 看起来你的代码中存在紧密的依赖关系。或者也可能没有。我不太确定。如果你还没有尝试过,请尝试遵循这个原则。
英文:
I don't really have an immediate solution for this, but I have some kind of advice. I guess you could use it to save a lot of time.
- first of all Im working in a startup which uses socketIo for almost
everything - We knew that this problem would happen so our initial design was to
make everything pluggable which means that we can swap out socketio for
sockjs and it will still work. - The way its done is by defining the common set of APIs which rarely change
in a system. We call it managers. The managers can just expose the API which the rest of the devs need to use without messing up anything. It speeds up a lot. - The manager implementation changes in the background but still the APIs are the same, so the engineers working on the core can confidently make changes.
- Seems like you have a tight dependency in your code. Or may be not. I'm not so sure. Try following this principle if you haven't.
答案3
得分: 0
我们将采取保留0.9.x版本和当前版本作为服务器上的独立库的方式。最终,当客户端池中的大部分客户端都更新完毕时,我们将关闭0.9.x版本。
我们将通过将socket.io服务包装在一个包中来管理这两个版本,该包将确定要传递请求的包装socket.io版本。这个确定将取决于请求的特性,比如自定义头部(可以添加到新版本的客户端)以及仅由一个版本或另一个版本独占使用的查询参数和其他头部。
由于我们使用的是Go语言,目前还没有普遍认可的方法来管理依赖项,更不用说一种可以尊重版本差异的方法了。假设回溯兼容分支的存储库没有损坏(实际上是损坏的),我们将有两个选择。第一种选择是分叉存储库并将回溯兼容版本作为主版本。然后,我们将导入它,就好像它与另一个版本无关。第二种选择是使用gopkg.in来假装这些分支是独立的存储库。
无论哪种情况,我们都可以这样导入这两个分支/存储库:
import (
socketioV0 "github.com/path/to/older/version"
socketioV1 "github.com/path/to/current/version"
)
然后在代码中使用它们的导入名称socketioV0
和socketioV1
。
英文:
We're going to go the route of keeping both the 0.9.x version and the current version as separate libraries on the server. Eventually, when the pool of clients has more-or-less all updated, we'll just pull the plug on the 0.9.x version.
The way we'll manage the two versions is by wrapping the socket.io services in a package that will determine which wrapped socket.io version to pass the request off to. This determination will depend on features of the request, such as custom headers (that can be added to the newer clients) as well as query parameters and other headers utilized exclusively by one version or the other.
Since we're using Go, there's so far no universally agreed upon way to manage dependencies, let alone a way that can respect version differences. Assuming the back-compat branch of the repo wasn't broken (which it is), we'd have two options. The first would be to fork the repo and make the back-compat version the master. We'd then import it as if it had nothing to do with the other one. The second option would be to use gopkg.in to pretend the separate branches were separate repos.
In either event, we could import the two branches/repos like so
import (
socketioV0 "github.com/path/to/older/version"
socketioV1 "github.com/path/to/current/version"
)
And then refer to them in the code using their import names socketioV0
and socketioV1
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论