Engineering Full Stack Apps with Java and JavaScript
RequestDispatcher mechanism used for forwarding to and including other resources. We will understand the basics of RequestDispatcher mechanism using a simple exercise.
Before proceeding, you need to configure your eclipse and tomcat. I am using eclipse Eclipse Java EE IDE for Web Developers version: Luna and Apache Tomcat version 8.0.18. Apache Tomcat version 8.0.18 support Servlet Spec 3.1, JSP Spec 2.3, EL Spec 3.0, WebSocket spec 1.1 and support java version 7 and above.
Create a Dynamic Web Project in eclipse and name it as RDMechanisms.
Leave the defaults in first two pages of the wizard and click next.
In the Web Module page, select ‘Generate web.xml deployment descriptor’ and
click Finish.
In the src folder under Java Resources, create three servlets FirstServlet, ForwardedServlet and IncludedServlet with the package com.javajee.rdmechanisms, leaving all options as default.
I will create the three servlets using the New > Servlet option of the eclipse and selecting only doGet method for implementation:
FirstServlet, which is the initial servlet that we will run.
IncludedServlet
ForwardedServlet
Note:
Eclipse create servlets using @WebServlet annotation (e.g. @WebServlet("/FirstServlet"), @WebServlet("/IncludedServlet") and @WebServlet("/ForwardedServlet"). However if we need to use the getNamedDispatcher method, we sould need to give a logical name for the servlet either adding to current annotation or by specifying a name in the web.xml through the <servlet-name> sub element of the <servlet> element.
We will also add a console print statement at the beginning of the doGet method of each servlet to know when they were called (e.g. System.out.println("FirstServlet.doGet");).
We will also create a PrintWriter out object by calling getWriter() on the response object and also print using that object (e.g. out.println("<br>FirstServlet.doGet");) so that we can know what all details are actually sent back to client. Also note the additional <br> tag inside the println sent back to client. Client is a browser and will only understand html, so to show the output in different lines, we need to use the html <br> tag.
FirstServlet's doGet initially
System.out.println("FirstServlet.doGet");
PrintWriter out = response.getWriter();
out.println("<br>FirstServlet.doGet");
IncludedServlet's doGet initially
System.out.println("IncludedServlet.doGet");
PrintWriter out = response.getWriter();
out.println("<br>IncludedServlet.doGet");
ForwardedServlet's doGet initially
System.out.println("ForwardedServlet.doGet");
PrintWriter out = response.getWriter();
out.println("<br>ForwardedServlet.doGet");
We will create a request dispatcher object for IncludedServlet inside FirstServlet and then call include as:
RequestDispatcher rd1 =
getServletContext().getRequestDispatcher("/IncludedServlet");
rd1.include(request, response);
You will get the output as below both in browser and console:
FirstServlet.doGet
IncludedServlet.doGet
[Rule] An included servlet's contents are added to the current servlets output.
Now add one more include statement using the same RequestDispatcher:
rd1.include(request, response);
rd1.include(request, response);
You will get the output as below both in browser and console:
FirstServlet.doGet
IncludedServlet.doGet
IncludedServlet.doGet
[Rule] Included servlets contents are added to the current servlets output. If we call more includes, data from all includes will be included in the response unless any of the include servlet or even the parent servlet do a commit. If a commit is made, no exception is thrown for subsequent calls to include. However, any data after commit (including from the included servlet and parent servlet) are ignored and are not sent to client.
Create one more RequestDispatcher object for ForwardedServlet inside FirstServlet and then call include as:
RequestDispatcher rd2 =
getServletContext().getRequestDispatcher("/ForwardedServlet");
rd2.forward(request, response);
In the console you will see the output with calls to all includes and the forward:
FirstServlet.doGet
IncludedServlet.doGet
IncludedServlet.doGet
ForwardedServlet.doGet
However in the browser response, you will only see:
ForwardedServlet.doGet
[Rule] Whenever you make a request to forward, all data already in the response buffer is cleared and not sent in response.
Now add one more forward statement using the same RequestDispatcher:
rd2.forward(request, response);
rd2.forward(request, response);
You wil get an exception as:
java.lang.IllegalStateException: Cannot forward after response has been committed
[Rule] You should not forward after a commit. When you forward, the response is commited by container before returning back, and hence you cannot forward again.
[Rule] If you do any commit before forward even unknowingly (e.g. from inside the include servlet), you will still get the exception.
[Rule] Similarly, if you try to forward after a call to out.flush() or out.close(), you will get the same exception.
What will happen if you try to include after a commit (after a forward, or after a call to out.flush() or out.close())?
[Rule] No exception, but any data after commit (including from the included servlet) are ignored and are not sent.
[Rule] Even if you try to write more contents to the initial servlet's out after a commit (after a forward, or after a call to out.flush() or out.close()), the data is discarded.
What will happen if we call getOutputStream() instead of getWriter() in the ForwardedServlet in the above example?
Since we have already called getWriter in the FirstServlet, calling getOutputStream will give an exception: java.lang.IllegalStateException: getWriter() has already been called for this response.
[Recommendation] It is recommended to call forward before any call to getWriter or getOutputStream, as we don’t know which one will be called in the forwarded resource. We have not followed this recommendation here as it does not suit our demo purpose.
Can you include or forward to other servlets from the forwarded servlets?
Yes, you may, and the rules remain same for including a servlet from any servlet. All included contents after a forward and until a commit or end of that forward will be part of response.
You may also forward to another servlet from the forwarded servlet (say, AnotherServlet). In this case the response will contain the data committed from the last forwarded to servlet (e.g. AnotherServlet).
What will happen
If you try to forward after out.close()
java.lang.IllegalStateException: Cannot forward after response has been committed
If you try to forward after out.flush()
java.lang.IllegalStateException: Cannot forward after response has been committed
If you try to include after out.close()
No exception, but any data after commit (including from the included servlet) are ignored and are not sent.
If you try to include after out.flush()
No exception, but any data after commit (including from the included servlet) are ignored and are not sent.
If we call two forward together
java.lang.IllegalStateException: Cannot forward after response has been committed. Container automatically commits after a forward request even if you don’t commit explicitly from the forwarded-to servlet.
If we call two include together
Data from all includes will be included in the response unless any of the include servlet or even the parent servlet do a commit. If a commit is made, no exception is thrown for subsequent calls to include. However, any data after commit (including from the included servlet and parent servlet) are ignored and are not sent to client.
If we call include and then forward
Whenever you make a request to forward, all data in buffer is cleared and not sent. However, if you do any commit before forward (e.g. from inside the include servlet in this case), you will get exception: java.lang.IllegalStateException: Cannot forward after response has been committed.
If we call forward and then include
Whenever you make a request to forward, all data is buffer is cleared and not sent. Container automatically commits after a forward request even if you don’t commit explicitly from the forwarded-to servlet. Any data after a commit does not throw any exception, but are ignored.
If we call getOutputStream() instead of getWriter() in the ForwardedServlet
Since we have already called getWriter in the FirstServlet, calling getOutputStream will give an exception: java.lang.IllegalStateException: getWriter() has already been called for this response. It is recommended to call forward before any call to getWriter or getOutputStream, as we don’t know which one will be called in the forwarded resource.
FirstServlet.java
package com.javajee.rdmechanisms;
import java.io.IOException;
import java.io.PrintWriter;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("/FirstServlet")
public class FirstServlet extends HttpServlet {
private static final long serialVersionUID = 1L;public FirstServlet() {
super();
}protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {System.out.println("FirstServlet.doGet");
PrintWriter out = response.getWriter();
out.println("<br>FirstServlet.doGet");RequestDispatcher rd1 = getServletContext().getRequestDispatcher(
"/IncludedServlet");
rd1.include(request, response);
rd1.include(request, response);RequestDispatcher rd2 = getServletContext().getRequestDispatcher(
"/ForwardedServlet");
rd2.forward(request, response);
//rd2.forward(request, response);}
}
ForwardedServlet.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("/ForwardedServlet")
public class ForwardedServlet extends HttpServlet {
private static final long serialVersionUID = 1L;public ForwardedServlet() {
super();
}protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("ForwardedServlet.doGet");PrintWriter out = response.getWriter();
out.println("<br>ForwardedServlet.doGet");}
}
IncludedServlet is exactly same as ForwardedServlet, but with all occurrences of ForwardedServlet replaced with IncludedServlet.
Eclipse Luna Java EE IDE for Web Developers and Apache Tomcat 8.0.18, using Servlet spec 3.1.