springboot接⼝如何多次获取request中的body内容
1. 概述
在使⽤springboot开发接⼝时,会将参数转化为Bean,⽤来进⾏参数的⾃动校验。同时也想获取request中原始body报⽂进⾏验签(防⽌报⽂传输过程中被篡改)。
因为通过将bean再转化为字符串后,body⾥⾯的报⽂格式、字段顺序会发⽣改变,就会导致验签失败。因此只能通过request 来获取body⾥⾯的内容。
既想接⼝⾃动实现参数校验,同时⼜想获取request中的原始报⽂,因此我们可以通过在controller中的restful⽅法中,写⼊两个参数,获取多次request中的body内容。
那么如何实现多次读取body内容了(因为request⾥的body是以字节流的⽅式读取的,默认情况下读取⼀次,字节流就没有了),下⾯就来⼤致分析⼀下。
2 接⼝接收参数的其他⽅式
2.1 接收参数⽅法⼀
⽅法⼀、
public R list(@RequestBody String rawMsg)
采⽤上述⽅式可以直接获得请求报⽂中的原始body信息,⽽且当body是⼀个json字符串时,rawMsg参数接⼝到的body值,不会改变json中key的顺序,即与发送⽅的body内容是保持⼀致的。这种⽅式可以⽤来对报⽂验签,因为被加密的字符串与发送⽅是保持⼀致的。
这种⽅式可以接受request⾥⾯body内容的原始格式,保持与发送⽅⼀致。
如下就可以对原始报⽂进⾏验签操作了
// ⽤公钥,对原始报⽂进⾏验签,在这⾥如果rawMsg⾥⾯是json时,当key的顺序改变后,会验签失败,
//如此我们可以通过request来获取body⾥⾯的原始报⽂
boolean verifyResult = SignVerifyUtils.verifySignature(rawMsg, Constant.NPIS_PUBLIC_KEY);
2.2 接收参数⽅法⼆
⽅法⼆、
public R list(@RequestBody @Validated ReqBean<ABCReqBean> abcReqBean)
这种接受参数的⽅法,可以将request⾥的json报⽂,直接转换成对应的bean对象。并且可以⽤来校验参数,例如某个字段是必传的、某个字段的值最⼤是多少等等。例如
@NotNull(message = "⽇期字段不允许为空")
@Size(min = 8, max = 8, message = "⽇期字符串的长度必须为 8")
private String beginDate;
有没有⼀种⽅法,既能同时利⽤参数校验功能,⼜能获取原始body⾥的内容来进⾏验签呢,这时候就可以采⽤下⾯的第3中⽅法。
2.3 接收参数⽅法三
@RequestMapping(method = {RequestMethod.POST}, value = "/dataQry")
public R list(@RequestBody @Validated ReqBean<ABCReqBean> abcReqBean,HttpServletRequest request){
}
在这⾥就可以通过将报⽂转换成abcReqBean对象,并实现接⼝参数的⾃动校验功能;同时可以利⽤request获取原始报⽂来进⾏验签。
注意:由于在接收参数时,HttpServletRequest只能读取⼀次body内容(因为是读的字节流,读完就没了),因此我们需要需要做特殊处理,
下⾯来看⼀种基于SpringBoot来解决HttpServletRequest只能读取⼀次的问题。
2.3.1 继承HttpServletRequestWrapper包装类,每次读取body后,再将参数写会request
为解决上述多次读取request中的body内容的问题,我们只需要将以下两个类,放到项⽬中即可,并通过@Component来注测为spring bean即可
继承HttpServletRequestWrapper ,实现每次读取request中的body后,在将内容写回request。
package fig;
import org.apachemons.io.IOUtils;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
/**
* @author:
*/
public class RequestWrapper extends HttpServletRequestWrapper {
//参数字节数组
private byte[] requestBody;
/
/Http请求对象
private HttpServletRequest request;
public RequestWrapper(HttpServletRequest request) throws IOException {
super(request);
}
/**
* @return
* @throws IOException
*/
@Override
public ServletInputStream getInputStream() throws IOException {
/**
* 每次调⽤此⽅法时将数据流中的数据读取出来,然后再回填到InputStream之中
* 解决通过@RequestBody和@RequestParam(POST⽅式)读取⼀次后控制器拿不到参数问题
*/
if (null == questBody) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
}
final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody);
return new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener listener) {
}
@Override
public int read() {
ad();
spring framework网络系统参数}
};
}
public byte[] getRequestBody() {
return requestBody;
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStream()));
}
2.3.2 将包装类加⼊过滤器链
回写参数的包装类写好之后接下来就是加⼊过滤器链之中,如下:
package fig;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/
**
* @author:
*/
@Component
@WebFilter(filterName = "channelFilter", urlPatterns = {"/*"})
public class ChannelFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
try {
ServletRequest requestWrapper = null;
if (request instanceof HttpServletRequest) {
requestWrapper = new RequestWrapper((HttpServletRequest) request);
}
if (requestWrapper == null) {
chain.doFilter(request, response);
} else {
chain.doFilter(requestWrapper, response);
}
} catch (IOException e) {
e.printStackTrace();
} catch (ServletException e) {
e.printStackTrace();
}
}
@Override
public void destroy() {
}
}
解决springboot v2.2以上重复读取request body内容问题
⼀、需求
项⽬有两个场景会⽤到从Request的Body中读取内容。
1、打印请求⽇志
2、提供Api接⼝,在api⽅法执⾏前,从Request Body中读取参数进⾏验签,验签通过后在执⾏api⽅法⼆、解决⽅案
2.1 ⾃定义RequestWrapper
public class MyRequestWrapper extends HttpServletRequestWrapper {
private final String body;
public MyRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
this.body = ad(request);
}
public String getBody() {
return body;
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new Bytes());
return new ServletInputStream() {
...略
};
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStream()));
}
}
RequestReadUtils(⽹上抄的)
private static final int BUFFER_SIZE = 1024 * 8;
public static String read(HttpServletRequest request) throws IOException {
BufferedReader bufferedReader = Reader();
for (Enumeration<String> iterator = HeaderNames(); iterator.hasMoreElements();) {
String type = Element();
System.out.println(type+" = "+Header(type));
}
System.out.println();
StringWriter writer = new StringWriter();
write(bufferedReader,writer);
Buffer().toString();
}
public static long write(Reader reader,Writer writer) throws IOException {
return write(reader, writer, BUFFER_SIZE);
}
public static long write(Reader reader, Writer writer, int bufferSize) throws IOException
{
int read;
long total = 0;
char[] buf = new char[bufferSize];
while( ( read = ad(buf) ) != -1 ) {
writer.write(buf, 0, read);
total += read;
}
return total;
}
2.2 定义Filter
@WebFilter
public class TestFilter implements Filter{
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain){ HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
MyRequestWrapper wrapper = NativeRequest(request, MyRequestWrapper.class);
chain.doFilter(wrapper == null ? new MyRequestWrapper(request) :wrapper,servletRequest);
}
}
三、遇到问题
使⽤的SpringBoot v2.1.x版本
1、Form提交⽆问题
2、获取RequestBody⽆问题
使⽤SpringBoot v2.2.0以上版本(包括v2.3.x)
1、Form提交⽆法获取参数
2、获取RequestBody⽆问题
四、问题排查
经过排查,v2.2.x对⽐v2.1.x的不同在于⼀下代码差异:
BufferedReader bufferedReader = Reader();
-----------------
char[] buf = new char[bufferSize];
while( ( read = ad(buf) ) != -1 ) {
writer.write(buf, 0, read);
total += read;
}
当表单提交时
1、v2.1.x⽆法read到内容,读取结果为-1
2、v2.2.x、v2.3.x能够读取到内容
当表单提交时(x-www-form-urlencoded),inputStream读取⼀次后后续不会触发wrapper的getInputStream操作,所以Controller⽆法获取到参数。
解决⽅案
MyRequestWrapper改造
public class MyRequestWrapper extends HttpServletRequestWrapper {
private final String body;
public MyRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
this.body = getBodyString(request);
}
public String getBody() {
return body;
}
public String getBodyString(final HttpServletRequest request) throws IOException {
String contentType = ContentType();
String bodyString = "";
StringBuilder sb = new StringBuilder();
if (StringUtils.isNotBlank(contentType) && (ains("multipart/form-data") || ains("x-www-form-urlencoded"))) { Map<String, String[]> parameterMap = ParameterMap();
for (Map.Entry<String, String[]> next : Set()) {
String[] values = Value();
String value = null;
if (values != null) {
if (values.length == 1) {
value = values[0];
} else {
value = String(values);
}
}
sb.Key()).append("=").append(value).append("&");
}
if (sb.length() > 0) {
bodyString = sb.toString().substring(0, sb.toString().length() - 1);
}
return bodyString;
} else {
InputStream());
}
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new Bytes());
return new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public int read() {
ad();
}
@Override
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论