大白话讲解如何解决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. 用实 ...
随机推荐
- 🤩全套Java教程_Java基础入门教程,零基础小白自学Java必备教程👻002 # 第二单元 常量,变量,数据类型 #
一.本单元知识点概述 二.本单元目标 (Ⅰ)重点知识目标 1.定义出各种数据类型的变量2.理解自动类型提升3.理解强制类型转换 (Ⅱ)能力目标 1.能够定义出所有类型的常量 2.理解Java中的基本数 ...
- Charles的breakpoint功能
修改请求报文 比如,前端已经控制了输入内容,而我们需要验证接口是否做了校验,这时候怎么测试? 可以通过charles抓包,修改请求报文,修改为在页面上无法输入的内容,发出去然后看后台怎么处理. 修改返 ...
- php 设计模式 --桥接模式
php抽象类和接口的区别 https://www.cnblogs.com/vinter/p/8716685.html 什么时候适合使用 --- 多个角色配合工作:抽象角色对应具体角色: <?ph ...
- 『GoLang』string及其相关操作
目录 1. 字符串简介 2. 字符串的拼接 3. 有关 string 的常用处理 3.1 strings 包 3.1.1 判断两个 utf-8 编码字符串是否相同 3.1.2 判断字符串 str 是否 ...
- Linux服务器时间同步配置
Linux服务器时间同步配置 以CentOS7 做时间服务器,其他服务器(Centos 6.RHEL7)同步该服务器时间 RHEL 7.CentOS 7 默认的网络时间协议 为Chrony 本教程 ...
- 定要过python二级 选择题第四套
1. 2. 3. 4. 5. 6. python用于人工智能 7. 8. 9. 10. 11. 12. 13. 14. 15. 16.
- Winfrom 中完美设置webbrowser控件内核版本
前言 .NET 版本更新了一代又一代,winform中的webbrowser控件的IE内核版本却始终用的IE7,好多网站都对IE7已经不支持.webbrowser这个控件就显得有些鸡肋,经过查找大佬门 ...
- Java(一)——基础知识
引言 之前一直对 Java 怀有固执的偏见,以为 Java 是编写前端的语言,作为一个机械生,非常抗拒去学它. 但是最近接触一点以后,完全改观了先前的看法,于是开启了对 Java 的大学习. 一.数据 ...
- InstallSheild相关
一.关于使用InstallSheild制作安装包的总结. 1.定制化制作需要了解InstallScript语法,相关资料可以去网上查找,后续提供比较好的资料. 2.有些软件运行是需要一些环境的,譬如使 ...
- Java初步学习——2021.10.12每日总结,第六周周二
(1)今天做了什么: (2)明天准备做什么? (3)遇到的问题,如何解决? 今天学习了菜鸟教程Java实例,数组 1.数组的排序和元素的查找--sort和binarySearch方法 import j ...