This is the 25th day of my participation in the August Challenge

The principle and common usage of Filter Filter in Java Web Servlet are introduced in detail.

Filters are part of the Servlet specification and have been available since version 2.3. It is used to filter and filter requests to or responses from resources.

When filters exist, requests from clients must pass through filters before reaching Web resources. In the case of the returned response, the response also passes through the filter before it reaches the Web server and responds to the client.

Filters can do many things, common ones include:

  1. Filter dirty sensitive characters (green sensitive strings);
  2. Avoid Chinese garbled characters (unified setting of request and response codes);
  3. Permission authentication (only requests with a specified Session or Cookie can access resources);
  4. For automatic login;

1 Filter interface

Filters correspond to the Javax.servlet.filter interface in Java, and that’s all. Classes that implement the Filter interface can be called filters, that’s all.

There are three abstract methods in the Filter interface, with init and destroy as the Filter application cycle methods!

public interface Filter {

    default public void init(FilterConfig filterConfig) throws ServletException {}

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException;

    default public void destroy(a) {}}Copy the code

1.1 Filter life cycle

  1. The birth ofThe filter instance is instantiated when the Web application is loaded and initialized by calling the init method. The servlet container only calls Filter after it is instantiatedinitMethod once. The init method must complete successfully before the filter can be asked to perform any filtering work. Unlike servlets, filters are all initialized immediately.
  2. Each filter passes a FilterConfig object in the init initialization method from which its initialization parameters can be retrieved. You can also get the ServletContext object through FilterConfig, which you can use to load resources for filtering tasks, for example.
  3. To survive: Consistent with the application lifecycle. It’s singleton in memory. Void doFIlter(Request, Response.chain) is invoked for each access to a resource within the interception range.
  4. deathWhen Filter is called when the application is uninstalled, the destroy method is called, which is called only once.

1.2 doFilter Filtering Method

Filter indoFilterMethod to perform filtering operations.

The doFilter method has a FilterChain parameter object that is created by the Servlet container and passed to the developer. FilterChain represents a FilterChain at the end of which the resource requested by the client is located.

In the current filter, you can use the FilterChain#doFilter method to call the next filter in the chain if you compound the filter rules, or to call the resource at the end of the chain if the calling filter is the last filter in the chain.

In other words, a Web application can have multiple filters, and they will form a chain of filters in a certain order. At the end of the chain is the resource to be accessed. When a request comes, it must pass through all the filters to access the real resource.

2 Filter usage

Developing a Filter is as simple as implementing the Filter interface and implementing the doFilter method.

public class FirstFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) {
        System.out.println("init");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("Prior to release");
        HttpServletRequest httpServletRequest= (HttpServletRequest) request;
        // Get the URL of the current request
        System.out.println(httpServletRequest.getRequestURL());
        // Release, invoke the next filter or access the resource
        chain.doFilter(request, response);
        System.out.println("After release");
    }

    @Override
    public void destroy(a) {
        System.out.println("destroy"); }}Copy the code

Then we also need to deploy the filter. Typically, the Filter Filter is configured in the deployment descriptor of a Web application, the web.xml file, which is similar to a Servlet.

First we define a Filter:

<filter>
    <filter-name>FirstFilter</filter-name>
    <filter-class>com.example.filter.FirstFilter</filter-class>
    <! Init (FilterConfig) ¶
    <init-param>
        <param-name>aaa</param-name>
        <param-value>bbb</param-value>
    </init-param>
    <init-param>
        <param-name>ccc</param-name>
        <param-value>ddd</param-value>
    </init-param>
</filter>
Copy the code

A
tag defines a filter,
represents the name of the current filter,
represents the class full path name of the current filter,
represents the initialization parameter of the current filter, These parameters can be obtained from the FilterConfig object as a parameter in the init method.

Then we need to define which resources or servlets this filter can work on!

<filter-mapping>
    <filter-name>FirstFilter</filter-name>
    <! Intercepting resource URL at specified path -->
    <url-pattern>/aa/*</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>FirstFilter</filter-name>
    <! -- Intercepting specified ServletName-->
    <servlet-name>Servlet2</servlet-name>
    <! -- Scheduler -->
    <! --<dispatcher>REQUEST</dispatcher>-->
</filter-mapping>
Copy the code

Multiple filter mappings can be configured for a filter using multiple
tags, or multiple mappings can be added to a
tag

  1. <filter-name/>Specifies the name of a filter
  2. <url-pattern/>Specify the resource path URL that the filter intercepts, “/“Indicates that all Web resources need to access this filter.”XXX “intercepts requests to access resources with the XXX suffix.
  3. <servlet-name/>Specifies the name of a Servlet that the filter intercepts.
  4. <dispatcher/>Specify the scheduling mode to be applied to filter interceptions. There are five optional configurations: FORWARD, REQUEST, INCLUDE, ASYNC, and ERROR.
    1. FORWARDThe filter is called if the target resource is accessed through the Forward () method of the RequestDispatcher, otherwise it is not called.
    2. REQUESTThe Web container invokes the filter when the user accesses the resource directly through the normal path. This filter is not invoked if the target resource is accessed through the RequestDispatcher’s include() or forward() methods. This is the default mode.
    3. INCLUDEThe filter is called if the target resource is accessed through the RequestDispatcher’s include() method, otherwise it is not called.
    4. ERROR: This filter is invoked if the target resource is invoked through a declarative exception handling mechanism. Otherwise, the filter is not called.
    5. SYNC: means that the filter will be applied under a call from AsyncContext.

In Servlet 3.0 and later, filters are configured using the @webFilter annotation directly on the Filter implementation class, reducing the complexity of configuration files. Such as:

@WebFilter(urlPatterns = "/aa/*",servletNames = "Servlet2")
Copy the code

Here are three servlets for a simple test:

@WebServlet(name = "Servlet1", value = "/aa/Servlet1")
public class Servlet1 extends HttpServlet {

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        System.out.println("Request arrived");
        response.setContentType("text/html; charset=utf-8");
        PrintWriter out = response.getWriter();
        out.println("Servlet1"); }}@WebServlet(name = "Servlet2", value = "/bb/Servlet2")
public class Servlet2 extends HttpServlet {

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        System.out.println("Request arrived");
        response.setContentType("text/html; charset=utf-8");
        PrintWriter out = response.getWriter();
        out.println("Servlet2"); }}@WebServlet(name = "Servlet3", value = "/bb/Servlet3")
public class Servlet3 extends HttpServlet {

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        System.out.println("Request arrived");
        response.setContentType("text/html; charset=utf-8");
        PrintWriter out = response.getWriter();
        out.println("Servlet3"); }}Copy the code

If we use the filter Settings above, Servlet1 and Servlet2 will pass through the filter while Servlet3 will not pass through the filter, and the console output will be:

If we comment out the chain-dofilter method, then Servlet1 and Servlet2 will have no output on the page, no output console will have no “request arrived” string because the request is blocked, and Servlet3 will have normal access:

3 Filter execution sequence

In the simple test above, we might find that the logic before chain-dofilter is executed before the request reaches the specified Servlet, and the logic after chain-dofilter is executed after the request reaches the specified Servlet and leaves.

The complete flow sequence for the filter chain looks like this: The client sends an HTTP request to the Web server, and the Web server forms a Filter chain by finding the corresponding Filter responsible for the URL of the request, and then conducts filtering operations from the first Filter, that is, calling filter.dofilter method, the logic of which is written by the developer. When the current request meets the requirements of the current filter or the filtering operation is completed, the chain-dofilter method should be called for clearance, which in turn calls the doFilter method of the next filter in the chain to continue filtering. If the current filter is the last filter in the filter chain, The chain-dofilter method will perform the resource access operation, and when it’s done, it will return in reverse order as the original filter was called, followed by the code following the chain-dofilter method. Finally, the response results are delivered to the Web server, which returns the response to the client.

3.1 Multiple Filters

If multiple filters exist in a request, the execution sequence of the filters is determined by the first
defined in the web. XML file.

As follows, two filters:

public class Filter1 implements Filter {
    @Override
    public void init(FilterConfig filterConfig) {
        System.out.println("Filter1-init");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("-----Filter1 before release -----");
        HttpServletRequest httpServletRequest= (HttpServletRequest) request;
        // Get the URL of the current request
        System.out.println(httpServletRequest.getRequestURL());
        // Release, invoke the next filter or access the resource
        chain.doFilter(request, response);
        System.out.println("-----Filter1 after release -----");
    }

    @Override
    public void destroy(a) {
        System.out.println("destroy"); }}public class Filter2 implements Filter {
    @Override
    public void init(FilterConfig filterConfig) {
        System.out.println("Filter2-init");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("-----Filter2 before release -----");
        HttpServletRequest httpServletRequest= (HttpServletRequest) request;
        // Get the URL of the current request
        System.out.println(httpServletRequest.getRequestURL());
        // Release, invoke the next filter or access the resource
        chain.doFilter(request, response);
        System.out.println("-----Filter2 after release -----");
    }

    @Override
    public void destroy(a) {
        System.out.println("destroy"); }}Copy the code

Here is the mapping configuration:

<filter>
    <filter-name>Filter1</filter-name>
    <filter-class>com.example.filter.Filter1</filter-class>
</filter>
<filter>
    <filter-name>Filter2</filter-name>
    <filter-class>com.example.filter.Filter2</filter-class>
</filter>

<! --Filter2 mapping before -->
<filter-mapping>
    <filter-name>Filter2</filter-name>
    <! -- Intercepting specified ServletName-->
    <servlet-name>Servlet2</servlet-name>
</filter-mapping>
<filter-mapping>
    <filter-name>Filter1</filter-name>
    <! Intercepting resource URL at specified path -->
    <url-pattern>/aa/*</url-pattern>
    <! -- Intercepting specified ServletName-->
    <servlet-name>Servlet2</servlet-name>
</filter-mapping>
<filter-mapping>
    <filter-name>Filter2</filter-name>
    <! Intercepting resource URL at specified path -->
    <url-pattern>/aa/*</url-pattern>
</filter-mapping>
Copy the code

You can see that Filter2 mapping comes first, so Filter2 will be executed first, but when the resource is returned, it will be executed in reverse order!

If we access Servlet1 and Servlet2, we get the following:

If configured via annotations, compare the string priorities of urlPatterns.

4 Application of Filter

Simple applications of Filter include:

  1. You can decide whether to call based on criteria in Filterchain.doFilter(request,response)Method, that is, whether to make the target resource accessible.
  2. Request \ Response can be preprocessed before accessing the target resource.
  3. After the target resource is accessed, the execution results of the target resource can be captured to achieve some special functions.

4.1 Dirty Word Filter

Profanity filter that replaces the profanity in the argument with **.

/** * profanity filter to block all access paths **@author lx
 */
@WebFilter("/*")
public class DirtyWordsFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) {
        System.out.println("DirtyWordsFilter-init");
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {

        HttpServletRequest request;
        HttpServletResponse response;
        try {
            request = (HttpServletRequest) req;
            response = (HttpServletResponse) resp;
        } catch (Exception e) {
            throw new RuntimeException("non-http request or response");
        }
        // Pass a decorator class whose getParameter filters out some dirty words
        DWHttpServletRequest dwrequest = new DWHttpServletRequest(request);
        chain.doFilter(dwrequest, response);
    }


    @Override
    public void destroy(a) {
        System.out.println("destroy"); }}/** * decorates the class to receive a Request object */
class DWHttpServletRequest extends HttpServletRequestWrapper {
    /** ** ** /
    private String[] strs = {"He he"."Whoever whoever"."The beast"."Animal"."Silly B"};

    public DWHttpServletRequest(HttpServletRequest request) {
        super(request);
    }

    /** * enhances the getParameter method * 

* to replace the profanity in the parameter with ** before returning */

@Override public String getParameter(String name) { String value = super.getParameter(name); if (value == null) { return value; } for (String s : strs) { // Replace the profanity with ** to return value = value.replace(s, "* *"); } returnvalue; }}Copy the code

Test the Servlet:

@WebServlet("/DirtyWordsServlet")
public class DirtyWordsServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String name = req.getParameter("name");
        resp.setContentType("text/html; charset=utf-8");
        PrintWriter out = resp.getWriter();
        out.println("<h1>" + name + "</h1>"); }}Copy the code

Try to request http://localhost:8081/filter/DirtyWordsServlet? Name = kun kun, the result is as follows:

4.2 Encoding Filter

SetCharacterEncoding is also required for the PARAMETERS of the POST request, and the setCharacterEncoding is also required for the parameters of the POST request. The setCharacterEncoding is also required for the parameters of the POST request. Very troublesome.

Now that we have the Filter Filter, we can use a Filter to solve the garble problem of all GET requests, which involves dynamic proxy applications (Tomcat8 and above are not applicable, because Tomcat8 uses UTF-8 decoding for urls by default and can directly retrieve normal parameter values).

Post request encoding and response encoding can also be set uniformly, eliminating the need to set request encoding or response encoding in individual servlets.

The CharacterEncodingFilter in Spring MVC is an implementation of an encoding filter!

@WebFilter("/*")
public class EncodingFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) {
        System.out.println("EncodingFilter-init");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // Convert request and Response to HTTP
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;

        /* * Universal encoding Settings */
        
        // Resolve garbled parameters in the POST request
        req.setCharacterEncoding("UTF-8");
        
        // Set the response code uniformly
        resp.setContentType("text/html; charset=UTF-8");

        // Use JDK dynamic proxy to dynamically enhance the getParameter(name) method of req objects
        // Fix tomcat8 + get request URL garbled (tomcat8 + don't need it)
        HttpServletRequest myReq = (HttpServletRequest) Proxy.newProxyInstance(HttpServletRequest.class.getClassLoader(), new Class[]{HttpServletRequest.class}, (proxy, method, args) -> {

            Object obj;
            // if the getParameter method is called
            if ("getParameter".equalsIgnoreCase(method.getName())) {
                // Get the request method for this time
                String md = req.getMethod();
                // Resolve get request submission parameters garbled problem, dynamic enhancement
                if ("get".equalsIgnoreCase(md)) {
                    // Call the getParameter method of the target object
                    String v = (String) method.invoke(req, args);
                    // Transcode the obtained results (versions after Tomcat8 do not require this)
                    return new String(v.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
                }
                // Other requests
                obj = method.invoke(req, args);
            }
            // If other methods are called, call directly from the target object
            else {
                obj = method.invoke(req, args);
            }
            return obj;
        });
        // Release the proxy object
        chain.doFilter(myReq, response);

    }


    @Override
    public void destroy(a) {
        System.out.println("destroy"); }}Copy the code

4.3 Counting THE Number of IP Access Counts

IP tools:

/ * * *@author lx
 */
public class IpUtil {

    /** * Obtain the user's real IP address **@paramRequest request *@return ip
     */
    public static String getIpAddr(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        System.out.println("x-forwarded-for ip: " + ip);
        if(ip ! =null&& ip.length() ! =0&&!"unknown".equalsIgnoreCase(ip)) {
            // There are multiple IP addresses after multiple reverse proxies. The first IP address is the real ONE
            if (ip.contains(",")) {
                ip = ip.split(",") [0]; }}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
            System.out.println("Proxy-Client-IP ip: " + ip);
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
            System.out.println("WL-Proxy-Client-IP ip: " + ip);
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
            System.out.println("HTTP_CLIENT_IP ip: " + ip);
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
            System.out.println("HTTP_X_FORWARDED_FOR ip: " + ip);
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
            System.out.println("X-Real-IP ip: " + ip);
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
            System.out.println("getRemoteAddr ip: " + ip);
        }
        System.out.println("Obtain client IP address:" + ip);
        returnip; }}Copy the code

IP statistics filter:

/** * Count the number of accesses by IP address **@author lx
 */
@WebFilter("/*")
public class IPCountFilter implements Filter {
    private FilterConfig config;

    /** * Initializes a set of maps in the init method to store the IP addresses and the number of times they were accessed. The map is stored in the ServletContext domain object
    @Override
    public void init(FilterConfig config) {
        this.config = config;
        // Prepare an empty set of maps:
        Map<String, Integer> map = new HashMap<>();
        // Store the map collection in the ServletContext domain object:
        ServletContext context = config.getServletContext();
        // Save to:
        context.setAttribute("map", map);
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        /* * development steps: * 1: obtain the current requested IP address: * 2: query from a map set, * if not found, save 1 in. * If found, store the value after +1. * 3: display on the page */
        // cast:
        HttpServletRequest request;
        HttpServletResponse response;
        try {
            request = (HttpServletRequest) req;
            response = (HttpServletResponse) res;
        } catch (ClassCastException e) {
            throw new ServletException("non-HTTP request or response");
        }
        // Obtain the IP address
        String ip = request.getRemoteAddr();

        // Get the map set from the context:
        ServletContext context = config.getServletContext();
        Map<String, Integer> map = (Map<String, Integer>) context.getAttribute("map");

        // Get the value of IP from the map set, and record the number of accesses
        Integer count = map.computeIfAbsent(ip, s -> 0);

        // Store the values back into the map collection
        map.put(ip, ++count);
        // Put the map collection back into the ServletContext:
        context.setAttribute("map", map);
        / / release
        chain.doFilter(request, response);
    }

    @Override
    public void destroy(a) {}}Copy the code

IPCountServlet:

@WebServlet("/IPCountServlet")
public class IPCountServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html; charset=utf-8");
        PrintWriter out = resp.getWriter();

        ServletContext servletContext = getServletContext();
        // Get the map set from the context:
        Map<String, Integer> map = (Map<String, Integer>) servletContext.getAttribute("map");
        for (Map.Entry<String, Integer> stringIntegerEntry : map.entrySet()) {
            out.println("IP: " + stringIntegerEntry.getKey() + "The number of visits is:"+ stringIntegerEntry.getValue()); }}}Copy the code

Note that with localhost, you might get an IPV6 formal address of 0:0:0:0:0:0:0:0:1, which corresponds to 127.0.0.1 for ipv4, or the localhost.

4.4 Disabling dynamic Resource Caching filters

Assume the dynamic resource access paths are “/Servlet” and “.jsp “. Dynamic resources such as servlets and JSPS should not be cached by the browser because the response results can change at any time.

@WebFilter(urlPatterns = {"/servlet/*", "*.jsp"})
public class NocacheFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) {
        System.out.println("NocacheFilter-init");
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
        // cast:
        HttpServletRequest request;
        HttpServletResponse response;
        try {
            request = (HttpServletRequest) req;
            response = (HttpServletResponse) resp;
        } catch (ClassCastException e) {
            throw new ServletException("non-HTTP request or response");
        }

        // Set the expiration time of dynamic resources and the browser does not cache them
        response.setHeader("Expires"."1");
        response.setHeader("Cache-Control"."no-cache");
        response.setHeader("Pragma"."no-cache");

        / / release:
        chain.doFilter(request, response);
    }


    @Override
    public void destroy(a) {
        System.out.println("destroy"); }}Copy the code

4.5 HTML tag filters

@WebFilter("/*")
public class HTMLFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) {
        System.out.println("HTMLFilter-init");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // Enhance objects
        HTMLHttpServlet htmlHttpServlet = new HTMLHttpServlet((HttpServletRequest) request);
        / / release
        chain.doFilter(htmlHttpServlet, response);

    }

    @Override
    public void destroy(a) {
        System.out.println("destroy"); }}/** * HTML tag filter, enhanced class */
class HTMLHttpServlet extends HttpServletRequestWrapper {

    /**
     * Constructs a request object wrapping the given request.
     *
     * @param request the {@link HttpServletRequest} to be wrapped.
     * @throws IllegalArgumentException if the request is null
     */
    public HTMLHttpServlet(HttpServletRequest request) {
        super(request);
    }

    /** * enhances the getParameter method */
    @Override
    public String getParameter(String name) {
        String value = super.getParameter(name);
        return filter(value);
    }

    /** * Tomcat: specified message string for * <p> * Filter the specified message string for characters that are sensitive * in HTML. This avoids potential attacks caused by including JavaScript * codes in the request URL that is often reported in error messages. * *@param message The message string to be filtered
     * @return the filtered version of the message
     */
    public static String filter(String message) {

        if (message == null)
            return null;

        char content[] = new char[message.length()];
        message.getChars(0, message.length(), content, 0);
        StringBuilder result = new StringBuilder(content.length + 50);
        for (char c : content) {
            switch (c) {
                case '<':
                    result.append("&lt;");
                    break;
                case '>':
                    result.append("&gt;");
                    break;
                case '&':
                    result.append("&amp;");
                    break;
                case '"':
                    result.append("&quot;");
                    break;
                default: result.append(c); }}returnresult.toString(); }}Copy the code

If you need to communicate, or the article is wrong, please leave a message directly. In addition, I hope to like, collect, pay attention to, I will continue to update a variety of Java learning blog!