Servlet Configuration
Sometimes it is necessary to provide initial configuration information for Servlets. Configuration information for a Servlet may consist of a string or a set of string values included in the Servlet's web.xml declaration. This functionality allows a Servlet to have initial parameters specified outside of the compiled code and changed without needing to recompile the Servlet. Each servlet has an object associated with it called the ServletConfig19. This object is created by the container and implements the javax.servlet.ServletConfig interface. It is the ServletConfig that contains the initialization parameters. A reference to this object can be retrieved by calling the getServletConfig() method. The ServletConfig object provides the following methods for accessing initial parameters:
getInitParameter(String name)
The getInitParameter() returns a String object that contains the value of the named initialization parameter or null if the parameter does not exist.
getInitParameterNames()
The getInitParameterNames() method returns the names of the Servlet's initialization parameters as an Enumeration of String objects or an empty Enumeration if the Servlet has no initialization parameters.
Defining initial parameters for a Servlet requires using the init-param, param-name, and param-value elements in web.xml. Each init-param element defines one initial parameter and must contain a parameter name and value specified by children param-name and param-value elements, respectively. A Servlet may have as many initial parameters as needed, and initial parameter information for a specific Servlet should be specified within the servlet element for that particular Servlet.
Using initial parameters, the HelloWorld Servlet can be modified to be more internationally correct. Instead of assuming the Servlet should say "Hello World!", it will be assumed the Servlet should say the equivalent for any given language. To accomplish this, an initial parameter will be used to configure the proper international "Hello" message. While HelloWorld.java will still not be perfectly compliant for all languages, it does demonstrate initial parameters. Modify HelloWorld.java to match the code in Listing 2-3.
Listing 2-3. InternationalizedHelloWorld.java
package com.jspbook; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class InternationalizedHelloWorld extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println("<html>"); out.println("<head>"); String greeting; greeting = getServletConfig().getInitParameter("greeting"); out.println("<title>" +greeting+"</title>"); out.println("</head>"); out.println("<body>"); out.println("<h1>" +greeting+"</h1>"); out.println("</body>"); out.println("</html>"); } }
Save the preceding code as InternationalizedHelloWorld.java in the /WEB-INF/classes/com/jspbook directory of the jspbook Web Application. Since this is the second code example, a full walk-through is given for deploying the Servlet. In future examples it will be expected that you deploy Servlets on your own to a specified URL.
Open up web.xml in the /WEB-INF folder of the jspbook Web Application and add in a declaration and mapping to /InternationalizedHelloWorld for the InternationalizedHelloWorld Servlet. When finished, web.xml should match Listing 2-4.
Listing 2-4. Updated web.xml
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" version="2.4"> <servlet> <servlet-name>HelloWorld</servlet-name> <servlet-class>com.jspbook.HelloWorld</servlet-class> </servlet> <servlet> <servlet-name>InternationalizedHelloWorld</servlet-name> <servlet-class> com.jspbook.InternationalizedHelloWorld </servlet-class> </servlet> <servlet-mapping> <servlet-name>InternationalizedHelloWorld</servlet-name> <url-pattern>/InternationalizedHelloWorld</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>HelloWorld</servlet-name> <url-pattern>/HelloWorld</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>welcome.html</welcome-file> </welcome-file-list> </web-app>
The InternationalizedHelloWorld Servlet relies on an initial parameter for the classic "Hello World" greeting. Specify this parameter by adding in the following entry to web.xml.
... <servlet> <servlet-name>InternationalizedHelloWorld</servlet-name> <servlet-class> com.jspbook.InternationalizedHelloWorld </servlet-class> <init-param> <param-name>greeting</param-name> <param-value>Bonjour!</param-value> </init-param> </servlet> ...
Leave the param-name element's body as greeting, but change the value specified in the body of the param-value tag to be a greeting of your choice. A candidate for a welcome message en francais20 would be "Bonjour!" After saving any changes, reload the jspbook Web Application and visit the Internationalized-HelloWorld Servlet to see the new message. Figure 2-7 shows an example browser rendering of InternationalizedHelloWorld Servlet's output.
Figure 2-7. Browser Rendering of InternationalizedHelloWorld Servlet
Instead of the basic "Hello World!", the Servlet now displays the initial parameter's value. This approach is nowhere near the best of solutions for internationalization issues, but it does work in some cases and is a good example to introduce initial Servlet configuration. In general, the initial parameter mechanism shown previously is used to provide simple configuration information for an entire Web Application. The HelloWorld Servlet example demonstrated initial parameters for one Servlet, but later on in the chapter it will be shown that the same method is used to provide initial parameters for an entire Web Application.
Limitations of Configuration: web.xml Additions
Initial parameters are a good method of providing simple one-string values that Servlets can use to configure themselves. This approach is simple and effective, but is a limited method of configuring a Servlet. For more complex Servlets it is not uncommon to see a completely separate configuration file created to accompany web.xml. When developing Servlets, keep in mind that nothing stops you from doing this. If the parameter name and parameter values mappings are not adequate, do not use them! It is perfectly OK to create a custom configuration file and package it in a WAR with the rest of a Web Application. A great example of doing this is shown by the Jakarta Struts framework appearing in Chapter 11. The Struts framework relies on a control Servlet that is configured via a custom and usually lengthy XML file.
Client/Server Servlet Programming
A Servlet request and response is represented by the javax.servlet.ServletRequest and javax.servlet.ServletResponse objects, or a corresponding subclass of them. For HTTP Servlets the corresponding classes are HttpServletRequest and HttpServletResponse. These two objects were quickly introduced with the HelloWorld Servlet example, but the example was primarily focused on showing how a Servlet is deployed for use. Coding and deploying are the fundamental parts of Servlet development. Deployment was explained first because it is the exact same process for any given Servlet. Once explained it is a fairly safe assumption that you can repeat the process or simply copy and edit what already exists. Servlet code varies greatly depending on what the Servlet are designed to do. Understanding and demonstrating some of the different uses of Servlets are a lot easier if time and space are not devoted to rehashing the mundane act of deployment. Servlet code is where discussion is best focused, and that is exactly what the rest of the chapter does.
Since this is a Servlet-focused book, very little time is going to be spent on discussing the normal techniques and tricks of coding with Java. Any good Java book will discuss these, and they all are valid for use with Servlets. Time is best spent focusing on the Servlet API. Understanding HTTP and the HttpServlet class is a good start, but knowledge of the HttpServletRequest and HttpServletResponse objects are needed before some useful Servlets can be built.
HttpServletRequest and HttpServletResponse
The Servlet API makes manipulating an HTTP request and response pair relatively simple through use of the HttpServletRequest and HttpServletResponse objects. Both of these objects encapsulate a lot of functionality. Do not worry if it seems like this section is skimming through these two objects. Detailing all of the methods and members would be both tedious and confusing without understanding the rest of the Servlet API, but API discussion has to start somewhere and these two objects are arguably the most important. In this section discussion will only focus on a few of the most commonly used methods of each object. Later chapters of the book cover the other methods in full and in the context of which they are best used.
HttpServletResponse
The first and perhaps most important functionality to discuss is how to send information back to a client. As its name implies, the HttpServletResponse object is responsible for this functionality. By itself the HttpServletResponse object only produces an empty HTTP response. Sending back custom content requires using either the getWriter() or getOutputStream() method to obtain an output stream for writing content. These two methods return suitable objects for sending either text or binary content to a client, respectively. Only one of the two methods may be used with a given HttpServletResponse object. Attempting to call both methods causes an exception to be thrown.
With the HelloWorld Servlet example, Listing 2-1, the getWriter() method was used to get an output stream for sending the HTML markup. In the first few lines of HelloWorld.java, a getWriter() call obtained a java.io.PrintWriter object suitable for sending back the text.
PrintWriter out = response.getWriter(); out.println("<html>"); out.println("<head>"); out.println("<title>Hello World!</title>");
Using an instance of a PrintWriter object consists of providing a String object and calling either the print(), println(), or write() methods. The difference between the methods is that println appends a new line character, '\n', to each line of response text. In both the HelloServlet.java code and the generated HTML page, the println() method was used to make the lines of HTML easy to read. As Table 2-1 shows, the HTML markup matches each println() call used in HelloWorld.java. In practice the lines of HTML will not always match up so nicely to the code in a Servlet, but the same idea is the reason println() is usually preferred over solely using the print() method. When the HTML markup does not match what is expected, it is far easier to debug by matching calls to the println() method.
Using a PrintWriter is not meant to be complex, and it should now be clear how to use the PrintWriter object for sending text. Above and beyond what has previously been shown is sending custom encoded text. So far only one type of text has been sent, the default text encoding of HTTP, ISO-8895-1, but changing the character encoding is possible and is covered in full in Chapter 12.
Table 2-1. HTML Markup from HelloWorld Servlet
Generated Markup |
HelloWorld.java |
---|---|
<html> |
out.println("<html>"); |
<head> |
out.println("<head>"); |
<title>Hello World!</title> |
out.println("<title>Hello World!</title>"); |
</head> |
out.println("</head>"); |
<body> |
out.println("</head>"); |
<h1>Hello World!</h1> |
out.println("<h1>Hello World!</h1>"); |
</body> |
out.println("</body>"); |
</html> |
out.println("</html>"); |
Compared to using the getWriter() method, the getOutputStream() method is used when more control is needed over what is sent to a client. The returned OutputStream can be used for sending text, but is usually used for sending non-text-related binary information such as images. The reason for this is because the getOutputStream() method returns an instance of a javax.servlet.ServletOutputStream object, not a PrintWriter. The ServletOutputStream object directly inherits from java.io.OutputStream and allows a developer to write raw bytes. The PrintWriter objects lack this functionality because it always assumes you are writing text.
In most practical situations it is rarely needed to send raw bytes rather than text to a client, but this functionality is something a good Servlet developer should be aware of21. Often the incorrect mindset is to think Servlets can only send dynamically created text. By sending raw bytes, a Servlet can dynamically provide any form of digital content. The primary restriction on this functionality is being able to create the needed bytes for a desired content. For commonly used formats, including images and audio, it is not uncommon to see a Java API built to simplify the task. Combining this API with the Servlet API, it is then relatively easy to send the custom format. A good example to use would be the Java API for Advanced Imaging (JAI). Using this API many of the popular image formats can be produced from the server-side, even on servers not supporting a GUI.
Full discussion of non-text-producing Servlets is outside the scope of this book. Producing custom images, audio, and other non-text formats via Java is not something specific to Servlets. The only thing a Servlet needs to do is appropriately set a MIME type and send a client some bytes, but that is not a good reason to completely avoid an example. For completeness, Listing 2-5 provides a Servlet that dynamically generates an image and sends the bytes using a ServletOutputStream.
Listing 2-5. DynamicImage.java
package com.jspbook; import javax.servlet.*; import javax.servlet.http.*; import java.io.*; import java.awt.*; import java.awt.image.*; import com.sun.image.codec.jpeg.*; public class DynamicImage extends HttpServlet { public void doGet( HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("image/jpeg"); // Create Image int width = 200; int height = 30; BufferedImage image = new BufferedImage( width, height, BufferedImage.TYPE_INT_RGB); // Get drawing context Graphics2D g = (Graphics2D)image.getGraphics(); // Fill background g.setColor(Color.gray); g.fillRect(0, 0, width, height); // Draw a string g.setColor(Color.white); g.setFont(new Font("Dialog", Font.PLAIN, 14)); g.drawString("http://www.jspbook.com",10,height/2+4); // Draw a border g.setColor(Color.black); g.drawRect(0,0,width-1,height-1); // Dispose context g.dispose(); // Send back image ServletOutputStream sos = response.getOutputStream(); JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(sos); encoder.encode(image); } }
Save the preceding code as DynamicImage.java in the /WEB-INF/classes/com/jspbook directory of the jspbook Web Application. Compile and deploy the DynamicImage Servlet with a mapping to the /DynamicImage URL extension of the jspbook Web Application. After reloading the Web Application, browse to http://127.0.0.1/jspbook/DynamicImage. A JPEG formatted image is dynamically generated on each request to the Servlet. Figure 2-8 shows an example of one of the dynamically generated images.
Figure 2-8. DynamicImage Servlet
Before going out and creating your own image-producing Servlet, a fair warning should be given regarding the preceding code. For simplicity the code uses an object from the com.sun.image.codec.jpeg package that is unofficially included in the J2SDK 1.4. Code from the com.sun package is not guaranteed to be around in future Java releases, nor is it meant for developers to use. A proper solution would be to use an instance of the ImageEncoder class from the Java Advanced Imaging API, but that would have required you download and install the JAI before running the example.
Response Headers
Along with sending content back to a client, the HttpServletResponse object is also used to manipulate the HTTP headers of a response. HTTP response headers are helpful for informing a client of information such as the type of content being sent back, how much content is being sent, and what type of server is sending the content. The HttpServletResponse object includes the following methods for manipulating HTTP response headers:
-
addHeader(java.lang.String name, java.lang.String value): The addHeader() method adds a response header with the given name and value. This method allows response headers to have multiple values.
-
containsHeader(java.lang.String name): The containsHeader() method returns a boolean indicating whether the named response header has already been set.
-
setHeader(java.lang.String name, java.lang.String value): The setHeader() method sets a response header with the given name and value. If the header had already been set, the new value overwrites the previous one. The containsHeader() method can be used to test for the presence of a header before setting its value.
-
setIntHeader(java.lang.String name, int value): The setIntHeader() sets a response header with the given name and integer value. If the header had already been set, the new value overwrites the previous one. The containsHeader() method can be used to test for the presence of a header before setting its value.
-
setDateHeader(java.lang.String name, long date): The setDateHeader() sets a response header with the given name and date value. The date is specified in terms of milliseconds since the epoch. If the header had already been set, the new value overwrites the previous one. The containsHeader() method can be used to test for the presence of a header before setting its value.
-
addIntHeader(java.lang.String name, int value): The addIntHeader() method adds a response header with the given name and integer value. This method allows response headers to have multiple values.
-
addDateHeader(java.lang.String name, long date): The addDateHeader() method adds a response header with the given name and date value. The date is specified in terms of milliseconds since the epoch22. This method doesn't override previous response headers and allows response headers to have multiple values.
In the introduction to HTTP that appeared earlier in this chapter, a few HTTP response headers were seen, and in the HelloWorld Servlet the Content-Type response header was used. In both these cases, elaboration on the headers' semantics was conveniently skipped. This was done intentionally to simplify the examples, but it is time to clarify what these unexplained HTTP headers mean (see Table 2-2), along with introducing some of the other helpful headers that can be set by an HttpServletResponse object.
In most cases the most important header to worry about as a Servlet author is Content-Type. This header should always be set to 'text/html' when a Servlet is sending back HTML. For other formats the appropriate MIME type23 should be set.
Response Redirection
Any HTTP response code can be sent to a client by using the setStatus() method of an HttpServletResponse object. If everything works OK, Servlet will send back a status code 200, OK. Another helpful status code to understand is 302, "Resource Temporarily Moved". This status code informs a client that the resource they were looking for is not at the requested URL, but is instead at the URL specified by the Location header in the HTTP response. The 302 response code is helpful because just about every Web browser automatically follows the new link without informing a user. This allows a Servlet to take a user's request and forward it any other resource on the Web.
Because of the common implementation of the 302 response code, there is an excellent use for it besides the intended purpose. Most Web sites track where visitors come from to get an idea of what other sites are sending traffic. The technique for accomplishing involves extracting the "referer" (note the slightly inaccurate spelling) header of an HTTP request. While this is simple, there is no equally easy way of tracking where a site sends traffic. The problem arises because any link on a site that leads to an external resource does send a request back to the site it was sent from. To solve the problem, a clever trick can be used that relies on the HTTP 302 response code. Instead of providing direct links to external resources, encode all links to go to the same Servlet on your site but include the real link as a parameter. Link tracking is then provided using the Servlet to log the intended link while sending the client back a 302 status code along with the real link to visit.
Table 2-2. HTTP 1.1 Response Header Fields
Header Field |
Header Value |
---|---|
Age |
A positive integer representing the estimated amount of time since the response was generated from the server. |
Location |
Some HTTP response codes redirect a client to a new resource. The location of this resource is specified by the Location header as an absolute URI. |
Retry-After |
The Retry-After response header field can be used with a 503 (Service Unavailable) response to indicate how long the service is expected to be unavailable to the requesting client. The value of this field can be either a date or an integer number of seconds (in decimals) after the time of the response. |
Server |
The Server field is a string representing information about the server that generated this response. |
Content-Length |
The Content-Length entity header field indicates the size of the message body, in decimal number of octets (8-bit bytes), sent to the recipient or, in the case of the HEAD method, the size of the entity body that would have been sent had the request been a GET. |
Content-Type |
The MIME type that corresponds to the content of the HTTP response. This value is often used by a browser to determine if the content should be rendered internally or launched for rendering by an external application. |
Date |
The Date field represents the date and time at which the message was originated. |
Pragma |
The Pragma field is used to include implementation-specific directives that may apply to any recipient along the request-response chain. The most commonly used value is "no-cache", indicating a resource shouldn't be cached. |
As you might imagine, using a Servlet to track links is very commonly done by sites with HTTP-aware developers. The HTTP 302 response code is used so often it has a convenience method, sendRedirect(), in the HttpServletResponse object. The sendRedirect() method takes one parameter, a string representing the new URL, and automatically sets the HTTP 302 status code with appropriate headers. Using the sendRedirect() method and a java.util.Hashtable, it is easy to create a Servlet for tracking link use. Save the code in Listing 2-6 as LinkTracker.java in the /WEB-INF/classes/com/jspbook directory of the jspbook Web Application. Deploy the Servlet to the /LinkTracker URL mapping.
Listing 2-6. LinkTracker.java
package com.jspbook; import java.util.*; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class LinkTracker extends HttpServlet { static private Hashtable links = new Hashtable(); String tstamp; public LinkTracker() { tstamp = new Date().toString(); } public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { String link = request.getParameter("link"); if (link != null && !link.equals("")) { synchronized (links){ Integer count = (Integer) links.get(link); if (count == null) { links.put(link, new Integer(1)); } else { links.put(link, new Integer(1+count.intValue())); } } response.sendRedirect(link); } else { response.setContentType("text/html"); PrintWriter out = response.getWriter(); request.getSession(); out.println("<html>"); out.println("<head>"); out.println("<title>Links Tracker Servlet</title>"); out.println("</head>"); out.println("<body>"); out.println("<p>Links Tracked Since"); out.println(tstamp+":</p>"); if (links.size() != 0) { Enumeration enum = links.keys(); while (enum.hasMoreElements()) { String key = (String)enum.nextElement(); int count = ((Integer)links.get(key)).intValue(); out.println(key+" : "+count+" visits<br>"); } } else { out.println("No links have been tracked!<br>"); } out.println("</body>"); out.println("</html>"); } } public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { doGet(request, response); } }
To complement the LinkTracker Servlet, some links are needed that use it. The links can be to any resource as long as they are encoded properly. Encoding the links is not difficult; it requires the real link be passed as the link parameter in a query string. Listing 2-7 is a simple HTML page that includes a few properly encoded links. Save the HTML as links.html in the base directory of the jspbook Web Application.
Listing 2-7. Some Links Encoded for the LinkTracker Servlet
<html> <head> <title>Some Links Tracked by the LinkTracker Servlet</title> </head> <body> Some good links for Servlets and JSP. Each link is directed through the LinkTracker Servlet. Click on a few and visit the <a href="LinkTracker">LinkTracker Servlet</a>. <ul> <li><a href="LinkTracker?link=http://www.jspbook.com"> Servlets and JSP Book Support Site</a></li> <li><a href="LinkTracker?link=http://www.jspinsider.com"> JSP Insider</a></li> <li><a href="LinkTracker?link=http://java.sun.com"> Sun Microsystems</a></li> </ul> </body> </html>
After reloading the Web Application, browse to http://127.0.0.1/jspbook/links.html. Figure 2-9 shows what the page looks like after rendered by a browser. Click a few times on any combination of the links.
Figure 2-9. Browser Rendering of links.html
Each link is directed through the LinkTracker Servlet, which in turn directs a browser to visit the correct link. Before each redirection the LinkTracker Servlet logs the use of the link by keying the link URL to an Integer object in a Hashtable. If you browse directly to the LinkTracker Servlet, http://127.0.0.1/jspbook/LinkTracker, it displays information about links visited. Figure 2-10 shows what the results look like after tracking a few links. Results are current as of the last reloading of the LinkTracker Servlet. This example does not log the information for long-term use, but nothing stops such a modification from being made.
Figure 2-10. Browser Rendering of Link Statistics from the LinkTracker Servlet
Response Redirection Translation Issues
Response redirection is a good tool to be aware of and works with any implementation of the Servlet API. However, there is a specific bug that tends to arise when using relative response redirection. For instance:
response.sendRedirect("../foo/bar.html");
would work perfectly fine when used in some Servlets but would not in others. The trouble comes from using the relative back, "../", to traverse back a directory. A JSP can correctly use this (assuming the browser translates the URL correctly), but the JSP can use it only if the request URL combined with the redirection ends up at the appropriate resource. For instance, if http://127.0.0.1/foo/bar.html is a valid URL, then http://127.0.0.1/foo/../foo/bar.html should also be valid. However, http://127.0.0.1/foo/foo/../foo/bar.html will not reach the same resource.
This may seem like an irrelevant problem, but we will soon introduce request dispatching that will make it clear why this is an issue. Request dispatching allows for requests to be forwarded on the server-sidemeaning the requested URL does not change, but the server-side resource that handles it can. Relative redirections are not always safe; " . . /" can be bad. The solution is to always use absolute redirections. Either use a complete URL such as:
response.sendRedirect("http://127.0.0.1/foo/bar.html");
Or use an absolute URL from the root, "/", of the Web Application.
response.sendRedirect("/for/bar.html")24;
In cases where the Web application can be deployed to a non-root URL, the HttpServletRequest getContextPath() method should be used in conjunction:
response.sendRedirect(request.getContextPath()+"/foo/bar.html");
Further information about the HttpServletRequest object and use of the getContextPath() method is provided later in this chapter.
Auto-Refresh/Wait Pages
Another response header technique that is uncommon but helpful is to send a wait page or a page that will auto-refresh to a new page after a given period of time. This tactic is helpful in any case where a response might take an uncontrollable time to generate, or for cases where you want to ensure a brief pause in a response. The entire mechanism revolves around setting the Refresh response header25. The header can be set using the following:
response.setHeader("Refresh", "time; URL=url" );
Where "time" is replaced with the amount of seconds, the page should wait, and "url" is replaced with the URL that the page should eventually load. For instance, if it was desired to load http://127.0.0.1/foo.html after 10 seconds of waiting, the header would be set as so:
response.setHeader("Refresh", "10; URL=http://127.0.0.1/foo.html");
Auto-refreshing pages are helpful because they allow for a normal "pull" model, waiting for a client's request, to "push" content. A good practical use case would be a simple your-request-is-being-processed-page that after a few seconds refreshes to show the results of the response. The alternative (also the most commonly used approach) is to wait until a request is officially finished before sending back any content. This results in a client's browser waiting for the response, sometimes appearing as if the request might time-out and resulting in the user making a time-consuming request twice26.
Another practical use case for wait page would be slowing down a request, perhaps to better ensure pertinent information is seen by the user. For example, a wait page that showed either an advertisement or legal information before redirecting to the appropriately desired page.
It should be clear that there are several situations where the Refresh response header can come in handy. While it is not a standard HTTP 1.1 header, it is something that is considered a de facto standard27.
HttpServletRequest
A client's HTTP request is represented by an HttpServletRequest object. The HttpServletRequest object is primarily used for getting request headers, parameters, and files or data sent by a client. However, the Servlet specification enhances this object to also interact with a Web Application. Some of the most helpful features include session management and forwarding of requests between Servlets.
Headers
HTTP headers set by a client are used to inform a server about what software the client is using and how the client would prefer a server send back requested information. From a Servlet, HTTP request headers can be accessed by calling the following methods:
-
getHeader(java.lang.String name): The getHeader() method returns the value of the specified request header as a string. If the request did not include a header of the specified name, this method returns null. The header name is case insensitive. You can use this method with any request header.
-
getHeaders(java.lang.String name): The getHeaders() method returns all the values of the specified request header as an Enumeration of String objects. Some headers, such as Accept-Language, can be sent by clients as several headers, each with a different value rather than sending the header as a comma-separated list. If the request did not include any headers of the specified name, this method returns an empty Enumeration object. The header name is case insensitive. You can use this method with any request header.
-
getHeaderNames(): The getHeaderNames() method returns an Enumeration of all the names of headers sent by a request. In combination with the getHeader() and getHeaders() methods, getHeaderNames() can be used to retrieve names and values of all the headers sent with a request. Some containers do not allow access of HTTP headers. In this case null is returned.
-
getIntHeader(java.lang.String name): The getIntHeader() method returns the value of the specified request header as an int. If the request does not have a header of the specified name, this method returns 1. If the header cannot be converted to an integer, this method throws a NumberFormatException.
-
getDateHeader(java.lang.String name): The getDateHeader() method returns the value of the specified request header as a long value that represents a Date object. The date is returned as the number of milliseconds since the epoch. The header name is case insensitive. If the request did not have a header of the specified name, this method returns 1. If the header cannot be converted to a date, the method throws an IllegalArgumentException.
HTTP request headers are very helpful for determining all sorts of information. In the later chapters HTTP request headers are used as the primary resource for mining data about a client. This includes figuring out what language a client would prefer, what type of Web browser is being used, and if the client can support compressed content for efficiency. For now it is helpful to understand that these headers exist, and to get a general idea about what type of information the headers contain. Listing 2-8 is a Servlet designed to do just that. Save the following code as ShowHeaders.java in the /WEB-INF/classes/com/jspbook directory of the jspbook Web Application.
Listing 2-8. ShowHeaders.java
package com.jspbook; import java.util.*; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class ShowHeaders extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println("<html>"); out.println("<head>"); out.println("<title>Request's HTTP Headers</title>"); out.println("</head>"); out.println("<body>"); out.println("<p>HTTP headers sent by your client:</p>"); Enumeration enum = request.getHeaderNames(); while (enum.hasMoreElements()) { String headerName = (String) enum.nextElement(); String headerValue = request.getHeader(headerName); out.print("<b>"+headerName + "</b>: "); out.println(headerValue + "<br>"); } out.println("</body>"); out.println("</html>"); } }
Compile the Servlet and deploy it to the /ShowHeaders path of the jspbook Web Application. After reloading the Web application, browse to http://127.0.0.1/jspbook/ShowHeaders to see a listing of all the HTTP headers your browser sends (see Figure 2-11).
Figure 2-11. Browser Rendering of the ShowHeaders Servlet
The preceding is a good example of the headers normally sent by a Web browser. They are fairly self-descriptive. You can probably imagine how these headers can be used to infer browser and internationalization information. Later on we will do just that28, but for now we end our discussion of request headers with Table 2-3, which lists some of the most relevant and helpful ones.
Form Data and Parameters
Perhaps the most commonly used methods of the HttpServletRequest object are the ones that involve getting request parameters: getParameter() and getParameters(). Anytime an HTML form is filled out and sent to a server, the fields are passed as parameters. This includes any information sent via input fields, selection lists, combo boxes, check boxes, and hidden fields, but excludes file uploads. Any information passed as a query string is also available on the server-side as a request parameter. The HttpServletRequest object includes the following methods for accessing request parameters.
-
getParameter(java.lang.String parameterName): The getParameter() method takes as a parameter a parameter name and returns a String object representing the corresponding value. A null is returned if there is no parameter of the given name.
Table 2-3. HTTP Request Headers
Name
Value
Host
The Host request header field specifies the Internet host and port number of the resource being requested, as obtained from the original URL given by the user or referring resource. Mandatory for HTTP 1.1.
User-Agent
The User-Agent request header field contains information about the user agent (or browser) originating the request. This is for statistical purposes, the tracing of protocol violations, and automated recognition of user agents for the sake of tailoring responses to avoid particular user-agent limitations.
Accept
The Accept request header field can be used to specify certain media types that are acceptable for the response. Accept headers can be used to indicate that the request is specifically limited to a small set of desired types.
Accept-Language
The Accept-Language request header field is similar to Accept, but restricts the set of natural languages that are preferred as a response to the request.
Accept-Charset
The Accept-Charset request header field can be used to indicate what character sets are acceptable for the response. This field allows clients capable of understanding more comprehensive or special-purpose character sets to signal that capability to a server that is capable of representing documents in those character sets. The ISO-8859-1 character set can be assumed to be acceptable to all user -agents. Referer (sic) The Referer request header field allows the client to specify, for the server's benefit, the address (URI) of the resource from which the Request URI was obtained.
-
getParameters(java.lang.String parameterName): The getParameters() method is similar to the getParameter() method, but it should be used when there are multiple parameters with the same name. Often an HTML form check box or combo box sends multiple values for the same parameter name. The getParameter() method is a convenient method of getting all the parameter values for the same parameter name returned as an array of strings.
-
getParameterNames(): The getParameterNames() method returns a java.util.Enumeration of all the parameter names used in a request. In combination with the getParameter() and getParameters() method, it can be used to get a list of names and values of all the parameters included with a request.
Like the ShowHeaders Servlet, it is helpful to have a Servlet that reads and displays all the parameters sent with a request. You can use such a Servlet to get a little more familiar with parameters, and to debug HTML forms by seeing what information is being sent. Listing 2-9 is such a Servlet.
Listing 2-9. ShowParameters.java
package com.jspbook; import java.util.*; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class ShowParameters extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println("<html>"); out.println("<head>"); out.println("<title>Request HTTP Parameters Sent</title>"); out.println("</head>"); out.println("<body>"); out.println("<p>Parameters sent with request:</p>"); Enumeration enum = request.getParameterNames(); while (enum.hasMoreElements()) { String pName = (String) enum.nextElement(); String[] pValues = request.getParameterValues(pName); out.print("<b>"+pName + "</b>: "); for (int i=0;i<pValues.length;i++) { out.print(pValues[i]); } out.print("<br>"); } out.println("</body>"); out.println("</html>"); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { doGet(request, response); } }
Compile and deploy the ShowParameters Servlet in the jspbook Web Application with a mapping to /ShowParameters path. After reloading the jspbook Web Application, create a few simple HTML forms and use the Servlet to see what parameters are sent. If your HTML is out of practice, do not worry. Listing 2-10 provides a sample HTML form along with a link to a great online HTML reference, http://www.jspinsider.com/reference/html.jsp.
Listing 2-10. exampleform.html
<html> <head> <title>Example HTML Form</title> </head> <body> <p>To debug a HTML form set its 'action' attribute to reference the ShowParameters Servlet.</p> <form action="http://127.0.0.1/jspbook/ShowParameters" method="post"> Name: <input type="text" name="name"><br> Password: <input type="password" name="password"><br> Select Box: <select name="selectbox"> <option value="option1">Option 1</option> <option value="option2">Option 2</option> <option value="option3">Option 3</option> </select><br> Importance: <input type="radio" name="importance" value="very">Very, <input type="radio" name="importance" value="normal">Normal, <input type="radio" name="importance" value="not">Not<br> Comment: <br> <textarea name="textarea" cols="40" rows="5"></textarea><br> <input value="Submit" type="submit"> </form> </body> </html>
Either save the preceding HTML, or create any other HTML form and set the action attribute to http://127.0.0.1/jspbook/ShowParameters, and browse to the page. Save the preceding HTML as exampleform.html in the base directory of jspbook Web Application and browse to http://127.0.0.1/jspbook/exampleform.html. Figure 2-12 shows what the page looks like rendered by a Web browser.
Figure 2-12. Browser Rendering of exampleform.html
Fill out the form and click on the button labeled Submit to send the information to the ShowParameters Servlet. Something resembling Figure 2-13 will appear.
Figure 2-13. Browser Rendering of the ShowParameters Servlet
On the server-side each piece of information sent by a form is referenced by the same name as defined in your HTML form and is linked to a value that a user entered. The ShowParameters Servlet shows this by using getParameterNames() for a list of all parameter names and subsequently calling getParameters() for the matching value or set of values for each name. The core of the Servlet is a simple loop.
Enumeration enum = request.getParameterNames(); while (enum.hasMoreElements()) { String pName = (String) enum.nextElement(); String[] pValues = request.getParameterValues(pName); out.print("<b>"+pName + "</b>: "); for (int i=0;i<pValues.length;i++) { out.print(pValues[i]); } out.print("<br>"); }
Using parameters, information can be solicited from HTML clients for use by a Servlet. While the ShowParameters Servlet only takes parameters and echoes them back to a client, normally those parameter values are used with other code to generate responses. Later on in the book this functionality will commonly be used with Servlets and JSP for further interacting with clients, including sending email and user authentication.
File Uploads
File uploads29 are simple for HTML developers but difficult for server-side developers. Sadly, this often results in discussion of Servlets and HTML forms that conveniently skip the topic of file uploads, but understanding HTML form file uploads is a needed skill for any good Servlet developer. Consider any situation where a client needs to upload something besides a simple string of text, perhaps when a picture needs to be uploaded. Using the getParameter() method will not work because it produces unpredictable resultsusually mangling the file being sent or failing to find the content of the file at all.
The reason file uploads are usually considered difficult is because of how the Servlet API handles them. There are two primary MIME types for form information: application/x-www-form-urlencoded and multipart/form-data. The first MIME type, application/x-www-form-urlencoded, is the MIME type most everyone is familiar with and results in the Servlet API automatically parsing out name and value pairs. The information is then available by invoking HttpServletRequest getParameter() or any of the other related methods as previously described. The second MIME type, multipart/form-data, is the one that is usually considered difficult. The reason why is because the Servlet API does nothing to help you with it30. Instead the information is left as is and you are responsible for parsing the request body via either HttpServletRequest getInputStream() or getReader().
The complete multipart/form-data MIME type and the format of the associated HTTP request are explained in RFC 1867, http://www.ietf.org/rfc/rfc1867.txt. You can use this RFC to determine how to appropriately handle information posted to a Servlet. The task is not very difficult, but, as will be explained later, this is usually not needed because other developers have created complementary APIs to handle file uploads.
To best understand what you are dealing with when multipart/form-data information is sent, it is valuable to actually look at the contents of such a request. This can be accomplished by making a file-uploading form and a Servlet that regurgitates the information obtained from the ServletInputStream provided. Listing 2-11 provides the code for such a Servlet. This Servlet accepts a multipart/form-data request and displays the contents of it as plain text.
Listing 2-11. ShowForm.java
package com.jspbook; import java.util.*; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class ShowForm extends HttpServlet { public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { response.setContentType("text/plain"); PrintWriter out = response.getWriter(); ServletInputStream sis = request.getInputStream(); for (int i = sis.read(); i != -1; i = sis.read()) { out.print((char)i); } } public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { doPost(request, response); } }
Save the preceding code as ShowForm.java in the /WEB-INF/classes/com/jspbook directory of the jspbook Web Application. Deploy the Servlet to /ShowForm. From now on information posted by any form can be viewed by directing the request to http://127.0.0.1/jspbook/ShowForm. Try the Servlet out by creating an HTML form that uploads a file (Listing 2-12).
Listing 2-12. multipartform.html
<html> <head> <title>Example HTML Form</title> </head> <body> <p>The ShowForm Servlet will display the content posted by an HTML form. Try it out by choosing a file (smaller size is preferred) to reference the ShowParameters Servlet.</p> <form action="http://127.0.0.1/jspbook/ShowForm" method="post" enctype="multipart/form-data"> Name: <input type="text" name="name"><br> File: <input type="file" name="file"><br> <input value="Submit" type="submit"> </form> </body> </html>
Save the preceding code as multipartform.html in the base directory of the jspbook Web Application and browse to it. Displayed is a small HTML form with two inputs, a name and a file to upload. Figure 2-14 provides a browser rendering of the page.
Figure 2-14. Browser Rendering of multipartform.html
Fill in the form with any value for the Name field and any file for the File field. A smaller file is preferred because its contents are going to be displayed by the ShowForm Servlet and a large file will take up a lot of space. A good candidate for a file is multipartform.html itself. After completing the form, click on the Submit button. The ShowForm Servlet will then show the content that was posted. For example, using multipartform.html as the file would result in something similar to the following:
-----------------------------196751392613651805401540383426 Content-Disposition: form-data; name="name" blah blah -----------------------------196751392613651805401540383426 Content-Disposition: form-data; name="file"; filename="multipartform.html" Content-Type: text/html <html> <head> <title>Example HTML Form</title> </head> <body> <p>The ShowForm Servlet will display the content posted by an HTML form. Try it out by choosing a file (smaller size is preferred) to reference the ShowParameters Servlet.</p> <form action="http://127.0.0.1/jspbook/ShowForm" method="post" enctype="multipart/form-data"> Name: <input type="text" name="name"><br> File: <input type="file" name="file"><br> <input value="Submit" type="submit"> </form> </body> </html> -----------------------------196751392613651805401540383426--
This would be the type of information you have to parse through when handling a multipart/form-data request. If the file posted is not text, it will not be as pretty as the preceding, but there will always be a similar format. Each multipart has a unique token declaring its start. In the preceding the following was used:
-----------------------------196751392613651805401540383426
This declares the start of the multipart section and concluded at the ending token, which is identical to the start but with '--' appended. Between the starting and ending tokens are sections of data (possibly nested multiparts) with headers used to describe the content. For example, in the preceding code the first part described a form parameter:
Content-Disposition: form-data; name="name" blah blah
The Content-Disposition header defines the information as being part of the form and identified by the name "name". The value of "name" is the content following; by default its MIME type is text/plain. The second part describes the uploaded file:
Content-Disposition: form-data; name="file"; filename="multipartform.html" Content-Type: text/html <html> <head> <title>Example HTML Form</title> </head> <body> ...
This time the Content-Disposition header defines the name of the form field to be "file", which matches what was in the HTML form, and describes the content-type as text/html, since it is not text/plain. Following the headers is the uploaded content, the code for multipartform.html.
You should be able to easily imagine how to go about creating a custom class that parses this information and saves the uploaded file to the correct location. In cases where the uploaded file is not using a special encoding, the task is as easy as parsing the file's name from the provided headers and saving the content as is. Listing 2-13 provides the code for doing exactly that, and accommodates a file of any size. The Servlet acts as a file upload Servlet, which places files in the /files directory of the jspbook Web Application and lets users browse through, optionally downloading previously uploaded files.
Listing 2-13. FileUpload.java
package com.jspbook; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class FileUpload extends HttpServlet { public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println("<html>"); out.print("File upload success. <a href=\"/jspbook/files/"); out.print("\">Click here to browse through all uploaded "); out.println("files.</a><br>"); ServletInputStream sis = request.getInputStream(); StringWriter sw = new StringWriter(); int i = sis.read(); for (;i!=-1&&i!='\r';i=sis.read()) { sw.write(i); } sis.read(); // ditch '\n' String delimiter = sw.toString(); int count = 0; while(true) { StringWriter h = new StringWriter(); int[] temp = new int[4]; temp[0] = (byte)sis.read(); temp[1] = (byte)sis.read(); temp[2] = (byte)sis.read(); h.write(temp[0]); h.write(temp[1]); h.write(temp[2]); // read header for (temp[3]=sis.read();temp[3]!=-1;temp[3]=sis.read()) { if (temp[0] == '\r' && temp[1] == '\n' && temp[2] == '\r' && temp[3] == '\n') { break; } h.write(temp[3]); temp[0] = temp[1]; temp[1] = temp[2]; temp[2] = temp[3]; } String header = h.toString(); int startName = header.indexOf("name=\""); int endName = header.indexOf("\"",startName+6); if (startName == -1 || endName == -1) { break; } String name = header.substring(startName+6, endName); if (name.equals("file")) { startName = header.indexOf("filename=\""); endName = header.indexOf("\"",startName+10); String filename = header.substring(startName+10,endName); ServletContext sc = request.getSession().getServletContext(); File file = new File(sc.getRealPath("/files")); file.mkdirs(); FileOutputStream fos = new FileOutputStream( sc.getRealPath("/files")+"/"+filename); // write whole file to disk int length = 0; delimiter = "\r\n"+delimiter; byte[] body = new byte[delimiter.length()]; for (int j=0;j<body.length;j++) { body[j] = (byte)sis.read(); } // check it wasn't a 0 length file if (!delimiter.equals(new String(body))) { int e = body.length-1; i=sis.read(); for (;i!=-1;i=sis.read()) { fos.write(body[0]); for (int l=0;l<body.length-1;l++) { body[l]=body[l+1]; } body[e] = (byte)i; if (delimiter.equals(new String(body))) { break; } length++; } } fos.flush(); fos.close(); } if (sis.read() == '-' && sis.read() == '-') { break; } } out.println("</html>"); } public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { doPost(request, response); } }
Save the preceding code as FileUpload.java in the /WEB-INF/classes/com/jspbook directory of the jspbook Web Application. The code parses through each part of the form datathat is, each parameter or fileand saves all files in the /files directory of the jspbook Web Application.
The code is purposely left as one large Servlet because it is initially easier to digest the information if it is all in one place; nothing stops you from reimplementing the preceding code in a more object-oriented fashion, perhaps modeling an enhanced version of the getParameter() method. Two important points should be noted about the code for the FileUpload Servlet. At all times information is read directly from the ServletInputStream objectthat is, directly from the client and minimally buffered. This allows for very large files to be handled equally well as files of a small size. Additionally, this parses completely through the information in the HTTP postthat is, it determines the delimiter and keeps parsing until the end of the request, when the delimiter with '--' appended is found.
ServletInputStream sis = request.getInputStream(); StringWriter sw = new StringWriter(); int i = sis.read(); for (;i!=-1 && i!='\r'; i=sis.read()) { sw.write(i); } sis.read(); // ditch '\n' String delimiter = sw.toString(); int count = 0; while(true) { ... if (sis.read() == '-' && sis.read() == '-') { break; } }
The while loop loops indefinitely, reading through each part of the form data. Recall form information is posted with a delimiter such as:
-----------------------------196751392613651805401540383426\r\n
Used to separate each section, ended with the same delimiter with '--' appended:
-----------------------------196751392613651805401540383426--
The code for FileUpload.java automatically reads a section using the delimiter, but the code conditionally continues based on what is found immediately after the delimiter. Should the character sequence '\r\n' be found then the loop continues, with the characters discarded. However, should the character '' be found, the loop is terminated because the end of the form information has been found.
The body of the indefinite while loop is responsible for parsing out the header and the content of the form-data part. Header information is found by parsing, starting from the delimiter, until the appropriate character sequence, '\r\n\r\n', is found.
StringWriter h = new StringWriter(); int[] temp = new int[4]; temp[0] = (byte)sis.read(); temp[1] = (byte)sis.read(); temp[2] = (byte)sis.read(); h.write(temp[0]); h.write(temp[1]); h.write(temp[2]); // read header for (temp[3]=sis.read();temp[3]!=-1;temp[3]=sis.read()) { if (temp[0] == '\r' && temp[1] == '\n' && temp[2] == '\r' && temp[3] == '\n') { break; } h.write(temp[3]); temp[0] = temp[1]; temp[1] = temp[2]; temp[2] = temp[3]; } String header = h.toString();
Recall that form-part header information is separated from content by a blank line, '\r\n'meaning the end of a line followed by a blank line; '\r\n\r\n', signifies the division between header and content information, which is why '\r\n\r\n' is being searched for. The actual search is trivial; a temporary array, temp, that holds four characters is used to check the last four characters parsed. After the entire header is parsed, it is echoed in the response, and the name of the form-part is determined.
int startName = header.indexOf("name=\""); int endName = header.indexOf("\"",startName+6); if (startName == -1 || endName == -1) { break; } String name = header.substring(startName+6, endName);
The form-part's name is specified, if it was provided, using the following format, name="name", where name is the name as specified in the HTML form using the name attribute. The preceding code does nothing more than use the string manipulation functions of the String class to determine the value of name.
After the name of the form-part is determined, the matching content is selectively handled: if the name is "file", the contents are saved as a file; any other name is treated as a form parameter and echoed back in the response. There is nothing special about the name "file"; it is an arbitrary name chosen so that FileUpload.java knows to save the content as a file. The code used to save the file is similar to the code used to parse header information, except this time the delimiter is the form-part delimiter, not '\r\n\r\n'.
if (name.equals("file")) { startName = header.indexOf("filename=\""); endName = header.indexOf("\"",startName+10); String filename = header.substring(startName+10,endName); ServletConfig config = getServletConfig(); ServletContext sc = config.getServletContext(); FileOutputStream fos = new FileOutputStream(sc.getRealPath("/")+filename); // write whole file to disk int length = delimiter.length(); byte[] body = new byte[delimiter.length()]; for (int j=0;j<body.length-1;j++) { body[j] = (byte)sis.read(); fos.write(body[j]); } int e = body.length-1; i = sis.read(); for (;i!=-1;i=sis.read()) { body[e] = (byte)i; if (delimiter.equals(new String(body))) { break; } fos.write(body[e]); for(int k=0;k<body.length-1;k++) { body[k] = body[k+1]; } length++; } fos.flush(); fos.close(); out.println("<p><b>Saved File:</b> "+filename+"</p>"); out.println("<p><b>Length:</b> "+ length+"</p>"); }
The code first determines the name of the file being uploaded by searching for a special string, filename="name", where name is the file's name, in the header. Next, a file is created in the /files directory of the Web Application31 with the same name, and the content is saved.
In order to use the FileUpload Servlet, an HTML form, similar to multipartform.html, needs to be created. The form may include any number of input elements of any type and in any order, but one file input must be present and the input must be named "file". Listing 2-14 is a simple example. Save the following code as fileupload.html in the root directory of the jspbook Web Application.
Listing 2-14. fileupload.html
<html> <head> <title>Example HTML Form</title> </head> <body> <p>Select a file to upload or <a href="/jspbook/files/">browse currently uploaded files.</a></p> <form action="http://127.0.0.1/jspbook/FileUpload" method="post" enctype="multipart/form-data"> File: <input type="file" name="file"><br> <input value="Upload File" type="submit"> </form> </body> </html>
The HTML form posts information to /FileUpload, the deployment of the FileUpload Servlet. Browse to http://127.0.0.1/jspbook/fileupload.html to try out the newly created HTML form. Figure 2-15 provides a browser rendering of the form.
Figure 2-15. Browser Rendering of fileupload.html
Fill in the form fields. The file field should be a file you wish to have on the server; try something such as a picture. Upon clicking on the Submit button, the form's information, including the file, is uploaded to the FileUpload Servlet. The FileUpload Servlet saves the file and displays a summary page, as shown in Figure 2-16.
Figure 2-16. Browser Rendering of the FileUpload Servlet's Response
Verify the file has been successfully uploaded by clicking on the link provided to browse the /files directory of the jspbook Web Application. Tomcat displays the default directory listing that includes the file just uploaded. To download the file or any other file in the directory, simply click on the file's listing.
In general, the UploadFile Servlet is demonstrating how a Servlet can parse multi-part form data and save uploaded files. The code can be adapted for other situations where files need to be uploaded, perhaps an online photo album or a more robust file sharing service. It should be noted that no restriction exists on what may be done with a file uploaded by an HTML form. In the FileUpload.java example, the file is saved in the /files directory of the Web Application, but the file could just as easily been saved elsewhere in the Web Application or not saved at all.
Using a File Upload API
As a good developer, it is helpful to understand exactly how the Servlet API handles file uploads; however, in most every practical case you can do away with manually parsing and handling a file upload. File uploads are nothing new, and several implementations of file upload API exist. A good, free, open source file upload API is the Jakarta Commons File Upload API, http://jakarta.apache.org/commons/fileupload/. Download the latest release of the code (it is a very small JAR) and put the JAR file in the /WEB-INF/lib directory of the jspbook Web Application.
There are several reasons a file upload API can be helpful. A great reason is it can greatly simplify your code. Consider the FileUpload Servlet in the previous section. Using the Jakarta Commons File Upload API, the code can be reduced to Listing 2-15.
Listing 2-15. FileUploadCommons.java
package com.jspbook; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; import org.apache.commons.fileupload.*; import java.util.*; public class FileUploadCommons extends HttpServlet { public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println("<html>"); out.print("File upload success. <a href=\"/jspbook/files/"); out.print("\">Click here to browse through all uploaded "); out.println("files.</a><br>"); ServletContext sc = getServletContext(); String path = sc.getRealPath("/files"); org.apache.commons.fileupload.FileUpload fu = new org.apache.commons.fileupload.FileUpload(); fu.setSizeMax(-1); fu.setRepositoryPath(path); try { List l = fu.parseRequest(request); Iterator i = l.iterator(); while (i.hasNext()) { FileItem fi = (FileItem)i.next(); fi.write(path+"/"+fi.getName()); } } catch (Exception e) { throw new ServletException(e); } out.println("</html>"); } public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { doPost(request, response); } }
The code more closely follows good object-oriented programming by abstracting request parsing logic. Instead of implementing RFC 1867 by hand, as we did in FileUpload.java, the Jakarta Commons File Upload API handles all the work.
ServletContext sc = getServletContext(); String path = sc.getRealPath("/files"); FileUpload fu = new FileUpload(); fu.setSizeMax(-1); fu.setRepositoryPath("/root/todelete"); try { List l = fu.parseRequest(request); Iterator i = l.iterator(); while (i.hasNext()) { FileItem fi = (FileItem)i.next(); fi.write(path+"/"+fi.getName()); } } catch (Exception e) { throw new ServletException(e); }
We will not discuss the file upload API in depth, but it should be easy to follow what is going on. The FileUpload object abstracts all of the code responsible for parsing multipart/form-data information. The only thing we need to care about is limiting the size of file uploads and specifying a temporary directory for the API to work with. The parseRequest() method takes a HttpServletRequest and returns an array of files parsed from the inputwhat more could you ask for?
In addition to simplifying code, there are a few other reasons that justify using an existing file upload API. There are several nuances of file uploads that need to be dealt with; multiple files can be uploaded, different encodings can be used, and excessively large files might be uploaded. In short, the less code you have to manage the better, and a good file upload API can easily take care of handling multipart/form-data information. Certainly consider using an existing file upload API when working with Servlets and file uploads. If anything, the Jakarta Commons File Upload API provides an excellent starting point for handling file uploads or creating a custom file upload API.
Request Delegation and Request Scope
Request delegation is a powerful feature of the Servlet API. A single client's request can pass through many Servlets and/or to any other resource in the Web Application. The entire process is done completely on the server-side and, unlike response redirection, does not require any action from a client or extra information sent between the client and server. Request delegation is available through the javax.servlet.RequestDispatcher object. An appropriate instance of a RequestDispatcher object is available by calling either of the following methods of a ServletRequest object:
-
getRequestDispatcher(java.lang.String path): The getRequestDispatcher() method returns the RequestDispatcher object for a given path. The path value may lead to any resource in the Web Application and must start from the base directory, "/".
-
getNamedDispatcher(java.lang.String name): The getNamedDispatcher() method returns the RequestDispatcher object for the named Servlet. Valid names are defined by the servlet-name elements of web.xml.
A RequestDispatcher object provides two methods for including different resources and for forwarding a request to a different resource.
-
forward(javax.servlet.ServletRequest, javax.servlet.ServletResponse): The forward() method delegates a request and response to the resource of the RequestDispatcher object. A call to the forward() method may be used only if no content has been previously sent to a client. No further data can be sent to the client after the forward has completed.
-
include(javax.servlet.ServletRequest, javax.servlet.ServletResponse): The include() method works similarly to forward() but has some restrictions. A Servlet can include() any number of resources that can each generate responses, but the resource is not allowed to set headers or commit a response.
Request delegation is often used to break up a large Servlet into smaller, more relevant parts. A simple case would include separating out a common HTML header that all pages on a site share. The RequestDispacher object's include() method then provides a convenient method of including the header with the Servlet it was separated from and in any other Servlet needing the header. Any future changes to the header, and all the Servlets automatically reflect the change. For now, an example of simple Servlet server-side includes will be held in abeyance. JavaServer Pages32 provide a much more elegant solution to this problem, and in practice Servlet request delegation is usually used for an entirely different purpose.
In addition to simple server-side includes, request delegation is a key part of server-side Java implementations of popular design patterns. With respect to Servlet and JSP, design patterns are commonly agreed-upon methods for building Web Applications that are robust in functionality and easily maintainable. The topic of design is given a whole chapter to itself, Chapter 11, so no direct attempt will be given to demonstrate it now. Instead, discussion will focus on laying the foundation for Chapter 11 by explaining the new object scope that request delegation introduces.
With Java there are well-defined scopes for variables that you should already be familiar with. Local variables declared inside methods are by default only available inside the scope of that method. Instance variables, declared in a class but outside a method or constructor, are available to all methods in the Java class. There are many other scopes too, but the point is that these scopes are helpful to keep track of objects and help the JVM accurately garbage-collect memory. In Servlets, all of the previous Java variable scopes still exist, but there are some new scopes to be aware of. Request delegation introduces the request scope.
Request scope and the other scopes mentioned in this chapter are not something officially labeled by the Servlet specification33. The Servlet specification only defines a set of methods that allow objects to be bound to and retrieved from various containers (that are themselves objects) in the javax.servlet package. Since an object bound in this manner is referenced by the container it was bound to, the bound object is not destroyed until the reference is removed. Hence, bound objects are in the same "scope" as the container they are bound to. The HttpServletRequest object is such a container and includes such methods. These methods can be used to bind, access, and remove objects to and from the request scope that is shared by all Servlets to which a request is delegated. This concept is important to understand and can easily be shown with an example.
An easy way to think of request scope is as a method of passing any object between two or more Servlets and being assured the object goes out of scope (i.e., will be garbage-collected) after the last Servlet is done with it. More powerful examples of this are provided in later chapters, but to help clarify the point now, here are two Servlets that pass an object. Save the code in Listing 2-16 as Servlet2Servlet.java in the /WEB-INF/classes/com/jspbook directory of the jspbook Web Application.
Listing 2-16. Servlet2Servlet.java
package com.jspbook; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class Servlet2Servlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { response.setContentType("text/html"); String param = request.getParameter("value"); if(param != null && !param.equals("")) { request.setAttribute("value", param); RequestDispatcher rd = request.getRequestDispatcher("/Servlet2Servlet2"); rd.forward(request, response); return; } PrintWriter out = response.getWriter(); out.println("<html>"); out.println("<head>"); out.println("<title>Servlet #1</title>"); out.println("</head>"); out.println("<body>"); out.println("<h1>A form from Servlet #1</h1>"); out.println("<form>"); out.println("Enter a value to send to Servlet #2."); out.println("<input name=\"value\"><br>"); out.print("<input type=\"submit\" "); out.println("value=\"Send to Servlet #2\">"); out.println("</form>"); out.println("</body>"); out.println("</html>"); } }
Deploy the preceding Servlet and map it to the /Servlet2Servlet URL extension. Next, save the code in Listing 2-17 as Servlet2Servlet2.java in the /WEB-INF/classes/com/jspbook directory of the jspbook Web Application.
Listing 2-17. Servlet2Servlet2.java
package com.jspbook; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class Servlet2Servlet2 extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println("<html>"); out.println("<head>"); out.println("<title>Servlet #2</title>"); out.println("</head>"); out.println("<body>"); out.println("<h1>Servlet #2</h1>"); String value = (String)request.getAttribute("value"); if(value != null && !value.equals("")) { out.print("Servlet #1 passed a String object via "); out.print("request scope. The value of the String is: "); out.println("<b>"+value+"</b>."); } else { out.println("No value passed!"); } out.println("</body>"); out.println("</html>"); } }
Deploy the second Servlet and map it to the /Servlet2Servlet2 URL extension. Reload the jspbook Web Application and the example is ready for use. Browse to http://127.0.0.1/jspbook/Servlet2Servlet. Figure 2-17 shows what the Servlet response looks like after being rendered by a Web browser. A simple HTML form is displayed asking for a value to pass to the second Servlet. Type in a value and click on the button labeled Send to Servlet #2.
Figure 2-17. Browser Rendering of Servlet2Servlet
Information sent from the HTML form is sent straight back to the same Servlet that made the form, Servlet2Servlet. The Servlet verifies a value was sent; creates a new String object; places the String in request scope; and forwards the request to the second Servlet, Servlet2Servlet2. Figure 2-18 shows what a browser rendering of the second Servlet's output looks like. The content on the page shows the request was delegated to the second Servlet, but you can verify the request was delegated by the first Servlet by looking at the URL. This technique is extremely useful and discussed further in the design pattern chapter.
Figure 2-18. Browser Rendering of Request Delegated to Servlet2Servlet2