當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 }
- 由於上述分析的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>();
友情鏈接
