http接口取参方式 – getParameter 和 getParameterValues[通俗易懂]

http接口取参方式 – getParameter 和 getParameterValues[通俗易懂]前言:最近写http接口时,有了很多关于接口取参方式的疑问,大家都知道Servlet常用的取参方式有getParameter、getParameterValues、getInputStream(读流形式)。SpringMvc常用的有封装好的@RequestParam,RequestBody。这些取参方式都有什么特点,我都写了测试接口,利用postman做了测试…

大家好,又见面了,我是你们的朋友全栈君。

前言:
     

    最近写http接口时,有了很多关于接口取参方式的疑问,大家都知道 Servlet常用的取参方式有 getParameter、getParameterValues、getInputStream(读流形式)。SpringMvc 常用的有封装好的 @RequestParam ,RequestBody 。这些取参方式都有什么特点,我都写了测试接口,利用postman 做了测试。通过测试现象得到了如下结论,如有错误,请指正。
    
 

测试结论 :

HttpServletRequest

1、getParameter() 取 Key- Value形式的值(URL带参+Form Data) 相同Key只取第一个值,且优先取 url上带参的值。         

2、getParameterValues() 取Key – Value全部值, 取Key的所有值。

3、Get方式 以 Query String Paramters 形式传参(参数在URL上),报文体中无值 无法用 getInputStream() 取报文值。

4、Post方式 application/x-www-form-urlencoded 形式 getParameter() 和 getInputStream() 都可取到值,且 getParameter() 和 getInputStream() 互斥。

5、Post方式 application/json 形式 ,只能由 getInputStream()读取。

SpringMvc 

1、@RequestParam String param  : 读取 Key – Value 形式的值 ,与 getParameterValues() 一样取key的所有值

2、@RequestBody String param : Query String Paramters + application/x-www-form-urlencoded  的 Key -Value形式,全部读取 例如:  

<– // 接口请求报文
POST /test/testPost10?name=123&amp;password=456 HTTP/1.1
Host: 127.0.0.1:8080
Content-Type: application/x-www-form-urlencoded
Cache-Control: no-cache
Postman-Token: 20c00b49-28fb-5556-253b-1be56a6f251c

name=abc&password=efg

–>  // 接口取到的值
name=123&name=abc&password=456&password=efg

3、@RequestBody String param : Query String Paramters + application/json 只读取 报文体中的值 例如:

<–
POST /test/testPost10?name=123&amp;password=456&amp;token=wsx HTTP/1.1
Host: 127.0.0.1:8080
Content-Type: application/json
Cache-Control: no-cache
Postman-Token: c1d1bb38-04f5-2872-0488-89a7085e01be

{

    “name”:”YHN”,
    “password”:”IJN”
    
}

–> 
{

    “name”:”YHN”,
    “password”:”IJN”
    
}

4、@RequestBody Object obj 只读取 application/json 形式的报文

 

探本朔源,我们通过源码来验证上面得到测试结论。

 

getParameter 和 getParameterValues 源码解析

直接在web项目中debug 会找到org.apache.catalina.connector.RequestFacade.java 这个类,是在tomcat中实现的。直接在github上 下载 tomcat 源码(我下的是最新版 tomcat9)

RequestFacade 中有 getParameter 和 getParameterValues方法 ,其中调用了 org.apache.catalina.connector.Request.java 中的方法这两个方法。我们直接去看 org.apache.catalina.connector.Request.java 中的 getParameter(String name) 和getParameterValues(String name) 方法。截取代码如下:

   /**
     * @return the value of the specified request parameter, if any; otherwise,
     * return <code>null</code>.  If there is more than one value defined,
     * return only the first one.
     *
     * @param name Name of the desired request parameter
     */
    @Override
    public String getParameter(String name) {

        if (!parametersParsed) {
            parseParameters();
        }

        return coyoteRequest.getParameters().getParameter(name);

    }
	
	
	/**
     * @return the defined values for the specified request parameter, if any;
     * otherwise, return <code>null</code>.
     *
     * @param name Name of the desired request parameter
     */
    @Override
    public String[] getParameterValues(String name) {

        if (!parametersParsed) {
            parseParameters();
        }

        return coyoteRequest.getParameters().getParameterValues(name);

    }

其中有一个关键函数 parseParameters() 附上解析代码

   /**
     * Parse request parameters.
     */
    protected void parseParameters() {

        parametersParsed = true;  // 设置解析标志 ,后面再使用getParamter()时不再次解析

        Parameters parameters = coyoteRequest.getParameters();
        boolean success = false;
        try {
            // Set this every time in case limit has been changed via JMX
            parameters.setLimit(getConnector().getMaxParameterCount());

            // getCharacterEncoding() may have been overridden to search for
            // hidden form field containing request encoding
            Charset charset = getCharset();

            boolean useBodyEncodingForURI = connector.getUseBodyEncodingForURI();
            parameters.setCharset(charset);   // 设置字符集
            if (useBodyEncodingForURI) {
                parameters.setQueryStringCharset(charset);  
            }
            // Note: If !useBodyEncodingForURI, the query string encoding is
            //       that set towards the start of CoyoyeAdapter.service()

			// 1、解析 Query String Paramters形式(url带参形式) 
            parameters.handleQueryParameters();  

            if (usingInputStream || usingReader) {
                success = true;
                return;
            }

            String contentType = getContentType();
            if (contentType == null) {
                contentType = "";
            }
            int semicolon = contentType.indexOf(';');
            if (semicolon >= 0) {
                contentType = contentType.substring(0, semicolon).trim();
            } else {
                contentType = contentType.trim();
            }

            if ("multipart/form-data".equals(contentType)) {
                parseParts(false);   // 解析 multipart/form-data
                success = true;
                return;
            }

            if( !getConnector().isParseBodyMethod(getMethod()) ) {
                success = true;
                return;
            }

            if (!("application/x-www-form-urlencoded".equals(contentType))) {
                success = true;
                return;
            }
            
            // 2、根据上面的 contentType 判断 下面解析 application/x-www-form-urlencoded 形式		
            int len = getContentLength();  // 报文体的长度 get方式 无报文体

            if (len > 0) {
                int maxPostSize = connector.getMaxPostSize();
                if ((maxPostSize >= 0) && (len > maxPostSize)) {
                    Context context = getContext();
                    if (context != null && context.getLogger().isDebugEnabled()) {
                        context.getLogger().debug(
                                sm.getString("coyoteRequest.postTooLarge"));
                    }
                    checkSwallowInput();
                    parameters.setParseFailedReason(FailReason.POST_TOO_LARGE);
                    return;
                }
                byte[] formData = null;
                if (len < CACHED_POST_LEN) {
                    if (postData == null) {
                        postData = new byte[CACHED_POST_LEN];
                    }
                    formData = postData;
                } else {
                    formData = new byte[len];
                }
                try {
                    if (readPostBody(formData, len) != len) {  // readPostBody方法(代码已截取)读取报文体的流  使用getStream().read() 方法 。注意:流只能读取一次。
                        parameters.setParseFailedReason(FailReason.REQUEST_BODY_INCOMPLETE);
                        return;
                    }
                } catch (IOException e) {
                    // Client disconnect
                    Context context = getContext();
                    if (context != null && context.getLogger().isDebugEnabled()) {
                        context.getLogger().debug(
                                sm.getString("coyoteRequest.parseParameters"),
                                e);
                    }
                    parameters.setParseFailedReason(FailReason.CLIENT_DISCONNECT);
                    return;
                }
                parameters.processParameters(formData, 0, len);  // 解析application/x-www-form-urlencoded
            } else if ("chunked".equalsIgnoreCase(
                    coyoteRequest.getHeader("transfer-encoding"))) {
                byte[] formData = null;
                try {
                    formData = readChunkedPostBody();
                } catch (IllegalStateException ise) {
                    // chunkedPostTooLarge error
                    parameters.setParseFailedReason(FailReason.POST_TOO_LARGE);
                    Context context = getContext();
                    if (context != null && context.getLogger().isDebugEnabled()) {
                        context.getLogger().debug(
                                sm.getString("coyoteRequest.parseParameters"),
                                ise);
                    }
                    return;
                } catch (IOException e) {
                    // Client disconnect
                    parameters.setParseFailedReason(FailReason.CLIENT_DISCONNECT);
                    Context context = getContext();
                    if (context != null && context.getLogger().isDebugEnabled()) {
                        context.getLogger().debug(
                                sm.getString("coyoteRequest.parseParameters"),
                                e);
                    }
                    return;
                }
                if (formData != null) {
                    parameters.processParameters(formData, 0, formData.length);
                }
            }
            success = true;
        } finally {
            if (!success) {
                parameters.setParseFailedReason(FailReason.UNKNOWN);
            }
        }

    }
	
	
	/**
     * Read post body in an array.
     *
     * @param body The bytes array in which the body will be read
     * @param len The body length
     * @return the bytes count that has been read
     * @throws IOException if an IO exception occurred
     */
    protected int readPostBody(byte[] body, int len)
        throws IOException {

        int offset = 0;
        do {
            int inputLen = getStream().read(body, offset, len - offset);
            if (inputLen <= 0) {
                return offset;
            }
            offset += inputLen;
        } while ((len - offset) > 0);
        return len;

    }

从上面的代码可知 :Parameters 中有两个方法去解析 paramter 的值
1、parameters.handleQueryParameters();
2、parameters.processParameters(formData, 0, len);

我们找到 org.apache.tomcat.util.http.Parameters.java 看这两个方法及相关方法:

/** Process the query string into parameters  解析 query string 即Url带参
     */
    public void handleQueryParameters() {
        if (didQueryParameters) {
            return;
        }

        didQueryParameters = true;

        if (queryMB == null || queryMB.isNull()) {
            return;
        }

        if(log.isDebugEnabled()) {
            log.debug("Decoding query " + decodedQuery + " " + queryStringCharset.name());
        }

        try {
            decodedQuery.duplicate(queryMB);
        } catch (IOException e) {
            // Can't happen, as decodedQuery can't overflow
            e.printStackTrace();
        }
        processParameters(decodedQuery, queryStringCharset); // 解析
    } 
	
	
	public void processParameters( byte bytes[], int start, int len ) {
        processParameters(bytes, start, len, charset);
    }

    private void processParameters(byte bytes[], int start, int len, Charset charset) {

        if(log.isDebugEnabled()) {
            log.debug(sm.getString("parameters.bytes",
                    new String(bytes, start, len, DEFAULT_BODY_CHARSET)));
        }

        int decodeFailCount = 0;

        int pos = start;
        int end = start + len;

        while(pos < end) {
            int nameStart = pos;
            int nameEnd = -1;
            int valueStart = -1;
            int valueEnd = -1;

            boolean parsingName = true;
            boolean decodeName = false;
            boolean decodeValue = false;
            boolean parameterComplete = false;

            do {
                switch(bytes[pos]) {
                    case '=':
                        if (parsingName) {
                            // Name finished. Value starts from next character
                            nameEnd = pos;
                            parsingName = false;
                            valueStart = ++pos;
                        } else {
                            // Equals character in value
                            pos++;
                        }
                        break;
                    case '&':
                        if (parsingName) {
                            // Name finished. No value.
                            nameEnd = pos;
                        } else {
                            // Value finished
                            valueEnd  = pos;
                        }
                        parameterComplete = true;
                        pos++;
                        break;
                    case '%':
                    case '+':
                        // Decoding required
                        if (parsingName) {
                            decodeName = true;
                        } else {
                            decodeValue = true;
                        }
                        pos ++;
                        break;
                    default:
                        pos ++;
                        break;
                }
            } while (!parameterComplete && pos < end);

            if (pos == end) {
                if (nameEnd == -1) {
                    nameEnd = pos;
                } else if (valueStart > -1 && valueEnd == -1){
                    valueEnd = pos;
                }
            }

            ....省略代码....  //看上面的 switch() 方法 和 addParameter(name, value) 方法
			
			try {
                String name;
                String value;

                if (decodeName) {
                    urlDecode(tmpName);
                }
                tmpName.setCharset(charset);
                name = tmpName.toString();

                if (valueStart >= 0) {
                    if (decodeValue) {
                        urlDecode(tmpValue);
                    }
                    tmpValue.setCharset(charset);
                    value = tmpValue.toString();
                } else {
                    value = "";
                }

                try {
                    addParameter(name, value);
                } catch (IllegalStateException ise) {
                   
				   ....省略代码....
				   
                }
            } catch (IOException e) {
			
                   ....省略代码....
            }
    }
	

现在已找到最核心的方法了,    processParameters() 通过switch() 方法解析 Key – Value 值 在调用 addParameter(name, value)方法,
下面来看 addParameter()方法:

    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
            setParseFailedReason(FailReason.TOO_MANY_PARAMETERS);
            throw new IllegalStateException(sm.getString(
                    "parameters.maxCountFail", Integer.valueOf(limit)));
        }

        ArrayList<String> values = paramHashValues.get(key);  // 根据key 取出一个 List出来 (同一个key,用List保存多个value值 )
        if (values == null) {
            values = new ArrayList<>(1);
            paramHashValues.put(key, values);
        }
        values.add(value);  // 保存value值
    }

这一步让我们找到了 parameter 解析后在内存的保存的位置 paramHashValues

 private final Map<String,ArrayList<String>> paramHashValues = new LinkedHashMap<>();

final 一个 LinkedHashMap (进入的顺序与被取出的顺序一致)。到了这一步已经可以到如下结论:

HttpServletRequest 调用 getParameter方法 或 getParameterValues方法时,实际是通过 org.apache.catalina.connector.RequestFacade.java 来调用 org.apache.catalina.connector.Request.java中的 getParameter方法 或 getParameterValues方法,其中通过 parseParameters 解析报文 ,先解析Query String Paramters(url带参),再解析 Form -Data 形式的值。最终把解析的报文值存入 org.apache.tomcat.util.http.Paramters.java 中 Map<String,ArrayList<String>> 中。   

 

现在回过头 来看org.apache.tomcat.util.http.Parameters.java 中的 getParameter() 和 getParameterValues 方法

   // 取 key的第一个Vaule值
   public String getParameter(String name ) {
        handleQueryParameters();
        ArrayList<String> values = paramHashValues.get(name);
        if (values != null) {
            if(values.size() == 0) {
                return "";
            }
            return values.get(0);
        } else {
            return null;
        }
    }
	
	// 取 key的全部Value值
	public String[] getParameterValues(String name) {
        handleQueryParameters();
        // no "facade"
        ArrayList<String> values = paramHashValues.get(name);
        if (values == null) {
            return null;
        }
        return values.toArray(new String[values.size()]);
    }

现在就可以理解了 为什么 getParameter 老是优先取url上带的参数了。且只取一个值了,getParameterValues 取Key的所有值。

现在 HttpServletRequest 的 结论 1 和 2 已验证完毕。

下一篇 来看看  getInputStream() 和 getReader()。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/163204.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 通达OA工作流不可编辑

    通达OA工作流不可编辑找到表:flow_run搜索自增长ID=流水号,查看该条记录的用户是否ok找到表:flow_run_prcs搜索流水号:run_id=***查看结果的PRCS_ID数字最大的那一条记录的OP_FLAG是否为1,如果不为1则改为1即可。

    2022年6月23日
    28
  • 怎样创建一个简单的mysql数据库文件_MySQL数据库

    怎样创建一个简单的mysql数据库文件_MySQL数据库学习java到数据库操作章节后发现没有数据库,折腾了1天总算弄好了学习所需要的数据库,感觉好开心。一.创建数据库注:已经安装好mysql。windows下运行cmd进入命令窗口,本人用的是win7系统,先输入F:进入F盘,然后输入“cdF:\mysql\mysql-5.7.18-winx64\bin”(注:不要引号,路径为自己解压mysql的路径)。输入nets

    2025年7月2日
    4
  • java静态变量加载顺序_内部类为什么不能有静态

    java静态变量加载顺序_内部类为什么不能有静态静态内部类加载顺序我们先来区分一下两个概念:类加载、加载。类加载的过程包括加载,初始化,验证,解析,准备,初始化等五个过程。加载是类加载的一部分。区分完这两个概念之后我们再来看下面的问题。我们声明一个类,这个类有个内部静态类。还有主函数,当我们启动程序之后,运行javaapplication……

    2022年10月10日
    3
  • Linux中查看redis版本

    Linux中查看redis版本快半年没有在Linux中使用redis了,命令有些生疏了,网上很多博文也不对,不知道博主是否直接复制的来的。以下为重新整理资料,便于忘记时候复习首先进入cd/usr/local目录不用说了我把redis安装到了redis文件夹中了,在bin目录下找到redis-server使用./redis-server–version查看版本信息[red@RedFaceloc…

    2022年6月1日
    39
  • linux安装python虚拟环境_windows安装python虚拟环境

    linux安装python虚拟环境_windows安装python虚拟环境准备1、使用wget命令下载安装包,耐心等待下载。安装步骤1、安装gcc2、安装readline3、把tgz文件进行解压4、切换到python目录5、解决PIP包管理器所需依赖包。6、安装文件7、开始编译安装,自定义安装目录。8、修改系统内置Python软链接。9、针对Centos系统的一些问题Centos的包资源管理器是yum,由于该管理器是由Python语言实现的,故依赖于系统安装Python…

    2022年8月28日
    6
  • Red Flag linux硬盘安装

    Red Flag linux硬盘安装 很早以前就想安装linux实验一下,今天从中科红旗RedFlag的网站下载了一个系统安装盘的iso,安装盘一共2CD,这里提前说一下,也不知道是不是我的WinXP文件关联有问题,下载下来的ISO显示的是WinRAR的图标,于是没看扩展名直接解压缩出来了(后面因为这个吃了不少苦头,没办法,习惯了),然后上网站上查了查,网上大都写的不明白,而且重复的很多(是不是高手都用linux,觉得

    2022年8月20日
    12

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号