Tomcat如何解析URL的请求参数(追踪HttpServletRequest对于请求参数的解析过程)
2017-02-09 15:54
696 查看
想起了前两个月同事问我:我发出的请求里如果有”a=f&a=g”,那么在Servlet里获取到的a的值是一个字符串”f,g”,这是怎么回事儿?
当时我就猜测是SpringMVC做的处理,然后启动了一个测试工程,并进行了Debug追踪,最终查询到了Tomcat的源码里,发现居然是它做的解析。
现在想写个博客记录一下,却发现自己已经找不到当时追踪的痕迹了。。。于是查找了一下资料,再次发现原来有两套方案。
参考资料:
tomcat中解析url中的参数或者post中的请求内容
解决URL参数中的%问题
设立一个标识位:
其标识了当前请求Request的参数是否已经解析了,如果已经解析过了,则可以使用Request的真实对象“org.apache.coyote.Request”的Parameters属性,否则 需要解析参数并初始化Parameters属性。
如下:
第一次解析请求参数,并初始化Parameters属性:
Parameters类的processParameters方法:
至此,请求的参数就已经被解析并存储在Request的Parameters对象中去了,以后再次调用Request的getPathParameter(String name)或者getParameterValues(String name)等方法时就会直接从Parameters类中获取了。
当时我就猜测是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类中获取了。