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設計模式