大家在开发过程中,可能会遇到对请求参数做下处理的场景,比如读取上送的参数中看调用方上送的系统编号是否是白名单里面的(更多的会用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. Jmeter通过正则表达式提取器提取响应结果数据

    Jmeter进行接口测试常常会运到一个问题:就是第二个请求如何接收上一个请求响应中的参数.比如,现在个学生金币充值的接口,得先调用登录接口然后从返回里面复制一下sign的值,放到cookie里这样才能 ...

  2. Linux下Electron loadURL报错 ERR_FAILED(-2) Not allowed to load local resource

    Linux下Electron loadURL报错 ERR_FAILED(-2) Not allowed to load local resource 背景 使用electron-vue的时候,窗体创建 ...

  3. 洛谷2149 Elaxia的路线(dp+最短路)

    QwQ好久没更新博客了,颓废了好久啊,来补一点东西 题目大意 给定两个点对,求两对点间最短路的最长公共路径. 其中\(n,m\le 10^5\) 比较简单吧 就是跑四遍最短路,然后把最短路上的边拿出来 ...

  4. Miller-Rabin学习笔记

    首先给出两个定理: 1.费马小定理 设p是一个素数,a是一个整数,且不是p的倍数,那么 \(a^{p−1} \equiv\ 1 \pmod p\) 2.二次探测定理 若\(p\)是素数,\(x\)是一 ...

  5. C 字符串相关的库函数

    字符串操作函数 size_t strlen( char *string ); 返回字符串长度 char* strcpy( char *dst, char const *src ); 将src复制到ds ...

  6. Apache Common-collection 反序列化利用链解析--TransformedMap链

    Apache Common-collection 反序列化利用链解析 TransformedMap链 参考Java反序列化漏洞分析 - ssooking - 博客园 (cnblogs.com) poc ...

  7. [JUC-5]ConcurrentHashMap源码分析JDK8

    在学习之前,最好先了解下如下知识: 1.ReentrantLock的实现和原理. 2.Synchronized的实现和原理. 3.硬件对并发支持的CAS操作及JVM中Unsafe对CAS的实现. 4. ...

  8. 4.1 The Example Domain 领域示例代码

    4.1 The Example Domain 领域示例代码 The examples will use some concepts those are used by GitHub, like Iss ...

  9. python中对列表的排序

    1.sort()对列表永久性的排序,首字母按照字母表的顺序排列 book=['python','java','c++','web'] book.sort() print(book) 结果如下: 2.向 ...

  10. 10-1 Python 学习笔记

    1. 项目 在文本编辑器中新建一个文件,写几句话来总结一下你至此学到的 Python 知识,其中每一行都以"In Python you can"打头. 将这个文件命名为learni ...