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.
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.
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.
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 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 <CRLF>.<CRLF>");
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: [email protected]
To: [email protected]
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:<[email protected]>
250 OK
>>>RCPT TO:<[email protected]>
250 OK
>>>DATA
354 Start mail input; end with <CRLF>.<CRLF>
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:<[email protected]>
RCPT TO:<[email protected]>
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 <CRLF>.<CRLF> 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.
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 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 =
"<TITLE>Not Found</TITLE><H1>Error 404 - File Not Found</H1>";
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.<HTML>
<HEAD><TITLE>Test Document</TITLE></HEAD>
<BODY>
<H1>This is a test.</H1>
</BODY>
</HTML>
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.
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.