Node.js,代理HTTPS流量,无需重新签名
我正在使用Node.js构建一个代理,该代理将在本地计算机上运行,并记录所有访问的域。类似于Fiddler的工作方式,但我的程序更简单,我不需要查看数据或解密任何东西 我在HTTP上做得很好,但在HTTPS上,它使用提供的自签名证书放弃了流量。这将导致浏览器显示警告。同样的事情不会发生在fiddler中,除非您选择解密HTTPS流量 所以我的问题是:如何使用Node.js代理HTTPS流量,从而使其对用户完全透明 这是我现在正在使用的代码,使用Node.js http代理。基于Github项目,代理镜像Node.js,代理HTTPS流量,无需重新签名,node.js,https,proxy,fiddler,Node.js,Https,Proxy,Fiddler,我正在使用Node.js构建一个代理,该代理将在本地计算机上运行,并记录所有访问的域。类似于Fiddler的工作方式,但我的程序更简单,我不需要查看数据或解密任何东西 我在HTTP上做得很好,但在HTTPS上,它使用提供的自签名证书放弃了流量。这将导致浏览器显示警告。同样的事情不会发生在fiddler中,除非您选择解密HTTPS流量 所以我的问题是:如何使用Node.js代理HTTPS流量,从而使其对用户完全透明 这是我现在正在使用的代码,使用Node.js http代理。基于Github项目,
var httpProxy = require('http-proxy'),
https = require('https'),
connect = require('connect'),
logger = connect.logger('dev'),
EventEmitter = require('events').EventEmitter,
util = require('util'),
Iconv = require('iconv').Iconv,
convertBuffer = require('./convertBuffer'),
fs = require('fs'),
net = require('net'),
url = require('url'),
path = require('path'),
certDir = path.join(__dirname, '/../cert'),
httpsOpts = {
key: fs.readFileSync(path.join(certDir, 'proxy-mirror.key'), 'utf8'),
cert: fs.readFileSync(path.join(certDir, 'proxy-mirror.crt'), 'utf8')
};
var Session = function (sessionId, req, res) {
EventEmitter.call(this);
var that = this;
this.id = req._uniqueSessionId = res._uniqueSessionId = sessionId;
this.request = req;
this.request.body = {asString: null, asBase64: null};
this.response = res;
this.response.body = {asString: null, asBase64: null};
this.state = 'start';
var hookInResponseWrite = function () {
var response = that.response;
var _write = response.write;
var output = [];
response.write = function (chunk, encoding) {
output.push(chunk);
_write.apply(response, arguments);
};
return output;
}, hookInRequestData = function () {
var request = that.request,
output = [];
request.on('data', function (chunk, encoding) {
output.push(chunk);
});
request.on('end', function () {
var buffersConcatenated = Buffer.concat(output);
request.body.asString = buffersConcatenated.toString();
that.emit('request.end', that);
});
return output;
};
this.response.bodyBuffers = hookInResponseWrite();
this.request.bodyBuffers = hookInRequestData();
this.ended = function () {
var buffersConcatenated = Buffer.concat(this.response.bodyBuffers);
this.response.body.asString = convertBuffer.convertEncodingContentType(buffersConcatenated,this.response.getHeader('content-type') || '');
this.response.body.asBase64 = buffersConcatenated.toString('base64');
this.removeAllListeners();
};
};
util.inherits(Session, EventEmitter);
Session.extractSessionId = function (req) {
return req._uniqueSessionId;
};
var SessionStorage = function () {
var sessionHash = {},
sessionIdCounter = 0,
nextId = function () {
return sessionIdCounter += 1;
};
this.startSession = function (req, res) {
var sessionId = nextId(), session;
sessionHash[sessionId] = session = new Session(sessionId, req, res);
return session;
};
this.popSession = function (req) {
var sessionId = Session.extractSessionId(req),
session = sessionHash[sessionId];
delete sessionHash[sessionId];
return session;
};
};
var ProxyServer = function ProxyServer() {
EventEmitter.call(this);
var proxyPort = 8888,
secureServerPort = 8887,
parseHostHeader = function (headersHost, defaultPort) {
var hostAndPort = headersHost.split(':'),
targetHost = hostAndPort[0],
targetPort = parseInt(hostAndPort[1]) || defaultPort;
return {hostname: targetHost, port: targetPort, host: headersHost};
},
sessionStorage = new SessionStorage(),
adjustRequestUrl = function(req){
if (requestToProxyMirrorWebApp(req)) {
req.url = req.url.replace(/http:\/\/localhost:8889\//, '/');
}
req.url = url.parse(req.url).path;
},
proxyServer = httpProxy.createServer({
changeOrigin: true,
enable: {
xforward: false
}
}, function (req, res, proxy) {
var parsedHostHeader = parseHostHeader(req.headers['host'], 80),
targetHost = parsedHostHeader.hostname,
targetPort = parsedHostHeader.port;
req.originalUrl = req.url;
adjustRequestUrl(req);
logger(req, res, function () {
proxy.proxyRequest(req, res, {
host: targetHost,
port: targetPort
});
})
}),
proxy = proxyServer.proxy,
secureServer = https.createServer(httpsOpts, function (req, res) {
var parsedHostHeader = parseHostHeader(req.headers.host, 443);
// console.log('secure handler ', req.headers);
req.originalUrl = req.url;
if(!req.originalUrl.match(/https/)){
req.originalUrl = 'https://' + parsedHostHeader.host + req.url;
}
adjustRequestUrl(req);
logger(req, res, function () {
proxy.proxyRequest(req, res, {
host: parsedHostHeader.hostname,
port: parsedHostHeader.port,
changeOrigin: true,
target: {
https: true
}
});
});
}),
listening = false,
requestToProxyMirrorWebApp = function (req) {
var matcher = /(proxy-mirror:8889)|(proxy-mirror:35729)/;
return req.url.match(matcher) || (req.originalUrl && req.originalUrl.match(matcher));
};
[secureServer,proxyServer].forEach(function(server){
server.on('upgrade', function (req, socket, head) {
// console.log('upgrade', req.url);
proxy.proxyWebSocketRequest(req, socket, head);
});
});
proxyServer.addListener('connect', function (request, socketRequest, bodyhead) {
//TODO: trying fixing web socket connections to proxy - other web socket connections won't work :(
// console.log('conenct', request.method, request.url, bodyhead);
var targetPort = secureServerPort,
parsedHostHeader = parseHostHeader(request.headers.host);
if(requestToProxyMirrorWebApp(request)){
targetPort = parsedHostHeader.port;
}
var srvSocket = net.connect(targetPort, 'localhost', function () {
socketRequest.write('HTTP/1.1 200 Connection Established\r\n\r\n');
srvSocket.write(bodyhead);
srvSocket.pipe(socketRequest);
socketRequest.pipe(srvSocket);
});
});
this.emitSessionRequestEnd = function (session) {
this.emit('session.request.end', session);
};
this.startSession = function (req, res) {
if (requestToProxyMirrorWebApp(req)) {
return;
}
var session = sessionStorage.startSession(req, res);
this.emit('session.request.start', session);
session.on('request.end', this.emitSessionRequestEnd.bind(this));
};
this.endSession = function (req, res) {
if (requestToProxyMirrorWebApp(req)) {
return;
}
var session = sessionStorage.popSession(req);
if (session) {
session.ended();
this.emit('session.response.end', session);
}
};
this.start = function (done) {
done = done || function () {
};
proxy.on('start', this.startSession.bind(this));
proxy.on('end', this.endSession.bind(this));
proxyServer.listen(proxyPort, function () {
secureServer.listen(secureServerPort, function () {
listening = true;
done();
});
});
};
this.stop = function (done) {
done = done || function () {
};
proxy.removeAllListeners('start');
proxy.removeAllListeners('end');
if (listening) {
secureServer.close(function () {
proxyServer.close(function () {
listening = false;
done();
});
});
}
};
};
util.inherits(ProxyServer, EventEmitter);
module.exports = ProxyServer;
你没有分享你的代码,这将使你很难帮助你,因为它不清楚如何“神奇地”退出流量。辞职流量不是一件小事,需要大量代码,这意味着您正在使用库或其他代表您这样做的东西。感谢Eric的回复。我想要的实际上是我可以很容易地用fiddlercore做的事情,但我也需要它在OSX上工作。我将尝试解释用例,但无法深入实际细节。用户位于防火墙后面,防火墙阻止对web的访问。要通过它,需要使用两个socks5代理。一个用于业务域,另一个用于一般域。我知道这听起来很奇怪,但这就是它的工作原理。在fiddlercore中,我只做了(但我确信库所做的是真正高级的):private void FiddlerApplication_BeforeRequestClient(Session oSession){if(isBusinessDomain(oSession.fullUrl))oSession[“x-overrideGateway”]=“socks=127.0.0.1:8888”;否则…我不确定fiddler是否真的代理了请求。我假设它像wireshark一样嗅探流量。如果您的用例更像fiddler,那么嗅探流量比实际代理流量要好。