UNPKG

whistle

Version:

HTTP, HTTP2, HTTPS, Websocket debugging proxy

184 lines (179 loc) 6.07 kB
var handleWebsocket = require('./https').handleWebsocket; var hparser = require('hparser'); var net = require('net'); var pluginMgr = require('./plugins'); var util = require('./util'); var config = require('./config'); var common = require('./util/common'); var formatHeaders = hparser.formatHeaders; var getRawHeaderNames = hparser.getRawHeaderNames; var getRawHeaders = hparser.getRawHeaders; var PLUGIN_PATH_RE = /^\/(\.\.\.whistle-path\.5b6af7b9884e1165\.\.\.\/\/\/)?(whistle|plugin)\.([^/?#]+)\/?/; function getPluginNameByReq(req, socket, 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 = config.isWebUIHost(host) ? (!net.isIP(host) || isUIPort) : (isUIPort && util.isLocalHost(host)); 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); } else if (!req.url.indexOf('/service/')) { req.isHttps = true; req.headers[util.REAL_HOST_HEADER] = common.SERVICE_HOST; req._isInternalReq = true; socket.fullUrl = req.fullUrl = util.getFullUrl(req); socket.isPluginReq = true; socket._isPureInternalReq = true; return callback(); } 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); if (newHeaders = getRawHeaders(newHeaders)) { rawData += '\r\n' + newHeaders; } return Buffer.from(rawData + '\r\n\r\n'); }; var sniPlugin = headers[util.SNI_PLUGIN_HEADER]; if (sniPlugin) { socket.sniPlugin = req.sniPlugin = sniPlugin; delete headers[util.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; socket.headers = headers; getPluginNameByReq(req, socket, 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.clientIp = clientIp; socket.clientPort = clientPort; socket._remoteAddr = clientInfo[2] || util.getRemoteAddr(req); socket._remotePort = clientInfo[3] || util.getRemotePort(req); delete headers[config.CLIENT_PORT_HEADER]; if (uiPort) { util.addClientInfo(socket); 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.url = req.url; socket.fromTunnel = req.fromTunnel; socket.fromComposer = req.fromComposer; socket.enableXFF = req.enableXFF; socket._isInternalReq = req._isInternalReq; socket.isInternalUrl = req.isInternalUrl; delete socket.headers[config.CLIENT_PORT_HEADER]; socket.getBuffer = function (newHeaders, path) { return getBuffer(null, newHeaders, path); }; handleWebsocket(socket, clientIp, clientPort); }); } module.exports = function (server) { server.on('upgrade', upgradeHandler); };