Skip to content

Commit

Permalink
Merge pull request #124 from Munter/fix/preconnect
Browse files Browse the repository at this point in the history
Fix checking of <link rel=preconnect href=...>
  • Loading branch information
Munter authored Dec 17, 2017
2 parents 576a34e + aaa4fce commit a8c0473
Showing 1 changed file with 120 additions and 42 deletions.
162 changes: 120 additions & 42 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 @@ -40,7 +43,9 @@ module.exports = function (options) {
return false;
}

var relationTypeExclusions = [];
var relationTypeExclusions = [
'HtmlPreconnectLink'
];

if (!options.recursive) {
relationTypeExclusions.push('HtmlAnchor');
Expand All @@ -55,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 @@ -156,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 @@ -192,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 @@ -213,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 @@ -316,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 @@ -346,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 a8c0473

Please sign in to comment.