messageRouteMiddleware.js
export default (routes) => { return async (context, next) => { if (routes[context.req.action]) { await routes[context.req.action](context,next); } else { console.log(context.req) next(); } } }定义路由
router.js
export default { userEnter:function(context,next){ console.log(context.res.user.name+' 进入聊天室'); next(); }, createRoom:function(context,next){ console.log('创建房间 '+context.res.room.anme); next(); } }使用:
new EasySocket() .openUse((context, next) => { console.log("open"); next(); }) .closeUse((context, next) => { console.log("close"); next(); }) .errorUse((context, next) => { console.log("error", context.event); next(); }) .messageUse(messageRouteMiddleware(router))//使用消息路由中间件,并传入定义好的路由 .connect('ws://localhost:8080')一切都变得美好了,感觉就像在使用koa。想一个问题,当接收到后端推送的消息时,我们需要做相应的DOM操作。比如路由里面定义的userEnter,我们可能需要在对应的函数里操作用户列表的DOM,追加新用户。这使用原生JS或JQ都是没有问题的,但是如果使用vue,react这些,因为是组件化的,用户列表可能就是一个组件,怎么访问到这个组件实例呢?(当然也可以访问vuex,redux的store,但是并不是所有组件的数据都是用store管理的)。
我们需要一个运行时注册中间件的功能,然后在组件的相应的生命周期钩子里注册中间件并且传入组件实例
运行时注册中间件,修改如下代码:
messageUse(fn, runtime) { this.messageMiddleware.push(fn); if (runtime) { this.messageFn = compose(this.messageMiddleware); } return this; }修改 messageRouteMiddleware.js
export default (routes,component) => { return async (context, next) => { if (routes[context.req.action]) { context.component=component;//将组件实例挂到context下 await routes[context.req.action](context,next); } else { console.log(context.req) next(); } } }类似vue mounted中使用
mounted(){ let client = this.$wsClients.get("im");//获取指定EasySocket实例 client.messageUse(messageRouteMiddleware(router,this),true)//运行时注册中间件,并传入定义好的路由以及当前组件中的this }路由中通过 context.component 即可访问到当前组件。
完美了吗?每次组件mounted 都注册一次中间件,问题很大。所以需要一个判断中间件是否已经注册的功能。也就是一个支持具名注册中间件的功能。这里就暂时不实现了,走另外一条路,也就是之前说到的远程事件的发布与订阅,我们也可以称之为跨进程事件。
跨进程事件看一段socket.io的代码:
Server (app.js)
var app = require('http').createServer(handler) var io = require('socket.io')(app); var fs = require('fs'); app.listen(80); function handler (req, res) { fs.readFile(__dirname + '/index.html', function (err, data) { if (err) { res.writeHead(500); return res.end('Error loading index.html'); } res.writeHead(200); res.end(data); }); } io.on('connection', function (socket) { socket.emit('news', { hello: 'world' }); socket.on('my other event', function (data) { console.log(data); }); });Client (index.html)
<script src="http://www.likecs.com/socket.io/socket.io.js"></script> <script> var socket = io('http://localhost'); socket.on('news', function (data) { console.log(data); socket.emit('my other event', { my: 'data' }); }); </script>注意力转到这两部分:
服务端
socket.emit('news', { hello: 'world' }); socket.on('my other event', function (data) { console.log(data); });客户端
var socket = io('http://localhost'); socket.on('news', function (data) { console.log(data); socket.emit('my other event', { my: 'data' }); });使用事件,客户端通过on订阅'news'事件,并且当触发‘new’事件的时候通过emit发布'my other event'事件。服务端在用户连接的时候发布'news'事件,并且订阅'my other event'事件。
一般我们使用事件的时候,都是在同一个页面中on和emit。而socket.io的神奇之处就是同一事件的on和emit是分别在客户端和服务端,这就是跨进程的事件。
那么,在某一端emit某个事件的时候,另一端如果on监听了此事件,是如何知道这个事件emit(发布)了呢?
没有看socket.io源码之前,我设想应该是emit方法里做了某些事情。就像java或c#,实现rpc的时候,可以依据接口定义动态生成实现(也称为代理),动态实现的(代理)方法中,就会将当前方法名称以及参数通过相应协议进行序列化,然后通过http或者tcp等网络协议传输到RPC服务端,服务端进行反序列化,通过反射等技术调用本地实现,并返回执行结果给客户端。客户端拿到结果后,整个调用完成,就像调用本地方法一样实现了远程方法的调用。