用nodejs實現支持pac腳本的代理


公司內的網絡有點搞笑,需要配置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上去的喲!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM