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