一次HTTP请求的编解码。

URL的编解码

       注意:这里所说的URL和URI是针对Servlet进行描述的,也就是request.getRequestURL()和request.getRequestURL()返回的URL和URI结果。
       解析请求的URL是在org.apache.coyote.HTTP11.InternalInputBuffer的parseRequestLine方法中进行的,这个方法把传过来的URL的byte[]设置到org.apache.coyote.Request的相应属性中。这里的URL仍然是byte格式,转成char是在org.apache.catalina.connector.CoyoteAdapter的converURI方法中完成的。
       对URL的URI部分进行解码的字符集是在connector的<Connector URIEncoding="UTF-8" />中定义的,如果没有定义,那么将以默认编码ISO-8859-1解析。所以有中文URL时最好把URIEncoding设置成UTF-8编码。
       下面介绍对QueryString(请求参数)的解析过程。以GET方式HTTP请求的QueryString与以POST方式HTTP请求的表单参数都是作为Parameters保存的,都通过request.getParameter获取参数值。对他们的解码是在request.getParameter方法第一次被调用时进行的。request.getParameter方法被调用时将会调用org.apache.catalina.connector.Request的parseParameters方法。这个方法将会对GET和POST方式传递的参数进行解码,但是他们的解码字符集有可能不一样。QueryString的解码字符集是在哪里定义的呢。他本身是通过HTTP的Header传到服务端的,并且也在URL中,但是否和URI的解码字符集一样呢。从前面浏览器对PathInfo(请求的具体的Servlet)和QueryString的编码采取不同的编码格式可以猜测到解码字符集肯定不会一致。的确是这样,QueryString的解码字符集要么是Header中ContentType定义的Charset,要么是默认的ISO-8859-1,要使用ContentType中定义的编码,就要将connector的<Connector URIEncoding="UTF-8" useBodyEncodingForURI="true" />中的useBodyEncodingForURI设置为true。这个配置项的名字容易让人产生混淆,他并不是对整个URL都采用BodyEncoding进行解码,而仅仅是对QueryString使用BodyEncoding解码,对这一点还要特别注意。

       从上面的URL编码和解码过程来看 ,比较复杂,而且编码和解码并不是我们在应用程序中能完全控制的,所以在我们的应用程序中,应该尽量避免在URL中使用非ASCII字符,不然很可能会碰到乱码问题。当然在我们的服务器端最好设置<Connector/>的URIEncoding和useBodyEncodingForURI两个参数。

HTTP Header的编解码

    当客户端发起一个HTTP请求时,除上面的URL外还可能会在Header中传递其他参数,如Cookie、redirectPath等,这些用户设置的值很可能也会存在编码问题,Tomcat对他们又是怎么解码的呢。
    对Header中的项进行解码也是在调用request.getHeader时进行的。如果请求的Header项没有解码则调用MessageBytes的toString方法,这个方法从byte到char的转化使用的默认编码也是ISO-8859-1,而我们也不能设置Header的其他解码格式,所以如果你设置的Header中有非ASCII字符,解码中肯定会有乱码。
    我们在添加Header时也是同样的道理,不要在Header中传递非ASCII字符,如果一定要传递,可以先将这些字符用org.apache.catalina.util.URLEncoder编码,再添加到Header中,这样在从浏览器到服务器的传递过程中就不会丢失信息了,我们要访问这些项时再按照相应的字符集解码即可。

POST表单的编解码

    前面提到了POST表单提交的参数的解码是在第一次调用request.getParameter时发生的,POST表单的参数传递方式与QueryString不同,他是通过HTTP的BODY传递到服务端的。当我们在页面上单击提交按钮时浏览器首先将根据ContentType的Charset编码格式对在表单中填入的参数进行编码,然后提交到服务器端,在服务器端同样也是用ContentType中的字符集进行解码的。所以通过POST表单提交的参数一般不会出现问题,而且这个字符集编码是我们自己设置的,可以通过request.setCharacterEncoding(charset)来设置。
    注意,你一定要在第一次调用request.getParameter方法之前就设置request.setCharacterEncoding(charset),否则你的POST表单提交上来的数据也可能出现乱码。发现了一个奇怪的现象,就是Tomcat在解析Parameter参数集合之前会获取Header的content-type请求头,并且检查这个content-type中的charset值。在默认情况下浏览器在提交form表单时,提交的content-type是不会含有charset信息。
    所以如果没有设置request.setCharacterEncoding(charset),那么表单提交的数据将会按照系统的默认编码方式解析。
    另外,针对multipart/form-data类型的参数,也就是上传的文件编码,同样也使用ContentType定义的字符集编码。值得注意的地方是,上传文件是用字节流的方式传输到服务器的本地临时目录,这个过程并没有涉及字符编码,而真正编码是在文件内容添加到parameters中时,如果用这个不能编码,则将会使用默认编码ISO-8859-1来编码。

HTTP Body的编解码

    当用户请求的资源已经成功获取后,这些内容将通过Response返回给客户端浏览器。这个过程要先经过编码,再到浏览器进行解码。编解码字符集可以通过response.setCharacterEncoding来设置,他将会覆盖request.getCharactorEncoding的值,并且通过Header的Content-Type返回客户端,浏览器接收到返回的Socket流时将通过Content-Type的charset来解码。如果返回的HTTP Header中Content-Type没有设置charset,那么浏览器将根据HTML的<meta HTTP-equiv="Content-Type" content="text/html; charset=GBK" />中的charset来解码。如果也没有定义,那么浏览器将使用默认的编码来解码。
    访问数据库都是通过客户端JDBC驱动来完成的,用JDBC来存取数据时要和数据内置编码保持一致,可以通过设置JDBC URL来指定,如MySQL:url="jdbc:mysql://localhost:3306/DB?useUnicode=true&characterEncoding=GBK"。

猜你喜欢

转载自blog.csdn.net/en_joker/article/details/81329544