One of the strengths of the Locomotive is that it comes with a built-in system of users and sessions. One of the main problems with the web is that there's no built-in way for web servers to recognize repeat requests from the same user. So, most web sites don't have user accounts for the people who visit their site, and their web pages cannot be tailored to any particular user. Most web sites also lack the concept of a "session", which is a continuous series of viewings of pages on that site by the same user. But since the Locomotive has both users and sessions, programmers like you can write dynamic web pages that give the user the feeling of a single continuous experience, as if that user were using a program right on their own computer.
The Locomotive automatically begins and maintains session for all the users who make requests to it. A little bit later, we'll talk about how that happens; first, we'll show you how to a user can log in to the Locomotive, and use that as an example of how to insert Steam variables into the pages you serve. The HTML document containing embedded Steam commands (which we will call a "HTML template" from now on) which is located at templates/base/login.tmpl includes an HTML form allowing the user to log in. In the Locomotive distribution, we have included a GenericHandler subclass called LoginHandler, which can handle the data from that HTML form. The LoginHandler class is called by a Handler called the BaseHandler.
First, let's look at the HTML template itself. (We're including just the relevant parts here, to save space)
<HTML> <HEAD><TITLE>Locomotive Base Login</TITLE></HEAD> <BODY BGCOLOR="FFFFFF"> ... In the meantime, you can go ahead and login below, using your username and password. [if [LOGIN_ERRORS] [quote ERROR: <FONT COLOR="FF0000">[LOGIN_ERRORS]</FONT>]] <FORM ACTION="base?login" METHOD=POST> <TABLE border=0> <TR> <TD align=left>Username:</TD> <TD><INPUT type=text NAME="LOGIN_USERNAME" VALUE="[LOGIN_USERNAME]" size=12 maxlength=12></TD> </TR> <TR> <TD align=left>Password:</TD> <TD><INPUT type=password NAME="LOGIN_PASSWORD" VALUE="" size=12 maxlength=12></TD> </TR> </TABLE> <INPUT TYPE=SUBMIT VALUE="Login Now"> </FORM> ... </BODY> </HTML>
Notice that this document is basically an HTML document, with a small difference. There are two Steam commands embedded in the document. First, let's take a look at the simpler Steam command, which simply inserts the Steam variable LOGIN_USERNAME into the line:
The purpose of LOGIN_USERNAME is to save the users a little typing; if they typed in their username on the form, but there was some kind of error in logging them in, then this form will be displayed again, with the username field already filled in for their convenience.<TD><INPUT type=text NAME="LOGIN_USERNAME" VALUE="[LOGIN_USERNAME]" size=12 maxlength=12></TD>
Let's walk through an example of how we would set the value of the LOGIN_USERNAME Steam variable, and then insert it into the document. In general, within a GenericHandler, you can set the value of a Steam variable by saying:
steam_vars is a Hashtable, so this method call simply puts a value into a Hashtable. Let's say that you made the following method call:steam_vars.put (String variables_name, String variables_value);
Then, to display the HTML template, you would call the method:steam_vars.put ("LOGIN_USERNAME", "Geronimo");
When the page is displayed, then you will actually see the line:displayPage ("templates/base/login.tmpl", steam_vars);
<TD><INPUT type=text NAME="LOGIN_USERNAME" VALUE="Geronimo" size=12 maxlength=12></TD>
Now, let's take a look at the other, more powerful Steam command from the previous document, whose purpose is to display error messages to tell the users how to correct their input:
In the Steam language, this means, "if the Steam variable named LOGIN_ERRORS contains any text, then we should insert the text within the [quote...] command." The [if...] Steam command allows you to insert different text depending on some condition you specify. It behaves in the same way as an if-then or an if-then-else construct in most programming languages. That are two variations of [if...] commands, the if-then form and the if-then-else form:[if [LOGIN_ERRORS] [quote ERROR: <FONT COLOR="FF0000">[LOGIN_ERRORS]</FONT>]]
[if [condition] [do-this-if-true] ]Our previous example belongs to the if-then form, because it has only two parts. In general, [condition] -- which can be any Steam command -- is counted as "true" only if it evaluates to non-empty text. If "condition" is simply a variable such as [LOGIN_ERRORS], then the [if ...] will be true only if the variable contains non-empty text. So, if LOGIN_ERRORS contained the text, "You did not type in a password.", then the [if...] statement would be interpreted as:
[if [condition] [do-this-if-true] [do-this-if-false] ]
Because the condition, [LOGIN_ERRORS], contains non-empty text, then the "do-this-if-true" part is executed, and the following line would be inserted into the document:[if ["You did not type in a password."] [quote ERROR: <FONT COLOR="FF0000">You did not type in a password.</FONT>]]
ERROR: <FONT COLOR="FF0000">You did not type in a password.</FONT>The [quote...] command is often used in combination with [if...] commands, to insert blocks of text if a condition is true or false. Notice how the Steam command, [LOGIN_ERRORS], which simply inserts a variable, was contained within the [quote...] command. You can put Steam commands within Steam commands, within Steam commands, etc. -- there is no limit on the depth of containment. (Just don't forget that for every open bracket [, you need a closing bracket ]!)
Using Steam commands, almost any kind of information from the Locomotive or the database can be inserted into a Web page. The Steam language has a sophisticated set of features for configuring Web pages, such as if-then-else conditions, variables, arithmetic and logical operators, iterative loops over arrays of elements, automatic conversion of the results of database queries into HTML tables, and insertion of one document into another document. You can refer to the Steam language guide, whenever you have questions about Steam. In fact, we recommend that you skim through at least the Quick Reference section of the Steam manual, so that you are aware of the tools in Steam that are available to you.
In particular, you should be aware of what you can or should write in Steam vs. in Java. The two chief advantages of Steam are that:
Now, we are ready to look at the source code for the LoginHandler class, which can be found at: org/locomotive/loco/base/LoginHandler.java. It is called by the BaseHandler class when the URL begins with: "base?login". The BaseHandler is a Handler included with the Locomotive which gives you access to some built-in GenericHandler subclasses. Its source code can be found at: org/locomotive/loco/base/BaseHandler.java. Here's the part of the code from BaseHandler which calls LoginHandler:
... public void handleRequest (HandlerData hd_in) { super.handleRequest(hd_in); hd = hd_in; // put some standard variables into Steam. StandardInsertions.insertAll (hd); // parse url tokens for proper function if (hd.url_tokens.length < 2) { ... } else if (hd.url_tokens[1].equals("login")) { LoginHandler lh = new LoginHandler(); lh.handleRequest(hd); } ... } /** * A sample login form module. */ public class LoginHandler extends GenericHandler { public void handleRequest (HandlerData hd) { super.handleRequest(hd); // if they are already logged in, then tell them if (!user.isAnonymous()) { logEvent ("login", "already", ""); displayPage ("base/error/already_logged_in.tmpl", steam_vars); return; } // if the form contains no data, then we had an HTTP 'GET' request; // but if it does contain data, then it was a 'POST'. if (form_data.isEmpty()) { logEvent ("login", "form", ""); displayPage ("base/login/login.tmpl", steam_vars); } else { // only if their session is persistent do we proceed. // if it is not, then attempting to associate a new user with // the session will be ignored. // // This currently means that their browser must support cookies // and that they need to have hit the Locomotive at least once // before getting this document. That's fine, because they // almost certainly had to visit the Login page before they // submitted the Login HTML form. if (session.isNew()) { logEvent ("login", "failed", "no_cookie_support"); displayPage ("base/error/no_cookie_support.tmpl", steam_vars); return; } handleFormSubmission(); } } }
Before we take a look at the handleFormSubmission()
method, let's
talk about the handleRequest()
method above.
user.isAnonymous()
is a method which tells whether the user has logged
in yet. If she hasn't yet, then she is anonymous; if she has, then she is no longer
anonymous. If the user's browser is capable of allowing her to start a continuous
session (her browser must support cookies for that to happen), then
session.isNew()
will be false -- that is, her session will persist
from one page viewing to the next.
At this point, we should explain how the Locomotive is able to use a browser's cookies to enable users to maintain continuous session during their visit to your web site. Before the advent of browser cookies, Web pages were generally "stateless"; this means that if you visited a web page once, and then, a few seconds later, you visited it again, that Web page had no way of saving any information related to yourself between those two visits. As far as that web site was concerned, you could be two different people; that Web page was not going to change no matter who you were. Now session information could be stored in the URLs that were loaded into each page, but if the user decided to visit another site and then returned via a bookmark, the session information would be lost.
Then came the concept of a cookie. A cookie is like a key that the web server sends to a browser, which the browser then sends back to the web server every time it interacts with that web server. Thus, the cookie acts as a label for that browser; the web server now knows the identity of that browser from now on. In the case of the Locomotive, because we can now tell whether the same browser (and therefore, the same user) is accessing the web site over and over, we can now say that this user has started a session which is lasting over several page viewings.
The problem is, some browsers don't support cookies. These days, it's pretty rare to find someone using a non-cookie supporting browser- you have to look back to Netscape 1.0 and Explorer 2.0, or the original AOL browser, to find one. If someone uses a browser that doesn't support cookies to connect to a Locomotive, they won't be able to use the parts of the Locomotive that use sessions or logged in users. So, whenever a browser comes to the web site, we need to check whether they support cookies. In the Locomotive, we do this by sending the browser a "test" session cookie the first time this browser visits the web site. Then, if the browser sends back the session cookie the second time it visits the web site, we will know that it supports cookies.
However, this means that we won't be able to tell whether the browser
supports cookies until its second visit. Now, it's possible that the first
page of your web site might be a login page, which might use your own version
of the LoginHandler class. The LoginHandler class is designed intelligently
in this case. It won't check whether the user's browser sent back a cookie
on the first login page, the one that displays the login form. Instead,
it will check for the cookie on the second page, when the user is submitting
the login form. That way, we are guaranteeing that the user's browser has
had a chance to receive the cookie and return it. That's why the
session.isNew()
method is in the else {...}
clause of
if (form_data.isEmpty())
.
So now you know that the Locomotive sends a Session cookie to each browser that connects to it. Now, sessions are often used to convey privileged information to users. Because of this, we might not want to simply log the user in and have them maintain the session forever- they could get up from the browser they were using, and someone could sit down, connect to the Locomotive, and access their private information. To deal with this situation, the Locomotive offers the option of automatically expiring sessions after a certain amount of time, which you can configure. We also don't make the session cookie persistent, by default. This means that when someone quits their browser, the session cookie is lost; when they start their browser again and connect to the Locomotive, they'll have to sign in again. This is also configurable.
Now, say that perhaps you wanted to keep track of how many times a particular browser connected to the Locomotive, or even how many users used a particular browser to log in? You couldn't use the session cookie, because that would be lost each time the session expired or each time someone quit their browser. For this case, the Locomotive provides a browser stamp cookie. The "BStamp" cookie is persistent, and won't expire, so once it's been set, it will be returned to the Locomotive on every request, for as long as the Browser is in use. Most people don't have any use for the Bstamp cookie, so you can turn it off in the loco.conf file, but keep it in mind.
We're getting a little far afield here- let's return to the LoginHandler class to
talk about the rest of the handleRequest()
method shown above.
logEvent()
The Locomotive is equipped to record information each time a page is viewed, including information about the particular user and session. We call this "event-logging". This kind of information is useful for many things; it can allow you to gather statistics on the web site, it can tell you about user behavior or preferences, it can give you a view of the "path" that users take through your web site, and it can even help you track down problems in your code, or track down troublesome users. Think of it as "bookkeeping" for your web site.
The method that you use to log the data about each page view is called
logEvent()
. logEvent()
takes three String arguments:
logEvent (String major_code, String minor_code, String data);
logEvent()
writes one line to the global Locomotive event-log.
That line shows the date, time, session ID, user ID, major code, minor code,
and data for that page view. Here is an example of a line from a Locomotive
event-log. (In the following line, assume that the word <TAB> means
that there is a tab key inserted between each item on the line):
Because each item on the line is separated by a <TAB>, the event log can be easily imported into a database or a spreadsheet. Leverage also has a Perl script which can analyze event logs and automatically generate many statistics for you. This script is at bin/parse_events.pl.1998-06-19 <TAB> 13:28:31 PDT <TAB> 22164 <TAB> 35858 <TAB> 10672 <TAB> joeuser <TAB> login <TAB> failed <TAB> no_cookie_support
In general, we recommend that you call logEvent()
every time
you display a page. That way, you can quickly tell how many pages were viewed
on your web site on any given day. However, when there is an error condition, you
can decide whether or not to log it as an event. Our philosophy is that
the event log tracks real, "productive" page views -- what the users themselves
would consider useful or interesting page views, which usually do not include
fatal error pages.
Now, let's take a look at the handleFormSubmission()
method.
Now, let's take a look at handleFormSubmission()
. This method
uses several methods of the form_data variable, and provides a good example
of how the FormData class works.
/** * handle the login form submission. At the end, we should have a * logged-in user, or else we'll redisplay the login form with * error messages. */ private void handleFormSubmission() { // if the username does not exist, or the password does not match, // then here's the message to give to the user: String generic_error = "Your username and password are invalid, " + "please re-enter. <BR>"; // pull the values from the form so we can work with them. String form_username = form_data.getAlphanum ("LOGIN_USERNAME", "username", 2, 12, true); String form_password = form_data.getString ("LOGIN_PASSWORD", "password", 6, 12); String form_errors = form_data.allErrorMsgs(); if (form_errors.equals("")) { // if there were no errors, then log this user into the system. try { // load this user from the db. user = new User (conn, form_username, false); if (! user.hasPassword (form_password)) { form_errors = generic_error; } } catch (LoadUserException lue) { handleException (lue, "login_lue"); return; } catch (UserNotFoundException unfe) { // if the user was not found, that's okay; it's not a // serious error. Display a normal error message. form_errors = generic_error; } } // if there are errors, set the error string and the value of // the LOGIN_USERNAME variable so that they can be inserted // into the HTML template containing the login form. if (! form_errors.equals ("")) { log(8, "login errors: " + form_errors); steam_vars.put ("LOGIN_ERRORS", form_errors); steam_vars.put ("LOGIN_USERNAME", form_username); logEvent ("login", "failed", form_errors); displayPage ("base/login/login.tmpl", steam_vars); return; } // if we get here, we know that the user is valid and loaded. // Associate the user with the current session persistently // (by modifying the loco_session table in the database). try { session.associateUser (conn, user); } catch (FailedSessionException fse) { handleException (fse, "login_fse"); return; } // run the default insertions again. This was done in // BaseHandler earlier, but we've authenticated a user since then, // so we need to reset some user-specific variables. hd.user = user; StandardInsertions.insertAll (hd); // log the event logEvent ("login", "success", ""); steam_vars.put ("LOGIN_MESSAGE", "Login Successful, welcome!"); displayPage ("base/generic/main.tmpl", steam_vars); }Now, let's explain all that went on in that complex piece of code. Hopefully, the comments helped guide you through the logic. First, let's take the following chunk of code:
String form_username = form_data.getAlphanum ("LOGIN_USERNAME", "username", 2, 12, true); String form_password = form_data.getString ("LOGIN_PASSWORD", "password", 6, 12); String form_errors = form_data.allErrorMsgs();
Here's where the power of the FormData class comes in. First,
we're getting the values of the username field and the password
field that the user entered into their HTML form. At the same
time, we're using the FormData methods, getAlphanum()
and getString()
, to check that those two fields are not null,
and that their length falls within legal bounds. If these conditions are
not met, then form_data
will store error messages for each of
these violations. In fact, every time you call a FormData method starting
with "get...()
",
form_data
will store one or more error messages for you if there
are errors.
For example, if the username is less than two letters, and the
password is empty, then the value of form_data.allErrorMsgs()
will be the String:
"Please type at least 2 letters or digits in the username field.<BR> You did not enter a value for the password field.<BR>"
Then, you can pass this String as a message back to the user by putting it inside a Steam variable -- in this case, the variable LOGIN_ERRORS. The following line comes from a little bit further down in the program:
Now, what if you don't like the error messages that come with the FormData methods? Well, you can override them. For example, if you don't like the error message given for the username field being too short, then, after the line:steam_vars.put ("LOGIN_ERRORS", form_errors);
String form_username = form_data.getAlphanum ("LOGIN_USERNAME", "username", 2, 12, true);
you can add:
form_data.setMsgForError(FormData.TOO_SHORT, "Your username is too short.<BR>");
The setMsgForError()
method above means, "if there was an
error code of FormData.TOO_SHORT
, then use this message as the error
message." If you want to override other error messages as well, then simply
repeat the previous block of code for other error codes.
So, now you know how to put an error message into a Steam variable to insert into an HTML template. The disadvantage of this method is that you can't change the error message unless you change the Java source code, which is somewhat inflexible. Here's an alternate way to do it. If there are only a few different possible errors, you can use Steam variables as boolean flags. For example, you can put the following Steam commands into your HTML template:
[if [USERNAME_TOO_LONG] [quote Hey, buddy, what's with the long moniker?]] [if [USERNAME_TOO_SHORT] [quote You call '[USERNAME_TOO_SHORT]' a username?]] [if [USERNAME_NOT_ALPHANUM] [quote Yo, just letters and digits, okay?]]
So, in this scheme, you don't create an all-purpose error message whenever there are errors. Instead, for each specific kind of error, the error message is specified in the Steam command, and the error message will be visible only if a specific Steam variable contains non-empty text. This way, we can change the error message at anytime, even while the Locomotive is running, and the new error message will take effect immediately.
In this setup, your Java code would look like this:
String form_username = form_data.getAlphanum ("LOGIN_USERNAME", "username", 2, 12, true); if (form_data.hasError(FormData.TOO_SHORT)) steam_vars.put ("USERNAME_TOO_SHORT", "true"); if (form_data.hasError(FormData.TOO_LONG)) steam_vars.put ("USERNAME_TOO_LONG", "true"); if (form_data.hasError(FormData.DOESNT-FIT-TYPE)) steam_vars.put ("USERNAME_NOT_ALPHANUM", "true");
The method form_data.hasError()
tells whether a certain
error code occurred during the last form_data.get?()
operation
to get the value of a field in the form.
Okay, now that you understand the error handling of FormData, let's continue with the discussion of the code. If the username field and the password field are both syntactically valid, then the next thing to do is to load an instance of a User object from the database, and then check whether that user's password matches the password field:
// if there were no errors, then log this user into the system. try { // load this user from the db. user = new User (conn, form_username, false); if (! user.hasPassword (form_password)) { form_errors = generic_error; } }
Notice that, in this case, the "user" variable is being assigned a new value, a new User object. Usually, if a user has already logged in, the "user" variable will already contain a User object which has the information about a user. Then, you can use that information in your Handler or GenericRequest. However, in this case, since the user has not logged in yet, we need to load that information from the database.
A word of caution here: remember that this LoginRequest object will execute only for the duration of this single page viewing. Therefore, the "user" variable will not persist to the next page viewing. The only reason that we load a user from the database into it is so that we can check that user's password against the one that was entered on the form. The next time this same user views a page, the Locomotive will be able to tell from their session (stored in the database) that they have logged in already, and it will load that user's information from the database into a new "user" variable, to be used inside a new Handler or GenericRequest object.
Let's look at the two Exceptions we catch when trying to load a user:
catch (LoadUserException lue) { handleException (lue, "login_lue"); return; } catch (UserNotFoundException unfe) { // if the user was not found, that's okay; it's not a // serious error. Display a normal error message. form_errors = generic_error; }
A LoadUserException is usually not a normal condition; it probably originated from a system malfunction, rather than from something the user did. So, we should handle it as a serious error. The handleException() method of GenericHandler writes to the global server log the information about the Java method calling stack -- exactly which line generated the error -- and gives the user an error message saying that a serious error occurred, so that we programmers know that we should look at it. The first argument of handleException() is the Exception object itself. Its second argument is an error code displayed to the user for them to tell us what the error was. The error code should help us to pinpoint the error, without being so descriptive that it scares users or makes them jump to conclusions.
The second Exception, a UserNotFoundException, is a more normal condition; that just means that there is no user in the database whose username matches the username field. In that case, we'll just give the user an error message. The lesson in this example is that not all Exception subclasses in the Locomotive are serious conditions. Please educate yourself about each one.
At this point, we know whether the user logged in, or whether there were errors. If the user made mistakes trying to log in, then we simply redisplay the login form, and insert the error messages by putting them into the Steam variable LOGIN_ERRORS:
// if there are errors, set the error string and the value of // the LOGIN_USERNAME variable so that they can be inserted // into the HTML template containing the login form. if (! form_errors.equals ("")) { steam_vars.put ("LOGIN_ERRORS", form_errors); steam_vars.put ("LOGIN_USERNAME", form_username); logEvent ("login", "failed", form_errors); displayPage ("base/login/login.dsht", steam_vars); return; }
However, if there were no errors, then we can go ahead and log the user into the system.
We're now at the point where we can log the user in. We do this by associating the User we just pulled from the database with the current session. Presently, the way you do this depends on what kind of module your writing. For Handlers, we'll take a look at the LoginHandler we've been looking at all chapter. For Servlets, we'll look at the LoginServlet class, which is identical to the LoginHandler class except for the section we'll talk about here.
Handlers
If you're writing a handler, you'll have to associate the user manually using the
session.associateUser()
method. This is how the LoginHandler does it:
// if we get here, we know that the user is valid and loaded. // Associate the user with the current session persistently // (by modifying the loco_session table in the database). try { session.associateUser (conn, user); } catch (FailedSessionException fse) { handleException (fse, "login_fse"); return; }
The associateUser()
method updates the USERID field of LOCO_SESSIONS table
in the database for this row, with the userid of the User we got loaded from the database.
Once we perform the database update, we have to make sure we're going to use the new user
for the rest of the request. We can do this by just replacing the old user with the new
one in our HandlerData object:
hd.user = user;
Once we've done that, we've finished logging the user in, and from now until when the session expires, that user will always be associated with that session.
Servlets
If you're writing a Servlet, then you can accomplish the log in with one step. Let's take a look at the LoginServlet code to see how to do it:
// if we get here, we know that the user is valid and loaded. // Associate the user with the current session persistently // (by modifying the loco_session table in the database). // this is done automatically when you replace the user in the // session object. session.putValue("locomotive.user", user);
As the comments say, this one method will do the work of associating the User with the
session in the database. You don't have to worry about calling the
associateUser()
method; the Locomotive will do it for you.
Now that we've successfully logged in, let's insert a welcome message for the user and log the event:
// log the event logEvent ("login", "success", ""); steam_vars.put ("LOGIN_MESSAGE", "Login Successful, welcome!");
Now, let's talk about something else Handlers and Servlets do differently- redirecting a request to another module.
Occasionally you may find the need to have one module partially process a request, and
then forward to another module to finish the work and send off the page. The Locomotive
provides an easy way to do this- the only issue is that it's different for Handlers and
Servlets. Servlets use something that's part of the Servlet API- an object called the
RequestDispatcher. Handlers use a static method called Loco.redirect()
.
We'll talk about Handlers first.
Redirecting Handlers
Redirecting a request to another Handler is easy. All you have to do is call the
Loco.redirect()
method, and supply your HandlerData object, and the URL
which the Locomotive should use to find the new Handler. Let's take a look at the code
at the bottom of LoginHandler to for an example:
// Now, let's send them back to the Base Handler and // let it display its default page. This lets the Base Handler // perform any of the personalization logic it normally // performs before it displays the page. Loco.redirect("base", hd);
As you see, we provided our HandlerData, and we sent the request to whatever handler the Locomotive would route the request URI "base" to- in this case the BaseHandler. The BaseHander will go ahead and reinsert the standard insertions, with the new username and userid, and redisplay the main base page, with our welcome message.
Redirecting Servlets
Redirecting Servlets is a little more complex, because the Servlet API provides a more power means of redirecting. The RequestDispatcher class, new to version 2.1 of the Servlet API, lets you both include request in a present request, to either insert part of a page or do some extra processing, and forward requests. We only need to forward requests, so that's what we'll talk about. Here's how the LoginServlet forwards the request back to the BaseServlet:
// Now, let's send them back to the Base Handler and let it // display its default page. This lets the Base Handler // perform any of the personalization logic it normally // performs before it displays the page. RequestDispatcher reqd = getServletContext().getRequestDispatcher( Loco.getConfig().getString("SERVLET_URI_TOKEN_PREFIX") + "/base"); reqd.forward(req, resp);
Now, you'll see, there's a whole lot more going on here than in the Handler example above.
First. we create a RequestDispatcher object by getting the servlet context and calling the
method getRequestDispatcher()
. When you call that method, you pass in a request
URI, just like in the redirect()
method above. You see here that we don't just
pass in "base"; instead, we preface it with the config variable
SERVLET_URI_TOKEN_PREFIX. As you might recall from
Chapter One, the SERVLET_URI_TOKEN_PREFIX is the part of the URL that tells the
Locomotive what service routing table should handle the routing. Here, we explicitly tell
the Locomotive to send the request to the ServletRoutingTable, because we know we
want to redirect to the BaseServlet. We probably should have done this in the
Handler redirect above, but the truth is, the BaseServlet and the BaseHandler do
the same thing, so we just let the Locomotive choose the default service to
route the request. As you might remember, you can set the default service by
setting the DEFAULT_SERVICE config variable to either 'HANDLER' or 'SERVLET'.
You can read more about these config variables in the
Administrator's Guide.
So, if our SERVLET_URI_TOKEN_PREFIX is set to 'servlet', then we ended up passing the
getRequestDispatcher()
method "servlet/base", which is guaranteed to
route the request to the BaseServlet (That is, as long as we mapped 'base' to the BaseServlet
in the servlet.conf file). To forward the request, we call the RequestDispatcher's
forward()
method, and pass in our ServletRequest and ServletResponse object.
That sends it off.
Now, there is another way to forward a request to another Servlet via the Servlet API.
This is the old way- the only way that was available in versions of the Servlet API previous
to 2.1. It involves calling the LocoServletResponse.sendRedirect()
method.
Here's what that would look like:
// alternatively, you could use a server-side redirect to // send them to the base page String servlet_path = req.getServletPath(); String path_to_servlet_engine = servlet_path.substring(0, servlet_path.indexOf(url_tokens[0])); java.net.URL url = new java.net.URL( req.getScheme(), req.getServerName(), req.getServerPort(), path_to_servlet_engine + "base"); resp.sendRedirect(url.toString());
We don't like this method of redirecting, because it isn't as efficient as the one we
talked about above. This actually sends the request back to the web server, which then
redirects it back to the Locomotive, which then figures out what service and module the
request should be routed to. Sending the request back to the web server is an extra step
that incurs a lot of overhead, and so we think the RequestDispatcher method shown above is
the way to go. resp.sendRedirect()
requires a complete URL, including the
server name and path to the Locomotive. That's why we ended up constructing a
java.net.url object to pass into it.
SUMMARY
In this example, we looked at many new methods that dealt with the user, session, and form_data variables. We still have not looked at how Java interacts with the database via JDBC. This will be the topic for our next example.