diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 48f9944..0000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,24 +0,0 @@ - - -##### Checklist - - -- [ ] `npm test` passes -- [ ] tests and/or benchmarks are included -- [ ] documentation is changed or added -- [ ] commit message follows commit guidelines - -##### Affected core subsystem(s) - - - -##### Description of change - diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index c8630f5..a7a80b8 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -12,6 +12,6 @@ jobs: uses: node-modules/github-actions/.github/workflows/node-test.yml@master with: os: 'ubuntu-latest, macos-latest' - version: '14, 16, 18, 20, 22' + version: '14, 16, 18, 20, 22, 23' secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/README.md b/README.md index cd1bccc..47af651 100644 --- a/README.md +++ b/README.md @@ -71,16 +71,6 @@ EGG_AGENT_CLOSE_TIMEOUT: agent worker boot timeout value [MIT](LICENSE) - - ## Contributors -|[
popomore](https://github.com/popomore)
|[
fengmk2](https://github.com/fengmk2)
|[
atian25](https://github.com/atian25)
|[
dead-horse](https://github.com/dead-horse)
|[
killagu](https://github.com/killagu)
|[
semantic-release-bot](https://github.com/semantic-release-bot)
| -| :---: | :---: | :---: | :---: | :---: | :---: | -|[
ngot](https://github.com/ngot)
|[
hyj1991](https://github.com/hyj1991)
|[
whxaxes](https://github.com/whxaxes)
|[
iyuq](https://github.com/iyuq)
|[
nightink](https://github.com/nightink)
|[
mansonchor](https://github.com/mansonchor)
| -|[
ImHype](https://github.com/ImHype)
|[
gxcsoccer](https://github.com/gxcsoccer)
|[
waitingsong](https://github.com/waitingsong)
|[
sjfkai](https://github.com/sjfkai)
|[
ahungrynoob](https://github.com/ahungrynoob)
|[
qingdengyue](https://github.com/qingdengyue)
| -[
wenjiasen](https://github.com/wenjiasen)
|[
czy88840616](https://github.com/czy88840616)
|[
gxkl](https://github.com/gxkl)
- -This project follows the git-contributor [spec](https://github.com/xudafeng/git-contributor), auto updated at `Mon Jun 03 2024 10:59:15 GMT+0800`. - - +[![contributors](https://contrib.rocks/image?repo=eggjs/egg-cluster&max=240&columns=26)](https://github.com/eggjs/egg-cluster/graphs/contributors) diff --git a/lib/app_worker.js b/lib/app_worker.js index dabbbbc..26a32dd 100644 --- a/lib/app_worker.js +++ b/lib/app_worker.js @@ -35,6 +35,7 @@ const clusterConfig = app.config.cluster || /* istanbul ignore next */ {}; const listenConfig = clusterConfig.listen || /* istanbul ignore next */ {}; const httpsOptions = Object.assign({}, clusterConfig.https, options.https); const port = options.port = options.port || listenConfig.port; +const reusePort = options.reusePort = options.reusePort || listenConfig.reusePort; const debugPort = options.debugPort; const protocol = (httpsOptions.key && httpsOptions.cert) ? 'https' : 'http'; @@ -121,10 +122,21 @@ function startServer(err) { exitProcess(); return; } - const args = [ port ]; - if (listenConfig.hostname) args.push(listenConfig.hostname); - debug('listen options %s', args); - server.listen(...args); + if (reusePort) { + const listenOptions = { port, reusePort }; + if (listenConfig.hostname) { + listenOptions.host = listenConfig.hostname; + } + debug('listen options %s', listenOptions); + server.listen(listenOptions); + } else { + const args = [ port ]; + if (listenConfig.hostname) { + args.push(listenConfig.hostname); + } + debug('listen options %s', args); + server.listen(...args); + } } if (debugPortServer) { debug('listen on debug port: %s', debugPort); diff --git a/lib/master.js b/lib/master.js index 2dc1c20..e219835 100644 --- a/lib/master.js +++ b/lib/master.js @@ -34,6 +34,7 @@ class Master extends EventEmitter { * - {Object} [plugins] - customized plugins, for unittest * - {Number} [workers] numbers of app workers, default to `os.cpus().length` * - {Number} [port] listening port, default to 7001(http) or 8443(https) + * - {Boolean} [reusePort] setting `reusePort` to `true` allows multiple sockets on the same host to bind to the same port. Incoming connections are distributed by the operating system to listening sockets. This option is available only on some platforms, such as Linux 3.9+, DragonFlyBSD 3.6+, FreeBSD 12.0+, Solaris 11.4, and AIX 7.2.5+. **Default:** `false`. * - {Number} [debugPort] listening a debug port on http protocol * - {Object} [https] https options, { key, cert, ca }, full path * - {Array|String} [require] will inject into worker/agent process diff --git a/lib/utils/options.js b/lib/utils/options.js index 3fff0f6..3103ea3 100644 --- a/lib/utils/options.js +++ b/lib/utils/options.js @@ -12,6 +12,7 @@ module.exports = function(options) { framework: '', baseDir: process.cwd(), port: options.https ? 8443 : null, + reusePort: false, workers: null, plugins: null, https: false, diff --git a/test/app_worker.test.js b/test/app_worker.test.js index 12db96b..d0b6529 100644 --- a/test/app_worker.test.js +++ b/test/app_worker.test.js @@ -232,6 +232,35 @@ describe('test/app_worker.test.js', () => { .expect(200); }); + it('should set reusePort=true in config', async () => { + app = utils.cluster('apps/app-listen-reusePort'); + // app.debug(); + await app.ready(); + + app.expect('code', 0); + app.expect('stdout', /egg started on http:\/\/127.0.0.1:17010/); + + await request('http://0.0.0.0:17010') + .get('/') + .expect('done') + .expect(200); + + await request('http://127.0.0.1:17010') + .get('/') + .expect('done') + .expect(200); + + await request('http://localhost:17010') + .get('/') + .expect('done') + .expect(200); + + await request('http://127.0.0.1:17010') + .get('/port') + .expect('17010') + .expect(200); + }); + it('should use hostname in config', async () => { const url = address.ip() + ':17010'; diff --git a/test/fixtures/apps/app-listen-reusePort/app.js b/test/fixtures/apps/app-listen-reusePort/app.js new file mode 100644 index 0000000..dc71e21 --- /dev/null +++ b/test/fixtures/apps/app-listen-reusePort/app.js @@ -0,0 +1,6 @@ +'use strict'; + +module.exports = app => { + // don't use the port that egg-mock defined + app._options.port = undefined; +}; diff --git a/test/fixtures/apps/app-listen-reusePort/app/router.js b/test/fixtures/apps/app-listen-reusePort/app/router.js new file mode 100644 index 0000000..26e4658 --- /dev/null +++ b/test/fixtures/apps/app-listen-reusePort/app/router.js @@ -0,0 +1,9 @@ +module.exports = app => { + app.get('/', ctx => { + ctx.body = 'done'; + }); + + app.get('/port', ctx => { + ctx.body = ctx.app._options.port; + }); +}; diff --git a/test/fixtures/apps/app-listen-reusePort/config/config.default.js b/test/fixtures/apps/app-listen-reusePort/config/config.default.js new file mode 100644 index 0000000..afbe3d5 --- /dev/null +++ b/test/fixtures/apps/app-listen-reusePort/config/config.default.js @@ -0,0 +1,11 @@ +'use strict'; + +module.exports = { + keys: '123', + cluster: { + listen: { + port: 17010, + reusePort: true, + }, + }, +}; diff --git a/test/fixtures/apps/app-listen-reusePort/package.json b/test/fixtures/apps/app-listen-reusePort/package.json new file mode 100644 index 0000000..f78929e --- /dev/null +++ b/test/fixtures/apps/app-listen-reusePort/package.json @@ -0,0 +1,3 @@ +{ + "name": "app-listen-reusePort" +} diff --git a/test/master.test.js b/test/master.test.js index abf32c6..b2a81ff 100644 --- a/test/master.test.js +++ b/test/master.test.js @@ -29,6 +29,17 @@ describe('test/master.test.js', () => { .end(done); }); + it('start success with reusePort=true', done => { + mm.env('local'); + app = utils.cluster('apps/master-worker-started', { reusePort: true }); + + app.expect('stdout', /egg start/) + .expect('stdout', /egg started/) + .notExpect('stdout', /\[master\] agent_worker#1:\d+ start with clusterPort:\d+/) + .expect('code', 0) + .end(done); + }); + it('start success in prod env', done => { mm.env('prod'); app = utils.cluster('apps/mock-production-app').debug(false);