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()。

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

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

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 循环队列 基本概念「建议收藏」

    循环队列 基本概念「建议收藏」循环队列是队列的一种特殊形式。首先介绍队列,然后引申出循环队列。队列又称为“先进先出”(FIFO)线性表限定插入操作只能在队尾进行,而删除操作只能在队首进行队列也可以采用顺序存储结构或链表结构来实现,分别称为顺序队列和链队列队列的顺序表示—顺序队列用一组连续的存储单元依次存放从队首到队尾的元素,附设两个指针head和tail分别指向队首元素和队尾元素的位置,(有的地方用f…

  • PHP+MySql例子

    对于熟悉做网站的人来说,要想网站做成动态的,肯定要有数据库的支持,利用特定的脚本连接到数据库,从数据库中提取资料、向数据库中添加资料、删除资料等。这里我通过一个实例来说明如何用php连接到数据库的。

    2021年12月23日
  • 环境变量和shell变量

    环境变量和shell变量变量分类变量分为环境变量和shell变量环境变量相当于全局变量,适用于当前SHELL(父进程)和由父进程调用的子进程,如打开编辑器vi、脚本、应用或是再打开一个子shell。shell变量就是当前shell使用的变量了,它只是“本地“有效,相当于本地变量,不适用于其他子进程,只在当前shell生命周期内有效永久变量不管是自定义的变量还是通过export导为环境变量的自定义变量都只是在shell生命周期内有效,这样的变量就是临时变量,如果我想设置一个变量使其永久生效怎么办呢?可以修改两个配置文

  • hash冲突以及hash冲突的解决方法

    hash冲突以及hash冲突的解决方法首先说一下hash冲突吧,hash冲突在hash表中一般情况下是会遇到的;hash冲突指的是你在向hash表中存数据时,首先要通过key值进行指定的hash算法进行计算,然后得到一个值,这个值就是你要将这个key对应的value存入的地址。但是在这个地址中已经有值存在,所以这个时候就发生了hash冲突,不同的key通过hash算法得到了对应的同一个值。hash冲突解决的方法:再hash法:这种方法就是有多个hash算法,当使用一个hash算法计算得到值发生hash冲突时那就使用另外一个hash算法

  • protostuff序列化map_为什么要实现序列化

    protostuff序列化map_为什么要实现序列化这几天在看rpc框架的东西,一哥们写的轻量级rpc框架(http://my.oschina.net/huangyong/blog/361751?fromerr=NpC3phqY)实现,写的rpc很不错,就跟着撸了遍代码,里面用到的序列化工具是protostuff,之前我们项目供应商接口用的xml,没用过protostuff,拿过来研究下,写个demo示例,以后再需要的话,也可以拿过来用。常用的

  • 数据分析模型篇—PEST分析

    数据分析模型篇—PEST分析今天给大家分享的是在企业战略管理中一个比较经典的分析—PEST分析。PEST分析一种企业所处宏观环境分析模型,宏观环境又称一般环境,是指影响一切行业和企业的各种宏观力量。而针对PEST,指的就是P政治(Politics),E经济(Economy),S社会(Society),T技术(Technology),这四类因素一般不受企业掌控,但是又会对企业的发展产生很大的影响,所以PEST分析在很多企业…

发表回复

您的电子邮箱地址不会被公开。

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