先前有一篇文章討論ASP.NET如何正確傳回中文下載檔名,網友帆歷經九九八十一難後,終於修成正果,還揪出一隻鬼 --- VS 2005內建的ASP.NET Development Server似乎不支援HeaderEncoding!

為了解開這個謎團,茶包射手再次整裝出發!

這回我們使用的辦案工具是HttpWatch Pro(原因是在我的Vista上Fiddler啟動時的UAC升等視窗都會被埋掉,啟動後會導致IE7 Hang住,最要命的是它抓不到我存取Localhost的記錄,怕明明要射茶包變成修理小提琴,我很有魄力地立刻換了武器),用HttpWatch的錄製封包功能,Dev Server與IIS7的Header差異馬上現形!

ASP.NET Development Server

HTTP/1.1 200 OK
Server: ASP.NET Development Server/9.0.0.0
Date: Sat, 08 Sep 2007 08:07:38 GMT
X-AspNet-Version: 2.0.50727
content-disposition: attachment;filename=銝剜?瑼?.xls
Cache-Control: private
Content-Type: application/octet-stream
Content-Length: 16384
Connection: Close

IIS 7

HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 16384
Content-Type: application/octet-stream
Server: Microsoft-IIS/7.0
X-AspNet-Version: 2.0.50727
content-disposition: attachment;filename=中文檔名.xls
X-Powered-By: ASP.NET
Date: Sat, 08 Sep 2007 08:08:21 GMT

我發現ASP.NET Dev Server傳回的Header中,明明要求用Big5 Encoding,但傳回的卻是亂碼,而依多年來跟中文亂碼打交道的經驗,直覺上像是UTF-8被解成Big5(長度變長,而且有問號)。我寫了兩行Code驗證! Bingo! 就是UTF-8被硬轉成Big5。

由這個現象來推斷,ASP.NET Dev Server並未依我們的要求,在Header區使用Big5 Encoding,而是用UTF-8囉?

要確證這一點,得靠Source Code,但沒有ASP.NET Dev Server的Code怎麼辦? 用Reflector? 可以。但我想到另一招,ASP.NET Dev Server的前身是一個Sample專案Cassini,所以我們可以找它爸爸驗一下DNA吧!

在我的機器上把Cassini Sample Web Server架起來,第一件事先確定是不是有其父必有其子? 下載中文檔名也會失敗?

答案是!!! Cassini傳回的Response Header與ASP.NET Dev Server除了Server Name外,完全相同。

Cassini

HTTP/1.1 200 OK
Server: Microsoft-Cassini/1.0.0.0
Date: Sat, 08 Sep 2007 08:02:56 GMT
X-AspNet-Version: 2.0.50727
content-disposition: attachment;filename=銝剜?瑼?.xls
Cache-Control: private
Content-Type: application/octet-stream
Content-Length: 16384
Connection: Close

追蹤了程式碼,我在Cassini.Connection Class中找到了答案,在Cassini的邏輯中,並不會因為指定Response.HeaderEncoding而變更編碼方式,一律用UTF-8解成byte[]後送至Socket,而在IE端則是以Big5去解析它,符合先前的實驗與分析。Case Closed!

private static String MakeResponseHeaders(
int statusCode, String moreHeaders, int contentLength, 
bool keepAlive) {
    StringBuilder sb = new StringBuilder();
    sb.Append("HTTP/1.1 " + statusCode + " " + 
     HttpWorkerRequest.GetStatusDescription(statusCode) + "\r\n");
    sb.Append("Server: Microsoft-Cassini/"
        +Messages.VersionString+"\r\n");
    sb.Append("Date: " + DateTime.Now.ToUniversalTime().
        ToString("R", DateTimeFormatInfo.InvariantInfo) + "\r\n");
    if (contentLength >= 0)
        sb.Append("Content-Length: " + contentLength + "\r\n");
    if (moreHeaders != null)
        sb.Append(moreHeaders);
    if (!keepAlive)
        sb.Append("Connection: Close\r\n");
    sb.Append("\r\n");
    return sb.ToString();
}
 
public void WriteEntireResponseFromString(
int statusCode, String extraHeaders, String body, 
bool keepAlive) {
    try {
        int bodyLength = (body != null) ? 
            Encoding.UTF8.GetByteCount(body) : 0;
        String headers = 
            MakeResponseHeaders(statusCode, extraHeaders, 
              bodyLength, keepAlive);
        _socket.Send(Encoding.UTF8.GetBytes(headers + body));
    }
    finally {
        if (!keepAlive)
            Close();
    }
}

Comments

# by eric

黑大哥的文章真的讓小弟佩服的五體投地.不論是文章的深度或隨時可以變出來的怪機司,都真的讓小弟佩服不已.國內的高手應該也是不少,但是像黑大哥這樣願意整理分享的實在是少數.在此致上深深的敬意!!

Post a comment