webpagetest
Version:
WebPageTest API wrapper for NodeJS
184 lines (155 loc) • 4.5 kB
JavaScript
/**
* Copyright (c) 2013, Twitter Inc.
* Copyright (c) 2020, Google Inc.
* Copyright (c) 2020, Marcel Duran and other contributors
* Released under the MIT License
*/
var https = require('https'),
http = require('http'),
path = require('path'),
url = require('url'),
fs = require('fs'),
mapping = require('./mapping');
var help = {};
function normalizeRequest(uri) {
var idx,
parsed = url.parse(uri, true),
pathname = path.join(parsed.pathname, '/');
pathname = pathname.slice(1, pathname.length - 1);
idx = pathname.lastIndexOf('/');
idx = (idx < 0 ? pathname.length : idx);
return {
command: pathname.slice(0, idx).toLowerCase(),
id: pathname.slice(idx + 1),
query: parsed.query
};
}
function output(callback, err, data, info) {
var code = 200,
type = 'application/json;charset=utf-8';
if (err) {
data = {error: err};
code = err.code || code;
} else if (info) {
if (info.encoding === 'binary') {
data = new Buffer.from(data, 'binary');
type = info.type;
} else {
data = {type: info.type, data: data};
}
}
if (!(data instanceof Buffer)) {
try {
data = JSON.stringify(data, null, 2);
} catch (ex) {
data = JSON.stringify({
data: data.toString(),
error: ex.message
}, null, 2);
}
if (callback) {
type = 'application/javascript;charset=utf-8';
data = callback + '(' + data + ');';
}
}
this.writeHead(code, {'Content-Type': type});
this.end(data);
}
function buildHelp(defaultServer) {
Object.keys(mapping.commands).forEach(function eachCommand(key) {
var name,
options = {},
opts = [mapping.options.common],
method = mapping.commands[key];
if (key === 'listen') {
return;
}
name = [
key,
method.param ? [
'/',
method.optional ? '[' : '<',
method.param,
method.optional ? ']' : '>'
].join('') : '',
method.options ? '?[options]' : ''
].join('');
if (method.options) {
opts = opts.concat(method.options);
}
opts.forEach(function eachOptions(option) {
var nokey = method.nokey && method.nokey.indexOf(option) > -1;
Object.keys(option).forEach(function eachOption(optKey) {
var opt = option[optKey];
// ignore shorthands
if (optKey === opt.key) {
return;
}
options[[
opt.key && !nokey ? opt.key + ', ' : '',
optKey
].join('')] = optKey === 'server' ?
opt.info.replace('%s', defaultServer) : opt.info;
});
});
help[name] = {
description: method.info,
options: options
};
});
help = {help: help};
}
function route(req, res) {
var uri = normalizeRequest(req.url),
method = (mapping.commands[uri.command] || {}).name,
args = [],
callback = uri.query.callback || uri.query.cb;
if (method && method !== 'listen') {
// id, url or script
if (uri.id) {
args.push(decodeURIComponent(uri.id));
}
// options
args.push(mapping.setOptions(uri.command, uri.query));
// callback
args.push(output.bind(res, callback));
this[method].apply(this, args);
} else {
// help
output.call(res, callback, null, help);
}
}
module.exports = {
listen: function listen(local, options, callback) {
var server;
if (fs.existsSync(options.key) && fs.existsSync(options.cert)) {
server = https.createServer({
key: fs.readFileSync(path.resolve(process.cwd(), options.key)),
cert: fs.readFileSync(path.resolve(process.cwd(), options.cert))
}, route.bind(this));
} else {
server = http.createServer(route.bind(this));
}
server.on('error', function listenError(err) {
if (typeof callback === 'function') {
callback(err);
}
}).on('listening', function listenError() {
if (typeof callback === 'function') {
callback(undefined, {
server: server,
protocol: server instanceof https.Server ? 'https' : 'http',
hostname: local.hostname,
port: local.port,
url: url.format({
protocol: server instanceof https.Server ? 'https' : 'http',
hostname: local.hostname,
port: local.port
})
});
}
}).listen(local.port);
buildHelp(url.format(this.config.hostname));
return server;
}
};