There's a lot of action going on with Java servlets. The recent public release of Java Servlet Specification v2.2 by Sun Microsystems enhances the functionality of the programming model and the deployment and runtime infrastructure of servlets, which provides for better packaging, security, distribution and management of servlet-based Web applications. The servlet technology is now a part of the Java 2 Enterprise Edition (J2EE) architecture and is expected to play an important role in the Web/enterprise application server market that has hitherto been dominated by proprietary programming models.
When Java servlets were introduced by Sun in early 1997, the primary goal was to provide a Java language alternative to the CGI (Common Gateway Interface) model. Accordingly, the initial model was designed to serve dynamic content for incoming HTTP requests. Those who followed servlets from their inception will remember their early description as applets on the server side. Such descriptions were perhaps appropriate with the initial servlet model. Over the past two years, however, Sun has revised the specification significantly to let servlets cater to developing production-quality Web applications. Most of the development, deployment and runtime features of the servlet model have changed considerably over several revisions. Servlets have now passed their infancy, and are beginning to be used for developing mission-critical Web applications with commercial application servers such as WebSphere, Netscape Application Server, NetDynamics, WebLogic and Orion.
So where do servlets stand today?
Servlet Programming Model - Overview
Java servlets are small, server-side programs that can be composed into dynamic Web applications. Servlets aren't user-invokable applications, but are hosted on servlet containers (more about them later).
Servlet containers operate in tandem with Web servers, and invoke servlets based on requests from these servers. When deploying servlets onto servlet containers, you can also specify canonical names to them. The servlet container maintains the mapping between these canonical names and servlet classes.
The servlet programming model is lightweight. Its core classes are HttpServlet, HttpServletRequest, HttpServletResponse, HttpSession, ServletContext and ServletConfig. Of these, the HttpServlet is the class that your application servlets extend from; the rest are interfaces. Container vendors provide implementations for all these classes/interfaces.
Servlet programming consists of overriding the init(), service() (or one of its derivatives to process GET, POST, HEAD, DELETE, OPTIONS, PUT and TRACE requests) and destroy() methods of the HttpServlet class. Refer to Table 1 for a brief overview of the programming model. In this table the core classes/interfaces of the servlet API are categorized based on their responsibilities.
At deployment time the site administrator configures the Web server to delegate all servlet requests to the servlet container. When a client user agent (typically a Web browser) sends an HTTP request whose request path maps to a servlet, the Web server delegates the request to the servlet container. In response to this, the servlet container creates HttpServletRequest and HttpServletResponse objects, sets up the environment, creates/locates a servlet instance that corresponds to the path specified in the incoming request, and invokes its service method with the HttpServletRequest and HttpServletResponse objects. The HttpServletRequest and HttpServletResponse objects are Java language objects that encapsulate the request and response streams from the client. The servlet's service method can process the request and write content dynamically to the response stream.
It can also forward the request and response objects to a JSP to create dynamic content. The service method can make use of additional utility classes and back-end layers composed of databases, Enterprise JavaBeans, CORBA servers and more to process the request.
This model is similar to the well-known CGI model for building Web sites with dynamic content. In addition to this basic facility, the servlet specification has facilities for session tracking and state management. (For an introductory discussion of these aspects, see references 3 and 4 in the Resources section at the end of this article.) However, the model and its level of abstraction aren't adequate for building enterprise-scale Web applications. These require higher-level abstractions for security, scalability, management and more. In the remaining sections of this article we'll look at some of the advanced facilities that the current servlet specification provides for building and managing large Web applications.
Web Applications, Servlets and Virtual Sandboxes
The J2EE architecture specifies Web applications as the Web-interface part of the J2EE's enterprise computing model. Since the J2EE model is emerging as a standard application server model, let's look at servlets from the point of view of Web applications.
A Web application is a collection of Java servlets, JSP pages, HTML/XML documents and other resources organized in a structured hierarchy of directories. You can also package constituents of Web applications into Web archive (WAR) files. A WAR file is primarily an application deployment unit.
Servlet containers provide the runtime environment for servlet-based Web applications. They also provide a host of other services, some of which we'll discuss here.
At the time of deployment, you can associate a Web application with a specific path of the Web server. This path serves as a document root for serving servlets and other resources that are part of the Web application and therefore define a name tree under this path. For example, if you have a Web application called MyWebApp, you can associate it with the path /MyWebApp of your Web server. All the constituents of the public directory of your Web applications as well as servlets can be accessed relative to this path.
As shown in Figure 1, the directory structure of a Web application consists of two parts. The first part is the public directory structure containing HTML/XML documents, JSP pages, images, applets, and so on. The container appropriately serves the directory's contents against incoming requests. The second part is a special WEB-INF directory that contains the following:
- A /classes subdirectory with all the class files (servlets and other helper classes) of your application
- A /lib subdirectory containing all the JAR files of your application
- A web.xml file that is the deployment descriptor of the Web application
The contents of this directory are for use only by the containers, and the containers won't serve these directly to clients.
What are the implications of the above organization?
First, a Web application (whether archived into a WAR file or not) allows you to package all constituents of the application into one physical unit. Prior to the introduction of the notion of Web applications, you'd usually do the following to deploy an application:
When compared to this, the Web application directory structure helps to organize Web applications in a clutterless manner. The publicly accessible resources, as well as your servlet and other classes, can now remain together. Thus you can deploy multiple Web applications independently on the same container. Container vendors provide facilities to deploy or remove Web applications as independent units.
- Copy all your servlets under a directory (which is generally configurable) specified by the container vendor.
- Configure the servlets following the procedure/user interfaces specified by the container vendor.
- Keep all the publicly accessible resources (such as HTML files and images) under the Web server's public directory tree.
The second important implication is the deployment descriptor. This is an attempt toward standardizing the deployment configuration of Web applications. The deployment descriptor, an XML document with a DTD (Document Type Definition - the current standard for specifying XML documents) specified in the Java servlet specification, allows you to postpone certain decisions from the build time to the deployment time. Examples of such decisions include initialization data for your servlets, canonical names for servlets, MIME type mappings, security (more about this later), database parameters and log file names. It's good practice to defer most of the hard-coding from your servlets to the deployment descriptor.
The third implication is that each Web application is associated with a different context, so you can bundle your servlets in more than one application. Instances of such servlets remain independent in the same Java Virtual Machine of the container and don't share the same context (more about context in the next section). Thus this notion of a Web application introduces a virtual sandbox within a JVM. Note: Previously, you could deploy the same servlet more than once with different canonical names and initialization parameters. However, all such servlets share the same context since there was no notion of a Web application.
Finally, different Web applications hosted on the same container can't share clients' session information. That is, if you have two Web applications deployed on the same container, servlets in both applications see two different sessions for the same client. This is a marked difference from the old servlet model in which a container (aka servlet engine) can establish only one client session. You now need to implement more ingenious ways to share data between your Web applications.
Containers - Not Just Hosts
The container, the runtime component of the servlet architecture, provides runtime and network services for hosting servlet-based Web applications. Note: While the servlet specification uses the term servlet container, the J2EE specification uses a more generic term, Web container, to denote that Web containers can manage Web applications containing JSP pages in addition to servlets.
Also, prior to version 2.2 of the specification, servlet engine was the term used to denote what is now called a container. While these terms represent the same in basic functionality, the change in language indicates a shift of emphasis from processing - that's what an engine does - to providing a runtime for hosting.
Although a container essentially provides runtime and network services for hosting Web applications, it's worthwhile to interpret a container as a request interceptor between network services and the servlet instances. This allows us to discuss what goes on between the container receiving a request and a servlet instance being invoked to handle that request. It also lets us look at certain innovations that containers can implement by virtue of their request-interception capabilities.
First, what goes on within a typical servlet container after it receives a request and before it invokes a servlet instance?
From a client's point of view, the container does the above-mentioned tasks while intercepting the request (see Figure 2) and delegating to a servlet instance, although from the container's point of view these are essential tasks in handling a request (see Figure 3).
- Request and response marshaling and unmarshaling: The container maps the requests and responses (typically over HTTP) into Java language objects (the HttpServletRequest and HttpServletResponse objects) that servlet instances can access. This eliminates certain HTTP protocol-level semantics from servlet development. Examples include extracting GET/POST parameters, cookies and other headers from requests, setting cookies, and so on.
- Request mapping: How does the container resolve an incoming request to a servlet class? This is based on canonical names assigned to servlets (in the deployment descriptor) and the root of the Web application. For example, consider a Web application with its root at /MyWebApp. If it consists of a servlet SnoopServlet with name Snoop (specified in the deployment descriptor), the container maps all requests to http://my.site.com/MyWebApp/Snoop to an instance of SnoopServlet. Refer to the Servlet API specification for more details on other mapping rules. The servlet container maintains a naming context for all servlet classes. Within this context the container can identify a servlet class to handle the request.
- Environment setup: Containers also create and manage the environment in which servlet instances exist and operate. As discussed earlier, the environment includes the ServletContext, ServletConfig and HttpSession objects.
- Life cycle: Containers are responsible for managing the life cycle of servlet instances, which involves locating or creating, initializing and destroying servlet instances. Containers manage the life cycle of HttpSession and ServletContext objects. Based on your servlet's threading model, they can also manage a pool of servlet instances and allocate instances to incoming requests.
In addition, containers can perform certain advanced tasks while intercepting a request.
Request serialization is one of the techniques used to create client/server applications with a limited number of threads/objects to handle requests. In the case of servlets, servlet containers may choose to implement request serialization for SingleThreadModel servlets. That is, the container may maintain a fixed number of instances of such servlets, and can keep the requests waiting in a queue to be processed when the number of concurrent requests to the servlet is more than the available number of instances. When an instance becomes free, the container can delegate a waiting request to it. In general, request serialization requires the underlying deployment framework (in this case, the container) to be able to represent requests in serializable structures, then deserialize them later for processing.
Such a capability is inherent with servlet containers since containers receive HTTP requests and map them to method invocations on service methods of servlet instances.
Declaritive security is a recent addition to the servlet API. A traditional approach to implementing authentication and access control is called programmatic security and involves the following steps:
With this approach each servlet would be programmed "to protect itself" from unauthorized access. To protect static resources (HTML pages, images, etc.), you'll be required to use the Web server's authentication mechanisms. That is, static and dynamic constituents of your application require different approaches to implementing authentication.
- Program the application to have one more entry point with login forms.
- Authenticate the user after he or she submits the login form, and save some kind of flag in the associated session to indicate that the user has been authenticated.
- To prevent users from directly accessing your servlets, each servlet should check for this flag.
The servlet specification has now introduced another approach, declarative security, in which the container takes the responsibility of protecting Web applications based on the container's interception ability. Here's a scenario:
- Use the container-provided tools to create, for example, users, passwords and groups/
- Add a security-constraint entry in the deployment descriptor of your Web application (see Listing 1 for an example). All GET and POST requests to resources under /MyWebApp require the user to have a role called manager. Based on this information, the container can enforce a login before delegating the request to servlets or before serving static resources. In the example below, the container uses form-based authentication and invokes the login.html page for any request under /MyWebApp.
Following are some of the implications of this approach:
- When the user requests a resource for the first time, say, /MyWebApp/buy, the container automatically serves the /MyWebApp/
login.html page, which requires the user to fill in the login name and password. Once the user submits this form, and if the supplied credentials belong to a user with the role "manager," the container authenticates the user, then redirects the user to /MyWebApp/buy. If the credentials don't match, the container serves the specified /MyWebApp/error.html. The authentication information will be valid for the life of the session.
In addition to the form-based approach, the servlet specification also supports basic, digest and HTTP client-certificate-based authentication.
- Since it's based on the URL patterns associated with different constituents of Web applications, it applies uniformly to servlets as well as static resources.
- The servlets needn't be programmed to check credentials every time they're invoked.
- The security requirements of the application can be changed at deployment time without having to change/recompile servlets.
Passivation is the process of swapping contents of session and context data to a persistent storage (for example, serialized to files). Containers can implement passivation to free some of the session and context objects and reuse them for different users/applications. The passivated sessions/contexts can be activated on demand when there's a client request requiring the session/context.
Distribution and Scalability
The new servlet specification addresses the issue of scalability by providing for distributable Web applications and distributable containers. A distributable container consists of a number of JVMs running on one or more host machines. In this setup you can deploy your servlets on a number of JVMs. Depending on the load-balancing strategy (vendor-specific), containers route incoming requests to one of these JVMs. In such a setup all requests from a client may or may not be handled by the same JVM. For seamless processing of requests in these cases, the container can employ one of the following strategies:
The first approach is sticky in nature and doesn't require elaborate distribution of state information across multiple JVMs. The second strategy is more performance intensive and may defeat the purpose of load balancing. Hence, the servlet specification doesn't require the second approach.
- A single JVM may handle all requests that are part of a single client session. That is, the container maintains an association between a client/session and a JVM. This JVM affinity preserves the integrity of state and sessions, since within a session the client requests are always served by the same JVM (see Figure 4A).
- Requests that are part of a single session may not be handled within a single JVM (see Figure 4B). In this case the container doesn't guarantee JVM affinity and will be required to provide distributed state- and session-management facilities. This involves distributing the state (request-specific as well as application state-specific) among all the nodes in the cluster after processing each request, making one of the nodes responsible for storing the state, or a combination of both.
Instead, it expects the containers to make sure that a single node in a cluster will handle all requests pertaining to a user session. However, we can expect some application server vendors to provide instance-independent load balancing.
For your Web application to avail itself of this facility, you should mark your Web applications as distributable in the deployment descriptor. Once marked, the container can activate the servlets contained in the Web application on multiple JVMs. The container can then employ any load-balancing strategy to distribute the incoming requests to servlets in one of the JVMs.
Although thus far the servlet specification is silent about the failover capability of servlet containers, it's one of the features we can expect from some of the Web application server vendors. It requires that the container swap the HttpSession (and perhaps ServletContext too) objects of each application (in each JVM) to a persistent media whenever there's a change in the state of these objects. This way, active client sessions can be reactivated upon restarting the container (or the host nodes thereof).
In addition, containers may implement session/context swapping between JVMs in the cluster to shift load from one JVM to another or to add/remove hosts in the cluster.
Java servlets are one of the major components of the J2EE architecture. As most major application server vendors are moving in the direction of the J2EE application programming model, Java servlets have gained added importance. Servlets are now replacing most of the older, proprietary programming models for building Web applications.
To summarize this discussion, the servlet programming model now addresses enterprise-level Web application development. The notion of Web applications and containers has profound implications on the way dynamic Web sites can be built and managed - a major leap from the earlier single-JVM, single-application model.
In Part 2 of this article we'll look at certain precautions that need to be taken while building servlet-based Web applications in the changed scenario.
- Java Servlet Specification v2.2:
- Java 2 Enterprise Edition:
- Hunter, J., and Crawford, W. (1998). Java Servlet Programming. O'Reilly & Associates.
- Servlet Essentials:
Dr. Subrahmanyam is a technical consultant with the electronic
commerce division of Wipro Technologies, based in Bangalore, India.
You can visit him at his Web site, www.Subrahmanyam.com.
He can be reached at: [email protected].
<!-- Define a collection of resources -->
<web-resource-name>My Web Application</web-resource-name>
<!-- Define authentication requirements -->
<!-- Specify how user-container communication should be handled -->
<!-- Specify how authentication should be done -->