通过参考koa中间件,socket.io远程事件调用,以一种新的姿势来使用WebSocket。
浏览器端浏览器端使用WebSocket很简单
// Create WebSocket connection. const socket = new WebSocket('ws://localhost:8080'); // Connection opened socket.addEventListener('open', function (event) { socket.send('Hello Server!'); }); // Listen for messages socket.addEventListener('message', function (event) { console.log('Message from server ', event.data); });MDN关于WebSocket的介绍
能注册的事件有onclose,onerror,onmessage,onopen。用的比较多的是onmessage,从服务器接受到数据后,会触发message事件。通过注册相应的事件处理函数,可以根据后端推送的数据做相应的操作。
如果只是写个demo,单单输出后端推送的信息,如下使用即可:
socket.addEventListener('message', function (event) { console.log('Message from server ', event.data); });实际使用过程中,我们需要判断后端推送的数据然后执行相应的操作。比如聊天室应用中,需要判断消息是广播的还是私聊的或者群聊的,以及是纯文字信息还是图片等多媒体信息。这时message处理函数里可能就是一堆的if else。那么有没有什么别的优雅的姿势呢?答案就是中间件与事件,跨进程的事件的发布与订阅。在说远程事件发布订阅之前,需要先从中间件开始,因为后面实现的远程事件发布订阅是基于中间件的。
中间件前面说了,在WebSocket实例上可以注册事件有onclose,onerror,onmessage,onopen。每一个事件的处理函数里可能需要做各种判断,特别是message事件。参考koa,可以将事件处理函数以中间件方式来进行使用,将不同的操作逻辑分发到不同的中间件中,比如聊天室应用中,聊天信息与系统信息(比如用户登录属于系统信息)是可以放到不同的中间件中处理的。
koa提供use接口来注册中间件。我们针对不同的事件提供相应的中间件注册接口,并且对原生的WebSocket做封装。
export default class EasySocket{ constructor(config) { this.url = config.url; this.openMiddleware = []; this.closeMiddleware = []; this.messageMiddleware = []; this.errorMiddleware = []; this.openFn = Promise.resolve(); this.closeFn = Promise.resolve(); this.messageFn = Promise.resolve(); this.errorFn = Promise.resolve(); } openUse(fn) { this.openMiddleware.push(fn); return this; } closeUse(fn) { this.closeMiddleware.push(fn); return this; } messageUse(fn) { this.messageMiddleware.push(fn); return this; } errorUse(fn) { this.errorMiddleware.push(fn); return this; } }通过xxxUse注册相应的中间件。 xxxMiddleware中就是相应的中间件。xxxFn 中间件通过compose处理后的结构
再添加一个connect方法,处理相应的中间件并且实例化原生WebSocket
connect(url) { this.url = url || this.url; if (!this.url) { throw new Error('url is required!'); } try { this.socket = new WebSocket(this.url, 'echo-protocol'); } catch (e) { throw e; } this.openFn = compose(this.openMiddleware); this.socket.addEventListener('open', (event) => { let context = { client: this, event }; this.openFn(context).catch(error => { console.log(error) }); }); this.closeFn = compose(this.closeMiddleware); this.socket.addEventListener('close', (event) => { let context = { client: this, event }; this.closeFn(context).then(() => { }).catch(error => { console.log(error) }); }); this.messageFn = compose(this.messageMiddleware); this.socket.addEventListener('message', (event) => { let res; try { res = JSON.parse(event.data); } catch (error) { res = event.data; } let context = { client: this, event, res }; this.messageFn(context).then(() => { }).catch(error => { console.log(error) }); }); this.errorFn = compose(this.errorMiddleware); this.socket.addEventListener('error', (event) => { let context = { client: this, event }; this.errorFn(context).then(() => { }).catch(error => { console.log(error) }); }); return this; }使用koa-compose模块处理中间件。注意context传入了哪些东西,后续定义中间件的时候都已使用。
compose的作用可看这篇文章 傻瓜式解读koa中间件处理模块koa-compose
然后就可以使用了:
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((context, next) => { //用户登录处理中间件 if (context.res.action === 'userEnter') { console.log(context.res.user.name+' 进入聊天室'); } next(); }) .messageUse((context, next) => { //创建房间处理中间件 if (context.res.action === 'createRoom') { console.log('创建房间 '+context.res.room.anme); } next(); }) .connect('ws://localhost:8080')可以看到,用户登录与创建房间的逻辑放到两个中间件中分开处理。不足之处就是每个中间件都要判断context.res.action,而这个context.res就是后端返回的数据。怎么消除这个频繁的if判断呢? 我们实现一个简单的消息处理路由。
路由定义消息路由中间件