A servlet is Java program that executes in a Java-enabled Web server. It runs entirely within the Java Virtual Machine (JVM), and its purpose is to service requests from Web clients. The Web server then returns a response, typically in HTML, to the client. Servlets are a powerful way to replace Common Gateway Interface (CGI) programs written in various languages, such as C and Perl. Why use servlets? Consider the following reasons:
PortabilityServlets are developed in Java, so you can move them to various operating systems without having to recompile the source code, as is the case with CGI modules.
EfficiencyServlets are loaded only once by the Web server. Each new request calls one of the servlets service methods, whereas a new CGI program is loaded for each client request.
PersistenceServlets, once loaded by the Web server, reside in memory, allowing a state to be maintained between requests. (An example of this is the shopping cart that is found on many Web sites.)
SecurityServlets run on the server in a Java sandbox, which prevents them from disrupting the operating system or breaching security. They can also take advantage of the Java Security Manager architecture.
Servlets are also robust and extensible, because they are developed in an object- oriented language.
The Servlet Architecture
The servlet architecture consists of two java packages: javax.servlet and javax.servlet.http. The javax.servlet package contains interfaces (classes that define methods but do not implement them) and classes that are extended and implemented by all servlets. The javax.servlet.http package contains classes that are extended by servlets that will handle
HTTP requests. Central to the servlet architecture is the javax.servlet.Servlet interface class found in the javax.servlet package. Servlets implement the methods within this class to handle communication with Web clients.
There are three methods that are most important: init, service, and destroy. The init method is called once by the Web server when loading the servlet. (The init method typically establishes database connections.) The service method receives, handles, and responds to client requests. The destroy method performs cleanup functions (such as closing open database connections or files and terminating long-running threads) when the servlet is explicitly unloaded or the Web server is shut down.
The SimpleServlet class, shown in Figure 1 (page 76), is an example of the most basic of Java servlets; it has the init, service, and destroy methods. SimpleServlet merely responds to a Web-browsing client, via HTML, with the message HELLO WORLD!
The Servlet Environment
Once initialized and loaded by the Web server, a servlet resides in memory, ready to handle client requests. The Web server calls the servlets service method to handle these client requests. For example, the following URL request entered on a browser will be routed to SimpleServlet:
http://someHost/server/SimpleServlet
(Note that a servlet may also be called from an HTML form.) To communicate with the servlet, the Web server passes two objects to the service method: an HttpServletRequest, which encapsulates communication from the client to the server, and an HttpServletResponse, which encapsulates the communication from the servlet back to the client. HttpServletRequest and HttpServletResponse are interfaces defined by the javax.servlet package.
Request Processing and Concurrency Problems
As I stated, only one instance of SimpleServlet will be initialized and loaded by the Web server. As shown in Figure 2, if several clients are accessing the Web server, the servlets service method will be called by the Web server to handle the multiple clients. Technically, the servlet is running as a thread within the context of the Web servers JVM. The resulting implication is that the calling clients will share the servlets resources, such as its instance variables, database connections, or external files.
For example, in Figure 3 (with the import statements and the init and destroy methods omitted for space), SimpleServlet is rewritten so that the service method saves the request object in the local variable clientRequest and then calls the displayWelcomMessage() method, whose purpose is to display the clients user name on the browser. The clients user name and password are retrieved from the clientRequest and stored in the username and password String variables before a response is made to the client with a welcome message.
All client calls will, however, share these variables, including the class variable clientRequest. But, when a simple servlet is written this way, there is no telling what each client will see on the browser; client 1 may see the user name from client 3 or client 2, which is unexpected behavior. Therefore, when developing servlets, you need to examine what might happen if multiple calls are made to the service method simultaneously. Specifically, examine what happens to servlet instance variables and how external resources are accessed in the service method.
Servlet Variables
A servlets instance variables (i.e., variables defined outside the scope of a method) maintain a client-specific state. For example, in Figure 3, the username and password
variables of SimpleServlet hold the calling clients input. The danger here is that the values of these class variables may end up in an unknown state. Consider the following scenario. Client 1 calls the SimpleServlets service method, sets the clientRequest variable, and is preempted. Client 2 also calls SimpleServlet and sets clientRequest.
Meanwhile, client 1 is back and continues to execute the displayWelcomMessage() method. The problem in this scenario is that, because the clients share the same instance variable, clientRequest, the values of username and password displayed for client 1 are those sent by client 2.
There are several solutions to this problem. The first solution is to avoid declaring servlet variables at the instance level by either passing objects from method to method or declaring variables local to a method. In Figure 4, for example, the clientRequest variable has been eliminated, and the username and password variables are local to the displayWelcomMessage() method.
The second solution is to develop the servlet so that it handles only one client request at a time. This solution is achieved by implementing the SingleThreadModel as well as extending the HttpServlet class, as shown in Figure 5. With this implementation, each service call is handled serially by the Web server (i.e., the server makes sure that your servlet runs only one service method at a time). However, for the purpose of scalability and performance, the Web server should be capable of spawning new instances of the servlet as well as supporting some kind of pooling mechanism to prevent blocked access to the servlet. The user may, otherwise, experience a degraded performance. If the servlet were to access external resources, however, there would still be the issue of handling concurrency problems (which Ill discuss in this article).
The third solution is to use Java threads and synchronize the methods or the specific block of code that is subject to concurrency problems. Java threads and synchronization may be new concepts to you, so let me explain them.
Accessing External Resources
Typical external resources include files, data structures, and databases that are accessed from the servlet via Java Database Connectivity (JDBC) or via Java Toolbox for AS/400s record-level access classes. (Note that the database connections are instance fields and are typically constructed in the init method.)
A single client should not monopolize a resource that it is not actively using. When a client acquires a resource and blocks that resource while awaiting another resource (such as I/O), Java will allow a second client to access the resource. This condition makes sense and is often desirable. However, the flow of events must be taken into account, since the first client may not be done with its task and the second client may want to access the same resources, leading to a deadlock.
Accessing external resources typically involves reading and writing to the resources
(i.e., any number of threads should execute read operations simultaneously but allow only one thread to write or to modify the resource at a time). For example, with SimpleServlet, you may want to add code to validate the user name and password in the database as well as log the date and time of the last access. This is done by adding the following methods to SimpleServlet:
public synchronized boolean ValidateUsernameAndPassword(...)
public synchronized void UpdateLoginDatetime(...)
With this approach, the database read and write operations of the validate and update methods are synchronized by including the keyword synchronized in their method declarations. The existence of the keyword synchronized creates a lock for each instance of the class. A thread that calls a synchronized method belonging to the object instance must obtain this lock and automatically release it upon exiting. Any thread that attempts to
execute ValidateUsernameAndPassword() or UpdateLoginDatetime() is forced to wait until the lock becomes available. This effectively serializes all operations on the database; however, there is no scalability with this approach, and it results in very poor performance if the number of clients grows too large.
Instead of synchronizing an entire method, you can synchronize blocks of code within the methods that modify the resource. In the example shown in Figure 6, the code within the synchronized block can be executed only a single thread at a time. When each thread enters the synchronized section, it obtains a lock for the object, or instance, represented by this. Again, this technique ensures that only one process can use the update and read operations at a given time. In addition, starvation may occur, whereby a runaway thread monopolizes the resource within the modify block, effectively causing all other modify threads to block or wait indefinitely.
Synchronizing sections of code in a class, however, does not prevent simultaneous reading from and writing to a resource. Two different instances of a class will have independent locks, each of which can be obtained by two separate threads. In one instance, a thread may obtain a lock upon entering a synchronized update section of code, while, in the second instance, another thread obtains a lock upon entering the read section. This may lead to unexpected results, such as data corruption.
Implementing the Wait and Notify Methods
The wait and notify methods in conjunction with synchronization allow for the controlled acquisition and release of a lock. The wait method allows a thread to release its lock, to put itself in a wait state, and then to continue execution at the point when it is notified to do so. The notify method allows the thread that is running to inform the thread that is waiting that it should resume and acquire the lock.
A thread in a wait state is, in effect, waiting for some external condition to become true; the lock is the mechanism that is used to indicate this condition. When the thread is notified that the lock is available, the awaited condition has become true. The code of Figure 7 illustrates how to use the wait and notify methods. In this example, the ProcessRequest object may be called from a servlet to read and modify a single external resource within the same operation. For example, a client may retrieve a value from a column in a database table and then update that column before proceeding. Implementing the wait and notify methods in this way allows simultaneous reads, blocks reads if the resource is being modified, and allows sequential writes (i.e., only one thread can write to the resource at a time, thus avoiding a race).
The Race to Robust Code
Servlets provide a great architecture for Web applications, but Java coders must take care to manage resource contention. Perhaps the best way to control contention is simply to code your servlets so its threads will have no shared resources, which is easily accomplished by using only local variables and refraining from the use of instance variables. Another simple option is to use the single thread optionbut only if you know that the servlet will have relatively few clients. But because instance variables are often a necessary evilsuch as JDBC connections and record-level access file opensyou will have resort to the use of synchronized blocks. You should always try to synchronize the smallest block of code possible in order to decrease the block time. The ultimate strategy for synchronization, because it removes any opportunity for deadlock, is to use wait and notify operations.
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
public class SimpleServlet extends HttpServlet
{
public void init(ServletConfig config)
throws ServletException
{
super.init(config);
}
public void service(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException
{
ServletOutputStream out = request.getOutputStream();
out.println("HELLO WORLD!");
}
public void destroy() {}
Figure 1: A simple servlet displays Hello World! on the browser.
Web Server
Simple Servlet Service( )....
Client 1
Client 3
Client 2
Response
Request
Figure 2: Multiple clients are handled by one servlet.
public class SimpleServlet extends HttpServlet
{
HttpServletRequest clientRequest;
String username = new String();
String password = new String();
public void service(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException
{
try {
clientRequest = request;
displayWelcomMessage(clientRequest);
} catch (Exception e) {}
}
public void displayWelcomMessage(
HttpServletRequest clientRequest)
throws Exception
{
username = clientRequest.getParameter("username");
password = clientRequest.getParameter("password");
ServletOutputStream out = request.getOutputStream();
out.println("Welcome " username + "! + ");
}
}
Figure 3: A simple servlet displays the input user name back to the client browser.
public class SimpleServlet extends HttpServlet implements
SingleThreadModel
{ public void service(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException
{
try {
displayWelcomMessage(request);
} catch (Exception e) {
e.printStackTrace();
}
}
public void displayWelcomMessage(
HttpServletRequest req)
throws Exception
{
String username = req.getParameter("user-name");
String password = req.getParameter("pass-word");
ServletOutputStream out = req.getOutputStream();
out.println("Welcome " username + "! + ");
}
public class SimpleServlet extends HttpServlet
{
HttpServletRequest clientRequest;
String username = new String();
String password = new String();
public void service(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException
{
try {
clientRequest = request;
displayWelcomeMessage(clientRequest);
} catch (Exception e) {}
}
public void updateDb() throws Exception{
// code omitted
synchronized(this) {
// update the database record
}
// code omitted
}
public void readDb(int prKey) throws Exception{
// code omitted
synchronized(this) {
// get the database record
}
// code omitted
}
public class ProcessRequest {
private boolean done_updating = FALSE;
public void synchronized update (object value) {
//update the resource
done_updating = TRUE;
notify();
}
public Object synchronized read() {
if (done_updating == FALSE)
wait();
Figure 4: Avoid instance variables at all costs.
Figure 5: The SingleThreadModel interface ensures that servlets run only one service method at a time.
Figure 6: Synchronizing a small block of code is more efficient than synchronizing a complete method.
//read from resource and return value
}
}
Figure 7: Ultimately, the use of wait and notify is the only way to ensure a deadlock does not occur.
LATEST COMMENTS
MC Press Online