首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Servlet Filter 實戰精粹:禁止訪問、內容替換與 GZIP 壓縮三大經典模式

     編輯:Java 相關知識

Servlet Filter 作為請求/響應處理管道的核心組件,能在不修改 Servlet/JSP 的前提下實現橫切關注點。本文通過三個生產級案例——IP/域名黑名單阻斷、響應內容動態替換、GZIP 壓縮傳輸,完整展示 Filter 開發模式、HttpServletResponseWrapper 緩存響應技巧及 web.xml 配置要點。

核心 API 速覽

  • doFilter(req, res, chain):核心邏輯,chain.doFilter() 前處理請求,後處理響應。
  • init(FilterConfig):讀取初始化參數(如黑名單列表)。
  • destroy():釋放資源。

若需中斷鏈路:直接 res.sendRedirect() 或自行輸出響應,不調用 chain.doFilter()。

案例一:禁止站點過濾器(黑名單阻斷)

場景:根據遠程主機或 Referer 域名攔截惡意訪問。

public class BannedAccessFilter implements Filter {
  private Set<String> bannedSites = new HashSet<>();

  public void init(FilterConfig config) {
    String param = config.getInitParameter("bannedSites");
    new StringTokenizer(param).forEachRemaining(t -> bannedSites.add(t));
  }

  public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
    HttpServletRequest request = (HttpServletRequest) req;
    String remote = request.getRemoteHost();
    String referer = getReferringHost(request.getHeader("Referer"));

    if (bannedSites.contains(remote) || bannedSites.contains(referer)) {
      showWarning((HttpServletResponse) res, bannedSites.contains(remote) ? remote : referer);
    } else {
      chain.doFilter(req, res);
    }
  }
  // getReferringHost() 解析 Referer 域名
  // showWarning() 輸出 HTML 禁止頁面
}

web.xml:<param-value>www.evil.com 127.0.0.1</param-value>,<url-pattern>/*</url-pattern> 全局生效。

黑名單攔截測試結果

案例二:替換過濾器(響應內容重寫)

難點:chain.doFilter() 執行後響應已提交,無法直接修改。

解法:自定義 CharArrayWrapper extends HttpServletResponseWrapper,重寫 getWriter() 返回寫入 CharArrayWriter 的 PrintWriter,攔截所有輸出。doFilter 中:

  1. 包裝 response → wrapper
  2. chain.doFilter(req, wrapper)
  3. String content = wrapper.toString() 獲取完整輸出
  4. 字符串替換 content.replace(target, replacement)
  5. 更新 Content-Length,寫入原 response.getWriter()
public abstract class ReplaceFilter implements Filter {
  public void doFilter(...) {
    CharArrayWrapper wrapper = new CharArrayWrapper((HttpServletResponse) response);
    chain.doFilter(request, wrapper);
    String out = wrapper.toString();
    out = FilterUtils.replace(out, getTargetString(), getReplacementString());
    response.setContentLength(out.length());
    response.getWriter().write(out);
  }
  protected abstract String getTargetString();
  protected abstract String getReplacementString();
}

// 具體實現
public class ReplaceSiteNameFilter extends ReplaceFilter {
  public String getTargetString() { return "google.com.cn"; }
  public String getReplacementString() { return "baidu.com"; }
}

web.xml:<url-pattern>/login.jsp</url-pattern> 精確匹配。

替換前頁面 替換後頁面

案例三:壓縮過濾器(GZIP 傳輸優化)

原理:瀏覽器發送 Accept-Encoding: gzip → 服務端壓縮響應體 → 設置 Content-Encoding: gzip → 瀏覽器自動解壓。

public class CompressionFilter implements Filter {
  public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
    HttpServletRequest request = (HttpServletRequest) req;
    HttpServletResponse response = (HttpServletResponse) res;

    if (!isGzipSupported(request)) {
      chain.doFilter(req, res); // 不支持直通
      return;
    }
    response.setHeader("Content-Encoding", "gzip");
    CharArrayWrapper wrapper = new CharArrayWrapper(response);
    chain.doFilter(request, wrapper); // 收集輸出
    char[] chars = wrapper.toCharArray();

    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    try (GZIPOutputStream gzip = new GZIPOutputStream(bos);
         OutputStreamWriter writer = new OutputStreamWriter(gzip)) {
      writer.write(chars);
    }
    response.setContentLength(bos.size());
    response.getOutputStream().write(bos.toByteArray());
  }
  private boolean isGzipSupported(HttpServletRequest req) {
    String enc = req.getHeader("Accept-Encoding");
    return enc != null && enc.contains("gzip");
  }
}

測試 Servlet:LongServlet 輸出 1 萬行重複文本,GZIP 壓縮比超 300:1。

<filter-mapping>
  <filter-name>CompressionFilter</filter-name>
  <servlet-name>LongServlet</servlet-name>
</filter-mapping>

最佳實踐提醒

  • CharArrayWrapper 適用於文本響應;二進制流(圖片/下載)需用 ByteArrayOutputStream 包裝 ServletOutputStream。
  • 壓縮過濾器應排除已壓縮資源(圖片、視頻、PDF),避免雙重壓縮浪費 CPU。
  • 替換過濾器慎用於大響應體,內存壓力大時考慮流式處理(如 ServletOutputStream 過濾鏈)。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved