5. 使用过滤器
实际中我们处理客户端数据时,大多数时候都是在JavaBean中实现的,我们当然可以在会话Bean中把decoding掺合进去,但没有谁愿意这么做,而事实上我们可能会有很多的Bean,这种做法是维护和更新所不能容许的。我们还可以在JSPs/Servlets中通过ServletRequest.setCharaterEncoding(String encoding)来设置,但在众多JSPs/Servlets中这么做也是件令人讨厌的事。最理想的办法是一劳永逸——只在一个地方进行编码和解码的处理,那就是在过滤器(Filter)中。我们只要在过滤器中对客户提交的数据正确解码了,就不用JSPs/Servlets/JavaBeans来操心了。我们的客户可能是大陆的,也可能是台湾省的,也就是说我们至少还得为区分GB2312和Big5来操心了,最理想的就是让它们提交上来的数据都是UTF-8编码的,那就不用决定decoding了,这里我们可以通过第四种方法来让服务器影响浏览器选择encoding,即设置响应头中的Content-Type,让所有的JSP和Servlet的响应实体的编码都是UTF-8,那么浏览器也就会选择我们所使用的UTF-8来编码提交的数据。
package filters;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
public class EncodingFilter implements Filter
{
private FilterConfig config;
private String defalutEncodeing;
public void init(FilterConfig config)
{
this.config = config;
defalutEncodeing = config.getInitParameter("encoding");
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException,ServletException
{
request.setCharacterEncoding(defalutEncodeing);
String uri = ((HttpServletRequest)request).getRequestURI();
if(uri.indexOf(".jsp") != -1 || uri.indexOf("servlet") != -1)
response.setContentType("text/html;charset=" + defalutEncodeing);
System.out.println("Filter set the encoding of the response to " +
response.getCharacterEncoding());
chain.doFilter(request, response);
}
public void destroy()
{
//...
}
}
在Context的web.xml中进行如下配置:
<filter>
<filter-name>Encoding Filter</filter-name>
<filter-class>filters.EncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>Encoding Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
可用一个test.jsp测试却发现,浏览器的输出并没有像我们预想的那样好,test.jsp源代码如下:
<%@ page pageEncoding="GBK"%>
<%String str = "在JSP中,编码已经被过滤器设置成:" + response.getCharacterEncoding();
System.out.println(str);%>
<%=str%>
它的输出如图3-3和图3-4,在客户端出现了乱码是没办法的事,因为通过服务器的输出我们可以发现在过滤器中,编码的确已经被设置成UTF-8,但在JSP中,编码又被设置成了ISO-8859-1。用过滤器对JSP编码设置失败,所以JSP的输出也失败了,我们看到了乱码。
图 3-3 对过滤器对JSP文件预设置编码失败
图 3-4 服务器输出说明在JSP中编码又被设置为ISO8859-1
但一段Servlet代码却证明了过滤器本来已经成功进行了设置。
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;
import java.io.IOException;
import java.io.PrintWriter;
public class encoding extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
PrintWriter out = response.getWriter();
String str = "在Servlet中,编码是:" + response.getCharacterEncoding();
System.out.println(str);
out.println(str);
out.flush();
}
}
你可以在有过滤器和没有过滤器的情况下做实验,结果应该发现在有过滤器状态下,服务器端和客户端均正确得到了UTF-8的输出,在没有过滤器的状态下,服务器端正确输出编码为ISO-8859-1,客户端却输出了乱码,不管是JSP时的乱码还是无过滤器Servlet的乱码,这些都是可以理解的,因为当没有对JSP明确设置page的contentType指令,JSP引擎自动把它设置为ISO-8859-1,而用ISO-8859-1来编码中文,当然只有得到乱码了。
现在可以说利用过滤器来设置响应实体编码,以达到控制浏览器提交数据的编码的希望是没有意义了,没有谁会用Servlet来输出含Form的HTML页。但在过滤器中设置用户提交的数据的编码码还是有意义的。对于JSP我们还是可以妥协到在每个JSP中来定义contentType,也许在一JSP文件中定义contentType,而其他JSP文件对这个文件使用静态包含是可行的。
<%--encoding.jsp--%>
<%@ page contentType=”text/html;charset=UTF-8”%>
静态包含
<%--form.jsp--%>
<%@ page pageEncoding="GBK"%>
<%@ include file="encoding.jsp"%>
<%String name = request.getParameter("name");%>
<html>
<head>
<title>Form</title>
</head>
<body>
<form method="POST" action="test.jsp">
<label>请输入您的姓名</label>
<input type="text" name="name" size="20">
<input type="submit" value="提交">
</form>
<%if(name != null && !name.equals("")){
//%><p>您的姓名是:<%=name%></p><%
}%>
</body>
</html>
在这里,我们对客户提交的数据的解码是在过滤器Encoding Filter中完成的。form.jsp中的<%@ page pageEncoding="GBK"%>是不能省略到encoding.jsp中去,很明显它只对使用了它的JSP文件有效,而且不会在include指令中传递到包含文件中去的。
6. URI的中文字符串
通常情况下,我们都会尽力避免在URI(Uniform Resource Indentifier,统一资源标识符,定义在RFC2396中)中出现非英文字符,但不是所有的时候都能避免,而且这种避免可能加大我们的开发成本或运行效率。如果想直接用含中文的URL(Uniform Resource Locator,统一资源定位符,定义在RFC1738中,是URI的子集)对服务器上的Web资源进行访问,那是不行的。比如:
http://localhost/我是中国人.html
是不能访问到服务器上的“我是中国人.html”,因为浏览器(我使用的是MSIE6.0b)会无条件对该URL用UTF-8来编码:
http://localhost/%E6%88%91%E6%98%AF%E4%B8%AD%E5%9B%BD%E4%BA%BA.
html
而服务器却用系统缺省编码来解码这个URL,即相当于执行了
java.net.URLDecoder.decoder(url);
这个动作。不过这个方法已经不被赞成使用了(Deprecated),而应该使用新的方法
public String decode(String url, String charset)
服务器使用缺省的GBK来解码URI,而浏览器却很难做到使用GBK来编码URI,JavaScript中有三个用来编码URI的全局函数
l encodeURI(uri)
l encodeURIComponent(uri)
l escape(uri)
前面两个函数出现在IE5.5+中,它们用UTF-8来对参数uri编码,并返回编码的字符串;最后那个函数已经不被赞成使用了(Deprecated),它直接使用“%”加字符的Unicode内码来表示字符,如
escape(“我是中国人”)=%u6211%u662F%u4E2D%u56FD%u4EBA
这样,我们只好借助java.net.URLEncoder来编码URI了,如
<%-- encodeURL.jsp--%>
<%String file = “我是中国人.html”;
url = java.net.URLEncoder.encode(file, "GBK");%>
<a href=”<%=url%>”><%=file%></a>
这样,我们就实现了访问文件名中含中文字符的Web文件。其实这种即增加开发成本,又牺牲服务器效率的做法是没有多大意义的,没有谁会故意非用个中文文件名不可。
在HttpServletRequest中,请求查询字符串(通过方法getQueryString()获得)即不是URI(通过方法getRequestURI()获得)的一部份,也不是URL(通过方法getRequestURIL()获得)的一部份,服务器使用URLDecoder解码URL时,丝毫不对它产生影响,可以说它是被独处理的,而我们前面所使用的过滤器Encoding Filter里面的
request.setCharacterEncoding(defalutEncodeing);
却会对它产生影响。让我们来看一个实验,该实验加载了过滤器Encoding Filter:
<script>
//浏览不会自动编码查询字符串中的非英文字符
function encodingHref(obj)
{
obj.href = encodeURI(obj.href);
}
</script>
<a href="test.jsp?name=胡洲" οnclick="encodingHref(this)">go</a>
test.jsp的源代码如下:
<%@ page contentType="text/html;charset=GBK"%>
pathInfo = <%=request.getPathInfo()%><br>
pathTranslated = <%=request.getPathTranslated()%><br>
contextPath = <%=request.getContextPath()%><br>
queryString = <%=request.getQueryString()%><br>
requestURI = <%=request.getRequestURI()%><br>
requestURL = <%=request.getRequestURL()%><br>
servletPath = <%=request.getServletPath()%><br>
name = <%=request.getParameter("name")%>
输出如图3-5:
图3-5
输出的结果是我们希望的。我们还可以在服务器端使用JSP代码:
<a href=”test.jsp?name=<%=URLEncoder.encode(“胡洲”, “UTF-8”)%>”>go</a>
事先编码好请求的 URI ,使用 JavaScript 代码编码查询字符串和使用 JSP 代码比起来,两者各有所长。