|
|
A quick introduction to Java Servlets
by Jay Eckles
In this article, I will very quickly introduce the experienced Java programmer to Java Servlets, the Java API for server-side web programming. Servlets are similar to CGI, ASP, PHP, and other server-side technologies in their capabilities; there are two main differences. First, Servlets are Java, with all of the benefits that Java brings to the table. Second, Servlets require the installation of a Servlet container, often called an application server, in order to run. We'll talk about the Servlet API, how to use it, and how to find more documentation about it.
For the purposes of this article, I'll assume that you're already familiar with the Java programming language and you have some experience with it. If not, there are a variety of Java tutorials available, including a good set of tutorials directly from Sun. Within that set is also a Servlet tutorial. It's how I learned to write Servlets.
Although not strictly required, before going through this introduction, I strongly encourage you to read my CGI Introduction. It introduces the original form of server-side programming from a language-agnostic point of view. While you may not be interested in writing CGI programs (you are, after all, reading a Servlet introduction), about 75% of the content of that article is relevant here. Whereas this article will discuss the details of constructing a Java Servlet, the CGI Introduction discusses the theory of server-side programming and all of the considerations that you should make when writing a server-side program.
If you are already familiar with another server-side technology, then you're ahead of the game. I'll take things from the beginning here, and you'll find that much of the knowledge you've acquired in ASP, CGI, etc. will be applicable to Servlets. Conversely, if you don't know any other server-side technology, then this is as good a place to start as any. What you learn from writing Java Servlets will also be greatly applied when you start learning alternate server-side technologies.
You may have already heard much of the buzz about Java Servlets, and one of the things you may have heard is that Java Server Pages (JSP) are very closely related. They are, in fact, very closely related to Servlets, but JSP will not be covered in this article. Perhaps in the future I'll discuss JSP and integration of JSP and Servlets in an intelligent web application architecture. In the meantime, you might want to check out the JSP chapter of the J2EE Tutorial.
First things first: What is a Java Servlet?
A Java Servlet is a Java class that is a subclass of javax.servlet.GenericServlet or (more commonly) javax.servlet.http.HttpServlet. Some people hold the mistaken notion that Java Servlets are only about providing web-based functionality. That is not true; using the GenericServlet class, a Java Servlet can provide services over any application protocol, not just HTTP (which is the protocol of the World Wide Web). It is true, though, that the vast majority of Servlets are in fact derivatives of HttpServlet and are intended to provide web-based services.
That's fine for a very formal definition of Java Servlets, but if you're new to server-side programming, it might not make much sense. Here's a less precise but potentially more useful description: Imagine you have a form on your website where users can order a product. Since you're reading this, chances are that you've already discovered that you need some sort of program to accept the input from that form and do something with it. The alternatives are not very useful; you could use a mailto: URL for the action of the form, but that's not really compliant with standards, and it just sends a bunch of garbled text to your inbox. You can also indicate a static HTML page as the action of the form if the method is GET, but again that doesn't do anything useful…the input just disappears into the ether. A Java Servlet is one type of server-side program that can accept the input of a form, do something useful with that input, and provide a customized, relevant response to the user who submitted the input.
How to "call" a Servlet
As I described, one way to "call" (that is, submit a request to) a Servlet is to set it as the action of an HTML form. Here's an example:
- <form action="http://yourserver/YourServlet" method="POST">
- <input type="text" size="10" name="username">
- <input type="submit" value="What's My Name?">
- </form>
In this example, http://yourserver/YourServlet is the URL for a Java Servlet. The method is POST, but it could just as easily have been GET. There's one input field in the form, a text input called "username". There's also a submit button. When the user clicks the submit button, the value the user typed into the "username" field will be sent to the Servlet. Presumably the Servlet will do something useful with the input and return a response.
Another way in which you can "call" a Servlet is directly via a URL. In the above example, there was a Java Servlet at http://yourserver/YourServlet. We can add input directly to that URL like this:
- http://yourserver/YourServlet?username=Jay
If a user entered this URL in a browser or clicked on a link to this URL, the Servlet would receive the input "username" with a value of "Jay". This is an example of a GET request. You'll often see this type of URL referred to as the "Querystring".
In that example, the string "username=Jay" is referred to as a "name/value pair". The string "username" is the "name", and "Jay" is the "value". Often you'll need to pass more than one item of input to the Servlet, so you'll need multiple name/value pairs. To include multiple pairs on the querystring, you separate the pairs by an ampersand (&):
- http://yourserver/YourServlet?username=Jay&zip=19460
You can add any number of name/value pairs to the querystring, but there is a limit to the amount of data that can be passed on the querystring. The rule of thumb is that for small amounts of data, you may use a querystring and a GET request, but for larger amounts of data, you should include a form on your page and use the POST request method.
More information on name/value pairs, the querystring, and HTTP request methods, see my CGI Introduction.
The reason you're here: How to Write a Servlet
Thought I'd never get around to it, didn't you? If you know Java, then writing a Java Servlet is quite simple. First, though, you need to take the time now to download and install the J2EE SDK. Note that's the J2EE SDK, which is different from the J2SE SDK, more properly known as the Java 2 SDK and colloquially known simply as the JDK. The J2EE SDK will include everything you need for J2EE development, which includes everything you need to develop Java Servlets. It even includes a Servlet container. Among the most important pieces included with this download is Servlet.jar, the JAR file that contains the actual Servlet classes that you will be extending. When you're compiling your Java Servlets, make sure that Servlet.jar is in your CLASSPATH.
Once you have the J2EE SDK installed, take a few minutes (or hours) to read the documentation. I will not teach you how to deploy your Servlets, because the process is different for each Servlet container. In most cases, though, deploying a Servlet is as simple as moving a compiled class file into a specified directory.
Environmental issues aside now, here's your first look at the code for an HTTP Servlet:
- import javax.servlet.http.* ;
-
- public class YourServlet extends HttpServlet{}
Fascinating! You say. What does it do? Absolutely nothing. It is, however, the simplest HTTP Servlet you can write. It will compile. Try saving this code in a file called YourServlet.java and compiling it using javac. This is a good way to determine if your environment is setup correctly and you have all of the libraries you need.
Although it's a very simple example, let's go through the two lines of code so you know what's going on. Line 1 is an import statement, and it imports the javax.servlet.http classes. We need to import these classes because we're going to extend javax.servlet.http.HttpServlet. Line 3 is the entire class: because it's the top-level class in the source file, you know it has to be public. Also, in order for it to be a Servlet, it has to extend either GenericServlet or HttpServlet, and we're going to focus on HttpServlet.
Producing Output: Hello, World!
Hello, World! being the perennial introductory program, we'll expand YourServlet to write the text "Hello, World!" to the browser:
- import javax.servlet.http.* ;
- import java.io.* ;
-
- public class YourServlet extends HttpServlet{
-
- public void doGet( HttpServletRequest request, HttpServletResponse response ) throws IOException{
- PrintWriter out = response.getWriter() ;
- out.write( "<html><head><title></title></head><body>Hello, World!</body></html>" ) ;
- }
-
- public void doPost( HttpServletRequest request, HttpServletResponse response ) throws IOException{
- doGet( request, response ) ;
- }
- }
As you can see, we're getting more involved now. In addition to the javax.servlet.http classes, we're now importing java.io classes. That's because to write output to the client browser, we're going to use a PrintWriter (a class with which you're hopefully familiar). We'll talk about that in a second. You'll notice that our class now has two methods, doGet and doPost. These are two methods that we inherit from HttpServlet but are overriding. If the Servlet is called via a GET request, the method doGet will be executed. If the Servlet is called via a POST request, the method doPost will be executed. Often we don't care what method was used, because the Servlet only has one purpose and will not perform processing conditional upon the request method. To prevent repetiation in the two methods, doPost simply calls doGet.
So, doGet is really the interesting part of our growing Servlet now. Like its brother doPost, doGet is public, returns nothing, and takes two parameters. The first is an instance of javax.servlet.http.HttpServletRequest; this represents the request that called the Servlet. The second is an instance of javax.servlet.http.HttpServletResponse; this represents the response that we will send to the client. You should also notice that the doGet and doPost methods throw an IOException. This is because we are using java.io methods in doGet that could throw such an exception. Rather than handle the exception, we're just going to throw it and let the calling code handle it. In real life, you'll probably want to use a try/catch block to handle the potential error.
To write HTML to the client's browser, the doGet method first obtains an instance of a PrintWriter. To do this, it simply calls the getWriter method of the response object. Then, to write HTML, we just call the write method of the out object. If you're familiar with CGI, you'll note that we didn't set a Content-Type. That's because Java Servlets will default to Content-Type: text/html if you use a PrintWriter for the output. If you wanted to use a different Content-Type, there's a method of the response object called setContentType. For example, we could have put the following code in doGet:
- response.setContentType( "text/plain" ) ;
- PrintWriter out = response.getWriter() ;
- out.write( "Hello, World!" ) ;
If you want to use a Content-Type other than text/html, you MUST call setContentType BEFORE you call getWriter. If you don't want to use a PrintWriter for the output, you could have gotten a javax.servlet.ServletOutputStream (a subclass of OutputStream) using response.getOutputStream and done with it as you wish. The standard Java IO facilities still apply here. Again, if you wanted to specify a particular Content-Type, you must call setContentType BEFORE you call getOutputStream.
While we're talking about getOutputStream, I should mention that just as in CGI (or ASP or PHP), there is absolutely no requirements that you return text/html or even text/plain to the client. If you want to return an image, you could return a Content-Type: image/jpeg and send the stream of bytes that makes up the image via the ServletOutputStream.
Getting and using input: Hello, <your name here>!
While the Hello, World! example is good because it provides a simple example that elucidates the inner workings of a Servlet, it doesn't do anything really interesting. Namely, it does not do any processing on any input that may have been passed to the Servlet. Therefore, we will expand YourServlet once again. In this example, the Servlet will take as input the name of the user who made the request and say, "Hello, <user>!" (of course, we'll replace <user> with the user's name).
All input that is passed to a Servlet will be made available through various methods in the request object. Now would be a good time to jump over to the Java API documentation and peruse the methods for javax.servlet.ServletRequest and javax.servlet.http.HttpServletRequest. There are several different types of input, and we'll look at a few of them.
Form input and querystring input:
What you want to know about most is how to get input from an HTML form or from a querystring. This is fairly easy…use HttpServletRequest.getParameter():
- public void doGet( HttpServletRequest request, HttpServletResponse response ){
- String username = request.getParameter( "username" ) ;
- // …
- }
Assuming that the Servlet is being request via a querystring like http://yourserver/YourServlet?username=Jay, then line 2 above would assign to the String username the value "Jay". Likewise, if the Servlet was called as the action for a form that had an input field named "username", line 2 would assign to the String username the value entered in the form field.
Sometimes, however, there are multiple values for a single input name - take the case of checkboxes. If you have a group of six checkboxes, each named "preferences", then you could have up to six values for the input name "preferences". You could call getParameter as in the above example, but you'd only get one of the six values. To get all of the values, you need to call HttpServletRequest.getParameterValues():
- public void doGet( HttpServletRequest request, HttpServletResponse response ){
- String [] preferences = request.getParameterValues( "preferences" ) ;
- // …
- }
The length of the preferences array would indicate the number of values that were received by the Servlet for that input name. To access the third of six values, you would simply reference preferences[2].
Getting meta-data about the request
Sometimes your processing will be conditional not only on the data submitted to the Servlet, but also on meta-data about the request itself. Examples of meta-data about the request include if/how the user is authenticated, how the request was made, the computer name/address from which the request was made, etc. Here's a brief description of some methods in the HttpServletRequest interface that provide this information:
- getAuthType() - returns the type of authentication used to protect the Servlet, if any. Example values returned include "BASIC" for HTTP Basic Authentication and "SSL" for Secure Sockets Layer.
- getMethod() - returns the HTTP method used to submit the request to the Servlet. It will almost always be one of "GET" or "POST". Note that HTTP method names are case sensitive.
- getRemoteUser() - returns the login ID of the user if and only if the user has been authenticated via BASIC or SSL or something similar. If the user has not been authenticated, then the method will return null.
- getLocale() - returns the preferred locale of the user as a java.util.Locale object. This is based on the "Accept-Language" header that the user sends with his or her request. You may be able to tell a bit about where the user is from with this method.
- getRemoteAddr() - returns the IP address of the client's computer.
- getRemoteHost() - tries to return the hostname of the client's computer, but may return the IP address if the hostname isn't available.
- getServerName() - returns the name or IP number of the server on which the Servlet resides. This can be useful if you develop Servlets on one machine but put them into production on another machine.
- isSecure() - returns true or false indicating whether or not the request was made on a secure channel like HTTPS.
Short-term Data Persistence: Sessions
One of the things that can be challenging for programmers of server-side web-based applications is that HTTP, the protocol of the web, is a stateless protocol. That means that no information is maintained from one client request to another. If you want to implement something like a wizard interface that must remember the previous steps that the user took, you're going to have to find a way to manage short-term persistent data. If you have a customized website and you want to remember a client's username and password so they don't have to log in every time they visit, you'll have to find a way to manage long-term persistent data. Luckily, one of the advantages of Java Servlets is their built-in mechanisms for doing this persistent data management.
Short-term persistent data is managed in a Java Servlet through a Session. Short-term basically means "during the user's current browser session." A session starts the first time a user submits a request to your Servlet, and it ends either when the user quits his or her browser or when the session times out (this time-out is configurable).
To use a Session, you first need to create a Session object, then you need to store some data in the session; finally, you retrieve data from the session. Here's some code to explain what we're talking about:
- public void doGet( HttpServletRequest request, HttpServletResponse response ) throws IOException{
- HttpSession session = request.getSession( true ) ;
- int i ;
- Integer oI = (Integer) session.getAttribute( "counter" ) ;
- if( oI == null ){
- i = 0 ;
- }
- else{
- i = oI.intValue() ;
- }
- i ++ ;
- Integer oI2 = new Integer( i ) ;
- session.setAttribute( "counter", oI2 ) ;
-
- PrintWriter out = response.getWriter() ;
- out.write( "You've visited " + i + " times." ) ;
- }
Notice that this code snippet only shows the doGet() method, not the entire class.
The first line to talk about is line 2, where we either get an existing session or create a new one. An important point is that a unique session will exist for each user who accesses your website, and the Servlet container will automatically make sure that the getSession() method will return the right session for the right user. Note that the type of the session object is HttpSession - if you used the import statement import javax.servlet.http.*;, then you will have imported this interface. Also note that we've passed a boolean value, true, to the getSession method. That means that if no session exists for this user, then create a new one. If we had passed false, then the Servlet container would not have created a new session, and the rest of our code might have failed at run time (because the session object would be null).
Line 4 is an example of retrieving data from a session object. Why did I use an Integer instead of just an int primitive on this line? Because the SESSION CAN ONLY CONTAIN OBJECTS. Let me say that another way: the getAttribute method of the session object returns a java.lang.Object. That means you CANNOT STORE PRIMITIVES IN A JAVA SERVLET SESSION. I can't tell you how long I beat my head against a wall trying to figure that one out. Also notice that because the method returns an Object type, we must cast the return value appropriately. The parameter passed to the getAttribute method, "counter", is just a string that serves as an identifier for the object we've stored in the session. You can use any String for an identifier. In proper Java parlance, "counter" is a name, and we're expecting that an object of type Integer is bound to that name.
In lines 5-10, we want to get the int value of the object we retrieved from the session. However, the oI object may be null. What if this is the first time that the user has submitted a request to this Servlet? A new session will be created and nothing will be in it. That would mean that line 4 would have returned null, so we must check for that. If it is not null, we're assigning the int value to an int variable we've declared.
In lines 11 and 12, we're incrementing i and creating a new Integer object with the incremented value.
Line 13 is an example of storing persistent data in a session. To the setAttribute method, we pass a name ("counter") by which we want to identify our data, and an object (oI2) that represents our data. Remember, the data that you attempt to store in a session must be an Object (or one of Object's subclasses, obviously).
In line 16 we attempt to do something semi-useful with the data we've stored and retrieved in the session. If you were to copy this whole doGet method into the YourServlet class you've been building and deployed it, when you accessed it, you'd find that each time you reloaded the page, the message would increment the number of times you've visited. "You've visited 1 times", "You've visited 2 times", "You've visited 3 times"… You can use this to test when a session expires by quitting your browser and loading the Servlet again, or by opening another browser application (say one instance of IE and one instance of Netscape). You can also try leaving your browser open for a while and try reloading the page after 5, 10, 15, or 20 minutes.
There are a few more methods in the HttpSession interface that can be helpful. First, you can determine how long it will take a session to time out by calling getMaxInactiveInterval(), which returns the maximum amount of time, in seconds, that a session can be inactive before it is deleted. There is a corresponding setMaxInactiveInterval() method you can use to explicitly set the timeout for each individual session. Most Servlet containers also provide some facility for setting the default time out period.
Another useful method is isNew(). This will tell you whether a session object you got from a getSession( true ) call is new or if it previously existed…That way you can do some initial setup on a new session and you don't have to repeat it again.
Finally, if you want to remove some data from a session, there is a removeAttribute() method. Just pass as a parameter the name to which the object is bound, and it will be deleted from the session. If you want to totally abandon the session (effectively logging out the user), you can call the invalidate() method.
Long-term Data Persistence: Cookies
There has been much ado about nothing regarding HTTP Cookies as of late. The simple fact is that they are nothing more than a mechanism for maintaining, with the user's permission, data between browser sessions. The data is stored on the client's computer and can be deleted by the user at his or her whim. The data also, presuming the client's user agent (browser) adheres to normal security precautions, cannot be transmitted to anyone except the server that left the cookie.
Java Servlets provide simple API methods for dealing with cookies; the HttpServletRequest and HttpServletResponse interfaces have methods for getting and setting cookies, and there is an entire class, javax.servlet.http.Cookie, for representing cookies and working with them. Let's look at some sample source that gets, edits, and re-sets a Cookie:
- public void doGet( HttpServletRequest request, HttpServletResponse response ) throws IOException{
- int x, i ;
- String s ;
- Cookie [] cookie = request.getCookies() ;
- Cookie counterCookie = null ;
- for( i = 0; i < cookie.length; i++ ){
- if( cookie[i].getName().equals("counter") ){
- counterCookie = cookie[i] ;
- }
- }
- if( counterCookie != null ){
- s = counterCookie.getValue() ;
- if( s != null ){
- x = Integer.parseInt( s ) ;
- }
- else{
- x = 0 ;
- }
- }
- else{
- try{
- counterCookie = new Cookie( "counter", null ) ;
- }catch( IllegalArgumentException e ){}
- x = 0 ;
- }
- x++ ;
- Integer oI = new Integer( x ) ;
- counterCookie.setValue( oI.toString() ) ;
- response.addCookie( counterCookie ) ;
- PrintWriter out = response.getWriter() ;
- out.write( "You've visited " + x + " times" ) ;
- }
This example looks more complicated than others we've seen so far, but that's largely due to the face that I'm doing a lot of checking to make sure things aren't null before I work with them.
Line 4 is the first line of any interest, and it shows you how to get the cookies that the client sent with his or her request. Unlike a session, you can't get a single cookie by name - you must get all the cookies at once using the request.getCookies() method. That method returns an array of objects of type Cookie.
To find the specific cookie we're looking for, on lines 6-10 we just loop through the array of cookies and check out the name associated with each. We're looking for a cookie named "counter". When we find it, we just assign it to another Cookie object, counterCookie (this will make it more convenient to work with; we could have just dealt with the cookie in the array if we wanted to).
On line 11, we check to see if counterCookie is null; it may be that there was no cookie sent with the name "counter". Even if you're very confident that the client will send a cookie with a specific name, you should still do this check for null. Clients can delete cookies if they wish, and they will not have any cookies to send you the first time they visit your site.
Line 12 is another important line - this is where we retrieve the value of the cookie. The method is simple enough - getValue(). That's all there is to it. The method will return a String, unlike an HttpSession which, as you recall, returns an Object. This makes cookies a little less convenient to work with sometimes, but it's nothing you can't work around. Notice on line 13 we check if the value of the cookie is null. Even if we know we set a value, some user agents store cookies in a text file that the client can edit - that means the value of the cookie, but not the cookie itself, may get deleted.
On line 22 we have an example of creating a new cookie from scratch (pardon the pun). The constructor for the Cookie class takes two strings as parameters; the first is the name of the cookie and the second is its value. In this case, I passed "counter" as the name and null as the value (we'll set the value later). Note that by default, this new cookie will only last the length of the user's browser session. If I wanted the cookie to stay on the client's computer longer, I could have called the Cookie.setMaxAge() method, passing an int representing the number of seconds the cookie should persist. A big number will make the cookie last a long time; a negative number will make the cookie expire with the session, and zero will delete the cookie completely. Another thing to note is that I had to put the constructor in a try/catch block because the constructor could throw an IllegalArgumentException. This will occur if the name I try to give the cookie doesn't conform to the cookie RFC 2109. According to the javax.servlet.http.Cookie documentation, this means that the name, "can contain only ASCII alphanumeric characters and cannot contain commas, semicolons, or white space or begin with a $ character."
By the time we get to line 27, we've retrieved the value of the cookie - if it existed - and stored it in an int primitive variable named x. We increment x, and then update the value of the cookie. To do that, we call setValue() on line 28. All of the Integer mess is just me trying to convert an int to a String. Now to send the cookie with the new value back to the client, all we have to do is call response.addCookie() on line 29. If we had multiple cookies, we could call addCookie multiple times.
Line 31 is where we finally do something semi-useful with the value from the cookie. Just like in our session example, this Servlet will print the message, "You've visited 1 times", "You've visited 2 times", etc. each time you reload the page. Unlike the session example, if we had called setMaxAge() on the cookie with a large value, then you could close the browser and access the Servlet again without the counter resetting.
(Not) the End
While that concludes this Quick Introduction to Java Servlets, this is hardly the end of the road for learning about Servlets. There are many features of the Java Servlets API that I have not covered here, which you can easily learn about on your own by perusing the API specification (if you didn't install the documentation locally, you can get the docs online from java.sun.com). I personally suggest reading the documentation on HttpServletRequest, HttpServletResponse, HttpSession, and Cookie, all in the javax.servlet.http package. Your Servlet container may also have come with documentation that talks about some of the other features of Java Servlets and the API. Another great way to learn about Servlets is to dive into some real-world code.
There are also some advanced topics surrounding Java Servlets that you may become interested in as your development expertise expands. You might want to take a look at JSP - Java Server Pages. You might want to take a look at the concept of Servlet chaining. If you become involved in any serious J2EE development, you'll want to learn about the Model-View-Controller paradigm and MVC frameworks such as Struts. There are also some core Java features that can be very, very useful in Servlets, not the least of which is JDBC - Java Database Connectivity.
Good luck, and I hope you find Java Servlet development as intriguing and useful as I do.
If you have any questions or would like to contact me for any reason, please email me at j.eckles@computer.org.
|