14 May 2015

Adding HTTP headers using Java HTTP web filters

A filter dynamically intercepts requests and responses to transform or use the information contained in the requests or responses. Filters typically do not themselves create responses, but instead provide universal functions that can be "attached" to any type of servlet or JSP page.
filters can be used to transform the response from a servlet or a JSP page.

The filter API is defined by the Filter, FilterChain, and FilterConfig interfaces in the javax.servlet package. You define a filter by implementing the Filter interface. A filter chain, passed to a filter by the container, provides a mechanism for invoking a series of filters. A filter config contains initialization data.
The most important method in the Filter interface is the doFilter method, which is the heart of the filter. Filers are header for :
  • Examines the request headers
  • Customizes the request object if it wishes to modify request headers or data or block the request entirely
  • Customizes the response object if it wishes to modify response headers or data
Response Header Filter:
This filter will add the configured list headers to response for mapped URLS.

Configuration file: WEB-INF\response-headers.xml

<?xml version="1.0" encoding="UTF-8" ?>
<response-header-mapper>
 <!-- generic rule for all html requests -->

 <mapping url="(.*).html">
  <header key="X-ServerName" value="MyServer" />
  <header key="Content-Type" value="text/html" />       
  <!-- cache all the html pages for one hour -->
  <header key="Cache-Control" value="private, max-age=3600" />
 </mapping>
 

 <!-- generic rule for all js requests -->
 <mapping url="(.*).js">
  <header key="X-ServerName" value="MyServer2" />
  <header key="Content-Type" value="text/javascript" />       
  <!-- cache all the js pages for 2 hour -->
  <header key="Cache-Control" value="private, max-age=7200" />
 </mapping>
</response-header-mapper>


In above configuration files we have applied header 'X-ServerName', 'Content-Type', 'Cache-Control' to URLS containing '(.*).html' pattern. So filter will add all these headers to html files

ResponseHeaderFilter.java

import java.io.*;
import java.util.*;
import java.util.regex.*;
import java.util.Map.Entry;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.xml.parsers.*;

import org.w3c.dom.*;
public class ResponseHeaderFilter implements Filter {
 HashMap<Pattern, LinkedHashMap<String, String>> headerPatternRules;

 /**
  * Create URL rules to Header mapping
  * It will create map with URL pattern as key
  * and value will be list of headers to apply
  *  
  * @param xmlFile relative path XML configuration file
  *
  * @return Rule to Header Mapping
  */
 public static HashMap<Pattern, LinkedHashMap<String, String>> getRules( String xmlFile) {
  HashMap<String, LinkedHashMap<String, String>> rules =
    new HashMap<String, LinkedHashMap<String, String>>();
  try {
   DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
   DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
   Document doc = dBuilder.parse(xmlFile);
   doc.getDocumentElement().normalize();
 
   NodeList mappings = doc.getElementsByTagName("mapping");
   for (int i = 0; i < mappings.getLength(); i++) {
    Node mapping = mappings.item(i);
    String urlPattern = mapping.getAttributes().getNamedItem("url").getNodeValue();
    NodeList headers = mapping.getChildNodes();
    LinkedHashMap<String, String> rule = rules.get(urlPattern);
    if (rule == null) {
     rule = new LinkedHashMap<String, String>();
    }
    for (int j = 0; j < headers.getLength(); j++) {
     Node header = headers.item(j);
     if (header.getNodeType() == Node.ELEMENT_NODE) {
      String headerName = header.getAttributes()
        .getNamedItem("key").getNodeValue();
      String headerValue = header.getAttributes()
        .getNamedItem("value").getNodeValue();
      rule.put(headerName, headerValue);
     }
    }
    rules.put(urlPattern, rule);
   }

   System.out.println("*** Rules: " + rules);
   return toRulePattern(rules);
  } catch (Exception e) {
   e.printStackTrace();
  }
  return null;
 }

 /**
  * Compile all the URL patterns
  *
  * @param rules Map of URL pattern and associated List of headers 
  *
  * @return Map of compiled URL pattern and associated List of
  * headers to add
  */
 public static HashMap<Pattern, LinkedHashMap<String, String>> toRulePattern(
   HashMap<String, LinkedHashMap<String, String>> rules) {
  HashMap<Pattern, LinkedHashMap<String, String>> patterRules =
    new HashMap<Pattern, LinkedHashMap<String, String>>();
  for (Entry<String, LinkedHashMap<String, String>> e : rules.entrySet()) {
   patterRules.put(Pattern.compile(e.getKey()), e.getValue());
  }
  return patterRules;
 }

 /**
  * Read configuration file and build the URL pattern to Header Map 
  *
  */
 public void init(FilterConfig fc) throws ServletException {
  String confFile = fc.getInitParameter("response-header-file").trim();
  String fullConfigFilePath = fc.getServletContext().getRealPath("/WEB-INF/" + confFile);
  System.out.println("** ResponseHeaderFilter: Loading configuration from: "
      + fullConfigFilePath);
  headerPatternRules = getRules(fullConfigFilePath);
 }

 /**
  * Filter method
  *
  * Checks request URL against Set of Patters from the list of URL pattern Map and
  * applies the headers associated.
  * 
  */
 public void doFilter(ServletRequest req, ServletResponse res, FilterChain fc)
   throws IOException, ServletException {
  HttpServletResponse response = (HttpServletResponse) res;
  String requestURL = ((HttpServletRequest) req).getRequestURI();

  for (Entry<Pattern, LinkedHashMap<String, String>> rule : headerPatternRules.entrySet()) {
         Matcher matcher = rule.getKey().matcher(requestURL);
         if (matcher.matches()) {
          LinkedHashMap<String, String> headers = rule.getValue();
    for (Entry<String, String> entry : headers.entrySet()) {
     response.setHeader(entry.getKey(), entry.getValue());
    }
         }
  }
  fc.doFilter(req, res);
 }

 public void destroy() {
 }
}


In init() method we read the configuration file (file name is defined in web.xml init-param of filter), then generates rules Map. Key of map is URL pattern and value is list of headers to apply.
In doFilter() method request URI is checked agaist all the rules defined in the configuration for matching of pattern. If match is found the list of header associated with pattern are added to response.
compile this java file and copy it in WEB-INF\classes folder

Modify WEB-INF\web.xml add Filter declaration and Mapping.

<filter>
 <filter-name>ResponseHeaderFilter</filter-name>
 <filter-class>ResponseHeaderFilter</filter-class>
 <init-param>
  <param-name>response-header-file</param-name>
  <param-value>response-headers.xml</param-value>
 </init-param>
</filter>

<filter-mapping>
 <filter-name>ResponseHeaderFilter</filter-name>
 <url-pattern>*</url-pattern>
</filter-mapping>

 

No comments:

Post a Comment