图 3. 一次 HTTP 要求的编码示例(查看大图)

如上图所示一次 HTTP 要求设计到很多地方须要编解码,它们编解码的规则是什么?下面将会重点阐述一下:

URL 的编解码

使用jsp从数据库提取数据一个WEB要求中涉及到的编解码 Node.js

用户提交一个 URL,这个 URL 中可能存在中文,因此须要编码,如何对这个 URL 进行编码?根据什么规则来编码?有如何来解码?如下图一个 URL:

图 4.URL 的几个组成部分

上图中以 Tomcat 作为 Servlet Engine 为例,它们分别对应到下面这些配置文件中:

Port 对应在 Tomcat 的 <Connector port=\"大众8080\"大众/> 中配置,而 Context Path 在 <Context path=\"大众/examples\"大众/> 中配置,Servlet Path 在 Web 运用的 web.xml 中的

<servlet-mapping> <servlet-name>junshanExample</servlet-name> <url-pattern>/servlets/servlet/</url-pattern> </servlet-mapping>

<url-pattern> 中配置,PathInfo 是我们要求的详细的 Servlet,QueryString 是要通报的参数,把稳这里是在浏览器里直接输入 URL 所以是通过 Get 方法要求的,如果是 POST 方法要求的话,QueryString 将通过表单办法提交到做事器端,这个将在后面再先容。

上图中 PathInfo 和 QueryString 涌现了中文,当我们在浏览器中直接输入这个 URL 时,在浏览器端和做事端会如何编码和解析这个 URL 呢?为了验证浏览器是怎么编码 URL 的我们选择 FireFox 浏览器并通过 HTTPFox 插件不雅观察我们要求的 URL 的实际的内容,以下是 URL:HTTP://localhost:8080/examples/servlets/servlet/ 君山 ?author= 君山在中文 FireFox3.6.12 的测试结果

图 5. HTTPFox 的测试结果

君山的编码结果分别是:e5 90 9b e5 b1 b1,be fd c9 bd,查阅上一届的编码可知,PathInfo 是 UTF-8 编码而 QueryString 是经由 GBK 编码,至于为什么会有“%”?查阅 URL 的编码规范 RFC3986 可知浏览器编码 URL 是将非 ASCII 字符按照某种编码格式编码成 16 进制数字然后将每个 16 进制表示的字节前加上“%”,以是终极的 URL 就成了上图的格式了。

默认情形下中文 IE 终极的编码结果也是一样的,不过 IE 浏览器可以修正 URL 的编码格式在选项 -> 高等 -> 国际里面的发送 UTF-8 URL 选项可以取消。

从上面测试结果可知浏览器对 PathInfo 和 QueryString 的编码是不一样的,不同浏览器对 PathInfo 也可能不一样,这就对做事器的解码造成很大的困难,下面我们以 Tomcat 为例看一下,Tomcat 接管到这个 URL 是如何解码的。

解析要求的 URL 是在 org.apache.coyote.HTTP11.InternalInputBuffer 的 parseRequestLine 方法中,这个方法把传过来的 URL 的 byte[] 设置到 org.apache.coyote.Request 的相应的属性中。
这里的 URL 仍旧是 byte 格式,转成 char 是在 org.apache.catalina.connector.CoyoteAdapter 的 convertURI 方法中完成的:

1 protected void convertURI(MessageBytes uri, Request request) 2 throws Exception { 3 ByteChunk bc = uri.getByteChunk(); 4 int length = bc.getLength(); 5 CharChunk cc = uri.getCharChunk(); 6 cc.allocate(length, -1); 7 String enc = connector.getURIEncoding(); 8 if (enc != null) { 9 B2CConverter conv = request.getURIConverter(); 10 try { 11 if (conv == null) { 12 conv = new B2CConverter(enc); 13 request.setURIConverter(conv); 14 } 15 } catch (IOException e) {...} 16 if (conv != null) { 17 try { 18 conv.convert(bc, cc, cc.getBuffer().length - 19 cc.getEnd()); 20 uri.setChars(cc.getBuffer(), cc.getStart(), 21 cc.getLength()); 22 return; 23 } catch (IOException e) {...} 24 } 25 } 26 // Default encoding: fast conversion 27 byte[] bbuf = bc.getBuffer(); 28 char[] cbuf = cc.getBuffer(); 29 int start = bc.getStart(); 30 for (int i = 0; i < length; i++) { 31 cbuf[i] = (char) (bbuf[i + start] & 0xff); 32 } 33 uri.setChars(cbuf, 0, length); 34 }

从上面的代码中可以知道对 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 办法通报的参数进行解码,但是它们的解码字符集有可能不一样。
POST 表单的解码将在后面先容,QueryString 的解码字符集是在哪定义的呢?它本身是通过 HTTP 的 Header 传到做事真个,并且也在 URL 中,是否和 URI 的解码字符集一样呢?从前面浏览器对 PathInfo 和 QueryString 的编码采纳不同的编码格式不同可以预测到解码字符集肯定也不会是同等的。
的确是这样 QueryString 的解码字符集要么是 Header 中 ContentType 中定义的 Charset 要么便是默认的 ISO-8859-1,要利用 ContentType 中定义的编码就要设置 connector 的 <Connector URIEncoding=”UTF-8” useBodyEncodingForURI=”true”/> 中的 useBodyEncodingForURI 设置为 true。
这个配置项的名字有点让人产生稠浊,它并不是对全体 URI 都采取 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 通报到做事真个。
当我们在页面上点击 submit 按钮时浏览器首先将根据 ContentType 的 Charset 编码格式对表单填的参数进行编码然后提交到做事器端,在做事器端同样也是用 ContentType 中字符集进行解码。
以是通过 POST 表单提交的参数一样平常不会涌现问题,而且这个字符集编码是我们自己设置的,可以通过 request.setCharacterEncoding(charset) 来设置。

其余针对 multipart/form-data 类型的参数,也便是上传的文件编码同样也是利用 ContentType 定义的字符集编码,值得把稳的地方是上传文件是用字节流的办法传输到做事器确当地临时目录,这个过程并没有涉及到字符编码,而真正编码是在将文件内容添加到 parameters 中,如果用这个编码不能编码时将会用默认编码 ISO-8859-1 来编码。

HTTP BODY 的编解码

当用户要求的资源已经成功获取后,这些内容将通过 Response 返回给客户端浏览器,这个过程先要经由编码再到浏览器进行解码。
这个过程的编解码字符集可以通过 response.setCharacterEncoding 来设置,它将会覆盖 request.getCharacterEncoding 的值,并且通过 Header 的 Content-Type 返回客户端,浏览器接管到返回的 socket 流时将通过 Content-Type 的 charset 来解码,如果返回的 HTTP Header 中 Content-Type 没有设置 charset,那么浏览器将根据 Html 的 <meta HTTP-equiv=\"大众Content-Type\公众 content=\"大众text/html; charset=GBK\"大众 /> 中的 charset 来解码。
如果也没有定义的话,那么浏览器将利用默认的编码来解码。

其它须要编码的地方

除了 URL 和参数编码问题外,在做事端还有很多地方可能存在编码,如可能须要读取 xml、velocity 模版引擎、JSP 或者从数据库读取数据等。

xml 文件可以通过设置头来制订编码格式

<?xml version=\"大众1.0\"大众 encoding=\"大众UTF-8\公众?>

Velocity 模版设置编码格式:

services.VelocityService.input.encoding=UTF-8

JSP 设置编码格式:

<%@page contentType=\"大众text/html; charset=UTF-8\"大众%>

访问数据库都是通过客户端 JDBC 驱动来完成,用 JDBC 来存取数据要和数据的内置编码保持同等,可以通过设置 JDBC URL 来制订如

MySQL:url=\公众jdbc:mysql://localhost:3306/DB?useUnicode=true&characterEncoding=GBK\"大众