├── .gitignore ├── LICENSE ├── Makefile ├── NEWS ├── README.md ├── TODO.md ├── lib └── node-varnish │ ├── index.js │ ├── varnish_client.js │ └── varnish_queue.js ├── package.json └── test ├── README.md └── acceptance ├── varnish.js └── varnish_emu.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Vizzuality 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its contributors 15 | may be used to endorse or promote products derived from this software without 16 | specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | npm install 3 | 4 | clean: 5 | rm -rf node_modules/* 6 | 7 | check: 8 | npm test 9 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | Versions 0.4.0 2 | 2014-MM-DD 3 | 4 | - Switch to 3-clause BSD license (#10) 5 | 6 | Versions 0.3.0 7 | 2014-02-28 8 | 9 | - Consistently emit Error instances on 'error' (#9) 10 | - Log varnish connection errors 11 | - Allow passing queue and reconnection parameters via VarnishQueue ctor 12 | 13 | Versions 0.2.0 14 | 2014-02-12 15 | 16 | - Added support for authentication 17 | - Fix EventEmitter inheritance 18 | - Logging is now optional: client.debug = true; 19 | - "ready_callback" now gets 2 params: err and clientInstance 20 | - Allow preventing queue from containing duplicates 21 | 22 | Versions 0.1.1 23 | 2012-07-10 24 | 25 | - Fix global variable leaks (#1) 26 | - Testsuite enhancements 27 | - Tests ported to mocha (#6) 28 | - Run on "make check" (#4) 29 | - Improve runtime predictability (#5) 30 | - Add "make all" and "make clean" fules 31 | 32 | Versions 0.1.0 33 | 2011-12-12 34 | 35 | - Initial release 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | node-varnish 2 | == 3 | 4 | A node.js connector to Varnish using the [Varnish telnet management protocol](https://www.varnish-cache.org/trac/wiki/ManagementPort). 5 | 6 | ```javascript 7 | var Varnish = require('node-varnish'); 8 | 9 | var client = new Varnish.VarnishClient('127.0.0.1', MANAGEMENT_PORT[, secret]); 10 | client.on('ready', function() { 11 | client.run_cmd('purge obj.http.X == test', function(){}); 12 | }); 13 | ``` 14 | 15 | For more usage examples, see the [tests](https://github.com/Vizzuality/node-varnish/blob/master/test/acceptance/varnish.js). 16 | 17 | Install 18 | -- 19 | ``` 20 | npm install node-varnish 21 | ``` 22 | 23 | Dependencies 24 | -- 25 | 26 | * [node.js](http://nodejs.org/) >=4.x 27 | * [varnish](https://www.varnish-cache.org/) >=2.x 28 | 29 | Contributors 30 | -- 31 | 32 | * [Javi Santana](https://github.com/javisantana/) 33 | * [Simon Tokumine](https://github.com/tokumine/) 34 | * [Tiago Relvao](https://github.com/relvao/) 35 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | * expose VarnishQueue options 2 | -------------------------------------------------------------------------------- /lib/node-varnish/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | VarnishClient: require('./varnish_client'), 3 | VarnishQueue: require('./varnish_queue') 4 | } 5 | 6 | -------------------------------------------------------------------------------- /lib/node-varnish/varnish_client.js: -------------------------------------------------------------------------------- 1 | var net = require('net'), 2 | util = require('util'), 3 | EventEmitter = require('events').EventEmitter; 4 | 5 | function VarnishClient(host, port, secret, ready_callback) { 6 | var self = this; 7 | var ready = false; 8 | var cmd_callback = null; 9 | var client = null; 10 | var connected = false; 11 | var connecting = false; 12 | self.debug = false; 13 | 14 | // secret is an optional argument 15 | if (arguments.length < 4 && typeof secret === 'function') { 16 | ready_callback = secret; 17 | secret = undefined; 18 | } 19 | 20 | function log() { 21 | self.debug && console.log.apply(console, arguments); 22 | } 23 | 24 | function connect() { 25 | if(connecting || connected ) return; 26 | connecting = true; 27 | log("VARNISH: connection"); 28 | ready = false; 29 | if(!client) { 30 | client = net.createConnection(port, host); 31 | client.on('connect', function () { 32 | log("VARNISH: connected"); 33 | connected = true; 34 | self.emit('connect'); 35 | connecting = false; 36 | }); 37 | } else { 38 | client.connect(port, host); 39 | } 40 | } 41 | 42 | function authenticate(challenge) { 43 | var err = '', 44 | crypto = require('crypto'), 45 | shasum = crypto.createHash('sha256'), 46 | authResponse = ''; 47 | 48 | if(authenticate.atempt){ 49 | err = new Error('Unable to authenticate - unauthorized'); 50 | log(err); 51 | ready_callback && ready_callback(err) 52 | self.emit('error', err); 53 | return; 54 | } 55 | authenticate.atempt = true; 56 | 57 | log('Authentication required. challenge: ', challenge); 58 | if(!secret) { 59 | err = new Error('Unable to authenticate - no secret provided'); 60 | log(err); 61 | ready_callback && ready_callback(err); 62 | self.emit('error', err); 63 | return; 64 | } 65 | 66 | shasum.update(challenge + "\n" + secret + "\n" + challenge + "\n"); 67 | authResponse = shasum.digest('hex'); 68 | client.write('auth ' + authResponse + "\n" ); 69 | 70 | log('Authentication submited: ', authResponse); 71 | } 72 | 73 | self.connect = connect; 74 | connect(); 75 | 76 | client.on('data', function (data) { 77 | data = data.toString(); 78 | var lines = data.split('\n', 2); 79 | if(lines.length == 2) { 80 | var tk = lines[0].split(' '); 81 | var code = parseInt(tk[0], 10); 82 | //var body_length = parseInt(tk[1], 10); 83 | var body = lines[1]; 84 | 85 | if(code === 107){//requires auth 86 | authenticate(body); 87 | return; 88 | } 89 | 90 | if(!ready) { 91 | ready = true; 92 | self.emit('ready'); 93 | log("VARNISH Client: ready"); 94 | ready_callback && ready_callback(null, self); 95 | } else if(cmd_callback) { 96 | var c = cmd_callback; 97 | cmd_callback = null; 98 | c(null, code, body); 99 | self.emit('response', code, body); 100 | log("VARNISH response: ", code, body); 101 | } 102 | } 103 | 104 | }); 105 | 106 | client.on('error', function(err) { 107 | log("[ERROR] some problem in varnish connection", err); 108 | self.emit('error', err); 109 | }); 110 | 111 | client.on('close', function(e) { 112 | log("[INFO] closed varnish connection"); 113 | self.close(); 114 | connected = false; 115 | connecting = false; 116 | }); 117 | 118 | // sends the command to the server 119 | function _send(cmd, callback) { 120 | cmd_callback = callback; 121 | if(connected) { 122 | client.write(cmd + '\n'); 123 | } else { 124 | connect(); 125 | } 126 | } 127 | 128 | // run command if there is no peding response 129 | // fist param of the callback are the error, null 130 | // if all went ok 131 | self.run_cmd = function(cmd, callback) { 132 | if(!connected) { 133 | connect(); 134 | } 135 | if(!cmd_callback) { 136 | _send(cmd, callback); 137 | } else { 138 | callback('response pending'); 139 | var err = new Error( 'there is a response pending' ); 140 | err.code = 'RESPONSE_PENDING'; 141 | self.emit('error', err); 142 | } 143 | }; 144 | 145 | // close the connection 146 | self.close = function() { 147 | client.end(); 148 | ready = false; 149 | self.emit('close'); 150 | }; 151 | 152 | } 153 | 154 | util.inherits(VarnishClient, EventEmitter); 155 | 156 | module.exports = VarnishClient; 157 | -------------------------------------------------------------------------------- /lib/node-varnish/varnish_queue.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter, 2 | util = require('util'), 3 | VarnishClient = require('./varnish_client'); 4 | 5 | // @param opts extrac configurations: 6 | // max_queue - max number of items in queue, defaults to 2000 7 | // new items will be discarded 8 | // max_recon_retries - max number of reconnection attempts 9 | // defaults to 120 10 | // recon_interval_ms - interval in milliseconds between 11 | // reconnection attempts. Defaults to 1000. 12 | // 13 | function VarnishQueue(host, port, secret, opts) { 14 | var self = this; 15 | var queue = []; 16 | var ready = false; 17 | var reconnectTimer = null; 18 | var reconnectTries = 0; 19 | opts = opts || {}; 20 | if ( ! opts.hasOwnProperty('max_queue') ) opts.max_queue = 2000; 21 | if ( ! opts.hasOwnProperty('max_recon_retries') ) opts.max_recon_retries = 120; 22 | if ( ! opts.hasOwnProperty('recon_interval_ms') ) opts.recon_interval_ms = 1000; 23 | 24 | var MAX_QUEUE = opts.max_queue; 25 | var MAX_RECONNECT_TRIES = opts.max_recon_retries; 26 | var RECONNECT_INTERVAL = opts.recon_interval_ms; 27 | 28 | var client = new VarnishClient(host, port, secret); 29 | 30 | self.debug = false; 31 | 32 | function log() { 33 | self.debug && console.log.apply(console, arguments); 34 | } 35 | 36 | // attach a dummy callback to error event to avoid nodejs throws an exception and closes the process 37 | self.on('error', function(e) { 38 | log("error", e); 39 | }); 40 | 41 | client.on('connect', function() { 42 | clearInterval(reconnectTimer); 43 | reconnectTries = 0; 44 | }); 45 | 46 | client.on('ready', function() { 47 | ready = true; 48 | log('sending pending'); 49 | _send_pending(); 50 | }); 51 | 52 | function reconnect() { 53 | ready = false; 54 | clearInterval(reconnectTimer); 55 | reconnectTimer = setInterval(function() { 56 | client.connect(); 57 | ++reconnectTries; 58 | if(reconnectTries >= MAX_RECONNECT_TRIES) { 59 | var err = new Error('max reconnect tries, aborting'); 60 | err.code = 'ABORT_RECONNECT'; 61 | self.emit('error', err); 62 | clearInterval(reconnectTimer); 63 | } 64 | }, RECONNECT_INTERVAL); 65 | } 66 | client.on('close', reconnect); 67 | client.on('error', function(err) { 68 | console.log("Varnish connection error: " + err); 69 | reconnect(); 70 | }); 71 | 72 | function _send_pending(empty_callback) { 73 | if(!ready) return; 74 | var c = queue.pop(); 75 | if(!c) return; 76 | client.run_cmd(c, function() { 77 | if(queue.length > 0) { 78 | process.nextTick(_send_pending); 79 | } else { 80 | if(empty_callback) { 81 | empty_callback(); 82 | } 83 | self.emit('empty'); 84 | } 85 | }); 86 | } 87 | 88 | self.run_cmd = function(cmd, nodup) { 89 | if ( nodup && queue.indexOf(cmd) != -1 ) { 90 | log("skip duplicated varnish command in queue: " + cmd); 91 | return; 92 | } 93 | queue.push(cmd); 94 | if(queue.length > MAX_QUEUE) { 95 | console.log("varnish command queue too long, removing commands"); 96 | var err = new Error("varnish command queue too long, removing commands"); 97 | err.code = 'TOO_LONG'; 98 | self.emit('error', err); 99 | queue.pop(); 100 | } 101 | if(ready) { 102 | _send_pending(); 103 | } 104 | }; 105 | 106 | self.end = function() { 107 | _send_pending(function() { 108 | client.close(); 109 | }); 110 | }; 111 | } 112 | 113 | util.inherits(VarnishQueue, EventEmitter); 114 | 115 | module.exports = VarnishQueue; 116 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-varnish", 3 | "version": "0.4.0", 4 | "main": "./lib/node-varnish/index.js", 5 | "description": "A node.js connector to Varnish using the Varnish telnet management protocol", 6 | "url": "https://github.com/Vizzuality/node-varnish", 7 | "licenses": [{ "type": "BSD" }], 8 | "repository": { 9 | "type": "git", 10 | "url": "git://github.com/Vizzuality/node-varnish.git" 11 | }, 12 | "author": { 13 | "name": "Javier Santana, Simon Tokumine, Vizzuality", 14 | "url": "http://vizzuality.com", 15 | "email": "jsantana@vizzuality.com" 16 | }, 17 | "devDependencies": { 18 | "mocha": "1.2.1" 19 | }, 20 | "scripts": { 21 | "test": "mocha -u tdd test/acceptance/varnish.js" 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | Tests 2 | -- 3 | To run the tests, run from root: 4 | 5 | ``` 6 | mocha --ui tdd test/acceptance/varnish.js 7 | ``` 8 | -------------------------------------------------------------------------------- /test/acceptance/varnish.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var net = require('net'); 3 | var varnish = require(__dirname + '/../../lib/node-varnish'); 4 | var VarnishEmu = require('./varnish_emu'); 5 | var tests = module.exports = {}; 6 | 7 | suite('varnish', function() { 8 | 9 | test('should connect', function(done) { 10 | var ok = false; 11 | var server = new VarnishEmu(); 12 | server.on('listening', function() { 13 | var client = new varnish.VarnishClient('127.0.0.1', server.address().port); 14 | client.on('connect', function() { 15 | ok = true; 16 | }); 17 | }); 18 | setTimeout(function() { 19 | assert.ok(ok); 20 | server.close_connections(); 21 | server.close(done); 22 | }, 200); 23 | }); 24 | 25 | test('should send a command', function(done) { 26 | var ok = false; 27 | var server = new VarnishEmu(function() { 28 | ok = true; 29 | }); 30 | server.on('listening', function() { 31 | var client = new varnish.VarnishClient('127.0.0.1', server.address().port); 32 | client.on('ready', function myReady() { 33 | client.run_cmd('purge obj.http.X == test', function(){}); 34 | }); 35 | }); 36 | setTimeout(function() { 37 | assert.ok(ok); 38 | server.close_connections(); 39 | server.close(done); 40 | }, 100); 41 | }); 42 | 43 | test('should emit close on server disconect', function(done) { 44 | var ok = false; 45 | var server = new VarnishEmu(); 46 | server.on('listening', function() { 47 | var client = new varnish.VarnishClient('127.0.0.1', server.address().port); 48 | client.on('ready', function() { 49 | client.on('close', function() { ok = true; }); 50 | server.close_connections(); 51 | server.close(); 52 | }); 53 | }); 54 | setTimeout(function() { 55 | assert.ok(ok); 56 | done(); 57 | }, 500); 58 | }); 59 | 60 | test('should emit response on command', function(done) { 61 | var ok = false; 62 | var server = new VarnishEmu(); 63 | server.on('listening', function() { 64 | var client = new varnish.VarnishClient('127.0.0.1', server.address().port); 65 | client.on('ready', function() { 66 | client.run_cmd('purge obj.http.X == test', function(){}); 67 | client.on('response', function(code, body) { 68 | ok = true; 69 | assert.equal(200, code); 70 | }); 71 | }); 72 | }); 73 | setTimeout(function() { 74 | assert.ok(ok); 75 | server.close_connections(); 76 | server.close(); 77 | done(); 78 | }, 100); 79 | }); 80 | 81 | test('should emit error when the user tries to send when thereis a pending command', function(done) { 82 | var ok = false; 83 | var server = new VarnishEmu(); 84 | server.on('listening', function() { 85 | var client = new varnish.VarnishClient('127.0.0.1', server.address().port); 86 | client.on('ready', function() { 87 | client.run_cmd('purge obj.http.X == test', function(){}); 88 | client.on('error', function(e) { 89 | ok = true; 90 | assert.equal('RESPONSE_PENDING', e.code); 91 | }); 92 | client.run_cmd('purge obj.http.X == test', function(){}); 93 | }); 94 | }); 95 | setTimeout(function() { assert.ok(ok); done(); }, 100); 96 | }); 97 | 98 | // 99 | // queue 100 | // 101 | 102 | test('should send command', function(done) { 103 | var server = new VarnishEmu(); 104 | server.on('listening', function() { 105 | var queue = new varnish.VarnishQueue('127.0.0.1', server.address().port); 106 | for(var i = 0; i < 5; ++i) { 107 | queue.run_cmd('purge simon_is == the_best'); 108 | } 109 | queue.on('empty', function() { 110 | assert.equal(5, server.commands); 111 | done(); 112 | }); 113 | }); 114 | }); 115 | 116 | test('should not send duplicate commands if asked', function(done) { 117 | var server = new VarnishEmu(); 118 | server.on('listening', function() { 119 | var queue = new varnish.VarnishQueue('127.0.0.1', server.address().port); 120 | for(var i = 0; i < 5; ++i) { 121 | queue.run_cmd('purge ' + (i%2), true); 122 | } 123 | queue.on('empty', function() { 124 | assert.equal(2, server.commands); 125 | done(); 126 | }); 127 | }); 128 | }); 129 | 130 | test('should send commands on connect', function(done) { 131 | // first create queue 132 | var queue = new varnish.VarnishQueue('127.0.0.1', 1234); 133 | for(var i = 0; i < 10; ++i) { 134 | queue.run_cmd('purge simon_is == the_best'); 135 | } 136 | 137 | // then server 138 | var server = new VarnishEmu(null, 1234); 139 | 140 | queue.on('empty', function() { 141 | assert.equal(10, server.commands); done(); 142 | }); 143 | }); 144 | 145 | // 146 | // auth 147 | // 148 | 149 | test('should authenticate', function(done) { 150 | var ok = false; 151 | var server = new VarnishEmu(null, null, true);//requires auth 152 | 153 | server.on('listening', function() { 154 | var secret = 'foo', 155 | client = new varnish.VarnishClient('127.0.0.1', server.address().port, secret); 156 | //client.debug = true; 157 | client.on('connect', function() { 158 | ok = true; 159 | }); 160 | }); 161 | setTimeout(function() { 162 | assert.ok(ok); 163 | server.close_connections(); 164 | server.close(done); 165 | }, 200); 166 | }); 167 | }); 168 | 169 | 170 | -------------------------------------------------------------------------------- /test/acceptance/varnish_emu.js: -------------------------------------------------------------------------------- 1 | var net = require('net'); 2 | 3 | function VarnishEmu(on_cmd_recieved, port, requireAuth) { 4 | var self = this, 5 | msg = '', 6 | code = 0; 7 | 8 | self.commands_recieved = []; 9 | var sockets = []; 10 | var server = net.createServer(function (socket) { 11 | if(requireAuth){ 12 | msg = 'bkiehgjkhonrgtlzvikyvynswrinerqf'; 13 | code = 107; 14 | } else { 15 | msg = 'hi, im a varnish emu, right?'; 16 | code = 200; 17 | } 18 | socket.write(code + " " + msg.length + "\n"); 19 | socket.write(msg); 20 | socket.on('data', function(data) { 21 | self.commands_recieved.push(data); 22 | server.commands++; 23 | on_cmd_recieved && on_cmd_recieved(self.commands_recieved); 24 | if(requireAuth){ 25 | if(data.toString('utf8') === 'auth 1ae1b9b5069b151d5ad5ee3339a33dc5321b5fa7ad2c31709d21490ba90e6a09\n'){ 26 | socket.write('200 0\n'); 27 | }else{ 28 | socket.write(code + " " + msg.length + "\n"); 29 | socket.write(msg); 30 | } 31 | }else{ 32 | socket.write('200 0\n'); 33 | } 34 | }); 35 | sockets.push(socket); 36 | }); 37 | 38 | server.commands = 0; 39 | server.listen(port || 0, "127.0.0.1"); 40 | server.close_connections = function() { 41 | for(var s in sockets) { 42 | sockets[s].end(); 43 | } 44 | }; 45 | return server; 46 | } 47 | 48 | module.exports = VarnishEmu; --------------------------------------------------------------------------------