mirror of
https://github.com/avwo/whistle.git
synced 2026-10-18 22:51:32 +08:00
153 lines
4.1 KiB
JavaScript
153 lines
4.1 KiB
JavaScript
var https = require('https');
|
|
var LRU = require('lru-cache');
|
|
var util = require('../util');
|
|
var http2 = require('../config').enableH2 ? require('http2') : null;
|
|
|
|
var SUPPORTED_PROTOS = ['h2', 'http/1.1', 'http/1.0'];
|
|
var H2_SETTINGS = { enablePush: false , enableConnectProtocol: false };
|
|
var clients = {};
|
|
var notH2 = new LRU({max: 2560, maxAge: 36000});
|
|
var pendingList = {};
|
|
|
|
function getName(req) {
|
|
var options = req.options;
|
|
return [req.sessoinId, options.servername, options.host, options.port].join('\n');
|
|
}
|
|
|
|
function getSocket(options, callback) {
|
|
if (!options) {
|
|
return callback();
|
|
}
|
|
util.connect({
|
|
servername: options.servername,
|
|
host: options.host,
|
|
port: options.port || 443,
|
|
rejectUnauthorized: false,
|
|
ALPNProtocols: SUPPORTED_PROTOS,
|
|
NPNProtocols: SUPPORTED_PROTOS
|
|
}, function(err, socket) {
|
|
if (err) {
|
|
return callback(false, null, err);
|
|
}
|
|
var proto = socket.alpnProtocol || socket.npnProtocol;
|
|
callback(proto === 'h2', socket);
|
|
});
|
|
}
|
|
|
|
function getClient(req, socket, name) {
|
|
var client = http2.connect('https://' + req.headers.host, {
|
|
setttings: H2_SETTINGS,
|
|
rejectUnauthorized: false,
|
|
createConnection: function() {
|
|
return socket;
|
|
}
|
|
});
|
|
clients[name] = client;
|
|
var handleClose = function() {
|
|
delete clients[name];
|
|
client.close();
|
|
socket.destroy();
|
|
};
|
|
socket.on('error', handleClose);
|
|
socket.on('close', handleClose);
|
|
client.on('error', handleClose);
|
|
return client;
|
|
}
|
|
|
|
function requestH2(client, req, res) {
|
|
var headers = util.formatH2Headers(req.headers);
|
|
delete req.headers.connection;
|
|
delete req.headers['keep-alive'];
|
|
delete req.headers['http2-settings'];
|
|
delete req.headers['proxy-connection'];
|
|
delete req.headers['transfer-encoding'];
|
|
headers[':path'] = req.url;
|
|
headers[':method'] = req.method;
|
|
headers[':authority'] = req.headers.host;
|
|
try {
|
|
var h2Session = client.request(headers);
|
|
h2Session.on('error', util.noop);
|
|
h2Session.on('response', function(h2Headers) {
|
|
var newHeaders = {};
|
|
h2Session.statusCode = h2Headers[':status'];
|
|
h2Session.httpVersion = '1.1';
|
|
h2Session.headers = newHeaders;
|
|
Object.keys(h2Headers).forEach(function(name) {
|
|
if (name[0] !== ':') {
|
|
newHeaders[name] = h2Headers[name];
|
|
}
|
|
});
|
|
res.response(h2Session);
|
|
});
|
|
req.pipe(h2Session);
|
|
} catch (e) {
|
|
client.emit('error', e);
|
|
}
|
|
}
|
|
|
|
exports.getServer = function(options, listener) {
|
|
var createServer;
|
|
if (options.allowHTTP1 && http2) {
|
|
createServer = http2.createSecureServer;
|
|
options.setttings = H2_SETTINGS;
|
|
} else {
|
|
createServer = https.createServer;
|
|
}
|
|
var server = createServer(options);
|
|
if (typeof listener === 'function') {
|
|
server.on('request', listener);
|
|
} else if (listener) {
|
|
Object.keys(listener).forEach(function(name) {
|
|
server.on(name, listener[name]);
|
|
});
|
|
}
|
|
return server;
|
|
};
|
|
|
|
/**
|
|
* TODO(v2.0): 遗留两种需要处理的H2请求
|
|
* 1. 设置代理后的请求
|
|
* 2. 插件转发回来的请求
|
|
*/
|
|
exports.request = function(req, res, callback) {
|
|
var options = req.useH2 && req.options;
|
|
var name = options && getName(req);
|
|
if (!options || notH2.get(name) || options._proxyPort) {
|
|
return callback();
|
|
}
|
|
var client = clients[name];
|
|
if (!client) {
|
|
var pendingItem = pendingList[name];
|
|
if (pendingItem) {
|
|
return pendingItem.push([req, res, callback]);
|
|
} else {
|
|
pendingList[name] = [[req, res, callback]];
|
|
}
|
|
}
|
|
getSocket(client ? null : options, function(isH2, socket, err) {
|
|
client = client || (isH2 && getClient(req, socket, name));
|
|
var pendingItem = pendingList[name];
|
|
delete pendingList[name];
|
|
if (client) {
|
|
notH2.del(name);
|
|
if (pendingItem) {
|
|
pendingItem.forEach(function(list) {
|
|
requestH2(client, list[0], list[1]);
|
|
});
|
|
} else {
|
|
requestH2(client, req, res);
|
|
}
|
|
} else {
|
|
!err && notH2.set(name, 1);
|
|
if (pendingItem) {
|
|
pendingItem.forEach(function(list) {
|
|
list[2](socket);
|
|
socket = null;
|
|
});
|
|
} else {
|
|
callback(socket);
|
|
}
|
|
}
|
|
});
|
|
};
|