1、内容替换Filter
有时候需要对网站的内容进行替换,防止输出非法内容或敏感内容。常规的解决方法是在保存数据进数据库前对非法敏感内容进行替换,或者在servlet里输出到客户端时进行替换。这两种解决方案都有很大的局限性,例如每个servlet都要进行替换、工作量大,与业务耦合度比较严重等。
使用内容替换Filter则方便许多。内容替换Filter的工作原理是,在servlet将内容输出到response时,response将内容缓存起来,在Filter中进行替换,然后在输出到客户端浏览器。由于默认的response并不能严格缓存输出内容,因此需要自定义一个具备缓存功能的response。
HttpCharacterResponseWrapper.java
package bqh.cslg;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
public class HttpCharacterResponseWrapper extends HttpServletResponseWrapper {
//字符数组writer
private CharArrayWriter charArrayWriter = new CharArrayWriter();
public HttpCharacterResponseWrapper(HttpServletResponse response) {
super(response);
}
//覆盖父类方法
@Override
public PrintWriter getWriter() throws IOException {
return new PrintWriter(charArrayWriter);
}
public CharArrayWriter getCharArrayWriter() {
return charArrayWriter;
}
}
该类覆盖了getWriter()方法,当servlet中使用该response对象调用getWriter()来输出内容时,内容将被输出到CharArrayWriter中,达到缓存效果。
Filter需要将自定义的response传到servlet中,代码如下:
package bqh.cslg;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Properties;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
public class OutputReplaceFilter implements Filter {
private Properties pp = new Properties();
public void init(FilterConfig filterconfig) throws ServletException {
//获取web.xml中初始化参数
String file = filterconfig.getInitParameter("file");
//文件实际位置
String realPath = filterconfig.getServletContext().getRealPath(file);
try {
//加载非法词
pp.load(new FileInputStream(realPath));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public void doFilter(ServletRequest servletrequest,
ServletResponse servletresponse, FilterChain filterchain)
throws IOException, ServletException {
//自定义response
HttpCharacterResponseWrapper response = new HttpCharacterResponseWrapper(
(HttpServletResponse) servletresponse);
//将自定义response传到servlet中
filterchain.doFilter(servletrequest, response);
//得到response输出内容
String output = response.getCharArrayWriter().toString();
//遍历敏感词
for (Object obj : pp.keySet()) {
String key = (String) obj;
output = output.replace(key, pp.getProperty(key));
}
PrintWriter out = servletresponse.getWriter();
//输出
out.write(output);
}
public void destroy() {
pp = null;
}
}
web.xml配置
<filter>
<filter-name>OutputReplaceFilter</filter-name>
<filter-class>bqh.cslg.OutputReplaceFilter</filter-class>
<init-param>
<param-name>file</param-name>
<param-value>/WEB-INF/sensitive.properties</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>OutputReplaceFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
WEB-INF下定义非法词库
sensitive.properties
Chna=China
\u8272\u60C5=**
\u66B4\u529B=**
www.baidu.com.cn=www.baidu.com
这里会自动将中文编程Unicode编码
测试:MyServlet
package bqh.cslg;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">");
out.println("<HTML>");
out.println(" <HEAD><TITLE>A Servlet</TITLE></HEAD>");
out.println(" <BODY>");
out.println(request.getParameter("p1") + "<br>");
out.println(request.getParameter("p2") + "<br>");
out.println(request.getParameter("p3") + "<br>");
out.println(request.getParameter("p4") + "<br>");
out.println(" </BODY>");
out.println("</HTML>");
out.flush();
out.close();
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
}
可以看到自定义的敏感词已经被代替。
2、GZIP压缩Filter
使用Filter实现GZIP压缩,需要自定义的response和servletOutputStream。
压缩Filter中需要先判断客户浏览器是否支持GZip的自动解压,如果支持,则进行GZIP压缩,否则不压缩。判断的依据是浏览器提供的header信息。
GZipFilter
package bqh.cslg;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebFilter("/*")
public class GZipFilter implements Filter {
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest arg0, ServletResponse arg1,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) arg0;
HttpServletResponse response = (HttpServletResponse) arg1;
//支持的编码方式
String acceptEncoding = request.getHeader("Accept-Encoding");
if (acceptEncoding != null
&& acceptEncoding.toLowerCase().indexOf("gzip") != -1) {
//客户端浏览器支持GZIP压缩
GZipResponseWrapper gzipRes = new GZipResponseWrapper(response);
chain.doFilter(request, gzipRes);
gzipRes.finishResponse();
} else {
//客户端浏览器不支持GZIP压缩
chain.doFilter(request, response);
}
}
@Override
public void init(FilterConfig arg0) throws ServletException {
}
}
GZipResponseWrapper
package bqh.cslg;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
public class GZipResponseWrapper extends HttpServletResponseWrapper {
private HttpServletResponse response;
// 自定义的outputstream,执行close的时候对数据压缩,并输出
private GZipOutputStream gzipOutputStream;
// 自定义的PrintWriter,将内容输出到GZipOutputStream中
private PrintWriter writer;
public GZipResponseWrapper(HttpServletResponse response) {
super(response);
this.response = response;
}
// 覆盖getOutputStream(),处理二进制内容
@Override
public ServletOutputStream getOutputStream() throws IOException {
if (gzipOutputStream == null) {
gzipOutputStream = new GZipOutputStream(response);
}
return gzipOutputStream;
}
// 覆盖getWriter(),处理字符内容
@Override
public PrintWriter getWriter() throws IOException {
if (writer == null) {
writer = new PrintWriter(new OutputStreamWriter(
new GZipOutputStream(response), "utf-8"));
}
return writer;
}
// 压缩后数据长度发生变化,因此将该方法置为空
@Override
public void setContentLength(int len) {
}
@Override
public void flushBuffer() throws IOException {
gzipOutputStream.flush();
}
public void finishResponse() throws IOException {
if (gzipOutputStream != null) {
gzipOutputStream.close();
}
if (writer != null) {
writer.close();
}
}
}
GZipResponseWrapper
package bqh.cslg;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
public class GZipResponseWrapper extends HttpServletResponseWrapper {
private HttpServletResponse response;
// 自定义的outputstream,执行close的时候对数据压缩,并输出
private GZipOutputStream gzipOutputStream;
// 自定义的PrintWriter,将内容输出到GZipOutputStream中
private PrintWriter writer;
public GZipResponseWrapper(HttpServletResponse response) {
super(response);
this.response = response;
}
// 覆盖getOutputStream(),处理二进制内容
@Override
public ServletOutputStream getOutputStream() throws IOException {
if (gzipOutputStream == null) {
gzipOutputStream = new GZipOutputStream(response);
}
return gzipOutputStream;
}
// 覆盖getWriter(),处理字符内容
@Override
public PrintWriter getWriter() throws IOException {
if (writer == null) {
writer = new PrintWriter(new OutputStreamWriter(
new GZipOutputStream(response), "utf-8"));
}
return writer;
}
// 压缩后数据长度发生变化,因此将该方法置为空
@Override
public void setContentLength(int len) {
}
@Override
public void flushBuffer() throws IOException {
gzipOutputStream.flush();
}
public void finishResponse() throws IOException {
if (gzipOutputStream != null) {
gzipOutputStream.close();
}
if (writer != null) {
writer.close();
}
}
}
GZipOutputStream
package bqh.cslg;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.zip.GZIPOutputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
public class GZipOutputStream extends ServletOutputStream {
// 原response
private HttpServletResponse response;
// JDK自带的GZIP压缩数据类
private GZIPOutputStream gzipOutputStream;
// 将压缩后的数据存放到ByteArrayOutputStream中
private ByteArrayOutputStream byteArrayOutputStream;
public GZipOutputStream(HttpServletResponse response) throws IOException {
this.response = response;
byteArrayOutputStream = new ByteArrayOutputStream();
gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream);
}
// 输出到JDK的GZIP类中
@Override
public void write(int b) throws IOException {
gzipOutputStream.write(b);
}
// 执行压缩,并将数据输出到浏览器
public void close() throws IOException {
// 执行压缩一定要调用该方法
gzipOutputStream.finish();
// 将压缩后的数据输出到客户端
byte[] content = byteArrayOutputStream.toByteArray();
// 设定压缩方式为GZIP,客户端浏览器会自动解压缩
response.addHeader("Content-Encoding", "gzip");
response.addHeader("Content-Length", Integer.toString(content.length));
// 输出到浏览器
ServletOutputStream out = response.getOutputStream();
out.write(content);
out.close();
}
public void flush() throws IOException {
gzipOutputStream.flush();
}
public void write(byte[] b, int off, int len) throws IOException {
gzipOutputStream.write(b, off, len);
}
public void write(byte[] b) throws IOException {
gzipOutputStream.write(b);
}
}
测试类:gzip.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<%@page import="java.net.URLConnection"%>
<%@page import="java.net.URL"%>
<%@page import="java.text.NumberFormat"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<style type="text/css">
body {
margin-top: 2px;
}
table {
margin-top: 10px;
border-collapse: collapse;
border: 1px solid #000000;
width: 350px;
}
td{
border: 1px solid #000000;
font-size: 12px;
padding: 2px;
background: #DDDDFF;
}
</style>
</head>
<body>
<%!
public void test(JspWriter out, String url) throws Exception {
// 模拟支持 GZIP 的浏览器
URLConnection connGzip = new URL(url).openConnection();
connGzip.setRequestProperty("Accept-Encoding", "gzip");
int lengthGzip = connGzip.getContentLength();
// 模拟不支持 GZIP 的浏览器
URLConnection connCommon = new URL(url).openConnection();
int lengthCommon = connCommon.getContentLength();
double rate = new Double(lengthGzip) / lengthCommon;
out.println("<table>");
out.println(" <tr>");
out.println(" <td colspan=3>网址: " + url + "</td>");
out.println(" </tr>");
out.println(" <tr>");
out.println(" <td>压缩后:" + lengthGzip + " byte</td>");
out.println(" <td>压缩前:" + lengthCommon + " byte</td>");
out.println(" <td>比率:" + NumberFormat.getPercentInstance().format(rate) + "</td>");
out.println(" </tr>");
out.println("</table>");
}
%>
<%
String[] urls = {
"http://localhost:8080/GZIPFilter/1.png",
"http://www.sina.com.cn/",
"https://v.qq.com/",
};
for(String url : urls){
test(out, url);
}
%>
</body>
</html>