最近有一個需要從攔截器中獲取post請求的參數的需求,這里記錄一下處理過程中出現的問題。
首先想到的就是request.getParameter(String )方法,但是這個方法只能在get請求中取到參數,post是不行的,后來想到了使用流的方式,調用request.getInputStream()獲取流,然后從流中讀取參數,如下代碼所示:
String body = ""; StringBuilder stringBuilder = new StringBuilder(); BufferedReader bufferedReader = null; InputStream inputStream = null; try { inputStream = request.getInputStream(); if (inputStream != null) { bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); char[] charBuffer = new char[128]; int bytesRead = -1; while ((bytesRead = bufferedReader.read(charBuffer)) > 0) { stringBuilder.append(charBuffer, 0, bytesRead); } } else { stringBuilder.append(""); } } catch (IOException ex) { e.printStackTrace(); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (bufferedReader != null) { try { bufferedReader.close(); } catch (IOException e) { e.printStackTrace(); } } } body = stringBuilder.toString();
代碼中的body就是request中的參數,我這里傳的是JSON數據:{"page": 1, "pageSize": 10},那么body就是:body = "{"page": 1, "pageSize": 10}",一個JSON字符串。這樣是可以成功獲取到post請求的body,但是,經過攔截器后,參數經過@RequestBody注解賦值給controller中的方法的時候,卻拋出了一個這樣的異常:
org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing
在網上查找資料后發現,request的輸入流只能讀取一次,那么這是為什么呢?下面是答案:
那是因為流對應的是數據,數據放在內存中,有的是部分放在內存中。read 一次標記一次當前位置(mark position),第二次read就從標記位置繼續讀(從內存中copy)數據。 所以這就是為什么讀了一次第二次是空了。 怎么讓它不為空呢?只要inputstream 中的pos 變成0就可以重寫讀取當前內存中的數據。javaAPI中有一個方法public void reset() 這個方法就是可以重置pos為起始位置,但是不是所有的IO讀取流都可以調用該方法!ServletInputStream是不能調用reset方法,這就導致了只能調用一次getInputStream()。
那么有什么辦法可以用戶解決呢?上面這篇博客中提到了解決方案,就是重寫HttpServletRequestWrapper把request保存下來,然后通過過濾器把保存下來的request再填充進去,這樣就可以多次讀取request了。步驟如下所示:
①寫一個類,繼承HttpServletRequestWrapper
import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.*; public class RequestWrapper extends HttpServletRequestWrapper { private final String body; public RequestWrapper(HttpServletRequest request) { super(request); StringBuilder stringBuilder = new StringBuilder(); BufferedReader bufferedReader = null; InputStream inputStream = null; try { inputStream = request.getInputStream(); if (inputStream != null) { bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); char[] charBuffer = new char[128]; int bytesRead = -1; while ((bytesRead = bufferedReader.read(charBuffer)) > 0) { stringBuilder.append(charBuffer, 0, bytesRead); } } else { stringBuilder.append(""); } } catch (IOException ex) { } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (bufferedReader != null) { try { bufferedReader.close(); } catch (IOException e) { e.printStackTrace(); } } } body = stringBuilder.toString(); } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes()); ServletInputStream servletInputStream = 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 byteArrayInputStream.read(); } }; return servletInputStream; } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(this.getInputStream())); } public String getBody() { return this.body; } }
②攔截器層面
import com.alibaba.fastjson.JSON; import com.miniprogram.api.douyin.user.req.DyuserReq; import com.miniprogram.common.auth.VisitLimitCount; import com.miniprogram.common.cache.RedisCache; import com.miniprogram.common.config.InterceptorConfigMap; import com.miniprogram.common.config.InterceptorUrlConfig; import com.miniprogram.common.douyin.SearchEngineMapConstants; import com.miniprogram.common.response.Response; import com.miniprogram.common.session.*; import com.miniprogram.common.utils.DateUtil; import com.miniprogram.dao.common.UserLoginEntity.Users; import com.miniprogram.service.douyin.users.UsersService; import com.miniprogram.web.douyin.config.RequestWrapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @Component("authSecurityInterceptor") public class AuthSecurityInterceptor extends HandlerInterceptorAdapter { private Logger logger = LoggerFactory.getLogger(AuthSecurityInterceptor.class); @Autowired private RedisCache redisCache; @Autowired private VisitLimitCount visitLimitCount; @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception { try { RequestWrapper requestWrapper = new RequestWrapper(httpServletRequest); String body = requestWrapper.getBody(); System.out.println(body); return true; }catch (Exception e){ logger.error("權限判斷出錯",e); } return false; } @Override public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { } }
③過濾器Filter,用來把request傳遞下去
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 { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { ServletRequest requestWrapper = null; if(servletRequest instanceof HttpServletRequest) { requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest); } if(requestWrapper == null) { filterChain.doFilter(servletRequest, servletResponse); } else { filterChain.doFilter(requestWrapper, servletResponse); } } @Override public void destroy() { } }
④在啟動類中注冊攔截器
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.MultipartConfigFactory; import org.springframework.boot.web.servlet.ServletComponentScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @SpringBootApplication // @ServletComponentScan //注冊過濾器注解 @Configuration public class WebApplication { public static void main(String[] args) { SpringApplication.run(WebApplication.class, args); } }