JAVA服务端的解码

  
    引起乱码问题的缘由是编码与解码方法的不对称,为了能和客户端正确的交互,需要了解下服务端是何时,何处以及如何对内容进行编解码的。按照请求处理的过程,我们先来一一分析一下Java服务端对HTTP内容的解码过程。
一,URI的解码
  在tomcat中,负责对URI解码的是org.apache.catalina.connector.CoyoteAdapter的convertURI方法。
    protected void convertURI(MessageBytes uri, org.apache.catalina.connector.Request request) throws Exception {ByteChunk bc = uri.getByteChunk();int length = bc.getLength();CharChunk cc = uri.getCharChunk();cc.allocate(length, -1);String enc = this.connector.getURIEncoding();if(enc != null) {B2CConverter bbuf = request.getURIConverter();try {if(bbuf == null) {bbuf = new B2CConverter(enc);request.setURIConverter(bbuf);}} catch (IOException var11) {log.error("Invalid URI encoding; using HTTP default");this.connector.setURIEncoding((String)null);}if(bbuf != null) {try {bbuf.convert(bc, cc);uri.setChars(cc.getBuffer(), cc.getStart(), cc.getLength());return;} catch (IOException var12) {log.error("Invalid URI character encoding; trying ascii");cc.recycle();}}}byte[] var13 = bc.getBuffer();char[] cbuf = cc.getBuffer();int start = bc.getStart();for(int i = 0; i < length; ++i) {cbuf[i] = (char)(var13[i + start] & 255);}uri.setChars(cbuf, 0, length);}

这句代码 String enc = this.connector.getURIEncoding()获取到了connector所设置的编码,如果取到了设置的编码则用B2CConverter的convert,将byte数组转换成char数组。若没有设置编码则用默认的解码方案,即认为它是 ISO-8859-1 编码的。设置URI的编码是在servlet.xml配置文件的Connector中:

    <Connector port="8080"   protocol="HTTP/1.1" URIEncoding=”UTF-8”/>
二,查询参数的解码
  参数的解码发生在第一次获取参数的时候,即调用HttpServletRequest的getParameter 、getParameterMap 、getParameterNames 和getParameterValues方法。tomcat提供给servlet的HttpServletRequest的实现是Catalina的org.apache.catalina.connector.RequestFacade。这个外观Request持有一个Catalina内部的org.apache.catalina.connector.Request对象,获取查询字符串的操作会直接委托给内部的Request。看看这个Request得getParameter方法:
    public String getParameter(String name) {if(!this.parametersParsed) {this.parseParameters();}return this.coyoteRequest.getParameters().getParameter(name);}

若目前还没对参数进行解析,则会通过parseParameters方法,解析出所有的参数值。见如下代码:

    protected void parseParameters() {this.parametersParsed = true;Parameters parameters = this.coyoteRequest.getParameters();String enc = this.getCharacterEncoding();boolean useBodyEncodingForURI = this.connector.getUseBodyEncodingForURI();if(enc != null) {parameters.setEncoding(enc);if(useBodyEncodingForURI) {parameters.setQueryStringEncoding(enc);}} else {parameters.setEncoding("ISO-8859-1");if(useBodyEncodingForURI) {parameters.setQueryStringEncoding("ISO-8859-1");}}parameters.handleQueryParameters();if(!this.usingInputStream && !this.usingReader) {if(this.getMethod().equalsIgnoreCase("POST")) {String contentType = this.getContentType();if(contentType == null) {contentType = "";}int semicolon = contentType.indexOf(59);if(semicolon >= 0) {contentType = contentType.substring(0, semicolon).trim();} else {contentType = contentType.trim();}if("application/x-www-form-urlencoded".equals(contentType)) {int len = this.getContentLength();if(len > 0) {int formData = this.connector.getMaxPostSize();if(formData > 0 && len > formData) {if(this.context.getLogger().isDebugEnabled()) {this.context.getLogger().debug(sm.getString("coyoteRequest.postTooLarge"));}return;}Object e = null;byte[] e1;if(len < CACHED_POST_LEN) {if(this.postData == null) {this.postData = new byte[CACHED_POST_LEN];}e1 = this.postData;} else {e1 = new byte[len];}try {if(this.readPostBody(e1, len) != len) {return;}} catch (IOException var11) {if(this.context.getLogger().isDebugEnabled()) {this.context.getLogger().debug(sm.getString("coyoteRequest.parseParameters"), var11);}return;}parameters.processParameters(e1, 0, len);} else if("chunked".equalsIgnoreCase(this.coyoteRequest.getHeader("transfer-encoding"))) {Object formData1 = null;byte[] formData2;try {formData2 = this.readChunkedPostBody();} catch (IOException var10) {if(this.context.getLogger().isDebugEnabled()) {this.context.getLogger().debug(sm.getString("coyoteRequest.parseParameters"), var10);}return;}if(formData2 != null) {parameters.processParameters(formData2, 0, formData2.length);}}}}}}

下面具体分析下参数的解析的过程。首先,获得charEncoding, 得到 charEncoding的规则就是:若已经显示指定(通过Request的api)则使用指定的值,若未指定则尝试获取ContentType头部中指定的字符集,都获取不到返回null。见Coyote中Request的getCharacterEncoding方法

  public String getCharacterEncoding() {if(this.charEncoding != null) {return this.charEncoding;} else {this.charEncoding = ContentType.getCharsetFromContentType(this.getContentType());return this.charEncoding;}}

然后, 若 charEncoding不为null则将charEncoding设置到Parameters的encoding中。否则将Parameters的encoding设置为默认编码ISO-8859-1。若声明了useBodyEncodingForURI为true,则将Parameters的queryStringEncoding也设置为与encoding相同的编码。useBodyEncodingForURI的使用同样是在Connector配置中:

         <Connector URIEncoding=”UTF-8” useBodyEncodingForURI=”true”/>
   接下来就是利用Parameters处理请求行中的查询字符串
    public void handleQueryParameters() {if(!this.didQueryParameters) {this.didQueryParameters = true;if(this.queryMB != null && !this.queryMB.isNull()) {if(debug > 0) {this.log("Decoding query " + this.decodedQuery + " " + this.queryStringEncoding);}try {this.decodedQuery.duplicate(this.queryMB);} catch (IOException var2) {var2.printStackTrace();}this.processParameters(this.decodedQuery, this.queryStringEncoding);}}}

有上面的方法可以看出, 在解码请求行的查询字符串时使用的编码是queryStringEncoding。若没有设置queryStringEncoding的值,则使用ISO-8859-1, 具体的解码过程在Parameters的urlDecode方法中。

    private String urlDecode(ByteChunk bc, String enc) throws IOException {if(this.urlDec == null) {this.urlDec = new UDecoder();}this.urlDec.convert(bc);String result = null;if(enc != null) {bc.setEncoding(enc);result = bc.toString();} else {CharChunk cc = this.tmpNameC;int length = bc.getLength();cc.allocate(length, -1);byte[] bbuf = bc.getBuffer();char[] cbuf = cc.getBuffer();int start = bc.getStart();for(int i = 0; i < length; ++i) {cbuf[i] = (char)(bbuf[i + start] & 255);}cc.setChars(cbuf, 0, length);result = cc.toString();cc.recycle();}return result;}

解析出来的参数就放在Parameters中,此时只处理完了请求行中的查询参数。对于GET方法只处理请求行就已经足够了,然而POST方法的查询参数还可能出现在请求体中,所以还要进一步的解析。所以接下来检查Content-Type是否为 "application/x-www-form-urlencoded", 只有 "application/x-www-form-urlencoded"类型的POST才会到请求体中解析查询参数。从请求体中读取到相关后使用Parameters的下面方法解析参数

    public void processParameters(byte[] bytes, int start, int len) {this.processParameters(bytes, start, len, this.encoding);}

可以看到, 从请求体中解析查询参数使用的是encoding的编码,同样如果没有设置的话还是采用 ISO-8859-1。经过parseParameters处理过之后,所有的请求参数都已经包含在Parameters中, 查询参数来自于请求行和请求体,同名的参数值会作为数组保存起来。然后可以利用参数的名字通过 Parameters的getParameter方法获取参数的值:

    public String getParameter(String name) {String[] values = this.getParameterValues(name);return values != null?(values.length == 0?"":values[0]):null;}

这个方法虽然只有两行,但是揭示了一个我们用Request的getParameter获取参数的一条重要规则。通常我们在传递参数的时候都是一个key对应一个value,当需要传递数组的时候会使用名字相同的key传递两次。如查询字符串为a=1&a=2,我们在服务端就可以通过getParameterValues方法获取到一个数组,数组的第一个和第二个元素分别为1和2,另外也可以通过getParameter方法获取第一个参数。

      通过上面的分析可知 ,参数解析的编码是可以指定的。通常我们得做法是在Filter中设置查询字符串的编码,如:
    <filter><filter-name>characterEncodingFilter</filter-name><filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class><init-param><param-name>encoding</param-name><param-value>UTF-8</param-value></init-param><init-param><param-name>forceEncoding</param-name><param-value>true</param-value></init-param></filter>

若再与URIEncoding、useBodyEncodingForURI配置参数结合,则可以完全控制服务端的URI与参数解码。

    private String urlDecode(ByteChunk bc, String enc) throws IOException {if(this.urlDec == null) {this.urlDec = new UDecoder();}this.urlDec.convert(bc);String result = null;if(enc != null) {bc.setEncoding(enc);result = bc.toString();} else {CharChunk cc = this.tmpNameC;int length = bc.getLength();cc.allocate(length, -1);byte[] bbuf = bc.getBuffer();char[] cbuf = cc.getBuffer();int start = bc.getStart();for(int i = 0; i < length; ++i) {cbuf[i] = (char)(bbuf[i + start] & 255);}cc.setChars(cbuf, 0, length);result = cc.toString();cc.recycle();}return result;}

JAVA服务端的解码相关推荐

  1. android+okhttp+java服务端(tomcat)+mysql实现登录注册

    先上代码(文章结尾有遇到的问题和对应解决方案) android客户端 >android:app-build.gradle 1.添加okhttp依赖,具体根据自己的okhttp包来决定 depen ...

  2. Flex通信-Java服务端通信实例

    Flex与Java通信的方式有很多种,比较常用的有以下方式: WebService:一种跨语言的在线服务,只要用特定语言写好并部署到服务器,其它语言就可以调用 HttpService:通过http请求 ...

  3. 聊一聊 Java 服务端中的乱象

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 来源:阿里巴巴中间件 查尔斯·狄更斯在<双城记>中写道 ...

  4. 那些年,我们见过的 Java 服务端乱象

    点击上方"方志朋",选择"设为星标" 做积极的人,而不是积极废人 Photo by The Book Tutor @Youtube 文 | 陈昌毅 导读 查尔斯 ...

  5. MobileIMSDK怎样修改服务端核心jar包的源码并替换掉Java服务端的jar包

    场景 MobileIMSDK怎样将Java服务端运行起来以及打成jar包运行: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/11 ...

  6. 人人都能掌握的Java服务端性能优化方案

    转载自 人人都能掌握的Java服务端性能优化方案 作为一个Java后端开发,我们写出的大部分代码都决定着用户的使用体验.如果我们的代码性能不好,那么用户在访问我们的网站时就要浪费一些时间等待服务器的响 ...

  7. 那些年,我们见过的 Java 服务端“问题”

    导读 明代著名的心学集大成者王阳明先生在<传习录>中有云: 道无精粗,人之所见有精粗.如这一间房,人初进来,只见一个大规模如此.处久,便柱壁之类,一一看得明白.再久,如柱上有些文藻,细细都 ...

  8. 那些年,我们见过的Java服务端乱象

    导读 查尔斯·狄更斯在<双城记>中写道:"这是一个最好的时代,也是一个最坏的时代."移动互联网的快速发展,出现了许多新机遇,很多创业者伺机而动:随着行业竞争加剧,互联网 ...

  9. QtJava笔记-Qt与Java进行SSL双向认证(Qt客户端,Java服务端)

    这里使用Java作为服务端,使用Qt作为客户端. 程序运行截图如下: 这里的证书Qt使用的p12,Java使用的jks,看以前的博文生成. 源码打包下载地址: https://github.com/f ...

最新文章

  1. 2014百度面试题目---“求比指定整数大且最小的不重复数”解答
  2. 20175330 数据结构-单链表(选做)
  3. java cellformat_java使用jxl对excel表导出进行样式的设置
  4. 房东收电费1.5犯法吗_深圳房东租客少了,感到很困难压力很大,几栋楼房间空着!...
  5. oracle+去括号,关于001 TK的几个问题,请大家一起讨论一下
  6. 常用的adb命令收集
  7. RUNOOB python练习题 39 数组排序
  8. How to test software requirements specification (SRS)?
  9. RHEL 4 安装单实例oracle 10g
  10. 40线性映射07——线性变换的矩阵表示、线性变换与基的关系、线性变换坐标间的关系、线性变换在不同基下矩阵之间的关系、相似矩阵
  11. Unity3D游戏资源的提取
  12. Windchill 零件操作通用类
  13. HCIA 学习笔记 准备考试
  14. 通信(一) 串口通信
  15. 解决电脑右键菜单反应慢问题
  16. linux执行可执行命令程序ls,linux运行可执行程序命令
  17. 真·电子二胡 (ESP32配合库乐队APP实现的电子制作)
  18. Word无法插入页码怎么办?Word页码选项变灰,无法使用解决方法
  19. 开启VPN和NAT服务
  20. Fiddler抓手机app的包

热门文章

  1. python中plot线条的形状、标记符号和颜色详情
  2. 数据结构循环队列C++实现
  3. 输入一个数n,然后打印出2的n次方
  4. 计算机专业素质拓展日记,拓展活动三年级日记
  5. Angular项目目录结构
  6. Python数据分析-绘图-2-Seaborn进阶绘图-6-回归图
  7. 【Emacs】之 Org-mode
  8. 科研画图都用什么软件
  9. MySQL安装教程包含所有平台(图解),MySQL下载步骤详解(带安装教程)
  10. 第二周 Ubuntu的简单介绍与使用