【茶包射手日記】怪異的 .NET 數學運算溢位錯誤
1 |
同事報案,某支提供檔案下載的 ASP.NET 程式,用瀏覽器可正常開啟,但有一支用 HttpWebRequest 存取它的 .NET 程式,原本可以執行,現在則出現 Arithmetic operation resulted in an overflow./數學運算導致溢位,由於下載程式裡幾乎沒什麼加減乘除計算,出現溢位錯誤讓人有點迷惑。
加入 Log 蒐集資訊,查出爆炸點在 byte[] b = new byte[response.ContentLength],進一步印出 ContentLength 找到原因,ContentLength = -1! (下圖是用 LINQPad 重現茶包的現場模擬)
由此我學到一個冷知識 - 在 C# new byte[-1] 的錯誤訊息不是陣列大小不可為負數,而是Arithmetic operation resulted in an overflow./數學運算導致溢位。
回頭追 ASPX 端,這才得知前陣子程式有修改,被改成類似以下寫法:
<%@ Page Language="C#"%>
<script runat="server">
void Page_Load(object sender, EventArgs e)
{
Response.Clear();
Response.Buffer = true;
Response.ContentType = "image/png";
Response.BinaryWrite(SomeFuncToGetByteArray());
Response.Flush();
Response.End();
}
</script>
依據經驗,推測 Response.Flush() 是禍首。實測拿掉 Reponse.Buffer = true 及 Response.Flush() 後,HttpWebRequest 程式即告正常。
來解說發生了什麼事。當程式呼叫 Response.Flush(),ASP.NET 將改以分塊傳輸編碼(Chunked Transfer Encoding)形式傳送內容,Reponse Header 會宣告 Transfer-Encoding: chunked,內容則拆成一到多個區塊分次傳送,最後會附加一個大小為 0 的區塊作為結束。每個區塊的組成為:16 進位內容長度(Bytes) + CRLF + 內容本身 + CRLF。以下用實例比較 Response.End() 與 Response.Flush() 的傳回結果:
AspFlush.aspx
<%@ Page Language="C#"%>
<script runat="server">
void Page_Load(object sender, EventArgs e)
{
Response.ContentType = "text/plain";
Response.Write("我達達的馬蹄是美麗的錯誤\n");
Response.Flush();
Response.Write("我不是歸人,\n");
Response.Flush();
Response.Write("是個過客");
Response.End();
}
</script>
AspEnd.aspx
<%@ Page Language="C#"%>
<script runat="server">
void Page_Load(object sender, EventArgs e)
{
Response.ContentType = "text/plain";
Response.Write("我達達的馬蹄是美麗的錯誤\n");
Response.Write("我不是歸人,\n");
Response.Write("是個過客");
Response.End();
}
</script>
上述兩支 ASPX 用瀏覽器檢視的結果相同,但 Response 結構有很大差異。Response.Flush() 時 Header 會有 Transfer-Encoding: chunked,沒有 Content-Length,接收端需解析區塊長度、組裝內容及判斷結束(換言之,不能單純用 GetResponseStream() 一次抓回);而單純 Response.End() 時,所有內容一次傳完,並會有 Content-Length:
最後,判定程式中為實驗加入的 Response.Flush() 並無必要性,將其還原後問題排除。
A 'Arithmetic operation resulted in an overflow' thrown by new byte[-1] which is caused by Response.Flush() + HttpWebRequest.
Comments
# by Anthony LEE
HttpResponse.Flush() 的實際做法出乎預料...(汗) 但係Google下來就有許多BinaryWrite+Flush的組合拳...(汗) e.g. https://stackoverflow.com/questions/848679/reading-a-binary-file-and-using-response-binarywrite p.s. 另外重溫古文 https://blog.darkthread.net/blog/about-response-flush/ 如果當年有得睇raw output就可能覺得這些鋸箭法很糟糕...