├── .gitignore ├── README.md ├── bin └── koa-cluster ├── lib └── http.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.exe 7 | *.o 8 | *.so 9 | 10 | # Packages # 11 | ############ 12 | # it's better to unpack these files and commit the raw source 13 | # git has its own built in compression methods 14 | *.7z 15 | *.dmg 16 | *.gz 17 | *.iso 18 | *.jar 19 | *.rar 20 | *.tar 21 | *.zip 22 | 23 | # Logs and databases # 24 | ###################### 25 | *.log 26 | *.sql 27 | *.sqlite 28 | 29 | # OS generated files # 30 | ###################### 31 | .DS_Store* 32 | # Icon? 33 | ehthumbs.db 34 | Thumbs.db 35 | 36 | # Node.js # 37 | ########### 38 | lib-cov 39 | *.seed 40 | *.log 41 | *.csv 42 | *.dat 43 | *.out 44 | *.pid 45 | *.gz 46 | 47 | pids 48 | logs 49 | results 50 | 51 | node_modules 52 | npm-debug.log 53 | 54 | # Components # 55 | ############## 56 | 57 | /build 58 | /components 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Koa Cluster 3 | 4 | ![KoaJs Slack](https://img.shields.io/badge/Koa.Js-Slack%20Channel-Slack.svg?longCache=true&style=for-the-badge) 5 | 6 | Lightweight, opinionated clustering for Koa apps. 7 | Use this if you don't want the complexity of other process managers. 8 | 9 | ## Usage 10 | 11 | ```bash 12 | koa-cluster app.js 13 | ``` 14 | 15 | ## Koa App 16 | 17 | You must export your Koa app like: 18 | 19 | ```js 20 | var app = module.exports = koa() 21 | ``` 22 | 23 | Make sure you don't create a `http` server yourself. 24 | Don't call `app.listen()`. 25 | This will handle it automatically. 26 | 27 | ### Check Continue 28 | 29 | This automatically utilizes the `checkContinue` event. 30 | You should handle this wherever appropriate in your Koa app: 31 | 32 | ```js 33 | app.use(function* () { 34 | if (this.req.checkContinue) this.res.writeContinue(); 35 | var body = yield parse(this); 36 | }) 37 | ``` 38 | 39 | ### Settings 40 | 41 | Custom settings are checked on the `app` that aren't part of Koa. 42 | You can set them however you'd like. 43 | These settings are: 44 | 45 | - `name` - your app's name 46 | - `port` 47 | - `hostname` 48 | - `maxHeadersCount` 49 | - `timeout` 50 | 51 | ## License 52 | 53 | The MIT License (MIT) 54 | 55 | Copyright (c) 2014 Jonathan Ong me@jongleberry.com 56 | 57 | Permission is hereby granted, free of charge, to any person obtaining a copy 58 | of this software and associated documentation files (the "Software"), to deal 59 | in the Software without restriction, including without limitation the rights 60 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 61 | copies of the Software, and to permit persons to whom the Software is 62 | furnished to do so, subject to the following conditions: 63 | 64 | The above copyright notice and this permission notice shall be included in 65 | all copies or substantial portions of the Software. 66 | 67 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 68 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 69 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 70 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 71 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 72 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 73 | THE SOFTWARE. 74 | -------------------------------------------------------------------------------- /bin/koa-cluster: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var program = require('commander') 4 | var cluster = require('cluster') 5 | 6 | program 7 | .usage('') 8 | .option('-t, --title ', 'title of the process') 9 | .option('-p, --processes ', 'number of processes to use', parseInt) 10 | .option('-s, --startsecs ', 'number of seconds which children needs to' 11 | + ' stay running to be considered a successfull start [1]', parseInt, 1) 12 | .parse(process.argv) 13 | 14 | // make sure the file exists 15 | var filename = require('path').resolve(program.args[0]) 16 | var requirename = require.resolve(filename) 17 | try { 18 | require('fs').statSync(requirename) 19 | } catch (err) { 20 | console.error('resolved %s to %s then %s', program.args[0], filename, requirename) 21 | console.error('however, %s does not exist', requirename) 22 | process.exit(1) 23 | } 24 | 25 | if (program.title) process.title = program.title 26 | 27 | cluster.setupMaster({ 28 | exec: require.resolve('../lib/http.js'), 29 | execArgv: ['--harmony'], 30 | args: [requirename], 31 | }) 32 | 33 | var procs; 34 | 35 | if (program.processes) { 36 | procs = program.processes; 37 | } else { 38 | var cpus = require('os').cpus().length 39 | procs = Math.ceil(0.75 * cpus) 40 | } 41 | 42 | for (var i = 0; i < procs; i++) cluster.fork() 43 | 44 | // remember the time which children have started in order 45 | // to stop everything on instant failure 46 | var startTime = process.hrtime() 47 | 48 | // http://nodejs.org/api/cluster.html#cluster_event_disconnect 49 | // not sure if i need to listen to the `exit` event 50 | cluster.on('disconnect', onDisconnect) 51 | 52 | // don't try to terminate multiple times 53 | var terminating = false 54 | process.on('SIGTERM', terminate) 55 | process.on('SIGINT', terminate) 56 | 57 | // i'm not even sure if we need to pass SIGTERM to the workers... 58 | function terminate() { 59 | if (terminating) return 60 | terminating = true 61 | 62 | // don't restart workers 63 | cluster.removeListener('disconnect', onDisconnect) 64 | // kill all workers 65 | Object.keys(cluster.workers).forEach(function (id) { 66 | console.log('sending kill signal to worker %s', id) 67 | cluster.workers[id].kill('SIGTERM') 68 | }) 69 | } 70 | 71 | function onDisconnect(worker) { 72 | var timeSinceStart = process.hrtime(startTime) 73 | timeSinceStart = timeSinceStart[0] + timeSinceStart[1] / 1e9 74 | if (timeSinceStart < program.startsecs) { 75 | console.log('worker ' + worker.process.pid + ' has died' 76 | + ' before startsecs is over. stopping all.') 77 | process.exitCode = worker.process.exitCode || 1 78 | terminate() 79 | } else { 80 | console.log('worker ' + worker.process.pid + ' has died. forking.') 81 | cluster.fork() 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /lib/http.js: -------------------------------------------------------------------------------- 1 | 2 | var start = Date.now() 3 | 4 | var http = require('http') 5 | var cluster = require('cluster') 6 | 7 | var args = process.argv 8 | 9 | var app = require(args[args.length - 1]) 10 | var callback = app.callback() 11 | 12 | var server = http.createServer() 13 | server.on('request', callback) 14 | server.on('checkContinue', function (req, res) { 15 | req.checkContinue = true 16 | callback(req, res) 17 | }) 18 | 19 | // custom koa settings 20 | // defaults to http://nodejs.org/api/http.html#http_server_maxheaderscount 21 | server.maxHeadersCount = app.maxHeadersCount || 1000 22 | server.timeout = app.timeout || 120000 23 | 24 | server.listen(app.port, app.hostname, function (err) { 25 | if (err) throw err 26 | console.log('%s listening on port %s, started in %sms', 27 | app.name || 'koa app', 28 | this.address().port, 29 | Date.now() - start) 30 | }) 31 | 32 | // don't try to close the server multiple times 33 | var closing = false 34 | process.on('SIGTERM', function () { 35 | close(0) 36 | }) 37 | process.on('SIGINT', function () { 38 | close(0) 39 | }) 40 | process.on('uncaughtException', function (err) { 41 | console.error(err.stack) 42 | close(1) 43 | }) 44 | process.on('exit', function () { 45 | console.log('exiting worker %s', cluster.worker.id) 46 | }) 47 | 48 | function close(code) { 49 | if (closing) return 50 | console.log('closing worker %s', cluster.worker.id) 51 | closing = true 52 | 53 | // to do: make this an option 54 | var killtimer = setTimeout(function () { 55 | process.exit(code) 56 | }, 30000) 57 | 58 | // http://nodejs.org/api/timers.html#timers_unref 59 | killtimer.unref() 60 | server.close() 61 | cluster.worker.disconnect() 62 | } 63 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "koa-cluster", 3 | "description": "Koa clustering and domain-based error handling utility", 4 | "version": "1.1.0", 5 | "author": { 6 | "name": "Jonathan Ong", 7 | "email": "me@jongleberry.com", 8 | "url": "http://jongleberry.com", 9 | "twitter": "https://twitter.com/jongleberry" 10 | }, 11 | "repository": "koajs/cluster", 12 | "license": "MIT", 13 | "dependencies": { 14 | "commander": "2" 15 | }, 16 | "bin": "bin/koa-cluster", 17 | "engines": { 18 | "node": ">= 4.0.0" 19 | } 20 | } 21 | --------------------------------------------------------------------------------