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

 

友情鏈接

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM