NodeJS实现HTTP/HTTPS代理
2011-05-06

    身在天朝,难免会用到代理的时候。 比如在学校内网用代理免费上外网,在墙内用代理上404网站等。

    现在使用的代理大部分为HTTP和Socket代理。 Socket代理更底层,需要本地解析域名,而HTTP代理则是基于HTTP协议之上的,不需要本地解析域名。下面我讲讲HTTP(S)代理的设计思路以及NodeJS代码实现。

HTTP协议

    HTTP协议简单说来就是浏览器把一串字符串发送到目标服务器,然后把目标服务器返回回来的一串字符串显示给用户。

    浏览器发送的这串字符主要分为两个部分,一部分是头,里面包含目标服务器域名,当前请求的文件路径等信息。另一部分是正文,一般的GET请求没有正文。

    服务器返回来的字符串也分为头和正文。

HTTP代理原理

    HTTP代理需要做的事情就是接收浏览器发来的请求字符串,再从请求字符串的头部分找出浏览器请求的目标主机,然后直接把这串请求字符串发给目标主机,再把目标主机返回的数据发给浏览器。 “什么?就这么简单?” “呃。。是啊,但这还没完。。”

    现代浏览器一般都是默认采用HTTP/1.1版本,并且默认会发送Connection: keep-alive请求。 这些信息是写在请求的头部的,意思是通知目标服务器采用keep-alive技术继续处理后续的请求。 但是我们做的代理程序要想支持keep-alive是比较麻烦的。所以干脆就把这个篡改成Connection: close。 这样就可以保证浏览器请求的每个文件都会单独发送一个HTTP请求。

下面是NodeJS代码实现

 

var net = require('net');
var local_port = 8893;

//在本地创建一个server监听本地local_port端口
net.createServer(function (client)
{
    
    //首先监听浏览器的数据发送事件,直到收到的数据包含完整的http请求头
    var buffer = new Buffer(0);
    client.on('data',function(data)
    {
        buffer = buffer_add(buffer,data);
        if (buffer_find_body(buffer== -1return;
        var req = parse_request(buffer);
        if (req === falsereturn;
        client.removeAllListeners('data');
        relay_connection(req);
    });

    //从http请求头部取得请求信息后,继续监听浏览器发送数据,同时连接目标服务器,并把目标服务器的数据传给浏览器
    function relay_connection(req)
    {
        console.log(req.method+' '+req.host+':'+req.port);
        
        //如果请求不是CONNECT方法(GET, POST),那么替换掉头部的一些东西
        if (req.method != 'CONNECT')
        {
            //先从buffer中取出头部
            var _body_pos = buffer_find_body(buffer);
            if (_body_pos < 0_body_pos = buffer.length;
            var header = buffer.slice(0,_body_pos).toString('utf8');
            //替换connection头
            header = header.replace(/(proxy\-)?connection\:.+\r\n/ig,'')
                    .replace(/Keep\-Alive\:.+\r\n/i,'')
                    .replace("\r\n",'\r\nConnection: close\r\n');
            //替换网址格式(去掉域名部分)
            if (req.httpVersion == '1.1')
            {
                var url = req.path.replace(/http\:\/\/[^\/]+/,'');
                if (url.path != urlheader = header.replace(req.path,url);
            }
            buffer = buffer_add(new Buffer(header,'utf8'),buffer.slice(_body_pos));
        }
        
        //建立到目标服务器的连接
        var server = net.createConnection(req.port,req.host);
        //交换服务器与浏览器的数据
        client.on("data", function(data){ server.write(data); });
        server.on("data", function(data){ client.write(data); });

        if (req.method == 'CONNECT')
            client.write(new Buffer("HTTP/1.1 200 Connection established\r\nConnection: close\r\n\r\n"));
        else
            server.write(buffer);
    }
}).listen(local_port);

console.log('Proxy server running at localhost:'+local_port);


//处理各种错误
process.on('uncaughtException', function(err)
{
    console.log("\nError!!!!");
    console.log(err);
});



/**
* 从请求头部取得请求详细信息
* 如果是 CONNECT 方法,那么会返回 { method,host,port,httpVersion}
* 如果是 GET/POST 方法,那么返回 { metod,host,port,path,httpVersion}
*/
function parse_request(buffer)
{
    var s = buffer.toString('utf8');
    var method = s.split('\n')[0].match(/^([A-Z]+)\s/)[1];
    if (method == 'CONNECT')
    {
        var arr = s.match(/^([A-Z]+)\s([^\:\s]+)\:(\d+)\sHTTP\/(\d\.\d)/);
        if (arr && arr[1] && arr[2] && arr[3] && arr[4])
            return { method: arr[1], host:arr[2], port:arr[3],httpVersion:arr[4] };
    }
    else
    {
        var arr = s.match(/^([A-Z]+)\s([^\s]+)\sHTTP\/(\d\.\d)/);
        if (arr && arr[1] && arr[2] && arr[3])
        {
            var host = s.match(/Host\:\s+([^\n\s\r]+)/)[1];
            if (host)
            {
                var _p = host.split(':',2);
                return { method: arr[1], host:_p[0], port:_p[1]?_p[1]:80, path: arr[2],httpVersion:arr[3] };
            }
        }
    }
    return false;
}




/**
* 两个buffer对象加起来
*/
function buffer_add(buf1,buf2)
{
    var re = new Buffer(buf1.length + buf2.length);
    buf1.copy(re);
    buf2.copy(re,buf1.length);
    return re;
}

/**
* 从缓存中找到头部结束标记("\r\n\r\n")的位置
*/
function buffer_find_body(b)
{
    for(var i=0,len=b.length-3;i<len;i++)
    {
        if (b[i] == 0x0d && b[i+1] == 0x0a && b[i+2] == 0x0d && b[i+3] == 0x0a)
        {
            return i+4;
        }
    }
    return -1;
}

 

另外,可以用 "nohup node some.js > /dev/null &" 命令让nodejs程序在后台运行。

完。

30532 read 32 comment(s)
从Elance提现到国内银行的完美方法
2011-05-04

    我开始做自由职业已经有一个月左右了。主要是在elance.com上接项目。

    第一个项目做的是用jQuery做一个图片裁剪的功能,包括图片上传进度显示,两个比例的图片裁剪框。这个项目是100刀,elance扣了8.75刀的中介费,到手91.25刀。这个比例还是可以接受的。

     第二个项目是用nodejs做一个gtalk聊天机器人、一个facebook chat聊天机器人、一个msn聊天机器人。每个机器人200刀。在5天内搞完另外给400刀的bonus。结果我顺利在规定时间搞完,拿了1000刀。哈哈哈。

     钱到了Elance帐户上,怎么变成能够提现的人民币是个麻烦事情。我搜了网上很多文章,都说只有用moneybookers的服务最便宜。 于是我又搞了个moneybookers帐户。 那天突然发现如果成为elance的付费会员(最便宜的付费会员$10/月),就可以享受每月一次的免费电汇(wire transfer),超出一次以后貌似每笔要给25刀5刀手续费。于是就搞了个付费会员玩玩,不仅可以享受免费电汇,还有更多的点数和其他东西可以享受。付费会员只是多了一些Connect。免费会员一样每月又一次免费电汇的机会。那天试探提了200刀到我的中国银行活期一本通上,用的swiftcode是BKCHCNBJ570。过了大概4天,发现卡上真的有200美元,一分中间行费用都没扣。太TM爽了,这绝对是最便宜的提现途径,虽然每月只有一次机会。

     下一步就是去银行结汇,把美元转成人民币,这个步骤不知道要不要收手续费。

2012年10月更新:

      大家最好还是用中国银行。因为中国银行现在收到美元之后是自动上账(不知道其他地方,反正成都貌似都是),而其他一些银行(尤其是工商银行)收到美元后,要人工审核再上到你的账户上。人工审核这个时间就说不清楚了,快的话当天,慢的话无底限。。。关键是工行还要求我本人带身份证到我卡的开户行去办理!别人给我汇款还要我亲自去开户行办理接收手续!!

      还是中国银行好。在Elance上点击withdraw之后,2-3天Elance就会提醒说汇款手续已经办理。一般这个时候钱已经在中国银行账号上了,直接在网银就可以办理结汇,当然汇率要低一些。

      另外,Elance前段时间不知道搞什么飞机,直接提美元到美国以外的国家,每笔收25刀手续费。但是提其他币种就还是继续原来的价格(每月一次免费,超出5刀一次)。 Elance解释说是提倡在Elance完成币种转换,然后直接汇当地货币给你。我觉得可能elance想通过汇率差价赚钱?  操蛋的是提供选择的币种没有RMB! 不过我随便选了个欧元,就可以了。

BTW,欢迎加入Elance QQ群:162095216

完。

27890 read 38 comment(s)
Mac OS 上的各种ipv6配置命令
2011-04-11

IPv6 在Mac OS上是默认开启的。也可以用下面的命令手动开启或者关闭ipv6支持:

在所有网卡上开启ipv6:

ip6 -a

 

关闭ipv6:

ip6 -x

 

要打开router advertisements (RAs)可以用下面的命令:

sysctl -w net.inet6.ip6.accept_rtadv=1

 

关闭RAs的话,将上面的命令最后的数字改成0即可。

 

私有地址默认没有开启,可以用下面的命令开启:

sysctl net.inet6.ip6.use_tempaddr=1

 

在Mac OS上开启ISATAP隧道支持:

 

1、下载ISATAP client for Mac OS X

地址:http://www.momose.org/macosx/isatap.html

2、解压ISATAP client

 

cd /usr/local
sudo tar xfz ~/Downloads/macosx-isatap-*.tar.gz

 

3、更改权限

 

sudo chown -R root:wheel /usr/local/isatap
sudo chmod -R 644 /usr/local/isatap/isatap.kext

 

4、配置ISATAP

4.1 配置ist0和得到IPv4地址(你需要制定现在使用的网卡,比如en0)

 

sudo ./config-ist.sh en0

 

4.2 指定ISATAP router (111.111.111.111更换为你当地的ISATAP服务器地址)

 

sudo ./ifconfig ist0 isataprtr 111.111.111.111
sudo ./rtsold.sh &

 

4.3 设置路由表

 

sudo route delete -inet6 default

 

注:在执行上面命令之前可以用netstat -r查看ipv6路由表上是否有default这一项,没有则不用执行上面命令

 

sudo route add -inet6 default -interface ist0

 

4.4 启动IPv6

 

sudo ifconfig ist0 up

 

5、关闭IPv6

 

sudo ifconfig ist0 down

 

这样ISATAP就配置好了。

 

我的例子:

我在电子科技大学清水河校区,我们学校采用802.1X拨号上网。 我在拨号可以上网的前提下配置了RAs支持,即可正常上ipv6网站。 另外,我也可以通过配置ISATAP,使用隧道连接上ipv6。

完。

27299 read 5 comment(s)
还可以注册的3个字母的.ca域名
2011-03-11

    最近想注册个短一点的域名,搜了一下,发现.ca的域名还有很多3个字母的没有被注册。于是搞了个脚本统计了一下所有的3个字母的.ca域名。更新时间是2011年3月9号。以下是列表,总共3353个。有需要的同学可以找自己喜欢的注册。 推荐在godaddy注册,好处不用多说。

agz.ca
ajz.ca
akz.ca
awx.ca
axb.ca
axc.ca
axd.ca
axm.ca
axq.ca
axv.ca
ayq.ca
ayv.ca
ayw.ca

查看全文...  

27201 read 11 comment(s)
Kindle 3的系统和网络情况以及通过其连电脑上网
2011-01-23

    搜索“kindle 3用作modem共享3G上网”,有很多文章,但绝大部分都是翻译国外一个哥们的文章,写的很大概,一般人很难操作成功。 昨天根据这篇文章研究了下kindle 3的系统,得到一些成功。

    Kindle 的系统是amazon基于linux开发的嵌入式系统。通过在kindle上安装usbNetwork(网上自己搜索,有很多东西都要用到usbNetwork),可以把kindle与电脑连成一个局域网内。然后通过ssh或者telnet可以进入kindle内部。(用户名是root,密码是空,一般usbNetwork装好后,电脑的ip就是192.168.2.1,kindle的ip是192.168.2.2)

    Kindle 3 打开wireless 并成功连上3g网络后,用ifconfig命令可以看到有一个ppp0连接,这就是3G网卡。 按理说就算是漫游,设备在连接上3G网络后,都应该有完全的网络访问能力。但是kindle系统限制了只能连接某几个ip(几个*.amazon.com的站点)。ping www.amazon.com 可以,但是ping 8.8.8.8就不行。 kindle系统里面的浏览器是通过fints-
g7g.amazon.com ( 72.21.206.113 ) 这个http代理服务器实现上网的(https代理服务器是另外一个)。

    可恶的是,amazon的代理服务器需要验证身份,在发送给代理服务器的http请求header里面需要加上x-fsn和x-fn-appid头才能通过验证。不同的kindle设备这两个值不同,所以需要先截取kindle内部浏览器发送的请求,得到这两个header值。方法是下载一个tcpdump-arm软件,传到kindle内(可以使用scp命令,或者先在U盘模式下存到kindle的sd卡里,然后可以在/mnt/us/下面找到)。然后进入kindle内部,运行此软件(如果提示没有权限,可以试试chmod 555 tcpdump-arm设置下权限)。运行方式是找到tcpdump-arm文件的文件夹,然后./tcpdump-arm -nAi ppp0 -s0。 然后在kindle上打开浏览器,随便上一个http网站,电脑上的命令行界面就可以看到截取的各种消息,很容易就从中找出需要的两个header值。

    得到两个header值之后,我们就可以在电脑上伪造kindle内部浏览器的请求了。 很遗憾,目前只能用firefox实现,并需要一枚firefox扩展:modify-headers。安装好这个扩展,先添加上面说的两个header,然后设置forefix的http代理服务器为 localhost 端口号是1234(随你)。 然后在kindle上做一个逆向端口转发,把本地的1234端口的数据转发到72.21.206.113的80端口上。 具体步骤是 先telnet或者ssh进你的kindle,然后输入ssh -R 1234:72.21.206.113:80 username@192.168.2.1。其中username@192.168.2.1是你电脑的ssh用户名和ip。能这样做的前提是你的电脑可以通过ssh登录,这个在mac os 和linux下非常容易,windows用户只能说靠你自己了。。。。

    此时你应该可以用你的firefox正常上网了。 你可以跳过添加验证header的步骤,也可以上网,但是仅限于*.amazon.com站点。 不过遗憾的是,就算能够正常上网,也无法访问https链接,因为modify-headers这个扩展貌似不支持https请求。

    另外,ssh可能会经常断,kindle的3g连接在一段时间不使用后,也会自动断开。为了防止此种情况发生,在创建好反向连接后,输入ping 192.168.2.2 从你的电脑不停的ping kindle,这样可以保持这个反向连接不断开。然后再开一个命令行窗口,再次进入kindle,输入ping www.amazon.com ,这样可以保持kindle的3G网络不断开。

    如果某个高人能够破解kindle系统对网络访问的限制,使其能访问任意站点,那就不用这么麻烦了,而且数据也不用走美国跑一趟。这应该是可以通过软方法实现的,因为既然kindle能直接访问*.amazon.com,那就证明kindle有网络访问权限,问题肯定在操作系统上。

    如果实在无法破解其限制,我在考虑做一个proxy程序,在kindle上运行,自动添加验证header到每个请求头部。这样,就可以简化到 1在kindle上运行usbNetwork和proxy并连接到电脑,2在电脑上任意浏览器输入http代理 192.168.2.2 80端口,就可以了。 而且也能更好的支持https。 我在考虑用开源的tinyproxy修改,交叉编译然后放到kindle里面运行。

    交叉编译我们学校教过,但是被我完全忘了。。。。一切得从头学一下。

    对于某些同学认为如果大家都滥用kindle的免费3G,amazon肯定会禁用全部kindle的3g访问。 我觉得不会,因为目前,所有流量都是走amazon的代理,他们知道所有流量信息,并且能区分不同的kindle设备发送的数据,觉得某个kindle有问题,把那个kindle设备禁用掉就行,不会殃及到其他人的。 要是那个高人破解了kindle系统的网络访问限制,那估计AT&T会真的找到amazon禁用全球漫游的数据。。。

    kindle的所有网络请求都走代理,虽然响应速度慢了点,但是下载速度还可以,我试过下载最高可以达到100-200K/s,但是如果持续的大流量下载,代理服务器会禁用你的连接一会儿。 比如我无法完整的看完一个视频。   另外,就是这个代理服务器可以顺便实现翻墙,不过悲剧的事情有来了,twitter和facebook等绝大部分墙外的东西登录都用的是https。。。解决办法是先用电脑正常翻墙登录并保存登录信息,然后再用kindle上。

完。

33673 read 20 comment(s)
上一页 1 2 3 4 [5] 6 7 8 ...35 下一页
Copyright © Longbill 2008-2017 , Designed by EndTo , Powered by EndCMS