原來的測試程序,在Response.Flush()之后,調用Response.End(),而出錯的程序在Response.Flush()之后,調用Response.Close()。直接將Close調用改為End后,問題消失。看來問題的根源就在這里了。
MSDN對兩個方法給出的注釋是:
- Close斷開客戶端的連接。
- End結束當前頁面的執行。
看來確有不同。通過查看源碼,Close 是服務器主動斷開連接,然后設置客戶端斷開連接的標志,沒有其他的操作。而 End 會再次 Flush 當前的內容,然后設置頁面結束的標志,然后引發 EndRequest 事件,並不立即斷開客戶端連接。
正是由於采取了不同的方法,導致最終的結果不同。Close 采用強硬手段,直接斷開客戶端連接,也就使得 xmlhttp 組件不知道內容是否已經傳完,於是導致取不到內容。而 End 采用常規方法,一步步操作,在 EndRequest 中明確告知后續操作自己正常結束,之后由后續操作正常斷開客戶端。說到這里,就不能不說,這跟.Net中http請求的生命周期相關。在http請求的生命周期中,Close 和 End 就像馬拉松的折返點一樣,Close 相當於到了折返點就直接坐汽車回起點了,而 End 相當於到了折返點繼續跑回去。那么最終的結果肯定是不同的。
到目前為止,還是沒有解釋為什么啟用IIS壓縮功能之前是正常的,啟用壓縮功能之后就出錯了呢?其實我們已經很接近真相了。因為啟用壓縮之后,服務器必須明確告訴客戶端,我傳給你的內容是經過壓縮的,壓縮方法是GZIP。這樣客戶端才能正確解析內容。而這個信息是放在http響應頭的 Content-Encoding 中的。由於 Close 主動斷開了連接,使得IIS沒有機會加上這個響應頭信息。所以就造成了采用Close方式時,xmlhttp 組件獲取不到這個信息。由於沒啟用壓縮之前,內容已經被接收完畢,並且xmlhttp可以正常解析,所以在啟用壓縮之前,即使缺少 Content-Encoding 信息,功能也是正常的。啟用壓縮之后,xmlhttp 理解不了接收到的數據,所以就出錯了。