Engineering Full Stack Apps with Java and JavaScript
You may use request and response wrappers to extend the functionality of request or response objects. We can pass in our custom wrapper to a RequestDispatcher forward and include, and override any default behavior. For instance, we can provide an extra layer of buffer using a wrapper with its own internal buffer independent of the output stream supplied by the container. We can pass this to other components and once we finally verify the content, then we can write it into containers output stream and thus actually committing the response.
Wrappers were common to be used along with RequestDispatcher before the addition of filters to the Java EE specification. After the addition of filters to the servlet specification, wrappers should be now used with filters than directly with servlets using RequestDispatcher mechanism. Filters give more flexibility and can be easily added/removed just by changing the deployment descriptor configuration. We will see how to use wrappers with filters in another tutorial and see its benefits over this approach there.
Setting up the basic servlet
We will reuse the same FirstServlet and ForwardedServlet from our RequestDispatcher basic demo. We will rename our FirstServlet to FirstServletV2. We will also need to change the url-pattern, the java file name, constructor name.
In FirstServletV2, we will remove the initial getWriter part as we will be using an outputstream later and we cannot call both getWriter and getOutputStream.
In FirstServletV2, you may remove or comment out the RD for include, and uncomment both the forwards. We will also add a request attribute to keep a count of the number of forwards we do.
Similarly we will ForwardedServlet to ForwardedServletV2 to get and print the value of the new request attribute.
The doGet of FirstServletV2 will now look like:
System.out.println("FirstServletV2.doGet");
RequestDispatcher rd2 = getServletContext().getRequestDispatcher(
"/ForwardedServletV2");
request.setAttribute("CallNumber", "1");
rd2.forward(request, response);
request.setAttribute("CallNumber", "2");
rd2.forward(request, response);
The doGet of ForwardedServletV2 will now look like:
System.out.println("ForwardedServletV2.doGet");
PrintWriter out = response.getWriter();
out.println("<br>ForwardedServlet.doGet"
+ request.getAttribute("CallNumber"));
Now try deploying and executing the class. We are trying to forward twice and we will get the exception in the console log:
java.lang.IllegalStateException: Cannot forward after response has been committed
Creating our wrapper class
We will now create a wrapper class to override the default buffer with one of our own. We will override the below methods in our custom wrapper:
void flushBuffer() – We will not provide any implementation and hence the next component will not be able to write any data to the client.
PrintWriter getWriter() – We will create and return our own writer object and hence the next component will not be able to write directly to the response stream.
OutputStream getOutputStream() – We will create and return our own output stream.
We will also extend ServletOutputStream to create our own ServletOutputStream so that we can instantiate it within the getOutputStream above to provide our own output stream. We will override only its abstract write method to write to our own output stream.
ServletOutputStream has added two new methods isReady() and setWriteListener(WriteListener) servlet spec 3.1 (Java EE 7). We will simply implement them and provide no code. However if you are using a lower version of servlet spec, then you don’t need to implement these.
Code for our complete wrapper class is given below:
package com.javajee.rdmechanisms;
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
public class CustCommitHttpRespWrapper extends HttpServletResponseWrapper {
private ByteArrayOutputStream out;
private ServletOutputStream outBytes;
private PrintWriter outWriter;
public CustCommitHttpRespWrapper(HttpServletResponse wrapper) {
super(wrapper);
}
private class MyServletOutputStream extends ServletOutputStream {
public void write(int b) {
out.write(b);
}
// Below two methods isReady and setWriteListener
// were added in servlet spec 3.1 (Java EE 7).
@Override
public boolean isReady() {
return false;
}
@Override
public void setWriteListener(WriteListener arg0) {
}
}
@Override
public void flushBuffer() {
// Do nothing
}
@Override
public PrintWriter getWriter() {
if (outWriter != null) {
return outWriter;
}
if (outBytes != null) {
throw new IllegalStateException("getOut already invoked");
}
out = new ByteArrayOutputStream();
outWriter = new PrintWriter(out);
return outWriter;
}
@Override
public ServletOutputStream getOutputStream() {
if (outBytes != null) {
return outBytes;
}
if (outWriter != null) {
throw new IllegalStateException("getWriter was invoked");
}
out = new ByteArrayOutputStream();
outBytes = new MyServletOutputStream();
return outBytes;
}
public ByteArrayOutputStream getOut() {
return out;
}
}
This is not a perfect implementation is just provided to show the possibilities of things we can do with a wrapper.
Back to the basic servlet
In our FirstServletV2, we will create an instance of our wrapper class and pass it instead of original response:
CustCommitHttpRespWrapper wraper = new CustCommitHttpRespWrapper(response);
RequestDispatcher rd2 = getServletContext().getRequestDispatcher(
"/ForwardedServlet");
request.setAttribute("CallNumber", "1");
rd2.forward(request, wraper);
request.setAttribute("CallNumber", "2");
rd2.forward(request, wraper);
If you execute the program now, you will not get any response in the browser as we are not sending anything to client. However, you can see the flow in the console through our print statements as:
FirstServletV2.doGet
ForwardedServletV2.doGet1
ForwardedServletV2.doGet2
After forwarding, we will now add code to actually write it to the client along with some error handling as:
if (wraper.isCommitted()) {
return;
}
if (wraper.getOut() != null) {
wraper.getOut().writeTo(response.getOutputStream());
}
Now deploy and execute our servlet and you will get the output in the browser as:
ForwardedServlet.doGet1
And in the cosole you will still get the output as:
FirstServletV2.doGet
ForwardedServletV2.doGet1
ForwardedServletV2.doGet2
Previously this would have been thrown an error in the console. We can also now decide to send an error to client even after data is written to output. Previously exception would have been thrown only in the console after sending some data as after the data was written to the original output stream, there was no way to tell the client about the error.
FirstServletV2.java (initial)
package com.javajee.rdmechanisms;
import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;@WebServlet("/FirstServletV2")
public class FirstServletV2 extends HttpServlet {
private static final long serialVersionUID = 1L;public FirstServletV2() {
super();
}protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("FirstServletV2.doGet");RequestDispatcher rd2 = getServletContext().getRequestDispatcher(
"/ForwardedServletV2");
request.setAttribute("CallNumber", "1");
rd2.forward(request, response);
request.setAttribute("CallNumber", "2");
rd2.forward(request, response);
}
}
FirstServletV2.java (final)
package com.javajee.rdmechanisms;
import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/FirstServletV2")
public class FirstServletV2 extends HttpServlet {
private static final long serialVersionUID = 1L;public FirstServletV2() {
super();
}protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("FirstServletV2.doGet");CustCommitHttpRespWrapper wraper = new CustCommitHttpRespWrapper(response);
RequestDispatcher rd2 = getServletContext().getRequestDispatcher(
"/ForwardedServletV2");
request.setAttribute("CallNumber", "1");
rd2.forward(request, wraper);
request.setAttribute("CallNumber", "2");
rd2.forward(request, wraper);if (wraper.isCommitted()) {
return;
}if (wraper.getOut() != null) {
wraper.getOut().writeTo(response.getOutputStream());
}}
}
Instead of writing the internal buffer to the output stream, we may even discard the current buffer and write new data to client.
ForwardedServletV2.java
package com.javajee.rdmechanisms;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/ForwardedServletV2")
public class ForwardedServletV2 extends HttpServlet {
private static final long serialVersionUID = 1L;public ForwardedServletV2() {
super();
}protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("ForwardedServletV2.doGet"
+ request.getAttribute("CallNumber"));PrintWriter out = response.getWriter();
out.println("<br>ForwardedServlet.doGet"
+ request.getAttribute("CallNumber"));}
}
The wrapper complete code is already given above.
Eclipse Luna Java EE IDE for Web Developers and Apache Tomcat 8.0.18, using Servlet spec 3.1.