兩年前用(yòng).net 2.0做了一個反向代理(lǐ)服務(wù)器,在這兩年時間裏,不斷修改BUG以及優化性能(néng),使得可(kě)用(yòng)性大大提高。近來碰到一個功能(néng)需求,實在無法找出有(yǒu)效的解決辦(bàn)法,隻好上來請教各位高人。 先說說反向代理(lǐ)的工(gōng)作(zuò)機理(lǐ)吧。 1、客戶端通過浏覽器訪問反向代理(lǐ)的時候,會發出一個HTTP請求,反向代理(lǐ)收到這個TCP連接的時候,建立一個新(xīn)的會話用(yòng)于處理(lǐ)這個請求(BeginAccept、EndAccept); 2、會話對象建立一個從客戶端接收數據的委托,開始異步讀取數據(BeginRead); 3、取得數據時,進入異步讀取的回調函數中(zhōng),開始處理(lǐ)數據(EndRead); 4、檢查反向代理(lǐ)與服務(wù)器的連接是否已建立,如果沒有(yǒu)建立,那麽需要先建立連接(ConnectServer),并建立服務(wù)器的異步讀取委托(BeginRead); 5、把數據異步寫入服務(wù)器(BeginWrite); 6、重新(xīn)建立客戶端異步讀取委托(BeginRead),回到3; 7、收到服務(wù)器返回數據時,處理(lǐ)後,異步寫入客戶端(BeginWrite); 8、重新(xīn)建立服務(wù)器異步讀取委托(BeginRead),回到7;
所有(yǒu)的數據傳輸,都使用(yòng)異步來完成,而隻需要在3和7處為(wèi)業務(wù)編寫數據處理(lǐ)代碼即可(kě)。 實際上,對于反向代理(lǐ)來說,隻需要處理(lǐ)客戶端發來的數據就可(kě)以了,需要把HTTP的HOST頭替換為(wèi)真實服務(wù)器,而對于服務(wù)器響應的數據,隻需要原樣發送給客戶端就可(kě)以了。
在步驟3中(zhōng),我們隻知道當前收到了客戶端發來的數據,而不知道這個數據是不是Http請求頭,或者是完整的Http請求頭。幸好,對于反向代理(lǐ)來說,不需要關心是否是完整的Http請求頭,隻需要檢查是否是Http請求頭,如果是,就修改Host即可(kě)。在這裏,我假設Http請求的第一個數據包肯定是獨立的數據包,不會“粘”在TCP連接中(zhōng)上一次數據的後面,這樣就可(kě)以直接使用(yòng)Http協議規定的格式來檢查這個數據包是否Http請求頭了。雖然這個假設沒有(yǒu)什麽依據,但它确實非常有(yǒu)效。
程序就這樣工(gōng)作(zuò)了兩年,沒有(yǒu)什麽問題。
但接下來,問題就出現了,有(yǒu)一個需求,要求能(néng)夠把服務(wù)器返回的頁(yè)面中(zhōng)的某個字符串替換為(wèi)指定的字符串。比如我用(yòng)反向代理(lǐ)指向博客園,我就需要把博客園頁(yè)面中(zhōng)所有(yǒu)使用(yòng)了絕對路徑的連接修改為(wèi)指向反向代理(lǐ)服務(wù)器的連接。這就要求在步驟7這裏處理(lǐ)數據,把數據轉為(wèi)字符串,然後替換鏈接,然後才發往客戶端。
但步驟7每次收到的數據隻是一個片段,而不是整個頁(yè)面的HTML。即使我們再次假設Http響應的第一個數據包是獨立的數據包,也隻能(néng)識别哪些是響應頭,哪些是數據體(tǐ)而已。也想過每一段數據轉為(wèi)這一段的字符串進行處理(lǐ),但是,如果剛好某個字符被網絡層拆分(fēn)到兩個TCP數據包裏怎麽辦(bàn)?還有(yǒu),想博客園這樣使用(yòng)了gzip的,如果不接受完整個頁(yè)面的數據,是無法解壓的;就算這兩種情況都不存在,而網絡層剛好在超鏈接的地方拆分(fēn)數據包怎麽辦(bàn)?
因此,最保守的做法就是拿(ná)到整個頁(yè)面數據再開始處理(lǐ)。也想過Http響應頭那裏有(yǒu)個Content-Length指明内容長(cháng)度的,但實際中(zhōng),很(hěn)多(duō)響應根本就不到這個段。
我查看過HttpListener類和HttpListenerRequest類,嘗試從中(zhōng)發現它是如何接受完一次請求(響應)的,可(kě)惜這兩個類調用(yòng)了大量NativeAPI,就無法得知了。
還有(yǒu)浏覽器,它又(yòu)是如何得知某次響應是否已經完成的呢(ne)?
還請各位高人多(duō)多(duō)指教!
這個代理(lǐ)已經放到codeplex上,大家有(yǒu)興趣可(kě)以看看:http://www.codeplex.com/XProxy/
|