大家在开发过程中,可能会遇到对请求参数做下处理的场景,比如读取上送的参数中看调用方上送的系统编号是否是白名单里面的(更多的会用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的请求参数只能读取一次的问题的更多相关文章

  1. HttpServletRequest获取请求参数中所有的信息

    /** * 获取客户端请求参数中所有的信息 * @param request * @return */ private Map<String, String> getAllRequestP ...

  2. AFNetworking 3.0 解决加密后请求参数是字符串问题

    把整个请求参数的json加密生成一个字符串传给服务器,错误提示:[NSJSONSerialization dataWithJSONObject:options:error:]: Invalid top ...

  3. HttpServletRequest获取请求参数

    private static String getRequestParameter(HttpServletRequest request, HttpServletResponse response) ...

  4. 自定义HttpReqeust,解决request请求参数只能拿一次就失效的问题

    定义一个过滤器并实现如下方法 @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResp ...

  5. httpServletRequest中的流只能读取一次的原因

    首先,我们复习一下InputStream read方法的基础知识, java InputStream read方法内部有一个,postion,标志当前流读取到的位置,每读取一次,位置就会移动一次,如果 ...

  6. SpringMVC 获取请求参数

    1.获取Request response对象 在SpringMVC的注解开发中,可以选择性的接收Request和Response对象来使用 2.获取request对象请求参数 a.通过request对 ...

  7. springMVC中接收请求参数&&数据转发

    ### 1. 接收请求参数 #### 1.1. [不推荐] 通过HttpServletRequest获取请求参数 假设存在: <form action="handle_login.do ...

  8. SpringBoot解决跨域请求拦截

    前言 同源策略:判断是否是同源的,主要看这三点,协议,ip,端口. 同源策略就是浏览器出于网站安全性的考虑,限制不同源之间的资源相互访问的一种政策. 比如在域名https://www.baidu.co ...

  9. Spring MVC如何接收浏览器传递来的请求参数--request--形参--实体类封装

    阅读目录 1. 通过HttpServletRequest获得请求参数和数据 2. 处理方法形参名==请求参数名 3. 如果形参名跟请求参数名不一样怎么办呢?用@RequestParam注解 4. 用实 ...

随机推荐

  1. 小白一看就懂的postman教程

    Postman的安装和注册 下载 直接在官网下载 https://www.postman.com/downloads/ postman有两种形式 客户端,下载后安装使用 网页版,在浏览器登录postm ...

  2. 一文让你彻底理解SQL的子查询

    什么是子查询 当一个查询是另一个查询的条件时,称之为子查询. 为什么要使用子查询 在SELECT.INSERT.UPDATE或DELETE命令中只要是表达式的地方都可以包含子查询,子查询甚至可以包含在 ...

  3. Linux 清理缓存

    1. free -m 命令可以查看内存使用情况  2. sync   :因为系统在操作的过程当中,会把你的操作到的文件资料先保存到buffer中去,因为怕你在操作的过程中因为断电等原因遗失数据,所以在 ...

  4. 《使用Jmeter进行批量发送http请求》

    本文主要针对批量接口发送数据 一:接口测试的环境准备 1:JDK的安装:网上下载即可>1.6.0版本以上 2:jemeter工具的下载 (免安装):网上下载即可 3:插件的下载安装地址:http ...

  5. AVS 通信模块之AVSConnectionManager

    AVSConnectionManager 类为客户端无缝地管理与AVS的连接 功能简介 失败时连接重试 允许后续重新连接 ping管理 AVS服务器断开时周期重连服务器 允许客户端完全启用或禁用连接管 ...

  6. Loj#6503-「雅礼集训 2018 Day4」Magic【分治NTT】

    正题 题目链接:https://loj.ac/p/6503 题目大意 \(n\)张卡\(m\)种,第\(i\)种卡有\(a_i\)张,求所有排列中有\(k\)对相邻且相同的卡牌. \(1\leq n\ ...

  7. NOIP 模拟七 考试总结

    T1匹配 签到大水题,这里有hash,kmp,ac自动机,还有后缀数组,后缀自动机任您挑选. 不过这个数据范围有些坑啊,re就很不爽.做法我还是比较倾向hash的,毕竟不论神魔字符算法,hash大都能 ...

  8. jmx_prometheus_javaagent+prometheus+alertmanager+grafana完成容器化java监控告警(二)

    一.拓扑图 二.收集数据 2.1前期准备 创建共享目录,即为了各节点都创建该目录,有两个文件,做数据共享 /home/target/prom-jvm-demo 1.下载文件 jmx_prometheu ...

  9. bzoj4712 洪水(动态dp)

    看起来很模板的一个题啊 qwq 但是我还是wei 题目要求的是一个把根节点和所有叶子断开连接的最小花费. 还是想一个比较\(naive\)的做法 我们令\(dp1[i]\)表示,在\(i\)的子树内, ...

  10. 洛谷3721 HNOI2017单旋(LCT+set+思维)

    这题难道不是spaly裸题吗? 言归正传QWQ 一看到这个题目,其实第一反应是很懵X的 从来没有见过类似的题目啊,什么\(spaly\),单旋.QWQ很懵逼啊 不过,我们可以注意到这么一件事情,就是我 ...