最近因為有客戶要求將網站部署到 Windows Azure WebSites 雲端平台,原本在開發環境測試都沒什麼狀況,反而是部署到 Windows Azure WebSites 之後才發現一個棘手的問題,一般網頁瀏覽並不會有問題,而是當使用 IE 瀏覽器,且有表單要送出資料時就會發生 502 - Web server received an invalid response while acting as a gateway or proxy server. 的問題,這才發現一個 ASP.NET MVC 的小臭蟲(Bug)。
※ 問題描述
這問題發生的機會並不高,因為我整個網站都用「中文」命名,所有 Controller 的名字都是中文,例如:
- 首頁Controller
- 會員專區Controller
- 最新消息Controller
基本上用了中文當作 Controller 名稱並不會有什麼問題,只要 URL 格式符合標準即可,也就是只要有適當的 UrlEncode 處理就能正常運作。
我們先來看看 ASP.NET MVC 的表單頁面宣告如下,我們使用 Html.BeginForm() 預設的第一個多載,也就是不傳入任何參數的版本:
@using (Html.BeginForm())
{
@Html.TextBox("Username")
<input type="submit" />
}
這個表單最後輸出的 HTML 如下:
<form action="/最新消息/Create" method="post">
<input id="Username" name="Username" type="text" value="" />
<input type="submit" />
</form>
你可以發現,在表單的 action 屬性中出現了「最新消息」這段中文字,基本上,直接傳送 UTF-8 文字編碼的網址到 IIS 伺服器,在大部分的情況下並不會有問題,只有在特定條件下才會導致錯誤發生,其分析如下。
發生 502 - Web server received an invalid response while acting as a gateway or proxy server. 這個問題的主因並不在「網址列」,而是在 HTTP 要求標頭中的 Referer 標頭 (Header) 出現了不合法的字元,也就是這裡出現了「沒有 URL 編碼過的網址」,進而引發錯誤:
POST /最新消息/Create HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Referer: http://example.azurewebsites.net/最新消息/Create
Accept-Language: zh-TW,en-US;q=0.5
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
Proxy-Connection: Keep-Alive
Content-Length: 12
DNT: 1
Host: example.azurewebsites.net
Pragma: no-cache
Cookie: ARRAffinity=f3c3d0f49d36fdfaf7d19292e1acb191ff9ac55c9c32f176e8f66d8f87fd73f4
你可能會想問說,IIS 遇到出現有中文的 HTTP Headers 就會掛掉嗎?其實並不是 IIS 的問題,而是因為 Windows Azure WebSites 服務都是用靠 URL Rewrite 與 Application Request Routing 這兩個模組來負責 Reverse Proxy 的工作(主要用途是做負載平衡),而這兩個模組遇到不合法的 HTTP Headers 就會回報 HTTP 502 的錯誤!
除了這個問題外,我也因為使用了 Ajax.BeginForm 輔助方法進而發現了另一個問題,因為瀏覽器會自動修正無效的網址列編碼,所以當表單的 action 屬性中的網址沒有做 URL 路徑編碼 (UrlPathEncode) 的話,瀏覽器還是會自動幫你做編碼,不過 URL 編碼只是一種「編碼方法」,他並沒有指明說要用什麼「文字編碼」來做 UrlEncode,在所有「非 IE 瀏覽器」裡,大家都會用 UTF-8 當成預設的文字編碼,但在 IE 瀏覽器之中,除了 IE10 比較標準外,IE9 以下的瀏覽器版本,全部都會依據作業系統的系統地區設定來自動決定編碼,當我們安裝「中文 (台灣)」時,文字編碼自動選擇 Big5 編碼,而這個 Big5 編碼的文字再經過 UrlEncode 編碼之後,才會傳到伺服器端。
如果你的伺服器上的系統地區設定是「中文 (台灣)」的話,程式也完全不會出問題,IIS 可以順利的解析所有文字編碼的問題。但是我們這次遇到的問題卻是因為網站要部署到 Windows Azure WebSites 雲端平台,而這些雲端平台上的主機,系統地區設定都設定為「英文 (美國)」,也因此當用戶端傳送 Big5 與 UrlEncode 編碼過的網址時,就無法正確解碼了,也因此發生 HTTP 404 錯誤,且只有使用 IE9 以下的瀏覽器版本才會出現這個問題!
※ 解決之道
解決這個問題,就是讓表單的 action 屬性中的網址路徑先經過 UrlPathEncode 編碼,在 .NET 底下,預設都是以 Unicode 做編碼,因此透過 .NET 先行編碼,再輸出到 HTML 裡,就可以確保輸出的網址路徑一定是在 Unicode 的文字編碼下做的 UrlPathEncode 結果,如此一來,不同瀏覽器之下發出的表單要求就可以一致,解決不同瀏覽器與版本之間的相容性問題。
在 ASP.NET MVC 的 View 裡,不需要對網址路徑特別做 UrlPathEncode 編碼,本文一開始我就講到,這應該算是 ASP.NET MVC 的一個小臭蟲(Bug),而且是只有當使用 Html.BeginForm() 且不傳入任何參數時才會導致表單輸出的 action 屬性內容沒有經過 UrlPathEncode 編碼。也就是說,只要用其他的多載,就可以解決這個問題,在這裡,我的解決方式是使用 FormExtensions.BeginForm Method (HtmlHelper, Object) 這個多載,也就是傳入一個 routeValues 物件,範例程式如下:
@using (Html.BeginForm(new {
action = this.ViewContext.RouteData.Values["action"].ToString() }))
{
@Html.TextBox("Username")
<input type="submit" />
}
上述程式的 HTML 輸出的結果如下:
<form action="/%E6%9C%80%E6%96%B0%E6%B6%88%E6%81%AF/Create" method="post">
<input id="Username" name="Username" type="text" value="" />
<input type="submit" />
</form>
如此一來,問題便迎刃而解。