前言:
之前写Java服务端处理POST请求时遇到了请求体转换成字符流所用编码来源的疑惑,在doPost方法里通过request.getReader()获取的BufferedReader对象内部的
Reader用的是什么编码将字节流转换成字符流的呢?又是在哪里设置呢和什么时候生效的呢?通过查找资料,我了解到通过HttpServletRequest对象获得请求体数据
有三种方法,其中两种是不管HTTP请求头设置Content-Type为何值都能够在不重复获取输入流的前提下获取到数据的,一个是request.getInputStream(),一个是request.getReader();
对于前者我们可以在其上面套一个InputStreamReader并设置编码便能正确读取出字符数据,但是对于后者猜测是通过request.setCharacterEncoding(charsetName);来设置;但是当时
挺想知道这两句代码是怎么关联起来的,于是就开始了读源码的过程。
步骤:
最开始的时候我是想通过request.getReader()来找出答案,于是通过打印request.getClass().toString(),知道了request对象真正的类是org.apache.catalina.connector.RequestFacade,
通过名字最终找出这个类是Tomcat安装目录中lib目录下的catalina.jar,导入到项目找出RequestFacade.getReader()的源码为:
public BufferedReader getReader() throws IOException { if (this.request == null) { throw new IllegalStateException(sm.getString("requestFacade.nullRequest")); } else { return this.request.getReader(); } }
然后找出this.request的类是org.apache.catalina.connector.Request,通过RequestFacade构造方法初始化,接着找到org.apache.catalina.connector.Request.getReader()的代码为:
public BufferedReader getReader() throws IOException { if (this.usingInputStream) { throw new IllegalStateException(sm.getString("coyoteRequest.getReader.ise")); } else { this.usingReader = true; this.inputBuffer.checkConverter(); if (this.reader == null) { this.reader = new CoyoteReader(this.inputBuffer); } return this.reader; } }
这里注意this.inputBuffer.checkConverter();这里将会把request.setCharacter(charsetName)设置的编码应用在字节流转换为字符串的过程上,这个过程后面再讲。
我们先看new CoyoteReader(this.inputBuffer);由于CoyoteBuffer是继承自BufferedReader,故真正将字节流转换为字符流的应该是this.inputBuffer,
查看代码得知它的类型为:org.apache.catalina.connector.InputBuffer,类定义为:
public class InputBuffer extends Reader implements ByteInputChannel, CharInputChannel, CharOutputChannel { 。。。。。 }
由于它和InputStreamReader有共同的父类Reader,故我猜测将字节流转换成字符流的应该就是InputBuffer类了,但是线索到了就断了我不知道接下来该看哪里了(后来理清思路后发现其实应该往上找看InputBuffer是在哪创建及赋值的),
于是我回到最初的猜测,request.getReader()是通过request.setCharacterEncoding(charsetName)来实现的;通过查看request.setCharacterEncoding(charsetName)源码
得知RequestFacade设置字符编码是通过内部的org.apache.catalina.connector.Request,而这个Request又是通过内部的org.apache.coyote.Request来实现的,导入所需jar包:tomcat-coyote.jar
其中coyoteRequest.setCharacterEncoding(charsetName)的代码为:
public void setCharacterEncoding(String enc) { this.charEncoding = enc; }
到了这里后线索又断了,我只知道最初RequestFacade设置的编码最终是保存在org.apache.catalina.connector.Request里,但是这个编码是什么时候用到了InputBuffer上就不知道了。
趁着这阶段还弄清楚了RequestFacade无论是设置编码、获得编码、getContentLength()等方法本质上都是通过org.apache.coyote.Request来最终实现的。
回到正题,线索断了以后我后来通过找到是哪里new了InputBuffer及是哪里给InputBuffer设置编码和字节流等思考继续回到了org.apache.catalina.connector.Request类的定义里,
通过搜索发现org.apache.catalina.connector.Request内部的this.inputBuffer是在构造方法里创建的,但是只有一个空壳,而RequestFacade.getInputStream()最终也是以this.inputBuffer作为了
字节流的参数new CoyoteInputStream(this.inputBuffer);故它可能本身既能读取字符流又能读取字节流,即它是存储着第一手的数据。
接着找到了org.apache.catalina.connector.Request中的一个方法: