同事報案,某支提供檔案下載的 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就可能覺得這些鋸箭法很糟糕...

Post a comment