好得很程序员自学网

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

spring应用中多次读取http post方法中的流遇到的问题

一、问题简述

先说下为啥有这个需求,在基于 spring 的web应用中,一般会在 controller 层获取 http 方法body中的数据。

方式1:

比如http请求的 content-type 为 application/json 的情况下,直接用 @requestbody 接收。

方式2:

也有像目前我们在做的这个项目,比较原始,是直接手动读取 流 。(不要问我为啥这么原始,第一版也不是我写的。)

?

1

2

3

4

5

6

@requestmapping ( "/xxx.do" )

   public void xxx(httpservletrequest request, httpservletresponse response) throws ioexception {

     jsonobject jsonobject = webutils.getparameters(request);

     //业务处理

     responseutil.setresponse(response, messagefactory.createsuccessmsg());

   }

webutils.getparameters如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

public static jsonobject getparameters(httpservletrequest request) throws ioexception {

   inputstream is = null ;

   is = new bufferedinputstream(request.getinputstream(), buffer_size);

   int contentlength = integer.valueof(request.getheader( "content-length" ));

   byte [] bytes = new byte [contentlength];

   int readcount = 0 ;

   while (readcount < contentlength) {

     readcount += is.read(bytes, readcount, contentlength - readcount);

   }

   string requestjson = new string(bytes, appconstants.utf8);

   if (stringutils.isblank(requestjson)) {

     return new jsonobject();

   }

   jsonobject jsonobj = jsonutils.tojsonobject(requestjson);

   return jsonobj;

}

当然,不管怎么说,都是对流进行读取。

问题是,假如我想在controller前面加一层aop,aop里面对进入controller层的方法进行日志记录,记录方法参数,应该怎么办呢。

如果是采用了方式1的话,简单。spring已经帮我们把参数从流里取出来,给我们提供好了,我们拿着打印一下日志即可。

如果是比较悲剧地采用了我们这种方式,参数里只有个httpservletrequest,那就只有自己去读取流了,然而,在aop中我们把流读了的话,

在controller层就读不到了。

毕竟,流只能读一次啊。

二、怎么一个流读多次呢

说一千道一万,流来自哪里,来自

?

1

javax.servlet.servletrequest#getinputstream

所以,我们的思路,是不是可以这样,定义一个filter,在filter中将request替换为我们自定义的request。

下面标红的为自定义的request。

?

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

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

/**

  *

  */

package com.ckl.filter;

import com.ckl.utils.basewebutils;

import com.ckl.utils.multireadhttpservletrequest;

import org.slf4j.logger;

import org.slf4j.loggerfactory;

import org.springframework.core.annotation.order;

import org.springframework.http.httpmethod;

import org.springframework.http.mediatype;

import javax.servlet.*;

import javax.servlet.annotation.webfilter;

import javax.servlet.http.httpservletrequest;

import java.io.ioexception;

/**

  * web流多次读写过滤器

  *

  * 拦截所有请求,主要是针对第三方提交过来的请求.

  * 为什么要做成可多次读写的流,因为可以在aop层打印日志。

  * 但是不影响controller层继续读取该流

  *

  * 该filter的原理:https://stackoverflow测试数据/questions/10210645/http-servlet-request-lose-params-from- post -body-after-read-it-once/17129256#17129256

  * @author ckl

  */

@order ( 1 )

@webfilter (filtername = "cacherequestfilter" , urlpatterns = "*.do" )

public class cacherequestfilter implements filter {

   private static final logger logger = loggerfactory.getlogger(cacherequestfilter. class );

   @override

   public void init(filterconfig filterconfig) throws servletexception {

     // todo auto-generated method stub

   }

   @override

   public void dofilter(servletrequest request, servletresponse response,

              filterchain chain) throws ioexception, servletexception {

     httpservletrequest httpservletrequest = (httpservletrequest) request;

     logger.info( "request uri:{}" ,httpservletrequest.getrequesturi());

     if (basewebutils.isformpost(httpservletrequest)){

       httpservletrequest = new multireadhttpservletrequest(httpservletrequest);

       string parameters = basewebutils.getparameters(httpservletrequest);

       logger.info( "cacherequestfilter receive post req. body is {}" , parameters);

     } else if (ispost(httpservletrequest)){

       //文件上传请求,没必要缓存请求

       if (request.getcontenttype().contains(mediatype.multipart_form_data_value)){

       } else {

         httpservletrequest = new multireadhttpservletrequest(httpservletrequest);

         string parameters = basewebutils.getparameters(httpservletrequest);

         logger.info( "cacherequestfilter receive post req. body is {}" , parameters);

       }

     }

     chain.dofilter(httpservletrequest, response);

   }

   @override

   public void destroy() {

     // todo auto-generated method stub

   }

   public static boolean ispost(httpservletrequest request) {

     return httpmethod.post.matches(request.getmethod());

   }

}

multireadhttpservletrequest.java:

import org.apache测试数据mons.io.ioutils;

import javax.servlet.servletinputstream;

import javax.servlet.http.httpservletrequest;

import javax.servlet.http.httpservletrequestwrapper;

import java.io.bufferedreader;

import java.io.bytearrayoutputstream;

import java.io.ioexception;

import java.io.inputstreamreader;

/**

  * desc:

  * https://stackoverflow测试数据/questions/10210645/http-servlet-request-lose-params-from-post-body-after-read-it-once/17129256#17129256

  * @author : ckl

  * creat_date: 2018/8/2 0002

  * creat_time: 13:46

  **/

public class multireadhttpservletrequest extends httpservletrequestwrapper {

   private bytearrayoutputstream cachedbytes;

   public multireadhttpservletrequest(httpservletrequest request) {

     super (request);

     cachedbytes = new bytearrayoutputstream();

     servletinputstream inputstream = null ;

     try {

       inputstream = super .getinputstream();

       ioutils.copy(inputstream, cachedbytes);

     } catch (ioexception e) {

       e.printstacktrace();

     }

   }

   @override

   public servletinputstream getinputstream() throws ioexception {

     return new cachedservletinputstream(cachedbytes);

   }

   @override

   public bufferedreader getreader() throws ioexception {

     return new bufferedreader( new inputstreamreader(getinputstream()));

   }

}

在自定义的request中,构造函数中,先把原始流中的数据读出来,放到bytearrayoutputstream cachedbytes中。

并且需要重新定义getinputstream方法。

以后每次程序中调用getinputstream方法时,都会从我们的偷梁换柱的request中的cachedbytes字段,new一个inputstream出来。

看上图红色部分:

getinputstream我们返回了自定义的cachedservletinputstream类。

那么,接下来是cachedservletinputstream:

?

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

package com.ceiec.webservice.utils;

import javax.servlet.readlistener;

import javax.servlet.servletinputstream;

import java.io.bytearrayinputstream;

import java.io.bytearrayoutputstream;

import java.io.ioexception;

/**

  * an inputstream which reads the cached request body

  */

public class cachedservletinputstream extends servletinputstream {

   private bytearrayinputstream input;

   public cachedservletinputstream(bytearrayoutputstream cachedbytes) {

     // create a new input stream from the cached request body

     byte [] bytes = cachedbytes.tobytearray();

     input = new bytearrayinputstream(bytes);

   }

   @override

   public int read() throws ioexception {

     return input.read();

   }

   @override

   public boolean isfinished() {

     return false ;

   }

   @override

   public boolean isready() {

     return false ;

   }

   @override

   public void setreadlistener(readlistener readlistener) {

   }

}

至此。完整的偷梁换柱就结束了。

现在,请再回过头去,看文章开头的代码,标红的部分。

是不是豁然开朗了?

三、代码地址

https://github测试数据/cctvckl/work_util/tree/master/spring-mvc-multiread-post

直接git 下载即可。

这是个单独的工程,直接eclipse或者idea导入即可。

运行方法:

我这边讲下idea:

直接运行jetty:run这个goal即可。

然后访问testpost.do即可(下面把curl贴出来,可以自己在接口测试工具里拼装):

?

1

2

3

4

5

6

curl -i -x post \

-h "content-type:application/json" \

-d \

'{ "id" : "32" }

' \

'http://localhost:8080/springmvc-multiread-post/testpost.do'

我这边演示下效果,可以发现,两次都读出来了:

总结

以上所述是小编给大家介绍的spring应用中多次读取http post方法中的流,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对网站的支持!

原文链接:http://HdhCmsTestcnblogs测试数据/grey-wolf/p/9953661.html

查看更多关于spring应用中多次读取http post方法中的流遇到的问题的详细内容...

  阅读:15次