Skip to content

12. MVC Web Application [person] – Version 7

12.1. Introduction

In this version, we assume that there may be client browsers that have disabled:

  1. the sending of cookies from the server
  2. the execution of JavaScript code embedded in the displayed HTML pages

Nevertheless, we want this type of browser to be able to use our application. Point 2 takes us back to version 2 of our application, as JavaScript was introduced starting with version 3. Version 2 ran the application without JavaScript, so point 2 is resolved.

Point 1 may or may not be difficult to handle. Version 6 of our application worked without cookies. By merging versions 2 and 6, we achieve the desired result. We will add an additional constraint: the application must manage a session. This is not a meaningless constraint. In an application where users must authenticate, the server must store the user’s username and password to prevent them from having to authenticate on every page they request.

So far, we have used three solutions to store information during client/server exchanges:

  1. the session
  2. cookies
  3. hidden fields.

Solution 2 can be ruled out since the client browser may have disabled cookies.

Solution 3 is the one from Version 6, which we examined earlier. It cannot be used for security reasons. If the login/password pair is embedded in every page sent to the browser, that means it travels over the network with every client-server exchange. This is not good for the application’s security. We could then consider using the HTTPS protocol, which encrypts client-server exchanges. However, using it for every page of the application will increase the server’s load.

You might want to rule out Solution 1 because it also relies on cookies. During the first client-server exchange, the server sends the client a session token, which the client then sends back to the server with every new request. Thanks to this token, the server can recognize the client and provide it with information it had stored during a previous exchange. The session token is sent by the server in a cookie. A browser that has not disabled cookies can send this cookie back to the server in subsequent requests. If cookies are disabled, there is another solution: the browser can include the session token in the URL it requests. This is what we see now as we revisit the [index.jsp] file from version 4:


<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>

<c:redirect url="/main"/>

Recall that line 5 above redirects the client to the URL [/personne4/main?jsessionid=XX], where XX is the session token, as shown in the screenshot below obtained after requesting the URL [http://localhost:8080/personne4]:

Image

Let’s take a closer look at how the <c:redirect> tag works with regard to the session token. Let’s use a browser that accepts cookies. Below, we configure the Firefox browser:

Image

In [1], we enable cookies, and in [2] we delete any existing ones to start from a known state. Then we request the URL [http://localhost:8080/personne4]. We receive the following response:

Image

The client’s initial HTTP request was as follows:

1
2
3
4
5
6
7
8
9
GET /person4/ HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: fr-fr,fr;q=0.8,en;q=0.6,en-us;q=0.4,de;q=0.2
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive

Note that the client does not send a session cookie. The HTTP response sent by the server is as follows:

1
2
3
4
5
6
7
HTTP/1.x 302 Temporarily Moved
Server: Apache-Coyote/1.1
Set-Cookie: JSESSIONID=1ACA010A6BA28FB9E30A1D3184F574BC; Path=/personne4
Location: http://localhost:8080/personne4/main;jsessionid=1ACA010A6BA28FB9E30A1D3184F574BC
Content-Type: text/html;charset=ISO-8859-1
Content-Length: 0
Date: Tue, May 23, 2006 09:10:05 GMT
  • Line 1: The server asks the client to redirect
  • line 3: the server sends a session token associated with the [JSESSIONID] attribute
  • line 4: the redirect URL contains the session token. The <c:redirect> tag placed it there because the client had not sent a session cookie.

The browser, which was asked to redirect, then made the following request:

GET /person4/main;jsessionid=1ACA010A6BA28FB9E30A1D3184F574BC HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: fr-fr,fr;q=0.8,en;q=0.6,en-us;q=0.4,de;q=0.2
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Cookie: JSESSIONID=1ACA010A6BA28FB9E30A1D3184F574BC
  • Line 1: It requests the redirect URL, including the session token. This is why the browser displays this URL in the screenshot.
  • Line 10: The browser sends back the session token that the server sent to it in the previous exchange. This is how cookies normally work when they are enabled on the client browser. If they are not enabled, the received cookies are not sent back.

The server responded to this second request with the following:

1
2
3
4
5
HTTP/1.x 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=ISO-8859-1
Content-Length: 2376
Date: Tue, 23 May 2006 09:10:05 GMT

It found the requested page and is sending it. Note that it no longer sends the session token. This is how session tokens normally work: the server sends it to the browser once in the form of a cookie, and the browser then sends it back with every request to be recognized.

Now, using the same browser, let’s request the URL [http://localhost:8080/personne4] again by typing it in manually. We then get the following page:

Image

We can see that the URL displayed by the browser no longer contains the session token. Let’s look at the first client/server exchange:

The browser made the following request:

GET /person4 HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: fr-fr,fr;q=0.8,en;q=0.6,en-us;q=0.4,de;q=0.2
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Cookie: JSESSIONID=1ACA010A6BA28FB9E30A1D3184F574BC

This is exactly the same request as the previous one, with one difference: on line 10, the browser sends back the session token it received during the very first exchange. Again, this is normal behavior if the browser's cookies are enabled.

The server sent the following response:

1
2
3
4
5
HTTP/1.x 302 Temporarily Moved
Server: Apache-Coyote/1.1
Location: http://localhost:8080/personne4/
Transfer-Encoding: chunked
Date: Tue, 23 May 2006 09:24:39 GMT

It instructs the client to redirect. Since it received a session token from the client, it continues the session and does not send a new session token. For the same reason, the <c:redirect> tag does not include this session token in the redirect URL. This is why the URL shown in the screenshot above does not contain a session token.

The key takeaway from all this is the following rule: the <c:redirect> tag only includes the session token in the redirect URL if the client has not sent the HTTP header:

Cookie: JSESSIONID=1ACA010A6BA28FB9E30A1D3184F574BC

This rule also applies to the <c:url> tag, which we will encounter later.

What happens with a browser on which cookies have been disabled? Let’s try it. First, we reset the browser:

Image

In [1], we disable cookies, and in [2] we delete any existing ones to start from a known state. Then we request the URL [http://localhost:8080/personne4]. We get the following response:

Image

We get the same result as before. However, the HTTP exchanges are not exactly the same:

GET /person4/ HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: fr-fr,fr;q=0.8,en;q=0.6,en-us;q=0.4,de;q=0.2
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive

HTTP/1.x 302 Temporarily Moved
Server: Apache-Coyote/1.1
Set-Cookie: JSESSIONID=911B8156E0A9D32C2D256020C898E05C; Path=/personne4
Location: http://localhost:8080/personne4/main;jsessionid=911B8156E0A9D32C2D256020C898E05C
Content-Type: text/html;charset=ISO-8859-1
Content-Length: 0
Date: Tue, 23 May 2006 09:39:55 GMT

GET /person4/main;jsessionid=911B8156E0A9D32C2D256020C898E05C HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: fr-fr,fr;q=0.8,en;q=0.6,en-us;q=0.4,de;q=0.2
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive

HTTP/1.x 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=ISO-8859-1
Content-Length: 2376
Date: Tue, May 23, 2006 09:39:55 GMT
  • Lines 1–9: The browser’s first request. It does not send a session cookie.
  • Lines 11–17: The server’s response, which instructs the browser to redirect to another URL. It sends a session cookie. Line 13: The <c:redirect> tag has included the token in the redirect URL on line 14.
  • Lines 19–27: The browser’s second request. It does not send back the session cookie that the server just sent because its cookies are disabled.
  • Lines 29–33: The server’s response. We can see that although the browser did not send a session cookie, the server does not start a new session as one might expect. This is evident from the fact that it does not send the [Set-Cookie] HTTP header as it did in line 13. This means it continues the previous session. It was able to retrieve this session thanks to the session token present in the URL requested by the browser in line 19.

Note that the server tracks a session by retrieving the session token sent by the client in two possible ways:

  • in the [Set-Cookie] HTTP header sent by the client
  • in the URL requested by the client

Now, using the same browser, let’s request the URL [http://localhost:8080/personne4] again by typing it in manually, just as we did when cookies were enabled. We then get the following page:

Image

The result is different from what we saw when cookies were allowed: the session token is in the URL displayed by the browser. Let’s explain this result without examining the HTTP exchanges that took place:

[cookies enabled]

  • During the second request for the URL [http://localhost:8080/personne4], the client browser sent back the session cookie it had received from the server during the first request for that same URL. The <c:redirect> tag therefore did not include the session token in the redirect address.

[cookies disabled]

  • During the second request for the URL [http://localhost:8080/personne4], the client browser does not send the session cookie it received from the server during the first request for that same URL, since its cookies are disabled. The <c:redirect> tag therefore includes the session token in the redirect URL. This is why it appears in the screenshot above.

The <c:redirect> and <c:url> tags allow you to include the session token in URLs. This is the solution proposed here.

12.2. The Eclipse Project

To create the Eclipse project [mvc-personne-07] for the web application [/personne7], duplicate the project [mvc-personne-06] by following the procedure described in section 6.2.

12.3. Configuring the [personne7] Web Application

The web.xml file for the /personne7 application is as follows:


<?xml version="1.0" encoding="UTF-8"?>
...
    <display-name>mvc-personne-07</display-name>
...

This file is identical to the one in the previous version except for line 3, where the display name of the web application has changed to [mvc-personne-07]. The home page [index.jsp] remains unchanged.


...
<c:redirect url="/do/form"/>

12.4. The view code

The views [form, response, errors] revert to what they were in version 2, i.e., without JavaScript. However, they retain the JSTL tags from the latest versions.

12.4.1. The [form] view

Image

The buttons associated with JavaScript code have been removed.

[form.jsp]:


<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
  pageEncoding="ISO-8859-1"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>

<html>
  <head>
    <title>Contact Form</title>
  </head>
  <body>
    <center>
      <h2>Person - form</h2>
      <hr>
      <form name="frmPersonne" action="<c:url value="validationFormulaire"/>" method="post">
        <table>
          <tr>
            <td>Last Name</td>
            <td><input name="txtName" value="${name}" type="text" size="20"></td>
          </tr>
          <tr>
            <td>Age</td>
            <td><input name="txtAge" value="${age}" type="text" size="3"></td>
          </tr>
          <tr>
        </table>
        <table>
          <tr>
            <td><input type="submit" name="button" value="Submit"></td>
            <td><input type="reset" value="Reset"></td>
            <td><input type="submit" name="button" value="Clear"></td>
          </tr>
        </table>
      </form>
    </center>
  </body>
</html>
  • Line 14: The POST target URL is written using the <c:url> tag so that the session token is included in case the client is a browser that does not send the [Cookie] HTTP header.
  • The form has two [submit] buttons: [Submit] (line 28) and [Clear] (line 30). Both buttons have the same name: button. When the POST is triggered, the browser will send the parameter:
  • button=Submit if the POST was triggered by the [Submit] button
  • button=Clear if the POST was triggered by the [Clear] button

This parameter will help us determine the exact action to take, as the URL [/do/validationFormulaire] now corresponds to two distinct actions.

12.4.2. The [response] view

Image

[response.jsp]:


<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>

<html>
    <head>
      <title>Person</title>
  </head>
  <body>
      <h2>Person - response</h2>
    <hr>
    <table>
        <tr>
          <td>Name</td>
        <td>${name}</td>
      </tr>
        <tr>
          <td>Age</td>
        <td>${age}</td>
      </tr>
    </table>      
    <br>
    <a href="<c:url value="returnForm"/>">${returnFormLink}</a>
  </body>
</html>

  • Line 24: The target URL of the HREF is written using the <c:url> tag so that the session token is included in case the client is a browser that does not send the [Cookie] HTTP header.

12.4.3. The [errors] view

Image

[errors.jsp]:


<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>

<html>
    <head>
      <title>Person</title>
  </head>
  <body>
      <h2>The following errors occurred</h2>
    <ul>
            <c:forEach var="error" items="${errors}">
                <li>${error}</li>
            </c:forEach>
    </ul>
    <br>
    <a href="<c:url value="returnForm"/>">${returnFormLink}</a>
  </body>
</html>

  • Line 18: The HREF target URL is written using the <c:url> tag so that the session token is included in case the client is a browser that does not send the [Cookie] HTTP header.

Readers are invited to test these new views using the same approach as in previous versions.

12.5. The [ServletPersonne] controller

The [ServletPersonne] controller for the [/personne7] web application is as follows:

package istia.st.servlets.personne;

...

@SuppressWarnings("serial")
public class ServletPerson extends HttpServlet {
    // instance parameters
    private String errorUrl = null;
    private ArrayList initializationErrors = new ArrayList<String>();
    private String[] parameters = {"formUrl", "responseUrl", "formBackLink"};
    private Map params = new HashMap<String, String>();

    // init
    @SuppressWarnings("unchecked")
    public void init() throws ServletException {
...
    }

    // GET
    @SuppressWarnings("unchecked")
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {

...
        // retrieve the request method
        String method = request.getMethod().toLowerCase();
        // retrieve the action to execute
        String action = request.getPathInfo();
...
        if (method.equals("post") && action.equals("/validateForm")) {
            // validate the input form
            doValidateForm(request, response);
            return;
        }
        if (method.equals("get") && action.equals("/returnForm")) {
            // return to the input form
            doReturnForm(request, response);
            return;
        }
        // other cases
        doInit(request, response);
    }

    // display empty form
    void doInit(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
...
    }

    // display pre-filled form
    void doFormSubmit(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
        // display the form
        getServletContext().getRequestDispatcher((String)params.get("urlFormulaire")).forward(
                request, response);
        return;
    }

    // display empty form
    void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
        // prepare the form template
        HttpSession session = request.getSession(true);        
        session.setAttribute("name", "");
        session.setAttribute("age", "");
        // display the form
        getServletContext().getRequestDispatcher((String)params.get("formUrl")).forward(
                request, response);
        return;
    }

    // Form validation
    void doValidateForm(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException{
        // retrieve the button that triggered the POST
        String button = request.getParameter("button").toLowerCase();
        // Process based on the button that triggered the POST
        if(button == null){
            doInit(request, response);
            return;
        }
        if("send".equals(button)){
            doSend(request, response);
            return;
        }
        if("delete".equals(button)){
            doDelete(request, response);
            return;
        }
    }

    // form validation
    void doSend(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException{
        // retrieve the parameters
        String name = request.getParameter("txtName");
        String age = request.getParameter("txtAge");
        // which we store in the session
        HttpSession session = request.getSession(true);        
        session.setAttribute("name", name);
        session.setAttribute("age", age);
        // the link back to the form is placed in the view template [response, errors]
        request.setAttribute("formReturnLink", (String)params.get("formReturnLink"));    
        // parameter validation
        ArrayList<String> callErrors = new ArrayList<String>();
    ...
        // Are there errors in the parameters?
        if (callErrors.size() != 0) {
            // send the error page
            request.setAttribute("errors", callErrors);
            getServletContext().getRequestDispatcher(errorURL).forward(
                    request, response);
            return;
        }
        // the parameters are correct - send the response page
        getServletContext().getRequestDispatcher((String)params.get("urlResponse")).forward(request,
                response);
        return;
    }

    // post
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {
...
    }
}
  • line 35: the [/retourFormulaire] action is performed via a GET request rather than a POST request as in the previous version.
  • lines 70–87: The [/validationFormulaire] action is triggered by a POST request caused by clicking one of the [Envoyer] or [Effacer] buttons on the [form] view. The [doValidationFormulaire] method handles these two cases using two different methods.
  • Lines 90–103: The [doEnvoyer] method corresponds to the [doValidationFormulaire] method from the previous version. The entered data is stored in the session (lines 96–98), whereas in the previous version it was placed in the request.
  • Lines 58–67: The new [doDelete] method must display an empty form. We could call the [doInit] method, which already performs this task. Here, we take the opportunity to also delete the [name, age] elements from the session so that it continues to reflect the form’s latest state.
  • lines 50-55: request the display of the [form] view without any apparent initialization of the view’s model. This model is actually composed of the [name, age] elements already in the session. No further action is required.

12.6. Tests

Start or restart Tomcat after integrating the Eclipse project [personne-mvc-07] into it, then request the URL [http://localhost:8080/personne7] using a browser with cookies disabled and existing cookies deleted. The following response is obtained:

Image

The source code received by the browser is as follows:

1
2
3
<form name="frmPersonne" action="validationFormulaire;jsessionid=9D4CC83FEFB51AE78B1FD71EC66F9EF3" method="post">
...
</form>

Line 1: The session token is in the POST target URL.

Let’s fill out the form and submit it:

Image

The source code received by the browser is as follows:

1
2
3
4
...
    <br>
    <a href="retourFormulaire;jsessionid=9D4CC83FEFB51AE78B1FD71EC66F9EF3">Back to form</a>
  </body>

Line 3: The session token is in the link's target URL.