This is the 15th day of my participation in Gwen Challenge
background
If you need to implement a business interceptor you need to intercept all incoming information from a request. In general, the body content in HttpServletRequst is read only once, but in some cases it may be read more than once. Because the body content exists as a stream, the first read is completed and the second read is not possible. A typical scenario is Filter After validating the contents of the body, the business method cannot continue reading the stream, resulting in parsing errors.
Solution 1
- use
ContentCachingRequestWrapper
We create a filter:
@Component
public class CachingRequestBodyFilter extends GenericFilterBean {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest currentRequest = (HttpServletRequest) servletRequest;
ContentCachingRequestWrapper wrappedRequest = newContentCachingRequestWrapper(currentRequest); chain.doFilter(wrappedRequest, servletResponse); }}Copy the code
In the Web filter doFilter method, create ContentCachingRequestWrapper to request to do packing. Then we can call the ContentCachingRequestWrapper method in the Controller for the request body. The following is an example:
@RestController
public class HelloController {
@PostMapping("/hello")
public String hello(@RequestBody String id, HttpServletRequest request) {
ContentCachingRequestWrapper requestWrapper = (ContentCachingRequestWrapper) request;
String requestBody = new String(requestWrapper.getContentAsByteArray());
return "Body."+ requestBody; }}Copy the code
Solution 2
Let’s see if we can decorate the request with a decorator so that it wraps the read and can be read multiple times. The decorator to meet httpsevletrequest interface specification, in the framework of the original spring boot offers a simple wrapper ContentCachingRequestWrapper, look from the source the wrapper is not practical, Without a ServletInputStream that encapsulates HTTP, logic built with @requestParam,@RequestBody, etc. using the underlying stream is still useless and can only be used by force. We consult ContentCachingRequestWrapper encapsulates a more reliability, less invasive decorator:
public class RepeatReadHttpRequest extends HttpServletRequestWrapper {
private static final Logger LOGGER = LoggerFactory.getLogger(RepeatReadHttpRequest.class);
private final ByteArrayOutputStream cachedContent;
private Map<String, String[]> cachedForm;
@Nullable
private ServletInputStream inputStream;
public RepeatReadHttpRequest(HttpServletRequest request) {
super(request);
this.cachedContent = new ByteArrayOutputStream();
this.cachedForm = new HashMap<>();
cacheData();
}
@Override
public ServletInputStream getInputStream(a) throws IOException {
this.inputStream = new RepeatReadInputStream(cachedContent.toByteArray());
return this.inputStream;
}
@Override
public String getCharacterEncoding(a) {
String enc = super.getCharacterEncoding();
return(enc ! =null ? enc : WebUtils.DEFAULT_CHARACTER_ENCODING);
}
@Override
public BufferedReader getReader(a) throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream(), getCharacterEncoding()));
}
@Override
public String getParameter(String name) {
String value = null;
if (isFormPost()) {
String[] values = cachedForm.get(name);
if (null! = values && values.length >0) {
value = values[0]; }}if (StringUtils.isEmpty(value)) {
value = super.getParameter(name);
}
return value;
}
@Override
public Map<String, String[]> getParameterMap() {
if(isFormPost() && ! CollectionUtils.sizeIsEmpty(cachedForm)) {return cachedForm;
}
return super.getParameterMap();
}
@Override
public Enumeration<String> getParameterNames(a) {
if(isFormPost() && ! CollectionUtils.sizeIsEmpty(cachedForm)) {return Collections.enumeration(cachedForm.keySet());
}
return super.getParameterNames();
}
@Override
public String[] getParameterValues(String name) {
if(isFormPost() && ! CollectionUtils.sizeIsEmpty(cachedForm)) {return cachedForm.get(name);
}
return super.getParameterValues(name);
}
private void cacheData(a) {
try {
if (isFormPost()) {
this.cachedForm = super.getParameterMap();
} else {
ServletInputStream inputStream = super.getInputStream();
IOUtils.copy(inputStream, this.cachedContent); }}catch (IOException e) {
LOGGER.warn("[RepeatReadHttpRequest:cacheData], error: {}", e.getMessage()); }}private boolean isFormPost(a) {
String contentType = getContentType();
return(contentType ! =null &&
(contentType.contains(MediaType.APPLICATION_FORM_URLENCODED_VALUE) ||
contentType.contains(MediaType.MULTIPART_FORM_DATA_VALUE)) &&
HttpMethod.POST.matches(getMethod()));
}
private static class RepeatReadInputStream extends ServletInputStream {
private final ByteArrayInputStream inputStream;
public RepeatReadInputStream(byte[] bytes) {
this.inputStream = new ByteArrayInputStream(bytes);
}
@Override
public int read(a) throws IOException {
return this.inputStream.read();
}
@Override
public int readLine(byte[] b, int off, int len) throws IOException {
return this.inputStream.read(b, off, len);
}
@Override
public boolean isFinished(a) {
return this.inputStream.available() == 0;
}
@Override
public boolean isReady(a) {
return true;
}
@Override
public void setReadListener(ReadListener listener) {}}}Copy the code
Replace the original request with Filter when using:
/ * * *@authorLambert * interceptor httprequest replace repeatable access to InputStream */
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean requestReplaceFilterRegistration(a) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new RequestReplaceFilter());
registrationBean.addUrlPatterns("/ *");
registrationBean.setName("RequestReplaceFilter");
registrationBean.setOrder(1);
return registrationBean;
}
public static class RequestReplaceFilter implements Filter {
@Override
public void init(javax.servlet.FilterConfig filterConfig) throws ServletException {}@Override
public void destroy(a) {}@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
filterChain.doFilter(newRepeatReadHttpRequest((HttpServletRequest) servletRequest), servletResponse); }}}Copy the code
Subsequent requests can use the wrapper to read the body information multiple times.