通常我只定义一个拦截器,对请求参数处理的时候,尤其是对post需要用到流,那这样处理完后跑到控制器那边就会报request body missing
错误提示:
流已经关闭,因为如果通过过滤拦截器读取流的话,IO流关闭只能读取一次, 即使不关闭的话,流有个read标志位,后续控制器会从read标志位开始读,读过流之后就读取不到数据了,除非利用void reset()方法,把pos位置位开始,重新读,但是不是任何流都可以使用,所以我们写通用的方法时候,读取完流中数据之后,需要进行包装request,将流重新写入,供后续控制器读取。
网络上解释如下:
那是因为流对应的是数据,数据放在内存中,有的是部分放在内存中。read 一次标记一次当前位置(mark position),第二次read就从标记位置继续读(从内存中copy)数据。 所以这就是为什么读了一次第二次是空了。 怎么让它不为空呢?只要inputstream 中的pos 变成0就可以重写读取当前内存中的数据。javaAPI中有一个方法public void reset() 这个方法就是可以重置pos为起始位置,但是不是所有的IO读取流都可以调用该方法!ServletInputStream是不能调用reset方法,这就导致了只能调用一次getInputStream()。
解决方法
1.自定义一个request
package com.zhongyun.zycx.utils;
import org.springframework.util.StreamUtils;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
/**
* @author lc
* @version 1.0
* @date 2021/10/27 10:27
*/
public class MyHttpServletRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
private String bodyString;
public MyHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
this.bodyString = StreamUtils.copyToString(request.getInputStream(), Charset.forName("UTF-8"));
body = bodyString.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 boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() throws IOException {
return bais.read();
}
};
}
}
写一个过滤器
一定要在启动类上加@ServletComponentScan(“com.zhongyun.zycx.handler”)
package com.zhongyun.zycx.handler;
import com.zhongyun.zycx.utils.MyHttpServletRequestWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@WebFilter(urlPatterns = "/*",filterName = "channelFilter")
public class ChannelFilter implements Filter {
private Logger logger = LoggerFactory.getLogger(ChannelFilter.class);
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
logger.info("================进入过滤器======================");
// 防止流读取一次后就没有了, 所以需要将流继续写出去
filterChain.doFilter(new MyHttpServletRequestWrapper((HttpServletRequest) servletRequest), servletResponse);
}
@Override
public void destroy() {
}
}
拦截器
package com.zhongyun.zycx.handler;
import net.sf.json.JSONObject;
import org.springframework.web.servlet.HandlerInterceptor;
import weixin.popular.util.StreamUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.nio.charset.Charset;
/**
* @author lc
* @version 1.0
* @date 2021/10/26 14:51
*/
public class TicketsInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
crossDomain(request,response); // 设置请求头,解决跨域问题
String word = "测试";
String method = request.getMethod();if ("GET".equals(method)) {
if (request.getParameter("word") != null) {
word = request.getParameter("word");
}
}
if ("POST".equals(method)) {
String paramstr = StreamUtils.copyToString(request.getInputStream(), Charset.defaultCharset());
JSONObject jsonObject = JSONObject.fromObject(paramstr);
if (jsonObject.containsKey("word")) {
word = jsonObject.getString("word");
}
}
if (word == null || word.length() < 2) {
response.setStatus(500, "word最少为2位");
return false;
}
System.out.println("释放拦截器");
return true;
}
// 跨域
public void crossDomain(HttpServletRequest request, HttpServletResponse response) {
response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
response.setHeader("Access-Control-Allow-Credentials", "true");
}
}
拦截器配置
package com.zhongyun.zycx.handler;
import org.aopalliance.intercept.Interceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 拦截器的配置
*
* @author lc
* @version 1.0
* @date 2021/10/26 14:58
*/
@Configuration//标识这是一个配置类
public class InterceptorConfiguration implements WebMvcConfigurer {
// 拦截路径
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册Interceptor拦截器(Interceptor这个类是我们自己写的拦截器类)
InterceptorRegistration registration = registry.addInterceptor(new TicketsInterceptor());
//addPathPatterns()方法添加需要拦截的路径
registration.addPathPatterns("/openapi/**"); //路径都被拦截
//excludePathPatterns()方法添加不拦截的路径
registration.excludePathPatterns( //添加不拦截路径
"/demo/loginPage", //登录页面的地址【不拦截】
"/**/*.html", //html静态资源
"/**/*.js", //js静态资源
"/**/*.css" //css静态资源
);
}
// 跨域,上面的拦截器配置了
/*@Override
public void addCorsMappings(CorsRegistry registry) {
}*/
}