javatomcatservletsrequestdispatcherdeployment-descriptor

Tomcat infinte loop on requestDispatcher#forward(). StackOverflowError


Tomcat is giving me StackOverflowError after constantly repeating these lines, where DiceBoardDispatcher is my HttpServlet and in line 34 I am calling requestDispatcher#forward().

nl.rickhurkens.rollDice.web.diceBoard.DiceBoardDispatcher.doGet(DiceBoardDispatcher.java:34)
        javax.servlet.http.HttpServlet.service(HttpServlet.java:634)
        javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
        org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
        nl.rickhurkens.rollDice.web.diceBoard.DiceBoardDispatcher.doGet(DiceBoardDispatcher.java:34)
        javax.servlet.http.HttpServlet.service(HttpServlet.java:634)
        javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
        org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)

I guess it's got something to do with my deployment, here is the relevant part of my web.xml:

  <servlet-mapping>
    <servlet-name>DiceBoard</servlet-name>
    <url-pattern>/dices/*</url-pattern>
  </servlet-mapping>

  <filter-mapping>
    <filter-name>RollDiceFilter</filter-name>
    <url-pattern>/dices/roll</url-pattern>
  </filter-mapping>
  <filter-mapping>
    <filter-name>SetSettingsFilter</filter-name>
    <url-pattern>/dices/setup</url-pattern>
  </filter-mapping>

What I am trying to do is have 2 actions both landing on the same page. I thought I'd set it up like this. Each action goes through its own Filter and they both end in the Servlet that dispatches to a JSP.

My url-patter mapping used to be to /diceBoard/*, which is a folder somewhere in the web-app. Changing from there to /dices/* fixed the problem when going to url/dices. Now I can reach that page normally, but when going to url/dices/roll I get the infinite loop (no matter if POST or GET).

Edit: My servlet code:

public class DiceBoardDispatcher extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HttpSession session = request.getSession();
        RequestDispatcher view = request.getRequestDispatcher("diceBoardPage.jsp");
        view.forward(request, response);
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        RequestDispatcher view = request.getRequestDispatcher("diceBoardPage.jsp");
        view.forward(request, response);
    }
}

And RollDiceFilter:

public class RollDiceFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest)request;
        HttpServletResponse res = (HttpServletResponse)response;
        HttpSession session = req.getSession();

        DiceBoard diceBoard = (DiceBoard)session.getAttribute("diceBoard");
        String[] lockedValues = req.getParameterValues("lock");

        diceBoard.unlockAllDice();
        if (lockedValues != null) {
            for (String value : lockedValues) {
                if (value != null) {
                    try {
                        diceBoard.lockDice(Integer.parseInt(value));
                    } catch (DiceNotInCollectionException e) {
                        // TODO: redirect to error page.
                        e.printStackTrace();
                    }
                }
            }
        }
        diceBoard.getCup().roll();

        chain.doFilter(req, res);
    }

    public void init(FilterConfig fConfig) throws ServletException { }
    public void destroy() { }
}

Solution

  • This is the line where your error is located:

    RequestDispatcher view = request.getRequestDispatcher("diceBoardPage.jsp");
    

    According to the getRequestDispatcher API, the input uri is relative to the current servlet context, so when you are executing your servlet at

    /dices/roll
    

    ... and it performs a dispatch to "diceBoardPage.jsp", it is actually dispatching to

    /dices/diceBoardPage.jsp
    

    And what servlet is mapped this pattern to? According to your deployment descriptor, every URL beginning with "/dices/*" is mapped to DiceBoard. I.E. the same servlet. Here comes the infinite loop that caused your StackOverflowError.

    If the JSP must stay within the dices uri, you must restrict the URL pattern which maps the DiceBoard servlet. Do not hesitate to add several values if needed:

    <servlet-mapping>
        <servlet-name>DiceBoard</servlet-name>
        <url-pattern>/dices/one</url-pattern>
        <url-pattern>/dices/two</url-pattern>
        <url-pattern>/dices/three</url-pattern>
    </servlet-mapping>