开发过javaweb项目的同学,应该都接触过ServeltRequest吧?ServletRequest接口中有一个方法叫做getParameterMap(),他会返回一个Map<String, String[]>对象,里面含有Request的请求参数,例如GET请求时?后边的一堆参数。那如果我们能修改Map<String, String[]>对象,岂不是能篡改浏览器请求时的一些参数?

1 ParameterMap

1.1 ServletRequest接口

服务器能从ServletRequest中篡改浏览器请求的参数?想想都令人兴奋,我们又多了一个可以个性化的地方。然而实际上是不可以的,我们来看看ServeltRequestgetParameterMap()方法的注释吧。


/**
* Returns a java.util.Map of the parameters of this request. Request
* parameters are extra information sent with the request. For HTTP
* servlets, parameters are contained in the query string or posted form
* data.
*
* @return an immutable java.util.Map containing parameter names as keys and
* parameter values as map values. The keys in the parameter map are
* of type String. The values in the parameter map are of type
* String array.
*/
public Map&lt;String, String[]&gt; getParameterMap();

人家说了,返回的这个Map对象一定是不可变的。所以呢,就死了这条心吧。咱们还是看看tomcat中ServletRequest的实现类里面到底是怎么构造不可变的Map。

1.2 Tomcat中的Request实现类

注意,以下凡是没有特殊说明的tomcat,其版本都是7.0.52

tomcat中ServletRequest的实现类是org.apache.catalina.connector.Request。在这个实现类中,方法getParameterMap()是这样实现的。


/**
* Returns a &lt;code&gt;Map&lt;/code&gt; of the parameters of this request.
* Request parameters are extra information sent with the request.
* For HTTP servlets, parameters are contained in the query string
* or posted form data.
*
* @return A &lt;code&gt;Map&lt;/code&gt; containing parameter names as keys
* and parameter values as map values.
*/
@Override
public Map&lt;String, String[]&gt; getParameterMap() { if (parameterMap.isLocked()) {
return parameterMap;
} Enumeration&lt;String&gt; enumeration = getParameterNames();
while (enumeration.hasMoreElements()) {
String name = enumeration.nextElement();
String[] values = getParameterValues(name);
parameterMap.put(name, values);
} parameterMap.setLocked(true); return parameterMap; }

如果属性parameterMap是上锁的,就返回这个属性。否则填充这个属性,然后上锁,返回属性。我就纳闷了,这个parameterMap高端啊,怎么就有一个判断是否上锁的方法,还有,这个属性在对象生成的时候已经做了初始化,所以它才可以直接调用这个属性的方法。

带着这些疑问,咱们来看看这个属性的初始化,其实很容易就能找到。


/**
* Hash map used in the getParametersMap method.
*/
protected ParameterMap&lt;String, String[]&gt; parameterMap = new ParameterMap&lt;&gt;();

它就是在对象创建的时候创建了ParameterMap类型的对象。

1.3 ParameterMap类

这个org.apache.catalina.util.ParameterMap类,看来我们得重点关注了。打开这个类,我们发现它其实就是一个代理类,里边包含一个private final Map<K,V> delegatedMap;属性,其次,还有一个private boolean locked = false;。看到这里大家可能就明白了,无非是在做增删改操作的时候,先判断有没有锁,再执行操作,如果有锁,就抛出异常。

2 奇特的ApplicationHttpRequest

2.1 ApplicationHttpRequest

其实,上一小结已经点明,ServletRequest声明返回的Map是不可修改的,tomcat里也做到了不可修改。我们以后使用的时候注意一下就行,别自作聪明修改ParameterMap里的属性。

但是笔者是个较真的人,利用IDE,笔者也看到了别的实现类,其中org.apache.catalina.core.ApplicationHttpRequest引起了笔者的注意。这个类翻译成中文就是应用级别的HTTP请求,那他有什么特殊点呢?它实际上也是一个代理类,里面包含类实际的Request对象,来看他的getParameterMap()方法。


/**
* Override the &lt;code&gt;getParameterMap()&lt;/code&gt; method of the
* wrapped request.
*/
@Override
public Map&lt;String, String[]&gt; getParameterMap() { parseParameters();
return (parameters); } /**
* Parses the parameters of this request.
*
* If parameters are present in both the query string and the request
* content, they are merged.
*/
void parseParameters() { if (parsedParams) {
return;
} parameters = new HashMap&lt;String, String[]&gt;();
parameters = copyMap(getRequest().getParameterMap());
mergeParameters();
parsedParams = true;
} /**
* Perform a shallow copy of the specified Map, and return the result.
*
* @param orig Origin Map to be copied
*/
Map&lt;String, String[]&gt; copyMap(Map&lt;String, String[]&gt; orig) { if (orig == null)
return (new HashMap&lt;String, String[]&gt;());
HashMap&lt;String, String[]&gt; dest = new HashMap&lt;String, String[]&gt;(); for (Map.Entry&lt;String, String[]&gt; entry : orig.entrySet()) {
dest.put(entry.getKey(), entry.getValue());
} return (dest); }

这三个方法依次看下来,org.apache.catalina.util.ParameterMap毛的没见到,只有HashMap,啥情况?,tomcat怎么留了这么一个口子?他是干什么用的?什么时候我们的程序能得到这个Request?

2.2 ApplicationDispatcher

带着这个疑问,笔者又深入的搜寻类一番,发现org.apache.catalina.core.ApplicationDispatcher中在方法forward()和方法include()里对原始的Request包装上了ApplicationHttpRequest

这个ApplicationDispatcher实际上实现了javax.servlet.RequestDispatcher.RequestDispatcher,而RequestDispatcher的作用是转发或者包含别的资源,例如JSP,Servlet。

说了,那么多,那到底怎么用呢?实际上ServletRequest有一个方法能够获取RequestDispatcher,然后再调用RequestDispatcher的forward或者include方法。

3 一个简单的实验

3.1 说明

笔者做了一个简单的实验,先说一下实验内容,在Controller的方法中,获取ParameterMap,然后给浏览器中显示它的类型。怎么对比呢?`

  1. /forward0直接获取ParameterMap类型
  2. /forward1调用forward转发请求到/forward3
  3. /forward2调用include包含请求到/forward3
  4. /forward4(没有对应的RequestMapping)在Filter中调用forward转发请求到/forward3
  5. /forward5(没有对应的RequestMapping)在Filter中调用include包含请求到/forward3
  6. /forward6调用forward转发请求到/forward6,注意这个只会调用一次,否则会进入死循环

3.2 代码

来看看Controller和Filter

ForwardController.java



import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException; @Controller
public class ForwardController { @RequestMapping(&quot;/forward0&quot;)
@ResponseBody
public String forward0(ServletRequest request, ServletResponse response) {
return request.getParameterMap().getClass().getCanonicalName();
} @RequestMapping(&quot;/forward1&quot;)
public void forward1(ServletRequest request, ServletResponse response) throws ServletException, IOException {
RequestDispatcher rd = request.getRequestDispatcher(&quot;/forward3&quot;);
rd.forward(request, response);
} @RequestMapping(&quot;/forward2&quot;)
public void forward2(ServletRequest request, ServletResponse response) throws ServletException, IOException {
RequestDispatcher rd = request.getRequestDispatcher(&quot;/forward3&quot;);
rd.include(request, response);
} @RequestMapping(&quot;/forward3&quot;)
@ResponseBody
public String forward3(ServletRequest request, ServletResponse response) {
return request.getParameterMap().getClass().getCanonicalName();
} @RequestMapping(&quot;/forward6&quot;)
@ResponseBody
public String forward6(ServletRequest request, ServletResponse response) {
return request.getParameterMap().getClass().getCanonicalName();
}
}

ForwardFilter.java


package com.gavinzh.learn.web.filter; import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException; public class ForwardFilter implements Filter{
public void init(FilterConfig filterConfig) throws ServletException { } public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest hsr = (HttpServletRequest) servletRequest; if (hsr.getAttribute(&quot;ForwardFilter&quot;) ==null){
hsr.setAttribute(&quot;ForwardFilter&quot;,ForwardFilter.class); if (hsr.getRequestURI().equals(&quot;/forward4&quot;)){
RequestDispatcher rd = servletRequest.getRequestDispatcher(&quot;/forward3&quot;);
rd.forward(servletRequest,servletResponse);
return;
}
if (hsr.getRequestURI().equals(&quot;/forward5&quot;)){
RequestDispatcher rd = servletRequest.getRequestDispatcher(&quot;/forward3&quot;);
rd.include(servletRequest,servletResponse);
return;
} if (hsr.getRequestURI().equals(&quot;/forward6&quot;)){
RequestDispatcher rd = servletRequest.getRequestDispatcher(&quot;/forward6&quot;);
rd.forward(servletRequest,servletResponse);
return;
} filterChain.doFilter(servletRequest,servletResponse);
}
filterChain.doFilter(servletRequest,servletResponse); } public void destroy() { }
}

3.3 最终结果

上述代码怎么组织运行,笔者就不细讲了,网上例子很多。结果我来展示一下:

  1. /forward0:org.apache.catalina.util.ParameterMap
  2. /forward1:java.util.HashMap
  3. /forward2:java.util.HashMap
  4. /forward4:java.util.HashMap
  5. /forward5:java.util.HashMap
  6. /forward6:java.util.HashMap

神奇不神奇?一个javaEE标准声明了是不可变对象,在这个实验里变成了可变对象。

4 奇袭ApplicationHttpRequest

话说到这里,大家对getParameterMap方法也有了个简单了解,如果想改变Map对象的K-V,你就搞一个转发请求。喝一杯咖啡,优雅地实现一些奇怪的逻辑。

不过笔者没有就此罢手,手贱登上了github,看了一下tomcat项目中的这个类。这个类代码的HashMap竟然被替代成了ParameterMap!!!

我就纳闷类,是谁改了这个bug?导致我不能利用这个bug做一些邪恶的事情。

当当当当,blame一下,找到了,ApplicationHttpRequest修改记录,是一个年轻小伙子16年左右修复了这个bug。Tomcat7.0.68版本,Tomcat8.0.14版本开始,这个bug被修复了。

是不是很气人,原先这个功能用的好好的,升了级竟然用不了了。

生气生气生气,怎么办怎么办怎么办,我想同学们已经有办法了。那就是反射ParameterMap,射射射,把locked属性,设置为可访问,然后将locked设置成false。

5 总结

笔者在这里和大家分享了一个小功能,小bug,耽误了大家的一些时间。但上边这些内容完全是笔者在生产开发中遇到的一些问题,笔者以有趣的方式来展示这些问题,以期和大家深入地探讨技术。

总结一下吧,ParameterMap这个Map是不可变的,建议大家还是别打这个对象的主意。为什么?javaEE标准里说了它是不可变的,那么各大Servlet容器厂商自然会以不同地方式实现这个不可变Map,今天你可以修改locked,明天一升级,人家改叫isLocked,那你的代码还能正常运行吗?

那有没有别的方式我们可以让它可变?有的,你写一个filter,在里面对request做一个包装,在getParameterMap时候,返回一个HashMap就可以了。

有趣吧?从可以修改ParameterMap到不能修改,到可以修改,再到建议不要修改,再到可以修改。每一步都是精华呀。

以上同步自谁说ParameterMap只能读不能写?

谁说ParameterMap只能读不能写?的更多相关文章

  1. Ubuntu下提示U盘没有权限--只能读不能写

    在Windows下,U盘能够正常地读写文件(能复制粘贴),但发现有个文件打不开.然后在Linux下,对U盘只能读不能写.提示:Read only system. 参考:https://bugs.lau ...

  2. html和jsp页面中把文本框禁用,只能读不能写的方法

    方法常用有三种: 第一种,使用   onfocus="this.blur()" <input name="deptno" type="text& ...

  3. python3 对excel读、写、修改的操作

    一.对excel的写操作实例: 将一个列表的数据写入excel, 第一行是标题,下面行数具体的数据 import xlwt #只能写不能读 stus = [['姓名', '年龄', '性别', '分数 ...

  4. python3 excel读、写、修改操作

    python3上Excel文件操作的库比较多,新手一开始不知道如何选择合适的库,故整理如下: xlwt: 只能写不能读,只支持python2.3到python2.7版本,只支持xls文件. xlrd ...

  5. python读、写、修改、追写excel文件

    三个工具包 python操作excel的三个工具包如下 xlrd: 对excel进行读相关操作 xlwt: 对excel进行写相关操作 xlutils: 对excel读写操作的整合 注意,只能操作.x ...

  6. python文件处理-读、写

    Python中文件处理的操作包括读.写.修改,今天我们一起来先学习下读和写操作. 一.文件的读操作 例一: #文件读操作 f = open(file="first_blog.txt" ...

  7. @property和@x.setter和@x.deleter表示可读可写可删除

    @property可以将python定义的函数“当做”属性访问,从而提供更加友好访问方式,但是有时候setter/deleter也是需要的.1>只有@property表示只读.2>同时有@ ...

  8. 文件及文件的操作-读、写、追加的t和b模式

    1.什么是文件? 文件是操作系统为用户或应用程序提供的一个读写硬盘的虚拟单位. 文件的操作核心:读和写 对文件进行读写操作就是向操作系统发出指令,操作系统将用户或者应用程序对文件的读写操作转换为具体的 ...

  9. 快读&快写模板【附O2优化】

    快读&快写模板 快读快写,顾名思义,就是提升输入和输出的速度.在这里简单介绍一下几种输入输出的优劣. C++ cin/cout 输入输出:优点是读入的时候不用管数据类型,也就是说不用背scan ...

随机推荐

  1. Java多线程_生产者消费者模式2

    在我的上一条博客中,已经介绍到了多线程的经典案列——生产者消费者模式,但是在上篇中用的是传统的麻烦的非阻塞队列实现的.在这篇博客中我将介绍另一种方式就是:用阻塞队列完成生产者消费者模式,可以使用多种阻 ...

  2. 使用手机安装Windows系统------DriveDroid

    今天给大家推荐的软件是: DriveDroid 1.说来都是无奈,前一段时间,重装系统结果按完之后进不去系统,然后手贱又把U启动盘给弄坏了 2.本来想这下需要去找同学借个电脑了,然后就想手机可不可以啊 ...

  3. Java面试题(反射篇+对象拷贝篇)

    反射 57.什么是反射? 反射主要是指程序可以访问.检测和修改它本身状态或行为的一种能力 Java反射: 在Java运行时环境中,对于任意一个类,能否知道这个类有哪些属性和方法?对于任意一个对象,能否 ...

  4. unity3d中的Quaternion.LookRotation

    android开发范例中的第二个粒子,是摇杆操作游戏,模式类似于“迷你高尔”,僵尸包围类型的设计游戏. 其中让我注意到这个函数的使用非常特别:Quaternion.LookRotation. 游戏针对 ...

  5. Mysql表,列,库的增删查改

    下面是我总结的一些基础的sql知识,主要是为了以后更好的查阅和帮助其他初学的人,同时记录自己的成长,还写了一点稍有难度的sql面试题级别的题目,好了废话不多说,见真题... #创建数据库 CREATE ...

  6. Apollo(阿波罗)配置中心Java客户端使用指南使用指南

          Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境.不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限.流程治理等特性,适用于微服务配置管 ...

  7. Matlab摄像头视频基本处理

    一.读取摄像头 1.首先保证摄像头及其驱动正确在电脑上安装 2.简单的代码显示驱动摄像头,并显示: vid = videoinput('winvideo',1); preview(vid); 3.默认 ...

  8. 01vue.config.js

      const path = require('path'); module.exports = { // 基本路径 publicPath: process.env.NODE_ENV === 'pro ...

  9. 蒲公英 &#183; JELLY技术周刊 Vol.21 -- 技术周刊 &#183; React Hooks vs Vue 3 + Composition API

    蒲公英 · JELLY技术周刊 Vol.21 选 React 还是 Vue,每个人心中都会有自己的答案,有很多理由去 pick 心水的框架,但是当我们扪心自问,我们真的可以公正的来评价这两者之间的差异 ...

  10. Linux centos6.5 安装

    本来打算玩 netty的 但是这个东西暂时也不用,而且我之前玩过mina就暂时不玩这个了,等以后有时间再玩,那玩啥呢?前几天和我们领导要了百度网盘会员,下了60G的大数据视屏,嘿嘿,有的玩了,今天开始 ...