HTTP协议中定义了很多与服务端交互的方法,其中最基本的有四种,分别是GET,POST,PUT,DELETE,对应增删改查四种操作,最常用的是GET和POST。
请求头类型Content-Type主要有:application/x-www-form-urlencoded,multipart/form-data和application/json。
application/x-www-form-urlencoded默认传参
其中application/x-www-form-urlencoded为默认的表单提交方式,提交的参数在请求体的form-data里。此时,前端如果通过ajax的默认方式传送参数而不做任何转换的话,参数到达后端是key1=value1&key2=value2…的形式,如下图所示(通过request.getReader获得缓冲输入流):
这种形式的参数可以被spingMVC默认的ServletModelAttributeMethodProcessor解析(通过request.getParameter获取)。如下图所示:
最终在处理方法处可以自动绑定到入参DTO上:
application/x-www-form-urlencoded + json 传参
前端如果通过JSON.stringify()进行编码,或者直接以json串形式传输的话,参数到达后端则是json形式,如下图所示:
此时默认的处理器是无法处理的,如下图所示:
ServletModelAttributeMethodProcessor解析时调用WebUtils的getParametersStartingWith方法,通过request.getParameterNames()来获取参数,以“=”和“&”做截断,json格式的参数中没有这两个符号,故解析出错。
此时后端就不能使用springmvc默认的请求处理器来解析了,也不能通过加@RequestBody使用RequestMappingHandlerAdapter处理器,因为不支持application/x-www-form-urlencoded,如下图所示:
application/json + json 传参
大多数情况下是这种形式的传参,正常处理即可。
下面说说博主实际遇到的问题。
有一个后端业务信息管理系统负责对业务系统的数据进行管理,增删改查等。管理系统的所有前端请求都是通过页面的ajax提交,使用默认请求头,参数未进行序列化。后端参数接收时未加注解。
现要增加工具方法接口,直接通过请求工具来发出请求,由于具体请求参数未知,后端需要通过HttpServerletRequest来接受请求的request。通过postman工具可以正常使用,但使用公司的小工具发送请求则会出现异常,原因是跨域请求(什么是跨域请求)。
于是添加了一个过滤器,该过滤器设置允许跨域请求,同时对request进行包装,以便多次读取(正常情况下,request的输入流只能读取一次)。最后在controller层通过request的getReader获取处理方法的request的输入流:
protected String getJsonRequestData(HttpServletRequest request, HttpServletResponse response) {
if (null == request) {
return null;
}
String method = request.getMethod();
String ret = null;
if (method.equalsIgnoreCase("GET") || method.equalsIgnoreCase("DELETE")) {
ret = request.getQueryString();
} else {
ret = getBodyData(request);//获取请求body
}
return ret;
}
protected String getBodyData(HttpServletRequest request) {
StringBuffer data = new StringBuffer();
String line = null;
BufferedReader reader = null;
try {
reader = request.getReader();
while (null != (line = reader.readLine()))
data.append(line);
} catch (IOException e) {
LOG.error("get request body data error: ", e);
return null;
}
return data.toString();
}
这时候接口工具可以正常使用,解决了跨域问题。
但是问题来了,由于过滤器对request请求进行了包装,导致默认请求处理器无法对包装后的request参数进行解析(因为没有重写getParameter方法),所以原来的所有接口都无法获取请求参数。
解决办法有以下几种:
1. 添加@RequestBody注解,同时在ajax发送请求时,参数进行json序列化,且指定contentType为application/json(如果不指定,则不支持默认application/x-www-form-urlencoded类型)。
2. 使用自定义注解,用自定义参数解析器进行参数解析,同时在ajax发送请求时参数进行json序列化。
3. 过滤器中排除原接口请求路径,即原来的所有接口的请求都不过滤。
4. 过滤器中只过滤新的请求。实际上与方法3是一样,根据需要来选择。
显然方法3和方法4是改动最小的。