Java Web乱码分析及解决方案(三)——响应乱码

来源:互联网 发布:win10添加网络驱动器 编辑:程序博客网 时间:2024/05/08 07:25

响应乱码

    请求乱码是客户端向服务器发送数据时,服务器解码错误。响应乱码则是服务器处理完请求后,输出到浏览器的数据被浏览器错误解码造成的显示乱码,这类乱码是最常见也是最直接的。造成这类乱码的情况最直接的一点就是服务器对Content-Type响应报文设置错误。


页面编码:

    我们的页面一般来说,可能是通过下面两种方式生成的,也就是常说的静态页面和动态页面:

    (1)静态页面:我们用记事本或其他IDE工具编写的页面(比如.html),这些页面在编写的时候就需要指定一个字符编码,比如我们指定为ISO-8859-1,那么我们编写中文,肯定会乱码,因为保存的时候就出现了问题。这种情况下,我们一般都会用unicode保存文件(UTF-8)。然后在页面中设置meta标签的content-type为相应编码就不会出现问题了。

    (2)动态页面:程序动态输出的,比如用模板技术或者Java的JSP、Servlet拼装的页面,这类技术一般都会保持一致的编码,比如java用unicode,所以输出的中文是没问题的。我们需要设置的是Content-Type来告诉浏览器页面的编码类型。

     使用JSP模板技术时,有两个参数影响编码,一个是pageEncoding,另一个是contentType,pageEncoding顾名思义指定的是页面编码,这个编码影响的是JSP文件编译为java和class文件时,容器读取JSP文件内容时使用的编码,比如,我们的JSP文件用UTF-8保存的,但是pageEncoding使用的是ISO-8859-1,那么JSP被预编译为class文件时就已经乱码了,JSP首先被翻译为java文件,java文件统一使用UTF-8,之后java编译为class文件,统一使用unicode编码(UTF-8),然后Web容器执行class文件输出HTTP包体到客户端,这个时候使用的编码是contentType指定的编码。

    虽然pageEncoding和contentType的关系有点乱,但是一般来说它们的设定是有关联的,很多Web容器在设定了一个属性后,另外一个属性也会自动设置为一致的,它们两个默认值都是ISO-8859-1。实际开发时,我们都会把这两个属性值设置为一致的。而且出现乱码时,首先确定浏览器实际使用的编码和contentType是否一致,如果一致,调整pageEncoding的值一般都可以解决乱码的。


Content-Type响应报头

    页面在服务端构成之后,我们就要传输给浏览器了,这个时候服务器会首先构建响应报头,有很多响应报头是容器自己处理的,比如Content-Language、Content-Length等。在众多的报头中有一个很famous很imporant的就是Context-Type报头。

    这个报头告诉客户端实体主体是什么媒体类型(MIME),Web浏览器通过Content-Type来确定用什么模块处理响应的主体,同时通过附加的charset参数确定文件的编码类型,然后浏览器调用相应的模块和编码方案解码响应主体的文件。

    在服务器端通过调用response. setContentType方法可以设置这个报头的属性。还要注意,我们这里讨论的是HTTP协议,但是很多Web容器不会只支持HTTP协议,HTTP协议只是它的一种情况。

 

setContentType、setCharsetEncoding和setLocale

    response的API开头就有如下说明:

The charset for the MIME body response canbe specified explicitly using the setCharacterEncoding(java.lang.String) andsetContentType(java.lang.String) methods, or implicitly using thesetLocale(java.util.Locale) method. Explicit specifications take precedenceover implicit specifications. If no charset is specified, ISO-8859-1 will beused. The setCharacterEncoding, setContentType, or setLocale method must becalled before getWriter and before committing the response for the characterencoding to be used.

See the Internet RFCs such as RFC 2045 formore information on MIME. Protocols such as SMTP and HTTP define profiles ofMIME, and those standards are still evolving.

 

    意思是,影响Response Body的字符编码的方法有三个,setContentType、setCharsetEncoding已经setLocale。(后面附注这三个方法的API说明,一手资料很重要)

    通过查看API可以知道,setContentType直接设定的就是Content-Type,可以顺带指定charset。setCharsetEncoding可以不调用,调用后,相当与设定Content-Type的字符编码部分。setLocale不重要,可以不理它,它设定的是Content-Language,如果设定了Content-Type它就失效了。而且调用SetContentType设置为text/html;charset=UTF-8就可以不调用setCharsetEncoding了(小心框架做了修改)。

 

出现乱码:

    根据上面说的,响应出现乱码,修改两个地方,一是调用Response.setContentType设定MIME和字符编码,二修改页面的HTML标签。二者保持一致就可以了。对于静态页面要确定文件保存时的编码和HTML标签指定的编码要一致。对于JSP文件还要记得多调整一个pageEncoding。

 

Tomcat官方给出的建议:

    Tomcat在它的官方文档中给出了避免乱的建议,原文如下:

What canyou recommend to just make everything work? (How to use UTF-8 everywhere).

Using UTF-8 asyour character encoding for everything is a safe bet. This should work forpretty much every situation.

In order to completelyswitch to using UTF-8, you need to make the following changes:

   1.Set URIEncoding="UTF-8" onyour <Connector> in server.xml.References: HTTPConnectorAJPConnector.

   2.Use a characterencoding filter withthe default encoding set to UTF-

   3.Change all your JSPs to include charset name in their contentType.

     For example, use <%@page contentType="text/html; charset=UTF-8" %> forthe usual JSP pages and <jsp:directive.page contentType="text/html; charset=UTF-8" /> forthe pages in XML syntax (aka JSP Documents).

   4.Change all your servlets to set the content type for responses and to include charset name in the content type to be UTF-8.

Use response.setContentType("text/html; charset=UTF-8") or response.setCharacterEncoding("UTF-8").

   5.Change any content-generation libraries you use (Velocity, Freemarker, etc.) to use UTF-8 and to specify UTF-8 in the content type of the responses that they generate.

   6.Disable any valves or filtersthat may read request parameters before your character encoding filter or jsppage has a chance to set the encoding to UTF-8. For more information see http://www.mail-archive.com/users@tomcat.apache.org/msg21117.html.

 

    意思是:在整改工程中使用UTF-8作为字符集,它能在所有situation中都完美的工作,为了能彻底的使用UTF-8,你需要做如下的设定:

    (1)在server.xml的connector中设置URIEncoding=“UTF-8”;

    (2)使用Tomcat提供的CharsetEncodingFilter(和Spring MVC的差不多);

    (3)在JSP页面的page指令中设定content-type=”text/html;charset=utf-8”; 或者jsp:directive.page contentType="text/html; charset=UTF-8" 

    (4)在所有Servlet中都调用setContentType为“text/html;charset=utf-8”或者setCharsetEncoding方法设定Content-Type的编码为UTF-8,

    (5)设置所有模板技术(比如FreeMarker、Velocity等)使用UTF-8,或明确的指定响应头的Content-Type为UTF-8;

    (6)在你的字符编码过滤器工作前,不要让任何的其他Filter读取Request的参数。

 

总结:

    页面出现乱码的问题就是浏览器对页面的解析使用的不是指定的字符集,这个时候可以先通过右键菜单看下浏览器对当前页面使用的那种字符集,然后对比下是不是Content-Type设定出现了错误,如果没错,确定下HTML标签的设定是不是出现了问题,如果两种都没问题,就要确定是不是文件保存时使用的不是指定字符集。

 

附注——Response的API

response.setContetType

Sets the content type of the response beingsent to the client, if the response has not been committed yet. The givencontent type may include a character encoding specification, for example, text/html;charset=UTF-8.The response's character encoding is only set from the given content type ifthis method is called before getWriter is called.

This method may be called repeatedly tochange content type and character encoding. This method has no effect if calledafter the response has been committed. It does not set the response's characterencoding if it is called after getWriter has been called or after theresponse has been committed.

Containers must communicate the content typeand the character encoding used for the servlet response's writer to the clientif the protocol provides a way for doing so. In the case of HTTP, the Content-Type headeris used.

    如果response还没有发生,那么在发送response给客户端之前设置response的content type,设置的content type可以包含字符编码,比如:text/html;charset=UTF-8。如果在getWriter被调用之前调用了这个方法,那么response的编码将会根据给定的content type设定。

    这个方法可以被重复的调用来改变content type和字符编码。如果在response提交之后调用,这个方法是无效的。如果在getWriter被调用之后或response提交之后调用这个方法,对response字符编码的设定也不会起作用。

    如果协议提供了和客户端通信的content type的方式,那么容器将使用response的wirter向容器发送关于content type和字符编码的信息。在http协议下使用的是Content-Type报头。

 

setCharsetEncoding

Sets the character encoding (MIME charset)of the response being sent to the client, for example, to UTF-8. If thecharacter encoding has already been set bysetContentType(java.lang.String) or setLocale(java.util.Locale),this method overrides it. Calling setContentType(java.lang.String) withthe String of text/html and calling this method with the String of UTF-8 isequivalent with calling setContentType with the String of text/html;charset=UTF-8.

This method can be called repeatedly tochange the character encoding. This method has no effect if it is called after getWriter hasbeen called or after the response has been committed.

Containers must communicate the characterencoding used for the servlet response's writer to the client if the protocolprovides a way for doing so. In the case of HTTP, the character encoding iscommunicated as part of the Content-Type header for text media types.Note that the character encoding cannot be communicated via HTTP headers if theservlet does not specify a content type; however, it is still used to encodetext written via the servlet response's writer.

 

    在response被发送给客户端之前,设置字符编码类型(MIME的字符),例如,使用“UTF-8”编码。如果字符编码已经通过setContentType或者setLocale设定了,这个方法将会覆盖他们。下面的调用是相等的,调用setContentType设置content Type为text/html,调用setCharsetEncoding设置字符编码为UTF-8,等同于调用setContentType设置content Type为text/html;charset=utf-8

    可以重复调用这个方法,但是必须在getWriter之前或者response被提交之前。

    容器会把字符编码告诉客户端,如果协议提供了相应的通信方式。在HTTP协议下,字符编码作为Content –Type报头的一部分提供给客户端。值得注意的是如果servlet没有指定content type,那么字符编码将不会通过Http协议发送给客户端,但是发送给客户端的文字仍然会用给定的字符集编码。



0 0
原创粉丝点击