前言
随着struts2漏洞的出现,以及struts2使用的不方便,过重的设计。所以市面上MVC的实际标已经成了Spring MVC。
因此本文主要针对Spring MVC的web环境下,Spring-web提供的这个jar里的util包内的一些类,因为都是比较共用的一些web类,因此在这里做一些介绍~
实用类介绍(排名不分先后)
ContentCachingRequestWrapper、ContentCachingResponseWrapper
字面理解:把请求/响应内容缓存起来的代理类~
这两个类出现的版本比较晚(如下),之前我们一直实用的HttpServletRequestWrapper,现在有了它,使用起来就更加的方便了~
/** * @since 4.1.3 */ public class ContentCachingRequestWrapper extends HttpServletRequestWrapper {}
我们都知道request.getInputStream()请求body里面的内容只能被获取一次的。通过这个类(但其实有bug):
**它能够解决:解决HttpServletRequest inputStream只能读取一次的问题**
**它能够解决:解决HttpServletRequest inputStream只能读取一次的问题**
上篇博文 【小家Spring】从OncePerRequestFilter的源码解读去看看Spring里面的Filter有什么特别以及常用Filter使用介绍
最后讲到请求日志里看到,payload只有请求结束的时候才打印出来,为何呢?
我们看看源码:
private final ByteArrayOutputStream cachedContent;
它使用一个字段:cachedContent来缓存body体里面的内容。但最重要的是,什么时候才会向cachedCotent里面写内容呢?我们继续跟踪发现:大体有两个地方:
public String getParameter(String name); public Map<String, String[]> getParameterMap(); public Enumeration<String> getParameterNames(); public String[] getParameterValues(String name) //都有这个判断。必须没被写过,并且是表单形式的Post方式才会往里写内容(这是一种非常特殊的传值方式,使用较少) if (this.cachedContent.size() == 0 && isFormPost()) { writeRequestParametersToCachedContent(); }
第二种方式才是重点:
@Override public int read() throws IOException { int ch = this.is.read(); if (ch != -1 && !this.overflow) { if (contentCacheLimit != null && cachedContent.size() == contentCacheLimit) { this.overflow = true; handleContentOverflow(contentCacheLimit); } else { cachedContent.write(ch); } } return ch; }
我们发现还是在read()方法里面,只有调用了请求流的read方法,才会把内容缓存起来。这个和Spring MVC的原理:@RequestBody注解的参数解析器(RequestResponseBodyMethodProcessor)就是调用了read()方法去获取到内容的。
到此我们其实能够很好的解释,上篇博文里为何请求开始没有payload,请求结束时有了
但是这么做,很多时候并不能满足我们的使用场景:
1、我们需要在filter中拿到requestBody数据进行处理(比如检查敏感词汇,过滤掉低俗的词汇等~)
2、在controller中注入@ReqeustBody读取rerquestBody数据
按照Spring目前的设计,这个request只要我们getInputStream()就不能再被controller接受了(·requestBody is missing…·),显然不是我们想要的。这个其实是Spring的一个bug,早在2014年就有人提出了,只是一直都没有被修复:this is a bug
在读取的时候应该先去缓存看看,有值就不要读流了,返回即可。这样缓存里面的数据是可以玩限次重复读的。在还没有这个类的时候,我写了一个wrapper,供以参考:
public class ContentCachingRequestWrapper extends HttpServletRequestWrapper{ private byte[] body; private BufferedReader reader; private ServletInputStream inputStream; public ContentCachingRequestWrapper(HttpServletRequest request) throws IOException{ super(request); //读一次 然后缓存起来 body = IOUtils.toByteArray(request.getInputStream()); inputStream = new RequestCachingInputStream(body); } public byte[] getBody() { return body; } @Override public ServletInputStream getInputStream() throws IOException { if (inputStream != null) { return inputStream; } return super.getInputStream(); } @Override public BufferedReader getReader() throws IOException { if (reader == null) { reader = new BufferedReader(new InputStreamReader(inputStream, getCharacterEncoding())); } return reader; } //代理一下ServletInputStream 里面真是内容为当前缓存的bytes private static class RequestCachingInputStream extends ServletInputStream { private final ByteArrayInputStream inputStream; public RequestCachingInputStream(byte[] bytes) { inputStream = new ByteArrayInputStream(bytes); } @Override public int read() throws IOException { return inputStream.read(); } @Override public boolean isFinished() { return inputStream.available() == 0; } @Override public boolean isReady() { return true; } @Override public void setReadListener(ReadListener readlistener) { } } }
因此,千万千万不要在Filter里提前去getInputStream(),否则@RequestBody将拿不到东西而报错的。使用此类的时候需要注意~
CookieGenerator
顾名思义,是生成Cookie的。使用起来也比较简单,不做介绍。一般在CAS单点登录里用得较多。现在都JWT了,就用得较少了
HtmlUtils
很多时候,由于特殊字符的原因,会造成用户输入的信息反馈到页面上时会显示成乱码,造成页面排版混乱;另外,黑客经常利用特殊字符对网站进行xss跨站攻击,所以我们需要对页面上提交的特殊字符进行html转码。
Spring提供的这个工具类,省去了我们写工具类对html中的特殊字符进行过滤的麻烦。
public static void main(String[] args) { String specialStr = "#<table id=\"testid\"><tr>test1;test2</tr></table>"; // 转义(用转义字符表示): #<table id="testid"><tr>test1;test2</tr></table> String str1 = HtmlUtils.htmlEscape(specialStr); System.out.println(str1); // 转义(用数字字符表示): #<table id="testid"><tr>test1;test2</tr></table> String str2 = HtmlUtils.htmlEscapeDecimal(specialStr); System.out.println(str2); // 转义(用16进制表示) String str3 = HtmlUtils.htmlEscapeHex(specialStr); System.out.println(str3); // 返转义,一个方法即可 结果见上面 specialStr System.out.println(HtmlUtils.htmlUnescape(str1)); System.out.println(HtmlUtils.htmlUnescape(str2)); System.out.println(HtmlUtils.htmlUnescape(str3)); }
这样转义后是安全的。比如用编辑器编辑成良好格式的HTML串传过来。我们一般要过滤掉<script>这种标签,防止被黑
另外JavaScriptUtils
可以js里面的一些特殊符号转义。如’ // 等等符号