struts2 項目,通常開發過程中,一些簡單的表單文件或者靜態描述頁面,能不走action流程就可以用html來做,可以一定程度上減少 struts2 的流程開銷,如果前端有apache的話,也可以進一步分攤業務服務器壓力。按照這樣的原則,一個系統做下來,難免有一些或者很多的靜態html文件。
在我的一個工程里面,所有文件的編碼格式均為 utf-8,包括這些靜態 html 文件。 在 IE 下訪問,頁面顯示完全沒有問題。用 httplook 查看 http 頭信息,也可以看到服務器回送的頁面字符集編碼為正常的 utf-8。 但是用 firefox 瀏覽的時候,所有的動態頁面(*.do)正常,唯獨 html 頁面全部亂碼,在瀏覽器菜單內手動選擇頁面編碼格式為 utf-8,則可以正常顯示中文了。雖然可以暫時解決,但不可能假設所有的用戶每次瀏覽這些靜態html的時候,都會手動去選擇編碼格式。這里必須要有一個治本的辦法。
在firefox內亂碼的html頁面上,右鍵“查看頁面信息”,可以看到http頭信息內的編碼格式為 gb2312,而頁面meta信息內指定的是utf-8。即firefox是按gb2312的缺省行為來解析utf-8編碼的頁面,當然會亂碼。問題就出在,firefox并不會像IE那樣可以根據meta信息覆蓋服務器回送的http頭信息,它是嚴格按照http協議規范的行為方式:按照http頭指定的編碼格式來解析頁面。也就是說,如果http頭回送里面指定了頁面的編碼格式,firefox會忽略meta信息的字符集指定。而IE則以meta優先。
為什么我的服務器輸出html頁面的時候,http頭會回送gb2312的編碼集?我在 web.xml 內加上 SetCharacterEncodingFilter,強制所有輸出字符集為 utf-8,問題依舊。因為項目做了 SEO,用到了 UrlRewriterFilter,便懷疑是這個 filter 在做 url forward 的時候,改變了輸出字符集,可是去掉這個 filter 后,問題還是存在。 于是再考察struts2的 FilterDispatcher,這是一個全局的派發過濾器,struts2的核心派發控制器。 最初的配置如下:
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>

<url-pattern>/**//*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
url-pattern 是 /* 的全范圍映射,當然也包括了 html 后綴文件。這種情況下,項目中任何一個請求,會經過3次filter,首先是SetCharacterFilter、然后是 UrlRewriterFilter、最后是 FilterDispatcher,之前已經排除了 UrlRewriterFilter 的問題。于是修改 FilterDispatcher 的 url-pattern 為 *.do,即只過濾 *.do 的請求,其他請求比如 *.html,則不會經過這個 filter。
這樣修改后,一切都正常了。
當 url-pattern 為 /* 時,所有請求包括對靜態資源文件的請求,都會有FilterDispatcher來接管派發。如果請求類型為靜態資源,則 FilterDispatcher 不會調用 action 處理模塊來接收這個請求,而是簡單的回送靜態資源。在這個回送過程中,FilterDispatcher 做的工作如下:
FilterDispatcher.java

protected void findStaticResource(String name, HttpServletRequest request, HttpServletResponse response) throws IOException
{

if (!name.endsWith(".class"))
{

for (String pathPrefix : pathPrefixes)
{
InputStream is = findInputStream(name, pathPrefix);

if (is != null)
{
Calendar cal = Calendar.getInstance();
// check for if-modified-since, prior to any other headers
long ifModifiedSince = 0;

try
{
ifModifiedSince = request.getDateHeader("If-Modified-Since");

} catch (Exception e)
{
LOG.warn("Invalid If-Modified-Since header value: '" + request.getHeader("If-Modified-Since") + "', ignoring");
}
long lastModifiedMillis = lastModifiedCal.getTimeInMillis();
long now = cal.getTimeInMillis();
cal.add(Calendar.DAY_OF_MONTH, 1);
long expires = cal.getTimeInMillis();

if (ifModifiedSince > 0 && ifModifiedSince <= lastModifiedMillis)
{
// not modified, content is not sent - only basic headers and status SC_NOT_MODIFIED
response.setDateHeader("Expires", expires);
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
is.close();
return;
}
// set the content-type header
String contentType = getContentType(name);

if (contentType != null)
{
response.setContentType(contentType);
}


if (serveStaticBrowserCache)
{
// set heading information for caching static content
response.setDateHeader("Date", now);
response.setDateHeader("Expires", expires);
response.setDateHeader("Retry-After", expires);
response.setHeader("Cache-Control", "public");
response.setDateHeader("Last-Modified", lastModifiedMillis);

} else
{
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Pragma", "no-cache");
response.setHeader("Expires", "-1");
}


try
{
copy(is, response.getOutputStream());

} finally
{
is.close();
}
return;
}
}
}

response.sendError(HttpServletResponse.SC_NOT_FOUND);
}

// 
..


protected InputStream findInputStream(String name, String packagePrefix) throws IOException
{
String resourcePath;

if (packagePrefix.endsWith("/") && name.startsWith("/"))
{
resourcePath = packagePrefix + name.substring(1);

} else
{
resourcePath = packagePrefix + name;
}

resourcePath = URLDecoder.decode(resourcePath, encoding);

return ClassLoaderUtil.getResourceAsStream(resourcePath, getClass());
}
可以看到,由 ClassLoaderUtil.getResourceAsStream 載入靜態資源,然后回送。ClassLoaderUtil 還是用了 xwork 的 lib,struts2并沒有重寫這個類。這個回送過程中,FilterDispatcher 并沒有指定回送字符集,因此輸出頁面會采用服務器默認字符集,當然跟具體操作系統也有關系。目前還沒看到有默認以UTF-8作為服務器字符集的。