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!