上一篇文章介绍了在浏览器端以中间件,路由,跨进程事件的姿势使用原生WebSocket。这篇文章将介绍如何使用Node.js以相同的编程模式来实现WebSocket服务端。
Node.js中比较流行的两个WebSocket库分别是socket.io与ws。其中socket.io已经实现了跨进程事件,广播,群发等功能,并且服务端与浏览器端是配套的,在不支持WebSocket技术的浏览器会降级为使用ajax轮询。所以。这里选择使用相对而言较为底层或原始的ws,在其基础上实现文章标题所提到的编程模式。
WS使用ws简简单单就可以启动一个WebSocket服务:
const WebSocket = require('ws'); const wss = new WebSocket.Server({ port: 8080 }); wss.on('connection', function connection(ws) { ws.on('message', function incoming(message) { console.log('received: %s', message); }); ws.send('something'); });上面的wss支持的事件有connection,close,error,headers,listening,ws支持的事件有message,close,error。更多详情可以这里。
中间件对ws进行封装,上面提到的事件:WSS的connection,ws的message,close,error。分别提供注册中间件的接口
class EasySocket { constructor() { this.connectionMiddleware = []; this.closeMiddleware = []; this.messageMiddleware = []; this.errorMiddleware = []; this.connectionFn = Promise.resolve(); this.closeFn = Promise.resolve(); this.messageFn = Promise.resolve(); this.errorFn = Promise.resolve(); } connectionUse(fn, runtime) { this.connectionMiddleware.push(fn); if (runtime) { this.connectionFn = compose(this.connectionMiddleware); } return this; } closeUse(fn, runtime) { this.closeMiddleware.push(fn); if (runtime) { this.closeFn = compose(this.closeMiddleware); } return this; } messageUse(fn, runtime) { this.messageMiddleware.push(fn); if (runtime) { this.messageFn = compose(this.messageMiddleware); } return this; } errorUse(fn, runtime) { this.errorMiddleware.push(fn); if (runtime) { this.errorFn = compose(this.errorMiddleware); } return this; } }通过xxxUse注册相应的中间件。 xxxMiddleware中就是相应的中间件。xxxFn是中间件通过compose处理后的结构。使用runtime参数可以在运行时注册中间件。
再添加一个listen方法,处理相应的中间件并且实例化WebSocket.Server
listen(config) { this.socket = new WebSocket.Server(config); this.connectionFn = compose(this.connectionMiddleware); this.messageFn = compose(this.messageMiddleware); this.closeFn = compose(this.closeMiddleware); this.errorFn = compose(this.errorMiddleware); this.socket.on('connection', (client, req) => { let context = { server: this, client, req }; this.connectionFn(context).catch(error => { console.log(error) }); client.on('message', (message) => { let req; try { req = JSON.parse(message); } catch (error) { req = message; } let messageContext = { server: this, client, req } this.messageFn(messageContext).catch(error => { console.log(error) }) }); client.on('close', (code, message) => { let closeContext = { server: this, client, code, message }; this.closeFn(closeContext).catch(error => { console.log(error) }) }); client.on('error', (error) => { let errorContext = { server: this, client, error }; this.errorFn(errorContext).catch(error => { console.log(error) }) }); }) }使用koa-compose模块处理中间件。注意xxContext传入了哪些东西,后续定义中间件的时候都可以使用。
compose的作用可看这篇文章 傻瓜式解读koa中间件处理模块koa-compose
使用:
import EasySocket from 'easy-socket-node'; const config = { port: 3001, perMessageDeflate: { zlibDeflateOptions: { // See zlib defaults. chunkSize: 1024, memLevel: 7, level: 3, }, zlibInflateOptions: { chunkSize: 10 * 1024 }, // Other options settable: clientNoContextTakeover: true, // Defaults to negotiated value. serverNoContextTakeover: true, // Defaults to negotiated value. //clientMaxWindowBits: 10, // Defaults to negotiated value. serverMaxWindowBits: 10, // Defaults to negotiated value. // Below options specified as default values. concurrencyLimit: 10, // Limits zlib concurrency for perf. threshold: 1024, // Size (in bytes) below which messages // should not be compressed. } } const easySocket = new EasySocket(); //使用中间件获取token easySocket .connectionUse((context,next)=>{ console.log("new Connected"); let location = url.parse(context.req.url, true); let token=location.query.token; if(!token){ client.send("invalid token"); client.close(1003, "invalid token"); return; } context.client.token=token; next(); }); easySocket .listen(config) console.log('Now start WebSocket server on port ' + config.port + '...')使用messageUse可以注册多个处理消息的中间件,比如
easySocket.messageUse((context, next) => { //群聊处理中间件 if (context.req.action === 'roomChatMessage') { //可以在这里持久化消息,将消息发送给其它群聊客户端 console.log(context.req); } next(); }) .messageUse((context, next) => { //私聊处理中间件 if (context.req.action === 'privateChatMessage') { //可以在这里持久化消息,将消息发送给私聊客户端 console.log(context.req); } next(); })