Translate
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 Test Document
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment