To create a servlet, you must perform the following tasks:
For a description of design considerations, see Designing Servlets. For information about creating the files that make up a servlet, see the following sections:
Writing the Servlet's Class File
This section describes how to write a servlet, and the decisions you have to make about your application and your servlet's place in it.
Creating the Class Declaration
To create a servlet, write a public Java class that includes basic I/O support as well as the package javax.servlet. The class must extend either GenericServlet or HttpServlet. Since NAS servlets exist in an HTTP environment, the latter is recommended.
The following example header shows the declaration of an HTTP servlet called myServlet:
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class myServlet extends HttpServlet {
servlet methods...
}
Overriding Methods
Next, you must override one or more methods to provide instructions for the servlet to perform its designated task.
All of the processing done by a servlet on a request-by-request basis happens in the service methods, either service() for generic servlets or one of the doOperation() methods for HTTP servlets. This method accepts the incoming request, processes it according to the instructions you provide, and directs the output appropriately. You can create other methods in a servlet as well.
Business logic may involve authenticating the application's user name and password, accessing a database to perform a transaction, or passing the request to an EJB.
Overriding init
You can override the class initializer init() if you need to initialize or allocate resources for the life of the servlet instance, such as a counter. The init() method runs after the servlet is instantiated but before it accepts any requests. For more information, see the servlet API specification.
Note that all init() methods must call super.init(ServletConfig) in order to set their scope correctly. This makes the servlet's configuration object available to the other methods in the servlet.
The following example init() method initializes a counter by creating a public integer variable called thisMany:
public class myServlet extends HttpServlet {
int thisMany;
public void init (ServletConfig config) throws ServletException {
super.init(config);
thisMany = 0;
}
Now other methods in the servlet can access this variable.
Overriding destroy
You can override the class destructor destroy() to write log messages or to release resources that would not be released through garbage collection. The destroy() method runs just before the servlet itself is deallocated from memory. For more information, see the servlet API specification.
For example, the destroy() method could write a log message like this, based on the example for Overriding init above:
out.println("myServlet was accessed " + thisMany " times.\n");
Overriding service, doGet, and doPost
When a request is made, NAS hands the incoming data to the servlet engine, which processes the request, including form data, cookies, session information, and URL name-value pairs, into an object of type HttpServletRequest called the request object. Client metadata is encapsulated as an object of type HttpServletResponse and is called the response object. The servlet engine passes both objects as parameters to the servlet's service() method.
The default service() method in an HTTP servlet routes the request to another method based on the HTTP transfer method (POST, GET, etc.) For example, HTTP POST requests are routed to the doPost() method, HTTP GET requests are routed to the doGet() method, and so on. This enables the servlet to perform different processing on the request data depending on the transfer method. Since the routing takes place in service(), you generally do not override service() in an HTTP servlet. Instead, override doGet() and/or doPost(), etc., depending on the type of request you expect.
Note.
The automatic routing in an HTTP servlet is based simply on a call to request.getMethod(), which provides the HTTP transfer method. In NAS, request data is already preprocessed into a name-value list by the time the servlet sees the data, so you could simply override the service() method in an HTTP servlet without losing any functionality. However, this does make the servlet less portable, since it is now dependent on preprocessed request data.
You must override the service() method (for generic servlets) or the doGet() and/or doPost() methods (for HTTP servlets) to perform the tasks needed to answer the request. Very often, this means accessing EJBs to perform business transactions, collating the needed information (in the request object or in a JDBC ResultSet object), and then passing the newly generated content to a JSP for formatting and delivery back to the client.
Most operations that involve forms use either a GET or a POST operation, so for most servlets you override either doGet() or doPost(). Note that you can implement both methods to provide for both types of input, or simply pass the request object to a central processing method, as in the following example:
public void doGet (HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
All of the actual request-by-request traffic in an HTTP servlet is handled in the appropriate doOperation() method, including session management, user authentication, dispatching EJBs and JSPs, and accessing NAS features.
Note.
If you have a servlet that you intend to also call using a RequestDispatcher method include() or forward() (see Calling a Servlet Programmatically), be aware that the request information is no longer sent as HTTP POST, GET, etc. RequestDispatcher methods always call service(). In other words, if a servlet overrides doPost(), it may not process anything if another servlet calls it, if the calling servlet happens to have received its data via HTTP GET. For this reason, be sure to implement routines for all possible types of input, as explained above.
Note.
Arbitrary binary data, like uploaded files or images, can be problematic, since the web connector translates incoming data into name-value pairs by default. You can program the web connector to properly handle this kind of data and package it correctly in the request object. See Programming the Web Connector for information about handling this type of data in the web connector.
Accessing Parameters and Storing Data
Incoming data is encapsulated in a request object. For HTTP servlets, the request object is of type HttpServletRequest. For generic servlets, the request object is of type ServletRequest. The request object contains all the parameters in a request, and also set your own values in the request. The latter are called attributes.
You can access all the parameters in an incoming request by using the getParameter() method. For example:
String username = request.getParameter("username");
You can also set and retrieve values in a request object using setAttribute() and getAttribute(), respectively. For example:
request.setAttribute("favoriteDwarf", "Dwalin");
This reveals one way to transfer data to a JSP, since JSPs have access to the request object as an implicit bean. For more information, see Using Java Beans.
Handling Sessions and Security
From the perspective of a web server or application server, a web application is a series of unrelated server hits. There is no automatic recognition that a user has visited the site before, even if their last interaction was mere seconds before. A session provides a context between multiple user interactions by "remembering" the application state. Clients identify themselves during each interaction by way of a cookie, or, in the case of a cookie-less browser, by placing the session identifier in the URL.
A session object can store objects, such as tabular data, information about the application's current state, and information about the current user. Objects bound to a session are available to other components that use the same session.
For more information, see Creating and Managing User Sessions.
Upon a successful login, you can direct a servlet to establish the user's identity in a standard object called a session object that holds information about the current session, including the user's login name and whatever other information you want to retain. Application components can then query the session object to obtain authentication for the user.
To provide a secure user session to your application, see Writing Secure Applications.
Accessing Business Logic Components
In the NAS programming model, you implement business logic, including database or directory transactions and complex calculations, in EJBs. A pointer to the request object can be passed as a parameter to an EJB, which then performs the specified task.
You can store the results from database transactions in JDBC ResultSet objects and pass pointers to these objects on to other components for formatting and delivery to the client. You can also store the results in the request object using the method request.setAttribute(), or in the session using the method session.putValue(). Objects stored in the request object are only valid for the length of the request, or in other words for this particular servlet thread. Objects stored in the session persist for the duration of the session, which can span many user interactions.
Note.
JDBC ResultSets are not serializable, and thus can not be distributed among multiple servers in a cluster. For this reason, do not store ResultSets in distributed sessions. For more information, see Creating and Managing User Sessions.
This example shows a servlet accessing an EJB called ShoppingCart by creating a handle to the cart by casting the user's session ID as a cart after importing the cart's remote interface. The cart is stored in the user's session.
import cart.ShoppingCart;
...
// Get the user's session and shopping cart
HttpSession session = request.getSession(true);
ShoppingCart cart = (ShoppingCart)session.getValue(session.getId());
// If the user has no cart, create a new one
if (cart == null) {
cart = new ShoppingCart();
session.putValue(session.getId(), cart);
}
You can access EJBs from servlets by using the Java Naming Directory Interface (JNDI) to establish a handle, or proxy, to the EJB. You can then refer to the EJB as a regular object; any overhead is managed by the bean's container.
This example shows the use of JNDI to look up a proxy for the shopping cart:
String jndiNm = "Bookstore/cart/ShoppingCart";
javax.naming.Context initCtx;
Object home;
try {
initCtx = new javax.naming.InitialContext(env);
} catch (Exception ex) {
return null;
}
try {
java.util.Properties props = null;
home = initCtx.lookup(jndiNm);
}
catch(javax.naming.NameNotFoundException e)
{
return null;
}
catch(javax.naming.NamingException e)
{
return null;
}
try {
IShoppingCart cart = ((IShoppingCartHome) home).create();
...
} catch (...) {...}
For more information on EJBs, see Introducing Enterprise JavaBeans.
Handling Threading Issues
By default, servlets are not thread-safe. The methods in a single instance of each servlet can be executed numerous times (up to the limit of available memory) simultaneously, with each execution occurring in a different thread though only one copy of the servlet exists in the servlet engine.
This arrangement is efficient in how it uses system resources, but it can be dangerous because of how memory is managed in Java. Because parameters (objects and variables) are passed by reference, different threads can overwrite the same memory space as a side effect.
To make a servlet (or a block within a servlet) thread-safe, do one of the following:
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class myServlet extends HttpServlet {
public void doGet (HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
//pre-processing
synchronized (this) {
//code in this block is thread-safe
}
//other processing;
}
public synchronized int mySafeMethod (HttpServletRequest request) {
//everything that happens in this method is thread-safe
}
}
Create a single-threaded servlet by implementing SingleThreadModel. In this case, when you register a single-threaded servlet with NAS, the servlet engine creates a pool of 10 servlet instances (i.e., 10 copies of the same servlet in memory) that can be used for incoming requests. A single-threaded servlet can be slower under load because new requests must wait for a free instance in order to proceed, but this is less of a problem with distributed, load-balanced applications since the load automatically shifts to a less busy kjs process. import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class myServlet extends HttpServlet
implements SingleThreadModel {
servlet methods...
}
Delivering Results to the Client
The final activity of a user interaction is to provide a response page to the client. The response page can be delivered to the client in two ways:
Creating a Response Page in a Servlet
You can generate the output page within a servlet by writing to the output stream. The recommended way to do this depends on the type of output.
You must always specify the output MIME type using setContentType() before any other output commences, as in this example:
response.setContentType("text/html");
For textual output, such as plain HTML, create a PrintWriter object and then write to it using println. For example:
PrintWriter output = response.getWriter();
output.println("Hello, World\n");
For binary output, you can write to the output stream directly by creating a ServletOutputStream object and then writing to it using print(). For example:
ServletOutputStream output = response.getOutputStream();
output.print(binary_data);
Note that your servlet can not call a JSP if you create a PrintWriter or ServletOutputStream object.
Note.
If you are using NAS with Netscape Enterprise Server (NES), do not set the date header in the output stream using setDateHeader(). Doing so results in a duplicate date field in the HTTP header of the response page that the server returns to the client. This is because NES automatically provides this header field. Conversely, Microsoft Internet Information Server (IIS) does not add a date header, so you must provide one in your code.
Creating a Response Page in a JSP
Servlets can invoke JSPs in two ways:
Note.
You identify which JSP to call by specifying a URI, or Universal Resource Identifier. This is normally a path relative to AppPath, with a leading slash / if the JSP is part of the generic application. For instance, if your JSP is part of an application called OnlineOffice, you would refer to a JSP called ShowSupplies.jsp as OnlineOffice/ShowSupplies.jsp. On the other hand, if ShowSupplies.jsp is part of the generic application and is in a subdirectory, specify the subdirectory with a leading slash, as in /GenericJSPs/ShowSupplies.jsp.
For more information about JSPs, see Presenting Application Pages with JavaServer Pages.
Creating the Servlet's Configuration File
Servlet configuration files contain metadata, information about the servlet that identifies it and establishes its role in the application. Some optional NAS features are controlled by configuration entries.
Each servlet requires a configuration file to identify it to NAS. A single configuration file can configure multiple servlets. Additionally, each application must have a configuration file that contains references to all servlet configuration files. A configuration file is a hierarchical list of name-value pairs with ordinal types. The format is called NTV (name-type-value). Each NTV file must contain certain fields in order to properly register the servlet with NAS.
For more information about creating servlet configuration files, see Creating Servlet and Application Configuration Files.
Dynamically Reloading Servlets
You can reload servlets into NAS without restarting the server. Simply overwrite the servlet by redeploying. NAS "notices" the new component and reloads it within 10 seconds. For more information, see Dynamic Reloading.
If you make any post-deployment changes to the ServletRegistryInfo section in a servlet's configuration file, you must stop the server and load the new configuration into the NAS registry in order to make the new configuration visible to NAS. This is because the configuration is read only when servlets are first instantiated.
To load a new servlet configuration file into NAS, perform the following steps to make the changes active:
Stop the server.
Deploy the new configuration file.
Execute servletReg configFile.ntv on the server to register the new configuration file.
Restart the server.
Accessing Optional NAS Features
NAS provides many additional features to augment your servlets for use in a NAS environment. These features are not a part of the official specifications, though some are based on emerging Sun standards and will conform to those standards in the future.
For more details on NAS features, see Taking Advantage of NAS Features.
NAS also provides support for more robust sessions, based on a model from a previous version of NAS. This model uses the same API as the session model described in the servlet 2.1 specification, which is also supported. For more details on distributable sessions, see Creating and Managing User Sessions.
Finally, the NAS implementation of security in servlets is a feature, since the servlet 2.1 specification makes no provision for security. The security model described in the forthcoming servlet 2.2 specification mirrors the NAS model for servlets. For more information about security in servlets, see Writing Secure Applications.
|