Translate

Friday, 23 December 2016

Chapter 30 Sample Applets


This chapter is the first of six chapters that cover applet programming. It introduces you to Java applets by way of several examples that illustrate different aspects of the classes and methods used to implement simple applets. Its purpose is to quickly get you up to speed in developing applets. Subsequent chapters fill in the details of the classes and methods that are introduced here. You should approach this chapter by trying to get a good general understanding of what is going on in the sample applets without dwelling on the details. You will learn the details when you study them in later chapters. This chapter assumes that you have covered the window-programming examples in Part IV, "Window Programming."

Hello Web!

The first program that you wrote in Chapter 4, "First Programs: Hello World! to BlackJack," was the HelloWorldApp program. It displayed the traditional Hello World! text to the console window. The main purpose of the program was to show you how to develop a simple Java program that actually produced some noticeable effect. The same rationale applies to the HelloWeb applet that you'll develop shortly. The program shows you how to write a simple applet that displays the text Hello Web! to the browser window. The code for the HelloWeb applet is shown in Listing 30.1.

Listing 30.1. The source code of the HelloWeb applet.
import java.applet.*;
import java.awt.*;

public class HelloWeb extends Applet {
  public void paint(Graphics g) {
  g.setFont(new Font("TimesRoman",Font.BOLD+Font.ITALIC,36));
  g.setColor(Color.blue);
  g.drawString("Hello Web!",10,80);
 }
}

Create the HelloWeb.java file in the \java\jdg\ch30 directory and compile it using javac HelloWeb.java.

In order to run the HelloWeb applet, you need to include it in a Web page. Using a text editor, create the HTML file shown in Listing 30.2 and save it as \java\jdg\ch30\helloweb.htm.

Listing 30.2. The helloweb.htm file.
<HTML>
<HEAD>
<TITLE>Hello Web!</TITLE>
</HEAD>
<BODY>
<APPLET CODE="HelloWeb.class" WIDTH=200 HEIGHT=200>
[HelloWeb applet]
</APPLET>
</BODY>
</HTML>

Now open the helloweb.htm file with your Java-enabled browser. I use Netscape 2.0; my browser display appears as shown in Figure 30.1.

Figure 30.1 : The hell.htm file, as displayed by a browser.

Although this example is rather trivial-you could have created the same results just using HTML-it provides a basic starting point from which to embark on your study of applets. Let's cover the HelloWeb.java applet first, and then examine the helloweb.htm file.

The HelloWeb applet, like all Java applets, extends the Applet class or one of its subclasses. HelloWeb consists of a single method-the paint() method, which you encountered several times in Part IV. Because the Applet class is a subclass of Panel, many of the window-programming techniques that you studied in Part IV come into play. The paint() method is passed a Graphics object that it uses to paint the screen. In this case, I set the current font to a bold, italic, 36-point TimesRoman font and set the drawing color to blue. I then use the drawString() method of the Graphics class to draw the text Hello Web! on the screen at position 10,80.

The helloweb.htm file is the Web page that is used to contain the HelloWeb applet. Web pages are written in the Hypertext Markup Language (HTML). Learning HTML, in itself, is the subject of many books. Here I'll use a common HTML document to hold the applets and not go off on a tangent about learning HTML.

The helloweb.htm file begins and ends with the <HTML> and </HTML> tags. These tags signify the start and finish of an HTML file. The HTML file consists of a head and a body. The head is contained between the <HEAD> and </HEAD> tags. Within the head is the title, which is displayed at the top of the browser window. The title is contained within the <TITLE> and </TITLE> tags.

The body of an HTML file is where all the Web page descriptions are located. The body is enclosed within the <BODY> and </BODY> tags. The body of this document, and of those used in the other examples of this chapter, consists of a single applet. The applet is denoted by the <APPLET> and </APPLET> tags.

Within the opening <APPLET> tag are several attributes. The CODE attribute has the value HelloWeb.class, which identifies the class file for this applet. Its WIDTH and HEIGHT attributes specify the dimensions of the applet, as displayed on the browser window.

The text [HelloWeb applet], appearing between the <APPLET> and </APPLET> tags, is displayed by browsers that are not capable of loading Java applets.

An Audio Player

The Applet class, as small as it is, provides a few very useful features for developing attractive Web pages. Among these are the capability to load and play audio files and to easily load and display images. The AudioPlayer applet, developed in this section, shows off these features. The source code for the AudioPlayer applet is shown in Listing 30.3.
Note
You need a sound board and speakers to run this applet.
Listing 30.3. The source code of the AudioPlayer applet.
import java.applet.*;
import java.awt.*;
import java.net.*;

public class AudioPlayer extends Applet {
 AudioClip music;
 Image background;
 public void init() {
  try {
   music = getAudioClip(new URL("file:///spacemusic.au"));
   background = getImage(new URL("file:///space.gif"));
  }catch (MalformedURLException ex) {
  }
  setLayout(new BorderLayout());
  Panel buttons = new Panel();
  buttons.add(new Button("Play"));
  buttons.add(new Button("Stop"));
  buttons.add(new Button("Loop"));
  add("South",buttons);
 }
 public void stop() {
   music.stop();
 }
 public void paint(Graphics g) {
  g.drawImage(background,0,0,this);
 }
 public boolean handleEvent(Event event) {
  if(event.target instanceof Button) {
   if("Play".equals(event.arg)) {
    music.play();
    return true;
   }else if("Stop".equals(event.arg)) {
    music.stop();
    return true;
   }else if("Loop".equals(event.arg)) {
    music.loop();
    return true;
   }
  }
  return false;
 }
}

Create and compile AudioPlayer.java in the same manner as you did HelloWeb.java. The HTML file that is used to display the applet is shown in Listing 30.4. Note that the CODE, WIDTH, and HEIGHT attributes of the applet have been changed. You will need two additional files to run the applet. Copy the spacemusic.au file from the \java\demo\Animator\audio directory to your \java\jdg\ch30 directory. This is an audio file that is supplied with the Java Developer's Kit. It contains music that is described as "space music." Also copy the space.gif file from the \jdg\ch30 directory of the CD-ROM to your \java\jdg\ch30 directory.

Listing 30.4. The audio.htm file.
<HTML>
<HEAD>
<TITLE>Audio Player</TITLE>
</HEAD>
<BODY>
<APPLET CODE="AudioPlayer.class" WIDTH=300 HEIGHT=350>
[AudioPlayer applet]
</APPLET>
</BODY>
</HTML>

Open the audio.htm file with your Java-enabled browser. Your screen display should look similar to the one shown in Figure 30.2.

Figure 30.2 : The audio.htm file, as displayed by a Web browser.

Your browser loads the audio.htm file and then the AudioPlayer.class file. The applet itself loads the background image and an audio file. To play the audio file, click on the Play button. The space music will be played, using your sound board and speakers. When the end of the music file is reached, the sound ends. If you click on the Loop button, the music will be continuously played in a never-ending fashion. Clicking on the Stop button causes the music to cease.

The AudioPlayer class, for all its additional capabilities, is only slightly longer than the HelloWeb class. It declares two field variables, music and background, which are used to hold the audio file and background image. The music variable is declared as type AudioClip, which is an interface defined in the Java.applet package.

The AudioPlayer class contains four access methods: init(), stop(), paint(), and handleEvent(). You are already familiar with the purpose of paint() and handleEvent() from Part IV.

The init() method is invoked by the browser's runtime system when an applet is initially loaded. It performs any initialization required before the main part of the applet is executed. The stop() method is invoked when the execution of an applet is terminated as the result of an applet's Web page no longer being displayed by the browser. You never need to invoke init() or stop() directly. They are invoked by the runtime system.

The init() method of AudioPlayer begins by loading the audio and image files. The getAudioClip() method of the Applet class loads an audio file that is referenced by an URL. The file:///spacemusic.au URL is used to load the spacemusic.au file from the directory in which the audio.htm and AudioPlayer.class files are located. The file:///space.gif URL is used in a similar manner.

After the audio and image files are loaded, the layout of the applet is set to a BorderLayout object. A Panel object is created and assigned to the buttons variable. The Play, Stop, and Loop buttons are created and added to the buttons panel. The buttons panel is then added to the bottom of the applet display area.

The stop() method uses the stop() method of the AudioClip interface to stop the music when the applet is no longer being displayed by the browser.

The paint() method draws the space.gif image assigned to the background variable on the Graphics context of the applet's display area.

The handleEvent() method handles the three prominent events associated with the applet. These events are the clicking of the Play, Stop, and Loop buttons. When the Play button is clicked, the play() method of the AudioClip interface is invoked to play the audio clip. When the Stop button is clicked, the stop() method of the AudioClip interface is invoked to stop the music. Finally, when the Loop button is clicked, the loop() method of the AudioClip interface is invoked to cause the music to be played in a never-ending, looping fashion.

BlackJack Revisited

From the previous example, you have probably surmised that it's very easy to convert Java window programs to applets. This is possible because of the fact that an applet is nothing more than a souped-up panel. It is also relatively easy to convert console programs to applets.

One of the first Java programs that you wrote, way back in Chapter 4, was the BlackJackApp program. This program allowed you to play blackjack on the console display. Because you used a class- and object-oriented approach to developing BlackJackApp (even before you were formally introduced to classes and objects), this program is relatively easy to convert to a simple applet. The source code of the converted applet is shown in Listing 30.5.

Listing 30.5. The source code of the Blackjack.java applet.
import java.applet.*;
import java.awt.*;
import java.util.Random;

public class Blackjack extends Applet {
 static final int BET = 0;
 static final int PLAY = 1;
 static final int DEALER = 2;
 int state = BET;
 int money = 1000;
 int bet = 0;
 Deck deck = new Deck();
 Hand playersHand;
 Hand dealersHand;
 Label topLine = new Label("Welcome to Blackjack!",Label.CENTER);
 Label totalLine = new Label("You have $1000.",Label.CENTER);
 Label dealersLabel = new Label("Dealer's Hand",Label.CENTER);
 Label playersLabel = new Label("Your Hand",Label.CENTER);
 TextArea dealerText = new TextArea(9,20);
 TextArea playerText = new TextArea(9,20);
 Button hitButton = new Button("Hit");
 Button stayButton = new Button("Stay");
 Label betLabel = new Label("Enter your bet: ",Label.RIGHT);
 TextField betField = new TextField();
 GridBagLayout gridbag = new GridBagLayout();;
 GridBagConstraints constraints = new GridBagConstraints();
 public void init() {
  setLayout(gridbag);
  constraints.fill = GridBagConstraints.BOTH;
  addComponent(topLine,0,0);
  addComponent(totalLine,0,1);
  addComponent(dealersLabel,1,0);
  addComponent(playersLabel,1,1);
  dealerText.setEditable(false);
  playerText.setEditable(false);
  addComponent(dealerText,2,0);
  addComponent(playerText,2,1);
  addComponent(hitButton,3,0);
  addComponent(stayButton,3,1);
  addComponent(betLabel,4,0);
  addComponent(betField,4,1);
 }
 void addComponent(Component c,int y,int x) {
  constraints.gridx = x;
  constraints.gridy = y;
  gridbag.setConstraints(c, constraints);
  add(c);
 }
 public boolean handleEvent(Event event) {
  if(event.target instanceof TextField && event.id == Event.ACTION_EVENT) {
   if(state == BET){
    updateBet();
    return true;
   }
  }else if(event.target instanceof Button && event.id == Event.ACTION_EVENT) {
   if(state == PLAY) {
    if("Hit".equals(event.arg)) {
     playersHand.addCard(deck.deal());
     playersHand.show(playerText,false);
     if(!playersHand.under(22)) state = DEALER;
    }else if("Stay".equals(event.arg)) state = DEALER;
    if(state == DEALER) {
     while(dealersHand.mustHit())
      dealersHand.addCard(deck.deal());
     dealersHand.show(dealerText,false);
     showResults();
    }
   }
  }
  return false;
 }
 public void updateBet() {
  betField.setEditable(false);
  betLabel.setText("Bet: ");
  try {
   Integer i = new Integer(betField.getText());
   bet = i.intValue();
  } catch (NumberFormatException ex) {
   bet = 1;
  }
  betField.setText(String.valueOf(bet));
  initialDeal();
  if(playersHand.blackjack()) playerWins();
  else state = PLAY;
 }
 void initialDeal() {
  playersHand = new Hand();
  dealersHand = new Hand();
  for(int i = 0;i<2;++i) {
   playersHand.addCard(deck.deal());
   dealersHand.addCard(deck.deal());
  }
  dealersHand.show(dealerText,true);
  playersHand.show(playerText,false);
 }
 void openBetting() {
  betLabel.setText("Enter your bet: ");
  betField.setText("");
  betField.setEditable(true);
  state = BET;
 }
 void playerWins() {
  money += bet;
  topLine.setText("Player wins $"+bet+".");
  totalLine.setText("You have $"+money+".");
  openBetting();
 }
 void dealerWins() {
  money -= bet;
  topLine.setText("Player loses $"+bet+".");
  totalLine.setText("You have $"+money+".");
  openBetting();
 }
 void tie() {
  topLine.setText("Tie.");
  totalLine.setText("You have $"+money+".");
  openBetting();
 }
 void showResults() {
  if(playersHand.busted() && dealersHand.busted()) tie();
  else if(playersHand.busted()) dealerWins();
  else if(dealersHand.busted()) playerWins();
  else if(playersHand.bestScore() > dealersHand.bestScore()) playerWins();
  else if(playersHand.bestScore() < dealersHand.bestScore()) dealerWins();
  else tie();
 }
}

class Deck {
 // Variable declarations
 int cards[]; // Array of 52 cards
 int topCard; // 0-51 (index of card in deck)
 Random random;

 // Method declarations
 public Deck() { // Constructor
  cards = new int[52];
  for(int i = 0;i<52;++i) cards[i] = i;
  topCard = 0;
  random = new Random();
  shuffle();
 }

 public void shuffle() {
  // Repeat 52 times
  for(int i = 0;i<52;++i) {
   // Randomly exchange two cards in the deck.
   int j = randomCard();
   int k = randomCard();
   int temp = cards[j];
   cards[j] = cards[k];
   cards[k] = temp;
  }
 }

 int randomCard() {
  int r = random.nextInt();
  if(r<0) r = 0-r;
  return r%52;
 }

 Card deal() {
  if(topCard>51) {
   shuffle();
   topCard = 0;
  }
  Card card = new Card(cards[topCard]);
  ++topCard;
  return card;
 }
} // End of Deck class

class Hand {
 // Variable declarations
 int numCards;
 Card cards[];
 static int MaxCards = 12;

 //Method declarations
 public Hand() { // Constructor
  numCards = 0;
  cards = new Card[MaxCards];
 }

 void addCard(Card c) {
  cards[numCards] = c;
  ++numCards;
 }

 void show(TextArea t,boolean hideFirstCard) {
  String results = "";
  for(int i = 0;i<numCards;++i) {
   if(i == 0 && hideFirstCard) results += "Hidden\n";
   else results += cards[i].value+" of "+cards[i].suite+"\n";
  }
  t.setText(results);
 }

boolean blackjack() {
 if(numCards == 2) {
  if(cards[0].iValue == 1 && cards[1].iValue == 10) return true;
  if(cards[1].iValue == 1 && cards[0].iValue == 10) return true;
 }
 return false;
}

boolean under(int n) {
 int points = 0;
 for(int i = 0;i<numCards;++i) points += cards[i].iValue;
 if(points<n) return true;
 else return false;
}

int bestScore() {
 int points = 0;
 boolean haveAce = false;
 for(int i = 0;i<numCards;++i) {
  points += cards[i].iValue;
  if(cards[i].iValue == 1) haveAce = true;
 }
 if(haveAce) {
  if(points+10 < 22) points += 10;
 }
 return points;
}

boolean mustHit() {
 if(bestScore()<17) return true;
 else return false;
}

 boolean busted() {
  if(!under(22)) return true;
  else return false;
 }
} // End of Hand class

class Card {
 // Variable declarations
 int iValue; // Numeric value corresponding to card.
 String value; // "A" "2" through "9" "T" "J" "Q" "K"
 String suite; // "S" "H" "C" "D"

 // Method declarations
 public Card(int n) { // Constructor
  int iSuite = n/13;
  iValue = n%13+1;
  switch(iSuite) {
   case 0:
    suite = "Spades";
    break;
   case 1:
    suite = "Hearts";
    break;
   case 2:
    suite = "Clubs";
    break;
   default:
    suite = "Diamonds";
  }
  if(iValue == 1) value = "Ace";
  else if(iValue == 10) value = "Ten";
  else if(iValue == 11) value = "Jack";
  else if(iValue == 12) value = "Queen";
  else if(iValue == 13) value = "King";
  else value = Integer.toString(iValue);
  if(iValue>10) iValue = 10;
 }

 int getValue() {
  return iValue;
 }
} // End of Card class

Create and compile Blackjack.java in the same manner as the previous examples. The HTML file that is used to display the applet is shown in Listing 30.6. Note that the CODE, WIDTH, and HEIGHT attributes of the applet have been changed.

Listing 30.6. The blackjack.htm file.
<HTML>
<HEAD>
<TITLE>Blackjack</TITLE>
</HEAD>
<BODY>
<APPLET CODE="Blackjack.class" WIDTH=400 HEIGHT=400>
[Blackjack applet]
</APPLET>
</BODY>
</HTML>

Open the blackjack.htm file with your browser. Your browser should display the applet, as shown in Figure 30.3.

Figure 30.3 : The Black initial display.

The top text line in the applet welcomes you to the game and tells you that you have $1000 to gamble. The Dealer's Hand and Your Hand text boxes are initially blank. Below them are the Hit and Stay buttons, which are initially disabled. To start the game, enter a bet, as shown in Figure 30.4.

Figure 30.4 : Entering a bet.

When you enter a bet, the label to the right of the text field changes from Enter your bet: to Bet:. The text field is grayed and can no longer be edited. This prevents you from changing your bet during the play of a hand.

The initial hands for the dealer and the player are displayed after the bet is entered. The first card of the dealer is hidden. You can now use the Hit and Stay buttons to obtain more cards or to stay with what you were dealt and let the dealer draw. Click on the Hit button until you are finished drawing cards. If you bust (go over 21), your hand will be finished and the dealer will draw his cards.

After you click Stay or bust, the dealer draws his hand and a win, loss, or tie results. Your available money is then updated based on your winnings. The Blackjack applet is more forgiving than the BlackJackApp program in Chapter 4: You can continue playing after you've run out of money. You can even bet $0 or negative values.

Go ahead and play the game for a while to become familiar with its operation.

Although the Blackjack applet may appear to be quite large, it reuses all of the classes developed for the original BlackJackApp program, with minor modifications.

The Blackjack program declares 21 field variables. Most of these are used to create its GUI interface. The BET, PLAY, and DEALER constants are used to maintain the state of the user's interaction with the game. The state variable is set to the initial BET state.

The money, bet, deck, playersHand, and dealersHand variables are declared and used in the same manner as in the BlackJackApp program.

Five labels are created and assigned to the topLine, totalLine, dealersLabel, playersLabel, and betLabel variables. The topLine and totalLine variables are displayed at the top of the applet. They display the welcome string and the amount of money that the player has available. The dealersLabel and playersLabel variables are used to label the text areas containing the dealer's and user's hands. The betLabel variable prompts the user to enter his or her bet.

The dealerText and playerText variables are used to refer to the TextArea objects that display the hands of the dealer and player. The betField text field is used to enter and display the user's bet.

The hitButton and stayButton variables are used to draw another card or to turn the draw over to the dealer.

The gridbag and constraints variables refer to the GridBagLayout and GridBagConstraints objects used to lay out the screen's display.

The init() method sets the layout to a GridBagLayout, invoking the addComponent() method to add the GUI components to the applet. The addComponent() method adds a component to an x,y location within the gridbag layout.

The handleEvent() method handles the user-interface events that drive the operation of the applet. It handles the event generated when the user enters text in the Bet: text field by invoking the updateBet() method. This method then sets up the rest of the applet's operation. The handleEvent() method handles the clicking of the Hit and Stay buttons by first making sure that the applet is in the Play state. This prevents the user from clicking on these buttons at arbitrary times during the applet's execution.

The Hit button is handled by adding a card to the player's hand, using the same approach as in BlackJackApp. The show() method of the Hand class is modified to display the player's hand to the TextArea object referenced by the playerText variable. If the user busts, the state is set to DEALER, preventing the user from drawing another card.

The Stay button is handled by setting the state variable to DEALER.

When the state is set to DEALER, the dealer is dealt additional cards using the same code as in BlackJackApp. The showResults() method is modified to update the applet's display labels and not the console window.

The updateBet() method is a new method that is added to support the applet conversion. It disables editing of the text field and changes the Label object referenced by the betLabel variable from Enter your bet: to Bet:. It then checks the bet for a valid value and redisplays it in the disabled text field. The initialDeal() method is invoked to deal an initial hand to the player and dealer. If the user is dealt blackjack, the hand is ended and the user is given his winnings. Otherwise, the state variable is set to play to enable the operation of the Hit and Stay buttons.

The initialDeal() method remains unchanged from the original BlackJackApp program.

The openBetting() method is used to change to the initial BET state. It sets the Enter your bet: prompt and enables the text field to be edited.

The playerWins(), dealerWins(), and tie() methods have been modified to display their results within the applet instead of on the console window. They also invoke the openBetting() method to return to the BET state.

The showResults() method remains unchanged from the original BlackJackApp program.

No changes were made to the Deck or Card classes. The show() method of the Hand class was modified to display the dealer's and player's hands to the TextArea objects.

The changes required to convert from the original BlackJackApp program to the Blackjack applet were minimal. This illustrates the power of object-oriented programming and the reusability of the components that are created using this approach.

Summary

This chapter introduces you to applet programming by way of several sample programs that illustrate the classes and methods used to implement simple applets.
Chapter 31, "Developing Applets," lays the framework for developing applets by expanding on the basics introduced here.

Thursday, 22 December 2016

Chapter 29 Protocol Handlers


In this chapter you'll learn how to write protocol handlers to support the use of custom protocols by Web browsers. You'll also learn how Web browsers implement protocols other than HTTP and how they are integrated with the browser's operation. You'll develop a simple protocol handler and integrate it with a Web client program. This chapter builds on the material presented in Chapter 17, "Network Programming with the java.net Package."

Using Protocol Handlers

Most popular Web browsers support protocols other than HTTP. These other protocols include FTP, gopher, e-mail, and application-specific protocols. Support for these protocols is usually built into the browser, causing the browser to become larger and slower to load.

Java supports additional protocols through the use of protocol handlers, also referred to as stream handlers. These protocol handlers are used to retrieve Web objects using application-specific protocols. The protocols are specified in the URL referencing the object.

Protocol handlers are implemented as subclasses of the URLStreamHandler class. The URLStreamHandler class defines four access methods that can be overridden by its subclasses, but only the openConnection() method is required to be overridden.

The openConnection() method takes an URL with its assigned protocol as a parameter and returns an object of class URLConnection. The URLConnection object can then be used to create input and output streams and to access the resource addressed by the URL.

The parseURL() and setURL() methods are used to implement custom URL syntax parsing. The toExternalForm() method is used to convert an URL of the protocol type to a String object.

The purpose of a protocol handler is to implement a custom protocol needed to access Web objects identified by URLs that require the custom protocol. Protocol handlers, like content handlers, are not directly instantiated or accessed. The methods of the URLConnection object that is returned by a protocol handler are invoked to access the resource referenced by the protocol.

A protocol is identified beginning with the first character of the URL and continuing to the first colon (:) contained in the URL. For example, the protocol of the URL http://www.jaworski.com is http and the protocol of the URL fortune://jaworski.com is fortune.

A protocol handler is associated with a specific protocol through the use of the URLStreamHandlerFactory interface. A class that implements the URLStreamHandlerFactory interface must implement the createURLStreamHandler() method. This method returns an URLStreamHandler object to be used for a specific protocol. An URLStreamHandlerFactory object is installed using the static setURLStreamHandlerFactory() method of the URL class.

Developing a Protocol Handler

The first step to implement a protocol handler is to define the protocol handler as a subclass of the URLStreamHandler class. The openConnection() method of the protocol handler creates an URLConnection object that can be used to access an URL designating the specified protocol.

A protocol handler is associated with a specific protocol type through the use of an URLStreamHandlerFactory object. The createURLStreamHandler() method of the URLStreamHandlerFactory interface is used to return a protocol handler for a specific protocol type.

The setURLStreamHandlerFactory() method of the URL class is used to set an URLStreamHandlerFactory as the default URLStreamHandlerFactory to be used with all protocol types.

A Simple Protocol Handler

This section presents an example of implementing a simple protocol handler. My ncSA server comes with a CGI program, named fortune, that returns a fortune cookie-type message when the program's URL is accessed. This section defines the fortune protocol to access the fortune program on my Web server and on other ncSA Web servers. The fortune protocol is not a real Internet protocol; I contrived it to illustrate the use of protocol handlers. The URL for the fortune protocol consists of fortune:// followed by the host name. For example, fortune://jaworski.com accesses the fortune protocol on my Web server.

The definition of the URLFortuneHandler class is shown in Listing 29.1.

Listing 29.1. The source code for the URLFortuneHandler class.
import java.net.*;
import java.io.*;

public class URLFortuneHandler extends URLStreamHandler {
 public URLConnection openConnection(URL url) throws IOException {
  String host=url.getHost();
  URL newURL = new URL("http://"+host+"/cgi-bin/fortune");
  return newURL.openConnection();
 }
}

The URLFortuneHandler class extends the URLStreamHandler class and provides a single method. The openConnection() method takes an URL object as a parameter and returns an object of the URLConnection class. It also throws the IOException exception.

The openConnection() method uses the getHost() method of the URL class to extract the host name contained in the URL. It then uses a new http URL by concatenating http:// with the host name and then the location of the fortune CGI program, /cgi-bin/fortune. The openConection() method of the URL class is used to return the URLConnection object associated with the new URL.

The URLFortuneHandler class wraps the fortune CGI program using the fortune protocol. This protocol is implemented through an HTTP connection to the CGI program.

The GetFortuneApp Program

The GetFortuneApp program illustrates the use of protocol handlers. It accesses the fortune CGI program on my Web server using the fortune protocol. The source code of the GetFortuneApp program is shown in Listing 29.2.

Listing 29.2. The source code for the GetFortuneApp program.
import java.net.*;
import java.io.*;

public class GetFortuneApp {
 public static void main(String args[]){
  try{
   FortuneFactory fortuneFactory = new FortuneFactory();
   URL.setURLStreamHandlerFactory(fortuneFactory);
   if(args.length!=1) error("Usage: java GetFortuneApp FortuneURL");
   System.out.println("Fetching URL: "+args[0]);
   URL url = new URL(args[0]);
   System.out.println((String) url.getContent());
  }catch (MalformedURLException ex){
   error("Bad URL");
  }catch (IOException ex){
   error("IOException occurred.");
  }
 }
 public static void error(String s){
  System.out.println(s);
  System.exit(1);
 }
}
class FortuneFactory implements URLStreamHandlerFactory {
 public FortuneFactory() {
 }
 public URLStreamHandler createURLStreamHandler(String protocol) {
  if(protocol.equals("fortune")){
   System.out.println("Requested protocol: "+protocol);
   return new URLFortuneHandler();
  }
  return null;
 }
}

When you invoke the GetFortuneApp program, provide it with the fortune://jaworski.com URL as a parameter. The GetFortuneApp program's output is as follows (you will get a different fortune each time you execute the program):

C:\java\jdg\ch29>java GetFortuneApp fortune://jaworski.com
Fetching URL: fortune://jaworski.com
Requested protocol: fortune
JACK AND THE BEANSTACK
by Mark Isaak
Long ago, in a finite state far away, there lived a JOVIAL
character named Jack. Jack and his relations were poor. Often their hash table was bare. One day Jack's parent said to him, "Our matrices are sparse. You must go to the market to exchange our RAM for some BASICs." She compiled a linked list of items to retrieve and passed it to him.

So Jack set out. But as he was walking along a Hamilton path, he met the traveling salesman.

"Whither dost thy flow chart take thou?" prompted the salesman in high-level language.

"I'm going to the market to exchange this RAM for some chips and Apples," commented Jack.

"I have a much better algorithm. You needn't join a queue there; I will swap your RAM for these magic kernels now."

Jack made the trade, then backtracked to his house. But when he told his busy-waiting parent of the deal, she became so angry she started thrashing.

"Don't you even have any artificial intelligence? All these kernels together hardly make up one byte," and she popped them out the window ...

C:\java\jdg\ch29>
GetFortuneApp connects to my Web server, invokes the fortune CGI program, and then displays the program's results.

The main() method creates an object of the FortuneFactory class that implements the URLStreamHandlerFactory interface. It then sets the object as the default protocol handler. An URL object is created using the URL string passed as the program's parameter. The getContent() method of the URL class is then used to extract the information generated by accessing the URL using the fortune protocol. The getContent() method results in the FortuneFactory object assigned to the fortuneFactory variable being invoked to retrieve an appropriate protocol handler. An object of class URLFortuneHandler is returned and its openConnection() method is invoked to extract the URLConnection object. This is performed behind the scenes as the result of invoking the URL class's getContent() method. The information returned from accessing the URL is then displayed.

The GetFortuneApp program defines the FortuneFactory class as implementing the URLStreamHandlerFactory interface. It implements the createURLStreamHandler() method and checks to see if the protocol type passed to it is fortune. If it is not, the null value is returned to signal that the Java-supplied protocol handler should be used. If the protocol type is fortune, the requested protocol is displayed and an URLFortuneHandler object is returned.

Summary

In this chapter you have learned how to write protocol handlers to access URLs via custom protocols. You have developed the URLFortuneHandler and integrated it with the GetFortuneApp program.

Wednesday, 21 December 2016

Chapter 28 Content Handlers

In this chapter you'll learn how to write content handlers to support the retrieval of objects by Web browsers. You'll also learn about the multipurpose Internet mail extensions (MIMEs) and how they are used to identify the type of objects that are provided by Web servers. You'll then develop a simple content handler and integrate it with a Web client program. This chapter builds on the material presented in Chapter 17, "Network Programming with the java.net Package."

Using Content Handlers

If you have been extensively involved with using your Web browser, you probably have encountered a number of external viewers or plug-ins that are used to supplement the capabilities provided by your browser. These external viewers are used to display and process files that are not normally supported by browsers.

Java supports additional internal or external viewers through the content handler mechanism. Content handlers are used to retrieve objects via an URLConnection object.

Content handlers are implemented as subclasses of the ContentHandler class. A content handler is only required to implement a single method-the getContent() method that overrides the method provided by the ContentHandler class. This method takes an URLConnection object as a parameter, and returns an object of a specific MIME type. You'll learn about MIME types in the following section of this chapter.

The purpose of a content handler is to extract an object of a given MIME type from an URLConnection object's input stream. Content handlers are not directly instantiated or accessed. The getContent() methods of the URL and URLConnection classes cause content handlers to be created and invoked to perform their processing.

A content handler is associated with a specific MIME type through the use of the ContentHandlerFactory interface. A class that implements the ContentHandlerFactory interface must implement the createContentHandler() method. This method returns a ContentHandler object to be used for a specific MIME type. A ContentHandlerFactory object is installed using the static setContentHandlerFactory() method of the URLConnection class.

Multipurpose Internet Mail Extensions (MIME)

Content handlers are associated with specific MIME types. Many Internet programs, including e-mail clients, Web browsers, and Web servers, use the multipurpose Internet mail extensions to associate an object type with a file. These object types include text, multimedia files, and application-specific files. MIME types consist of a type and a subtype. Examples are text/html, text/plain, image/gif, and image/jpeg, where text and image are the types and html, plain, gif, and jpeg are the subtypes. The URL classes provided by Java support the processing of each of these types; however, the number of MIME type/subtype combinations is large and growing. Content handlers are used to support MIME type processing.

Web servers map MIME types to the files they serve using the files' extensions. For example, files with the .htm and .html extensions are mapped to the text/html MIME type/subtype. Files with the .gif and .jpg extensions are mapped to image/gif and image/jpeg. The MIME type of a file is sent to Web browsers by Web servers when the server sends the designated files to the browsers in response to browser requests.

Developing a Content Handler

The first step to implementing a content handler is to define the class of the object to be extracted by the content handler. The content handler is then defined as a subclass of the ContentHandler class. The getContent() method of the content handler performs the extraction of objects of a specific MIME type from the input stream associated with an URLConnection object.

A content handler is associated with a specific MIME type through the use of a ContentHandlerFactory object. The createContentHandler() method of the ContentHandlerFactory interface is used to return a content handler for a specific MIME type.

Finally, the setContentHandlerFactory() method of the URLConnection class is used to set a ContentHandlerFactory as the default ContentHandlerFactory to be used with all MIME types.

A Simple Content Handler

This section presents an example of implementing a simple content handler. A bogus MIME type, text/cg, is created to implement objects of the character grid type. A character grid type is a two-dimensional grid made up of a single character. An example follows:

O
O
O
O
O
O
O
O
O
This example is a character grid object that is five character positions wide and five character positions high. It uses the O character to draw the grid. The grid is specified by a boolean array that identifies whether the drawing character is to be displayed.

This particular character grid is represented using the following text string:

55O1000101010001000101010001

The first character (5) represents the grid's height. The second character (also 5) represents the grid's width. The third character is the grid's drawing character. The remaining characters specify whether the draw character should be displayed at a particular grid position. A 1 signifies that the draw character should be displayed, and a 0 signifies that it should not be displayed. The array is arranged in row order beginning with the top of the grid.

The definition of the CharGrid class is shown in Listing 28.1.

Listing 28.1. The source code for the CharGrid class.
public class CharGrid {
 public int height;
 public int width;
 public char ch;
 public boolean values[][];
 public CharGrid(int h,int w,char c,boolean vals[][]) {
  height = h;
  width = w;
  ch = c;
  values = vals;
 }
}

The GridContentHandler Class

The GridContentHandler class is used to extract CharGrid objects from an URLConnection. Its source code is shown in Listing 28.2.

Listing 28.2. The source code for the GridContentHandler class.
import java.net.*;
import java.io.*;

public class GridContentHandler extends ContentHandler {
  public Object getContent(URLConnection urlc) throws IOException {
   DataInputStream in = new DataInputStream(urlc.getInputStream());
   int height = (int) in.readByte() - 48;
   int width = (int) in.readByte() - 48;
   char ch = (char) in.readByte();
   boolean values[][] = new boolean[height][width];
   for(int i=0;i<height;++i) {
    for(int j=0;j<width;++j) {
     byte b = in.readByte();
     if(b == 48) values[i][j] = false;
     else values[i][j] = true;
   }
  }
  in.close();
  return new CharGrid(height,width,ch,values);
 }
}

The GridContentHandler class extends the ContentHandler class and provides a single method. The getContent() method takes an URLConnection object as a parameter and returns an object of the Object class. It also throws the IOException exception.

The getContent() method creates an object of class DataInputStream and assigns it to the in variable. It uses the getInputStream() method of the URLConnection class to access the input stream associated with an URL connection.

The height, width, and draw character of the CharGrid object are read one byte at a time from the input stream. The values[][] array is then read and converted to a boolean representation. The CharGrid object is then created from the extracted values and returned.

The GetGridApp Program

The GetGridApp program illustrates the use of content handlers. It retrieves an object of the CharGrid type from my Web sever. I use the ncSA HTTPD server on a Linux system. I've set up the server's MIME type file to recognize files with the .cg extension as text/cg.

The source code of the GetGridApp program is shown in Listing 28.3.

Listing 28.3. The source code for the GetGridApp program.
import java.net.*;
import java.io.*;

public class GetGridApp {
 public static void main(String args[]){
  try{
   GridFactory gridFactory = new GridFactory();
   URLConnection.setContentHandlerFactory(gridFactory);
   if(args.length!=1) error("Usage: java GetGridApp URL");
   System.out.println("Fetching URL: "+args[0]);
   URL url = new URL(args[0]);
   CharGrid cg = (CharGrid) url.getContent();
   System.out.println("height: "+cg.height);
   System.out.println("width: "+cg.width);
   System.out.println("char: "+cg.ch);
   for(int i=0;i<cg.height;++i) {
    for(int j=0;j<cg.width;++j) {
     if(cg.values[i][j]) System.out.print(cg.ch);
     else System.out.print(" ");
    }
    System.out.println();
   }
  }catch (MalformedURLException ex){
   error("Bad URL");
  }catch (IOException ex){
   error("IOException occurred.");
  }
 }
 public static void error(String s){
  System.out.println(s);
  System.exit(1);
 }
}
class GridFactory implements ContentHandlerFactory {
 public GridFactory() {
 }
 public ContentHandler createContentHandler(String mimeType) {
  if(mimeType.equals("text/cg")) {
   System.out.println("Requested mime type: "+mimeType);
   return new GridContentHandler();
  }
  return null;
 }
}

When you invoke the GetGridApp program, provide it with the URL http://www.jaworski.com/java/chargrid.cg as a parameter.

The GetGridApp program's output is as follows:

C:\java\jdg\ch28>java GetGridApp http://www.jaworski.com/java/chargrid.cg
Fetching URL: http://www.jaworski.com/java/chargrid.cg
Requested mime type: text/cg
height: 5
width: 5
char: j
jjjjj
  j
  j
j j
 jj

C:\java\jdg\ch28>
This connects to my Web server, retrieves the chargrid.cg file, extracts the CharGrid object contained in the file, and displays it on the console window. The character grid object displays a grid of j characters.

The main() method creates an object of the GridFactory class, which implements the ContentHandlerFactory interface. It then sets the object as the default content handler. An URL object is created using the URL string passed as the program's parameter. The getContent() method of the URL class is then used to extract the CharGrid object from the URL. The getContent() method results in the GridFactory object assigned to the gridFactory variable being invoked to retrieve an appropriate content handler. An object of class GridContentHandler is returned and its getContent() method is invoked to extract the CharGrid object. This is performed behind the scene as the result of invoking the URL class's getContent() method. The CharGrid object is then displayed.

The GetGridApp program defines the GridFactory class as a ContentHandlerFactory. It implements the createContentHandler() method and checks to see if the MIME type passed to it is text/cg. If it is not, the null value is returned to signal that the Java-supplied content handler should be used. If the MIME type is text/cg, the requested MIME type is displayed, and a GridContentHandler object is returned.

Summary

In this chapter you have learned how to write content handlers to support the retrieval of objects by Web browsers. You have learned about the multipurpose Internet mail extensions and how they are used to identify the type of objects that are provided by Web servers. You have developed the GridContentHandler class and integrated it with the GetGridApp program.
Chapter 29, "Protocol Handlers," shows you how to integrate custom protocol handlers into your Web-based applications.

Monday, 19 December 2016

Chapter 27 Server Programs

In this chapter you'll learn how to write server programs to support Internet client/server applications. You'll also learn about the server programs found on the Internet and how they are written. You'll develop simple server programs that support the sending of mail and the retrieval of Web pages. This chapter builds on the material presented in Chapters 17, "Network Programming with the java.net Package," and 26, "Client Programs." You might want to review these chapters before continuing with the material presented in this chapter. Types of Servers Chapter 26 introduced you to the types of client programs found on the Internet. For every client, there must be a server. Typical servers include e-mail, Web, FTP, telnet, netnews, and DNS. Other, less-popular servers such as echo, ping, and finger are also commonly supported. E-mail servers move mail from client programs through the Internet to their destination hosts and store it until it is retrieved. The Simple Message Transfer Protocol (SMTP) is used to move mail. The Post Office Protocol (POP) is used to store mail and serve it to destination client programs. Web servers implement the Hypertext Transfer Protocol (HTTP) in order to serve Web pages over the Internet. The most popular Web servers are the ncSA and CERN HTTPD servers, which are publicly available and may be freely downloaded. Commercial Web servers, such as those provided by Netscape and Microsoft, are only a small percentage of those that are in current operation. FTP servers implement the File Transfer Protocol to make files available over the Internet. The most popular FTP server is a publicly available server developed by Washington University in St. Louis, Missouri. The domain name system provides the backbone for Internet communication by translating domain names to their IP addresses. The most popular DNS software is the publicly available BIND software developed by the University of California at Berkeley. Telnet servers are found in UNIX, VMS, and other multiuser operating systems. These servers allow remote login and implement the telnet protocol covered in Chapter 26. Server Responsibilities A server program listens for incoming connections on the well-known port associated with its service protocol. When an incoming connection is initiated by a client, the server accepts the connection, and typically spawns a separate thread to service that client. The client sends service requests over the connection. The server performs the service and then returns the results to the client. An SMTP Server Chapter 26 introduced the SMTP and developed a client program for generating Internet e-mail and sending it to an SMTP server. This section shows how the other side of the client/server connection is implemented. RFC 821 describes the details of this protocol. Here I will implement only a minimal subset of the available features of SMTP. An SMTP server listens on port 25 for incoming client connections. When a connection request is received, the server accepts the connection and sends a server-ready notification to the client. When the client sends the HELO command, the server responds by sending a 250 code, which indicates that it is okay to initiate a mail transmission. The server then waits for the client to send the MAIL command. It acknowledges the MAIL command with another 250 code. Having processed the MAIL command, the server then waits for the RCPT command. The server processes the RCPT command by checking to see if the destination e-mail address is valid for the server. It responds by indicating that the address is valid (using the 250 code) or that the user is not known to the server (using the 550 code). When the client sends the DATA command, the server sends the 354 code to tell the client to start sending the contents of the mail message. The client then sends the message data one line at a time. The server checks each line to see if it consists of a single period (.), indicating the end of the message data. When this happens, it sends another 250 code to the client, indicating that it has found the end of the message. The server removes the first period occurring in any message text line that it receives from the client. After the server receives the end of the message text, it waits for the QUIT command. When it receives the QUIT command, it sends a 221 code, indicating that it is closing the transmission channel. It then closes the socket connection. The SMTPServerApp Program The SMTPServerApp program illustrates the basic operation of an SMTP server program. It implements the basic SMTP commands described in the previous section. Its source code is shown in Listing 27.1. Listing 27.1. The source code for the SMTPServerApp program. import java.net.*; import java.io.*; import jdg.ch26.NVTInputStream; import jdg.ch26.NVTOutputStream; public class SMTPServerApp {  public static void main(String args[]){   SMTPServer server = new SMTPServer();   server.run();  } } class SMTPServer {  static final int HELO = 1;  static final int MAIL = 2;  static final int RCPT = 3;  static final int DATA = 4;  static final int END_DATA = 5;  static final int QUIT = 6;  static final int FINISHED = 9;  NVTOutputStream out;  NVTInputStream in;  String hostName;  public SMTPServer() {   super();  }  public void run() {   try{    ServerSocket server = new ServerSocket(25);    int localPort = server.getLocalPort();    hostName = InetAddress.getLocalHost().getHostName();    System.out.println("SMTPServerApp is listening on port "+localPort+".");    boolean finished = false;    do {     Socket client = server.accept();     String destName = client.getInetAddress().getHostName();     int destPort = client.getPort();     System.out.println("Accepted connection to "+destName+" on port "+      destPort+".");     out = new NVTOutputStream(client.getOutputStream());     in = new NVTInputStream(client.getInputStream(),out);     getMail();     client.close();    } while(!finished);   }catch (UnknownHostException ex) {    System.out.println("UnknownHostException occurred.");   }catch (IOException ex){    System.out.println("IOException occurred.");   }  }  void getMail() {   out.println("220 "+hostName+" Simple Mail Transport Service Ready");   int state = HELO;   do {    String line = "";    try {     line = in.readLine();     if(line == null) state = FINISHED;    }catch(IOException ex) {     System.out.println("IOException occurred.");     System.exit(1);    }    switch(state){    case HELO:     if(commandIs("HELO",line)) {      out.println("250 Hello");      System.out.println(line);      state = MAIL;     }else{      out.println("500 ERROR");      System.out.println(line);     }     break;    case MAIL:     if(commandIs("MAIL",line)) {      out.println("250 OK");      System.out.println(line);      state = RCPT;     }else{      out.println("500 ERROR");      System.out.println(line);     }     break;    case RCPT:     if(commandIs("RCPT",line)) {      out.println("250 OK");      System.out.println(line);      state = DATA;     }else{      out.println("500 ERROR");      System.out.println(line);     }     break;    case DATA:     if(commandIs("DATA",line)) {      out.println("354 Start mail input; end with .");      System.out.println(line);      state = END_DATA;     }else{      out.println("500 ERROR");      System.out.println(line);     }     break;    case END_DATA:     if(endOfData(line)) {      out.println("250 OK");      System.out.println("End of Message Received.");      state = QUIT;     }else{      System.out.println(stripFirstPeriod(line));     }     break;    case QUIT:     if(commandIs("QUIT",line)) {      out.println("221 "+hostName+" Service closing transmission channel");      System.out.println(line);      System.out.println("");      state = FINISHED;     }else{      out.println("500 ERROR");      System.out.println(line);     }     break;    }   } while(state != FINISHED);  }  boolean commandIs(String s,String line) {   int n = s.length();   if(s.equalsIgnoreCase(line.substring(0,n))) return true;   return false;  }  boolean endOfData(String s) {   if(s.equals(".")) return true;   return false;  }  String stripFirstPeriod(String s) {   try {    if(s.charAt(0) == '.') return s.substring(1);   }catch(Exception ex){   }   return s;  } } To run SMTPServerApp, type java SMTPServer at the DOS prompt. It will then display the following notice to indicate that it is up and running: C:\java\jdg\ch27>java SMTPServerApp SMTPServerApp is listening on port 25. In order to use SMTPServerApp, you have to send e-mail to your machine's Internet address. You can use any e-mail client program to send e-mail to SMTPServerApp, but I'll use the MailClientApp developed in the previous chapter to allow you to track both sides of the SMTP connection. Open a second console window and run MailClientApp as shown in the following code (substitute your host system name as the e-mail's destination): jaworski:~/jdg/ch26$ java MailClientApp From: jamie@jaworski.com To: jamie@jaworski-pc.hctg.saic.com Subject: Test of SMTPServerApp Enter message text. Terminate message text with an initial period. This is a test of SMTPServerApp. . End of message read. Connected to jaworski-pc.hctg.saic.com at port 25. 220 Jaworski-pc.hctg.saic.com Simple Mail Transport Service Ready >>>HELO jaworski 250 Hello >>>MAIL FROM: 250 OK >>>RCPT TO: 250 OK >>>DATA 354 Start mail input; end with . Subject: Test of SMTPServerApp >>>This is a test of SMTPServerApp. >>>. 250 OK >>>QUIT 221 Jaworski-pc.hctg.saic.com Service closing transmission channel jaworski:~/jdg/ch26$ In this example, I am sending e-mail from my computer at home (jaworski.com) to the computer I use at work (jaworski-pc.hctg.saic.com). You can work the example by sending e-mail from your computer to your computer using separate windows for sender and receiver. Note If you cannot determine your hostname or IP address, you can always use localhost as your hostname. Now look at the data displayed in the SMTPServerApp window: C:\java\jdg\ch27>java SMTPServerApp SMTPServerApp is listening on port 25. Accepted connection to jaworski.com on port 1205. HELO jaworski MAIL FROM: RCPT TO: DATA Subject: Test of SMTPServerApp This is a test of SMTPServerApp. End of Message Received. QUIT Note The SMTPServerApp program is designed to loop forever to receive and process new SMTP connections. When you are finished running the program, use Ctrl+C to terminate its operation. The data display is not as verbose as that of the mail client, but it shows all the commands and data received. Compare the display of SMTPServerApp with that of MailClientApp to follow how both sides of the Simple Message Transport Protocol were implemented. The main() method of SMTPServerApp creates an object of the SMTPServer class and invokes its run() method. The SMTPServer class declares seven constants that are used to maintain the state of the mail protocol as it interacts with a mail client program. It also declares the NVTInputStream and NVTOutputStream objects that it uses for client communication. The hostName variable is used to store the name of the local host running the SMTP server. The run() method creates a ServerSocket object on port 25. It retrieves the local host name and stores it using the hostName variable. It then identifies on what port it is listening. The do statement is used to service individual mail clients in a sequential manner. It accepts a client socket connection, gets the parameters of the connection, and displays them to the console window. The input and output streams associated with the connection are created and assigned to the in and out variables. The getMail() method is then invoked to receive mail from the client. The getMail() method implements a subset of SMTP in order to receive mail from the client. It does not store the messages it receives, but merely displays the results of the client interaction on the console window. When getMail() is invoked, it sends the 220 Simple Mail Transport Service Ready line to the mail client along with its hostname. It then sets the state variable to the HELO constant. The state variable is used to maintain the state of the communication with the client. The getMail() method uses a do statement to receive and process commands that it receives from the mail client. It reads a line from the client and verifies that the line is not null. (A null line signals that the connection with the client has been terminated.) The getMail() method processes the newly read line in different ways depending on the setting of the state variable. If the current state is HELO, it checks to see if the received line contains the HELO command. If it does, a 250 OK response is sent to the client and the state is set to MAIL. If it does not, a 500 ERROR response is sent to the client and the current state remains unchanged. If the current state is MAIL, it checks to see if the received line contains the MAIL command. If it does, a 250 OK response is sent to the client and the state is set to RCPT. If it does not, a 500 ERROR response is sent to the client and the current state remains unchanged. If the current state is RCPT, it checks to see if the received line contains the RCPT command. If it does, a 250 OK response is sent to the client and the state is set to DATA. If it does not, a 500 ERROR response is sent to the client and the current state remains unchanged. If the current state is DATA, it checks to see if the received line contains the DATA command. If it does, a 354 Start mail input; end with . response is sent to the client and the state is set to END_DATA. If it does not, a 500 ERROR response is sent to the client and the current state remains unchanged. If the current state is END_DATA, it checks to see if the received line contains the end-of-message data command, which is a line consisting of a single period (.). If it does, a 250 OK response is sent to the client and the state is set to QUIT. If it does not, the first period of the received line (if any) is stripped before the line is displayed to the console window. If the current state is QUIT, it checks to see if the received line contains the QUIT command. If it does, a 250 OK response is sent to the client and the state is set to FINISHED. If it does not, a 500 ERROR response is sent to the client and the current state remains unchanged. When the current state becomes FINISHED, the do statement is terminated. The commandIs() method is used to determine whether a command received from a mail client matches a specific command string. The endOfData() method checks a received line to see if it consists of a single period indicating the end of a message transmission. The stripFirstPeriod() method is used to strip out the first period of a message text line. A Web Server Web servers implement HTTP in order to retrieve Web resources identified by URLs. HTTP is an application-level protocol that is designed to be quick and efficient. It is based on the request-response paradigm. Web browsers initiate connections with Web servers and submit service requests. The servers, upon receiving a request, locate the specified resource and perform the requested operation. Typical Web browser requests are to retrieve a designated file or send data to a CGI program. HTTP supports several request types, referred to as methods. These include the GET, HEAD, and POST methods. The Web server developed in the following section supports only the GET request. The server responds to GET requests by returning the requested resource to the browser. The server's response begins with a header and is followed by the requested data. The header consists of a status line and one or more general header lines. The status line identifies the version of HTTP being used and a status code. General header lines include a MIME version identifier, a date/time line, a content type indicator, and a content length identifier. A blank line is inserted between the header and the body of the resource data. The WebServerApp Program The WebServerApp program illustrates the basic operation of a Web server. (See Listing 27.2.) It is a single-threaded Web server that supports a subset of the HTTP 1.0 protocol. Many Web servers are multithreaded, allowing them to simultaneously support multiple browser connections. Web servers for low-end pc platforms, such as the Apache Web server, are single threaded to make up for processing deficiencies of slow pcs and slower Internet connections. WebServerApp can easily be converted to a multithreaded server by implementing the interior of the do statement as a separate thread. Listing 27.2. The source code for the WebServerApp program. import java.net.*; import java.io.*; import jdg.ch26.NVTInputStream; import jdg.ch26.NVTOutputStream; public class WebServerApp {  public static void main(String args[]){   WebServer server = new WebServer();   server.run();  } } class WebServer {  public WebServer() {   super();  }  public void run() {   try{    ServerSocket server = new ServerSocket(8080);    int localPort = server.getLocalPort();    System.out.println("WebServerApp is listening on port "+localPort+".");    do {     Socket client = server.accept();     String destName = client.getInetAddress().getHostName();     int destPort = client.getPort();     System.out.println("Accepted connection to "+destName+      " on port "+destPort+".");     NVTOutputStream outStream =      new NVTOutputStream(client.getOutputStream());     NVTInputStream inStream =      new NVTInputStream(client.getInputStream(),outStream);     boolean finished = false;     String inLine = inStream.readLine();     System.out.println("Received: "+inLine);     if(getRequest(inLine)) {      String fileName = getFileName(inLine);      File file = new File(fileName);      if(file.exists()) {       System.out.println(fileName+" requested.");       outStream.println("HTTP/1.0 200 OK");       outStream.println("MIME-Version: 1.0");       outStream.println("Content-Type: text/html");       int len = (int) file.length();       outStream.println("Content-Length: "+len);       outStream.println("");       sendFile(outStream,file);       outStream.flush();      }else{       outStream.println("HTTP/1.0 404 Not Found");       String notFound =        "Not Found

Error 404 - File Not Found

";       outStream.println("Content-Type: text/html");       outStream.println("Content-Length: "+notFound.length()+2);       outStream.println("");       outStream.println(notFound);      }     }     client.close();    } while(true);   }catch (IOException ex){    System.out.println("IOException occurred.");   }  }  boolean getRequest(String s) {   if(s.length() > 0) {    if(s.substring(0,3).equalsIgnoreCase("GET")) return true;   }   return false;  }  String getFileName(String s) {   String f = s.substring(s.indexOf(' ')+1);   f = f.substring(0,f.indexOf(' '));   try {    if(f.charAt(0) == '/') f = f.substring(1);   } catch(StringIndexOutOfBoundsException ex) {   }   if(f.equals("")) f = "index.htm";   return f;  }  void sendFile(NVTOutputStream out,File file) {   try {    DataInputStream in = new DataInputStream(new FileInputStream(file));    int len = (int) file.length();    byte buffer[] = new byte[len];    in.readFully(buffer);    out.write(buffer,0,len);    in.close();   }catch(Exception ex){    System.out.println("Error retrieving file.");    System.exit(1);   }  } } The standard socket implementation that comes with the Windows 95 version of Java 1.0 has a flaw that does not close sockets correctly. When you fetch a Web document from WebServerApp, you might have to click on the Stop button of your Web browser to have it display the retrieved Web page. When I run WebServerApp on other operating system platforms, such as Linux, using Java 1.0.1, there is no socket closure problem and everything works as it should. Run WebServerApp as follows: C:\java\jdg\ch27>java WebServerApp WebServerApp is listening on port 8080. It responds by indicating that it is listening on port 8080. I had it use port 8080 instead of the standard port 80 so as not to interfere with any Web server that you might currently have running on your system. I have supplied a default Web page, index.htm, that is retrieved by WebServerApp. (See Listing 27.3.) You can also retrieve other Web pages by placing them in the same directory as WebServerApp and referencing them in the URL opened by your Web browser. Listing 27.3. The contents of the index.htm file. Test Document

This is a test.

Note If the WebServerApp program does not find the index.htm file, it will return an error message. Because WebServerApp is a server, you need to use a client program in order to interact with it. Launch your favorite Web browser and open the URL of your machine followed by :8080 to have the browser submit its request to port 8080 instead of port 80. For example, if your host name is my.host.name.com, open the URL http://my.host.name.com:8080. WebServerApp responds by identifying the browser connection and sending the index.htm file. You can access other files by appending their names to the URL. For example, to access the test.htm file in the directory where you launched WebServerApp, use the URL http://my. host.name.com:8080/test.htm. The following output is displayed by WebServerApp on the console window: C:\java\jdg\ch27>java WebServerApp WebServerApp is listening on port 8080. Accepted connection to jaworski-pc.hctg.saic.com on port 2145. Received: GET / HTTP/1.0 index.htm requested. When you access the URL http://my.host.name.com:8080, WebServerApp is instructed to return the default no name HTML file. It responds by sending index.htm to the Web browser. Your browser's display should contain the message shown in Figure 27.1. Figure 27.1 : Web browser display. Note If you cannot find your hostname, you can use localhost instead. For example, the URL http://localhost:8080 can be used instead of http://my.host.name.com:8080. Experiment by creating your own HTML files and using your browser to access them using WebServerApp. Use Ctrl+C to terminate the operation of WebServerApp. The main() method of WebServerApp creates a WebServer object and invokes its run() method. The WebServer class implements a single default constructor and three access methods. The run() method supports Web client retrieval requests and is the heart of the WebServerApp processing. It creates a ServerSocket object using port 8080 and then displays its operational status on the console window. A do statement is used to accept and process incoming client connections. It retrieves the parameters associated with a connection and displays them to the console window. The input and output streams associated with the connection are created and assigned to the inStream and outStream variables. A line is then read from the input stream and displayed to the console window. The line received from the browser client is checked to see if it is a GET request. If it is, the name of the requested HTML file is retrieved from the browser request line. If the file exists within the current directory, a 200 OK status line is sent to the browser, followed by a MIME-version 1.0 header line. This line tells the browser that the server is cognizant of MIME version 1.0 when returning the requested file. It then specifies the MIME type of the requested file as text or html. Real Web servers would send a MIME type that matched the extension of the file returned. See Chapter 28, "Content Handlers," for a discussion of MIME types and their use by Web servers and browsers. The length of the file to be returned is obtained using the length() method of the File class, and a notification of the file's length is returned to the browser using a Content-Length header line. A blank line follows the Content-Length header line to signal the end of the HTTP header. The sendFile() method is then invoked to send the requested file to the browser. If the file requested by the browser does not exist, the HTTP status line sent to the browser contains a 404 Not Found error code and a short HTML file indicating that the error is sent to the browser. If the request received from the browser client is not a GET request, it is ignored and the connection is closed. The getRequest() method determines whether an incoming client request uses the GET method. The getFileName() method extracts the requested HTML filename from an incoming browser request line. The sendFile() method sends the file requested by a Web client using the output stream of the server-client connection. It sends the file by reading all bytes of the file into a byte array and then sending the entire array over the connection. This approach works well with small files, but may break with large files, depending on available memory resources. Summary In this chapter you have learned how to write programs that implement the server end of Internet client/server applications. You have learned about the common server programs found on the Internet and how they are structured. You have developed an SMTP server and a primitive Web server. In Chapter 28 you'll learn how to write content handlers that are used with Web client applications.

Chapter 26 Client Programs

In this chapter you will learn how to write client programs that support networked client/server applications. You'll learn about the typical client programs found on the Internet and how they are structured. You'll develop simple client programs that support remote login and the sending of mail, and that fetch a list of Web pages. This chapter builds on the material presented in Chapter 17, "Network Programming with the java.net Package." You might want to review Chapter 17 before continuing with the material presented in this chapter.

Types of Clients

Of the client/server applications that are found on the Internet, only a small group is typically used. These include e-mail, the Web, FTP, Usenet news groups, and telnet. Gopher and WAIS, both precursors of the Web, have declined in popularity, having been subsumed by the Web. Typical Internet client programs include e-mail programs, Web browsers, FTP programs, and telnet clients.

E-mail programs provide an easy-to-use interface by which mail can be created and sent, retrieved and displayed, and managed. Popular window-based clients include Eudora and Pegasus. UNIX systems provide a number of popular e-mail clients, including Pine, Elm, and mh.

Web browsers provide a window on the World Wide Web and support the display of Web pages, including, of course, Java programs. The Netscape browser is the most popular browser on the Web and is Java capable. It is supported on UNIX, Windows, and Macintosh systems.

FTP programs provide a convenient way to retrieve files from public Internet file servers or from private file directories. Although a number of user-friendly FTP client programs are available, the simple text-based FTP client is still the most popular and most widely supported.

Newsreader programs simplify the process of working with messages that are posted to Usenet news groups. A number of netnews client programs are available for Windows, Macintosh, UNIX, and other operating system platforms.

Telnet clients are used to remotely log into other systems. These systems are usually UNIX or other operating systems that are powerful enough to provide the underlying capabilities needed to implement multiuser support. Windows and Macintosh systems, because of their inherent limitations, do not support telnet server applications.

Client Responsibilities

Client programs perform a service for their users by connecting with their server counterparts, forwarding service requests based on user inputs, and providing the service results back to the user.

In most cases, the client must initiate the connection. Typically, the server listens on a well-known port for a client connection. The client initiates the connection, which is accepted by the server. The client sends a service request to the server, based on user inputs. The server receives the service request, performs the service, and returns the results of the service to the client. The client receives the service results and displays them to the user.

A Simple Telnet Client

A telnet client program provides users with the capability to log into remote systems. It connects to a telnet server (called a telnet daemon) that listens on port 23 for an incoming connection. The telnet client connects to the daemon, which usually runs a login program and, upon successful login, runs a shell program.

The telnet client must be capable of simultaneously exchanging data with both the user and the remote system. The protocol used for communication between the client and the server is specified in RFC 854, the Telnet Protocol Specification. RFC 854 identifies three basic elements of the telnet protocol: the concept of a network virtual terminal, the principle of negotiated options, and the symmetry between terminals and processes.

The Network Virtual Terminal

The network virtual terminal, or NVT, is a very simple device that forms the basis for establishing telnet-based communication. All telnet clients and servers are required to support the NVT as a minimum capability. It is an abstract device that consists of a printer and a keyboard. The user types characters on the keyboard that are forwarded to the server. The server returns data to the user and the NVT displays it on the printer. The NVT provides local character echoing and half-duplex operation, although remote echoing and full-duplex operation can be used as negotiated options. Lines are terminated using a standard carriage-return-line-feed combination.

The NVT also provides for control operations that support process interruption and the discarding of excessive output. These operations are signaled by using the Interpret as Command (IAC) code as described in the next section.

The Interpret as Command Code

The IAC code is sent from a client or server to a program on the other end of a telnet connection to send a control code or to negotiate an option, as described in the next section. The IAC is a single byte consisting of the value 255 or hex 0xFF. The IAC may be followed by a single byte to send a control code, or by two or more bytes to negotiate an option. For example, the IAC followed by a byte with the decimal value of 243 is used to send a break command.

Because the IAC is used to indicate a command or option negotiated, a special byte sequence is needed to send the byte value 255 used for the IAC. This is accomplished by sending two IACs in succession.

Negotiated Options

Because all telnet clients and servers are required to implement the NVT, they all have a common, but primitive, basis from which to begin operation. Additional options, such as full- duplex operation and character echoing, can be used based on the principle of negotiated options.

Options are negotiated when either the client or server program sends an IAC code to the other. The IAC code is followed by a WILL or DO code and an option code. The WILL code informs the program on the other side of the connection that it intends to use a particular option. The other program may respond with a DO or a DONT response, consisting of the IAC, followed by the DO or DONT code, followed by the option.

A program can also request that the program on the other side of the connection implement an option. This is accomplished by sending the IAC code, the DO code, and the option code. The other program can respond with a WILL or WONT response. A WILL response is indicated by sending the IAC, followed by the WILL code, followed by the option code. A WONT response is sent in the same manner, with the WONT code being used instead of the WILL code.

Symmetry Between Terminals and Processes

As you probably have surmised from reading the previous sections, the communication between client and server is highly symmetrical. Either the client or server can initiate option negotiation. The use of symmetry between client and host simplifies the implementation of the telnet protocol and allows client and host software to be developed from a common base. The TelnetApp program, presented in the next section, makes use of two I/O filters, NVTInputStream and NVTOutputStream, that implement some of the basic elements of the telnet protocol. These streams do not support control characters or additional options. Option negotiation is handled by refusing any additional options other than those provided by the basic NVT.

The TelnetApp Program

The TelnetApp program implements a minimum set of features of the telnet protocol in order to accomplish a remote login to a telnet server. The purpose of the program is not to provide you with a telnet client, but to show you the basics of how these clients work. More sophisticated and powerful telnet client programs can be retrieved from the Internet. The source code of the TelnetApp program is shown in Listing 26.1.

Listing 26.1. The source code for the TelnetApp program.
import java.lang.*;
import java.net.*;
import java.io.*;
import jdg.ch26.NVTInputStream;
import jdg.ch26.NVTOutputStream;
import jdg.ch26.NVTPrinter;

public class TelnetApp {
  public static void main(String args[]){
    PortTalk portTalk = new PortTalk(args);
    portTalk.start();
  }
 }

class PortTalk extends Thread {
  Socket connection;
  PrintStream outStream;
  NVTInputStream inStream;
  NVTPrinter printer;
  public PortTalk(String args[]){
    if(args.length!=2) error("Usage: java TelnetApp host port");
    String destination = args[0];
    int port = 0;
    try {
      port = Integer.valueOf(args[1]).intValue();
    }catch (NumberFormatException ex) { error("Invalid port number"); }
    try{
      connection = new Socket(destination,port);
    }catch (UnknownHostException ex) { error("Unknown host"); }
    catch (IOException ex) { error("IO error creating socket"); }
    try{
       outStream = new PrintStream(connection.getOutputStream());
       inStream = new NVTInputStream(connection.getInputStream(),outStream);
    }catch (IOException ex) { error("IO error getting streams"); }
    System.out.println("Connected to "+destination+" at port "+port+".");
  }
  public void run() {
    printer = new NVTPrinter(inStream);
    printer.start();
    yield();
    processUserInput();
    shutdown();
  }
  public void processUserInput() {
    try {
     String line;
     boolean finished = false;
     DataInputStream userInputStream = new DataInputStream(System.in);
     do {
       line = userInputStream.readLine();
       if(line == null) finished = true;
       else outStream.println(line);
      } while(!finished);
     } catch(IOException ex) {
     error("Error reading user input");
   }
  }
  public void shutdown(){
  try{
     connection.close();
   }catch (IOException ex) { error("IO error closing socket"); }
  }
  public void error(String s){
    System.out.println(s);
    System.exit(1);
  }
 }

Note
The TelnetApp class uses the NVTPrinter, NVTInputStream, and NVTOutputStream classes that are supplied in the following sections. You must type in the NVTPrinter.java, NVTInputStream.java, and NVTOutputStream.java files before compiling TelnetApp.java. The Java compiler will automatically compile these files when TelnetApp.java is compiled.
You use the TelnetApp program in the same way as any other telnet program. But bear in mind that it is only a minimal telnet client. Run the program by invoking it with the hostname of a computer that supports telnet and the well-known telnet port number, port 23.

In the following example, I use the program to log into my account at CTS. Note that the program operates in half-duplex mode, so characters are echoed locally. I substituted asterisks (*) for my password. Take caution when using this program because it will display your password characters in the same manner as any other text that you type.

Also, notice that commands that I type are echoed by my cts.com host:

C:\java\jdg\ch26>java TelnetApp cts.com 23
Connected to cts.com at port 23.

UNIX System V Release 3.2 (crash.cts.com) (ttyp2)

 login: jaworski
Password:****

Last successful login for jaworski: Tue Apr 09 23:17:46 PDT 1996 on ttyp34
Last unsuccessful login for jaworski: Fri Apr 05 09:56:34 PST 1996 on ttyp9

              Welcome to CTSNET!

        Enter 'help' for assistance and information.

1% l
l
total 16
drwx------ 2 jaworski guest 272 Sep 08 1995 Mail
drwxr-xr-x 2 jaworski guest 208 Dec 07 15:09 News
drwxr-xr-x 2 jaworski guest 224 Sep 08 1995 bin
drwxr-xr-x 2 jaworski guest 384 Apr 04 08:43 download
lrwxrwxrwx 1 root root 15 Mar 15 02:56 dropbox -> /ftp/j/jaworski

drwx------ 2 jaworski guest 160 Dec 08 10:35 ga
drwx------ 2 jaworski guest 288 Apr 08 09:49 mail
drwxr-xr-x 3 jaworski guest 112 Dec 01 12:20 writing
2% exit
exit
3% logout

Connection broken.

C:\java\jdg\ch26>
The TelnetApp program creates an object of class PortTalk to perform its processing. This class extends the Thread class in order to implement multithreading capabilities. Its constructor uses the parameters passed in the TelnetApp command-line invocation to set up the connection to the specified host and port.

The run() method creates an object of the NVTPrinter class to interface with the destination host and invokes the processUserInput() method to interface with the user. The processUserInput() method reads a line at a time from the user's console and sends it to the telnet server.

The NVTPrinter Class

The NVTPrinter class performs most of the interesting processing because it interfaces with the server. It does this using the NVTInputStream class covered in the next section. NVTPrinter is also implemented as a subclass of Thread. Its source code is shown in Listing 26.2.

Listing 26.2. The source code for the NVTPrinter class.
package jdg.ch26;

import java.io.*;

public class NVTPrinter extends Thread {
 NVTInputStream inStream;
 public NVTPrinter(NVTInputStream in) {
  super();
  inStream = in;
 }
 public void run() {
  boolean finished = false;
  try {
   do {
    int i = inStream.read();
    if(i == -1) finished = true;
    else{
     System.out.print((char) i);
     System.out.flush();
     yield();
    }
   } while(!finished);
   System.out.println("\nConnection broken.");
   System.exit(0);
  } catch (IOException ex) {
   System.out.println("NVTPrinter error");
   System.exit(1);
  }
 }
}

The NVTInputStream Class

The NVTInputStream class implements the network virtual terminal input interface. Its source code is shown in Listing 26.3.

Listing 26.3. The source code for the NVTInputStream class.
package jdg.ch26;

import java.io.*;

public class NVTInputStream extends FilterInputStream {
 byte IAC = (byte) 0xff;
 byte DO = (byte) 0xfd;
 byte WILL = (byte) 0xfb;
 byte CR = 13;
 byte LF = 10;
 int WONT = 252;
 int DONT = 254;
 int BUFFER_SIZE = 1024;
 OutputStream out;
 byte lineBuffer[] = new byte[BUFFER_SIZE];
 int numBytes = 0;
 public NVTInputStream(InputStream inStream,OutputStream outStream) {
  super(inStream);
  out = outStream;
 }
 public int read() throws IOException {
  boolean recIAC;
  int i;
  do {
   recIAC = false;
   i = in.read();
   if(i == -1) return i;
   byte b = (byte) i;
   if(b == IAC) {
    recIAC = true;
    int cmd = in.read();
    if(cmd == -1) return cmd;
    byte b2 = (byte) cmd;
    if(b2 == IAC) return 255;
    else if(b2 == DO) {
     int opt = in.read();
     if(opt == -1) return opt;
     out.write(255);
     out.write(WONT);
     out.write(opt);
     out.flush();
    }else if(b2 == WILL) {
     int opt = in.read();
     if(opt == -1) return opt;
     out.write(255);
     out.write(DONT);
     out.write(opt);
     out.flush();
    }
   }
  } while(recIAC);
  return i;
 }
 public String readLine() throws IOException {
  numBytes = 0;
  boolean finished = false;
  do {
   int i = read();
   if(i == -1) return null;
   byte b = (byte) i;
   if(b == LF) {
    if(numBytes>0) {
     if(lineBuffer[numBytes-1] == 13)
      return new String(lineBuffer,0,0,numBytes-1);
    }
   }
   lineBuffer[numBytes] = b;
   ++numBytes;
  } while (!finished);
  return null;
 }
}

NVTInputStream uses the network virtual terminal conventions, covered earlier in this chapter, to filter the input stream associated with the connection. It implements the basic read() method and also a convenient readLine() method.

The NVTOutputStream Class

The NVTOutputStream class provides an output analog to the NVTInputStream class. It implements the basic write() method according to the NVT conventions. It also provides a println() method that uses the carriage-return-line-feed (CR-LF) end-of-line conventions. Its source code is shown in Listing 26.4.

Listing 26.4. The source code for the NVTOutputStream class.
package jdg.ch26;

import java.io.*;

public class NVTOutputStream extends PrintStream {
  int IAC = 255;
  byte CR = 13;
  byte LF = 10;
  public NVTOutputStream(OutputStream outStream) {
    super(outStream);
  }
  public void write(int i) {
    if(i == IAC) super.write(i);
    super.write(i);
  }
  public void println(String s) {
    super.print(s);
    super.write(CR);
    super.write(LF);
    super.flush();
  }
 }

A Mail Client

Although mail is sent on the Internet using a variety of protocols, the Simple Message Transfer Protocol (SMTP), described in Request for Comments (RFC) 821, is the basic protocol used to move mail from one host to another. SMTP consists of a mail sender, a mail receiver, and a set of line-oriented commands used to send mail from the sender to the receiver.
Note
Requests for Comments are numbered notes of the Internet community that are usually, but not always, used to describe some aspect of the protocols or services used on the Internet.
RFC 821 describes the complete set of commands used by mail senders and receivers. Here I am using only a minimal subset of these commands to illustrate the development of an SMTP client, the mail sender.

An SMTP client connects to an SMTP server by establishing a connection to port 25 of the server's host. The server accepts the connection, sends a one-line ready notification to the client, and awaits client commands.

The client sends the HELO command with its hostname to introduce itself to the server. The server responds by sending a code that indicates that it is OK to initiate a mail transmission.

The client sends the MAIL command to the server to indicate that it has mail from a specific user. The server responds with a notification to proceed.

The client sends the RCPT command to identify the recipient of the e-mail. The server responds by telling the client whether or not the recipient is valid.

The client sends the DATA command to indicate that it is ready to send the message. The server responds by telling the client that it is OK to begin sending message data.

The client sends the message, a line at a time, terminating the message with a line containing a single period (.). A line of message text beginning with a period is sent by prepending an extra initial period to the message line.

The server acknowledges receiving the last line of text by sending an OK command to the client.

The client then terminates the connection by sending a QUIT command to the server. The server then responds by notifying the client that it is closing the connection.

The MailClientApp Program

The MailClientApp program illustrates the basic operation of a mail client program. It implements the basic SMTP commands described in the previous section. Its source code is shown in Listing 26.5.

Listing 26.5. The source code for the MailClientApp program.
import java.lang.*;
import java.net.*;
import java.io.*;
import java.util.Vector;
import jdg.ch26.NVTInputStream;
import jdg.ch26.NVTOutputStream;
import jdg.ch26.NVTPrinter;

public class MailClientApp {
  public static void main(String args[]){
    MessageInterface messageInterface = new MessageInterface();
    Message msg = messageInterface.getMsg();
    MailTalk mailTalk = new MailTalk(msg);
    mailTalk.run();
  }
 }

class Message {
  String source;
  String destination;
  String subject;
  String text[];
  public Message() {
  super();
  }
  public void setDestination(String dest) {
    destination = dest;
  }
  public String getDestination() {
    return destination;
  }
  public void setSource(String src) {
    source = src;
  }
  public String getSource() {
    return source;
  }
  public String getDestinationHost() {
    return destination.substring(destination.indexOf('@')+1);
  }
    public void setSubject(String subj) {
  subject = subj;
  }
  public String getSubject() {
    return subject;
  }
  public void setText(Vector txt) {
    int n = txt.size();
    text = new String[n];
    for(int i = 0; i< n; ++i) {
    text[i] = (String) txt.elementAt(i);
   }
  }
  public String[] getText() {
   return text;
  }
}

class MessageInterface {
  Message msg;
  public MessageInterface() {
    msg = new Message();
  }
 public Message getMsg() {
   try {
   System.out.print("From: ");
   System.out.flush();
   DataInputStream inStream = new DataInputStream(System.in);
   msg.setSource(inStream.readLine());
   System.out.print("To: ");
   System.out.flush();
   msg.setDestination(inStream.readLine());
   System.out.print("Subject: ");
   System.out.flush();
   msg.setSubject(inStream.readLine());
   System.out.println("Enter message text.");
   System.out.println("Terminate message text with an initial period.");
   Vector text = new Vector();
   boolean finished = false;
   do {
     String line = inStream.readLine();
     if(endOfText(line)) finished = true;
     else text.addElement(line);
   } while(!finished);
   msg.setText(text);
   System.out.println("End of message read.");
   }catch (IOException ex) {
    System.out.println("IO Exception");
    System.exit(1);
   }
   return msg;
 }
 boolean endOfText(String s) {
   if(s.length() == 0) return false;
   if(s.charAt(0) == '.') return true;
   return false;
 }
}

class MailTalk {
   // Communication states
   static final int START = 0;
   static final int HELO = 1;
   static final int MAIL = 2;
   static final int RCPT = 3;
   static final int DATA = 4;
   static final int TEXT = 5;
   static final int QUIT = 6;
   static final int FINISHED = 9;
   Socket connection;
   String localHostName;
   NVTOutputStream outStream;
   NVTInputStream inStream;
   Message msg;
   public MailTalk(Message msg){
     this.msg = msg;
     String destination = msg.getDestinationHost();
     int port = 25;
     try{
       connection = new Socket(destination,port);
       localHostName = InetAddress.getLocalHost().getHostName();
     }catch (UnknownHostException ex) { error("Unknown host"); }
     catch (IOException ex) { error("IO error creating socket"); }
     try{
       outStream = new NVTOutputStream(connection.getOutputStream());
       inStream = new NVTInputStream(connection.getInputStream(),outStream);
    }catch (IOException ex) { error("IO error getting streams"); }
    System.out.println("Connected to "+destination+" at port "+port+".");
  }
  public void run() {
   sendMail();
   shutdown();
 }
 public void sendMail() {
   try {
   int state = START;
   String line;
   do {
    line = inStream.readLine();
    if(line == null) state = FINISHED;
    else{
      System.out.println(line);
      switch(state) {
      case START:
       if(gotResponse(220,line)){
        outStream.println("HELO "+localHostName);
        System.out.println(">>>HELO "+localHostName);
        state = HELO;
      }else state=FINISHED;
     break;
   case HELO:
   if(gotResponse(250,line)){
     outStream.println("MAIL FROM:<"+msg.getSource()+">");
     System.out.println(">>>MAIL FROM:<"+msg.getSource()+">");
     state = MAIL;
   }else state=FINISHED;
   break;
  case MAIL:
   if(gotResponse(250,line)){
    outStream.println("RCPT TO:<"+msg.getDestination()+">");
    System.out.println(">>>RCPT TO:<"+msg.getDestination()+">");
    state = RCPT;
  }else state=FINISHED;
  break;
 case RCPT:
   if(gotResponse(250,line)){
    outStream.println("DATA");
    System.out.println(">>>DATA");
    state = DATA;
  }else state=FINISHED;
  break;
 case DATA:
  if(gotResponse(354,line)){
    String text[] = msg.getText();
    int len = text.length;
    outStream.println("Subject: "+msg.getSubject());
    outStream.println("");
    System.out.println("Subject: "+msg.getSubject());
    System.out.println("");
    for(int i=0;i<len;++i) {
      if(text[i].length() > 0 && text[i].charAt(0) == '.') {
      outStream.println("."+text[i]);
      System.out.println("."+text[i]);
    }else{
      outStream.println(text[i]);
      System.out.println(">>>"+text[i]);
     }
    }
    outStream.println(".");
    System.out.println(">>>.");
    state = TEXT;
   }else state=FINISHED;
   break;
  case TEXT:
    if(gotResponse(250,line)){
      outStream.println("QUIT");
      System.out.println(">>>QUIT");
      state = QUIT;
    }else state=FINISHED;
     break;
    case QUIT:
      state=FINISHED;
      break;
     }
    }
   } while(state != FINISHED);
  } catch(IOException ex) {
    error("IO Exception while sending mail");
  }
 }
 boolean gotResponse(int n,String s) {
  try {
  int responseCode = Integer.valueOf(s.trim().substring(0,3)).intValue();
  String line = s;
  while(line.charAt(3) == '-') {
    line = inStream.readLine();
    System.out.println(line);
   }
   if(responseCode == n) return true;
  }catch(NumberFormatException ex) {
  }catch(IOException ex){
  }
  return false;
 }
 public void shutdown(){
  try{
   connection.close();
  }catch (IOException ex) { error("IO error closing socket"); }
 }
 public void error(String s){
   System.out.println(s);
   System.exit(1);
 }
}

The MailClientApp program prompts you for the from: name that you want associated with the sent message. SMTP is inherently insecure and will allow you to send e-mail using the e-mail address of another person as the from: address. In the example, I send a message using my daughter's e-mail address to myself. The subject of the message is Test Message and it contains a mere two lines of text. The following output shows a sample dialog with the MailClientApp program:
C:\java\jdg\ch26>java MailClientApp
From: emily@jaworski.com
To: jamie@jaworski.com
Subject: Test Message
Enter message text.
Terminate message text with an initial period.
This is a test.
It is only a test.
.
End of message read.
Connected to jaworski.com at port 25.
220-jaworski.com Sendmail 8.6.12/8.6.9 ready at Wed, 10 Apr 1996 00:33:31 -0700
220 ESMTP spoken here
>>>HELO athome.jaworski.com
250 jaworski.com Hello athome.jaworski.com [204.212.153.194], pleased to meet you
>>>MAIL FROM:<emily@jaworski.com>
250 <emily@jaworski.com>... Sender ok
>>>RCPT TO:<jamie@jaworski.com>
250 <jamie@jaworski.com>... Recipient ok
>>>DATA
354 Enter mail, end with "." on a line by itself
Subject: Test Message

>>>This is a test.
>>>It is only a test.
>>>.
250 AAA02243 Message accepted for delivery
>>>QUIT
221 jaworski.com closing connection

C:\java\jdg\ch26>
After the message is received by the e-mail client, it connects to my SMTP server and sends the message using the SMTP commands summarized earlier in this chapter.

The >>> arrows indicate commands that were sent by the program.

The Web Fetcher Program

Web browsers are the most popular client programs found on the Internet. They allow users to download and display Web pages, usually one at a time. The program shown in Listing 26.6 allows the user to specify a list of Web pages to be retrieved, and retrieves these Web pages and stores them on the local file system. This is an example of how custom Web clients can be implemented in Java.

Listing 26.6. The source code for the WebFetchApp program.
import java.util.Vector;
import java.io.*;
import java.net.*;

public class WebFetchApp {
  public static void main(String args[]){
   WebFetch fetch = new WebFetch();
   fetch.run();
  }
 }

class WebFetch {
  String urlList = "url-list.txt";
  Vector URLs = new Vector();
  Vector fileNames = new Vector();
  public WebFetch() {
   super();
 }
 public void getURLList() {
  try {
    DataInputStream inStream = new DataInputStream(new FileInputStream(urlList));
    String inLine;
    while((inLine = inStream.readLine()) != null) {
     inLine = inLine.trim();
     if(!inLine.equals("")) {
      int tabPos = inLine.lastIndexOf('\t');
      String url = inLine.substring(0,tabPos).trim();
      String fileName = inLine.substring(tabPos+1).trim();
      URLs.addElement(url);
      fileNames.addElement(fileName);
    }
   }
  }catch(IOException ex){
  error("Error reading "+urlList);
 }
}
public void run() {
  getURLList();
  int numURLs = URLs.size();
  for(int i=0;i<numURLs;++i)
  fetchURL((String) URLs.elementAt(i),(String) fileNames.elementAt(i));
 System.out.println("Done.");
}
public void fetchURL(String urlName,String fileName) {
  try{
    URL url = new URL(urlName);
    System.out.println("Getting "+urlName+"...");
    File outFile = new File(fileName);
    PrintStream outStream = new PrintStream(new FileOutputStream(outFile));
    DataInputStream inStream = new DataInputStream(url.openStream());
    String line;
    while ((line = inStream.readLine())!= null) outStream.println(line);
    inStream.close();
    outStream.close();
  }catch (MalformedURLException ex){
   System.out.println("Bad URL");
  }catch (IOException ex){
   System.out.println("IOException occurred.");
 }
}
public void error(String s){
  System.out.println(s);
  System.exit(1);
 }
}

To use the program, create a file named url-list.txt that contains the names of the URLs you want to retrieve and the names of the files in which you want them stored. The following url-list.txt file was used to retrieve some pretty famous Web pages; it is included on the CD, in the \jdg\ch26 directory:

http://www.yahoo.com yahoo.htm
http://www.cnn.com cnn.htm
http://home.netscape.com netscape.htm
The output generated for the WebFetchApp program is as follows:

C:\java\jdg\ch26>java WebFetchApp
Getting http://www.yahoo.com...
Getting http://www.cnn.com...
Getting http://home.netscape.com...
Done.

C:\java\jdg\ch26>
Summary

In this chapter you have learned how to write client programs that implement the client end of Internet client/server applications. You have learned about the common client programs found on the Internet and how they are structured. You have developed a simple telnet client, an e-mail program, and the Web fetcher program.
In Chapter 27, "Server Programs," you'll learn how to write simple server applications.