想起了前两个月同事问我:我发出的请求里如果有”a=f&a=g”,那么在Servlet里获取到的a的值是一个字符串”f,g”,这是怎么回事儿?
当时我就猜测是SpringMVC做的处理,然后启动了一个测试工程,并进行了Debug追踪,最终查询到了Tomcat的源码里,发现居然是它做的解析。
现在想写个博客记录一下,却发现自己已经找不到当时追踪的痕迹了。。。于是查找了一下资料,再次发现原来有两套方案。
参考资料:
tomcat中解析url中的参数或者post中的请求内容
解决URL参数中的%问题
方案一:
Servlet API 2.3之前,Tomcat使用avax.servlet.http.HttpUtils对参数进行解析。代码如下:
/**
*本方法使用Hashtable<String,String[]>数据结构来存储请求参数的键值,对于同名键值对,采用值数组来存储,即一个key对应一个value数组。Hashtable相对于HashMap具有线程安全的优势。
*
*/
public static Hashtable<String,String[]> parseQueryString(String s) {
String valArray[] = null;
if (s == null) {
throw new IllegalArgumentException();
}
Hashtable<String,String[]> ht = new Hashtable<String,String[]>();
StringBuilder sb = new StringBuilder();
StringTokenizer st = new StringTokenizer(s, "&");
while (st.hasMoreTokens()) {
String pair = st.nextToken();
int pos = pair.indexOf('=');
if (pos == -) {
// XXX
// should give more detail about the illegal argument
throw new IllegalArgumentException();
}
String key = parseName(pair.substring(, pos), sb);
String val = parseName(pair.substring(pos+, pair.length()), sb);
if (ht.containsKey(key)) {
String oldVals[] = ht.get(key);
valArray = new String[oldVals.length + ];
for (int i = ; i < oldVals.length; i++)
valArray[i] = oldVals[i];
valArray[oldVals.length] = val;
} else {
valArray = new String[];
valArray[] = val;
}
ht.put(key, valArray);
}
return ht;
}
方案二:
将解析方法放在org.apache.catalina.connector.Request里(Tomcat的实现类),下面我们来分析一下Tomcat对请求参数的解析过程:
- 设立一个标识位:
/**
* Request parameters parsed flag.
*/
protected boolean parametersParsed = false;
其标识了当前请求Request的参数是否已经解析了,如果已经解析过了,则可以使用Request的真实对象“org.apache.coyote.Request”的Parameters属性,否则 需要解析参数并初始化Parameters属性。
如下:
@Override
public String getParameter(String name) {
if (!parametersParsed) {
parseParameters();
}
return coyoteRequest.getParameters().getParameter(name);
}
- 第一次解析请求参数,并初始化Parameters属性:
/**
* Parse request parameters.
*/
protected void parseParameters() {
parametersParsed = true;
//拿到真实对象的Parameters属性,然后初始化之
Parameters parameters = coyoteRequest.getParameters();
boolean success = false;
try {
//省略前置操作代码
byte[] formData = null;
if (len < CACHED_POST_LEN) {
if (postData == null) {
postData = new byte[CACHED_POST_LEN];
}
formData = postData;
} else {
formData = new byte[len];
}
//拿到formData以后 调用Parameters的方法进行参数解析
parameters.processParameters(formData, , len);
} else if ("chunked".equalsIgnoreCase(
coyoteRequest.getHeader("transfer-encoding"))) {
byte[] formData = null;
try {
formData = readChunkedPostBody();
}
if (formData != null) {
parameters.processParameters(formData, , formData.length);
}
}
//省略异常处理代码
success = true;
}
}
- Parameters类的processParameters方法:
public void processParameters( byte bytes[], int start, int len ) {
processParameters(bytes, start, len, getCharset(encoding));
}
private void processParameters(byte bytes[], int start, int len,
Charset charset) {
//省略前置操作代码,主要是将字节转换为字符串,并分割&、=等字符
try {
addParameter(name, value);
} catch (IllegalStateException ise) {
//省略异常处理代码
}
/**
*Parameters类使用LinkedHashMap<String,ArrayList<String>>()来存储键值对
*/
public void addParameter( String key, String value )
throws IllegalStateException {
if( key==null ) {
return;
}
parameterCount ++;
if (limit > - && parameterCount > limit) {
// Processing this parameter will push us over the limit. ISE is
// what Request.parseParts() uses for requests that are too big
parseFailed = true;
throw new IllegalStateException(sm.getString(
"parameters.maxCountFail", Integer.valueOf(limit)));
}
ArrayList<String> values = paramHashValues.get(key);
if (values == null) {
values = new ArrayList<String>();
paramHashValues.put(key, values);
}
values.add(value);
}
- 至此,请求的参数就已经被解析并存储在Request的Parameters对象中去了,以后再次调用Request的getPathParameter(String name)或者getParameterValues(String name)等方法时就会直接从Parameters类中获取了。