CS396N - Web Programming |
Spring 2002 |
Chapter 17 - Network Programming |
import java.net.*;
public class NetworkClient {
protected String host;
protected int port;
/** Register host and port. The connection won't
* actually be established until you call
* connect.
*/
public NetworkClient(String host, int port) {
this.host = host;
this.port = port;
}
/** Establishes the connection, then passes the socket
* to handleConnection.
*/
public void connect() {
try {
Socket
client = new Socket(host, port);
handleConnection(client);
} catch(UnknownHostException uhe) {
System.out.println("Unknown host:
" + host);
uhe.printStackTrace();
} catch(IOException ioe) {
System.out.println("IOException:
" + ioe);
ioe.printStackTrace();
}
}
/** This is the method you will override when
* making a network client for your task.
* The default version sends a single line
* ("Generic Network Client") to the server,
* reads one line of response, prints it,
then exits.
*/
protected void handleConnection(Socket
client)
throws IOException {
PrintWriter out
= SocketUtil.getWriter(client);
BufferedReader in
= SocketUtil.getReader(client);
out.println("Generic
Network Client");
System.out.println
("Generic Network Client:\n"
+
"Made connection to " +
host +
" and got '" + in.readLine()
+ "' in response");
client.close();
}
/** The hostname of the server we're contacting. */
public String getHost() {
return(host);
}
/** The port connection will be made on. */
public int getPort() {
return(port);
}
}
Part 2: SocketUtil.java
import java.net.*;
import java.io.*;
/** A shorthand way to create BufferedReaders and
* PrintWriters associated with a Socket.
*/
public class SocketUtil {
/** Make a BufferedReader to get incoming data. */
public static BufferedReader getReader(Socket
s)
throws IOException {
return(new BufferedReader(
new InputStreamReader(s.getInputStream())));
}
/** Make a PrintWriter to send outgoing data.
* This PrintWriter will automatically flush
stream
* when println is called.
*/
public static PrintWriter getWriter(Socket
s)
throws IOException {
// Second argument of true means autoflush.
return(new PrintWriter(s.getOutputStream(),
true));
}
}
Part 3: NetworkClientTest.java
/** Make simple connection to host and port specified. */
public class NetworkClientTest {
public static void main(String[] args) {
String host = "localhost";
int port = 8088;
if (args.length > 0) {
host = args[0];
}
if (args.length > 1) {
port = Integer.parseInt(args[1]);
}
NetworkClient nwClient
= new NetworkClient(host, port);
nwClient.connect();
}
}
Part 4: Running
Program
>java NetworkClientTest
ftp.netscape.com 21
Generic Network Client:
Made Connection to ftp.netscape.com and got
`220 ftp26 FTP server (UNIX(r)
System V Release 4.0)
ready.` in response
Example : Interactive Tokenizer
import java.util.StringTokenizer;
/** Prints the tokens resulting from treating the first
* command-line argument as the string to be tokenized
* and the second as the delimiter set.
*/
public class TokTest {
public static void main(String[] args) {
if (args.length == 2) {
String input = args[0], delimiters
= args[1];
StringTokenizer
tok =
new StringTokenizer(input, delimiters);
while (tok.hasMoreTokens())
{
System.out.println(tok.nextToken());
}
} else {
System.out.println
("Usage: java TokTest
string delimeters");
}
}
}
Output:
Example : A Client to Verify Email Addresses
Part 1: AddressVerifier.java
import java.net.*;
import java.io.*;
/** Given an e-mail address of the form user@host,
* connect to port 25 of the host and issue an
* 'expn' request for the user. Print the results.
*/
public class AddressVerifier extends NetworkClient
{
private String username;
public static void main(String[] args) {
if (args.length != 1) {
usage();
}
MailAddress address = new MailAddress(args[0]);
AddressVerifier verifier
= new AddressVerifier(address.getUsername(),
address.getHostname(), 25);
verifier.connect();
}
public AddressVerifier(String username, String hostname,
int port) {
super(hostname, port);
this.username = username;
}
/** NetworkClient, the parent class, automatically establishes
* the connection and then passes the Socket
to
* handleConnection. This method does all
the real work
* of talking to the mail server.
*/
// You can't use readLine, because it blocks. Blocking
I/O
// by readLine is only appropriate when you know how
many
// lines to read. Note that mail servers send a varying
// number of lines when you first connect or send no
line
// closing the connection (as HTTP servers do), yielding
// null for readLine. Also, we'll assume that 1000 bytes
// is more than enough to handle any server welcome
// message and the actual EXPN response.
protected void handleConnection(Socket
client) {
try {
PrintWriter out = SocketUtil.getWriter(client);
InputStream in = client.getInputStream();
byte[]
response = new byte[1000];
// Clear out mail server's welcome
message.
in.read(response);
out.println("EXPN " + username);
// Read the response to the EXPN
command.
int numBytes = in.read(response);
// The 0 means to use normal
ASCII encoding.
System.out.write(response, 0,
numBytes);
out.println("QUIT");
client.close();
} catch(IOException ioe) {
System.out.println("Couldn't
make connection: " + ioe);
}
}
/** If the wrong arguments, then warn user. */
public static void usage() {
System.out.println ("You must supply an email
address " +
"of the form 'username@hostname'.");
System.exit(-1);
}
}
Part 2: MailAddress.java
import java.util.*;
/** Takes a string of the form "user@host" and
* separates it into the "user" and "host" parts.
*/
public class MailAddress {
private String username, hostname;
public MailAddress(String emailAddress) {
StringTokenizer tokenizer
= new StringTokenizer(emailAddress,
"@");
this.username
= getArg(tokenizer);
this.hostname
= getArg(tokenizer);
}
private static String getArg(StringTokenizer tok) {
try { return(tok.nextToken()); }
catch (NoSuchElementException nsee) {
System.out.println("Illegal email
address");
System.exit(-1);
return(null);
}
}
public String getUsername() {
return(username);
}
public String getHostname() {
return(hostname);
}
}
Output:
Example : A Class to retrieve a Given URL
import java.util.*;
/** This parses the input to get a host,
port, and file, then
* passes these three values
to the UriRetriever class to
* grab the URL from the Web.
*/
public class UrlRetriever {
public static void main(String[]
args) {
checkUsage(args);
StringTokenizer tok
= new StringTokenizer(args[0]);
String protocol = tok.nextToken(":");
checkProtocol(protocol);
String host = tok.nextToken(":/");
String uri;
int port = 80;
try {
uri = tok.nextToken("");
if (uri.charAt(0)
== ':') {
tok = new StringTokenizer(uri);
port = Integer.parseInt(tok.nextToken(":/"));
uri = tok.nextToken("");
}
} catch(NoSuchElementException
nsee) {
uri = "/";
}
UriRetriever
uriClient = new UriRetriever(host, port, uri);
uriClient.connect();
}
/** Warn user if the URL was forgotten. */
private static void checkUsage(String[]
args) {
if (args.length !=
1) {
System.out.println("Usage:
UrlRetriever <URL>");
System.exit(-1);
}
}
/** Tell user that this can only handle HTTP. */
private static void checkProtocol(String
protocol) {
if (!protocol.equals("http"))
{
System.out.println("Don't
understand protocol " + protocol);
System.exit(-1);
}
}
}
Output:
Example : A Class to retrieve a Given URL using Java's URL Class
import java.net.*;
import java.io.*;
/** Read a remote file using the standard
URL class
* instead of connecting explicitly
to the HTTP server.
*/
public class UrlRetriever2 {
public static void main(String[]
args) {
checkUsage(args);
try {
URL url = new URL(args[0]);
BufferedReader
in = new BufferedReader(
new InputStreamReader(url.openStream()));
String
line;
while
((line = in.readLine()) != null) {
System.out.println("> " + line);
}
in.close();
} catch(MalformedURLException
mue) { // URL constructor
System.out.println(args[0] + "is an invalid URL: " + mue);
} catch(IOException
ioe) { // Stream constructors
System.out.println("IOException:
" + ioe);
}
}
private static void checkUsage(String[]
args) {
if (args.length !=
1) {
System.out.println("Usage:
UrlRetriever2 <URL>");
System.exit(-1);
}
}
}
Output:
Example : Using URL Methods
import java.net.*;
/** Read a URL from the command line, then
print
* the various components.
*/
public class UrlTest {
public static void main(String[]
args) {
if (args.length ==
1) {
try {
URL url = new URL(args[0]);
System.out.println
("URL: " + url.toExternalForm() + "\n" +
" File: " + url.getFile()
+ "\n" +
" Host: " + url.getHost()
+ "\n" +
" Port: " + url.getPort()
+ "\n" +
" Protocol: " + url.getProtocol()
+ "\n" +
" Reference: " + url.getRef());
} catch(MalformedURLException
mue) {
System.out.println("Bad URL.");
}
} else
System.out.println("Usage:
UrlTest <URL>");
}
}
Output:
Note: If Port is not explicitly stated
in the URL, then the standard port for the protocol is assumed and getPort
returns
-1
import java.net.*;
import java.io.*;
/** A starting point for network servers.
You'll need to
* override handleConnection,
but in many cases listen can
* remain unchanged. NetworkServer
uses SocketUtil to simplify
* the creation of the PrintWriter
and BufferedReader.
*/
public class NetworkServer
{
private int port, maxConnections;
/** Build a server on specified
port. It will continue to
* accept connections,
passing each to handleConnection until
* an explicit exit
command is sent (e.g., System.exit) or
* the maximum number
of connections is reached. Specify
* 0 for maxConnections
if you want the server to run
* indefinitely.
*/
public NetworkServer(int port, int
maxConnections) {
setPort(port);
setMaxConnections(maxConnections);
}
/** Monitor a port for connections.
Each time one is
* established, pass
resulting Socket to handleConnection.
*/
public void listen()
{
int i=0;
try {
ServerSocket
listener = new ServerSocket(port);
Socket
server;
while((i++
< maxConnections) || (maxConnections == 0)) {
server = listener.accept();
handleConnection(server);
}
} catch (IOException
ioe) {
System.out.println("IOException:
" + ioe);
ioe.printStackTrace();
}
}
/** This is the method that provides
the behavior to the
* server, since it
determines what is done with the
* resulting socket.
<B>Override this method in servers
* you write.</B>
* <P>
* This generic version
simply reports the host that made
* the connection, shows
the first line the client sent,
* and sends a single
line in response.
*/
protected void handleConnection(Socket
server)
throws
IOException{
BufferedReader in =
SocketUtil.getReader(server);
PrintWriter out = SocketUtil.getWriter(server);
System.out.println
("Generic
Network Server: got connection from " +
server.getInetAddress().getHostName()
+ "\n" +
"with
first line '" + in.readLine()
+ "'");
out.println("Generic
Network Server");
server.close();
}
/** Gets the max connections server
will handle before
* exiting. A value
of 0 indicates that server should run
* until explicitly
killed.
*/
public int getMaxConnections() {
return(maxConnections);
}
/** Sets max connections. A value
of 0 indicates that server
* should run indefinitely
(until explicitly killed).
*/
public void setMaxConnections(int
maxConnections) {
this.maxConnections
= maxConnections;
}
/** Gets port on which server is listening. */
public int getPort() {
return(port);
}
/** Sets port. <B>You can only
do before "connect" is
* called.</B> That
usually happens in the constructor.
*/
protected void setPort(int port)
{
this.port = port;
}
}
Part 2: NetworkServerTest.java
public class NetworkServerTest {
public static void main(String[] args) {
int port = 8088;
if (args.length > 0) {
port = Integer.parseInt(args[0]);
}
NetworkServer nwServer
= new NetworkServer(port, 1);
nwServer.listen();
}
}
2nd - Start client on host system
port 6001
Example : EchoServer
import java.net.*;
import java.io.*;
import java.util.StringTokenizer;
/** A simple HTTP server that generates
a Web page showing all
* of the data that it received
from the Web client (usually
* a browser). To use this
server, start it on the system of
* your choice, supplying a
port number if you want something
* other than port 8088. Call
this system server.com. Next,
* start a Web browser on the
same or a different system, and
* connect to http://server.com:8088/whatever.
The resultant
* Web page will show the data
that your browser sent. For
* debugging in servlet or
CGI programming, specify
* http://server.com:8088/whatever
as the ACTION of your HTML
* form. You can send GET or
POST data; either way, the
* resultant page will show
what your browser sent.
*/
public class EchoServer extends
NetworkServer {
protected int maxRequestLines =
50;
protected String serverName = "EchoServer";
/** Supply a port number as a command-line
* argument. Otherwise,
use port 8088.
*/
public static void main(String[]
args) {
int port = 8088;
if (args.length > 0)
{
try {
port = Integer.parseInt(args[0]);
} catch(NumberFormatException
nfe) {}
}
new EchoServer(port,
0);
}
public EchoServer(int port, int
maxConnections) {
super(port, maxConnections);
listen();
}
/** Overrides the NetworkServer
handleConnection method to
* read each line of
data received, save it into an array
* of strings, then
send it back embedded inside a PRE
* element in an HTML
page.
*/
public void handleConnection(Socket
server)
throws
IOException{
System.out.println
(serverName + ": got connection from " +
server.getInetAddress().getHostName());
BufferedReader in =
SocketUtil.getReader(server);
PrintWriter out = SocketUtil.getWriter(server);
String[] inputLines
= new String[maxRequestLines];
int i;
for (i=0; i<maxRequestLines;
i++) {
inputLines[i]
= in.readLine();
if (inputLines[i]
== null) // Client closed connection.
break;
if (inputLines[i].length()
== 0) { // Blank line.
if (usingPost(inputLines)) {
readPostData(inputLines, i, in);
i = i + 2;
}
break;
}
}
printHeader(out);
for (int j=0; j<i;
j++) {
out.println(inputLines[j]);
}
printTrailer(out);
server.close();
}
// Send standard HTTP response and
top of a standard Web page.
// Use HTTP 1.0 for compatibility
with all clients.
private void printHeader(PrintWriter
out) {
out.println
("HTTP/1.0
200 OK\r\n" +
"Server:
" + serverName + "\r\n" +
"Content-Type:
text/html\r\n" +
"\r\n"
+
"<!DOCTYPE
HTML PUBLIC " +
"\"-//W3C//DTD HTML 4.0 Transitional//EN\">\n" +
"<HTML>\n"
+
"<HEAD>\n"
+
"
<TITLE>" + serverName + " Results</TITLE>\n" +
"</HEAD>\n"
+
"\n"
+
"<BODY
BGCOLOR=\"#FDF5E6\">\n" +
"<H1
ALIGN=\"CENTER\">" + serverName +
" Results</H1>\n" +
"Here
is the request line and request headers\n" +
"sent
by your browser:\n" +
"<PRE>");
}
// Print bottom of a standard Web page.
private void printTrailer(PrintWriter
out) {
out.println
("</PRE>\n"
+
"</BODY>\n"
+
"</HTML>\n");
}
// Normal Web page requests use
GET, so this server can simply
// read a line at a time. However,
HTML forms can also use
// POST, in which case we have
to determine the number of POST
// bytes that are sent so we know
how much extra data to read
// after the standard HTTP headers.
private boolean usingPost(String[]
inputs) {
return(inputs[0].toUpperCase().startsWith("POST"));
}
private void readPostData(String[]
inputs, int i,
BufferedReader in)
throws
IOException {
int contentLength =
contentLength(inputs);
char[] postData = new
char[contentLength];
in.read(postData, 0,
contentLength);
inputs[++i] = new String(postData,
0, contentLength);
}
// Given a line that starts with
Content-Length,
// this returns the integer value
specified.
private int contentLength(String[]
inputs) {
String input;
for (int i=0; i<inputs.length;
i++) {
if (inputs[i].length()
== 0)
break;
input =
inputs[i].toUpperCase();
if (input.startsWith("CONTENT-LENGTH"))
return(getLength(input));
}
return(0);
}
private int getLength(String length)
{
StringTokenizer tok
= new StringTokenizer(length);
tok.nextToken();
return(Integer.parseInt(tok.nextToken()));
}
}
2nd - Access server with Browser
Example : EchoServer with Multithreading
import java.net.*;
import java.io.*;
/** A multithreaded variation of EchoServer.
*/
public class ThreadedEchoServer extends
EchoServer
implements Runnable {
public static void main(String[]
args) {
int port = 8088;
if (args.length > 0)
{
try {
port = Integer.parseInt(args[0]);
} catch(NumberFormatException
nfe) {}
}
ThreadedEchoServer
echoServer =
new ThreadedEchoServer(port,
0);
echoServer.serverName
= "Threaded EchoServer";
}
public ThreadedEchoServer(int port,
int connections) {
super(port, connections);
}
/** The new version of handleConnection
starts a thread. This
* new thread will call
back to the <I>old</I> version of
* handleConnection,
resulting in the same server behavior
* in a multithreaded
version. The thread stores the Socket
* instance since run
doesn't take any arguments, and since
* storing the socket
in an instance variable risks having
* it overwritten if
the next thread starts before the run
* method gets a chance
to copy the socket reference.
*/
public void handleConnection(Socket
server) {
Connection
connectionThread = new Connection(this, server);
connectionThread.start();
}
public void run() {
Connection
currentThread =
(Connection)Thread.currentThread();
try {
super.handleConnection(currentThread.getSocket());
} catch(IOException
ioe) {
System.out.println("IOException:
" + ioe);
ioe.printStackTrace();
}
}
}
/** This is just a Thread with a field
to store a Socket object.
* Used as a thread-safe means
to pass the Socket from
* handleConnection to run.
*/
class Connection extends Thread {
private Socket serverSocket;
public Connection(Runnable serverObject,
Socket serverSocket)
{
super(serverObject);
this.serverSocket =
serverSocket;
}
public Socket getSocket() {
return serverSocket;
}
}
Output - System providing 2 threads
to 2 clients