This commit is contained in:
2025-09-19 14:25:20 +08:00
parent 269893a435
commit fbf3f77229
24949 changed files with 2839404 additions and 0 deletions

View File

@@ -0,0 +1,194 @@
var httpNative = require('http'),
httpsNative = require('https'),
web_o = require('./web-outgoing'),
common = require('../common'),
followRedirects = require('follow-redirects');
web_o = Object.keys(web_o).map(function(pass) {
return web_o[pass];
});
var nativeAgents = { http: httpNative, https: httpsNative };
/*!
* Array of passes.
*
* A `pass` is just a function that is executed on `req, res, options`
* so that you can easily add new checks while still keeping the base
* flexible.
*/
module.exports = {
/**
* Sets `content-length` to '0' if request is of DELETE type.
*
* @param {ClientRequest} Req Request object
* @param {IncomingMessage} Res Response object
* @param {Object} Options Config object passed to the proxy
*
* @api private
*/
deleteLength: function deleteLength(req, res, options) {
if((req.method === 'DELETE' || req.method === 'OPTIONS')
&& !req.headers['content-length']) {
req.headers['content-length'] = '0';
delete req.headers['transfer-encoding'];
}
},
/**
* Sets timeout in request socket if it was specified in options.
*
* @param {ClientRequest} Req Request object
* @param {IncomingMessage} Res Response object
* @param {Object} Options Config object passed to the proxy
*
* @api private
*/
timeout: function timeout(req, res, options) {
if(options.timeout) {
req.socket.setTimeout(options.timeout);
}
},
/**
* Sets `x-forwarded-*` headers if specified in config.
*
* @param {ClientRequest} Req Request object
* @param {IncomingMessage} Res Response object
* @param {Object} Options Config object passed to the proxy
*
* @api private
*/
XHeaders: function XHeaders(req, res, options) {
if(!options.xfwd) return;
var encrypted = req.isSpdy || common.hasEncryptedConnection(req);
var values = {
for : req.connection.remoteAddress || req.socket.remoteAddress,
port : common.getPort(req),
proto: encrypted ? 'https' : 'http'
};
['for', 'port', 'proto'].forEach(function(header) {
req.headers['x-forwarded-' + header] =
(req.headers['x-forwarded-' + header] || '') +
(req.headers['x-forwarded-' + header] ? ',' : '') +
values[header];
});
req.headers['x-forwarded-host'] = req.headers['x-forwarded-host'] || req.headers['host'] || '';
},
/**
* Does the actual proxying. If `forward` is enabled fires up
* a ForwardStream, same happens for ProxyStream. The request
* just dies otherwise.
*
* @param {ClientRequest} Req Request object
* @param {IncomingMessage} Res Response object
* @param {Object} Options Config object passed to the proxy
*
* @api private
*/
stream: function stream(req, res, options, _, server, clb) {
// And we begin!
server.emit('start', req, res, options.target || options.forward);
var agents = options.followRedirects ? followRedirects : nativeAgents;
var http = agents.http;
var https = agents.https;
if(options.forward) {
// If forward enable, so just pipe the request
var forwardReq = (options.forward.protocol === 'https:' ? https : http).request(
common.setupOutgoing(options.ssl || {}, options, req, 'forward')
);
// error handler (e.g. ECONNRESET, ECONNREFUSED)
// Handle errors on incoming request as well as it makes sense to
var forwardError = createErrorHandler(forwardReq, options.forward);
req.on('error', forwardError);
forwardReq.on('error', forwardError);
(options.buffer || req).pipe(forwardReq);
if(!options.target) { return res.end(); }
}
// Request initalization
var proxyReq = (options.target.protocol === 'https:' ? https : http).request(
common.setupOutgoing(options.ssl || {}, options, req)
);
// Enable developers to modify the proxyReq before headers are sent
proxyReq.on('socket', function(socket) {
if(server && !proxyReq.getHeader('expect')) {
server.emit('proxyReq', proxyReq, req, res, options);
}
});
// allow outgoing socket to timeout so that we could
// show an error page at the initial request
if(options.proxyTimeout) {
proxyReq.setTimeout(options.proxyTimeout, function() {
proxyReq.abort();
});
}
// Ensure we abort proxy if request is aborted
req.on('aborted', function () {
proxyReq.abort();
});
// handle errors in proxy and incoming request, just like for forward proxy
var proxyError = createErrorHandler(proxyReq, options.target);
req.on('error', proxyError);
proxyReq.on('error', proxyError);
function createErrorHandler(proxyReq, url) {
return function proxyError(err) {
if (req.socket.destroyed && err.code === 'ECONNRESET') {
server.emit('econnreset', err, req, res, url);
return proxyReq.abort();
}
if (clb) {
clb(err, req, res, url);
} else {
server.emit('error', err, req, res, url);
}
}
}
(options.buffer || req).pipe(proxyReq);
proxyReq.on('response', function(proxyRes) {
if(server) { server.emit('proxyRes', proxyRes, req, res); }
if(!res.headersSent && !options.selfHandleResponse) {
for(var i=0; i < web_o.length; i++) {
if(web_o[i](req, res, proxyRes, options)) { break; }
}
}
if (!res.finished) {
// Allow us to listen when the proxy has completed
proxyRes.on('end', function () {
if (server) server.emit('end', req, res, proxyRes);
});
// We pipe to the response unless its expected to be handled by the user
if (!options.selfHandleResponse) proxyRes.pipe(res);
} else {
if (server) server.emit('end', req, res, proxyRes);
}
});
}
};

View File

@@ -0,0 +1,147 @@
var url = require('url'),
common = require('../common');
var redirectRegex = /^201|30(1|2|7|8)$/;
/*!
* Array of passes.
*
* A `pass` is just a function that is executed on `req, res, options`
* so that you can easily add new checks while still keeping the base
* flexible.
*/
module.exports = { // <--
/**
* If is a HTTP 1.0 request, remove chunk headers
*
* @param {ClientRequest} Req Request object
* @param {IncomingMessage} Res Response object
* @param {proxyResponse} Res Response object from the proxy request
*
* @api private
*/
removeChunked: function removeChunked(req, res, proxyRes) {
if (req.httpVersion === '1.0') {
delete proxyRes.headers['transfer-encoding'];
}
},
/**
* If is a HTTP 1.0 request, set the correct connection header
* or if connection header not present, then use `keep-alive`
*
* @param {ClientRequest} Req Request object
* @param {IncomingMessage} Res Response object
* @param {proxyResponse} Res Response object from the proxy request
*
* @api private
*/
setConnection: function setConnection(req, res, proxyRes) {
if (req.httpVersion === '1.0') {
proxyRes.headers.connection = req.headers.connection || 'close';
} else if (req.httpVersion !== '2.0' && !proxyRes.headers.connection) {
proxyRes.headers.connection = req.headers.connection || 'keep-alive';
}
},
setRedirectHostRewrite: function setRedirectHostRewrite(req, res, proxyRes, options) {
if ((options.hostRewrite || options.autoRewrite || options.protocolRewrite)
&& proxyRes.headers['location']
&& redirectRegex.test(proxyRes.statusCode)) {
var target = url.parse(options.target);
var u = url.parse(proxyRes.headers['location']);
// make sure the redirected host matches the target host before rewriting
if (target.host != u.host) {
return;
}
if (options.hostRewrite) {
u.host = options.hostRewrite;
} else if (options.autoRewrite) {
u.host = req.headers['host'];
}
if (options.protocolRewrite) {
u.protocol = options.protocolRewrite;
}
proxyRes.headers['location'] = u.format();
}
},
/**
* Copy headers from proxyResponse to response
* set each header in response object.
*
* @param {ClientRequest} Req Request object
* @param {IncomingMessage} Res Response object
* @param {proxyResponse} Res Response object from the proxy request
* @param {Object} Options options.cookieDomainRewrite: Config to rewrite cookie domain
*
* @api private
*/
writeHeaders: function writeHeaders(req, res, proxyRes, options) {
var rewriteCookieDomainConfig = options.cookieDomainRewrite,
rewriteCookiePathConfig = options.cookiePathRewrite,
preserveHeaderKeyCase = options.preserveHeaderKeyCase,
rawHeaderKeyMap,
setHeader = function(key, header) {
if (header == undefined) return;
if (rewriteCookieDomainConfig && key.toLowerCase() === 'set-cookie') {
header = common.rewriteCookieProperty(header, rewriteCookieDomainConfig, 'domain');
}
if (rewriteCookiePathConfig && key.toLowerCase() === 'set-cookie') {
header = common.rewriteCookieProperty(header, rewriteCookiePathConfig, 'path');
}
res.setHeader(String(key).trim(), header);
};
if (typeof rewriteCookieDomainConfig === 'string') { //also test for ''
rewriteCookieDomainConfig = { '*': rewriteCookieDomainConfig };
}
if (typeof rewriteCookiePathConfig === 'string') { //also test for ''
rewriteCookiePathConfig = { '*': rewriteCookiePathConfig };
}
// message.rawHeaders is added in: v0.11.6
// https://nodejs.org/api/http.html#http_message_rawheaders
if (preserveHeaderKeyCase && proxyRes.rawHeaders != undefined) {
rawHeaderKeyMap = {};
for (var i = 0; i < proxyRes.rawHeaders.length; i += 2) {
var key = proxyRes.rawHeaders[i];
rawHeaderKeyMap[key.toLowerCase()] = key;
}
}
Object.keys(proxyRes.headers).forEach(function(key) {
var header = proxyRes.headers[key];
if (preserveHeaderKeyCase && rawHeaderKeyMap) {
key = rawHeaderKeyMap[key] || key;
}
setHeader(key, header);
});
},
/**
* Set the statusCode from the proxyResponse
*
* @param {ClientRequest} Req Request object
* @param {IncomingMessage} Res Response object
* @param {proxyResponse} Res Response object from the proxy request
*
* @api private
*/
writeStatusCode: function writeStatusCode(req, res, proxyRes) {
// From Node.js docs: response.writeHead(statusCode[, statusMessage][, headers])
if(proxyRes.statusMessage) {
res.statusCode = proxyRes.statusCode;
res.statusMessage = proxyRes.statusMessage;
} else {
res.statusCode = proxyRes.statusCode;
}
}
};

View File

@@ -0,0 +1,162 @@
var http = require('http'),
https = require('https'),
common = require('../common');
/*!
* Array of passes.
*
* A `pass` is just a function that is executed on `req, socket, options`
* so that you can easily add new checks while still keeping the base
* flexible.
*/
/*
* Websockets Passes
*
*/
module.exports = {
/**
* WebSocket requests must have the `GET` method and
* the `upgrade:websocket` header
*
* @param {ClientRequest} Req Request object
* @param {Socket} Websocket
*
* @api private
*/
checkMethodAndHeader : function checkMethodAndHeader(req, socket) {
if (req.method !== 'GET' || !req.headers.upgrade) {
socket.destroy();
return true;
}
if (req.headers.upgrade.toLowerCase() !== 'websocket') {
socket.destroy();
return true;
}
},
/**
* Sets `x-forwarded-*` headers if specified in config.
*
* @param {ClientRequest} Req Request object
* @param {Socket} Websocket
* @param {Object} Options Config object passed to the proxy
*
* @api private
*/
XHeaders : function XHeaders(req, socket, options) {
if(!options.xfwd) return;
var values = {
for : req.connection.remoteAddress || req.socket.remoteAddress,
port : common.getPort(req),
proto: common.hasEncryptedConnection(req) ? 'wss' : 'ws'
};
['for', 'port', 'proto'].forEach(function(header) {
req.headers['x-forwarded-' + header] =
(req.headers['x-forwarded-' + header] || '') +
(req.headers['x-forwarded-' + header] ? ',' : '') +
values[header];
});
},
/**
* Does the actual proxying. Make the request and upgrade it
* send the Switching Protocols request and pipe the sockets.
*
* @param {ClientRequest} Req Request object
* @param {Socket} Websocket
* @param {Object} Options Config object passed to the proxy
*
* @api private
*/
stream : function stream(req, socket, options, head, server, clb) {
var createHttpHeader = function(line, headers) {
return Object.keys(headers).reduce(function (head, key) {
var value = headers[key];
if (!Array.isArray(value)) {
head.push(key + ': ' + value);
return head;
}
for (var i = 0; i < value.length; i++) {
head.push(key + ': ' + value[i]);
}
return head;
}, [line])
.join('\r\n') + '\r\n\r\n';
}
common.setupSocket(socket);
if (head && head.length) socket.unshift(head);
var proxyReq = (common.isSSL.test(options.target.protocol) ? https : http).request(
common.setupOutgoing(options.ssl || {}, options, req)
);
// Enable developers to modify the proxyReq before headers are sent
if (server) { server.emit('proxyReqWs', proxyReq, req, socket, options, head); }
// Error Handler
proxyReq.on('error', onOutgoingError);
proxyReq.on('response', function (res) {
// if upgrade event isn't going to happen, close the socket
if (!res.upgrade) {
socket.write(createHttpHeader('HTTP/' + res.httpVersion + ' ' + res.statusCode + ' ' + res.statusMessage, res.headers));
res.pipe(socket);
}
});
proxyReq.on('upgrade', function(proxyRes, proxySocket, proxyHead) {
proxySocket.on('error', onOutgoingError);
// Allow us to listen when the websocket has completed
proxySocket.on('end', function () {
server.emit('close', proxyRes, proxySocket, proxyHead);
});
// The pipe below will end proxySocket if socket closes cleanly, but not
// if it errors (eg, vanishes from the net and starts returning
// EHOSTUNREACH). We need to do that explicitly.
socket.on('error', function () {
proxySocket.end();
});
common.setupSocket(proxySocket);
if (proxyHead && proxyHead.length) proxySocket.unshift(proxyHead);
//
// Remark: Handle writing the headers to the socket when switching protocols
// Also handles when a header is an array
//
socket.write(createHttpHeader('HTTP/1.1 101 Switching Protocols', proxyRes.headers));
proxySocket.pipe(socket).pipe(proxySocket);
server.emit('open', proxySocket);
server.emit('proxySocket', proxySocket); //DEPRECATED.
});
return proxyReq.end(); // XXX: CHECK IF THIS IS THIS CORRECT
function onOutgoingError(err) {
if (clb) {
clb(err, req, socket);
} else {
server.emit('error', err, req, socket);
}
socket.end();
}
}
};