|
Second Summer 2002 |
We discuss Java networking and look at what it takes to develop a browser and a server.
First, we are going to write a network client.
/*
* Copyright (c) 2000 David Flanagan. All rights reserved.
* This code is from the book Java Examples in a Nutshell, 2nd Edition.
* It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
* You may study, use, and modify it for any non-commercial purpose.
* You may distribute it non-commercially as long as you retain this notice.
* For a commercial use license, or to purchase the book (recommended),
* visit http://www.davidflanagan.com/javaexamples2.
*/
import java.io.*;
import java.net.*;
/**
* This program connects to a server at a specified host and port.
* It reads text from the console and sends it to the server.
* It reads text from the server and sends it to the console.
**/
public class GenericClient {
public static void main(String[] args) throws IOException {
try {
// Check the number of arguments
if (args.length != 2)
throw new IllegalArgumentException("Wrong number of args");
// Parse the host and port specifications
String host = args[0];
int port = Integer.parseInt(args[1]);
// Connect to the specified host and port
Socket s = new Socket(host, port);
// Set up streams for reading from and writing to the server.
// The from_server stream is final for use in the inner class below
final Reader from_server=new InputStreamReader(s.getInputStream());
PrintWriter to_server = new PrintWriter(s.getOutputStream());
// Set up streams for reading from and writing to the console
// The to_user stream is final for use in the anonymous class below
BufferedReader from_user =
new BufferedReader(new InputStreamReader(System.in));
// Pass true for auto-flush on println()
final PrintWriter to_user = new PrintWriter(System.out, true);
// Tell the user that we've connected
to_user.println("Connected to " + s.getInetAddress() +
":" + s.getPort());
// Create a thread that gets output from the server and displays
// it to the user. We use a separate thread for this so that we
// can receive asynchronous output
Thread t = new Thread() {
public void run() {
char[] buffer = new char[1024];
int chars_read;
try {
// Read characters until the stream closes
while((chars_read = from_server.read(buffer)) != -1) {
// Loop through the array of characters, and
// print them out, converting all \n characters
// to the local platform's line terminator.
// This could be more efficient, but it is probably
// faster than the network is, which is good enough
for(int i = 0; i < chars_read; i++) {
if (buffer[i] == '\n') to_user.println();
else to_user.print(buffer[i]);
}
to_user.flush();
}
}
catch (IOException e) { to_user.println(e); }
// When the server closes the connection, the loop above
// will end. Tell the user what happened, and call
// System.exit(), causing the main thread to exit along
// with this one.
to_user.println("Connection closed by server.");
System.exit(0);
}
};
// We set the priority of the server-to-user thread above to be
// one level higher than the main thread. We shouldn't have to do
// this, but on some operating systems, output sent to the console
// doesn't appear when a thread at the same priority level is
// blocked waiting for input from the console.
t.setPriority(Thread.currentThread().getPriority() + 1);
// Now start the server-to-user thread
t.start();
// In parallel, read the user's input and pass it on to the server.
String line;
while((line = from_user.readLine()) != null) {
to_server.print(line + "\n");
to_server.flush();
}
// If the user types a Ctrl-D (Unix) or Ctrl-Z (Windows) to end
// their input, we'll get an EOF, and the loop above will exit.
// When this happens, we stop the server-to-user thread and close
// the socket.
s.close();
to_user.println("Connection closed by client.");
System.exit(0);
}
// If anything goes wrong, print an error message
catch (Exception e) {
System.err.println(e);
System.err.println("Usage: java GenericClient <hostname> <port>");
}
}
}
Let's see how it works:
burrowww.cs.indiana.edu% pwd
/nfs/paca/home/user1/dgerman/browser/server
burrowww.cs.indiana.edu% ls -ld *
-rw-r--r-- 1 dgerman faculty 4749 Aug 6 23:43 GenericClient.java
burrowww.cs.indiana.edu% javac GenericClient.java
burrowww.cs.indiana.edu% java GenericClient burrowww.cs.indiana.edu 10200
Connected to burrowww.cs.indiana.edu/129.79.245.98:10200
GET /index.html
<html>
<head><title>Homework One Page</title></head>
<body bgcolor=white>
<center>
<img src="http://www.cs.indiana.edu/images/academics.jpg">
</center> <p>
SAMPLE HOMEWORK ONE PAGE:
Your first homework assignment is asking you to
<ul>
<li> install Apache,
and to
<li> post a picture of yourself on the website,
</ul> as I include above.
Please also provide links to a couple of other documents that
describe your interests, and add more information to your page.
(For example my current interests are:
<a href="http://www.cs.indiana.edu/classes/a201">A201</a> and
<a href="http://www.cs.indiana.edu/classes/a348">A348</a>).
Later you will provide links to your homework assignments from
this page. <p>
</body>
</html>
Connection closed by server.
burrowww.cs.indiana.edu%
This is our (own) telnet. Now for the server part. We start from this:
import java.io.*;
import java.net.*;
public class HTTPServer {
public static void main(String[] args) throws Exception {
final int httpd = Integer.parseInt(args[0]); // our port
ServerSocket ssock = new ServerSocket(httpd);
System.out.println("Have opened port " + httpd + " locally.");
Socket sock = ssock.accept();
System.out.println("Client has made socket connection.");
OneConnection client = new OneConnection(sock);
String s = client.getRequest();
}
}
class OneConnection {
Socket sock;
BufferedReader in = null;
DataOutputStream out = null;
OneConnection(Socket sock) throws Exception {
this.sock = sock;
in =
new BufferedReader
(new InputStreamReader
(sock.getInputStream()));
out = new DataOutputStream(sock.getOutputStream());
}
String getRequest() throws Exception {
String s = null;
while ((s = in.readLine()) != null) {
System.out.println("got: " + s);
}
return s;
}
}
Let's see how we can use this.
We can start the server on tucotuco and connect from burrowww.
Here's a simultaneous set of screens, first the server side:
And the client side:tucotuco.cs.indiana.edu% javac HTT*.java tucotuco.cs.indiana.edu% java HTTPServer 12345 Have opened port 12345 locally. Client has made socket connection. got: bye got: I am here got: klsjsajdasldkja got: got: got: asdkjasjdaslkd tucotuco.cs.indiana.edu%
So far, so good.burrowww.cs.indiana.edu% java GenericClient tucotuco.cs.indiana.edu 12345 Connected to tucotuco.cs.indiana.edu/129.79.245.110:12345 bye I am here klsjsajdasldkja asdkjasjdaslkd ^Cburrowww.cs.indiana.edu%
We, of course, can also make a connection using a web browser.
Of, course, we don't show the browser.tucotuco.cs.indiana.edu% pwd /nfs/paca/home/user1/dgerman/browser/server tucotuco.cs.indiana.edu% java HTTPServer 12345 Have opened port 12345 locally. Client has made socket connection. got: GET / HTTP/1.0 got: Connection: Keep-Alive got: User-Agent: Mozilla/4.79 [en] (X11; U; SunOS 5.8 sun4u) got: Host: tucotuco.cs.indiana.edu:12345 got: Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */* got: Accept-Encoding: gzip got: Accept-Language: en got: Accept-Charset: iso-8859-1,*,utf-8 got:
But you can try, as well, and in the process see the HTTP headers.
Let's teach our server a little HTTP:
import java.io.*;
import java.net.*;
public class HTTPServer {
public static void main(String[] args) throws Exception {
final int httpd = Integer.parseInt(args[0]); // our port
ServerSocket ssock = new ServerSocket(httpd);
System.out.println("Have opened port " + httpd + " locally.");
Socket sock = ssock.accept();
System.out.println("Client has made socket connection.");
OneConnection_A client = new OneConnection_A(sock);
String s = client.getRequest();
}
}
class OneConnection {
Socket sock;
BufferedReader in = null;
DataOutputStream out = null;
OneConnection(Socket sock) throws Exception {
this.sock = sock;
in =
new BufferedReader
(new InputStreamReader
(sock.getInputStream()));
out = new DataOutputStream(sock.getOutputStream());
}
String getRequest() throws Exception {
String s = null;
while ((s = in.readLine()) != null) {
System.out.println("got: " + s);
}
return s;
}
}
class OneConnection_A extends OneConnection {
OneConnection_A (Socket sock) throws Exception {
super(sock);
}
String getRequest() throws Exception {
String s = null;
while ((s = in.readLine()) != null) {
System.out.println("got: " + s);
if (s.indexOf("GET") > -1) {
out.writeBytes("HTTP-1.0 200 OK\r\n");
s = s.substring(4);
int i = s.indexOf(" ");
System.out.println("file: " + s.substring(0, i));
return s.substring(0, i);
}
}
return null;
}
}
Here's what happens if you start it and call with
from Netscape (or some other browser):http://tucotuco.cs.indiana.edu:12345
Now we're getting so much closer:tucotuco.cs.indiana.edu% pwd /nfs/paca/home/user1/dgerman/browser/server tucotuco.cs.indiana.edu% ls -ld * -rw-r--r-- 1 dgerman faculty 4749 Aug 6 23:43 GenericClient.java -rw-r--r-- 1 dgerman faculty 1477 Aug 7 00:36 HTTPServer.java tucotuco.cs.indiana.edu% javac *Server*.java tucotuco.cs.indiana.edu% java HTTPServer 12345 Have opened port 12345 locally. Client has made socket connection. got: GET / HTTP/1.0 file: / tucotuco.cs.indiana.edu%
import java.io.*;
import java.net.*;
public class HTTPServer {
public static void main(String[] args) throws Exception {
final int httpd = Integer.parseInt(args[0]); // our port
ServerSocket ssock = new ServerSocket(httpd);
System.out.println("Have opened port " + httpd + " locally.");
Socket sock = ssock.accept();
System.out.println("Client has made socket connection.");
OneConnection_B client = new OneConnection_B(sock);
String s = client.getRequest();
client.sendFile(s);
}
}
class OneConnection {
Socket sock;
BufferedReader in = null;
DataOutputStream out = null;
OneConnection(Socket sock) throws Exception {
this.sock = sock;
in =
new BufferedReader
(new InputStreamReader
(sock.getInputStream()));
out = new DataOutputStream(sock.getOutputStream());
}
String getRequest() throws Exception {
String s = null;
while ((s = in.readLine()) != null) {
System.out.println("got: " + s);
}
return s;
}
}
class OneConnection_A extends OneConnection {
OneConnection_A (Socket sock) throws Exception {
super(sock);
}
String getRequest() throws Exception {
String s = null;
while ((s = in.readLine()) != null) {
System.out.println("got: " + s);
if (s.indexOf("GET") > -1) {
out.writeBytes("HTTP-1.0 200 OK\r\n");
s = s.substring(4);
int i = s.indexOf(" ");
System.out.println("file: " + s.substring(0, i));
return s.substring(0, i);
}
}
return null;
}
}
class OneConnection_B extends OneConnection_A {
OneConnection_B(Socket sock) throws Exception {
super(sock);
}
void sendFile(String fname) throws Exception {
String where = "htdocs" + fname;
if (where.indexOf("..") > -1)
throw new SecurityException("No access to parent dirs");
System.out.println("Looking for " + where);
File f = new File(where);
DataInputStream din = new DataInputStream(new FileInputStream(f));
int len = (int) f.length();
byte[] buf = new byte[len];
din.readFully(buf);
out.writeBytes("Content-Length: " + len + "\r\n");
out.writeBytes("Content-type: text/html\r\n\r\n");
out.write(buf);
out.flush();
out.close();
}
}
Assume now you call
using Netscape. Then the result is as follows.http://tucotuco.cs.indiana.edu:12345/one.html
First, the server reports all:
tucotuco.cs.indiana.edu% javac HTTPServer.java
tucotuco.cs.indiana.edu% du -a htdocs
1 htdocs/one.html
2 htdocs
tucotuco.cs.indiana.edu% cat htdocs/one.html
This is a very
simple file,
really...
:-)
tucotuco.cs.indiana.edu% java HTTPServer 12345
Have opened port 12345 locally.
Client has made socket connection.
got: GET /one.html HTTP/1.0
file: /one.html
Looking for htdocs/one.html
tucotuco.cs.indiana.edu%
Then, the file is shown in the browser.
(One can also verify that with our GenericClient).
Last improvement we need to make: turn the server in a multithreaded server.
import java.io.*;
import java.net.*;
public class HTTPServer {
public static void main(String[] args) throws Exception {
final int httpd = Integer.parseInt(args[0]); // our port
ServerSocket ssock = new ServerSocket(httpd);
while (true) {
Socket sock = ssock.accept();
System.out.println("** Client has made socket connection.");
OneConnection_C client = new OneConnection_C(sock);
new Thread(client).start();
}
}
}
class OneConnection {
Socket sock;
BufferedReader in = null;
DataOutputStream out = null;
OneConnection(Socket sock) throws Exception {
this.sock = sock;
in =
new BufferedReader
(new InputStreamReader
(sock.getInputStream()));
out = new DataOutputStream(sock.getOutputStream());
}
String getRequest() throws Exception {
String s = null;
while ((s = in.readLine()) != null) {
System.out.println("got: " + s);
}
return s;
}
}
class OneConnection_A extends OneConnection {
OneConnection_A (Socket sock) throws Exception {
super(sock);
}
String getRequest() throws Exception {
String s = null;
while ((s = in.readLine()) != null) {
System.out.println("got: " + s);
if (s.indexOf("GET") > -1) {
out.writeBytes("HTTP-1.0 200 OK\r\n");
s = s.substring(4);
int i = s.indexOf(" ");
System.out.println("file: " + s.substring(0, i));
return s.substring(0, i);
}
}
return null;
}
}
class OneConnection_B extends OneConnection_A {
OneConnection_B(Socket sock) throws Exception {
super(sock);
}
void sendFile(String fname) throws Exception {
String where = "htdocs" + fname;
if (where.indexOf("..") > -1)
throw new SecurityException("No access to parent dirs");
System.out.println("Looking for " + where);
File f = new File(where);
DataInputStream din = new DataInputStream(new FileInputStream(f));
int len = (int) f.length();
byte[] buf = new byte[len];
din.readFully(buf);
out.writeBytes("Content-Length: " + len + "\r\n");
out.writeBytes("Content-type: text/html\r\n\r\n");
out.write(buf);
out.flush();
out.close();
}
}
class OneConnection_C extends OneConnection_B implements Runnable {
OneConnection_C(Socket sock) throws Exception {
super(sock);
}
public void run() {
try {
String filename = getRequest();
sendFile(filename);
} catch (Exception e) {
System.out.println("Exception: " + e);
}
}
}
This way it can receive and service more than one call.
The output above was generated by calling the server three times:tucotuco.cs.indiana.edu% java HTTPServer 12345 ** Client has made socket connection. got: GET /one.html HTTP/1.0 file: /one.html Looking for htdocs/one.html ** Client has made socket connection. got: GET /two.html HTTP/1.0 file: /two.html Looking for htdocs/two.html Exception: java.io.FileNotFoundException: htdocs/two.html (No such file or directory) ** Client has made socket connection. got: GET /one.html HTTP/1.0 file: /one.html Looking for htdocs/one.html ^Ctucotuco.cs.indiana.edu%
One of the files is missing, hence the exception.http://tucotuco.cs.indiana.edu:12345/one.html http://tucotuco.cs.indiana.edu:12345/two.html http://tucotuco.cs.indiana.edu:12345/one.html
I have two more references for I/O and networking.