1. 程式人生 > >proxyme——java NIO實現的http代理,支援https

proxyme——java NIO實現的http代理,支援https

proxyme 一個http代理

使用java NIO的http代理。支援https。建議不要再chrome上使用本代理,因為chrome本身會請求很多谷歌的api,結果被牆住了,又只有兩個執行緒,導致其他都被阻塞,很尷尬。

之前也打算做過這個東西,結果做出來的有點缺陷(現在想可能是selector中鎖的問題,忘記了)。這大概隔了半年,這個專案的http代理功能實現了。

原始碼地址

https://github.com/arloor/proxyme

執行日誌

11:23:08.883 [main] INFO com.arloor.proxyme.HttpProxyBootStrap - 在8080埠啟動了代理服務
11:23:12.208 [localSelector] INFO com.arloor.proxyme.LocalSelector - 接收瀏覽器連線: /127.0.0.1:50317
11:23:12.210 [localSelector] INFO com.arloor.proxyme.ChannalBridge - 請求—— CONNECT cn.bing.com:443 HTTP/1.1
11:23:12.291 [localSelector] INFO com.arloor.proxyme.ChannalBridge - 建立遠端連線: cn.bing.com/202.89.233.100:443
11:23:12.291 [remoteSlector] INFO com.arloor.proxyme.RemoteSelector - 註冊remoteChannel到remoteSelector。remoteChannel: cn.bing.com/202.89.233.100:443
11:23:12.298 [localSelector] INFO com.arloor.proxyme.ChannalBridge - 傳送請求517 -->cn.bing.com/202.89.233.100:443
11:23:12.365 [remoteSlector] INFO com.arloor.proxyme.ChannalBridge - 接收響應2720 <--cn.bing.com/202.89.233.100:443
11:23:12.365 [remoteSlector] INFO com.arloor.proxyme.ChannalBridge - 接收響應1360 <--cn.bing.com/202.89.233.100:443
11:23:12.366 [remoteSlector] INFO com.arloor.proxyme.ChannalBridge - 接收響應1360 <--cn.bing.com/202.89.233.100:443
11:23:12.369 [remoteSlector] INFO com.arloor.proxyme.ChannalBridge - 接收響應1360 <--cn.bing.com/202.89.233.100:443
11:23:12.369 [remoteSlector] INFO com.arloor.proxyme.ChannalBridge - 接收響應1 <--cn.bing.com/202.89.233.100:443
11:23:12.378 [localSelector] INFO com.arloor.proxyme.ChannalBridge - 傳送請求93 -->cn.bing.com/202.89.233.100:443
11:23:12.382 [localSelector] INFO com.arloor.proxyme.ChannalBridge - 傳送請求1022 -->cn.bing.com/202.89.233.100:443
...
...
11:23:13.281 [localSelector] INFO com.arloor.proxyme.LocalSelector - 接收瀏覽器連線: /127.0.0.1:50319
11:23:13.282 [localSelector] INFO com.arloor.proxyme.ChannalBridge - 請求—— GET http://s.cn.bing.net/th?id=OSA.xiipvhS2Pp2bEg&w=80&h=80&c=8&rs=1&pid=SatAns HTTP/1.1
11:23:13.382 [localSelector] INFO com.arloor.proxyme.ChannalBridge - 建立遠端連線: s.cn.bing.net/112.84.133.11:80
11:23:13.383 [remoteSlector] INFO com.arloor.proxyme.RemoteSelector - 註冊remoteChannel到remoteSelector。remoteChannel: s.cn.bing.net/112.84.133.11:80
11:23:13.383 [localSelector] INFO com.arloor.proxyme.ChannalBridge - 傳送請求340 -->s.cn.bing.net/112.84.133.11:80
11:23:13.383 [localSelector] INFO com.arloor.proxyme.ChannalBridge - 傳送請求409 -->s.cn.bing.net/112.84.133.11:80

效能與記憶體

佔用cpu不到1%

記憶體最大35m(不含jvm自身)。GC次數和時間很少

總的來說,效能可以了吧。

思路

兩個執行緒,每個執行緒一個selector。

localSelector執行緒,負責接收本地瀏覽器的連線請求和讀寫瀏覽器到代理的socketChannel

remoteSelector執行緒,負責讀寫web伺服器到代理的socketChannel。

ChannelBridge類,持有localSocketChannel和remoteSocketChannel。職責是處理請求和響應,並轉發。

RequestHeader類,職責是格式化請求行和請求頭。

實現中的注意點

首先是健壯性!每一個try塊都是很重要的!都解決了一個問題

其次是鎖的問題:

selector.select()會佔有鎖,channel.register(selector)需要持有同樣的鎖。

如果呼叫上面的兩個方法的語句在兩個執行緒中,會讓channel.regiter等很久很久,導致響應難以及時得到。

而在實現中,這是一個生產者消費者問題。localSelector執行緒根據本地瀏覽器請求產生了一個從代理到web伺服器的remoteChannel。而remoteSelector要接收這個remoteChannel,這也就是消費了。

很自然的,避免上面鎖等待最好的方法:localSelector生成remoteChannel,將其放入佇列。remoteSelector執行緒從佇列中取。再結合selector.wakeup()使其從阻塞中返回,可以快速地接收(register)這個remoteChannel。

這兩點,就是最最重要的兩點了。

另外還有,因為代理而需要改變的請求頭了,參見com.arloor.proxyme.RequestHeader.reform()方法。

最後,https代理實現中的坑。http代理傳輸的內容是明文,位元組肯定大於0,而https傳輸的位元組可能小於0。因為這個,傳輸https資料的bybebuff時,要特意指定bytebuff的limit為實際大小。

還有一個小問題,向remoteChannel 寫的時候,有時候會寫0個位元組,原因是底層tcp緩衝滿了,我的處理是等0.1秒,再繼續傳。當然設定OP_WRITE這個監聽選項的目的就是處理這種情況。

http代理不神祕。

可以改進的地方

對channel的讀寫可以加入更多的執行緒來進行。

其實這就是要加入reactor模式了。reactor模式可以看:java設計模式