whistle
Version:
HTTP, HTTP2, HTTPS, Websocket debugging proxy
213 lines (203 loc) • 6.34 kB
JavaScript
var path = require('path');
var fs = require('fs');
var http = require('http');
var url = require('url');
var Buffer = require('safe-buffer').Buffer;
var util = require('./util');
var importModule = require('./import');
var pkg = require('../package.json');
var getHomedir = require('../lib/util/common').getHomedir;
var isRunning = util.isRunning;
var error = util.error;
var warn = util.warn;
var info = util.info;
var readConfig = util.readConfig;
var MAX_RULES_LEN = 1024 * 256;
var DEFAULT_OPTIONS = { host: '127.0.0.1', port: 8899 };
var options;
function showStartWhistleTips(storage, isClient) {
if (isClient) {
error('No running whistle client, please install and start the latest whistle client: https://github.com/avwo/whistle-client');
} else {
error('No running whistle, execute `w2 start' + (storage ? ' -S ' + storage : '') + '` to start whistle on the cli.');
}
}
function handleRules(filepath, callback, port) {
importModule(filepath, function(getRules) {
if (typeof getRules !== 'function') {
return callback(getRules);
}
var opts = {
port: port,
existsPlugin: existsPlugin
};
if (options && options.host) {
opts.host = options.host;
}
getRules(callback, opts);
});
}
function getString(str) {
return typeof str !== 'string' ? '' : str.trim();
}
function existsPlugin(name) {
if (!name || typeof name !== 'string') {
return false;
}
var pluginPaths = require('../lib/plugins/module-paths').getPaths();
for (var i = 0, len = pluginPaths.length; i < len; i++) {
try {
if (fs.statSync(path.join(pluginPaths[i], name)).isDirectory()) {
return true;
}
} catch(e) {}
}
return false;
}
function getBody(res, callback) {
var resBody = '';
res.setEncoding('utf8');
res.on('data', function(data) {
resBody += data;
});
res.on('end', function() {
if (res.statusCode != 200) {
callback(resBody || 'response ' + res.statusCode + ' error');
} else {
callback(null, JSON.parse(resBody));
}
});
}
var reqOptions;
function request(body, callback) {
if (!reqOptions) {
reqOptions = url.parse('http://' + util.joinIpPort(options.host || '127.0.0.1', options.port) + '/cgi-bin/rules/project');
reqOptions.headers = {
'content-type': 'application/x-www-form-urlencoded'
};
if (options.specialAuth) {
reqOptions.headers['x-whistle-special-auth'] = options.specialAuth;
}
reqOptions.method = 'POST';
if (options.username || options.password) {
var auth = [options.username || '', options.password || ''].join(':');
reqOptions.headers.authorization = 'Basic ' + new Buffer.from(auth).toString('base64');
}
}
var req = http.request(reqOptions, function(res) {
getBody(res, function(err, data) {
if (err) {
throw err;
}
callback(data);
});
});
// 不处理错误,直接抛出终止进程
req.end(body);
}
function checkDefault(running, storage, isClient, callback) {
if (running) {
return callback();
}
if (isClient) {
return callback(true);
}
var execCallback = function(err) {
callback && callback(err);
callback = null;
};
var req = http.get('http://' + DEFAULT_OPTIONS.host + ':' + DEFAULT_OPTIONS.port + '/cgi-bin/status', function(res) {
res.on('error', execCallback);
getBody(res, function(err, data) {
if (err || !data || data.name !== pkg.name || data.storage !== storage) {
return execCallback(true);
}
callback(null, DEFAULT_OPTIONS.port);
});
});
req.on('error', execCallback);
req.end();
}
function readClientConfig() {
var procPath = path.join(getHomedir(), '.whistle_client.pid');
try {
var info = fs.readFileSync(procPath, { encoding: 'utf-8' }).split(',');
if (info.length === 4) {
return {
pid: info[0],
options: {
host: info[1],
port: info[2],
specialAuth: info[3]
}
};
}
} catch (e) {}
}
module.exports = function(filepath, storage, force, isClient) {
var config;
var dir = '';
if (isClient) {
storage = '';
config = readClientConfig() || '';
} else {
storage = storage || '';
dir = encodeURIComponent(storage);
config = readConfig(dir) || '';
if (config.options) {
delete config.options.specialAuth;
}
}
options = config.options || '';
isRunning(options && config.pid, function(running) {
checkDefault(running, dir, isClient, function(err, port) {
if (err) {
return showStartWhistleTips(storage, isClient);
}
filepath = path.resolve(filepath || '.whistle.js');
if (port) {
options = DEFAULT_OPTIONS;
} else {
port = options.port = options.port > 0 ? options.port : pkg.port;
}
handleRules(filepath, function(result) {
if (!result) {
error('The name and rules cannot be empty.');
return;
}
var name = getString(result.name);
if (!name || name.length > 64) {
error('The name cannot be empty and the length cannot exceed 64 characters.');
return;
}
var rules = getString(result.rules);
if (rules.length > MAX_RULES_LEN) {
error('The rules cannot be empty and the size cannot exceed 256k.');
return;
}
var groupName = getString(result.groupName) || getString(result.group);
var setRules = function() {
var body = [
'name=' + encodeURIComponent(name),
'rules=' + encodeURIComponent(rules),
'groupName=' + encodeURIComponent(groupName.trim())
].join('&');
request(body, function() {
info('Setting whistle' + (isClient ? ' client' : '') + ' (' + util.joinIpPort(options.host || '127.0.0.1', port) + ') rules successful.');
});
};
if (force) {
return setRules();
}
request('name=' + encodeURIComponent(name) + '&enable=1&top=1', function(data) {
if (data.rules) {
info('Successfully enabled.');
warn('Warning: The rule already exists, to override the content, add CLI option --force.');
return;
}
setRules();
});
}, port);
});
});
};