您的位置:首页 > Web前端 > Node.js

用nodejs实现支持pac脚本的代理

2012-05-03 21:55 232 查看
公司内的网络有点搞笑,需要配置pac脚本才能访问外网,但是除了浏览器之外的软件,大部分都不支持pac脚本的代理,也就一部分软件支持使用IE的代理,幸免遇难。

平时也就忍了,前段时间想装个archlinux的虚拟机,发现连pacman都用不了,google了很久也没有找到一个代理软件支持pac脚本的,于是乎想到用nodejs写一个,因为pac脚本本身是js的,所以实现起来应该比较方便。

首先认识一下pac脚本吧:http://en.wikipedia.org/wiki/Proxy_auto-config

看来真正的代理服务器我们不用去实现了,我们仅需要实现一个能够根据用户请求的网址交给pac脚本去计算代理服务器地址和端口,然后建立隧道(Tunnel)就可以了。

用nodejs建立隧道的代码很简单

net.createServer(function (clientSocket){
clientSocket.once('data', function (firstChunk){
// 解析http协议头, 分析出请求的url
var url = /[A-Z]+\s+([^\s]+)\s+HTTP/.exec(firstChunk)[1];
if (url.indexOf('//') === -1) {
// https协议交给pac脚本会得到错误的端口.
url = 'http://' + url;
}
// 这个异步调用是在使用pac脚本计算应该使用哪个代理.
getProxyHostAndPort(url, function (hostAndPort){
var serverSocket = net.connect(hostAndPort.port, hostAndPort.host, function() {
clientSocket.pipe(serverSocket);
serverSocket.write(firstChunk);
serverSocket.pipe(clientSocket);
serverSocket.on('end', function() {
clientSocket.end();
});
});
});
});
}).listen(8088);


请注意:为了支持https,所以直接用net.createServer而不是http.createServer。

现在就只剩下解析pac脚本了,首先得拿到pac脚本的文件内容,这点用nodejs的http.get就可以了,这里就不介绍了。

我们假设已经拿到了pac脚本的代码,存在变量pacCode里,现在是时候把pac脚本里定义的FindProxyForURL函数导入到上下文了。

要用eval吗?等等,看看wiki里面的例子,FindProxyForURL里用到了两个未定义的函数shExpMatch还有isInNet,这两个函数我们得提供给pac脚本,不然在调用FindProxyForURL的时候会报错的.

我们先根据他们的用法猜猜他们是干嘛用的:shExpMatch是做shellExp的匹配测试,而isInNet是计算一个ip是否在一个网段(用掩码表示)内。

那就实现这两个函数

var shExpMatch = function (){
var _map = { '.': '\\.', '*': '.*?', '?': '.' };
var _rep = function (m){ return _map[m] };
return function (text, exp){
return new RegExp(exp.replace(/\.|\*|\?/g, _rep)).test(text);
};
}();
var isInNet = function (){
function convert_addr(ipchars) {
var bytes = ipchars.split('.');
return ((bytes[0] & 0xff) << 24) |
((bytes[1] & 0xff) << 16) |
((bytes[2] & 0xff) <<  8) |
(bytes[3] & 0xff);
}
return function (ipaddr, pattern, maskstr) {
var match = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(ipaddr);
if (match[1] > 255 || match[2] > 255 ||
match[3] > 255 || match[4] > 255) {
return false;    // not an IP address
}
var host = convert_addr(ipaddr);
var pat  = convert_addr(pattern);
var mask = convert_addr(maskstr);
return ((host & mask) == (pat & mask));
};
}();


现在可以继续了

完成getProxyHostAndPort这个函数(我们采用new Function的方式避免使用eval,使用eval对js压缩工具不友好)

var fnPac = new Function('shExpMatch', 'isInNet',
pacCode + ';return FindProxyForURL;')(shExpMatch, isInNet);
function getProxyHostAndPort(url, callback){
var hostAndPort = parseHostAndPort(url);
var str = fnPac(url, hostAndPort.host);
var p = str.split(/\s*;\s*/g)[0];
if (p.indexOf('PROXY') !== -1) {
var m = /PROXY\s*([^:]+)(?::(\d+))?/.exec(p);
callback({
host: m[1],
port: Number(m[2]) || 8080
});
} else {
callback(hostAndPort);
}
}


基本上可以宣告结束了,不过让我郁闷的是pac脚本里还支持一个函数叫dnsResolve,而且公司里的pac脚本也用到了。

这个函数是个同步的,但是nodejs里提供的dns.resolve接口是异步的,这咋整?

解决的办法比较恶心,大家有兴趣的话就看源代码吧:

https://github.com/hackwaly/http-proxy-pac/blob/master/proxy.js

顺带说一句,这个源代码也是通过这个代理用git给push上去的哟!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: