第六章 过滤器Filter

时间:2023-03-10 05:48:46
第六章 过滤器Filter

Filter概述

Filter不用于客户端请求,只用于对request,response进行修改或对context,session,request事件进行监听。

1.概述

第六章 过滤器Filter

如上图,多个filter组成一个FilterChain。

2.Filter接口

第六章 过滤器Filter

第六章 过滤器Filter

第六章 过滤器Filter

3.Filter配置

第六章 过滤器Filter

第六章 过滤器Filter

防盗链Filter

第六章 过滤器Filter

第六章 过滤器Filter

代码详解:

1.编写过滤器

public class ImageRedirectFilter implements Filter {

public void init(FilterConfig config) throws ServletException {

}

public void doFilter(ServletRequest req, ServletResponse res,

FilterChain chain) throws IOException, ServletException {

HttpServletRequest request = (HttpServletRequest) req;

HttpServletResponse response = (HttpServletResponse) res;

// 禁止缓存

response.setHeader("Cache-Control", "no-store");

response.setHeader("Pragrma", "no-cache");

response.setDateHeader("Expires", 0);

// 链接来源地址

String referer = request.getHeader("referer");

if (referer == null || !referer.contains(request.getServerName())) {

request.getServerName()//获取你的网站的域名

/**

* 如果 链接地址来自其他网站,则返回错误图片

*/

request.getRequestDispatcher("/error.gif").forward(request,

response);

} else {

/**

* 图片正常显示

*/

chain.doFilter(request, response);

}

}

public void destroy() {

}

}

过滤器继承Filter接口,重载三个方法,分别是init(),doFilter(),destroy().其中doFilter()中必须执行

chain.doFilter(request, response);以便于执行接下来的过滤器。在doFilter()中判断浏览器请求的源地址和服务器的域名是否一致。

2.过滤器配置

第六章 过滤器Filter

通过配置<url-pattern>,说明只有当浏览器访问/images或者/upload/images目录时才执行该过滤器。

字符编码Filter

第六章 过滤器Filter

public class CharacterEncodingFilter implements Filter {

private String characterEncoding;

private String enable;

private boolean enabled;

@Override

public void init(FilterConfig config) throws ServletException {

characterEncoding = config.getInitParameter("characterEncoding");

enable = config.getInitParameter("enable");

enabled = "true".equalsIgnoreCase(enable.trim())

|| "1".equalsIgnoreCase(enable.trim());

}

@Override

public void doFilter(ServletRequest request, ServletResponse response,

FilterChain chain) throws IOException, ServletException {

if (enabled&&characterEncoding != null) {

request.setCharacterEncoding(characterEncoding);

response.setCharacterEncoding(characterEncoding);

}

chain.doFilter(request, response);

}

@Override

public void destroy() {

characterEncoding = null;

}

}

第六章 过滤器Filter

第六章 过滤器Filter

个人认为编码Filter应该处于第一个Filter,这样,在tomcat解析request参数前就对参数进行编码设置了。

日志记录Filter

public class LogFilter implements Filter {

private Log log = LogFactory.getLog(this.getClass());

private String filterName;

public void init(FilterConfig config) throws ServletException {

// 获取 Filter 的 name,配置在 web.xml 中

filterName = config.getFilterName();

log.info("启动 Filter: " + filterName);

}

public void doFilter(ServletRequest req, ServletResponse res,

FilterChain chain) throws IOException, ServletException {

HttpServletRequest request = (HttpServletRequest) req;

HttpServletResponse response = (HttpServletResponse) res;

long startTime = System.currentTimeMillis();

String requestURI = request.getRequestURI();

requestURI = request.getQueryString() == null ? requestURI

: (requestURI + "?" + request.getQueryString()); //判断请求中是否含有参数,如果有,则加入到requestURI中去。

chain.doFilter(request, response);

long endTime = System.currentTimeMillis();

log.info(request.getRemoteAddr() + " 访问了 " + requestURI + ", 总用时 "

+ (endTime - startTime) + " 毫秒。");//request.getRemoteAddr()返回客户端或代理服务器的IP地址

}

public void destroy() {

log.info("关闭 Filter: " + filterName);

}

}

web配置省略。

异常捕捉Filter

第六章 过滤器Filter

public class ExceptionHandlerFilter implements Filter {

public void destroy() {

}

public void doFilter(ServletRequest request, ServletResponse response,

FilterChain chain) throws IOException, ServletException {

try { //捕获异常

chain.doFilter(request, response);

} catch (Exception e) { //得到异常后,根据不同异常作相应的处理

Throwable rootCause = e;

while (rootCause.getCause() != null) {

rootCause = rootCause.getCause();

}

String message = rootCause.getMessage();

message = message == null ? "异常:" + rootCause.getClass().getName()

: message;

request.setAttribute("message", message);

request.setAttribute("e", e);

if (rootCause instanceof AccountException) {//判断rootCause是否是AccountException的一个实例

request.getRequestDispatcher("/accountException.jsp").forward(

request, response);

} else if (rootCause instanceof BusinessException) {

request.getRequestDispatcher("/businessException.jsp").forward(

request, response);

} else {

request.getRequestDispatcher("/exception.jsp").forward(request,

response);

}

}

}

public void init(FilterConfig arg0) throws ServletException {

}

}

权限验证Filter

第六章 过滤器Filter

public class PrivilegeFilter implements Filter {

private Properties pp = new Properties();

public void init(FilterConfig config) throws ServletException {

// 从 初始化参数 中获取权 限配置文件 的位置

String file = config.getInitParameter("file");

String realPath = config.getServletContext().getRealPath(file);

try {

pp.load(new FileInputStream(realPath));

} catch (Exception e) {

config.getServletContext().log("读取权限控制文件失败。", e);

}

}

public void doFilter(ServletRequest req, ServletResponse res,

FilterChain chain) throws IOException, ServletException {

HttpServletRequest request = (HttpServletRequest) req;

// 获取访问的路径,例如:admin.jsp

String requestURI = request.getRequestURI().replace(

request.getContextPath() + "/", "");

// 获取 action 参数,例如:add

String action = req.getParameter("action");

action = action == null ? "" : action;

// 拼接成 URI。例如:log.do?action=list

String uri = requestURI + "?action=" + action;

// 从 session 中获取用户权限角色。

String role = (String) request.getSession(true).getAttribute("role");

role = role == null ? "guest" : role;

boolean authentificated = false;

// 开始检查该用户角色是否有权限访问 uri

for (Object obj : pp.keySet()) {

String key = ((String) obj);

// 使用正则表达式验证 需要将 ? . 替换一下,并将通配符 * 处理一下

if (uri.matches(key.replace("?", "\\?").replace(".", "\\.")

.replace("*", ".*"))) {

// 如果 role 匹配

if (role.equals(pp.get(key))) {

authentificated = true;

break;

}

}

}

if (!authentificated) {

throw new RuntimeException(new AccountException(

"您无权访问该页面。请以合适的身份登陆后查看。"));

}

// 继续运行

chain.doFilter(req, res);

}

public void destroy() {

pp = null;

}

}

第六章 过滤器Filter

第六章 过滤器Filter

内容替换Filter

第六章 过滤器Filter

public class HttpCharacterResponseWrapper extends HttpServletResponseWrapper {

private CharArrayWriter charArrayWriter = new CharArrayWriter();

public HttpCharacterResponseWrapper(HttpServletResponse response) {

super(response);

}

@Override

public PrintWriter getWriter() throws IOException {

return new PrintWriter(charArrayWriter);//将response的输出目标变为charArrayWriter

}

public CharArrayWriter getCharArrayWriter() {

return charArrayWriter;

}

}

第六章 过滤器Filter

public class OutputReplaceFilter implements Filter {

private Properties pp = new Properties();

public void init(FilterConfig config) throws ServletException {

String file = config.getInitParameter("file");

String realPath = config.getServletContext().getRealPath(file);

try {

pp.load(new FileInputStream(realPath));

} catch (IOException e) {

}

}

public void doFilter(ServletRequest req, ServletResponse res,

FilterChain chain) throws IOException, ServletException {

// 自定义的 response

HttpCharacterResponseWrapper response = new HttpCharacterResponseWrapper(

(HttpServletResponse) res);

// 提交给 Servlet 或者下一个 Filter

chain.doFilter(req, 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 = res.getWriter();

out.write(output);

out.println("<!-- Generated at " + new java.util.Date() + " -->");

}

public void destroy() {

}

}

代码解析:

1.定义一个类HttpCharacterResponseWrapper用于替换原来的response。

2.在过滤器中用res声明一个新的response:

HttpCharacterResponseWrapper response = new HttpCharacterResponseWrapper(

(HttpServletResponse) res);

3.调用chain.doFilter(req, response)时,如果有输出就会调用response.getWriter(), 此时调用的是覆盖后的PrintWriter(),该PrintWriter()将会把数据写到charArrayWriter中去。

4.执行完chain.doFilter(req, response)后,获取charArrayWriter中的数据:

String output = response.getCharArrayWriter().toString();

5.修改output数据

6.再通过原始的res来输出output中的数据

PrintWriter out = res.getWriter();

out.write(output);

以上代码的功能是先将res包装成response(此时response的输出目标不再是客户端,而是charArrayWriter缓存区),接着在读取charArrayWriter内的内用,再通过res的写出方法写到客户端。

GZIP压缩Filter

第六章 过滤器Filter

public class GZipFilter implements Filter {

public void destroy() {

}

public void doFilter(ServletRequest req, ServletResponse res,

FilterChain chain) throws IOException, ServletException {

HttpServletRequest request = (HttpServletRequest) req;

HttpServletResponse response = (HttpServletResponse) res;

String acceptEncoding = request.getHeader("Accept-Encoding");

System.out.println("Accept-Encoding: " + acceptEncoding);

if (acceptEncoding != null

&& acceptEncoding.toLowerCase().indexOf("gzip") != -1) {

// 如果客户浏览器支持 GZIP 格式, 则使用 GZIP 压缩数据

GZipResponseWrapper gzipResponse = new GZipResponseWrapper(response);

chain.doFilter(request, gzipResponse);

// 输出压缩数据

gzipResponse.finishResponse();

} else {

// 否则, 不压缩

chain.doFilter(request, response);

}

}

public void init(FilterConfig arg0) throws ServletException {

}

}

类GZipResponseWrapper代码如下:

public class GZipResponseWrapper extends HttpServletResponseWrapper {

// 默认的 response

private HttpServletResponse response;

// 自定义的 outputStream, 执行close()的时候对数据压缩,并输出

private GZipOutputStream gzipOutputStream;

// 自定义 printWriter,将内容输出到 GZipOutputStream 中

private PrintWriter writer;

public GZipResponseWrapper(HttpServletResponse response) throws IOException {

super(response);

this.response = response;

}

public ServletOutputStream getOutputStream() throws IOException {

if (gzipOutputStream == null)

gzipOutputStream = new GZipOutputStream(response);

return gzipOutputStream;

}

public PrintWriter getWriter() throws IOException {

if (writer == null)

writer = new PrintWriter(new OutputStreamWriter(

new GZipOutputStream(response), "UTF-8"));

return writer;

}

// 压缩后数据长度会发生变化 因此将该方法内容置空

public void setContentLength(int contentLength) {

}

public void flushBuffer() throws IOException {

gzipOutputStream.flush();

}

public void finishResponse() throws IOException {

if (gzipOutputStream != null)

gzipOutputStream.close();

if (writer != null)

writer.close();

}

}

类GZipOutputStream代码如下:

public class GZipOutputStream extends ServletOutputStream {

private HttpServletResponse response;

// JDK 自带的压缩数据的类

private GZIPOutputStream gzipOutputStream;

// 将压缩后的数据存放到 ByteArrayOutputStream 对象中

private ByteArrayOutputStream byteArrayOutputStream;

public GZipOutputStream(HttpServletResponse response) throws IOException {

this.response = response;

byteArrayOutputStream = new ByteArrayOutputStream();

gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream);

}

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);

}

}

代码详解:

1.在过滤器中有如下代码:

// 如果客户浏览器支持 GZIP 格式, 则使用 GZIP 压缩数据

GZipResponseWrapper gzipResponse = new GZipResponseWrapper(response);

chain.doFilter(request, gzipResponse);

// 输出压缩数据

gzipResponse.finishResponse();

第一行是将response包装,目的是压缩response中的数据

第三行是将response中压缩的数据输出到客户端

2.构造一个GZipResponseWrapper类,在此类中重写getWriter方法改变其输出目的地。

public PrintWriter getWriter() throws IOException {

if (writer == null)

writer = new PrintWriter(new OutputStreamWriter(

new GZipOutputStream(response), "UTF-8"));

return writer;

}

因此过滤器中gzipResponse获取的writer将会把数据输出到GZipOutputStream(response)流中。

3.GZipOutputStream流继承于ServletOutputStream,在此类中创建一个缓存区ByteArrayOutputStream并生成GZIPOutputStream的对象gzipOutputStream,并将该流的输出指向缓存区,该类的write方法自动将数据压缩并写到指定目的地。GZipOutputStream的输出都是通过GZIPOutputStream的gzipOutputStream写入到缓存区。

4.当过滤器执行chain.doFilter(request, gzipResponse)期间,假如有数据输出则会调用gzipResponse.getWriter()方法得到PrintWriter out对象。执行out.write("String"),则会进一步调用OutputStreamWriter的write()方法,又会进一步调用GZipOutputStream的write()方法,在

GZipOutputStream的write()方法中,调用GZIPOutputStream的write方法把需要输出的数据压缩并缓存到ByteArrayOutputStream中。

5.当过滤器执行gzipResponse.finishResponse()时,便会关闭PrintWriter writer对象对应的流,即进一步关闭OutputStreamWriter流,进一步关闭GZipOutputStream流,在GZipOutputStream的close方法中将缓存区的ByteArrayOutputStream通过response输出到客户端。

图像水印Filter

思路和上一节GZIP压缩是类似的。

1.WaterMarkFilter代码如下:

public class WaterMarkFilter implements Filter {

// 水印图片,配置在初始化参数中

private String waterMarkFile;

public void init(FilterConfig config) throws ServletException {

String file = config.getInitParameter("waterMarkFile");

waterMarkFile = config.getServletContext().getRealPath(file);

}

public void doFilter(ServletRequest req, ServletResponse res,

FilterChain chain) throws IOException, ServletException {

HttpServletRequest request = (HttpServletRequest) req;

HttpServletResponse response = (HttpServletResponse) res;

// 自定义的response

WaterMarkResponseWrapper waterMarkRes = new WaterMarkResponseWrapper(

response, waterMarkFile);

chain.doFilter(request, waterMarkRes);

// 打水印,输出到客户端浏览器

waterMarkRes.finishResponse();

}

public void destroy() {

}

}

2.WaterMarkResponseWrapper代码如下:

public class WaterMarkResponseWrapper extends HttpServletResponseWrapper {

// 水印图片位置

private String waterMarkFile;

// 原response

private HttpServletResponse response;

// 自定义servletOutputStream,用于缓冲图像数据

private WaterMarkOutputStream waterMarkOutputStream;

public WaterMarkResponseWrapper(HttpServletResponse response,

String waterMarkFile) throws IOException {

super(response);

this.response = response;

this.waterMarkFile = waterMarkFile;

this.waterMarkOutputStream = new WaterMarkOutputStream();

}

// 覆盖getOutputStream(),返回自定义的waterMarkOutputStream

public ServletOutputStream getOutputStream() throws IOException {

return waterMarkOutputStream;

}

public void flushBuffer() throws IOException {

waterMarkOutputStream.flush();

}

// 将图像数据打水印,并输出到客户端浏览器

public void finishResponse() throws IOException {

// 原图片数据

byte[] imageData = waterMarkOutputStream.getByteArrayOutputStream()

.toByteArray();

// 打水印后的图片数据

byte[] image = ImageUtil.waterMark(imageData, waterMarkFile);

// 将图像输出到浏览器

response.setContentLength(image.length);

response.getOutputStream().write(image);

waterMarkOutputStream.close();

}

}

3.WaterMarkOutputStream代码如下:

public class WaterMarkOutputStream extends ServletOutputStream {

// 缓冲图片数据

private ByteArrayOutputStream byteArrayOutputStream;

public WaterMarkOutputStream() throws IOException {

byteArrayOutputStream = new ByteArrayOutputStream();

}

public void write(int b) throws IOException {

byteArrayOutputStream.write(b);

}

public void close() throws IOException {

byteArrayOutputStream.close();

}

public void flush() throws IOException {

byteArrayOutputStream.flush();

}

public void write(byte[] b, int off, int len) throws IOException {

byteArrayOutputStream.write(b, off, len);

}

public void write(byte[] b) throws IOException {

byteArrayOutputStream.write(b);

}

public ByteArrayOutputStream getByteArrayOutputStream() {

return byteArrayOutputStream;

}

}

4.ImageUtil代码如下:

public class ImageUtil {

/**

*

* @param imageData

* JPG 图像文件

* @param waterMarkFile

* 水印图片

* @return 加水印后的图像数据

* @throws IOException

*/

public static byte[] waterMark(byte[] imageData, String waterMarkFile)

throws IOException {

// 水印图片的右边距 下边距

int paddingRight = 10;

int paddingBottom = 10;

// 原始图像

Image image = new ImageIcon(imageData).getImage();

int imageWidth = image.getWidth(null);

int imageHeight = image.getHeight(null);

// 水印图片

Image waterMark = ImageIO.read(new File(waterMarkFile));

int waterMarkWidth = waterMark.getWidth(null);

int waterMarkHeight = waterMark.getHeight(null);

// 如果图片尺寸过小,则不打水印,直接返回

if (imageWidth < waterMarkWidth + 2 * paddingRight

|| imageHeight < waterMarkHeight + 2 * paddingBottom) {

return imageData;

}

BufferedImage bufferedImage = new BufferedImage(imageWidth,

imageHeight, BufferedImage.TYPE_INT_RGB);

Graphics g = bufferedImage.createGraphics();

// 绘制原始图像

g.drawImage(image, 0, 0, imageWidth, imageHeight, null);

// 绘制水印图片

g.drawImage(waterMark, imageWidth - waterMarkWidth - paddingRight,

imageHeight - waterMarkHeight - paddingBottom, waterMarkWidth,

waterMarkHeight, null);

g.dispose();

// 转成JPEG格式

ByteArrayOutputStream out = new ByteArrayOutputStream();

JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);

encoder.encode(bufferedImage);

byte[] data = out.toByteArray();

out.close();

return data;

}

}

代码详解:

1.过滤器中如下代码:

// 自定义的response

WaterMarkResponseWrapper waterMarkRes = new WaterMarkResponseWrapper(

response, waterMarkFile);

对response进行包装,使得response的输出数据放入缓存,并对图像数据添加水印

2.chain.doFilter(request, waterMarkRes)执行过程中,如果输出图片,则会调用waterMarkRes.getOutputStream()方法,由于在WaterMarkResponseWrapper中重写了该方法,故得到的是waterMarkOutputStream流。waterMarkOutputStream流中将数据写入到缓存区byteArrayOutputStream流中。

3.过滤器执行waterMarkRes.finishResponse()时,将会把waterMarkOutputStream中的数据取出并添加水印后,通过response.getOutputStream().write(image)将图片写出。

缓存Filter

第六章 过滤器Filter

具体代码详见《JavaWeb整合开发王者归来》

XSLT转换Filter

第六章 过滤器Filter

第六章 过滤器Filter

具体代码详见《JavaWeb整合开发王者归来》

文件上传Filter

第六章 过滤器Filter

1.UploadRequestWrapper代码如下

public class UploadRequestWrapper extends HttpServletRequestWrapper {

private static final String MULTIPART_HEADER = "Content-type";

// 是否是上传文件

private boolean multipart;

// map,保存所有的域

private Map<String, Object> params = new HashMap<String, Object>();

@SuppressWarnings("all")

public UploadRequestWrapper(HttpServletRequest request) {

super(request);

// 判断是否为上传文件

multipart = request.getHeader(MULTIPART_HEADER) != null

&& request.getHeader(MULTIPART_HEADER).startsWith(

"multipart/form-data");

if (multipart) {

try {

// 使用apache的工具解析

DiskFileUpload upload = new DiskFileUpload();

upload.setHeaderEncoding("utf8");

// 解析,获得所有的文本域与文件域

List<FileItem> fileItems = upload.parseRequest(request);

for (Iterator<FileItem> it = fileItems.iterator(); it.hasNext();) {

// 遍历

FileItem item = it.next();

if (item.isFormField()) {

// 如果是文本域,直接放到map里

params.put(item.getFieldName(), item.getString("utf8"));

} else {

// 否则,为文件,先获取文件名称

String filename = item.getName().replace("\\", "/");

filename = filename

.substring(filename.lastIndexOf("/") + 1);

// 保存到系统临时文件夹中

File file = new File(System

.getProperty("java.io.tmpdir"), filename);

// 保存文件内容

OutputStream ous = new FileOutputStream(file);

ous.write(item.get());

ous.close();

// 放到map中

params.put(item.getFieldName(), file);

}

}

} catch (Exception e) {

e.printStackTrace();

}

}

}

@Override

public Object getAttribute(String name) {

// 如果为上传文件,则从map中取值

if (multipart && params.containsKey(name)) {

return params.get(name);

}

return super.getAttribute(name);

}

@Override

public String getParameter(String name) {

// 如果为上传文件,则从map中取值

if (multipart && params.containsKey(name)) {

return params.get(name).toString();

}

return super.getParameter(name);

}

public static void main(String[] args) {

System.out.println(System.getProperties().toString().replace(", ",

"\r\n"));

}

}

以上代码自定义了request并重写其相关方法。并且判断request中的数据类型,如果是multipart,则把数据名和对应的值放入map集合。通过request.getParameter()和request.getAttribute()方法便可方便获取request的数据。