社区编辑申请
注册/登录
JSON 数据读一次就没了,怎么办?
开发 项目管理
接口幂等性的处理,同一个接口,在短时间内接收到相同参数的请求,接口可能会拒绝处理。那么在判断的时候,就需要先把请求的参数提取出来进行判断,如果是 JSON 参数,此时就会有问题,参数提前取出来了,将来在接口中再去获取 JSON 参数,就会发现没有了。

对于前端传来的 JSON 数据,我们在服务端基本上都是通过 IO 流来解析,如果是古老的 Servlet,那么我们直接解析 IO 流;如果是在 SpringMVC 中,我们往往通过 @RequestBody 注解来解析。

如果通过 IO 流来解析参数,默认情况下,IO 流读一次就结束了,就没有了。而往往有些场景,需要我们多次读取参数,我举一个例子:

接口幂等性的处理,同一个接口,在短时间内接收到相同参数的请求,接口可能会拒绝处理。那么在判断的时候,就需要先把请求的参数提取出来进行判断,如果是 JSON 参数,此时就会有问题,参数提前取出来了,将来在接口中再去获取 JSON 参数,就会发现没有了。

我们来看看这个问题怎么解决,这也是最近松哥在做的 TienChin 项目的一个小知识点,和大家分享下。

新建一个 Spring Boot 项目,引入 Web 依赖,我们一起来看下面的问题。

1. 问题演示

假设我现在有一个处理接口幂等性的拦截器,在这个拦截器中,我需要先获取到请求的参数,然后进行比对等等,我这里先简单模拟一下,比如我们项目中有如下拦截器:

public class IdempotenceInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String s = request.getReader().readLine();
System.out.println("s = " + s);
return true;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

}
}

在这个拦截器中先把请求的参数拎出来,瞅一眼。通过 IO 流读取出来的参数最大特点是一次性,也就是读一次就失效了。

然后我们配置一下这个拦截器:

@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new IdempotenceInterceptor()).addPathPatterns("/**");
}
}

最后再来看看 Controller 接口:

@RestController
public class HelloController {
@PostMapping("/hello")
public void hello(@RequestBody String msg) throws IOException {
System.out.println("msg = " + msg);
}
}

在接口参数上我们加了 @RequestBody 注解,这个底层也是通过 IO 流来读取数据的,但是由于 IO 流在拦截器中已经被读取过一次了,所以到了接口中再去读取就会出错。报错信息如下:

然而很多时候,我们希望 IO 流能够被多次读取,那么怎么办呢?

2. 问题解决

这里我们可以利用装饰者模式对 HttpServletRequest 的功能进行增强,具体做法也很简单,我们重新定义一个 HttpServletRequest:

public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;

public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException {
super(request);
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
body = request.getReader().readLine().getBytes("UTF-8");
}

@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}

@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bais.read();
}

@Override
public int available() throws IOException {
return body.length;
}

@Override
public boolean isFinished() {
return false;
}

@Override
public boolean isReady() {
return false;
}

@Override
public void setReadListener(ReadListener readListener) {

}
};
}
}

这段代码并不难,很好懂。

首先在构造 RepeatedlyRequestWrapper 的时候,就通过 IO 流将数据读取出来并存入到一个 byte 数组中,然后重写 getReader 和 getInputStream 方法,在这两个读取 IO 流的方法中,都从 byte 数组中返回 IO 流数据出来,这样就实现了反复读取了。

接下来我们定义一个过滤器,让这个装饰后的 Request 生效:

public class RepeatableFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
ServletRequest requestWrapper = null;
if (request instanceof HttpServletRequest
&& StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) {
requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response);
}
if (null == requestWrapper) {
chain.doFilter(request, response);
} else {
chain.doFilter(requestWrapper, response);
}
}

@Override
public void destroy() {

}
}

判断一下,如果请求数据类型是 JSON 的话,就把 HttpServletRequest “偷梁换柱”改为 RepeatedlyRequestWrapper,然后让过滤器继续往下走。

最后再配置一下这个过滤器:

@Bean
FilterRegistrationBean<RepeatableFilter> repeatableFilterBean() {
FilterRegistrationBean<RepeatableFilter> bean = new FilterRegistrationBean<>();
bean.addUrlPatterns("/*");
bean.setFilter(new RepeatableFilter());
return bean;
}

好啦大功告成。

以后,我们的 JSON 数据就可以通过 IO 流反复读取了。

责任编辑:武晓燕 来源: 江南一点雨
相关推荐

2022-06-06 07:50:55

PythonJSON

2022-06-10 09:00:53

前端项目个JSON

2022-06-30 18:17:00

数据集云数据建模计数据仓库

2022-06-15 08:25:07

Python天气数据可视化分析

2022-06-27 17:46:53

PythonFlask

2022-06-28 09:26:25

Python配置文件

2022-06-23 11:42:22

MySQL数据库

2022-06-29 09:19:09

静态代码C语言c代码

2022-06-27 19:01:04

Python应用程序数据

2022-06-14 15:07:04

IPC客户端服务端

2022-06-23 12:43:36

区块链加密货币

2022-07-03 06:10:15

2022-06-28 22:13:33

Polars数据处理与分析

2022-06-24 10:16:59

Python精选库

2022-07-01 05:47:19

PyCharm插件开发

2022-06-21 10:04:25

数据中心智慧城市

2022-06-24 10:52:47

人工智能作业帮T前线

2022-06-29 14:46:00

网络攻击数据泄露勒索软件

2022-07-01 14:25:27

机器学习人工智能工业4.0

2022-06-15 16:16:21

分布式数据库鸿蒙

编辑推荐

从人肉运维到智能运维,京东金融服务监控的进阶之路阿里10年分布式数据库技术沉淀,AliSQL X-Cluster的应用实战爱奇艺CTO汤兴:道天地将法,《孙子兵法》的管理之道强一致、高可用、自动容灾能力背后,阿里X-Paxos的应用实践猫眼电影李明辉:机器学习在票房预估中的实战
我收藏的内容
点赞
收藏

51CTO技术栈公众号