最近在做AntdPro 的项目,但是使用框架的post请求后台SpringMVC接收不到参数,经过研究发现了我们所谓的请求之密。
HTTP请求中,如果是get请求,那么表单参数以name=value&name1=value1的形式附到url的后面,如果是post请求,那么表单参数是在请求体中,也是以name=value&name1=value1的形式在请求体中。我们先看一下我们的请求头
GET请求
RequestURL:http://127.0.0.1:8080/test/test.do?name=mikan&address=street
Request Method:GET
Status Code:200 OK
Request Headers
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Query String Parameters
name:mikan
address:street
可以看到我们的GET请求参数是被存放在QueryString
中的(可以通过request.getParameter()
获取请求参数)
POST请求
POST请求参数的存放位置是和我们的Content-Type
有关的
1.表单提交与Jquery异步请求
RequestURL:http://127.0.0.1:8080/test/test.do
Request Method:POST
Status Code:200 OK
Request Headers
Content-Type:application/x-www-form-urlencoded
Form Data
name:mikan
address:street
可以看到我们的POST请求参数是被存放在Form Data
中的(可以通过request.getParameter()
获取请求参数)
2.JS原生异步请求XMLHttpRequest
RequestURL:http://127.0.0.1:8080/test/test.do
Request Method:POST
Status Code:200 OK
Request Headers
Content-Type:text/plain;charset=UTF-8
Request Payload
name=mikan&address=street
注意同样是POST请求,默认的Content-Type:text/plain;charset=UTF-8
而且参数存放在了Request Payload
(无法通过request.getParameter()
获取请求参数)
3.文件上传
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryda3sd7BGGCFjy3P6
Request Payload
------WebKitFormBoundaryda3sd7BGGCFjy3P6
Content-Disposition: form-data; name="file"; filename="413.png"
Content-Type: image/png
------WebKitFormBoundaryda3sd7BGGCFjy3P6--
默认的Content-Type: multipart/form-data;
而且参数存放在了Request Payload
4.指定参数格式为Json的POST请求
Content-Type: application/json; charset=utf-8
Request Payload
{"option":"ZC","approveMsg":"参数","taskId":"8a948c4c5e363076015e36d0b4980004"}
默认的Content-Type: application/json;
而且参数存放在了Request Payload
(无法通过request.getParameter()
获取请求参数)
基本的POST请求就上述的四种情况,我们最常用的就是表单的POST请求以及JQuery的POST异步请求,这种POST请求默认的Content-Type:application/x-www-form-urlencoded
也就是键值对的提交方式,而剩下的三种方式我们都无法通过
request.getParameter()
或者框架字段映射获取参数(这三种方式的Content-Type 都不为application/x-www-form-urlencoded), 那么原因何在呢?我们来看一下服务器接受请求的源码
protectedvoid parseParameters() {
//省略部分代码......
parameters.handleQueryParameters();// 这里是处理url中的参数
//省略部分代码......
if ("multipart/form-data".equals(contentType)) { // 这里是处理文件上传请求
parseParts();
success = true;
return;
}
if(!("application/x-www-form-urlencoded".equals(contentType))) {// 这里如果是非POST请求直接返回,不再进行处理
success = true;
return;
}
//下面的代码才是处理POST请求参数
//省略部分代码......
try {
if (readPostBody(formData, len)!= len) { // 读取请求体数据
return;
}
} catch (IOException e) {
// Client disconnect
if(context.getLogger().isDebugEnabled()) {
context.getLogger().debug(
sm.getString("coyoteRequest.parseParameters"),e);
}
return;
}
parameters.processParameters(formData, 0, len); // 处理POST请求参数,把它放到requestparameter map中(即request.getParameterMap获取到的Map,request.getParameter(name)也是从这个Map中获取的)
// 省略部分代码......
}
protected int readPostBody(byte body[], int len)
throws IOException {
int offset = 0;
do {
int inputLen = getStream().read(body, offset, len - offset);
if (inputLen <= 0) {
return offset;
}
offset += inputLen;
} while ((len - offset) > 0);
return len;
}
我们可以看到只有在不是文件上传且ContentType是"application/x-www-form-urlencoded"
的时候 我们会将参数存放在Map中,而我们的request.getParameter
正是从此Map中取值,所以原因就很明了了
那我们如何接受上述的2,3,4方式的参数呢?
- 文件上传我们需要框架的支持,这里就不多说了
- 我们可以设置ContentType为
"application/x-www-form-urlencoded"
(通用) - ContentType为
"application/json"
是以Json格式传输数据,我们后台可以使用@RequestBody
注解接受 - ContentType为
"text/plain"
的,我们可以使用流进行读取
private String getStringFromStream(HttpServletRequest req) {
ServletInputStream is;
try {
is = req.getInputStream();
int nRead = 1;
int nTotalRead = 0;
byte[] bytes = new byte[10240];
while (nRead > 0) {
nRead = is.read(bytes, nTotalRead, bytes.length - nTotalRead);
if (nRead > 0)
nTotalRead = nTotalRead + nRead;
}
String str = new String(bytes, 0, nTotalRead, "utf-8");
return str;
} catch (IOException e) {
e.printStackTrace();
return "";
}
}
String rev = getStringFromStream(request);
Object obj = JsonUtils.jsonToObj(rev,CompleteTaskVo.class);
if(obj instanceof CompleteTaskVo){
params = (CompleteTaskVo)obj;
}
总结
HTTP POST表单请求提交时:Content-Typeapplication/x-www-form-urlencoded,而使用原生AJAX的POST请求如果不指定请求头RequestHeader,默认使用的Content-Type是text/plain;charset=UTF-8。
表单提交数据是名值对的方式,而文件上传服务器需要特殊处理,普通的post请求数据格式不固定,不一定是名值对的方式,所以服务器无法知道具体的处理方式,所以只能通过获取原始数据流的方式来进行解析。jquery在执行post请求时,会设置Content-Type为application/x-www-form-urlencoded,所以服务器能够正确解析,而使用原生ajax请求时,如果不显示的设置Content-Type,那么默认是text/plain,这时不能用request.getParameter(name)的形式获取,所以才只能通过获取原始数据流的方式来进行解析请求数据。