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)


相关推荐

  • VScode快捷键和设置

    VScode快捷键和设置一.快捷键单行注释 ctrl+/ 多行注释 alt+shift+a 复制上一行代码到下一行 alt+shift+↓ 移动代码到上一行或下一行 alt+↑,alt+↓ 返回到上次编辑位置 alt+← 跳转到指定文件 ctrl+p 全文搜索字符 ctrl+shift+f 格式化代码 shift+alt+f…

  • Nginx反向代理缓存(proxy_cache配置)

    Nginx反向代理缓存(proxy_cache配置)

  • 学生信息管理系统登录入口_学生信息管理系统Java sql

    学生信息管理系统登录入口_学生信息管理系统Java sqlphp大作页,使用php+mysql技术,实现了基本的分页,信息查询,修改,增加,删除操作有以下几个基本页面登录页面首页修改学生基本信息修改学生学籍信息修改学生成绩信息增加学生信息源码在我的github上,欢迎大家访问https://gitee.com/zxhjames/zxh/tree/master/StudentInformationManageSystem…………………

  • 镁光256Gb NAND Flash芯片介绍

    镁光256Gb NAND Flash芯片介绍总体概述该芯片是一款典型的大容量NANDFlash存储颗粒,支持OpenNANDFlashInterface(ONFI)2.1的接口标准,采用ONFINANDFlash的操作协议。该芯片采用Multiple-levelCell(MLC)技术,根据不同的容量,一个芯片内部封装了多个DIE(LUN),每个DIE由两个Plane构成,一个Plane可以分成2048个Block,每个Bl…

  • batchnorm原理理解「建议收藏」

    batchnorm原理理解「建议收藏」接触CNN也一段时间了,最近也到了秋招期间,面试的时候可能会问到的一些内容需要做一个整理CNN-BN层参考了一个大神的博客,感觉讲的很深入也很好理解。我这里主要是对他的博客做一个自己的归纳整理,主要是为了方便自己去理解,也欢迎大家一起讨论自己的理解。这里给出大神的博客地址:https://blog.csdn.net/qq_25737169/article/details/79048…

  • html简单网页代码 案例_制作网页代码案例

    html简单网页代码 案例_制作网页代码案例当初面试官让我现场手写内存溢出案例代码,我就以Java代码的方式列举了几个典型的内存溢出案例。今天分享给大家,希望大家在日常工作中,尽量避免写这些low水平的代码

发表回复

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

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