mirror of
https://github.com/avwo/whistle.git
synced 2026-10-18 14:41:12 +08:00
250 lines
6.4 KiB
JavaScript
250 lines
6.4 KiB
JavaScript
var net = require('net');
|
|
var tls = require('tls');
|
|
var url = require('url');
|
|
var path = require('path');
|
|
var EventEmitter = require('events').EventEmitter;
|
|
var connect = require('../proxy/connect');
|
|
var util = require('../../util');
|
|
var rules = require('../rules');
|
|
var parseReq = require('../proxy/parse-req');
|
|
var cert = require('./cert');
|
|
var ServerAgent = require('./server-agent'); //TODO 放到公共地方,可以destroy
|
|
var serverAgent = new ServerAgent();
|
|
var DEFAULT_CERT = cert.DEFAULT_CERT;
|
|
var LOCALHOST = '127.0.0.1';
|
|
var d = require('domain').create().on('error', util.noop);
|
|
var config, app;
|
|
|
|
function getFullUrl(socket, wss) {
|
|
return (wss ? 'wss:' : 'ws:') + '//' + socket.headers.host + socket.url;
|
|
}
|
|
|
|
function getRequestEmitter(socket, wss) {
|
|
var reqEmitter = new EventEmitter();
|
|
reqEmitter.url = getFullUrl(socket, wss);
|
|
reqEmitter.ip = socket.remoteAddress; //对tls的请求,为兼容node v0.10.x只能通过转发,导致获取的请求ip都是本地ip
|
|
reqEmitter.method = socket.method;
|
|
reqEmitter.headers = socket.headers;
|
|
reqEmitter.httpVersion = socket.httpVersion;
|
|
return reqEmitter;
|
|
}
|
|
|
|
function handleWebsocket(socket, wss, callback) {
|
|
var reqEmitter = app.reqEmitter;
|
|
var headers = socket.headers;
|
|
var host = headers.host;
|
|
var fullUrl = socket.fullUrl = getFullUrl(socket, wss);
|
|
var options = url.parse(fullUrl);
|
|
var _rules = reqEmitter.rules = rules.resolveRules(fullUrl);
|
|
var proxyUrl = util.rule.getMatcher(_rules.proxy);
|
|
var reqSocket, options, matchedUrl;
|
|
var now = Date.now();
|
|
|
|
function execCallback(err, socket) {
|
|
err && util.emitError(reqEmitter, err);
|
|
callback(err, socket);
|
|
}
|
|
|
|
if (proxyUrl) {
|
|
proxyUrl = (wss ? 'wss:' : 'ws:') + util.removeProtocol(proxyUrl);
|
|
rules.resolveHost(proxyUrl, function(err, ip, customHost) {
|
|
reqEmitter.customHost = customHost;
|
|
if (err || !ip) {
|
|
return execCallback(err || new Error('lookup DNS faild: ' + proxyUrl));
|
|
}
|
|
|
|
options = url.parse(proxyUrl);
|
|
socket.options = {
|
|
host: ip,
|
|
port: options.port,
|
|
isWs: !wss
|
|
};
|
|
reqEmitter.host = ip;
|
|
reqEmitter.dnsTime = Date.now() - now;
|
|
reqEmitter.emit('send');
|
|
connect(socket, function(err, proxySocket) {
|
|
if (err) {
|
|
return execCallback(err);
|
|
}
|
|
|
|
if (wss) {
|
|
reqSocket = tls.connect({
|
|
rejectUnauthorized: false,
|
|
socket: proxySocket
|
|
}, pipeData);
|
|
} else {
|
|
reqSocket = proxySocket;
|
|
pipeData();
|
|
}
|
|
|
|
});
|
|
|
|
}, true);
|
|
return;
|
|
}
|
|
|
|
matchedUrl = util.rule.getUrl(_rules.rule);
|
|
if (/^wss?:\/\//.test(matchedUrl)) {
|
|
reqEmitter.realUrl = fullUrl = matchedUrl;
|
|
} else {
|
|
matchedUrl = null;
|
|
}
|
|
|
|
options = url.parse(fullUrl);
|
|
|
|
rules.resolveHost(fullUrl, function(err, ip, customHost) {
|
|
reqEmitter.customHost = customHost;
|
|
if (err || !ip) {
|
|
return execCallback(err || new Error('lookup DNS faild: ' + fullUrl));
|
|
}
|
|
reqEmitter.host = ip;
|
|
reqEmitter.dnsTime = Date.now() - now;
|
|
reqEmitter.emit('send');
|
|
|
|
var isWss = options.protocol == 'wss';
|
|
reqSocket = (isWss ? tls : net).connect({
|
|
rejectUnauthorized: false,
|
|
host: ip,
|
|
port: options.port || (isWss ? 443 : 80)
|
|
}, pipeData);
|
|
}, true);
|
|
|
|
function pipeData() {
|
|
var headers = socket.headers;
|
|
var origin;
|
|
if (matchedUrl) {
|
|
headers.host = options.host;
|
|
origin = headers.origin;
|
|
headers.origin = (options.protocol == 'wss' ? 'https://' : 'http://') + options.host;
|
|
}
|
|
|
|
reqSocket.write(socket.getBuffer(matchedUrl ? headers : null));
|
|
socket.pipe(reqSocket);
|
|
parseReq(reqSocket, function(err, res) {
|
|
if (err) {
|
|
return execCallback(err);
|
|
}
|
|
|
|
headers = res.headers;
|
|
if (matchedUrl) {
|
|
headers['access-control-allow-origin'] = origin;
|
|
}
|
|
socket.write(res.getBuffer(matchedUrl ? headers : null));
|
|
res.pipe(socket);
|
|
reqEmitter.emit('response', {
|
|
headers: headers,
|
|
realUrl: matchedUrl,
|
|
statusCode: res.statusCode
|
|
});
|
|
execCallback(null, reqSocket);
|
|
}, true);
|
|
}
|
|
}
|
|
|
|
module.exports = function dispatch(socket, hostname, _app) {
|
|
var reqSocket, buffer;
|
|
var content = '';
|
|
app = _app;
|
|
config = app.config;
|
|
function destroy() {
|
|
socket.destroy();
|
|
reqSocket && reqSocket.destroy();
|
|
}
|
|
|
|
function abortIfUnavailable(socket) {
|
|
return socket.on('error', destroy)
|
|
.on('close', destroy);
|
|
}
|
|
|
|
abortIfUnavailable(socket);
|
|
socket.on('data', request);
|
|
socket.on('end', request);
|
|
|
|
function request(chunk) {
|
|
socket.removeListener('data', request);
|
|
socket.removeListener('end', request);
|
|
if (!chunk) {//没有数据
|
|
return destroy();
|
|
}
|
|
|
|
buffer = buffer ? Buffer.concat([buffer, chunk]) : chunk;
|
|
if (/upgrade\s*:\s*websocket/i.test(buffer && buffer.toString())) { //ws
|
|
parseReq(socket, function(err, socket) {
|
|
if (err) {
|
|
return destroy();
|
|
}
|
|
var reqEmitter = getRequestEmitter(socket);
|
|
app.emit('websocket', reqEmitter);
|
|
app.reqEmitter = reqEmitter;
|
|
handleWebsocket(socket, false, function(err, req) {
|
|
if (err) {
|
|
return destroy();
|
|
}
|
|
abortIfUnavailable(reqSocket = req);
|
|
});
|
|
}, buffer, true);
|
|
buffer = null;
|
|
} else {
|
|
cert.createCert(hostname, function(err) {
|
|
serverAgent.createServer(hostname, handleTlsSocket, function(port) {
|
|
d.run(function() {
|
|
reqSocket = net.connect(port, LOCALHOST, function() {
|
|
reqSocket.write(buffer);
|
|
reqSocket.pipe(socket).pipe(reqSocket);
|
|
buffer = null;
|
|
});
|
|
abortIfUnavailable(reqSocket);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
function handleTlsSocket(socket) {
|
|
var reqSocket;
|
|
function destroy() {
|
|
socket.destroy();
|
|
reqSocket && reqSocket.destroy();
|
|
}
|
|
|
|
function abortIfUnavailable(socket) {
|
|
return socket.on('error', destroy)
|
|
.on('close', destroy);
|
|
}
|
|
|
|
abortIfUnavailable(socket);
|
|
parseReq(socket, function(err, socket) {
|
|
if (err) {
|
|
return destroy();
|
|
}
|
|
//wss
|
|
|
|
var headers = socket.headers;
|
|
if (headers.upgrade && headers.upgrade.toLowerCase() == 'websocket') {
|
|
var reqEmitter = getRequestEmitter(socket, true);
|
|
app.emit('websocket', reqEmitter);
|
|
app.reqEmitter = reqEmitter;
|
|
handleWebsocket(socket, true, function(err, req) {
|
|
if (err) {
|
|
return destroy();
|
|
}
|
|
reqSocket = req;
|
|
abortIfUnavailable(reqSocket);
|
|
});
|
|
} else {
|
|
//https
|
|
reqSocket = net.connect(config.port, LOCALHOST, function() {
|
|
headers[util.HTTPS_FIELD] = 1;
|
|
reqSocket.write(socket.getBuffer(headers));
|
|
socket.pipe(reqSocket).pipe(socket);
|
|
});
|
|
abortIfUnavailable(reqSocket);
|
|
}
|
|
}, true);
|
|
|
|
|
|
}
|
|
|