当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方法。代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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>();