做后端开发,会跟各种请求打交道,Get或者Post甚至其他,而就Get和Post不同的编码方式和content-type提交也有许多的不同,在开发过程中,经常遇到参数接收不到的问题,无论是原生servlet还是springboot框架,都有到过这种问题。这篇文中就这些问题,通过分析,希望能让自己能更进一步的了解HTTP请求和参数接收相关的原理。
Get请求比较简单,请求的参数为[key=value],通过&
连接起来,一般被称为查询字符串(Query String),例如:http://127.0.0.1/test?name=strive&age=23
,参数为:name=strive&age=23
.
GET /get?name=strive HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Sec-Fetch-Site: same-origin
Referer: http://localhost:8080/getAndPostIndex
Accept-Encoding: gzip, deflate, br
其实我们只需要看/get?name=strive
即查询参数,GET请求参数是被存放在QueryString
中的,这种在我们后台是可以通过request.getParameter()
获取请求参数的。
下面我们来看POST请求,POST请求的方式比Get复杂,它参数的存放位置是和Content-Type
有关系的,具体有什么关系,继续往下看。
POST /postUrl HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 11
Cache-Control: max-age=0
Origin: http://localhost:8080
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Sec-Fetch-Site: same-origin
Referer: http://localhost:8080/getAndPostIndex
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,ko;q=0.8,en;q=0.7
Form Data
name=strive
这种方式提交的参数放在了以[key=value]
方式存在了请求体中,这种在我们后台是也是可以通过request.getParameter()
获取请求参数的。
POST /postFileUpload HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 3768
Cache-Control: max-age=0
Origin: http://localhost:8080
Upgrade-Insecure-Requests: 1
#Content-Type为multipart/form-data
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary6A2xLxBKaSvqBvDd
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Sec-Fetch-Site: same-origin
Referer: http://localhost:8080/getAndPostIndex
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,ko;q=0.8,en;q=0.7
#Request的请求体
Request Payload
------WebKitFormBoundary6A2xLxBKaSvqBvDd
Content-Disposition: form-data; name="file"; filename="Baidu.cer"
Content-Type: application/x-x509-ca-cert
#这里省略了文件的数据
------WebKitFormBoundary6A2xLxBKaSvqBvDd
Content-Disposition: form-data; name="name"
strive
------WebKitFormBoundary6A2xLxBKaSvqBvDd--
在Content-Type: multipart/form-data
时,而且POST提交的参数存放在了Request Payload
,这种请求参数没办法通过request.getParameter()
获取请求参数的。
POST /postJson HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 13
Accept: */*
Origin: http://localhost:8080
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36
Sec-Fetch-Mode: cors
Content-Type: application/json
Sec-Fetch-Site: same-origin
Referer: http://localhost:8080/getAndPostIndex
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,ko;q=0.8,en;q=0.7
#Request的请求体
Request Payload
{name:strive}
在Content-Type: application/json
时,而且POST提交的参数存放在了Request Payload
,这种请求参数没办法通过request.getParameter()
获取请求参数的。
POST /postPlain HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 13
Cache-Control: max-age=0
Origin: http://localhost:8080
Upgrade-Insecure-Requests: 1
Content-Type: text/plain
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Sec-Fetch-Site: same-origin
Referer: http://localhost:8080/getAndPostIndex
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,ko;q=0.8,en;q=0.7
#Request的请求体
Request Payload
name=strive
在Content-Type: text/plain
时,而且POST提交的参数存放在了Request Payload
,这种请求参数没办法通过request.getParameter()
获取请求参数的。
从上面的几个请求的报文的例子中我们可以找到一些规律,以application/x-www-form-urlencoded
作POST提交的数据,后台是可以通过request.getParameter()
获取请求参数的。而multipart/form-data
,application/json
和text/plain
作POST提交数据时,后台没法通过request.getParameter()
获取请求参数的。application/x-www-form-urlencoded
的参数放在了名为Form Data
的请求体中,而其他的则放在了名为Request Payload
的请求体中。那是什么原因了?
解析请求参数的实现源码org.apache.catalina.connector.Request#parseParameters
如下:
protected void parseParameters() {
this.parametersParsed = true;
Parameters parameters = this.coyoteRequest.getParameters();
boolean success = false;
try {
parameters.setLimit(this.getConnector().getMaxParameterCount());
Charset charset = this.getCharset();
boolean useBodyEncodingForURI = this.connector.getUseBodyEncodingForURI();
parameters.setCharset(charset);
if (useBodyEncodingForURI) {
parameters.setQueryStringCharset(charset);
}
parameters.handleQueryParameters();
if (this.usingInputStream || this.usingReader) {
success = true;
return;
}
//获取请求的contentType
String contentType = this.getContentType();
if (contentType == null) {
contentType = "";
}
int semicolon = contentType.indexOf(59);
if (semicolon >= 0) {
contentType = contentType.substring(0, semicolon).trim();
} else {
contentType = contentType.trim();
}
//处理文件上传
if ("multipart/form-data".equals(contentType)) {
this.parseParts(false);
success = true;
return;
}
if (!this.getConnector().isParseBodyMethod(this.getMethod())) {
success = true;
return;
}
if ("application/x-www-form-urlencoded".equals(contentType)) {
//如果是这种contenttype则解析
int len = this.getContentLength();
if (len <= 0) {
if ("chunked".equalsIgnoreCase(this.coyoteRequest.getHeader("transfer-encoding"))) {
Object var21 = null;
Context context;
byte[] formData;
try {
formData = this.readChunkedPostBody();
} catch (IllegalStateException var17) {
parameters.setParseFailedReason(FailReason.POST_TOO_LARGE);
context = this.getContext();
if (context != null && context.getLogger().isDebugEnabled()) {
context.getLogger().debug(sm.getString("coyoteRequest.parseParameters"), var17);
}
return;
} catch (IOException var18) {
parameters.setParseFailedReason(FailReason.CLIENT_DISCONNECT);
context = this.getContext();
if (context != null && context.getLogger().isDebugEnabled()) {
context.getLogger().debug(sm.getString("coyoteRequest.parseParameters"), var18);
}
return;
}
if (formData != null) {
parameters.processParameters(formData, 0, formData.length);
}
}
} else {
int maxPostSize = this.connector.getMaxPostSize();
Context context;
if (maxPostSize >= 0 && len > maxPostSize) {
context = this.getContext();
if (context != null && context.getLogger().isDebugEnabled()) {
context.getLogger().debug(sm.getString("coyoteRequest.postTooLarge"));
}
this.checkSwallowInput();
parameters.setParseFailedReason(FailReason.POST_TOO_LARGE);
return;
}
context = null;
byte[] formData;
if (len < 8192) {
if (this.postData == null) {
this.postData = new byte[8192];
}
formData = this.postData;
} else {
formData = new byte[len];
}
try {
if (this.readPostBody(formData, len) != len) {
parameters.setParseFailedReason(FailReason.REQUEST_BODY_INCOMPLETE);
return;
}
} catch (IOException var19) {
Context context = this.getContext();
if (context != null && context.getLogger().isDebugEnabled()) {
context.getLogger().debug(sm.getString("coyoteRequest.parseParameters"), var19);
}
parameters.setParseFailedReason(FailReason.CLIENT_DISCONNECT);
return;
}
// 处理POST请求参数,把它放到requestparameter map中(即request.getParameterMap获取到的Map,request.getParameter(name)也是从这个Map中获取的)
parameters.processParameters(formData, 0, len);
}
success = true;
return;
}
success = true;
} finally {
if (!success) {
parameters.setParseFailedReason(FailReason.UNKNOWN);
}
}
}