您的位置:首页 > 理论基础 > 计算机网络

Tomcat如何解析URL的请求参数(追踪HttpServletRequest对于请求参数的解析过程)

2017-02-09 15:54 696 查看
想起了前两个月同事问我:我发出的请求里如果有”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 == -1) {
// XXX
// should give more detail about the illegal argument
throw new IllegalArgumentException();
}
String key = parseName(pair.substring(0, pos), sb);
String val = parseName(pair.substring(pos+1, pair.length()), sb);
if (ht.containsKey(key)) {
String oldVals[] = ht.get(key);
valArray = new String[oldVals.length + 1];
for (int i = 0; i < oldVals.length; i++)
valArray[i] = oldVals[i];
valArray[oldVals.length] = val;
} else {
valArray = new String[1];
valArray[0] = 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, 0, len);
} else if ("chunked".equalsIgnoreCase(
coyoteRequest.getHeader("transfer-encoding"))) {
byte[] formData = null;
try {
formData = readChunkedPostBody();
}
if (formData != null) {
parameters.processParameters(formData, 0, 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 > -1 && 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>(1);
paramHashValues.put(key, values);
}
values.add(value);
}


至此,请求的参数就已经被解析并存储在Request的Parameters对象中去了,以后再次调用Request的getPathParameter(String name)或者getParameterValues(String name)等方法时就会直接从Parameters类中获取了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  tomcat