SpringBoot v2.2以上重复读取Request Body内容的解决方案

作者:jlcon 时间:2022-06-27 11:00:16 

SpringBoot v2.2以上重复读取Request Body内容

一、需求

项目有两个场景会用到从Request的Body中读取内容。

  • 打印请求日志

  • 提供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 = RequestReadUtils.read(request);
}
public String getBody() {
 return body;
}
@Override
public ServletInputStream getInputStream() throws IOException {
 final ByteArrayInputStream bais = new ByteArrayInputStream(body.getBytes());  
 return new ServletInputStream() {
  ...略
 };
}
@Override
public BufferedReader getReader() throws IOException {
 return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
}

RequestReadUtils(网上抄的)


private static final int BUFFER_SIZE = 1024 * 8;

public static String read(HttpServletRequest request) throws IOException {
       BufferedReader bufferedReader = request.getReader();
       for (Enumeration<String> iterator = request.getHeaderNames(); iterator.hasMoreElements();) {
        String type = iterator.nextElement();
  System.out.println(type+" = "+request.getHeader(type));
 }
       System.out.println();
       StringWriter writer = new StringWriter();
       write(bufferedReader,writer);
       return writer.getBuffer().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 = reader.read(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 = WebUtils.getNativeRequest(request, MyRequestWrapper.class);
 chain.doFilter(wrapper == null ? new MyRequestWrapper(request) :wrapper,servletRequest);
}
}

三、遇到问题

使用的SpringBoot v2.1.x版本

  • Form提交无问题

  • 获取RequestBody无问题

使用SpringBoot v2.2.0以上版本(包括v2.3.x)

  • Form提交无法获取参数

  • 获取RequestBody无问题

四、问题排查

经过排查,v2.2.x对比v2.1.x的不同在于一下代码差异:


BufferedReader bufferedReader = request.getReader();
-----------------
char[] buf = new char[bufferSize];
while( ( read = reader.read(buf) ) != -1 ) {
   writer.write(buf, 0, read);
   total += read;
}

当表单提交时

  • v2.1.x无法read到内容,读取结果为-1

  • 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 = request.getContentType();
    String bodyString = "";
    StringBuilder sb = new StringBuilder();
    if (StringUtils.isNotBlank(contentType) && (contentType.contains("multipart/form-data") || contentType.contains("x-www-form-urlencoded"))) {
        Map<String, String[]> parameterMap = request.getParameterMap();
        for (Map.Entry<String, String[]> next : parameterMap.entrySet()) {
            String[] values = next.getValue();
            String value = null;
            if (values != null) {
                if (values.length == 1) {
                    value = values[0];
                } else {
                    value = Arrays.toString(values);
                }
            }
            sb.append(next.getKey()).append("=").append(value).append("&");
        }
        if (sb.length() > 0) {
            bodyString = sb.toString().substring(0, sb.toString().length() - 1);
        }
        return bodyString;
    } else {
        return IOUtils.toString(request.getInputStream());
    }
}
@Override
public ServletInputStream getInputStream() throws IOException {
 final ByteArrayInputStream bais = new ByteArrayInputStream(body.getBytes());

return new ServletInputStream() {
  @Override
  public boolean isFinished() {
   return false;
  }
  @Override
  public boolean isReady() {
   return false;
  }
  @Override
  public int read() {
   return bais.read();
  }
  @Override
  public void setReadListener(ReadListener readListener) {
  }
 };
}
@Override
public BufferedReader getReader() throws IOException {
 return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
}

SpringBoot 读取Request参数的坑

后端拿参数相关

默认配置时,

getInputStream()和getReader()一起使用会报错

使用两遍getInputStream(),第二遍会为空

当存在@RequestBody等注解时,springMVC已读取过一遍流,默认单独使用getInputStream()或getReader()都为空。

解决:写filter继承HttpServletRequestWrapper,缓存InputStream,覆盖getInputStream()和getReader()方法,使用ByteArrayInputStream is = new ByteArrayInputStream(body.getBytes());读取InputStream。

注意:springboot中,过滤器只需@Component即可生效,另外可在FilterRegistrationBean中配置路径和优先级。

对于 * ,必须在InterceptorRegistry中调用addInterceptor方法。(路径可链式添加)

关于流

只能读一遍,类似管子。

只承担传输职责,而与处理和存储无关。

对于byte流而言,进行重复读取易于实现,但指针不重置,应是为了与InputStream接口定义保持一致。

来源:https://blog.csdn.net/jlcon/article/details/108520840

标签:SpringBoot,Request,Body
0
投稿

猜你喜欢

  • Android用tabhost实现 界面切换,每个界面为一个独立的activity操作

    2021-11-09 11:22:26
  • 简单记事本java源码实例

    2023-11-26 02:03:17
  • Java深入讲解Object类常用方法的使用

    2022-11-22 00:16:51
  • Android开发Jetpack Compose元素Modifier特性详解

    2023-03-10 23:29:54
  • C#设计模式之Singleton模式

    2022-03-28 09:01:31
  • 详解Java实践之建造者模式

    2023-01-14 23:03:13
  • mvn中dependencyManagement的使用详解

    2021-11-05 10:19:20
  • springboot 整合 seata的配置过程

    2023-01-13 01:28:33
  • java.util.Collection源码分析与深度理解

    2022-07-31 09:05:52
  • Android语音声波控件 Android条形波控件

    2023-10-29 02:03:05
  • Spring Boot统一异常处理最佳实践(拓展篇)

    2023-10-29 16:00:04
  • C#无损压缩图片

    2022-05-26 22:54:39
  • java 8如何自定义收集器(collector)详解

    2022-02-12 07:22:17
  • java显示当前运行时的参数(java运行参数)

    2023-09-07 10:03:22
  • Java设计模式之java桥接模式详解

    2023-10-15 15:19:10
  • 实现一个Android锁屏App功能的难点总结

    2022-07-21 03:03:19
  • Mybatis Trim标签用法简单介绍

    2021-10-11 03:04:00
  • Android Toast实现全屏显示

    2023-12-22 09:16:05
  • Java Spring Controller 获取请求参数的几种方法详解

    2023-04-07 02:11:17
  • Spring Boot如何使用Spring Security进行安全控制

    2022-03-26 03:59:41
  • asp之家 软件编程 m.aspxhome.com