Skip to content

Commit

Permalink
Test HtmlPreconnectLink relations separately from all other ones
Browse files Browse the repository at this point in the history
Just attempt to connect to the host:port (with tls if https)
  • Loading branch information
papandreou authored and Munter committed Dec 17, 2017
1 parent 6b165b6 commit aaa4fce
Showing 1 changed file with 117 additions and 41 deletions.
158 changes: 117 additions & 41 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ var request = require('request');
var version = require('../package.json').version;
var relationDebugDescription = require('./relationDebugDescription');
var prettyBytes = require('pretty-bytes');
var urlModule = require('url');
var net = require('net');
var tls = require('tls');

var checkFragments = require('./transforms/checkFragments');

Expand Down Expand Up @@ -57,67 +60,77 @@ module.exports = function (options) {
crossorigin: false
};

function logHttpResult(status, url, redirects, relations) {
function logResult(status, url, redirects, relations) {
redirects = redirects || [];
relations = relations || [];

var at = _.uniq(relations.map(relationDebugDescription)).join('\n ');
var skip = shouldSkip(url);

if (status !== 200) {
var invalidStatusReport = {
if (status === false) {
errorCount += 1;
t.push(null, {
ok: false,
skip: skip,
name: 'should accept connections',
operator: 'error',
expected: ['connection accepted', url].join(' '),
actual: [status, url].join(' '),
at: at
});
} else if (status !== 200 && status !== true) {
errorCount += 1;
t.push(null, {
ok: false,
skip: skip,
name: 'should respond with HTTP status 200',
operator: 'error',
expected: [200, url].join(' '),
actual: [status, url].join(' '),
at: at
};

errorCount += 1;
t.push(null, invalidStatusReport);
});
}

var report = {
ok: true,
skip: skip,
name: 'URI should have no redirects - ' + url,
operator: 'noRedirects',
expected: [200, url].join(' '),
at: at
};
if (typeof status !== 'boolean') {
var report = {
ok: true,
skip: skip,
name: 'URI should have no redirects - ' + url,
operator: 'noRedirects',
expected: [200, url].join(' '),
at: at
};

if (redirects.length) {
var log = [].concat({ redirectUri: url }, redirects).map(function (item, idx, arr) {
if (arr[idx + 1]) {
item.statusCode = arr[idx + 1].statusCode;
} else {
item.statusCode = 200;
}
if (redirects.length) {
var log = [].concat({ redirectUri: url }, redirects).map(function (item, idx, arr) {
if (arr[idx + 1]) {
item.statusCode = arr[idx + 1].statusCode;
} else {
item.statusCode = 200;
}

return item;
});
return item;
});

var logLine = log.map(function (redirect) {
return [redirect.statusCode, redirect.redirectUri].join(' ');
}).join(' --> ');
var logLine = log.map(function (redirect) {
return [redirect.statusCode, redirect.redirectUri].join(' ');
}).join(' --> ');

report.actual = logLine;
report.actual = logLine;

if (log[0].statusCode !== 302) {
report.ok = false;
if (log[0].statusCode !== 302) {
report.ok = false;
}
} else {
report.actual = [status, url].join(' ');
}
} else {
report.actual = [status, url].join(' ');
}

if (!report.ok) {
errorCount += 1;
if (!report.ok) {
errorCount += 1;
}
t.push(null, report);
}

t.push(null, report);

// Check for mixed-content warnings
var secureSourceRelations = relations.filter(function (relation) {
return relation.type !== 'HtmlAnchor' && relation.from.nonInlineAncestor.url.indexOf('https:') === 0;
Expand Down Expand Up @@ -158,6 +171,42 @@ module.exports = function (options) {
}
}

function tryConnect(url, relations, attempt) {
var urlObj = urlModule.parse(url);
var hostname = urlObj.hostname;
var isTls = urlObj.protocol === 'https:';
var port = urlObj.port ? parseInt(urlObj.port, 10) : (isTls ? 443 : 80);
attempt = attempt || 1;
return function (callback) {
(isTls ? tls : net).connect(port, hostname, function () {
logResult(true, url, undefined, relations);

callback(undefined, true);
}).on('error', function (error) {
var code = error.code;
var status = false;
if (code) {
// Some servers send responses that request apparently handles badly when using the HEAD method...
if (code === 'HPE_INVALID_CONSTANT' && attempt === 1) {
return tryConnect(url, relations, attempt + 1)(callback);
}

if (code === 'ENOTFOUND') {
status = 'DNS Missing';
} else {
status = code;
}
} else {
status = 'Unknown error';
}

logResult(status, url, undefined, relations);

callback(undefined, false);
});
};
}

function httpStatus(url, relations, attempt) {
attempt = attempt || 1;

Expand Down Expand Up @@ -194,7 +243,7 @@ module.exports = function (options) {
status = 'Unknown error';
}

logHttpResult(status, url, undefined, relations);
logResult(status, url, undefined, relations);

callback(undefined, status);
} else {
Expand All @@ -215,7 +264,7 @@ module.exports = function (options) {
redirects = res.request.redirects || [];
var firstRedirectStatus = redirects[0] && redirects[0].statusCode;

logHttpResult(status, url, redirects, relations);
logResult(status, url, redirects, relations);

callback(undefined, firstRedirectStatus || status);
}
Expand Down Expand Up @@ -318,12 +367,13 @@ module.exports = function (options) {
concurrency: options.concurrency || 100
})
.queue(checkFragments(t))
.queue(function (assetGraph, callback) {
.queue(function checkUrls(assetGraph, callback) {
var hrefMap = {};

hrefMap = _.groupBy(assetGraph.findRelations({
crossorigin: true,
href: /^(?:https?:)?\/\//
href: /^(?:https?:)?\/\//,
type: query.not('HtmlPreconnectLink')
}, true), function (relation) {
var url = relation.to.url.replace(/#.*$/, '');

Expand All @@ -348,6 +398,32 @@ module.exports = function (options) {
}
);
})
.queue(function checkPreconnects(assetGraph, callback) {
var hrefMap = {};

hrefMap = _.groupBy(assetGraph.findRelations({
crossorigin: true,
href: /^(?:https?:)?\/\//,
type: 'HtmlPreconnectLink'
}, true), function (relation) {
var url = relation.to.url.replace(/#.*$/, '');

return url;
});

var hrefs = Object.keys(hrefMap);

t.push({
name: 'Connecting to ' + hrefs.length + ' hosts (checking <link rel="preconnect" href="...">'
});
async.parallelLimit(
hrefs.map(function (url) {
return tryConnect(url, hrefMap[url]);
}),
20,
callback
);
})
// .writeStatsToStderr()
.run(function () {
var results = t.close();
Expand Down

0 comments on commit aaa4fce

Please sign in to comment.