mirror of
https://github.com/avwo/whistle.git
synced 2026-10-18 22:51:32 +08:00
186 lines
5.8 KiB
JavaScript
186 lines
5.8 KiB
JavaScript
var handleWebsocket = require('./https').handleWebsocket;
|
|
var hparser = require('hparser');
|
|
var net = require('net');
|
|
var Buffer = require('safe-buffer').Buffer;
|
|
var pluginMgr = require('./plugins');
|
|
var util = require('./util');
|
|
var config = require('./config');
|
|
|
|
var formatHeaders = hparser.formatHeaders;
|
|
var getRawHeaderNames = hparser.getRawHeaderNames;
|
|
var getRawHeaders = hparser.getRawHeaders;
|
|
var PLUGIN_PATH_RE =
|
|
/^\/(\.\.\.whistle-path\.5b6af7b9884e1165\.\.\.\/\/\/)?(whistle|plugin)\.([^/?#]+)\/?/;
|
|
|
|
function getPluginNameByReq(req, callback) {
|
|
if (!req) {
|
|
return callback();
|
|
}
|
|
var host = req.headers.host;
|
|
var index = host.indexOf(':');
|
|
var port = req.isHttps ? 443 : 80;
|
|
if (index !== -1) {
|
|
port = host.substring(index + 1);
|
|
host = host.substring(0, index);
|
|
}
|
|
var isUIPort = port == config.port || port == config.uiport;
|
|
var isWebUI = isUIPort && util.isLocalHost(host);
|
|
if (!isWebUI && (isWebUI = config.isWebUIHost(host)) && net.isIP(host)) {
|
|
isWebUI = isUIPort;
|
|
}
|
|
var pluginName;
|
|
var isPluginUrl;
|
|
var internalPath;
|
|
if (!config.pureProxy && PLUGIN_PATH_RE.test(req.url)) {
|
|
internalPath = RegExp['$&'];
|
|
req.isInternalUrl = !!RegExp.$1;
|
|
isPluginUrl = RegExp.$2 === 'plugin';
|
|
pluginName = RegExp.$3;
|
|
} else if (!isWebUI) {
|
|
pluginName =
|
|
(isUIPort || !net.isIP(host)) && config.getPluginNameByHost(host);
|
|
}
|
|
if (!pluginName) {
|
|
return callback();
|
|
}
|
|
var plugin = isPluginUrl
|
|
? pluginMgr.getPluginByName(pluginName)
|
|
: pluginMgr.getPlugin(pluginName + ':');
|
|
if (!isWebUI && !plugin) {
|
|
return callback();
|
|
}
|
|
if (internalPath) {
|
|
req.url = '/' + req.url.substring(internalPath.length);
|
|
}
|
|
pluginMgr.loadPlugin(plugin, function (err, ports) {
|
|
var uiPort = ports && ports.uiPort;
|
|
if (err || !uiPort) {
|
|
var msg = [
|
|
'HTTP/1.1',
|
|
err ? 500 : 404,
|
|
err ? 'Internal Server Error' : 'Not Found'
|
|
].join(' ');
|
|
err = err ? '<pre>' + util.encodeHtml(err) + '</pre>' : 'Not Found';
|
|
var length = Buffer.byteLength(err);
|
|
err = [
|
|
msg,
|
|
'Content-Type: text/html; charset=utf8',
|
|
'Content-Length: ' + length,
|
|
'\r\n',
|
|
err
|
|
].join('\r\n');
|
|
}
|
|
callback(err, uiPort);
|
|
});
|
|
}
|
|
|
|
function upgradeHandler(req, socket) {
|
|
util.removeSpecPath(req);
|
|
++util.proc.allWsRequests;
|
|
++util.proc.totalAllWsRequests;
|
|
var done, reqDestroyed, resDestroyed, resSocket;
|
|
function destroy(err) {
|
|
if (resSocket) {
|
|
if (!resDestroyed) {
|
|
resDestroyed = true;
|
|
resSocket.destroy(err);
|
|
}
|
|
} else if (!reqDestroyed) {
|
|
reqDestroyed = true;
|
|
socket.destroy(err);
|
|
}
|
|
if (socket.isLogRequests) {
|
|
--util.proc.wsRequests;
|
|
}
|
|
socket.isLogRequests = false;
|
|
if (!done) {
|
|
done = true;
|
|
--util.proc.allWsRequests;
|
|
}
|
|
}
|
|
var headers = req.headers;
|
|
util.onSocketEnd(socket, destroy);
|
|
util.addTunnelData(socket, headers);
|
|
socket._clientId = util.getComposerClientId(headers);
|
|
var getBuffer = function (method, newHeaders, path) {
|
|
var rawData = [
|
|
(method || 'GET') +
|
|
' ' +
|
|
(path || socket.url || req.url) +
|
|
' ' +
|
|
'HTTP/1.1'
|
|
];
|
|
newHeaders = formatHeaders(newHeaders || headers, req.rawHeaders);
|
|
rawData.push(getRawHeaders(newHeaders));
|
|
return Buffer.from(rawData.join('\r\n') + '\r\n\r\n');
|
|
};
|
|
var sniPlugin = headers[config.SNI_PLUGIN_HEADER];
|
|
if (sniPlugin) {
|
|
socket.sniPlugin = req.sniPlugin = sniPlugin;
|
|
delete headers[config.SNI_PLUGIN_HEADER];
|
|
}
|
|
req.isPluginReq = socket.isPluginReq = util.checkPluginReqOnce(req);
|
|
util.handleForwardedProps(req);
|
|
var isHttps = socket.isHttps || req.isHttps || !!headers[config.HTTPS_FIELD];
|
|
req.isHttps = socket.isHttps = isHttps;
|
|
// format headers
|
|
req.isWs = true;
|
|
socket.fullUrl = util.getFullUrl(req);
|
|
socket._fwdHost = req._fwdHost;
|
|
getPluginNameByReq(req, function (err, uiPort) {
|
|
if (err) {
|
|
return socket.write(err);
|
|
}
|
|
var clientInfo = util.parseClientInfo(req);
|
|
var clientIp = clientInfo[0] || util.getClientIp(req);
|
|
var clientPort = clientInfo[1] || util.getClientPort(req);
|
|
socket._disabledProxyRules = req._disabledProxyRules;
|
|
socket._remoteAddr = clientInfo[2] || util.getRemoteAddr(req);
|
|
socket._remotePort = clientInfo[3] || util.getRemotePort(req);
|
|
delete headers[config.CLIENT_PORT_HEAD];
|
|
if (uiPort) {
|
|
headers[config.CLIENT_IP_HEAD] = clientIp;
|
|
headers[config.CLIENT_PORT_HEAD] = clientPort;
|
|
headers['x-whistle-remote-address'] = socket._remoteAddr;
|
|
headers['x-whistle-remote-port'] = socket._remotePort;
|
|
headers[config.PLUGIN_HOOK_NAME_HEADER] = config.PLUGIN_HOOKS.UI;
|
|
socket.pause();
|
|
util.connect(
|
|
{
|
|
port: uiPort || config.port,
|
|
host: '127.0.0.1'
|
|
},
|
|
function (err, s) {
|
|
resSocket = s;
|
|
if (err || socket._hasError) {
|
|
return destroy(err);
|
|
}
|
|
resSocket.on('error', destroy);
|
|
resSocket.write(getBuffer(req.method));
|
|
socket.pipe(resSocket).pipe(socket);
|
|
socket.resume();
|
|
}
|
|
);
|
|
return;
|
|
}
|
|
// 不能放到上面,否则转发后的协议将丢失
|
|
delete headers[config.HTTPS_FIELD];
|
|
socket.rawHeaderNames = getRawHeaderNames(req.rawHeaders);
|
|
socket.headers = headers;
|
|
socket.url = req.url;
|
|
socket.fromTunnel = req.fromTunnel;
|
|
socket.fromComposer = req.fromComposer;
|
|
socket.enableXFF = req.enableXFF;
|
|
socket.isInternalUrl = req.isInternalUrl;
|
|
delete socket.headers[config.CLIENT_PORT_HEAD];
|
|
socket.getBuffer = function (newHeaders, path) {
|
|
return getBuffer(null, newHeaders, path);
|
|
};
|
|
handleWebsocket(socket, clientIp, clientPort);
|
|
});
|
|
}
|
|
|
|
module.exports = function (server) {
|
|
server.on('upgrade', upgradeHandler);
|
|
};
|