Java Technology Home Page

Advanced Search

Java Technology Home Page
Technologies
- J2EE
- J2SE
- J2ME
- Java Card
- Web Services
- Wireless
- XML
- Other
Downloads
- Early Access
Documentation
- APIs
- Tutorials
- Code Samples
- See All
Industry News
Developer Services
- Bug Database
- Forums
- Support
- See All
Java BluePrints
    Printable Page  Printable Page

Articles Index

Burrowing Through Firewalls

Most firewalls do not allow direct Internet Protocol (IP) traffic between the Internet and the internal network they are protecting. Java developers frequently ask how to make their applets communicate with a server program, running on the originating host, when a firewall separates the applets and the server. This article examines the various issues surrounding applet-server communications by using the WWW proxy server and HTTP protocol to burrow through firewalls.

Some Background

In the Java network security model, applets are allowed to make a network connection only to the server from which they were downloaded (the originating host). This prevents rogue applets from initiating unwanted network connections to arbitrary hosts. A connection back to the originating server machine can be in two forms:

  • A socket/datagram connection to a server program running on the originating host
  • An HTTP connection to a Common Gateway Interface (CGI) script, or to a server program running on the originating machine

Because it is not always possible to establish an IP connection through a firewall, several client-server programs that need to talk directly to each other, solve this problem by having proxy support built into them, and having a proxy (or SOCKS) server running on the firewall machine. This is not possible for applets. However, most organizations behind firewalls have a WWW proxy server running that allows people inside the organization to access the Web. Java knows about WWW proxy servers and allows URL connections to the originating host by forwarding them to the proxy server. This allows applets to invoke CGI programs (or servlets, if the Jeeves server is being run), pass them input data, and receive the output returned from these CGI programs. For an excellent discussion on reading from and writing to URL connections, see the online Java Tutorial on the JavaSoft web site. But the problem to solve is when the applet wants to talk to a standalone server program (daemon) that runs continously on the originating host (perhaps the service is to allow the clients to talk to each other in real time like in a collaboration tool).

The Application

A concept is best demonstrated by way of application. The application used to demonstrate the concepts presented here is a very simple chat server/applet. In this applet, when a user types in a line of text, it is echoed to all the users connected to the applet at the same time. The only other thing the applet can do is to set the user name. There is minimal error checking done, if any. This simple server/applet does not deal with error conditions inherently possible in HTTP connections like timeouts. The user interfaces and functionality for the applet are minimal. Here is the chat applet along with links to the source code for the client and the server.

A real chat application should include many more features, as for example, those included in the JDC Forum applet. These features include, creating multiple rooms, seeing a list of currently logged in members, sending private messages, and so on. Also, instead of HTTP mode connection (which is slow), where possible, socket mode connections are used.

Note: The JDC will present the complete source code for the Forum applet and discuss its features in a future article. Watch these pages for that.

The Concepts

For an applet to communicate with a server program using HTTP protocol, the server program has to pretend that it is an HTTP server, serving WWW documents. In other words, it is going to mimick an httpd program. The server creates a ServerSocket and starts accepting connections on a well known port. Whatever information needs to be sent by the client to the server is formatted as a URL message (using the GET method of HTTP) and a URLConnection is made. When that happens, the server process' accept call succeeds and a connection is established with the client (actually with the proxy server). The server can read whatever was sent by the client and write back to the client, followed by a close on the socket connection. This effectively completes one single transaction between the client and the server. If anything further needs to be sent or received, the process is repeated. But enough of the theoretical discussion; the next two sections present and discuss the code to demonstrate these concepts. To make the code snippets smaller for the purpose of this discussion, the error conditions are not checked.

The Server End

The first thing a server process needs to do when communicating in real time with clients is to create a server socket on a well known port number (user port numbers should be greater than 1024).

ServerSocket servSock = 
   new ServerSocket (portNum);

The server can then accept connections from clients. The server socket accept method blocks until a client has initiated a connection using the URL connection mechanism, described under "The Client End". After getting a client socket, the server then gets the corresponding input and output streams and reads the input sent by the client.

Socket sock = servSock.accept ();
DataInputStream din = new DataInputStream(
   new BufferedInputStream (
      sock.getInputStream()));
PrintStream out = new PrintStream(
   new BufferedOutputStream(
      sock.getOutputStream()), true);
String inStr = din.readLine(); 

Because the client is communicating with the server using the HTTP protocol, the server ensures that the valid HTTP protocol is used (to prevent other types of connections on the port). Also, since the GET method is being used, inStr should begin with a GET. Note that the client sends the string to the server in an encoded format because the GET method does not allow any spaces or special characters that are not allowed in a URL. The server then interprets what the client has sent, and replies on the same connection. The response is preceeded by standard HTTP reply headers, so that the proxy server on the client end knows that it is receiving valid HTTP messages, which it then passes on to the actual client.

static final String HTTP_PROTOCOL = " HTTP/1.0";
static final String HTTP_GET_CMD = "GET /";

if (!inStr.endsWith(HTTP_PROTOCOL) ||
    !inStr.startsWith (HTTP_GET_CMD)) {

   System.out.println ("Error in message");
   din.close ();
   out.close ();
   sock.close ();
}

// Strip out the "GET /" at the beginning and 
// " HTTP/1.0" at the end here in the code 
// to get the actual
inStr = inStr.substring (HTTP_GET_CMD.length (),
inputStr.length () - HTTP_PROTOCOL.length ());

// Decode the encoded string and deal with it
inStr = decode (inStr);

// Deal with the message and send a response 
// back, first sending the standard headers
out.println ("HTTP/1.0 200 Demo Server\r");
out.println ("\r");

// Print out the actual message
out.println ("Message from server to client");
out.close ();
sock.close ();

The Client End

The client first creates a base URL to the originating host at the port number on which the server is listening on. It then tags the message to the server at the end of the base URL, after encoding it so as not to allow spaces and other special characters.

String msgToServer =
   encode ("Message from client to server");
URL baseUrl = new URL ("http://" +
                getCodeBase ().getHost () +
		":" + portNum + "/");
URL u = new URL (baseUrl, msgToServer);

Up to this point, no connection has been made with the server; only the URL object has been created. The next step is to open the connection and get an input stream to read whatever the server is sending over the connection. All the data sent by the server on this connection is read in a while loop before closing the connection.

DataInputStream in = new DataInputStream 
   (new BufferedInputStream 
       (u.openConnection().getInputStream()));

String fromServer;
while ((fromServer = in.readLine()) != null) {

   // Deal with the message
}
in.close ();

That is all there is to it (well, at least the basics). In the real world, a lot of error checking needs to be done. The proxy server occasionally throws IO errors, such as "Document contains no data", or "Stream Closed" if it cannot make the connections. Occasionally there are timeout errors. All these error conditions need to be checked.

Simulating Full Duplex Connections

The preceding sections describe a simple application; when the client needs to send something to the server, it opens a URLConnection and sends the data as part of the URL string. The server responds on the same connection. But in a typical application, there needs to be a full duplex connection between the client and the server. This is especially important when the server needs to send a message to the client asynchronously. This can be simulated by using the same mechanism. In other words, the client initiates an HTTP connection to receive asynchronous messages, and indicates that as part of the message. The server can remember this connection and when there is something to send, it can use the connection and close it away. The client receiving the message on this connection simply opens another connection and waits for the next message. In the meantime, if the client wants to send a message to the server, it can open up another connection. The server can respond on the second connection, or the previous connection that it remembers, depending on the application. This is demonstrated in the source code for the simple chat applet and server.

A better alternative for providing a full-duplex connection is to use the Keep-Alive option, which is part of HTTP/1.1, but that is left as an exercise to the reader.

What About the POST method?

So far, the discussion has covered the HTTP GET method. The GET method has disadvantages, such as limits on the amount of data that can be sent in a single request and the need to encode the data so that it does not contain any special characters. The POST method may be more suitable for certain applications because it does not have the above limitations. This is how to send data to the server using the POST method:

URL u = new URL ("http://" +
           getCodeBase ().getHost () +
	   ":" + portNum);
URLConnection connection = u.openConnection;
connection.setDoOutput (true);
PrintStream ps = new PrintStream (
   connection.getOutputStream ());
ps.println ("Message 1");
ps.println ("Message 2");
ps.close ();

On the server side, after the call to accept succeeds, it reads all the HTTP headers. Following the HTTP headers is a blank line, followed by the data that was sent using the POST method. The first header line consists of the POST command (for this example, it will read as "POST / HTTP/1.0". One of the lines will contain the Content-length header field (an example is "Content-length: 86"). The number of bytes in the body of the message is specified on that line. The code in the server should look like this:

// Read the headers
String inStr = din.readLine ();
while (!inStr.equals ("")) {

   // if inStr has Content-length, read the 
   // length into a variable here
   inStr = din.readLine ();
}

// Read actual message, based 
// on content length

In terms of performance, a direct socket connection is faster than an equivalent HTTP connection, because of the overhead involved in HTTP connections. But using the HTTP protocol is the only reliable way of communicating between an applet and a server through a firewall.


[ This page was last updated Aug-09-2002 ]

Company Info | Licensing | Employment | Press | Contact |   XML
JavaOne | Java Community Process | Java Wear and Books | Content Feeds | Java Series Books

Java, J2EE, J2SE, J2ME, and all Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries.
 
 
Unless otherwise licensed, code in all
technical manuals herein (including articles,
FAQs, samples) is provided under this License.

Sun Microsystems, Inc.

Copyright © 1995-2003 Sun Microsystems, Inc.
All Rights Reserved. Terms of Use. Privacy Policy. Trademarks.