一、前言

  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内容的工具类,问题迎刃而解。

HttpServletRequestWrapper使用技巧(自定义session和缓存InputStream)的更多相关文章

  1. python_way ,自定义session

    python_way ,自定义session container = {} #可以是数据库,可以是缓存也可以是文件 class Session: def __init__(self, handler) ...

  2. Asp.net Mvc 自定义Session (二)

    在 Asp.net Mvc 自定义Session (一)中我们把数据缓存工具类写好了,今天在我们在这篇把 剩下的自定义Session写完 首先还请大家跟着我的思路一步步的来实现,既然我们要自定义Ses ...

  3. Asp.net Mvc 自定义Session (一),

    大家都知道用系统默认的session 会存在这样的问题 如果用户过多的话 session 会自动消亡,而且不能支持分布式和集群. 这系列博客主要讲解  怎样 解决用户过多的session自动消亡,和分 ...

  4. Tronado自定义Session

    这里就不一一诉说Session和Cookie直接的关系了,下面以一张图来概括: 下面是一个简单的Tornaod自定义Session的例子,看完后你可能会明白为什么我们在Django里可以直接使用req ...

  5. Python web框架 Tornado(三)自定义session组件

    我们在学习Django框架的过程中,内部封装了session组件,以方便于我们使用进行验证.但是Tornado框架是没有session的,所以如果想使用session的话,就需要我们自己定制相对应的组 ...

  6. 【ASP.NET Core】自定义Session的存储方式

    在开始今天的表演之前,老周先跟大伙伴们说一句:"中秋节快乐". 今天咱们来聊一下如何自己动手,实现会话(Session)的存储方式.默认是存放在分布式内存中.由于HTTP消息是无状 ...

  7. 可灵活扩展的自定义Session状态存储驱动

    Session是互联网应用中非常重要的玩意儿,对于超过单台部署的站点集群,都会存在会话共享的需求.在web.config中,微软提供了sessionstate节点来定义不同的Session状态存储方式 ...

  8. windows使用nginx+memcached实现负载均衡和session或者缓存共享

    windows使用nginx+memcached实现负载均衡和session或者缓存共享 两台server server1:115.29.186.215 windows2008 64位操作系统 ser ...

  9. 自定义session扫描器

    为何要自定义session扫描器 由于服务器来管理session的销毁不怎么靠谱,因此很多网站都会自己定义一个session扫描器来管理session的创建和销毁. 实现思路 首先,创建一个sessi ...

随机推荐

  1. embed标签详解

    HTML-embed标签详解 Embed(一).基本语法:embed src=url说明:embed可以用来插入各种多媒体,格式可以是 Midi.Wav.AIFF.AU.MP3等等,      Net ...

  2. Python之函数(自定义函数,内置函数,装饰器,迭代器,生成器)

    Python之函数(自定义函数,内置函数,装饰器,迭代器,生成器) 1.初始函数 2.函数嵌套及作用域 3.装饰器 4.迭代器和生成器 6.内置函数 7.递归函数 8.匿名函数

  3. mac下Fiddler的安装-启动

    使用教程参考:http://www.cnblogs.com/TankXiao/archive/2012/02/06/2337728.html#request 环境安装 Mono安装 首先,Mac下需要 ...

  4. 存储区域网络(Storage Area Network,简称SAN)

    存储区域网络(Storage Area Network,简称SAN)采用网状通道(Fibre Channel ,简称FC,区别与Fiber Channel光纤通道)技术,通过FC交换机连接存储阵列和服 ...

  5. Ajax增删改查-----------删 改

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  6. 20165206 2017-2018-2 《Java程序设计》第七周学习总结

    20165206 2017-2018-2 <Java程序设计>第七周学习总结 教材学习内容总结 MySqL:是世界上最流行的开源数据管理系统. 配置启动MySQL. 连接数据库:Conne ...

  7. 在Ubuntu内制作自己的VOC数据集

    一.VOC数据集的简介 PASCAL VOC为图像的识别和分类提供了一整套标准化的优秀数据集,基本上就是目标检测数据集的模板.现在有VOC2007,VOC2012.主要有20个类.而现在主要的模型评估 ...

  8. mybatis 遍历map;

    mybatis 遍历map; 参考http://blog.csdn.net/hj7jay/article/details/78652050 ps: ${m[key]}这是显示 打印的key读value ...

  9. 009-Python-面向对象

    1.面向对象: 1.1什么是类? 类:把一类事物的相同的特征和动作整合到一起就是类,类是一个抽象的概念: 1.2什么是对象? 对象:就是基于类而创建的一个具体事物(具体存在的)也是特征和动作整合到一起 ...

  10. django-会话 cookie 中缺少HttpOnly 属性-安全加强

    如果django程序扫描到会话 cookie 中缺少 HttpOnly 属性问题,需要如何进行安全加强? https://docs.djangoproject.com/en/2.2/ref/setti ...