HttpServletRequestWrapper使用技巧(自定义session和缓存InputStream)
一、前言
javax.servlet.http.HttpServletRequestWrapper 是一个开发者可以继承的类,我们可以重写相应的方法来实现session的自定义以及缓存InputStream,在程序中可以多次获取request body的内容。
二、自定义seesion
import javax.servlet.http.*; public class CustomizeHttpServletRequest extends HttpServletRequestWrapper { public CustomizeHttpServletRequest(HttpServletRequest request) { super(request); this.response = response; } @Override public HttpSession getSession() { //return super.getSession(); // 默认使用的是servlet容器session管理 return this.getSession(true); } @Override public HttpSession getSession(boolean create) { Cookie[] cookies = this.getCookies(); String sessionId = ""; //这里编写自己获取session的逻辑 //既然是自定义逻辑,可以从内存中取,也可以从缓存中取。 } }
也许大家都用过shiro的session管理或者spring-session,其实想要自己去实现,也是很简单的。
三、缓存InputStream
自定义工具类 ContentCachingRequestWrapper
import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import org.apache.commons.io.IOUtils; public class ContentCachingRequestWrapper extends HttpServletRequestWrapper{ private byte[] body; private BufferedReader reader; private ServletInputStream inputStream; public ContentCachingRequestWrapper(HttpServletRequest request) throws IOException{ super(request); body = IOUtils.toByteArray(request.getInputStream()); inputStream = new RequestCachingInputStream(body); } public byte[] getBody() { return body; } @Override public ServletInputStream getInputStream() throws IOException { if (inputStream != null) { return inputStream; } return super.getInputStream(); } @Override public BufferedReader getReader() throws IOException { if (reader == null) { reader = new BufferedReader(new InputStreamReader(inputStream, getCharacterEncoding())); } return reader; } private static class RequestCachingInputStream extends ServletInputStream { private final ByteArrayInputStream inputStream; public RequestCachingInputStream(byte[] bytes) { inputStream = new ByteArrayInputStream(bytes); } @Override public int read() throws IOException { return inputStream.read(); } @Override public boolean isFinished() { return inputStream.available() == 0; } @Override public boolean isReady() { return true; } @Override public void setReadListener(ReadListener readlistener) { } } }
spring工具类 ContentCachingRequestWrapper
package org.springframework.web.util; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.net.URLEncoder; import java.util.Arrays; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import org.springframework.http.HttpMethod; public class ContentCachingRequestWrapper extends HttpServletRequestWrapper { private static final String FORM_CONTENT_TYPE = "application/x-www-form-urlencoded"; private final ByteArrayOutputStream cachedContent; private ServletInputStream inputStream; private BufferedReader reader; /** * Create a new ContentCachingRequestWrapper for the given servlet request. * @param request the original servlet request */ public ContentCachingRequestWrapper(HttpServletRequest request) { super(request); int contentLength = request.getContentLength(); this.cachedContent = new ByteArrayOutputStream(contentLength >= 0 ? contentLength : 1024); } @Override public ServletInputStream getInputStream() throws IOException { if (this.inputStream == null) { this.inputStream = new ContentCachingInputStream(getRequest().getInputStream()); } return this.inputStream; } @Override public String getCharacterEncoding() { String enc = super.getCharacterEncoding(); return (enc != null ? enc : WebUtils.DEFAULT_CHARACTER_ENCODING); } @Override public BufferedReader getReader() throws IOException { if (this.reader == null) { this.reader = new BufferedReader(new InputStreamReader(getInputStream(), getCharacterEncoding())); } return this.reader; } @Override public String getParameter(String name) { if (this.cachedContent.size() == 0 && isFormPost()) { writeRequestParametersToCachedContent(); } return super.getParameter(name); } @Override public Map<String, String[]> getParameterMap() { if (this.cachedContent.size() == 0 && isFormPost()) { writeRequestParametersToCachedContent(); } return super.getParameterMap(); } @Override public Enumeration<String> getParameterNames() { if (this.cachedContent.size() == 0 && isFormPost()) { writeRequestParametersToCachedContent(); } return super.getParameterNames(); } @Override public String[] getParameterValues(String name) { if (this.cachedContent.size() == 0 && isFormPost()) { writeRequestParametersToCachedContent(); } return super.getParameterValues(name); } private boolean isFormPost() { String contentType = getContentType(); return (contentType != null && contentType.contains(FORM_CONTENT_TYPE) && HttpMethod.POST.matches(getMethod())); } private void writeRequestParametersToCachedContent() { try { if (this.cachedContent.size() == 0) { String requestEncoding = getCharacterEncoding(); Map<String, String[]> form = super.getParameterMap(); for (Iterator<String> nameIterator = form.keySet().iterator(); nameIterator.hasNext(); ) { String name = nameIterator.next(); List<String> values = Arrays.asList(form.get(name)); for (Iterator<String> valueIterator = values.iterator(); valueIterator.hasNext(); ) { String value = valueIterator.next(); this.cachedContent.write(URLEncoder.encode(name, requestEncoding).getBytes()); if (value != null) { this.cachedContent.write('='); this.cachedContent.write(URLEncoder.encode(value, requestEncoding).getBytes()); if (valueIterator.hasNext()) { this.cachedContent.write('&'); } } } if (nameIterator.hasNext()) { this.cachedContent.write('&'); } } } } catch (IOException ex) { throw new IllegalStateException("Failed to write request parameters to cached content", ex); } } /** * Return the cached request content as a byte array. */ public byte[] getContentAsByteArray() { return this.cachedContent.toByteArray(); } private class ContentCachingInputStream extends ServletInputStream { private final ServletInputStream is; public ContentCachingInputStream(ServletInputStream is) { this.is = is; } @Override public int read() throws IOException { int ch = this.is.read(); if (ch != -1) { cachedContent.write(ch); } return ch; } @Override public boolean isFinished() { return is.available() == 0; } @Override public boolean isReady() { return true; } @Override public void setReadListener(ReadListener readlistener) { } } }
获取InputStream
1、使用自定义工具类的时候调用方法 getBody
2、使用spring工具类的时候调用方法 getContentAsByteArray
打印request中的所有请求信息,详细代码如下。
private void printRequest(HttpServletRequest request) { String body = StringUtils.EMPTY; try { if (request instanceof ContentCachingRequestWrapper) { body = new String(((ContentCachingRequestWrapper) request).getContentAsByteArray(), "UTF-8"); LOGGER.info("Request-Inputstream: " + body); } } catch (IOException e) { LOGGER.error("printRequest 获取body异常...", e); } JSONObject requestJ = new JSONObject(); JSONObject headers = new JSONObject(); Collections.list(request.getHeaderNames()) .stream() .forEach(name -> headers.put(name, request.getHeader(name))); requestJ.put("headers", headers); requestJ.put("parameters", request.getParameterMap()); requestJ.put("body", body); requestJ.put("remote-user", request.getRemoteUser()); requestJ.put("remote-addr", request.getRemoteAddr()); requestJ.put("remote-host", request.getRemoteHost()); requestJ.put("remote-port", request.getRemotePort()); requestJ.put("uri", request.getRequestURI()); requestJ.put("url", request.getRequestURL()); requestJ.put("servlet-path", request.getServletPath()); requestJ.put("method", request.getMethod()); requestJ.put("query", request.getQueryString()); requestJ.put("path-info", request.getPathInfo()); requestJ.put("context-path", request.getContextPath()); LOGGER.info("Request-Info: " + JSON.toJSONString(requestJ, SerializerFeature.PrettyFormat)); }
request中的所有请求信息示例
Request-Inputstream: { "timestamp":1539155028668, "appId":"cmos10086e36ipz2otyy8gfqh", "nonce":691879, "telephone":"18736085778", "signature":"226e734a49d513b3b1e364a06fc6f4eb5e2c425c6446ce6a7a950f1d8d6af06c" } Request-Info: { "headers":{ "x-real-ip":"211.138.20.171", "content-length":"183", "content-encoding":"UTF-8", "host":"221.176.66.251", "connection":"close", "content-type":"application/json", "accept-encoding":"gzip,deflate", "user-agent":"Apache-HttpClient/4.5.3 (Java/1.7.0_76)" }, "remote-host":"172.17.20.92", "method":"POST", "body":"{\"timestamp\":1539155028668,\"appId\":\"cmos10086e36ipz2otyy8gfqh\",\"nonce\":691879,\"telephone\":\"18736085778\",\"signature\":\"226e734a49d513b3b1e364a06fc6f4eb5e2c425c6446ce6a7a950f1d8d6af06c\"}", "uri":"/wmhopenapi/hevb-api/total", "url":"http://221.176.66.251/wmhopenapi/hevb-api/total", "servlet-path":"/hevb-api/total", "remote-addr":"172.17.20.92", "context-path":"/wmhopenapi", "remote-port":49174, "parameters":{} }
四、在Filter中替换掉默认的Request
import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.util.ContentCachingRequestWrapper; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; @Configuration public class FilterConfig { @Bean public FilterRegistrationBean wmhFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new WmhFilter()); registration.addUrlPatterns("/*"); registration.setName("MyFilter"); registration.setOrder(1); return registration; } private static class WmhFilter implements Filter { @Override public void init(javax.servlet.FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { chain.doFilter(new ContentCachingRequestWrapper((HttpServletRequest) request), response); } @Override public void destroy() { } } }
五、使用场景
个人认为,在做权限管理、用户管理、登录等场景,尤其是多系统的情况下,常常需要借助第三方的工具比如shiro,spring-session,完成权限、角色、用户、登录的管理逻辑。之前我自己也尝试过使用spring-session+redis缓存实现共享session和单点登录的逻辑。如果时间充分的话,完全可以自己去写一套session管理的工具,并应用到项目中去。
最近在配合其他组的同时联调接口的时候,遇到了这样的一种情况:他说request body的内容是按照我的协议来的,我后端的实现是通过@RequestBody注解拿到的一个java 对象,有一个字段值为null,很是诡异。于是我俩就纠结是谁的问题,我说他参数传的不对,他说我字段定义不对并让我打印一下Request InputStream,于是就开始寻找解决方案。我们都知道Input Sream只能获取一次,再次获取一定会抛出异常。通过寻找HttpServletRequest的子类,发现了spring提供了这样一个缓存Input Stream内容的工具类,问题迎刃而解。
本文来自博客园,作者:hjzqyx,转载请注明原文链接:https://www.cnblogs.com/hujunzheng/p/9766739.html