好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

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

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

一、需求

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

打印请求日志 提供Api接口,在api方法执行前,从Request Body中读取参数进行验签,验签通过后在执行api方法

二、解决方案

2.1 自定义RequestWrapper

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

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(网上抄的)

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

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

?

1

2

3

4

5

6

7

8

9

10

11

@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的不同在于一下代码差异:

?

1

2

3

4

5

6

7

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改造

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

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 v2.2以上重复读取Request Body内容的解决方案的详细内容...

  阅读:41次