eggjs / egg

🥚 Born to build better enterprise frameworks and apps with Node.js & Koa
https://eggjs.org
MIT License
18.88k stars 1.81k forks source link

[RFC] egg-socket.io #269

Closed atian25 closed 7 years ago

atian25 commented 7 years ago

Directory Structure

app
├── io
│   ├── controller
│   │   └── chat.js
│   └── middleware
│       ├── auth.js
│       ├── filter.js
├── router.js
config
 ├── config.default.js
 └── plugin.js

Configuration

// {app_root}/config/config.default.js
exports.io = {
  namespace: {
    '/': {
      connectionMiddleware: [],
      packetMiddleware: [],
    },
  },
  redis: {
    host: '127.0.0.1',
    port: 6379
  }
};

Middleware

middleware are functions which every connection or packet will be processed by.

Connection Middleware

config/config.default.js

exports.io = {
  namespace: {
    '/': {
      connectionMiddleware: ['auth'],
    },
  },
};

pay attention to the namespace, the config will only work for a specific namespace.

Packet Middleware

config/config.default.js

exports.io = {
  namespace: {
    '/': {
      packetMiddleware: ['filter'],
    },
  },
};

pay attention to the namespace, the config will only work for a specific namespace.

Controller

controller is designed to handle the emit event from the client.

example:

app/io/controller/chat.js

module.exports = app => {
  return function* () {
    const message = this.args[0];
    console.log(message);
    this.socket.emit('res', `Hi! I've got your message: ${message}`);
  };
};

next, config the router at app/router.js

module.exports = app => {
  // or app.io.of('/')
  app.io.route('chat', app.io.controllers.chat);
};
ngot commented 7 years ago

基于cluster-client,实现client没问题,但是server还是不可以的。 一旦启动 cluster 模式,websocket就会握手失败。我感觉,要和egg有机结合起来,并利用多核能力,需要设计一番。

atian25 commented 7 years ago

嗯, websocket / socket.io 算是比较重要的一个 showcase 的插件.

cc @fengmk2 @popomore @dead-horse

popomore commented 7 years ago

主要是集群的不好做

denghongcai commented 7 years ago

egg-cluster 有支持 sticky session 的开关,并且插件可以有办法在框架启动之前改变 cluster 的配置,有办法吗

fengmk2 commented 7 years ago

依赖 socket.io ,基于标准的 redis 来做 cluster server。跟 cluster-client 没有半毛关系的。

denghongcai commented 7 years ago

@fengmk2 现在的 cluster 逻辑会导致 websocket 握手失败

gxcsoccer commented 7 years ago

没明白为啥要用 cluster-client 实现?

atian25 commented 7 years ago

当我没说 cluster-client ~

反正就是需要一个完善的 websocket 插件~

fengmk2 commented 7 years ago

@denghongcai 所以要基于 redis 来维持 websocket 的 session,走 socket.io 标准的模式。

fengmk2 commented 7 years ago

目前最快的是 https://github.com/uWebSockets/uWebSockets

但是最通用和最易用的是 socket.io https://www.npmjs.com/package/socket.io

fengmk2 commented 7 years ago

https://www.npmjs.com/package/socket.io-redis

By running socket.io with the socket.io-redis adapter you can run multiple socket.io instances in different processes or servers that can all broadcast and emit events to and from each other.

arden commented 7 years ago

@fengmk2 socket.io可以直接使用uWebsockets

arden commented 7 years ago

websocket和socket.io都是非常重要的一个模块,使用的人太多了,所以egg得在这个上面封装一套支持集群模式的方案。

atian25 commented 7 years ago

将会在 https://github.com/eggjs/egg-websocket 做.

@fouber

arden commented 7 years ago

@atian25 会有 socket.io 吗?

atian25 commented 7 years ago

@arden 这个库有可能内部实现用 socket.io 吧, 看具体写的时候.

@gxcsoccer @ngot 你们谁上?

ngot commented 7 years ago

我来搞

fengmk2 commented 7 years ago

@ngot 👍 ,可以在正文 issue 写一下方案

arden commented 7 years ago

@fengmk2 @ngot @atian25 目前我们也使用了socket.io,使用uWebsockets做为engine。本来想用pm2来做集群方案,可是在pm2的cluster运行模式下,socket.io有总有问题,只能使用pm2的fork运行模式,直接在代码里通过socket.io-redis实现socket.io的cluster模式。但光socket.io-redis还不够,session这块还需要处理。所以整个项目依赖了以下几个模块: 集群和session处理模块 https://github.com/uqee/sticky-cluster https://github.com/socketio/socket.io-redis 高效率websocket引擎 https://github.com/uWebSockets/uWebSockets https://github.com/Unitech/pm2

另外据说socket.io 2.0默认用uWebsockets做为ws引擎。

ngot commented 7 years ago

https://github.com/eggjs/egg-cluster/pull/14

ngot commented 7 years ago

https://github.com/eggjs/egg-bin/pull/32

ngot commented 7 years ago

我觉得如果基于socket.io做的话,就叫 egg-socket.io 吧,egg-websocket 要做的话,就只对uWebSockets封装

popomore commented 7 years ago

要不要把某领域的解决方案放这里 https://github.com/eggjs/egg/issues/287

fengmk2 commented 7 years ago

@ngot 改名了

ngot commented 7 years ago

https://github.com/eggjs/egg-socket.io 先占坑

atian25 commented 7 years ago

npm 也占个 0.0.1吧

发自我的 iPhone

在 2017年2月10日,19:29,Hengfei Zhuang notifications@github.com 写道:

https://github.com/eggjs/egg-socket.io 先占坑

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or mute the thread.

calidion commented 7 years ago

很好啊。 可以在所有的npm包里都打上egg-前缀,多么美妙的事情啊。哈哈。

strongant commented 7 years ago

同意楼上的意见!@calidion

luckydrq commented 7 years ago

@ngot egg-socket.io 什么时候可用?

jinMaoHuiHui commented 7 years ago

好多插件正在进行中,能否有个页面可以让满怀期待的吃瓜群众看到计划或详细进展

atian25 commented 7 years ago

@jinMaoHuiHui 主要还是关注各个插件对应的 issue 即可, 这些插件并不属于 egg 的核心代码, 所以不会纳入到 egg 1.0 的规划里面.

另外, 我们更期望社区这边能参与进来一起共建.

ngot commented 7 years ago

讨论一下集成到egg的设计思路:

socket.io server主要功能

思考

对于这种长连接应用,比基于 请求 - 响应http 服务,要更复杂,类型更多。

socket.io 库本身,类似于koa,只提供了最基本的API,如使用中间件使用 use ,并没有类似 egg 这样的约定开发规范。

我思考,既然 socket.io 集成 egg 就应该按照约定的思路,设计一套开发规范出来。

规范草案

app
├── bgtask # 后台任务
│   └── adPush.js # 比如广告推送
├── cmidware # tcp连接级别中间件
│   └── auth.js # 授权验证
├── controller # 消息处理
│   └── chat.js # 聊天消息处理
├── pmidware # tcp包级别中间件
│   └── filter.js # 一些过滤
└── tcp-router.js # 一些配置,比如消息事件绑定

大家看看如何?

dead-horse commented 7 years ago

如果不混部,为什么要强行在 egg 里面支持 socket.io,我们设计的这一套 web 开发相关的规范本来就美为 socket.io 考虑,等于是要再造一个基于 socket.io 的上层框架,我们本身也没有类似的强需求,不应该做到 egg 里面。

  1. 如果应用真的足够复杂(或者规模上去了),会有单独的 websocket 服务,适合单独部署,也不需要用 egg。
  2. 当应用刚起步,在 web 网站的基础上有一些实时推送的需求,用 egg 开发网站服务,顺便用 egg-socket.io 解决实时推送问题。

我的看法是我们主要解决的第二类问题。

ngot commented 7 years ago

赞同,第二点需求。 那么,关于开发规范,问题呢?

dead-horse commented 7 years ago

在没有最佳实践的前提下,我们主要解决几个问题:

  1. 负载均衡
  2. 集群方案
  3. 和 http 服务共享 session
  4. service 层的代码共享

先做轻量级,后面再去规划,以前做过一个试验性质的 koa.io,不过因为没有使用场景无法深入使用而停止开发了。

atian25 commented 7 years ago

赞同 @dead-horse , 如果第一点, egg-socket.io 就不是插件了,而是基于 egg-core 的一个框架封装了,跟 egg 是同一层的了

fengmk2 commented 7 years ago

tcp-router.js 还是可以服用 app/router.js 的,只是里面通过 app.io.xxx api 来实现路由。

这样确保路由都在同一个地方配置吧。

同样,cmidware和pmidware也可以放到 middleware 目录,然后增加 socket 子目录区分? bgtask 就是 schedule 吧?

需要跟 http 请求复用 service 等通用代码,就需要造一个基于 websocket 的 ctx 了

fengmk2 commented 7 years ago

websocket 是否也能抽象为 请求 - 响应 模式?

dead-horse commented 7 years ago

@fengmk2 我们先不去强行抽象为请求响应模式吧,复用 service 代码不需要完整的造 ctx,其实只是复用业务逻辑,本来我们也不推荐在 service 上直接使用 ctx 的方法。

dead-horse commented 7 years ago

目录的话如果到处都要拆两个的话,不如统一放到app/sio 下面?

atian25 commented 7 years ago

middleware 没必要拆分目录吧, 那个目录只是加载用的, 挂载是在 config.middleware 的

ngot commented 7 years ago

middleware因为ctx其实是不同的,放在一起,感觉会混

ngot commented 7 years ago

我倾向于这样的 app/sio 单独目录,比较好组织和解析,看起来也不容易混。

atian25 commented 7 years ago

那这里是否也需要考虑到和 websocket 插件的统一规范不?

dead-horse commented 7 years ago

先不用考虑这么多,实践中摸索吧,egg-socketio 也只是我们提供的一个插件和一个扩展的解决方案,先不侵入到 egg 的核心中。

arden commented 7 years ago

@ngot 首先,不支持 http 服务和 socket 服务混布。主要考虑到,目前的 sticky 负载不是适合于 http 服务,同时,两套约定规范融合于同一个项目也是非常的复杂和痛苦。从应用维护角度看来,也是长连接服务单独部署比较合理。 其实我还是比较认可这种方案,现在有很多http/socket混合在一起的方案,但实际上都很少使用,感觉实用价值并不大,并且容易复杂化。

dead-horse commented 7 years ago

@arden 上面说了,如果混部,那根本不需要考虑 egg 集成的事情了。

ngot commented 7 years ago

Directory Structure

├── app
│   ├── io 
│   │   ├── controller
│   │   │   └── chat.js
│   │   └── middleware
│   │       ├── auth.js
│   │       └── filter.js
│   └── router.js
├── config
│   ├── config.default.js
│   └── plugin.js
└──  package.json

router.js

module.exports = app => {
  app.get('/', 'home'); // http router

  app.io.on('connection', socket => {
    socket.on('chat', app.io.controller.chat);
  });
};

config.js

exports.io = {
    cmiddleware: [ 'auth' ], // connection middleware
    pmiddleware: [ 'filter' ], // packet middleware
};

app/io/middleware/auth.js

module.exports = app => {
    return (socket, next) => {
        console.log('auth middleware!');
        next();
    };
};

app/io/middleware/filter.js

module.exports = app => {
    return (packet, next) => {
        console.log('filter middleware!');
        next();
    };
};

app/io/controller/chat.js

module.exports = app => {
    return msg => {
        console.log('chat :', msg + process.pid);
        setTimeout(function () {
            socket.emit('res', 'Hello World !');
        }, 500);
    }
};

最终就这样吧

atian25 commented 7 years ago

midware typo ?

ngot commented 7 years ago

@atian25 fixed