├── .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 |
--------------------------------------------------------------------------------