大白话讲解如何解决HttpServletRequest的请求参数只能读取一次的问题
大家在开发过程中,可能会遇到对请求参数做下处理的场景,比如读取上送的参数中看调用方上送的系统编号是否是白名单里面的(更多的会用request中获取IP地址判断)、需要对请求方上送的参数进行大小写转换或者字符处理、或者对请求方上送的用户名参数判断是否有对当前请求地址的访问权限(多用于越权处理)等,这些都可以通过实现Filter自定义一个类,在该类中进行相应处理。但是会有个问题,如果是POST请求过来,在Filter的实现类中读取了请求参数,那么后续在Controller里面就无法获取,这是由于我们获取POST请求参数的时候,是通过读取request的IO流来实现的,一旦读取了那么流关闭后,后续就用不了了。
那么解决这个问题,核心考虑就是支持多次读取,可通过缓存方式把流解析的数据缓存下来,这样就可以重复读取缓存下来的数据。举个不恰当的例子,如下
public class MockHttpRequest {
public static String readBook(String bookName) {
String fullName = bookName.concat(" 作者:xxx");
System.out.println(fullName);
bookName = null;
return bookName;
}
public static void readTwice(String bookName) {
bookName = readBook(bookName);
System.out.println(bookName);
}
public static void main(String[] args) {
readTwice("hello");
}
}
=========执行结果如下=========在readbook方法中读取了bookName后,对bookName做了清空处理(类比流读取后清空),那么第二次访问就拿不到了
hello 作者:xxx
null
如果我们把bookName缓存下来,那么其他方法都读取缓存的字段,就可以实现重复多次读取,如下
public class MockHttpRequest {
private static String bufferBook = null;
public static String readBook(String bookName) {
String fullName = bookName.concat(" 作者:xxx");
System.out.println(fullName);
bufferBook = bookName;
bookName = null;
return bookName;
}
public static void readTwice(String bookName) {
bookName = readBook(bookName);
System.out.println(bufferBook);
}
public static void main(String[] args) {
readTwice("hello");
}
}
================执行结果如下=============无论readTwice还是更多次,只要读取缓存下来的字段bufferBook,就能一直获取bookName
hello 作者:xxx
hello
所以【解决HttpServletRequest的请求参数只能读取一次的问题】就从数据缓存角度考虑,我们在HttpServletRequest里面创建一个私有属性,用来缓存用户上传的参数,不就可以实现多次读取的功能了吗?我们考虑新生产一个类实现HttpServletRequest,然后在这个类中定义一个属性字段,后续多次读取参数都从这个属性中获取。
tomcat提供的jar中已经有一个应用了装饰器模式的类HttpServletRequestWrapper(该类实现了HttpServletRequest),我们可以定义类NotesHttpServletRequestWrapper extends HttpServletRequestWrapper,然后在NotesHttpServletRequestWrapper中定义一个属性requestBody,具体代码见下:
public class NotesHttpServletRequestWrapper extends HttpServletRequestWrapper {
private byte[] requestBody = null;//用来缓存从HttpServletRequest的io流中读取的参数转为字节缓存下来
//初始化的时候,就从request读取放到属性字段
//比如在filter中通过NotesHttpServletRequestWrapper requestWrapper = new NotesHttpServletRequestWrapper(request);
public NotesHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
try {
requestBody = StreamUtils.copyToByteArray(request.getInputStream());
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {//后续读取流的操作都是从属性字段中读取的缓存下来的信息
final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bais.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener listener) {
return;
}
};
}
//获取request请求body中参数
public static Map<String,Object> getRequestParamsFromStream(InputStream in) {
String param= null;
BufferedReader buffReader=null;
try {
buffReader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8.name()));
StringBuilder paramsBuilder = new StringBuilder();
String inputStr;
while ((inputStr = buffReader.readLine()) != null)
paramsBuilder.append(inputStr);
if(!JSONValidator.from(paramsBuilder.toString()).validate()){
return new HashMap<String, Object>();
}
JSONObject jsonObject = JSONObject.parseObject(paramsBuilder.toString());
if(jsonObject==null){
return new HashMap<String, Object>();
}
param= jsonObject.toJSONString();
} catch (Exception e) {
e.printStackTrace();
}finally{
if(buffReader!=null){
try {
buffReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return JSONObject.parseObject(param,Map.class);
}
}
自己实现的HttpServletRequestWrapper类的使用,一般是结合Filter进行,如下:
@Component("notesRequestWrapperFilter")
public class NotesRequestWrapperFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
if (!(servletRequest instanceof HttpServletRequest) ||
!(servletResponse instanceof HttpServletResponse)) {
throw new ServletException("Unsupport request");
}
//这里可以做一些请求权限的校验,杜绝越权漏洞
//TODO 比如header中存放token,token解析出账号,判断账号的角色下是否有关联该请求接口
HttpServletRequest request = (HttpServletRequest) servletRequest;
System.out.println("uri===" + request.getRequestURI());
System.out.println("url===" + request.getRequestURL());
NotesHttpServletRequestWrapper requestWrapper = new NotesHttpServletRequestWrapper(request);//初始化HttpServletRequest的封装类
System.out.println("===HttpMethod.POST.name()===" + HttpMethod.POST.name());
String systemCode = "";
if(HttpMethod.POST.name().equals(request.getMethod())) {
//获取request请求body中参数
Map<String,Object> paramsMap = requestWrapper.getRequestParamsFromStream(requestWrapper.getInputStream());
systemCode = paramsMap.get("systemCode");
}else {
System.out.println("===请求方式===" + request.getMethod());
systemCode = request.getParameter("systemCode");
}
//判断请求方是否位于白名单,系统白名单存在库表、配置中心都可以
List<String> systemsPermit = new ArrayList<String>();
if(!systemsPermit.contains(systemCode)) {
throw new ServletException("Unsupport System");
}
chain.doFilter(requestWrapper, servletResponse);//注意这个地方往后传的就是我们自己封装的类的实例了
}
大白话讲解如何解决HttpServletRequest的请求参数只能读取一次的问题的更多相关文章
- HttpServletRequest获取请求参数中所有的信息
/** * 获取客户端请求参数中所有的信息 * @param request * @return */ private Map<String, String> getAllRequestP ...
- AFNetworking 3.0 解决加密后请求参数是字符串问题
把整个请求参数的json加密生成一个字符串传给服务器,错误提示:[NSJSONSerialization dataWithJSONObject:options:error:]: Invalid top ...
- HttpServletRequest获取请求参数
private static String getRequestParameter(HttpServletRequest request, HttpServletResponse response) ...
- 自定义HttpReqeust,解决request请求参数只能拿一次就失效的问题
定义一个过滤器并实现如下方法 @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResp ...
- httpServletRequest中的流只能读取一次的原因
首先,我们复习一下InputStream read方法的基础知识, java InputStream read方法内部有一个,postion,标志当前流读取到的位置,每读取一次,位置就会移动一次,如果 ...
- SpringMVC 获取请求参数
1.获取Request response对象 在SpringMVC的注解开发中,可以选择性的接收Request和Response对象来使用 2.获取request对象请求参数 a.通过request对 ...
- springMVC中接收请求参数&&数据转发
### 1. 接收请求参数 #### 1.1. [不推荐] 通过HttpServletRequest获取请求参数 假设存在: <form action="handle_login.do ...
- SpringBoot解决跨域请求拦截
前言 同源策略:判断是否是同源的,主要看这三点,协议,ip,端口. 同源策略就是浏览器出于网站安全性的考虑,限制不同源之间的资源相互访问的一种政策. 比如在域名https://www.baidu.co ...
- Spring MVC如何接收浏览器传递来的请求参数--request--形参--实体类封装
阅读目录 1. 通过HttpServletRequest获得请求参数和数据 2. 处理方法形参名==请求参数名 3. 如果形参名跟请求参数名不一样怎么办呢?用@RequestParam注解 4. 用实 ...
随机推荐
- pypandoc库实现文档转换
写在前面: 对于python程序员来说,文件格式之间转换很常用,尤其是把我们爬虫爬到的内容转换成想要的文档格式时.这几天看到一个网站上有许多文章,个人很喜欢,直接复制太麻烦,为了将爬到的html文件以 ...
- 如何在Ubuntu 18.04安装Git
在Ubuntu 18.04安装Git 更新apt包列表 apt-get update -y apt-get upgrade -y 安装Git: apt install git 检查Git版本 git ...
- kibana操作
一些KIBANA的操作,记录下,免下次重复写 #创建索引名为kb_question的索引,并添加mapping,即各字段属性 PUT kb_question { "mappings" ...
- xadmin使用富文本
环境:pycharm django1.11.20 python2.7 后台xadmin(根据网络各种资料实现) 本教程接上篇如何安装 xadmin,如何不清楚,请看上一篇(django安装xadmin ...
- vue 改变路由参数
更改路由传递的参数: const query = JSON.parse(JSON.stringify(this.$route.query)) // 获取路由参数信息 query.id = Name / ...
- Excel备忘录
1. 导入文本文件(.txt) 2. 排序 3. 批量填充空白 选定区域,Ctrl+G,定位,空值. 输入内容,Ctrl+Enter. 4. 清除无法修改的背景色. 5. 身份证号 数字精度为15位, ...
- 【C++ Primer Plus】编程练习答案——第8章
1 void ch8_1_print(const std::string & str, int n = 0 ) { 2 using namespace std; 3 static int fl ...
- java/ kotlin下的单例模式
单例模式属于创建型模式, 顾名思义,就是说整个系统中只有一个该对象的实例. 为什么要使用单例模式? 1, 对于一些需要频繁创建,销毁的对象, 使用单例模式可以节省系统资源 2, 对于全局持有的对象,单 ...
- PowerDotNet平台化软件架构设计与实现系列(02):数据库管理平台
为了DB复用和简化管理,我们对常见应用依赖的DB模块进行更高级的提取和抽象. 虽然一些ORM可以简化DB开发,但是我们还是需要进行改进和优化,否则应用越多,后期管理运维越混乱. 根据常见开发需要,数据 ...
- SONiC架构分析
目录 系统架构 设计原则 核心组件 SWSS 容器 syncd 容器 网络应用容器 内部通信模型 SubscriberStateTable NotificationProducer/Consumer ...