UNPKG

whistle

Version:

HTTP, HTTP2, HTTPS, Websocket debugging proxy

262 lines (247 loc) 7.1 kB
var PipeStream = require('pipestream'); var util = require('./util'); var socketMgr = require('./socket-mgr'); var config = require('./config'); var HTTPS_RE = /^https:/i; var clientIpKey = config.CLIENT_IP_HEADER; function addErrorEvents(req, res) { ++util.proc.allHttpRequests; ++util.proc.totalAllHttpRequests; var finished; var countdown = function () { if (req.isLogRequests) { --util.proc.httpRequests; req._hasClosed = true; req.emit('_closed'); } req.isLogRequests = false; if (!finished) { finished = true; --util.proc.allHttpRequests; } }; var clientReq; var done; req .on('dest', function (_req) { clientReq = _req; if (!req.noReqBody) { clientReq.on('error', abort); } }) .on('error', abort); res .on('src', function (_res) { if (clientReq && req.noReqBody) { clientReq.on('error', abort); } _res.on('error', abort); }) .on('error', abort) .once('close', abort) .once('finish', countdown); function abort(err) { if (clientReq === false) { return; } countdown(); req._hasError = true; clientReq = req._clientReq || clientReq; if (clientReq) { if (clientReq.destroy) { clientReq.destroy(); } else if (clientReq.abort) { clientReq.abort(); } clientReq = false; } if (done) { return; } done = true; if (req.customParser) { socketMgr.removeContext(req); socketMgr.removePending(req); } if ( req._hasRespond || res._headerSent || !res.writable || (err && err.code === 'ERR_WHISTLE_ABORTED') ) { if (!finished) { res.emit('error', new Error('Aborted')); } return res.destroy(); } err = util.getErrorStack(err || 'Closed'); res.response(util.wrapGatewayError(err)); } } function addTransforms(req, res) { var reqIconvPipeStream, resIconvPipeStream, svrRes, initedResTransform; req.addTextTransform = function (transform) { if (!reqIconvPipeStream) { reqIconvPipeStream = util.getPipeIconvStream(req.headers); initReqZipTransform().add(reqIconvPipeStream); } reqIconvPipeStream.add(transform); return req; }; req.addZipTransform = function (transform, head, tail) { initReqZipTransform()[head ? 'addHead' : tail ? 'addTail' : 'add']( transform ); return req; }; function initReqZipTransform() { if (!req._needGunzip) { delete req.headers['content-length']; req._needGunzip = true; } return req; } function initResZipTransform() { if (!initedResTransform) { initedResTransform = true; res._needGunzip = true; removeContentLength(); res.add(function (src, next) { if (resIconvPipeStream) { var pipeIconvStream = util.getPipeIconvStream(res.headers); pipeIconvStream.add(resIconvPipeStream); next(src.pipe(pipeIconvStream)); } else { next(src); } }); } } res.addZipTransform = function (transform, head, tail) { initResZipTransform(); res[head ? 'addHead' : tail ? 'addTail' : 'add'](transform); return res; }; res.addTextTransform = function (transform, head, tail) { if (!resIconvPipeStream) { resIconvPipeStream = new PipeStream(); initResZipTransform(); } resIconvPipeStream[head ? 'addHead' : tail ? 'addTail' : 'add'](transform); return res; }; res.on('src', function (_res) { svrRes = _res; removeContentLength(); }); function removeContentLength() { if (svrRes && res._needGunzip) { delete svrRes.headers['content-length']; } } } module.exports = function (req, res, next) { util.removeSpecPath(req); PipeStream.wrapSrc(req); PipeStream.wrapDest(res); addTransforms(req, res); addErrorEvents(req, res); req.isPluginReq = util.checkPluginReqOnce(req); var headers = req.headers; var socket = req.socket || {}; var clientInfo = util.parseClientInfo(req); var clientIp = clientInfo[0] || util.getForwardedFor(headers); req._remoteAddr = clientInfo[2] || util.getRemoteAddr(req); req._remotePort = clientInfo[3] || util.getRemotePort(req); if (clientIp && util.isLocalAddress(clientIp)) { delete headers[clientIpKey]; clientIp = null; } if (!socket[clientIpKey]) { socket[clientIpKey] = clientIp || util.getClientIp(req); } req.clientIp = clientIp = clientIp || socket[clientIpKey]; req.method = util.getMethod(req.method); req._clientId = util.getComposerClientId(headers); var clientPort = clientInfo[1] || headers[config.CLIENT_PORT_HEADER]; delete headers[config.CLIENT_PORT_HEADER]; if (!(clientPort > 0)) { clientPort = null; } if (!socket[config.CLIENT_PORT_HEADER]) { socket[config.CLIENT_PORT_HEADER] = clientPort || socket.remotePort; } req.clientPort = clientPort = clientPort || socket[config.CLIENT_PORT_HEADER]; util.handleForwardedProps(req); var isHttps = req.socket.isHttps || req.isHttps || headers[config.HTTPS_FIELD]; if (isHttps) { req.isHttps = true; delete headers[config.HTTPS_FIELD]; delete headers[util.HTTPS_PROTO_HEADER]; } var sniPlugin = headers[util.SNI_PLUGIN_HEADER]; if (sniPlugin) { req.sniPlugin = sniPlugin; delete headers[util.SNI_PLUGIN_HEADER]; } if (!req.isHttps && HTTPS_RE.test(req.url)) { req.isHttps = true; } util.addTunnelData(socket, headers); var alpn = headers[config.ALPN_PROTOCOL_HEADER]; if (alpn) { var index = typeof alpn === 'string' ? alpn.indexOf('.') : -1; if (index > 0) { req._alpn = alpn; req._h2ReqId = alpn.substring(index + 1); alpn = alpn.substring(0, index); // 如果保留 h2 session,则删除默认的 Timeout 逻辑 req.setTimeout(0); } if (alpn === 'httpH2') { req.isH2 = !req.isHttps; } else if (alpn === 'httpsH2') { req.isH2 = req.isHttps; } else if (req.isHttps || req.isPluginReq) { req.isH2 = true; } req.rawHeaders = []; delete headers[config.ALPN_PROTOCOL_HEADER]; delete headers.connection; } var conn = headers['proxy-connection']; if (conn) { if (headers.connection) { headers.connection = conn; } delete headers['proxy-connection']; } res.response = function (_res) { if (req._hasRespond) { return; } _res.once('readable', function() { req._ttfb = Date.now(); req.setTTFB && req.setTTFB(); }); req._hasRespond = true; if (_res.realUrl) { req.realUrl = res.realUrl = _res.realUrl; } res.headers = req.resHeaders = _res.headers; res.statusCode = req.statusCode = _res.statusCode = util.getStatusCode(_res.statusCode); util.drain(req, function () { if (util.getStatusCode(_res.statusCode)) { res.src(_res); res.writeHead(_res.statusCode, _res.headers); } else { util.sendStatusCodeError(res, _res); } }); }; next(); };