Web Server Java -- Servlets and JSP
Pages: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
Session Tracking
Problem:
You want to keep track of one user across several servlet invocations within the same browser session.
Solution:
Use an HttpSession object.
Discussion
HTTP was designed to be a stateless protocol: you would connect to a server, download a laboratory report, and that would be the end of it. Then people started getting clever, and began using it for interactive applications. For such purposes as a shopping cart in an online mall, and tracking answers during an online quiz or moves in an online game, the notion of an HTTP session has evolved to keep track of a particular browser. Sessions can be identified either by use of a cookie (see Cookies) or by a Session Identifier that is added to the URL. In either case the session ends when the user's browser program exits, but will otherwise stick around for a long time (there is probably a major denial-of-service attack hidden in here, so beware).
Using a session is fairly simple within the Servlet API. You
request the HttpSession object from the
HttpRequest that is passed into your
service( ) or doGet( )/doPost(
) method. The session object behaves rather like a
Hashtable except that the method names are
putValue( ) and getValue( ).
This allows you to store an arbitrary number of objects in the session and
retrieve them later.
This program uses an HttpSession to
keep track of a user's responses during a quiz about Java. There are some 20
categories; once you pick a category, you can answer all the multiple-choice
questions in that topic. The first question looks like Figure
18-4.
|
After you've answered a few questions, it may look like Figure 18-5.
|
At the end of the quiz, you'll see the total number of questions that you answered correctly.
|
Debugging Tip for Servlets Using an HttpSession Objects (such as |
The Exam object (an object containing
all the questions and answers, along with the number of correct answers) is
loaded using an XamDataAccessor (the code for these
two classes is not shown) and stored in a Progress
object. Progress, an inner class inside the
servlet, is a tiny data structure used to monitor your progress through one
quiz. When you change topics, the Progress object
is discarded and a new one created. The bulk of the code in Example
18-7 is taken up in checking and tracking your answers and in generating
the HTML to show the results of your previous question (if any), as well as
the question and possible answers for the current question.
Example 18-7: DoTestServlet.java
/** A Java Servlet to administer the tests over the Web.
* Saves exam and status session object to avoid having to reload it,
* but also to keep the exam constant during a session!
*/
public class DoTestServlet extends HttpServlet {
/** Where to find the exams du jour */
protected final static String DIRECTORY =
"/home/ian/webs/daroadweb/quizzes-";
/** The body color */
protected final static String BGCOLOR = "white";
/** An Inner Class to track the student's progress */
class Progress {
Exam exam; // exam being taken
boolean done; // exam is finished.
String category; // name of exam, in effect
int curQuest; // Question number working on, 0-origin
int correct; // number gotten right on first try
}
/** Service is used to service each request. */
public void service(HttpServletRequest request,
HttpServletResponse response) throws IOException, ServletException {
PrintWriter out = response.getWriter( );
HttpSession session;
Progress progress;
String reqCategory;
// Set response type to HTML. Print the HTML header.
response.setContentType("text/html");
out.println("<HTML>");
// Find the requested category
reqCategory = request.getParameter("category");
reqSubject = request.getParameter("subject"); // unix or java
// Request the user's session, creating it if new.
session = request.getSession(true);
if (session.isNew( )) {
// out.println("<B>NEW SESSION</B>");
progress = new Progress( );
progress.category = reqCategory;
session.putValue("progress", progress);
} else {
progress = (Progress) session.getValue("progress");
}
if (reqCategory != null && progress.category != null &&
!reqCategory.equals(progress.category)) {
// CHANGE OF CATEGORIES
// out.println("<B>NEW PROGRESS CUZ " +
// reqCategory + " != " +progress.category + "</B>");
progress = new Progress( );
progress.category = reqCategory;
session.putValue("progress", progress);
}
if (progress.exam == null) {
XamDataAccessor ls = new XamDataAccessor( );
try {
progress.exam = ls.load(DIRECTORY + subject + "/" +
progress.category + ".xam");
} catch (IOException ex) {
eHandler(out, ex, "We had some problems loading that exam!");
} catch (NullPointerException ex) {
eHandler(out, ex, "Hmmm, that exam file seems to be corrupt!");
}
}
// Now that we have "exam", use it to get Title.
out.print("<TITLE>Questions on ");
out.print(progress.exam.getCourseTitle( )); out.println("</TITLE>");
out.print("<BODY BGCOLOR=\""); out.print(BGCOLOR); out.println("\">");
out.print("<H1>");
out.print(progress.exam.getCourseTitle( ));
out.println("</h2>");
// Guard against reloading last page
if (progress.done) {
out.println("<HR><a href=\"/quizzes/\">Another Quiz?</a>");
out.flush( );
return;
}
// Are we asking a question, or marking it?
out.println("<P>");
String answer =request.getParameter("answer");
int theirAnswer = -1;
if (answer != null) {
// MARK IT.
Q q = progress.exam.getQuestion(progress.curQuest);
theirAnswer = Integer.parseInt(answer);
if (theirAnswer == q.getAns( )) {
// WE HAVE A RIGHT ANSWER -- HURRAH!
if (!q.tried) {
out.println("<P><B>Right first try!</B>");
progress.correct++;
} else
out.println("<P><B>Right. Knew you'd get it.</B>");
q.tried = true; // "Tried and true..."
if (++progress.curQuest >= progress.exam.getNumQuestions( )) {
out.print("<P>END OF EXAM.");
if (progress.correct == progress.curQuest) {
out.println("<P><B>Awesome!</B> You got 100% right.");
} else {
out.print("You got ");
out.print(progress.correct);
out.print(" correct out of ");
out.print(progress.curQuest);
out.println(".");
}
out.println("<HR><a href=\"/quizzes/\">Another Quiz?</a>");
// todo invalidate "progress" in case user retries
progress.done = true;
// Return, so we don't try to print the next question!
return;
} else {
out.print("Going on to next question");
theirAnswer = -1;
}
} else {
out.print("<B>Wrong answer</B>. Please try again.");
q.tried = true;
}
}
// Progress?
out.print("<P>Question ");
out.print(progress.curQuest+1);
out.print(" of ");
out.print(progress.exam.getNumQuestions( ));
out.print(". ");
if (progress.curQuest >= 2) {
out.print(progress.correct);
out.print(" correct out of ");
out.print(progress.curQuest);
out.print(" tried so far (");
double pct = 100.0 * progress.correct / progress.curQuest;
out.print((int) pct);
out.println("%).");
}
// Now generate a form for the next (or same) question
out.print("<FORM ACTION=/servlet/DoTestServlet METHOD=POST>");
out.print("<INPUT TYPE=hidden NAME=category VALUE=");
out.print(progress.category); out.println(">");
out.println("<HR>");
Q q = progress.exam.getQuestion(progress.curQuest);
out.println(q.getQText( ));
for (int j=0; j<q.getNumAnswers( ); j++) {
out.print("<BR><INPUT TYPE=radio NAME=answer VALUE=\"");
out.print(j);
out.print("\"");
if (j==theirAnswer)
out.print(" CHECKED");
out.print(">");
out.print(q.getAnsText(j));
out.println("</INPUT>");
}
out.println("<HR>");
out.println("<INPUT TYPE=SUBMIT VALUE=\"Mark it!\"");
out.println("</FORM>");
out.println("</HTML>");
out.close( );
}
void eHandler(PrintWriter out, Exception ex, String msg) {
out.println("<H1>Error!</h2>");
out.print("<B>");
out.print(msg);
out.println("</B>");
out.println("<pre>");
ex.printStackTrace(out);
out.flush( );
out.close( );
}
}

