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: 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:<jamie@jaworski.com>
250 OK
>>>RCPT TO:<jamie@jaworski-pc.hctg.saic.com>
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:<jamie@jaworski.com>
RCPT TO:<jamie@jaworski-pc.hctg.saic.com>
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.