Modify html response using filter

There may be instances where you want to modify the response before it is written to the client. In this tutorial we will show how to write a regex to append to a html element.

Detailed Video Notes

I was working on a project where we wanted to replace the contents of a html heading element. We wanted to capture the html before the response was written and use a regular expression to swap out the contents. The approach taken was to modify the response using a J2EE filters. In order to perform this operation you need to create a HttpServletResponseWrapper class and pass it in the doFilter method which overrides the getWriter or getOutputStream method. Let's see it in action.

Project set up

[0:26]

Since spring boot makes it easy to set and run a java project, lets generate a project from the spring initializer website by selecting web and velocity as a template engine. Next we can create a template to render, a @Controller to return the view, a filter and code to register the filter. One thing to note, the filter will be registered using servlet 3.0 and spring mechanisms. In the instance you are not using spring or servlet 3.0 to register your filter, you could register it within the web.xml. In our template our use case will be to append text to html heading elements.

Let's run the results to validate our set up is working.

Template - index.vm

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Level up lunch - replace html</title>
</head>
<body>

<h2>Hello world</h2>

</body>
</html>

BaseController

@Controller
public class BaseController {

    @RequestMapping(value = "/", method=RequestMethod.GET)
    public String baseUrl() {
        return "index";
    }
}

Create J2EE Filter

public class ReplaceHTMLFilter implements Filter {

    @Override
    public void destroy() {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain filterChain) throws IOException, ServletException {

        filterChain.doFilter(request, capturingResponseWrapper);
    }

    @Override
    public void init(FilterConfig arg0) throws ServletException {
    }

}

Register filter with spring

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public ReplaceHTMLFilter replaceHtmlFilter() {
        return new ReplaceHTMLFilter();
    }
}

or

<filter>
   <filter-name>Compression Filter</filter-name>
   <filter-class>CompressionFilter</filter-class>
</filter>

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

Creating response wrapper

[1:0]

In order to capture the response before it is written, we need to create a HttpServletResponseWrapper that overrides the getWriter or getOutputStream method.

public class HtmlResponseWrapper extends HttpServletResponseWrapper {

    private final ByteArrayOutputStream capture;
    private ServletOutputStream output;
    private PrintWriter writer;

    public HtmlResponseWrapper(HttpServletResponse response) {
        super(response);
        capture = new ByteArrayOutputStream(response.getBufferSize());
    }

    @Override
    public ServletOutputStream getOutputStream() {
        if (writer != null) {
            throw new IllegalStateException(
                    "getWriter() has already been called on this response.");
        }

        if (output == null) {
            output = new ServletOutputStream() {
                @Override
                public void write(int b) throws IOException {
                    capture.write(b);
                }

                @Override
                public void flush() throws IOException {
                    capture.flush();
                }

                @Override
                public void close() throws IOException {
                    capture.close();
                }

                @Override
                public boolean isReady() {
                    return false;
                }

                @Override
                public void setWriteListener(WriteListener arg0) {
                }
            };
        }

        return output;
    }

    @Override
    public PrintWriter getWriter() throws IOException {
        if (output != null) {
            throw new IllegalStateException(
                    "getOutputStream() has already been called on this response.");
        }

        if (writer == null) {
            writer = new PrintWriter(new OutputStreamWriter(capture,
                    getCharacterEncoding()));
        }

        return writer;
    }

    @Override
    public void flushBuffer() throws IOException {
        super.flushBuffer();

        if (writer != null) {
            writer.flush();
        } else if (output != null) {
            output.flush();
        }
    }

    public byte[] getCaptureAsBytes() throws IOException {
        if (writer != null) {
            writer.close();
        } else if (output != null) {
            output.close();
        }

        return capture.toByteArray();
    }

    public String getCaptureAsString() throws IOException {
        return new String(getCaptureAsBytes(), getCharacterEncoding());
    }

}

Modify filter with regex

[1:17]

The wrapper is then passed to the doFilter method of the filter chain which gives us the ability to modify capture the response as a string. Next we want to modify the filter to check capture the response as a string if the contentType is HTML. If it is we will execute a regex to find all the instances of <h2> and replace them with <h3> and append the text "HTML replaced".

public class ReplaceHTMLFilter implements Filter {

    @Override
    public void destroy() {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain filterChain) throws IOException, ServletException {

        HtmlResponseWrapper capturingResponseWrapper = new HtmlResponseWrapper(
                (HttpServletResponse) response);

        filterChain.doFilter(request, capturingResponseWrapper);

        if (response.getContentType() != null
                && response.getContentType().contains("text/html")) {

            String content = capturingResponseWrapper.getCaptureAsString();

            // replace stuff here
            String replacedContent = content.replaceAll(
                    "<h2[^>]*>(.*?)</h2>",
                    "<h3>$1 - HTML replaced</h3>");

            System.out.println(replacedContent);

            response.getWriter().write(replacedContent);

        }

    }

    @Override
    public void init(FilterConfig arg0) throws ServletException {
    }

}

Inspecting the output

[1:42]

Lets run the example and inspect the output.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Level up lunch - replace html</title>
</head>
<body>

<h3>Hello world - HTML replaced</h3>

</body>
</html>

While this is one approach to replacing values within your template, if you see repeated pattern you may want to consider a different approach as it could lead confusion and long term maintainability issues.

Thanks for joining in today's level up, have a great day!