zoukankan      html  css  js  c++  java
  • tomcat源码---->request的请求参数分析

      当contentType为application/json的时候,在servlet中通过request.getParameter得到的数据为空。今天我们就java的请求,分析一下request得到参数的过程。如果你还不知道自己喜欢什么,你就真的迷失了。

    java的请求参数

    首先我把环境代码贴出来,如下前端jsp的代码:$.ajax方法里面默认的contenttype为:application/x-www-form-urlencoded

    <script type="text/javascript" src="js/jquery-3.1.0.js"></script>
    <script type="text/javascript">
        function testJson() {
            $.ajax({
                url: "ParameterServlet",
                type: "post",
                data: {
                    "username": "huhx",
                    "love": "javascript"
                }
                // contentType: "application/json"
            });
        }
    </script>
    </head>
    <body>
        hello world.
        <input type="text" name="username"><br>
        <button onclick="testJson()">love you, huhx</button>
    </body>
    </html>

    后端java的代码:

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println(request.getContentType());
        String username = request.getParameter("username");
        System.out.println(username);
    }

    一、request.getParameter("username")执行的实际代码其实是Request类的getParameter方法:

    public String getParameter(String name) {
        if (!parametersParsed) {
            parseParameters();
        }
        return coyoteRequest.getParameters().getParameter(name);
    }

    parametersParsed是一个为false的全局变量,由于是线程的问题。parseParameters()针对这个请求应该只会执行一次。这里关键代码有两个其一如下

    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();
        success = true;
        return;
    }
    
    if (!("application/x-www-form-urlencoded".equals(contentType))) {
        success = true;
        return;
    }

    这段代码不难理解,首先会得到请求类型。从上述代码可以得出两点:

    • 当contenttype有多种时,只会取第一种。比如contenttype为application/json;application/x-www-form-urlencoded,最终是application/json
    • 当contenttype不为application/x-www-form-urlencoded,直接return掉了。也就是说关键代码二不执行了
    • 当contenttype为multipart/form-data时,parseParts()方法里使用的解析文件的框架是apache自带的fileupload。

    好了,以下贴出关键二的代码:

    formData = readChunkedPostBody();
    parameters.processParameters(formData, 0, len)
    • readChunkedPostBody方法,其实就是request.getInputStream().read()方法,将请求的数据通过流的方式读取出来。为了方便,下面贴出代码:
    protected byte[] readChunkedPostBody() throws IOException {
        ByteChunk body = new ByteChunk();
    
        byte[] buffer = new byte[CACHED_POST_LEN];
    
        int len = 0;
        while (len > -1) {
            len = getStream().read(buffer, 0, CACHED_POST_LEN); // getStream()返回的是CoyoteInputStream,一种带有缓存的流。
            if (connector.getMaxPostSize() >= 0 &&
                    (body.getLength() + len) > connector.getMaxPostSize()) {
                // Too much data
                checkSwallowInput();
                throw new IllegalStateException(
                        sm.getString("coyoteRequest.chunkedPostTooLarge"));
            }
            if (len > 0) {
                body.append(buffer, 0, len);
            }
        }
        if (body.getLength() == 0) {
            return null;
        }
        if (body.getLength() < body.getBuffer().length) {
            int length = body.getLength();
            byte[] result = new byte[length];
            System.arraycopy(body.getBuffer(), 0, result, 0, length);
            return result;
        }
    
        return body.getBuffer();
    }
    • processParameters()是在Parameters类里面的方法,做的工作就是对请求的数据,做key与value的拆分,然后存放进一个名叫paramHashValues的Map中。后续的request.getParameter取的就是paramHashValues里面的数据
    ArrayList<String> values = paramHashValues.get(key);
    if (values == null) {
        values = new ArrayList<String>(1);
        paramHashValues.put(key, values);
    }
    values.add(value);

    解析过程我就不分析了,感兴趣的可以自己查看源码参见tomcat的源码:Parameters类processParameters方法。代码如下:

      1 private void processParameters(byte bytes[], int start, int len, Charset charset) {
      2 
      3     if(log.isDebugEnabled()) {
      4         log.debug(sm.getString("parameters.bytes",
      5                 new String(bytes, start, len, DEFAULT_CHARSET)));
      6     }
      7 
      8     int decodeFailCount = 0;
      9 
     10     int pos = start;
     11     int end = start + len;
     12 
     13     while(pos < end) {
     14         int nameStart = pos;
     15         int nameEnd = -1;
     16         int valueStart = -1;
     17         int valueEnd = -1;
     18 
     19         boolean parsingName = true;
     20         boolean decodeName = false;
     21         boolean decodeValue = false;
     22         boolean parameterComplete = false;
     23 
     24         do {
     25             switch(bytes[pos]) {
     26                 case '=':
     27                     if (parsingName) {
     28                         // Name finished. Value starts from next character
     29                         nameEnd = pos;
     30                         parsingName = false;
     31                         valueStart = ++pos;
     32                     } else {
     33                         // Equals character in value
     34                         pos++;
     35                     }
     36                     break;
     37                 case '&':
     38                     if (parsingName) {
     39                         // Name finished. No value.
     40                         nameEnd = pos;
     41                     } else {
     42                         // Value finished
     43                         valueEnd  = pos;
     44                     }
     45                     parameterComplete = true;
     46                     pos++;
     47                     break;
     48                 case '%':
     49                 case '+':
     50                     // Decoding required
     51                     if (parsingName) {
     52                         decodeName = true;
     53                     } else {
     54                         decodeValue = true;
     55                     }
     56                     pos ++;
     57                     break;
     58                 default:
     59                     pos ++;
     60                     break;
     61             }
     62         } while (!parameterComplete && pos < end);
     63 
     64         if (pos == end) {
     65             if (nameEnd == -1) {
     66                 nameEnd = pos;
     67             } else if (valueStart > -1 && valueEnd == -1){
     68                 valueEnd = pos;
     69             }
     70         }
     71 
     72         if (log.isDebugEnabled() && valueStart == -1) {
     73             log.debug(sm.getString("parameters.noequal",
     74                     Integer.valueOf(nameStart), Integer.valueOf(nameEnd),
     75                     new String(bytes, nameStart, nameEnd-nameStart,
     76                             DEFAULT_CHARSET)));
     77         }
     78 
     79         if (nameEnd <= nameStart ) {
     80             if (valueStart == -1) {
     81                 // &&
     82                 if (log.isDebugEnabled()) {
     83                     log.debug(sm.getString("parameters.emptyChunk"));
     84                 }
     85                 // Do not flag as error
     86                 continue;
     87             }
     88             // &=foo&
     89             UserDataHelper.Mode logMode = userDataLog.getNextMode();
     90             if (logMode != null) {
     91                 String extract;
     92                 if (valueEnd > nameStart) {
     93                     extract = new String(bytes, nameStart, valueEnd
     94                             - nameStart, DEFAULT_CHARSET);
     95                 } else {
     96                     extract = "";
     97                 }
     98                 String message = sm.getString("parameters.invalidChunk",
     99                         Integer.valueOf(nameStart),
    100                         Integer.valueOf(valueEnd), extract);
    101                 switch (logMode) {
    102                     case INFO_THEN_DEBUG:
    103                         message += sm.getString("parameters.fallToDebug");
    104                         //$FALL-THROUGH$
    105                     case INFO:
    106                         log.info(message);
    107                         break;
    108                     case DEBUG:
    109                         log.debug(message);
    110                 }
    111             }
    112             setParseFailedReason(FailReason.NO_NAME);
    113             continue;
    114             // invalid chunk - it's better to ignore
    115         }
    116 
    117         tmpName.setBytes(bytes, nameStart, nameEnd - nameStart);
    118         if (valueStart >= 0) {
    119             tmpValue.setBytes(bytes, valueStart, valueEnd - valueStart);
    120         } else {
    121             tmpValue.setBytes(bytes, 0, 0);
    122         }
    123 
    124         // Take copies as if anything goes wrong originals will be
    125         // corrupted. This means original values can be logged.
    126         // For performance - only done for debug
    127         if (log.isDebugEnabled()) {
    128             try {
    129                 origName.append(bytes, nameStart, nameEnd - nameStart);
    130                 if (valueStart >= 0) {
    131                     origValue.append(bytes, valueStart, valueEnd - valueStart);
    132                 } else {
    133                     origValue.append(bytes, 0, 0);
    134                 }
    135             } catch (IOException ioe) {
    136                 // Should never happen...
    137                 log.error(sm.getString("parameters.copyFail"), ioe);
    138             }
    139         }
    140 
    141         try {
    142             String name;
    143             String value;
    144 
    145             if (decodeName) {
    146                 urlDecode(tmpName);
    147             }
    148             tmpName.setCharset(charset);
    149             name = tmpName.toString();
    150 
    151             if (valueStart >= 0) {
    152                 if (decodeValue) {
    153                     urlDecode(tmpValue);
    154                 }
    155                 tmpValue.setCharset(charset);
    156                 value = tmpValue.toString();
    157             } else {
    158                 value = "";
    159             }
    160 
    161             try {
    162                 addParameter(name, value);
    163             } catch (IllegalStateException ise) {
    164                 // Hitting limit stops processing further params but does
    165                 // not cause request to fail.
    166                 UserDataHelper.Mode logMode = maxParamCountLog.getNextMode();
    167                 if (logMode != null) {
    168                     String message = ise.getMessage();
    169                     switch (logMode) {
    170                         case INFO_THEN_DEBUG:
    171                             message += sm.getString(
    172                                     "parameters.maxCountFail.fallToDebug");
    173                             //$FALL-THROUGH$
    174                         case INFO:
    175                             log.info(message);
    176                             break;
    177                         case DEBUG:
    178                             log.debug(message);
    179                     }
    180                 }
    181                 break;
    182             }
    183         } catch (IOException e) {
    184             setParseFailedReason(FailReason.URL_DECODING);
    185             decodeFailCount++;
    186             if (decodeFailCount == 1 || log.isDebugEnabled()) {
    187                 if (log.isDebugEnabled()) {
    188                     log.debug(sm.getString("parameters.decodeFail.debug",
    189                             origName.toString(), origValue.toString()), e);
    190                 } else if (log.isInfoEnabled()) {
    191                     UserDataHelper.Mode logMode = userDataLog.getNextMode();
    192                     if (logMode != null) {
    193                         String message = sm.getString(
    194                                 "parameters.decodeFail.info",
    195                                 tmpName.toString(), tmpValue.toString());
    196                         switch (logMode) {
    197                             case INFO_THEN_DEBUG:
    198                                 message += sm.getString("parameters.fallToDebug");
    199                                 //$FALL-THROUGH$
    200                             case INFO:
    201                                 log.info(message);
    202                                 break;
    203                             case DEBUG:
    204                                 log.debug(message);
    205                         }
    206                     }
    207                 }
    208             }
    209         }
    210 
    211         tmpName.recycle();
    212         tmpValue.recycle();
    213         // Only recycle copies if we used them
    214         if (log.isDebugEnabled()) {
    215             origName.recycle();
    216             origValue.recycle();
    217         }
    218     }
    219 
    220     if (decodeFailCount > 1 && !log.isDebugEnabled()) {
    221         UserDataHelper.Mode logMode = userDataLog.getNextMode();
    222         if (logMode != null) {
    223             String message = sm.getString(
    224                     "parameters.multipleDecodingFail",
    225                     Integer.valueOf(decodeFailCount));
    226             switch (logMode) {
    227                 case INFO_THEN_DEBUG:
    228                     message += sm.getString("parameters.fallToDebug");
    229                     //$FALL-THROUGH$
    230                 case INFO:
    231                     log.info(message);
    232                     break;
    233                 case DEBUG:
    234                     log.debug(message);
    235             }
    236         }
    237     }
    238 }
    View Code
    • 由于上述分析的contenttype不为form-data的和x-www-form-urlencoded的不会执行关键二的代码,所以对于请求类型为application/json通过request.getParameter得到的数据为空。

    tomcat对请求数据是通过一个个字符判断的,PE的处理方式是通过正则表达式的。关于正则表达式可以参见博客:平安夜快乐

    二、request.getAttribute()其实就是RequestFacade类的getAttribute

    首先看一下request.setAttribute(name, value)的方法:

    public void setAttribute(String name, Object value) {
    
        // Name cannot be null
        if (name == null) {
            throw new IllegalArgumentException
                (sm.getString("coyoteRequest.setAttribute.namenull"));
        }
    
        // Null value is the same as removeAttribute()
        if (value == null) {
            removeAttribute(name);
            return;
        }
    
        // Special attributes,它的名字一般是tomcat内部的属性,都是心org.apache.catalina开头的。
        SpecialAttributeAdapter adapter = specialAttributes.get(name);
        if (adapter != null) {
            adapter.set(this, name, value);
            return;
        }
        // 安全的检查
      ...........
    Object oldValue = attributes.put(name, value); // Pass special attributes to the native layer if (name.startsWith("org.apache.tomcat.")) { coyoteRequest.setAttribute(name, value); } // Notify interested application event listeners notifyAttributeAssigned(name, value, oldValue); }

    这里的attributes实质上是一个Map,一个多线程安全的Map。至于getAttribute其实就是从Map中得到数据。

    private final Map<String, Object> attributes = new ConcurrentHashMap<String, Object>();

    友情链接

  • 相关阅读:
    【C/C++】小坑们
    【CUDA】Windows 下常用函数头文件
    【OS】Heap & Stack
    【C/C++】Rotate Array
    【C/C++】泛型栈
    【IDE】我的花里胡哨VS
    【PAT】我要通过!
    【C/C++】内存基础
    【CUDA】Win10 + VS2017新 CUDA 项目配置
    【LeetCode】不同路径
  • 原文地址:https://www.cnblogs.com/huhx/p/baseusewebparameter1.html
Copyright © 2011-2022 走看看