天天看点

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

想起了前两个月同事问我:我发出的请求里如果有”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类中获取了。