123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 |
- /*
- * Redownloading every files after Phantomas has finished
- * Checks weight and every kind of compression
- *
- */
- var debug = require('debug')('ylt:weightChecker');
- var request = require('request');
- var http = require('http');
- var async = require('async');
- var Q = require('Q');
- var zlib = require('zlib');
- var WeightChecker = function() {
- var MAX_PARALLEL_DOWNLOADS = 10;
- var REQUEST_TIMEOUT = 10000; // 10 seconds
- function recheckAllFiles(data) {
- var deferred = Q.defer();
- var requestsList = JSON.parse(data.toolsResults.phantomas.offenders.requestsList);
- delete data.toolsResults.phantomas.metrics.requestsList;
- delete data.toolsResults.phantomas.offenders.requestsList;
- // Transform every request into a download function with a callback when done
- var redownloadList = requestsList.map(function(entry) {
- return function(callback) {
- redownloadEntry(entry, callback);
- };
- });
- // Lanch all redownload functions and wait for completion
- async.parallelLimit(redownloadList, MAX_PARALLEL_DOWNLOADS, function(err, results) {
- if (err) {
- debug(err);
- deferred.reject(err);
- } else {
- debug('All files checked');
-
- var metrics = {};
- var offenders = {};
- // Total weight
- offenders.totalWeight = listRequestWeight(results);
- metrics.totalWeight = offenders.totalWeight.totalWeight;
- data.toolsResults.weightChecker = {
- metrics: metrics,
- offenders: offenders
- };
- deferred.resolve(data);
- }
- });
- return deferred.promise;
- }
- function listRequestWeight(requests) {
- var results = {
- totalWeight: 0,
- byType: {
- html: {
- totalWeight: 0,
- requests: []
- },
- css: {
- totalWeight: 0,
- requests: []
- },
- js: {
- totalWeight: 0,
- requests: []
- },
- json: {
- totalWeight: 0,
- requests: []
- },
- image: {
- totalWeight: 0,
- requests: []
- },
- video: {
- totalWeight: 0,
- requests: []
- },
- webfont: {
- totalWeight: 0,
- requests: []
- },
- other: {
- totalWeight: 0,
- requests: []
- }
- }
- };
- requests.forEach(function(req) {
- var weight = (typeof req.weightCheck.bodySize === 'number') ? req.weightCheck.bodySize + req.weightCheck.headersSize : req.contentLength;
- var type = req.type || 'other';
- results.totalWeight += weight;
- results.byType[type].totalWeight += weight;
- results.byType[type].requests.push({
- url: req.url,
- weight: weight
- });
- });
- return results;
- }
- function redownloadEntry(entry, callback) {
-
- function onError(message) {
- debug('Could not download %s Error: %s', entry.url, message);
- entry.weightCheck = {
- message: message
- };
- setImmediate(function() {
- callback(null, entry);
- });
- }
- if (entry.method !== 'GET') {
- onError('only downloading GET');
- return;
- }
- if (entry.status !== 200) {
- onError('only downloading requests with status code 200');
- return;
- }
- debug('Downloading %s', entry.url);
- // Always add a gzip header before sending, in case the server listens to it
- var reqHeaders = entry.requestHeaders;
- reqHeaders['Accept-Encoding'] = 'gzip, deflate';
- var requestOptions = {
- method: entry.method,
- url: entry.url,
- headers: reqHeaders,
- timeout: REQUEST_TIMEOUT
- };
- download(requestOptions, function(error, result) {
- if (error) {
- if (error.code === 'ETIMEDOUT') {
- onError('timeout after ' + REQUEST_TIMEOUT + 'ms');
- } else {
- onError('error while downloading: ' + error.code);
- }
- return;
- }
-
- debug('%s downloaded correctly', entry.url);
- entry.weightCheck = result;
- callback(null, entry);
- });
- }
- // Inspired by https://github.com/cvan/fastHAR-api/blob/10cec585/app.js
- function download(requestOptions, callback) {
- var statusCode;
- request(requestOptions)
- .on('response', function(res) {
-
- // Raw headers were added in NodeJS v0.12
- // (https://github.com/joyent/node/issues/4844), but let's
- // reconstruct them for backwards compatibility.
- var rawHeaders = ('HTTP/' + res.httpVersion + ' ' + res.statusCode +
- ' ' + http.STATUS_CODES[res.statusCode] + '\r\n');
- Object.keys(res.headers).forEach(function(headerKey) {
- rawHeaders += headerKey + ': ' + res.headers[headerKey] + '\r\n';
- });
- rawHeaders += '\r\n';
- var uncompressedSize = 0; // size after uncompression
- var bodySize = 0; // bytes size over the wire
- var body = ''; // plain text body (after uncompressing gzip/deflate)
- var isCompressed = false;
- function tally() {
- if (statusCode !== 200) {
- callback({code: statusCode});
- return;
- }
- var result = {
- body: body,
- headersSize: Buffer.byteLength(rawHeaders, 'utf8'),
- bodySize: bodySize,
- isCompressed: isCompressed,
- uncompressedSize: uncompressedSize
- };
- callback(null, result);
- }
- switch (res.headers['content-encoding']) {
- case 'gzip':
- var gzip = zlib.createGunzip();
- gzip.on('data', function (data) {
- body += data;
- uncompressedSize += data.length;
- }).on('end', function () {
- isCompressed = true;
- tally();
- });
- res.on('data', function (data) {
- bodySize += data.length;
- }).pipe(gzip);
- break;
- case 'deflate':
- var deflate = zlib.createInflate();
- deflate.on('data', function (data) {
- body += data;
- uncompressedSize += data.length;
- }).on('end', function () {
- isCompressed = true;
- tally();
- });
- res.on('data', function (data) {
- bodySize += data.length;
- }).pipe(deflate);
- break;
- default:
- res.on('data', function (data) {
- body += data;
- uncompressedSize += data.length;
- bodySize += data.length;
- }).on('end', function () {
- tally();
- });
- break;
- }
- })
- .on('response', function(response) {
- statusCode = response.statusCode;
- })
- .on('error', function(err) {
- callback(err);
- });
- }
- return {
- recheckAllFiles: recheckAllFiles,
- listRequestWeight: listRequestWeight,
- redownloadEntry: redownloadEntry,
- download: download
- };
- };
- module.exports = new WeightChecker();
|