http请求参数中加号被替换为空格及请求参数被URLDeCode
今天遇到两个有关tomcat的比较有意思的问题
1.用postman模拟https请求的时候,如果请求参数是name=jay+love。最后服务端用request.getParameter("name")收到的是【jay love】
2.遇到上诉问题之后随即想到对jay+love进行URLEnCode处理,即name=jay%2blove。最后服务端用request.getParameter("name")收到的是【jay+love】,这里被自动URLDeCode解码了。
关于第一个问题:
加号变空格在网上找到一个说法:
html中因为一些非标准的做法,将+等同于空格进行处理(当Html的表单被提交时, 每个表单域都会被Url编码之后才在被发送。由于历史的原因,表单使用的Url编码实现并不符合最新的标准。例如对于空格使用 的编码并不是%20,而是+号,如果表单使用的是Post方法提交的,我们可以在HTTP头中看到有一个Content-Type的header ,值为application/x-www-form-urlencoded,大部分应用程序均能处理这种非标准实现的Url编码)。
这样就解释了为什么【jay+love】变成了【jay love】。
其次我还想到了,为什么我们在html进行form表单提交的时候并没有注意到这样的问题。因为当Html的表单被提交时, 每个表单域都会被Url编码之后才在被发送。所以这个问题就被隐藏了。
关于第二个问题:
自动DeCode我首先是想到了是调用getParameter这个方法的时候,方法内部对参数进行了URLDeCode。点进去发现方法属于这个接口javax.servlet.ServletRequest,位于javax.servlet-api.jar中。
那么源码就位于WEB容器中,也就是tomcat的源码。下载tomcat源码后,我下载的是apache-tomcat-8.5.32-src。
愉快的找到这个接口。发现有很多实现类。
getParameter的实现类那我到底用的是哪个呢,我也不知道,那我就在调用的地方打印一下呗。System.out.println(request.getClass().getName());
输出:org.apache.catalina.connector.RequestFacade。
那我们看一下RequestFacade里面怎么写的。【Facade模式对Request对象进行包装】
RequestFacade.getParameter()可以看到他又调用了org.apache.catalina.connector.Request.getParameter,那么这个方法干什么了呢,如图:
org.apache.catalina.connector.Request.getParameter不难看出,这个方法解析参数parseParameters。并且看注释说明:
获取指定的参数,比如我们这里的name,如果有就返回值,没有就返回null。如果获取的参数不止一个,比如请求参数中有两个name,那么只返回第一个.
protected void parseParameters() {
parametersParsed = true;
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()
parameters.handleQueryParameters();
if (usingInputStream || usingReader) {
success = true;
return;
}
if( !getConnector().isParseBodyMethod(getMethod()) ) {
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);
success = true;
return;
}
if (!("application/x-www-form-urlencoded".equals(contentType))) {
success = true;
return;
}
int len = getContentLength();
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) {
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);
} 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);
}
}
}
可以看到第三行:
Parametersparameters =coyoteRequest.getParameters();
直接获取Parameters。这不就是我们想要的东西嘛。点进去看看。
image就是一个简单的get方法。看一下这个成员变量的定义。
image接下来看看那些地方调用了他。
image矮油,我去这一行调用这名字很可疑呀,去看看。
image恍然大悟,原来是构造方法的时候就调用了URLDecoder了。至此,我大概知道了这个流程。
今天有幸遇到这个问题,特此记录。