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