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

网友投稿 373 2023-01-19


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

一、问题简述

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

方式1:

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

方式2:

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

@RequestMapping("/XXX.do")

public void XXX(HttpServletRequest request, HttpServletResponse response) throws IOException {

JSONObject jsonObject = WebUtils.getParameters(request);

     //业务处理

ResponseUtil.setResponse(response, MessageFactory.createSuccessMsg());

}

WebUtils.getParameters如下:

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层就读不到了。

毕竟,流只能读一次啊。

二、怎么一个流读多次呢

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

javax.servlet.ServletRequest#getInputStream

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

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

/**

*

*/

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.com/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 {

phttp://rivate 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.commons.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.com/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:

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.com/cctvckl/work_util/tree/master/spring-mvc-multiread-post

直接git 下载即可。

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

运行方法:

我这边讲下idea:

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

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

curl -i -X POST \

-H "Content-Type:application/json" \

-d \

'{"id":"32"}

' \

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

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

总结

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


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:包含post测试入门教程的词条
下一篇:接口日志管理工具(接口调用日志)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~