大家在开发过程中,可能会遇到对请求参数做下处理的场景,比如读取上送的参数中看调用方上送的系统编号是否是白名单里面的(更多的会用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. modern php enable zend opcache

    字节码缓存能存储预先编译好的php代码 * 如果是自己编译PHP ./configure --enable-opcache 编译好后 php.ini zend_extension=opcache.so ...

  2. git pull 时remote: HTTP Basic: Access denied解决方案

    当qian windows用户密码过期更改了密码后,操作git pull 拉取远程仓库代码或git push时报错 如下:remote: HTTP Basic: Access denied  Auth ...

  3. Android Kotlin协程入门

    Android官方推荐使用协程来处理异步问题.以下是协程的特点: 轻量:单个线程上可运行多个协程.协程支持挂起,不会使正在运行协程的线程阻塞.挂起比阻塞节省内存,且支持多个并行操作. 内存泄漏更少:使 ...

  4. P7599-[APIO2021]雨林跳跃【二分,倍增,ST表】

    正题 题目链接:https://www.luogu.com.cn/problem/P7599 题目大意 \(n\)棵树,在某棵树上时可以选择向左右两边第一棵比它高的树跳,现在\(q\)次询问从\([A ...

  5. Spring Security 学习+实践

    Spring Security是Spring为解决应用安全所提供的一个全面的安全性解决方案.基于Spring AOP和Servlet过滤器,启动时在Spring上下文中注入了一组安全应用的Bean,并 ...

  6. Redis之品鉴之旅(一)

    Redis之品鉴之旅(一) 好知识就如好酒,需要我们坐下来,静静的慢慢的去品鉴.Redis作为主流nosql数据库,在提升性能的方面是不可或缺的.下面就拿好小板凳,我们慢慢的来一一品鉴. 1)redi ...

  7. c#中多线程间的同步

    目录 一.引入 二.Lock 三.Monitor 四.Interlocked 五.Semaphore 六.Event 七.Barrier 八.ReaderWriterLockSlim 九.Mutex ...

  8. python基础知识三——try与except处理异常语句

    try/except介绍 与其他语言相同,在python中,try/except语句主要是用于处理程序正常执行过程中出现的一些异常情况,如语法错(python作为脚本语言没有编译的环节,在执行过程中对 ...

  9. salesforce零基础学习(一百零八)MFA

    本篇参考:https://security.salesforce.com/mfa https://sfdc.co/bvtuQT  (MFA官方研讨会的文档) https://sfdc.co/iwiQK ...

  10. mysql 建表后 重新构建 自增字段 (保留 原有字段结构)

    添加字段 1.去除原id的自增功能:ALTER TABLE A_A MODIFY COLUMN id int(10) NOT NULL FIRST ; 2.添加名称为cstId,类型为bigint的字 ...