A servlet is a server-side program written in the JavaTM programming
language that interacts with clients and is usually tied to a HyperText
Transfer Protocol (HTTP) server. One common use for a servlet is to extend a
web server by providing dynamic web content.
Servlets have an advantage over other technologies in that they are
compiled, have threading capability built in, and provide a secure
programming environment. Even web sites that previously did not
provide servlet support, can do so now by using programs such
as JRun or the Java module for the Apache web server.
The web-based auction application
uses a servlet to accept and process buyer and seller input through the
browser and dynamically return auction item information to the browser.
The AuctionServlet
program is created by extending the
HttpServlet
class. The HttpServlet
class
provides a framework for handling HTTP requests and responses.
This section examines the AuctionServlet
and includes
information on how to use Cookie
and Session
objects in a servlet.
HttpServlet
The AuctionServlet class
extends HttpServlet
, which is an abstract class.
public class AuctionServlet extends HttpServlet {
A servlet can be either loaded when the web server starts up or loaded
when requested by way of an HTTP URL that specifies the servlet.
The servlet is usually loaded by a separate classloader in the web
server because this allows the servlet to be reloaded by unloading the
class loader that loaded the servlet class. However, if the servlet
depends on other classes and one of those classes changes, you
will need to update the date stamp on the servlet for it to
reload.
After a servlet loads, the first stage in its lifecycle is the web
server calls the servlet's init
method. Once loaded and
initialized, the next stage in the servlet's lifecycle is to
serve requests. The servlet serves requests through its service
,
doGet
, or doPost
method implementations.
The servlet can optionally implement a destroy
method to
perform clen-up operations before the web server unloads
the servlet.
The init Method
The init
method is only called once by the
web server when the servlet is first started. The init
method
is passed a ServletConfig
object containing initialization
information pertaining to the web server where the application is running.
The ServletConfig
object is used to access information
maintained by the web server including values from the
initArgs
parameter in the servlet properties file.
Code in the init
method uses the
ServletConfig
object to retrieve the initArgs
values by calling the config.getInitParameter("parameter")
method.
The AuctionServlet.init
method also contacts the
Enterprise JavaBeans server to create a context (ctx
)
object. The ctx
object is used in the service
method to establish a connection with the Enterprise JavaBeans server.
Context ctx=null;
private String detailsTemplate;
public void init(ServletConfig config)
throws ServletException{
super.init(config);
try {
ctx = getInitialContext();
}catch (Exception e){
System.err.println(
"failed to contact EJB server"+e);
}
try {
detailsTemplate=readFile(
config.getInitParameter("detailstemplate"));
} catch(IOException e) {
System.err.println(
"Error in AuctionServlet <init>"+e);
}
}
The destroy Method
The destroy
method is a lifecycle method implemented by
servlets that need to save their state between servlet loading
and unloading. For example, the destroy
method would
save the current servlet state, and the next time the servlet is
loaded, that saved state would be retrieved by the init
method. You should be aware that the destroy
method might not be called if the server machine crashes.
public void destroy() {
saveServletState();
}
The service Method
The AuctionServlet
is an HTTP servlet that handles
client requests and generates responses through its service
method. It accepts as parameters the HttpServletRequest
and HttpServletResponse
request and response objects.
-
HttpServletRequest
contains the headers and input streams sent
from the client to the server.
-
HttpServletResponse
is the output stream that is used to send
information from the servlet back to the client.
The service
method handles standard
HTTP client requests received by way of its HttpServletRequest
parameter by delegating the request to one of the following methods designed
to handle that request. The different types of requests are described in
the HTTP Requests section.
- doGet for GET, conditional GET, and HEAD requests.
- doPost for POST requests.
- doPut for PUT requests.
- doDelete for DELETE requests.
- doOptions for OPTIONS requests.
- doTrace for TRACE requests.
The AuctionServlet
program provides its own
service
method implementation that calls one of the
following methods based on the value returned by the call
to cmd=request.getParameter("action")
. These method
implementations match the default implementations provided
in the doGet
and doPost
methods called
by the default service
method, but add some auction
application-specific functionality for looking up Enterprise Beans.
- listAllItems(out)
- listAllNewItems(out)
- listClosingItems(out)
- insertItem(out, request)
- itemDetails(out, request)
- itemBid(out, request)
- registerUser(out, request)
public void service(HttpServletRequest request,
HttpServletResponse response)
throws IOException {
String cmd;
response.setContentType("text/html");
ServletOutputStream out = response.getOutputStream();
if (ctx == null ) {
try {
ctx = getInitialContext();
}catch (Exception e){
System.err.println(
"failed to contact EJB server"+e);
}
}
cmd=request.getParameter("action");
if(cmd !=null) {
if(cmd.equals("list")) {
listAllItems(out);
}else
if(cmd.equals("newlist")) {
listAllNewItems(out);
}else if(cmd.equals("search")) {
searchItems(out, request);
}else if(cmd.equals("close")) {
listClosingItems(out);
}else if(cmd.equals("insert")) {
insertItem(out, request);
}else if (cmd.equals("details")) {
itemDetails(out, request );
}else if (cmd.equals("bid")) {
itemBid(out, request) ;
}else if (cmd.equals("register")) {
registerUser(out, request);
}
}else{
// no command set
setTitle(out, "error");
}
setFooter(out);
out.flush();
}
HTTP Requests
A request is a message sent from a client program such
as a browser to a server program. The first line of the
request message contains a method that indicates the action
to perform on the incoming Uniform Resource Locator (URL).
The two commonly used mechanisms for sending information
to the server are POST
and GET
.
- GET requests might pass parameters to a URL by appending them
to the URL.
GET
requests can be bookmarked and emailed and
include the information to the URL of the response.
- POST requests might pass additional data to a URL by
directly sending it to the server separately from the URL.
POST requests cannot be bookmarked or emailed and do not change the
URL of the response.
PUT requests are the reverse of GET requests. Instead of
reading the page, PUT requests write (or store) the page.
DELETE requests are for removing web pages.
OPTIONS requests are for getting information about the
communication options available on the request/response
chain.
TRACE requests are for testing or diagnostic purposes because
they let the client see what is being received at the other
end of the request chain.
Using Cookies in servlets
HTTP cookies are essentially custom HTTP headers that are passed between
a client and a server. Although cookies are not overwhelmingly popular,
they do enable state to be shared between the two machines. For example,
when a user logs into a site, a cookie can maintain a reference verifying
the user has passed the password check and can use that reference to
identify that same user on future visits.
Cookies are normally associated with a server. If you set the domain
to .java.sun.com
, then the cookie is associated with the
domain. If no domain is set, the cookie is only associated with the server
that created the cookie.
Setting a Cookie
The JavaTM Servlet API includes a Cookie
class that you
can use to set or retrieve the cookie from the HTTP header. HTTP cookies
include a name and value pair.
The startSession
method shown here is in the
LoginServlet program. In
this method, the name in the name and value pair used to create
the Cookie
is JDCAUCTION
,
and a unique identifier generated by the server is the value.
protected Session startSession(String theuser,
String password,
HttpServletResponse response) {
Session session = null;
if ( verifyPassword(theuser, password) ) {
// Create a session
session = new Session (theuser);
session.setExpires (sessionTimeout + i
System.currentTimeMillis());
sessionCache.put (session);
// Create a client cookie
Cookie c = new Cookie("JDCAUCTION",
String.valueOf(session.getId()));
c.setPath ("/");
c.setMaxAge (-1);
c.setDomain (domain);
response.addCookie (c);
}
return session;
}
Later versions of the Servlet API include a Session API, to create a
session using the Servlet API in the previous example you can use
the getSession
method.
HttpSession session = new Session (true);
The startSession
method is called by requesting the
login action from a POST
to the LoginServlet
as follows:
<FORM ACTION="/LoginServlet" METHOD="POST">
<TABLE>
<INPUT TYPE="HIDDEN" NAME="action" VALUE="login">
<TR>
<TD>Enter your user id:</TD>
<TD><INPUT TYPE="TEXT" SIZE=20
NAME="theuser"></TD>
</TR>
<TR>
<TD>Enter your password:<TD>
<TD><INPUT TYPE="PASSWORD" SIZE=20
NAME="password"></TD>
</TR>
</TABLE>
<INPUT TYPE="SUBMIT" VALUE="Login" NAME="Enter">
</FORM>
The cookie is created with an maximum age of -1, which means the
cookie is not stored but remains alive while the browser runs. The
value is set in seconds, although when using values smaller than a few
minutes you need to be careful of machine times being slightly out of
sync.
The path value can be used to specify that the cookie only applies
to files and directories under the path set on that machine. In this example
the root path /
means the cookie is applicable to all directories.
The domain value in the example is read from the initialization parameters
for the servlet. If the domain is null
, the cookie is applied to
that machines domain only.
Retrieving a Cookie
The cookie is retrieved from the HTTP headers with a call to
the getCookies
method on the request:
Cookie c[] = request.getCookies();
You can later retrieve the name and value pair settings by calling
the Cookie.getName
method to retrieve the name,
and the Cookie.getValue
method to retrieve the value.
LoginServlet
has a validateSession
method
that checks the user's cookies to find a JDCAUCTION
cookie that was set in this domain:
private Session validateSession
(HttpServletRequest request,
HttpServletResponse response) {
Cookie c[] = request.getCookies();
Session session = null;
if( c != null ) {
Hashtable sessionTable = new Hashtable();
for (int i=0; i < c.length &&
session == null; i++ ) {
if(c[i].getName().equals("JDCAUCTION")) {
String key = String.valueOf (c[i].getValue());
session=sessionCache.get(key);
}
}
}
return session;
}
If you use the Servlet session API then you can use the following method,
note that the parameter is false to specify the session value is returned
and that a new session is not created.
HttpSession session = request.getSession(false);
Generating Sessions
The LoginServlet.validateSession
method returns a
Session
object represented by the
Session class.
The Session
class uses an identifier generated from
a numeric sequence. This numbered session identifier is the value part of
the name and value pair stored in the cookie.
The only way to reference the user name on the server is with this
session identifier, which is stored in a simple memory cache with the
other session IDs. When a user terminates a session, the
LoginServlet
logout action is called like this:
http://localhost:7001/LoginServlet?action=logout
The session cache implemented in the
SessionCache.java program
includes a reaper thread to remove sessions older than a preset time. The
preset timeout could be measured in hours or days depending on
how many visitors visit the site.
Preventing Page Caching
The LoginServlet.setNoCache
method sets the
Cache-Control
or Pragma
values
(depending on which version of the HTTP protocol is being used) in the
response header to no-cache
. The expiration header Expires
is also set to 0, alternatively you can set the time to be the current system
time. Even if the client does not cache the page, there are often
proxy servers in a corporate network that would. Only pages using Secure Socket
Layer (SSL) are not cached by default.
private void setNoCache (HttpServletRequest request,
HttpServletResponse response) {
if(request.getProtocol().compareTo ("HTTP/1.0") == 0) {
response.setHeader ("Pragma", "no-cache");
} else if (request.getProtocol().compareTo
("HTTP/1.1") == 0) {
response.setHeader ("Cache-Control", "no-cache");
}
response.setDateHeader ("Expires", 0);
}
Restricting Access and Redirections
If you install the LoginServlet
as the default servlet or
servlet to run when serving any page under the document root, you can
use cookies to restrict users to certain sections of the site. For
example, you can allow users who have cookies that state they
have logged in to access sections of the site that require
a login password and keep all others out.
The LoginServlet program checks
for a restricted directory in its init
method. The init
method shown below sets the protectedDir
variable to true
if the config
variable passed to it specifies a protected directory.
The web server configuration file provides the settings passed to a servlet
in the config
variable.
public void init(ServletConfig config)
throws ServletException {
super.init(config);
domain = config.getInitParameter("domain");
restricted = config.getInitParameter("restricted");
if(restricted != null) {
protectedDir=true;
}
Later on in the validateSession
and service
methods, the protectedDir
variable is checked and the
HttpResponse.sendRedirect
method is called to send
the user to the correct page based on their login and session status.
if(protectedDir) {
response.sendRedirect (restricted+"/index.html");
}else{
response.sendRedirect (defaultPage);
}
The init
method also retrieves the servlet context for
the FileServlet
servlet so methods can be called on
the FileServlet
in the validateSession
method. The advantage to calling methods on the FileServlet
servlet to serve the files rather than serving the files from
within the LoginServlet
servlet, is you get the
full advantage of all the functionality added into the FileServlet
servlet such as memory mapping or file caching.
The downside is that the code may not be portable to other servers that
do not have a FileServlet
servlet.
This code retrieves the FileServlet
context.
FileServlet fileServlet=(FileServlet)
config.getServletContext().getServlet("file");
The validateSession
method prevents users without a
logon session from accessing the restricted directory.
HTTP Error Codes
You can return a HTTP error code using the sendError
method. For example, the HTTP 500 error code indicates an internal
server error, and the 404 error code indicates page not found.
This code segment returns the HTTP 500 error code.
protected void service (HttpServletRequest request,
HttpServletResponse response)
throws ServletException {
response.sendError (500);
}
Reading GET and POST Values
The Servlet API has a getParameter
method in the
HttpServletRequest
class that returns the GET
or
POST
value for the name you supply.
- The HTTP
GET
request handles name and value pairs
as part of the URL. The getParameter
method parses
the URL passed in, retrieves the name=value
pairs
deliminated by the ampersand (&
) character, and returns
the value.
- The HTTP
POST
request reads the name and value pairs
from the input stream from the client. The getParameter
method parses the input stream for the name and value pairs.
The getParameter
method works well for simple servlets,
but if you need to retrieve the POST
parameters in the
order they were placed on the web page or handle multi-part posts, you
can write your own code to parse the input stream.
The next example returns POST parameters in the order they
were received from the web page. Normally, the parameters are
stored in a Hashtable
which does not maintain the sequence
order of elements stored in it. The example keeps a reference to
each name and value pair in a vector that can be traversed to
return the values in the order they were received by the server.
package auction;
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class PostServlet extends HttpServlet {
private Vector paramOrder;
private Hashtable parameters;
public void init(ServletConfig config)
throws ServletException {
super.init(config);
}
public void service(HttpServletRequest request,
HttpServletResponse response)
throws IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
if(request.getMethod().equals("POST")
&& request.getContentType().equals(
"application/x-www-form-urlencoded")) {
parameters=parsePostData(
request.getContentLength(),
request.getInputStream());
}
for(int i=0;i<paramOrder.size();i++) {
String name=(String)paramOrder.elementAt(i);
String value=getParameter((
String)paramOrder.elementAt(i));
out.println("name="+name+" value="+value);
}
out.println("</body></html>");
out.close();
}
private Hashtable parsePostData(int length,
ServletInputStream instream) {
String valArray[] = null;
int inputLen, offset;
byte[] postedBytes = null;
boolean dataRemaining=true;
String postedBody;
Hashtable ht = new Hashtable();
paramOrder= new Vector(10);
StringBuffer sb = new StringBuffer();
if (length <=0) {
return null;
}
postedBytes = new byte[length];
try {
offset = 0;
while(dataRemaining) {
inputLen = instream.read (postedBytes,
offset,
length - offset);
if (inputLen <= 0) {
throw new IOException ("read error");
}
offset += inputLen;
if((length-offset) ==0) {
dataRemaining=false;
}
}
} catch (IOException e) {
System.out.println("Exception ="+e);
return null;
}
postedBody = new String (postedBytes);
StringTokenizer st =
new StringTokenizer(postedBody, "&");
String key=null;
String val=null;
while (st.hasMoreTokens()) {
String pair = (String)st.nextToken();
int pos = pair.indexOf('=');
if (pos == -1) {
throw new IllegalArgumentException();
}
try {
key = java.net.URLDecoder.decode(
pair.substring(0, pos));
val = java.net.URLDecoder.decode(
pair.substring(pos+1,
pair.length()));
} catch (Exception e) {
throw new IllegalArgumentException();
}
if (ht.containsKey(key)) {
String oldVals[] = (String []) ht.get(key);
valArray = new String[oldVals.length + 1];
for (int i = 0; i < oldVals.length; i++) {
valArray[i] = oldVals[i];
}
valArray[oldVals.length] = val;
} else {
valArray = new String[1];
valArray[0] = val;
}
ht.put(key, valArray);
paramOrder.addElement(key);
}
return ht;
}
public String getParameter(String name) {
String vals[] = (String []) parameters.get(name);
if (vals == null) {
return null;
}
String vallist = vals[0];
for (int i = 1; i < vals.length; i++) {
vallist = vallist + "," + vals[i];
}
return vallist;
}
}
To find out whether the request is POST or GET, call the
getMethod
in the HttpServletRequest
class.
To determine the format of the data being posted, call the
getContentType
method in the HttpServletRequest
class. For simple HTML web pages, the type returned by this call
will be application/x-www-form-urlencoded
.
If you need to create a post with more than one part such
as the one created by the following HTML form, the servlet
will need to read the input stream from the post to
reach individual section. Each section distinguished by a
boundary defined in the post header.
<FORM ACTION="/PostMultiServlet"
METHOD="POST" ENCTYPE="multipart/form-data">
<INPUT TYPE="TEXT" NAME="desc" value="">
<INPUT TYPE="FILE" NAME="filecontents" value="">
<INPUT TYPE="SUBMIT" VALUE="Submit" NAME="Submit">
</FORM>
The next example extracts a description and a file from the client
browsers. It reads the input stream looking for a line matching the
boundary string, reads the content line, skips a line and then reads
the data associated with that part. The uploaded file is simply displayed,
but could also be written to disk.
package auction;
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class PostMultiServlet extends HttpServlet {
public void init(ServletConfig config)
throws ServletException {
super.init(config);
}
public void service(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
if (request.getMethod().equals("POST")
&& request.getContentType().startsWith(
"multipart/form-data")) {
int index = request.getContentType().indexOf(
"boundary=");
if (index < 0) {
System.out.println("can't find boundary type");
return;
}
String boundary =
request.getContentType().substring(
index+9);
ServletInputStream instream =
request.getInputStream();
byte[] tmpbuffer = new byte[8192];
int length=0;
String inputLine=null;
boolean moreData=true;
//Skip until form data is reached
length = instream.readLine(
tmpbuffer,
0,
tmpbuffer.length);
inputLine = new String (tmpbuffer, 0, 0,
length);
while(inputLine.indexOf(boundary)
>0 && moreData) {
length = instream.readLine(
tmpbuffer,
0,
tmpbuffer.length);
inputLine = new String (tmpbuffer, 0, 0,
length);
if(inputLine !=null)
System.out.println("input="+inputLine);
if(length<0) {
moreData=false;
}
}
if(moreData) {
length = instream.readLine(
tmpbuffer,
0,
tmpbuffer.length);
inputLine = new String (tmpbuffer, 0, 0,
length);
if(inputLine.indexOf("desc") >=0) {
length = instream.readLine(
tmpbuffer,
0,
tmpbuffer.length);
inputLine = new String (tmpbuffer, 0, 0,
length);
length = instream.readLine(
tmpbuffer,
0,
tmpbuffer.length);
inputLine = new String (tmpbuffer, 0, 0,
length);
System.out.println("desc="+inputLine);
}
}
while(inputLine.indexOf(boundary)
>0 && moreData) {
length = instream.readLine(
tmpbuffer,
0,
tmpbuffer.length);
inputLine = new String (tmpbuffer, 0, 0,
length);
}
if(moreData) {
length = instream.readLine(
tmpbuffer,
0,
tmpbuffer.length);
inputLine = new String (tmpbuffer, 0, 0,
length);
if(inputLine.indexOf("filename") >=0) {
int startindex=inputLine.indexOf(
"filename");
System.out.println("file name="+
inputLine.substring(
startindex+10,
inputLine.indexOf("\"",
startindex+10)));
length = instream.readLine(
tmpbuffer,
0,
tmpbuffer.length);
inputLine = new String (tmpbuffer, 0, 0,
length);
}
}
byte fileBytes[]=new byte[50000];
int offset=0;
if (moreData) {
while(inputLine.indexOf(boundary)
>0 && moreData) {
length = instream.readLine(
tmpbuffer,
0,
tmpbuffer.length);
inputLine = new String (tmpbuffer, 0, 0, length);
if(length>0 && (
inputLine.indexOf(boundary) <0)) {
System.arraycopy(
tmpbuffer,
0,
fileBytes,
offset,
length);
offset+=length;
} else {
moreData=false;
}
}
}
// trim last two newline/return characters
// before using data
for(int i=0;i<offset-2;i++) {
System.out.print((char)fileBytes[i]);
}
}
out.println("</body></html>");
out.close();
}
}
Threading
A servlet must be able to handle multiple concurrent requests.
Any number of end users at any given time could invoke the
servlet, and while the init
method is always
run single-threaded, the service
method is
multi-threaded to handle multiple requests.
This means any static or public fields accessed by the
service
method should be restricted
to simple thread access. The example below uses the
synchronized
keyword to restrict access to a counter
so it can only be updated by one thread at a time:
int counter
Boolean lock = new Boolean(true);
synchronized(lock){
counter++;
}
HTTPS
Many servers, browsers, and the Java Plug-In have the ability to support
the secure HTTP protocol called HTTPS. HTTPS is similar to HTTP except the
data is transmitted over a secure socket layer (SSL) instead of
a normal socket connection. Web servers often listen for HTTP
requests on one port while listening for HTTPS requests on another.
The encrypted data that is sent over the network includes checks to verify
if the data has been tampered in transit. SSL also authenticates
the webserver to its clients by providing a public key certificate. In
SSL 3.0 the client can also authenticate itself with the server, again using
a public key certificate.
Public key cryptography (also called asymmetric key encryption) uses a public
and private key pair.
Any message encrypted (made unintelligible) with the private key in the pair
can only be decrypted with the corresponding public key.
Certificates are digitally signed statements generated from a trusted
third party Certificate Authority. The Certificate Authority needs proof
that you are who you say you are because clients will be trusting the certificate
they receive. It is this certificate that contains the public key in the public
and private key pair. The certificate is signed by the private key
of the Certificate Authority, and most browsers know the public key for the
main Certificate Authorities.
While public key encryption is good for authentication purposes, it is
not as fast as symmetric key encryption and so the SSL protocol uses both types
of keys in the lifecycle of an SSL connection. The client and server begin
an HTTPS transaction with a connection initialization or handshaking phase.
It is in the handshaking stage that the server is authenticated using the
certificate that the client has received. The client uses the server's public
key to encrypt messages sent to the server. After the client has been
authenticated and the encryption algorithm or cipher has been agreed between
the two parties, new symmetric session keys are used to encrypt and
decrypt any further communication.
The encryption algorithm or cipher can be one of many popular algorithms
like Rivest Shamir and Adleman (RSA) or Data Encryption Standard (DES). The
greater the number of bits used to make the key, the more difficult
it is to break into using brute force search techniques.
HTTPS using public key cryptography and certificates lets you provide
the amount of privacy your application needs for safe and secure
transactions. Servers, browsers, and Java Plug-In have their own
setup for enabling HTTPS using SSL communications. In general, the steps
involve the following:
- Get a private key and a digitally-signed certificate with the
matching public key.
- Install the certificate in a location specified by the software
you are using (server, browser, or Java Plug-In).
- Enable SSL features and specify your certificate and private key
files as instructed in your documentation.
You should enable SSL features according to your specific application
requirements
depending on the level of security you need. For example, you do not need
to verify the identity of customers browsing auction items, but you will
want to encrypt credit card and other personal information supplied when
buyers and sellers register to participate.
HTTPS can be used for any data not just HTTP web pages. Programs written in
the Java language can be downloaded over an HTTPS connection, and you can open
a connection to a HTTPS server in the Java Plug-in. To write a program in the
Java language that uses SSL. SSL requires an SSL library and a detailed knowledge
of the HTTPS handshaking process. Your SSL library should cover the necessary
steps as this information is restricted by export security control.
[TOP]