Richard G Baldwin (512) 223-4758, baldwin@austin.cc.tx.us, http://www2.austin.cc.tx.us/baldwin/

Servlets, Introduction to Servlets

Java Programming, Lecture Notes # 680, Revised 2/7/99.

Preface

Introduction

Using the servletrunner Program

Sample Program

Interesting Code Fragments
Program Listing


Preface

Students in Prof. Baldwin's Advanced Java Programming classes at ACC will be responsible for knowing and understanding all of the material in this lesson beginning with the spring semester of 1999.

This lesson was originally written on January 3, 1999. The sample servlet was tested using the JDK 1.2 download package from JavaSoft along with the Java Servlet Development Kit (JSDK) 2.0 from JavaSoft. All tests were performed under Win95.

The servlet was tested using the servletrunner program that is included in the JSDK with the servletrunner program running on the same machine and also with the servletrunner running on a different machine on the network.

The servlet was also tested using the JavaSoft Java Web Server 1.1.1 program running on a different machine on the network.

Introduction

According to JavaSoft:

Servlets are modules that run inside request/response-oriented servers, such as Java-enabled web servers, and extend them in some manner. For example, a servlet might be responsible for taking data in an HTML order-entry form and applying the business logic used to update a company's order database. Servlets are to servers what applets are to browsers.

The Servlet API, which you use to write servlets, assumes nothing about how a servlet is loaded, the server environment in which the servlet runs, or the protocol used to transmit data to and from the user. This allows servlets to be embedded in many different web servers.

Servlets are an effective substitute for CGI scripts: they provide a way to generate dynamic documents that is both easier to write and faster to run. They also address the problem of doing server-side programming with platform-specific APIs. Servlets are developed with the Java Servlet API, a standard Java extension. While it is not part of the core Java framework, which must always be part of all products bearing the Java brand, it will be made available with such products by their vendors as an add-on package. It is already supported by many popular web servers.

In some ways, a servlet is similar to an applet. An applet is a chunk of Java code that executes under control of a browser. A servlet is a chunk of Java code that executes under control of a server program.

You must run your servlet under the control of a Java-enabled server program. There is a growing list of such programs available. As of 1/3/99, a list of Java-enabled server programs was available at http://jserv.javasoft.com/products/java-server/servlets/environments.html.

Material is available all over the web, which expounds on the advantages of servlets over other approaches as well as providing examples of how you might use servlets. Therefore, I won't repeat that information here.

Using the servletrunner Program

You can test your servlet under an actual Java-enabled server. In addition, JavaSoft provides a program, similar to appletviewer that can be used to test your servlets. The name of the program is servletrunner and it is included in the JSDK download package. The servletrunner program tends to be a little easier to use than an actual Java-enabled server. With servletrunner you avoid some of the administration issues that accompany the use of a real server program.

The discussion in this lesson applies to the Win95 version of the JSDK. Hopefully the other versions are similar.

According to JavaSoft:

The servletrunner is a small utility, intended for testing. It is multithreaded, so it can run more than one servlet. It can be used therefore, to run multiple servlets simultaneously, or to test one servlet that calls other servlets in order to satisfy client requests. Unlike some web servers, it does not automatically reload servlets when they are updated. Because it is small, however, there is very little overhead associated with stopping and restarting it in order to use a new version of a servlet.

Pay particular attention to the last two sentences in the above description to save yourself a lot of frustration. What this means is that once you run a servlet in servletrunner, simply replacing the servlet class file with a new version of the class file does not cause servletrunner to forget the original version. It will continue to execute the original version even if you provide a new class file. To cause it to load the new version, you must terminate and restart servletrunner.

Once you have installed the JSDK, locate the folder containing the program named servletrunner and include that folder in your path environmental variable.

You can obtain a usage message about servletrunner by starting it with the -help flag. This is what I got when I did that with version 2.0 on 1/3/99:

servletrunner -help
Usage: servletrunner [options]
Options:
  -p port     the port number to listen on
  -b backlog  the listen backlog
  -m max      maximum number of connection handlers
  -t timeout  connection timeout in milliseconds
  -d dir      servlet directory
  -s filename servlet property file name

You can control the values for any of these parameters by entering command-line arguments when you start the program. There are default values for each parameter. The default values are displayed if you simply start the program running with no command-line arguments. The default values for version 2.0 on 1/3/99 are as shown below:

servletrunner
servletrunner starting with settings:
  port = 8080
  backlog = 50
  max handlers = 100
  timeout = 5000
  servlet dir = .\examples
  document dir = .\examples
  servlet propfile = .\examples\servlet.properties

Pay attention to the setting for servlet dir. This is the name and location of the folder that must contain the class file for your servlet. You can copy your class files to that folder, or you can change that setting to make it match the directory that contains your class file when you start the program running.

There is more than one way to execute a servlet. The way that is illustrated by the sample program in this lesson is to call the servlet directly by putting its URL in your browser. The URL for a servlet has the following general form:

http://machine-name:port/servlet/servlet-name

where servlet-name corresponds to the name that you have given to your servlet. I will discuss some of the other ways of executing a servlet in subsequent lessons.

For example, the following URL was used in the browser to execute the sample servlet in this lesson:

http://webserver:8080/servlet/Servlet01

where:

An alternative URL used in the browser to execute the sample servlet in this lesson was:

http://localhost:8080/servlet/Servlet01

where:

In either case, the servletrunner program must be running before the servlet is executed. Also, in both cases, the portion of the URL that reads /servlet/ is the same. Unlike other URLs, this is not the actual name of a directory on the server machine. Rather, it tells the server program to invoke the servlet whose name follows. (In this particular case, the actual servlet class file was in a directory named examples which was the default for servletrunner shown earlier.)

Sample Program

At this point, I will begin discussing servlets in light of a sample servlet program. This program will illustrate some, but not nearly all of material that I will discuss. The purpose of this program is to illustrate a very simple servlet and to serve as a vehicle for discussion of many aspects of servlets.

When executed, The servlet produces the following text in the browser window in large red letters.

Hello Big Red World

Please note that the use of servlets is not restricted to HTTP servers. However, the discussion in this lesson will generally apply only to HTTP servers.

Interesting Code Fragments

As is often the case, the first fragment shows the import statements necessary to support servlet programming. The two javax packages are part of the JSDK that must be downloaded separately from the JDK at JavaSoft. Some of the early JavaSoft literature indicates that the servlet packages would be incorporated directly into JDK 1.2, but that doesn't seem to be the case..

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

All servlets must implement the Servlet interface. You can implement it directly. However, it is more common to implement it indirectly by extending a class that implements the interface (such as HttpServlet). The Servlet interface declares methods for managing the servlet and its communications with clients. You will need to provide some or all of these methods when you write your servlet.

The next fragment shows the beginning of the controlling class for the servlet that extends HttpServlet.

public class Servlet01 extends HttpServlet{

Two objects are passed to a servlet when it called by a client:

This can be accomplished in more than one way. In this servlet, the doGet() method is overridden, and the two objects are passed in as parameters. Note that the doGet() method throws an exception of type ServletException. In this case, some of the code inside the method also throws an IOException.

  public void doGet(HttpServletRequest req, 
                    HttpServletResponse res)
                      throws ServletException, IOException{

As a practical matter, the JSDK and a Java-enabled server provide a method for each of the commands that an HTTP client can send to its server. When the server receives a command from the client, the corresponding method is called on the servlet. As the servlet programmer, you override some or all of these methods to provide the desired behavior.

The doGet() method corresponds to the HTTP GET operation. If you don't override the method, the default implementation reports an HTTP BAD_REQUEST error.

Overriding this method to support the GET operation also automatically supports the HEAD operation. (HEAD is a GET that returns no body in the response. It just returns the requested header fields.)

The fundamental purpose of the GET operation is to make it possible for the client to get something from the server. When you override the doGet() method, you should

The headers should include content type and encoding. The content type must be set before the writer is accessed.

This servlet simply constructs and returns an HTML file containing formatted text. The following fragment shows the use of setContentType() to set the content type being returned before accessing the writer. Then the getWriter() method is used to get the output stream.

    res.setContentType("text/html");
    PrintWriter out = res.getWriter();  

You will need to do some research on your own to learn about the different content types that can be returned by a servlet.

That brings us to the next fragment which constructs and returns the various elements of the HTML page and then terminates the doGet() method. If you have written any HTML, you will probably recognize all of this. If not, you will need to do a little research on your own to learn about the various parts of an HTML page. At this level, it is all very simple. The boldface material between the <BODY> and </BODY> tags represent the real information content of the page. The rest is mostly formatting information.

This body material says to

In case you haven't already figured it out, most HTML tags such as <BODY> require a terminating tag such as </BODY>. Thus, most tag types come in pairs, but this is not true of all tags. An HTML page is pure text with the various tags specifying how the browser is to interpret and display that text. Thus, in this case, the code simply prints the requisite text to the output stream named out. Recall from above that in this case, the output stream was obtained by invoking the getWriter() method on the HttpServerResponse object which takes care of transporting the text back to the client.

    out.println("<HTML>");
    out.println("<HEAD><TITLE=Servlet01</TITLE></HEAD>");
    out.println("<BODY>");
    
    out.println("<h1 align=\"center\">"
               +"<font color=\"#FF0000\">");
    out.println("Hello Big Red World");
    out.println("</font></h1>");

    out.println("</BODY></HTML>");
  }//end doGet()
}//end class Servlet01      

Up to this point, everything has been pretty simple for this servlet. Getting everything set up to run the servlet was much more complicated than writing the servlet. All that is necessary at this point is to store the class file for the servlet in the required servlet directory for the server being used and then address the servlet with a browser. Different servers will have different requirements insofar as where the class files for the servlet are to be stored. As you saw with the servletrunner program, you can specify the directory containing servlet files when you start the program.

Usually in most lessons, when I complete the discussion of the last sample program, that is the end of the lesson. In this case, however, there are several important topics that I want to discuss before ending the lesson. Hopefully I will be able to find the time later to expand on these topics and provide sample programs to illustrate them.

One of the incoming objects is of type ServletRequest, which is a type specified by an interface of the same name. This interface declares a variety of methods by which the servlet can extract incoming information from the object. This framework provides the servlet's only access to incoming data.

The data provided by the ServletRequest object includes parameter names and values, attributes, and an input stream. Subclasses of ServletRequest can provide additional protocol-specific data. For example, HTTP data is provided by the interface HttpServletRequest, which extends ServletRequest.

The following is a list of available methods of the ServletRequest interface along with a brief description of their purpose. As you can see, in the general case, it is possible for the servlet to obtain a great deal of information in order to carry out its duties.

  • getAttribute(String) - Returns the value of the named attribute of the request, or null if the attribute does not exist.
  • getCharacterEncoding() - Returns the character set encoding for the input of this request.
  • getContentLength() - Returns the size of the request entity data, or -1 if not known.
  • getContentType() - Returns the Internet Media Type of the request entity data, or null if not known.
  • getInputStream() - Returns an input stream for reading binary data in the request body.
  • getParameter(String) - Returns a string containing the lone value of the specified parameter, or null if the parameter does not exist.
  • getParameterNames() - Returns the parameter names for this request as an enumeration of strings, or an empty enumeration if there are no parameters or the input stream is empty.
  • getParameterValues(String) - Returns the values of the specified parameter for the request as an array of strings, or null if the named parameter does not exist.
  • getProtocol() - Returns the protocol and version of the request as a string of the form
    <protocol>/<major version>.<minor version>.
  • getReader() - Returns a buffered reader for reading text in the request body.
  • getRealPath(String) - Applies alias rules to the specified virtual path and returns the corresponding real path, or null if the translation can not be performed for any reason.
  • getRemoteAddr() - Returns the IP address of the agent that sent the request.
  • getRemoteHost() - Returns the fully qualified host name of the agent that sent the request.
  • getScheme() - Returns the scheme of the URL used in this request, for example "http", "https", or "ftp".
  • getServerName() - Returns the host name of the server that received the request.
  • getServerPort() - Returns the port number on which this request was received.

The other incoming object is an object of type ServletResponse, which is a type defined by an interface of the same name. This interface declares a variety of methods by which the servlet can return data to the client.

If you don't explicitly set the character set in your MIME media type, with setContentType(), one will be selected and the content type will be modified accordingly.

If you will be using a writer, you must call the setContentType() method before calling the getWriter() method. If you will be using the output stream, and want to call setContentType(), you must do so before using the output stream.

The list of methods of the ServletResponse object, as shown below, is much shorter than the list for ServletRequest.

  • getCharacterEncoding() - Returns the character set encoding used for this MIME body.
  • getOutputStream() - Returns an output stream for writing binary response data.
  • getWriter() - Returns a print writer for writing formatted text responses.
  • setContentLength(int) - Sets the content length for this response.
  • setContentType(String) - Sets the content type for this response.

Later when we discuss HTTP servlets in particular, you will see that these two interfaces are extended into two more-specialized interfaces that declare a large number of additional methods that the servlet can use to obtain data from or return data to the client.

These are the classes and interfaces that make up a basic Servlet. HTTP servlets have additional objects that provide session-tracking capabilities. The servlet writer can use these APIs to maintain state between the servlet and the client that persists across multiple connections during some time period.

Just like applets, servlets have a prescribed life cycle. Browsers load and run applets. Servers load and run servlets. Servers accept requests from clients, and may use their servlets to return data to the clients.

Servers can also remove servlets. So, the stages of a servlet's life cycle are:

A server runs the servlet's init() method when it loads the servlet. Most servlets are run in multi-threaded servers. However, there are no concurrency issues during servlet initialization. The server calls the init() method when it loads the servlet, and does not call it again unless it is reloading the servlet.

The server cannot reload a servlet until after it has removed the servlet by calling the destroy() method. Initialization is allowed to complete before client requests are handled or the servlet is destroyed.

This can be a problem when you are developing a servlet and repeatedly testing it with a server. Once the servlet is initially loaded from the class file, for some servers, simply providing a new class file does not cause the server to remove the old version of the servlet and load the new version. With the servletrunner program, you must terminate the program to cause it to load the new version of the servlet. The same also appears to be true with the JavaSoft server program named Java Web Server 1.1.1. However, it may be possible to use some feature of the administration tool to force the server to remove an old version and load a new version of a servlet without terminating the server.

After the servlet is loaded and initialized, the second stage in the life of the servlet begins. At this point, the server may call upon the servlet to respond to client requests. In so doing, the servlet handles client requests by processing them in its service() method which is called by the server. Normally, the service() request from each client is run in a separate thread by the servlet.

If you are writing a generic servlet, you will probably override the service() method. However, if you are writing servlets to be used with HTTP servers, you probably won't override service(). The service() method of the HttpServlet class handles the setup and dispatches the request to methods such as doGet() and doPost(). When writing HTTP servlets, you will normally override the doXXX() methods instead of the service() method.

Servlets can process requests from multiple clients concurrently in a multithreaded manner. This means that the service() methods should be written to be thread safe. One description that I have seen for how to write a thread-safe method is to:

use the fewest possible number of instance variables, and synchronize access to them.

Occasionally for some reason, you may decide to prevent your servlet from processing concurrent client requests. In this case, you should cause your servlet to implement the SingleThreadModel interface. This interface guarantees that no two threads will execute the servlet's service() methods concurrently. Implementing the interface does not require writing any extra methods. Merely declaring that the servlet implements the interface is sufficient to prevent the server from making concurrent calls to the service() method.

The third and last stage of a servlet's life cycle is removal. When a server removes a servlet, it runs the servlet's destroy() method. This method is run only once. The server will not run it again until after it reloads and reinitializes the servlet. When the destroy() method runs, other threads might be running service requests. If it is necessary to access shared resources while doing cleanup, that access should be synchronized.

As mentioned earlier, servlets must implement the javax.servlet.Servlet interface. For writing servlets that run under control of servers that use the HTTP protocol, the most common way to write servlets is to extend the javax.servlet.http.HttpServlet class which is a way to indirectly implement the Servlet interface.

According to JavaSoft:

The HttpServlet class implements the Servlet interface by extending the GenericServlet base class, and provides a framework for handling the HTTP protocol. Its service() method supports standard HTTP/1.1 requests by dispatching each request to a method designed to handle it.

For servlets that extend the HttpServlet class, the following four methods may be overridden to cause your servlet to interact with the client.

You may need to do some outside research on the HTTP protocol to learn about the nature of each type of client request listed above

By default, if not overridden, these methods return a BAD_REQUEST (400) error.

The servlet presented earlier used the doGet() method to handle GET requests. I hope to find the time to publish additional lessons that illustrate how to use the other methods listed above to handle the other types of requests.

Each of the methods requires two arguments. The first, an HttpServletRequest object, encapsulates the data from the client. The second, an HttpServletResponse object encapsulates the response to the client. Earlier in this lesson, I discussed the ServletRequest interface and showed you a long list of methods declared by that interface the servlet can use to obtain information about the request.

HttpServletRequest is an interface that extends ServletRequest and provides eighteen additional methods by which the servlet can obtain information about the incoming request. Similarly, HttpServletResponse is an interface that extends the ServletResponse interface discussed earlier. This extension adds about twelve methods that the servlet can use to return data to the client.

You may also find that you need to override the init() and destroy() methods in your servlet. Recall that each of these methods is called only once. The init() method is called when the server loads the servlet class. The destroy() method is called when the server removes the servlet class.

The servlet should use the init() method to prepare the resources it manages and to make the servlet ready to handle client requests. It can do this without regard for multi-threading issues because it is guaranteed that there is only a single thread running during initialization. If the init() method is unable to run to a successful completion, it should throw an UnavailableException object.

The init() method receives a ServletConfig object as a parameter. For reasons that I won't bother to discuss here, the overridden init() method should call super.init() and pass this configuration object as a parameter. If you are curious as to the reasons, you can look it up in the documentation.

Overriding the destroy() method is more problematic, particularly if it is necessary to deal with resources that may be shared by service() threads that are still running when the server decides to remove the servlet and invoke its destroy() methods. I'm simply going to punt on this one, and suggest that if you find yourself in this situation, you should find a good book with an example similar to your situation and use that example for guidance. Maybe I'll find the time to provide an example myself in a subsequent lesson.

Some applets and applications display information about a servlet, such as a short description of the purpose of the servlet, its author, and perhaps its version number. The Servlet API provides a method named getServletInfo() to return this information. By default, this method returns null. You can override this method to return a String containing information about your servlet.

Program Listing

A complete listing of the program follows.

/*File Servlet01.java, Copyright 1999, R.G.Baldwin
Rev 1/2/99
The purpose of this program is to illustrate a very 
simple servlet.

The servlet was tested using the JDK 1.2 download package
from JavaSoft along with the Java Servlet Development Kit
(JSDK) 2.0 from JavaSoft. All tests were performed 
under Win95.

The servlet was tested using the servletrunner program that
is included in the JSDK with the servletrunner program 
running on the same machine and also with the servletrunner
program running on a different machine on the network. 

The servlet was also tested using the JavaSoft Java Web 
Server 1.1.1 program running on a different machine on 
the network.

The servlet produces the following text in the browser 
window in large red letters.

Hello Big Red World
**********************************************************/
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class Servlet01 extends HttpServlet{
  public void doGet(HttpServletRequest req, 
                    HttpServletResponse res)
                      throws ServletException, IOException{

    //Establish the type of output
    res.setContentType("text/html");
    
    //Get an output stream
    PrintWriter out = res.getWriter();
    
    //Construct an HTML page to return to the client
    out.println("<HTML>");
    out.println("<HEAD><TITLE=Servlet01</TITLE></HEAD>");
    out.println("<BODY>");
    
    out.println("<h1 align=\"center\">"
               +"<font color=\"#FF0000\">");
    out.println("Hello Big Red World");
    out.println("</font></h1>");

    out.println("</BODY></HTML>");
  }//end doGet()
}//end class Servlet01

-end-