├── .gitignore ├── lib ├── storage.js ├── communication.js ├── util.js ├── storage │ └── base.js ├── communication │ ├── base.js │ └── mqtt.js ├── monitor.js └── site.js ├── site-monitor.js ├── package.json ├── tests └── index.js ├── sample-config.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | .nodester.appconfig 3 | /config.json 4 | /monitor.log -------------------------------------------------------------------------------- /lib/storage.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Base: require('./storage/base'), 3 | findByType: function(){ 4 | return this.Base; 5 | } 6 | }; -------------------------------------------------------------------------------- /lib/communication.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Base: require('./communication/base'), 3 | Mqtt: require('./communication/mqtt'), 4 | findByType: function(type){ 5 | if (type === "mqtt") { 6 | return this.Mqtt; 7 | } else { 8 | return this.Base; 9 | } 10 | } 11 | }; -------------------------------------------------------------------------------- /site-monitor.js: -------------------------------------------------------------------------------- 1 | var monitor = require('./lib/monitor'); 2 | 3 | //Uncaught exception 4 | process.on("uncaughtException", function(error) { 5 | console.log("Uncaught Exception: " + error + " TRACE: " + error.stack); 6 | }); 7 | 8 | //Start monitoring 9 | monitor(require('./config.json')); -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | //Native lib 2 | var util = require('util'); 3 | 4 | /** 5 | * Async for each 6 | * 7 | * An asynchronous for each 8 | */ 9 | util.asyncForEach = function(array, fn, callback) { 10 | var completed = 0; 11 | if(array.length === 0) { 12 | callback(); // done immediately 13 | } 14 | var len = array.length; 15 | for(var i = 0; i < len; i++) { 16 | fn(array[i], function() { 17 | completed++; 18 | if(completed === array.length) { 19 | callback(); 20 | } 21 | }); 22 | } 23 | }; 24 | 25 | 26 | module.exports = util; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "site-monitor", 3 | "version": "0.3.0", 4 | "description": "Website monitor", 5 | "main": "site-monitor.js", 6 | "scripts": { 7 | "start": "node site-monitor.js" 8 | }, 9 | "keywords": [ 10 | "website", 11 | "monitor", 12 | "uptime", 13 | "pingdom" 14 | ], 15 | "repository": { 16 | "type": "git", 17 | "url": "https://git@github.com/ollieparsley/node-site-monitor.git" 18 | }, 19 | "dependencies": { 20 | "mqtt": "0.3.*", 21 | "nodemailer": "6.4.*" 22 | }, 23 | "devDependencies": { 24 | "mocha": "8.2.*" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/index.js: -------------------------------------------------------------------------------- 1 | var http = require('http'), 2 | monitor = require('../site-monitor'), 3 | server = null; 4 | 5 | describe('site-monitor', function () { 6 | 7 | it('should monitor site', function ( done ) { 8 | 9 | monitor(require('../config')); 10 | 11 | setTimeout(function () { 12 | console.log('verify your email for a down notification.'); 13 | 14 | //bring the server up 15 | server = http.createServer(function ( req, res ) { 16 | res.end('hello world'); 17 | }).listen(8080); 18 | 19 | setTimeout(function () { 20 | console.log('verify your email for an up notification'); 21 | done(); 22 | }, 10000); 23 | }, 2000); 24 | 25 | }); 26 | 27 | }); 28 | -------------------------------------------------------------------------------- /sample-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "storage": { 3 | "type": "stdout" 4 | }, 5 | "users": [ 6 | { 7 | "username":"ollie", 8 | "contact_methods":[ 9 | { 10 | "type":"email", 11 | "address": "site_alerts@hootware.com" 12 | }, 13 | { 14 | "type":"mqtt", 15 | "topic": "site-monitor" 16 | } 17 | ] 18 | } 19 | ], 20 | "sites": [ 21 | { 22 | "type": "http", 23 | "name": "OllieParsley.com", 24 | "url": "http://ollieparsley.com/dsadsa?dsadsa", 25 | "content": "Ollie Parsley", 26 | "interval": 60, 27 | "timeout": 5 28 | } 29 | ], 30 | "email": { 31 | "sender": "awesome@node-site-monitor.com", 32 | "service": "Gmail", 33 | "username": "some@gmail.com", 34 | "password": "password1234" 35 | }, 36 | "mqtt": { 37 | "brokers": [ 38 | { 39 | "host": "127.0.0.1", 40 | "port": 1883, 41 | "secure": false, 42 | "clientIdPrefix": "prefix_", 43 | "username": "my_username", 44 | "password": "my_password" 45 | } 46 | ] 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /lib/storage/base.js: -------------------------------------------------------------------------------- 1 | var events = require('events'); 2 | var util = require('util'); 3 | var http = require('http'); 4 | var Url = require('url'); 5 | 6 | /** 7 | * Base storage 8 | * 9 | * @param config Object the configuration 10 | * 11 | * @returns Site 12 | */ 13 | function BaseStorage(config) { 14 | events.EventEmitter.call(this); 15 | 16 | //The config 17 | this.config = config; 18 | 19 | } 20 | 21 | //Inherit event emitter 22 | util.inherits(BaseStorage, events.EventEmitter); 23 | 24 | //Export 25 | module.exports = BaseStorage; 26 | 27 | /** 28 | * Log success 29 | * 30 | * @returns void 31 | */ 32 | BaseStorage.prototype.logSuccess = function(site, stats) { 33 | //Remove the body, response and request 34 | delete stats.body; 35 | delete stats.response; 36 | delete stats.request; 37 | this.log("[SUCCESS] [" + site.name + "] [" + site.url + "] - " + JSON.stringify(stats)); 38 | } 39 | 40 | /** 41 | * Log failure 42 | * 43 | * @returns void 44 | */ 45 | BaseStorage.prototype.logFailure = function(site, stats) { 46 | //Remove the body, response and request 47 | delete stats.body; 48 | delete stats.response; 49 | delete stats.request; 50 | this.log("[FAILURE] [" + site.name + "] [" + site.url + "] - " + JSON.stringify(stats)); 51 | } 52 | 53 | /** 54 | * Log failure 55 | * 56 | * @returns void 57 | */ 58 | BaseStorage.prototype.logFalseAlarm = function(site) { 59 | this.log("[FALSE ALARM] [" + site.name + "] [" + site.url + "] - " + JSON.stringify({ 60 | isDown: site.isDown(), 61 | wasDown: site.wasDown() 62 | })); 63 | } 64 | 65 | 66 | /** 67 | * Log communication success 68 | * 69 | * @returns void 70 | */ 71 | BaseStorage.prototype.logCommunicationSuccess = function(site, communication) { 72 | this.log("[COMMS][SUCCESS] [" + site.name + "] [" + site.url + "]" + JSON.stringify(communication.toArray())); 73 | } 74 | 75 | /** 76 | * Log failure 77 | * 78 | * @returns void 79 | */ 80 | BaseStorage.prototype.logCommunicationFailure = function(site, communication, error) { 81 | this.log("[COMMS][FAILURE] [" + site.name + "] [" + site.url + "] - " + JSON.stringify(communication.toArray()) + ' - ' + error.stack); 82 | } 83 | 84 | /** 85 | * Log the message 86 | * 87 | * @returns void 88 | */ 89 | BaseStorage.prototype.log = function(message) { 90 | var date = new Date(); 91 | var datedMsg = date.toDateString() + ' ' + date.toTimeString().substr(0, 8) 92 | console.log(datedMsg + ": " + message); 93 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Node Site Monitor 2 | 3 | A simple node server that will check the status of any number of websites and alert any number of users in different ways. 4 | 5 | ## Why do this?? 6 | 7 | Well, we wanted a free distributed system for monitoring our websites. We can easily do that with various free node hosting solutions. 8 | The different alert types are free and therefore the entire end-to-end check doesn't cost a thing. 9 | 10 | ## Install 11 | ``` 12 | npm install site-monitor 13 | ``` 14 | OR 15 | ``` 16 | git clone git://github.com/hootware/node-site-monitor.git 17 | ``` 18 | 19 | ## Usage 20 | 21 | ### Command line 22 | 23 | This will use the config.json file 24 | ``` 25 | node site-monitor 26 | ``` 27 | 28 | ### Code 29 | 30 | You can optionally give all the options in the monitor method, or it will use the default config.json 31 | ``` 32 | var monitor = require('site-monitor'); 33 | monitor(opts) //see sample-config.json for options. 34 | ``` 35 | 36 | ## Check types 37 | 38 | The different ways that are checked to see the status of a site 39 | 40 | * Check if host is reachable 41 | * Check HTTP status code 42 | * Check for connect timeouts 43 | * Check to see if text on the page matches what is expected 44 | 45 | ## Alert types 46 | 47 | The different ways of sending alerts to users. Users can have multiple methods, each with different "availability windows" 48 | 49 | * E-mail: 50 | * GMail is the only service available at the moment 51 | * Other providers/SMTP setup coming soon 52 | * MQTT - MQ Telemetry Transport 53 | * Supply an array of brokers to connect to 54 | * Supply a different topic for each user 55 | * Make your own... just extend the base communication class lib/communication/base.js 56 | 57 | 58 | ## Storage types 59 | 60 | The different ways to store the site check data and what 61 | 62 | * stdout (console.log) 63 | * (future) file 64 | * (future) MySQL 65 | * (future) MongoDB 66 | * Make your own... just extend the base communication class lib/storage/base.js 67 | 68 | 69 | ## Setup 70 | This is all done in a simple config file. As long as you match the format in the config.json example it will work fine. 71 | The arrays in the config don't have any soft limits, so the only limits will be in node or hardware. Let us know if you have any issues. 72 | If you want to change the config, you need to restart the application. 73 | 74 | Copy the `sample-config.json` and rename to `config.json` then start the application. 75 | -------------------------------------------------------------------------------- /lib/communication/base.js: -------------------------------------------------------------------------------- 1 | var events = require('events'); 2 | var util = require('util'); 3 | var http = require('http'); 4 | var Url = require('url'); 5 | 6 | //Mailer 7 | var nodemailer = require('nodemailer'); 8 | 9 | 10 | 11 | /** 12 | * Base communication 13 | * 14 | * @param config Object the configuration 15 | * 16 | * @returns Site 17 | */ 18 | function BaseCommunication(user, config, baseConfig) { 19 | events.EventEmitter.call(this); 20 | 21 | //User 22 | this.user = user; 23 | 24 | //The config 25 | this.config = config; 26 | 27 | //The base config 28 | this.baseConfig = baseConfig; 29 | 30 | } 31 | 32 | //Inherit event emitter 33 | util.inherits(BaseCommunication, events.EventEmitter); 34 | 35 | //Export 36 | module.exports = BaseCommunication; 37 | 38 | /** 39 | * Can send 40 | * 41 | * @returns void 42 | */ 43 | BaseCommunication.prototype.toArray = function() { 44 | return {user: this.user, config: this.config}; 45 | } 46 | 47 | /** 48 | * Can send 49 | * 50 | * @returns void 51 | */ 52 | BaseCommunication.prototype.isAllowed = function() { 53 | return true; 54 | } 55 | 56 | /** 57 | * Log success 58 | * 59 | * @returns void 60 | */ 61 | BaseCommunication.prototype.send = function(up_down, site, stats, callback) { 62 | // send an e-mail 63 | var htmlBody = '

Hello ' + this.user.username + ',

'; 64 | var body = 'Hello ' + this.config.username + ',' + "\n\n"; 65 | 66 | //Remove stats 67 | delete stats.response; 68 | delete stats.request; 69 | 70 | //Loop through stats 71 | Object.keys(stats).forEach(function(key){ 72 | htmlBody += '
  • ' + key + ': ' + stats[key] + '
  • '; 73 | body += key + ': ' + stats[key] + "\n"; 74 | }); 75 | 76 | htmlBody += '

    Thanks
    Your site monitor

    '; 77 | body += "\nThanks\nYour site monitor"; 78 | 79 | //Get the transport 80 | var transport = nodemailer.createTransport("SMTP", { 81 | service: this.baseConfig.email.service, 82 | auth: { 83 | user: this.baseConfig.email.username, 84 | pass: this.baseConfig.email.password 85 | } 86 | }); 87 | 88 | var mailOptions = { 89 | transport: transport, 90 | from: "Site Alert <" + this.baseConfig.email.sender + ">", 91 | to: this.config.address, 92 | subject: up_down == 'down' ? site.name + ' is down!' : site.name + ' is up!', 93 | text: body, 94 | html: htmlBody 95 | } 96 | 97 | nodemailer.sendMail(mailOptions, function(error){ 98 | if(error){ 99 | callback(false, error); 100 | }else{ 101 | callback(true); 102 | } 103 | }); 104 | 105 | } 106 | -------------------------------------------------------------------------------- /lib/monitor.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var util = require('./util'); 3 | var storages = require('./storage'); 4 | var communications = require('./communication'); 5 | var Site = require('./site'); 6 | 7 | module.exports = function ( config ) { 8 | 9 | //Get the config 10 | if (!config) { 11 | config = require('../config.json'); 12 | } 13 | 14 | //Sites 15 | var sites = []; 16 | 17 | //Storage 18 | var storage = new (storages.findByType(config.storage.type)); 19 | 20 | //Add each site in the config into the array 21 | config.sites.forEach(function(siteConfig, index){ 22 | sites.push(new Site(siteConfig, index)); 23 | }); 24 | 25 | //Set up communications 26 | config.users.forEach(function(user){ 27 | user.comms = {}; 28 | user.contact_methods.forEach(function(communicationMethod){ 29 | //Set up 30 | var commsClass = communications.findByType(communicationMethod.type); 31 | user.comms[communicationMethod.type] = new commsClass({username: user.username}, communicationMethod, config); 32 | }); 33 | }); 34 | 35 | //Run the checks 36 | var runChecks = function(){ 37 | util.asyncForEach(sites, function(site){ 38 | //Check the site requires a check 39 | if (site.requiresCheck()) { 40 | site.check(function(stats){ 41 | //Up down 42 | var up_down = false; 43 | 44 | //Check for different situations 45 | if (site.isDown() && !site.wasDown()) { 46 | storage.logFailure(site, stats); 47 | up_down = 'down'; 48 | 49 | } else if (!site.isDown() && site.wasDown()) { 50 | //Site is up and was down 51 | storage.logSuccess(site, stats); 52 | up_down = 'up'; 53 | 54 | } else if (site.isDown() && site.wasDown()) { 55 | //Site still down 56 | storage.logFailure(site, stats); 57 | 58 | } else { 59 | //All is good in the hood 60 | storage.logSuccess(site, stats); 61 | 62 | } 63 | 64 | //Only carry on if the stats of the site has changed 65 | if (up_down !== false) { 66 | 67 | //Check one more time after a half second delay 68 | setTimeout(function(){ 69 | site.check(function(stats){ 70 | //Stats is the same as previous check 71 | if (site.isDown() === site.wasDown()) { 72 | //Send alerts to available users 73 | config.users.forEach(function(user){ 74 | user.contact_methods.forEach(function(communicationMethod){ 75 | //Send 76 | var comms = user.comms[communicationMethod.type]; 77 | if (comms.isAllowed()) { 78 | comms.send(up_down, site, stats, function(success, error){ 79 | if (success) { 80 | storage.logCommunicationSuccess(site, comms); 81 | } else { 82 | storage.logCommunicationFailure(site, comms, error); 83 | } 84 | }); 85 | } 86 | }); 87 | }); 88 | 89 | } else { 90 | //Log the false alarm! 91 | storage.logFalseAlarm(site); 92 | } 93 | 94 | }.bind(this)); 95 | 96 | }.bind(this), 500); 97 | } 98 | }); 99 | } 100 | }); 101 | } 102 | 103 | //Run the first check now 104 | runChecks(); 105 | 106 | //Run further checks 107 | setInterval(function(){ 108 | runChecks(); 109 | }, 10 * 1000); 110 | } -------------------------------------------------------------------------------- /lib/communication/mqtt.js: -------------------------------------------------------------------------------- 1 | var events = require('events'); 2 | var util = require('util'); 3 | var http = require('http'); 4 | var Url = require('url'); 5 | var mqtt = require('mqtt'); 6 | var BaseCommunication = require("./base"); 7 | 8 | 9 | /** 10 | * Base communication 11 | * 12 | * @param config Object the configuration 13 | * 14 | * @returns Site 15 | */ 16 | function MqttCommunication(user, config, baseConfig) { 17 | BaseCommunication.call(this, user, config, baseConfig); 18 | 19 | //MQTT Brokers 20 | this.brokers = {}; 21 | 22 | //MQTT Brokers 23 | var currentBrokerId = 0; 24 | 25 | var self = this; 26 | baseConfig.mqtt.brokers.forEach(function(brokerConfig){ 27 | currentBrokerId++; 28 | var brokerId = "broker_" + (currentBrokerId.toString()); 29 | 30 | //Options 31 | var options = { 32 | keepalive: 300, 33 | clientId: brokerConfig.clientIdPrefix + "node-site-monitor", 34 | clean: true, 35 | username: brokerConfig.username, 36 | password: brokerConfig.password 37 | }; 38 | 39 | function connect() { 40 | //Create client and assign to broker array 41 | var client = brokerConfig.secure ? mqtt.createSecureClient(brokerConfig.port, brokerConfig.host, options) : mqtt.createClient(brokerConfig.port, brokerConfig.host, options); 42 | 43 | //Add client connect event 44 | client.on("connect", function() { 45 | console.log("MQTT client connected to broker: " + options.clientId); 46 | self.brokers[brokerId] = client; 47 | }.bind(this)); 48 | client.on("error", function(error) { 49 | console.log("Broker connection " + brokerId + " error: ", error); 50 | }.bind(this)); 51 | client.on("close", function() { 52 | console.log("Broker connection close: " + brokerId); 53 | connect(); 54 | }.bind(this)); 55 | } 56 | 57 | //Connect now 58 | connect(); 59 | 60 | }.bind(this)); 61 | 62 | 63 | 64 | } 65 | 66 | //Inherit event emitter 67 | util.inherits(MqttCommunication, BaseCommunication); 68 | 69 | //Export 70 | module.exports = MqttCommunication; 71 | 72 | /** 73 | * Can send 74 | * 75 | * @returns void 76 | */ 77 | MqttCommunication.prototype.isAllowed = function() { 78 | return true; 79 | } 80 | 81 | /** 82 | * Log success 83 | * 84 | * @returns void 85 | */ 86 | MqttCommunication.prototype.send = function(up_down, site, stats, callback) { 87 | topic = this.config.topic; 88 | 89 | //Remove stats 90 | delete stats.response; 91 | delete stats.request; 92 | 93 | //Send to all brokers 94 | var self = this; 95 | console.log("MQTT sending to all brokers: " + topic); 96 | Object.keys(self.brokers).forEach(function(brokerId) { 97 | console.log("MQTT sending to broker " + brokerId + " on topic: " + topic); 98 | var broker = self.brokers[brokerId]; 99 | broker.publish( 100 | topic, //Topic 101 | JSON.stringify({ 102 | "id": site.id, 103 | "up": up_down === "up" ? true : false, 104 | "title": site.name + " is " + up_down + "!", 105 | "content": up_down === "up" ? "Woooo this site is back up!!" : "Oh crap the site is down!", 106 | "stats": stats 107 | }), //Message 108 | {qos:0,retain: false}, //Options 109 | function(){ //Callback 110 | //Message sent 111 | } 112 | ); 113 | 114 | console.log("Sent on broker: " + brokerId + " topic " + topic); 115 | 116 | }.bind(this)); 117 | 118 | //Callback with true 119 | callback(true); 120 | 121 | } 122 | -------------------------------------------------------------------------------- /lib/site.js: -------------------------------------------------------------------------------- 1 | var events = require('events'); 2 | var util = require('util'); 3 | var http = require('http'); 4 | var https = require('https'); 5 | var Url = require('url'); 6 | 7 | /** 8 | * Site instance 9 | * 10 | * @param config Object the site configuration 11 | * 12 | * @returns Site 13 | */ 14 | function Site(config, id) { 15 | events.EventEmitter.call(this); 16 | 17 | //The ID 18 | this.id = id; 19 | 20 | //The id of the client 21 | this.config = config; 22 | 23 | //The name 24 | this.name = config.name; 25 | 26 | //Type 27 | this.type = config.type; 28 | 29 | //Url 30 | this.url = config.url; 31 | 32 | //Content 33 | this.content = config.content !== undefined && config.content.length > 0 ? config.content : null; 34 | 35 | //Interval 36 | this.interval = config.interval; 37 | 38 | //Timeout 39 | this.timeout = config.timeout; 40 | 41 | //Last run 42 | this.lastRun = 0; 43 | 44 | //Is down 45 | this.down = false; 46 | 47 | //Previous down 48 | this.previousDown = true; 49 | 50 | } 51 | 52 | //Inherit event emitter 53 | util.inherits(Site, events.EventEmitter); 54 | 55 | //Export 56 | module.exports = Site; 57 | 58 | /** 59 | * If the site is currently down or not 60 | * 61 | * @returns void 62 | */ 63 | Site.prototype.wasDown = function() { 64 | return this.previousDown; 65 | } 66 | 67 | /** 68 | * If the site is currently down or not 69 | * 70 | * @returns void 71 | */ 72 | Site.prototype.isDown = function() { 73 | return this.down; 74 | } 75 | 76 | /** 77 | * Requires a check or not 78 | * 79 | * @returns void 80 | */ 81 | Site.prototype.requiresCheck = function() { 82 | if ((this.lastRun + (this.interval * 1000)) < new Date().getTime()) { 83 | return true; 84 | } 85 | return false; 86 | } 87 | 88 | 89 | /** 90 | * Check 91 | * 92 | * @returns void 93 | */ 94 | Site.prototype.check = function(callback) { 95 | //Make the request and then decide if the result was success or failure 96 | this.request(function(stats){ 97 | //Set the preview down status 98 | this.previousDown = this.down ? true : false; 99 | 100 | //Check status 101 | if (stats.statusCode !== 304 && (stats.statusCode < 200 || stats.statusCode > 299)) { 102 | //Not a good status code 103 | this.down = true; 104 | stats.notes = 'A non 304 or 2XX status code'; 105 | 106 | } else if (stats.contentMatched === false) { 107 | //Content didn't match 108 | this.down = true; 109 | 110 | } else { 111 | //Up 112 | this.down = false; 113 | } 114 | 115 | //Callback 116 | callback(stats); 117 | }.bind(this)); 118 | } 119 | 120 | 121 | /** 122 | * Send an error message to the user 123 | * 124 | * @param message string the message to show to the user 125 | * 126 | * @returns void 127 | */ 128 | Site.prototype.request = function(callback) { 129 | 130 | //Set the last run 131 | this.lastRun = new Date().getTime(); 132 | 133 | //Keep the request meta data with the request 134 | var stats = { 135 | startTime: new Date().getTime(), 136 | connectTime: 0, 137 | responseTime: 0, 138 | connectTimeout: false, 139 | connectFailed: false, 140 | contentMatched: null, 141 | request: null, 142 | response: null, 143 | body: null, 144 | statusCode: null, 145 | notes: null 146 | }; 147 | 148 | //Create the request 149 | var url = Url.parse(this.url); 150 | var httpModule = http; 151 | if (url.protocol.substr(0,5) === "https") { 152 | httpModule = https; 153 | url.port = url.port ? url.port : 443; 154 | } else { 155 | url.port = url.port ? url.port : 80; 156 | } 157 | 158 | var request = httpModule.request({ 159 | port: url.port, 160 | host: url.host, 161 | path: url.path, 162 | headers: { 163 | 'User-Agent': 'node-site-monitor/0.1.0', 164 | }, 165 | method: 'GET', 166 | agent: false 167 | 168 | }, function ( response ) { 169 | 170 | //Response 171 | stats.response = response; 172 | 173 | //Status code 174 | stats.statusCode = response.statusCode; 175 | 176 | //Set the response time 177 | stats.connectTime = (new Date().getTime() - stats.startTime) / 1000; 178 | 179 | //Clear the request timeout 180 | clearTimeout(connectTimeout); 181 | request.connectTimeout = null; 182 | 183 | //Collect the body 184 | var body = ''; 185 | response.on('data', function (chunk) { 186 | body += chunk.toString('utf8'); 187 | }); 188 | 189 | //Respond with whole body 190 | response.on('end', function () { 191 | stats.body = body; 192 | stats.responseTime = (new Date().getTime() - stats.startTime) / 1000; 193 | 194 | //Check the content matched 195 | if (this.content !== null) { 196 | if (stats.body.indexOf(this.content) >= 0) { 197 | stats.contentMatched = true; 198 | } else { 199 | stats.contentMatched = false; 200 | stats.notes = 'The site content did not contain the string: "' + this.content + '"'; 201 | } 202 | } 203 | 204 | callback(stats); 205 | }.bind(this)); 206 | 207 | }.bind(this)); 208 | 209 | stats.request = request; 210 | 211 | //Create the response timeout timer 212 | var connectTimeout = setTimeout(function() { 213 | request.abort(); 214 | stats.connectTimeout = true; 215 | stats.connectFailed = true; 216 | callback(stats); 217 | }.bind(this), this.timeout * 1000); 218 | 219 | //Attach error handlers 220 | request.on('error', function(err) { 221 | //Connection error 222 | stats.connectFailed = true; 223 | clearTimeout(connectTimeout); 224 | callback(stats); 225 | }); 226 | 227 | //End the request and start receiving the response 228 | request.end(); 229 | }; 230 | --------------------------------------------------------------------------------