| ★ wanayoo — archive 1999 http://developer.iplanet.com/viewsource/kadel_ssjava/kadel_ssjava_p.html | Nouvelle recherche | Portail wanayoo |
![]() |
![]() |
![]() |
downloads | ||||||||||||||||
| technologies | |||||||||||||||||||
| support |
![]() |
![]() |
|
Server-Side Java: Extending Web Servers Through Java Part 1: Client-Server Programming With Java Applications By Rich Kadel
Send comments and questions about this article to View Source. Java has matured to the point where it should now be considered for serious application development. To be honest, I probably wouldn't have said that a year ago, about the time I started trying some serious things. But Java and I have matured together, at about the same pace. Now that I do serious things with Java, I have less of a fear that Java might not do what I want it to do. Don't get me wrong. I know there's still lots of room for improvement. But I know where the boundaries are, I know how to avoid stepping over them, and I constantly see those boundaries expanding with each new Java implementation. Java is actually quite powerful, and it has a number of advantages over other languages that, in my opinion, make Java a better choice for almost all programming tasks:
I have written client/server applications in Java, C++, C, and other languages for more than 10 years now. I have to say that, when I started my first Java client/server application, I was stunned at how easy it was to develop; it's been even easier to maintain and extend. This is due, primarily, to the fundamental features of the Java language itself, in that Java is object-oriented, multithreaded, and network-aware. With the data streaming interfaces we have now in Java 1.0.2, and the object serialization we will have in future Java 1.1 implementations, Java is an excellent choice for developing client-server applications. Java is often thought of as the client-side language for developing "thin-client" applications. But if Java is also used to develop the server, the same objects, and therefore the same source code and algorithms, can be used on both sides of the client/server connection. I'll spend the rest of the article going into each of these advantages in detail. Developing server-side software for the Web There used to be few choices when it came to developing for the Web. HTML was about it at first. Then the Common Gateway Interface (CGI) came along, but the language choices were still usually limited to PERL or C, for lack of examples or tools in other languages. Then came the server application programming interfaces, or APIs, such as the Netscape Server Application Programming Interface (NSAPI), and more client-side (browser-side) programming choices including Java and JavaScript as well as server-side JavaScript. Now the choices are much broader, and they include Java on both the client and the server. This is the first of a two-part article on developing server-side programs in Java. If you're not sold on the idea, read on. I will first talk about why Java is an excellent choice for server-side programming. Then I'll get into some of the nuts-and-bolts of Java development on the server, using Java applications. In part two, I'll dive head-first into Netscape Server Applets (Web server-embedded Java programs) and LiveConnect for the server, which is an important part of the new features of Netscape Enterprise Server 3.0, now available in beta. This technology is very powerful, even though it's still very new, and it allows you to actually extend the server using Java applets that are web server-aware. Difference between Java Applets and Java Applications Java first gained its wide-spread popularity from several simple, yet interesting, Java applets running under Netscape Navigator 2.0. These examples showed users that Web pages could actually be very dynamic, and even interactive, using Java. But most of these initial applets demonstrated little more than spinning heads, moving text, and 3D molecules. Because of the strict security constraints placed on Java applets, these examples were cute, but not very useful. (This is changing with the release of Java 1.1 and Netscape Communicator; see the sidebar "Object Signing to the Rescue.") I'm sure somebody is going to disagree with me here, but from my point of view, the typical Java applet has a lot of flash, but very little function. Without the ability to interact with its environment - for example, by saving a new file, or communicating information with another program - a Java applet puts the end-user in a very passive, "Look, but don't touch" role. Most popular software applications - word processors, spread sheets, databases - are centered on the "data" and how the user manipulates the data. Stand-alone Java applets have built-in functions to download images, sound files, and even some static data. But for the sake of security (an important, albeit suffocating cause) Java can't, for example, drop that file into, say, a Microsoft Word document (except through some really basic, unformatted cut-and-paste), and it can't read an image you created in Corel Draw 7.
I'll be the first to admit that my first impressions of Java were not very positive. Like lots of people, I thought that Java was limited to these restricted applets. (Boy, was I missing the point!) Then I found out how to write Java applications. Java applications (as I use the term) are Java programs that run outside of a browser. They have the same basic capabilities as a C or C++ program, but run inside a stand-alone virtual machine (VM), which makes them work with almost any operating system. (Applets run in a VM that is embedded in the browser.) A Java application may or may not have a window or GUI. If it does have a GUI, it creates its own window (or windows) and behave just like all other windows on your desktop, whether under Windows, Macintosh, or UNIX Motif. A Java application can also, of course, run on your UNIX, NT, or other server. The great thing about these Java applications is that there are no Java-enforced security constraints! I could actually write an application that would wipe my entire hard disk, if I wanted to! (Well, I probably wouldn't want to, but hey, now there were possibilities.) What I did want to do was to write Java applications that could, for example, write to and read from the server's hard disk. With this kind of capability, I could use Java to develop simple (or not-so-simple) database servers and proxies. By this time, I was hooked. Java was all the buzz, and I was experiencing the advantages of Java as a language and development platform daily, if not hourly. (I outlined some of these advantages at the beginning of this article; if you wan't more detail, you can find it at JavaSoft's web site.) Java is an Excellent Choice for Both Clients and Servers You could develop a server in any language, and it would be transparent to the client, as long as the server honored the specified protocol. However, Java has become my first choice for many reasons. One of the biggest advantages is that you can use the same or similar classes on both the client and the server side for reading and writing the "objects" related to your business problem. Even at the lowest level, Java's Data classes guarantee that the numbers and strings you send will be translated correctly even among heterogeneous platforms. I already referred to the tremendous programming benefits of Java for applications, but the portability of a server written in Java is a strong selling point, worth a second look. My company, DTAI, developed the Interactive Org Chart as a client/server product. Our original version, developed for Netscape AppFoundry, included a very simple, hand-coded database and a tailored protocol. We had to be able to distribute our product on several client and server platforms. That meant that we had to produce servers for both Windows NT and UNIX. Most programmers dread the thought of writing a C or C++ application that has to be ported to two or more platforms, each with completely different programming environments, and system interfaces. But with Java, we wrote each server only once and it worked perfectly on both UNIX and NT. Even if you think you have no multiplatform server requirements today, take a moment and try to recall the type of hardware and operating system you or your company were using 5 or 10 years ago. Did it change? I would venture to say that almost 100 percent of you would have to answer "Yes." And chances are that the system you develop for in the next 5 or 10 years will be different from the one you use today. Taking Advantage of Java's Built-in Network and Data Classes Another of Java's strengths as a programming environment is that it is very network-aware. And, in fact, despite all of the other security restrictions on applets, an applet can open a TCP/IP socket connection, as long as it is only to the host from which the applet was downloaded. This is an important point. When a Web page containing an applet is downloaded to a browser, that applet is not allowed to open a socket connection to the local system (where the browser is running). Nor is it allowed to connect to other systems (for example, computers inside the client's firewall). Therefore, even though it can open a connection back to the Web server computer, it will not be able to extract any potentially private data from the client to send back to the server. Through the socket, the client can both send and receive data. This permits the client to use the server as a proxy to the server's local resources, and even to other network resources accessible by the server. Some Java developers have even implemented a virtual file system for applets. With such systems, a user can select, open, and save files from an applet, but the data is actually read from and written to the server's local file system, and the data is transferred between the client and the server through sockets. There are several Java classes that make it easy to set up network connections, using TCP/IP, and socket I/O. Java also has a powerful I/O stream model that allows you to combine I/O stream classes together - with sockets, files, or other stream targets - to meet any of several possible configurations. It's easy to use Java to open a connection to any kind of server program on another computer and set up buffered data streams for reading and writing information between the two programs. The following code shows just how easy that is: Socket socket = new Socket( "some.host.com", 80 );
DataInputStream instream =
new DataInputStream(
new BufferedInputStream(
socket.getInputStream() ) );
DataOutputStream outstream =
new DataOutputStream(
new BufferedOutputStream(
socket.getOutputStream() ) );
The Socket class only requires two pieces of information - passed to the "constructor" function above - in order to make its connection: the target computer's host name and the TCP/IP port number. Unless the constructor throws an exception (which may happen if, for instance, the program on the other end doesn't respond), the first line in the above example will cause a solid connection to be made between this Java process and another process, possibly on another machine, anywhere on the network. The example just shown calls only two methods on the Socket object, getInputStream() and getOutputStream(). I could use these stream objects, as is, to send data to the program on the other end of the socket. But these generic input and output streams are unbuffered. This means that every time I write even the smallest piece of data to the stream, an entire TCP/IP packet will be created and sent across the socket. They're also byte-oriented, which means I'd have to convert all of my data into pure bytes, send it across, and convert it back on the other end. Keep in mind that a byte-oriented protocol is called for in some instances, but for most of my applications, I think it's simpler to think of sending data in a stream. That is, if I have a Java object "Car" in my program, when I send it across a stream, I want to send a "Car", not just a bunch of bytes. I can enable buffering by wrapping a BufferedInputStream or BufferedOutputStream around my simple streams. Then I can wrap the DataInputStream and DataOutputStream classes around the buffered streams. The "DataXxxStream" classes offer me an API that is much broader. Instead of requiring me to convert my integers into bytes, I can simply call outstream.writeInt() with my integer, and DataOutputStream converts that Java int into the bytes. Then on the other end, another Java program can use the DataInputStream method instream.readInt(), which returns the value as an int. Simple! And there's something even more important here. Hidden in this elegant API is the fact that the Java library has implemented a standard method for reading and writing primitive data types so that they are consistent between the two processes. Strings are written and read using the functions writeUTF() and readUTF(). (UTF stands for Unicode Transfer Format and is a standard, compact format for transferring the Unicode strings that Java uses). And all numeric data is written in a consistent byte order (called network byte order) so that Java programs running on any type of machine can communicate according to a universally adopted low-level protocol. Therefore, using Java on both the client and the server will guarantee that these primitive data objects are converted and interpreted correctly, on both ends. The next implementation of Java, version 1.1, includes an extremely attractive new feature for client-server implementations: Object Serialization. This feature will seamlessly encode your Java objects into a stream of bytes for transmission across the socket (or to a file), and then decode and reconstruct the objects as they are received. Object Serialization will greatly simplify Java communication patterns. Design or Choose a Protocol When most of us write more than the simplest of object-oriented or even structured programs, we don't just create a whole bunch of ints, floats, doubles, and strings, and toss them around. We create classes or structures and collect the relevant data in encapsulated objects. Similarly, when you pass data from one program to another, you have to design a protocol that is usually a little more sophisticated than something that says "pass a bunch of numbers to the server." A higher-level protocol defines the standard by which the client and server will communicate. It's the "rules of engagement," including how a client introduces itself, how the server and client will make requests and or respond to one another, and how they will terminate the transaction. The great thing about the openness of the Internet is that there are a number of open standards for Internet communication. Depending on your application, you may want - or need - to adopt one of these standards. For instance, if you were to write an email client, you would want to get familiar with protocols such as SMTP, POP3, and IMAP4. These are TCP/IP-based standards for talking to existing mail servers. (In fact, you could be looking at implementing a mail server in Java, and you would need one or more of these protocols to talk to any email client.) Some other client/server protocol examples include JDBC and LDAP. These may also be appropriate for your application, depending on your needs. My point here is that you can choose an existing standard protocol - whether it's open, or some company or industry standard - and implement the protocol in Java.You can also design your own protocol. Now the sky's the limit. If you are designing a new client/server capability, you can decide what data is transferred and how. An Example Client/Server Application As an example, suppose you work for one of the "Big Three" auto manufacturers, and you are directed to implement a new thin-client strategy. Your first assignment is to develop a Java applet that would be used by hundreds or thousands of people who have input into the development of new vehicles. This applet will show the current parts inventory for one of a selection of new car designs, and it will allow the user to add parts to the inventory. It might look something like the following do-nothing applet: When you design the protocol for the application, you might come up with a number of message types, including the following:
Write Methods that Support Your Protocol Once you decide exactly what commands and data you want to transfer, and when you want to transfer the data, you can start to develop Java methods to read and write the data. Below are some sample methods that could support our parts inventory protocol. To send a new part to the server, the client applet could call writeAddPart, which writes a command requesting the server to add a part with the given data elements: public void writeAddPart( DataOutput outstream, Part part )
throws IOException {
outstream.writeUTF( "Add Part" );
// this is the command the server will respond to
outstream.writeUTF( part.getName() );
outstream.writeInt( part.getQuantity() );
outstream.writeUTF( part.getManufacturer() );
}
The next method, readFromServer, reads the next response from the server (a string that could be one of possibly many different response codes), and performs an appropriate action. In this example, only a Part response is accepted. The function then calls readPart to generate a Part object from the server, and adds it to its own internal list: public void readFromServer( DataInput instream ) throws IOException {
// this goes into an infinite loop, so
// you will want to run this in a Thread
while ( true ) {
String command = instream.readUTF();
if ( command.equals( "Part" ) ) {
Part part = readPart( instream );
partsList.addPart( part );
// partsList is an application
// object referenced by this class
}
else { // add more commands here, if any
throw new IOException( "Unexpected command: "+command );
}
}
}
The next method is readPart, which simply creates a new Part object and reads the attributes from the stream. Note that this function could be used on both the client - to read the part from the server, as shown above - or on the server - to read a part that the client added. The function should probably be put in a utility library for the parts inventory application, and used by both client and server: public Part readPart( DataInput instream ) throws IOException {
Part part = new Part();
part.setName( instream.readUTF() );
part.setQuantity( instream.readInt() );
part.setManufacturer( instream.readUTF() );
return part;
}
Selecting the JVM Environment for Your Server All Java programs run in what is referred to as a Java virtual machine (JVM). This is typically a single process that includes a byte-code interpreter or just-in-time (JIT) compiler, loads and executes Java classes, and manages the threads, memory allocation, and garbage collection. (Interpreters and JITs read and execute Java byte-cod, the Java equivalent of machine code, which is portable to any platform. JITs can actually compile some of the byte-code to much faster native machine code as the application loads.) Netscape Navigator includes an embedded JVM in which Java applets, downloaded across the Internet, are loaded and run. The latest Web servers - including Netscape Enterprise Server - now include their own embedded JVMs. Instead of loading client applets over the Internet, the server loads server applets from its local file system in response to URL requests. One of the advantages to this approach is that - in the case of Netscape Enterprise Server - server applets have access to the Web server's environment and capabilities. They can also be used to respond to HTTP protocol requests from Web browsers in a flexible and dynamic way. The Netscape server Java API adds a number of convenience classes that make HTTP transactions simple and painless. (I'll devote much more detail to the topic of Netscape server applets and their capabilities in Part 2 of this article.) When you write a Java server, one of the things you have to decide is what your Java environment will be. If your Java server is not embedded in a Web server or similar environment, then you can run your Java application using one of the JavaSoft Java interpreters or JITs, one from your system vendor, or one from a third party. These JVMs are usually distributed as part of the Java Developer's Kit (JDK) or an integrated development environment (IDE) like Symantec Café or Sun's JavaWorkshop. (Enough with the acronyms!) Loading a Stand-alone Java Application The stand-alone interpreters typically run under the name java (easy enough to remember!). And to run a stand-alone Java application from a DOS or UNIX shell command line, you can type java MyApp, where MyApp.class is accessible through your CLASSPATH. Without getting into a too much of a Java tutorial, stand-alone Java applications start from a "main" function (very similar to the way you'd start applications written in C and C++). When you run the interpreter on a Java class file, the interpreter scans that class file for a static function with the exact signature public static void main( String args[] ), and executes that function. From here, I create a Server object that, when constructed, will open a Java ServerSocket. ServerSocket is a class in the standard java.net package, and it opens a specific TCP/IP port on the server to which clients can connect. The main responsibility of the server is to loop indefinitely, and call the ServerSocket accept() method, which returns (with a new Socket object) only when a new client has connected. In the Java model, as each client connects, you have one of two choices:
A Useful Package of Java Client/Server Utilities I've developed a small package of classes - dtai.net - that provide a generic framework for almost any client/server architecture. We have used this numerous times at DTAI as a foundation for many of our client/server applications. I've included the source code with this article. Feel free to use these classes in your own client/server implementations. The classes include NetServer and NetPort; NetPort is subclassed into two versions, NetPortToClient (used by the server) and NetPortToServer (used by the client). These classes are meant to be subclasses that you can extend to meet your specific needs. They handle most of the thread, socket, and data streaming code, and they allow you to concentrate on the issues that are specific to your application. As a client/server developer, you would subclass NetServer and NetPortToClient for your server-side program. For simple servers, I often do this in one .java file, with two classes. To give you an example, I've also included 45-line server program with this article, called Sample2.java. Sample2.java doesn't really do anything useful. It simply reads a command string from the client and responds to the client by writing a new string with the value I did it!. But it's very easy to make this program more functional, simply by adding code to the readInput() function. Some aspects of the program to pay attention to are the following:
The Client Side The code for the client side is even simpler. NetPortToServer also subclasses NetPort, and it has the advantages of the DataInput and DataOutput implementations as well as being able to subclass Thread. (Again, use of the Thread and the readInput() model is optional.) The constructor for NetPortToServer takes the server's host identifier (hostname or Internet address) and the server's port number. I often use the NetPortToServer class as-is, without subclassing. Here is a short, three-line example of how easy it is to invoke a simple client-to-server transaction:
Obviously, in this example, the protocol was predetermined. The server should be waiting for a command, in the form of a UTF String. It should also respond with one UTF String. If anything goes wrong, the server could send an error message back in that string or simply shut down the connection. What I haven't shown is that you will have to wrap these lines inside of a try/catch block to catch any I/O exceptions, such as if the server shuts down the connection. Starting the Server I've found that one of the trickier parts of client/server programming is to figure out how to start the server in the first place. During most of my programming, I just assume that the server is running. This is easy to do in the beginning, because I usually start the server myself. But when I deploy my applications, the production version has to have a way to start the server automatically. There are several ways to do this under UNIX:
Starting the server under Windows NT is a little more difficult. Unlike UNIX, Windows NT does not treat background processes as "first-class citizens." I developed a small C program called spawn.c (which compiles to spawn.exe) to start a program in the background. It usually works. (Well, I'm not much of a Windows programmer, but even the Windows experts I know seem to get road-blocked on Windows background processes!) When it does work, it is consistent. Using this program, you can write a DOS batch file as a CGI program and spawn the server as a background process. Another alternative for Windows NT, similar to the rc file approach, is to start the Java server as a Windows NT Service. Microsoft has a utility program that comes with its Java Software Developer's Kit (SDK) that will register a class as an NT Service. You have to create a small "wrapper" class that subclasses a Microsoft class file (which is included with the SDK). It is relatively simple to use, but I still prefer the straightforward and debuggable UNIX rc scripts. (Such is life.) Conclusion Java is an extremely powerful language for client/server programming. And although Java is already seen as an applicable client-side software tool, don't overlook Java for other applications, including server-side development. Java is maturing rapidly, and there are even promises that Java will perform as fast as any other modern programming language by year-end. In my opinion, Java is the best choice for most programming tasks, even today. In Part 2 of this article, I'll discuss Netscape's Server Applets, and new features in Enterprise Server 3.0, including server-side LiveConnect, that allow Java programs more control and interoperability with other parts of the Netscape Web server.
Technical reviewer for this article was Junhe Liu of Netscape Communications' developer support engineering team.
View Source wants your feedback!
Rich Kadel is a senior software engineer and lead engineer in charge of Java development at DTAI Incorporated, a progressive technology company based in San Diego, CA. DTAI develops advanced software products and provides custom computer solutions to a number of market areas. Rich currently leads the development of DTAI's Interactive Org Chart, a 100 percent Java client/server Intranet application first developed for Netscape's AppFoundry program. During the production of Interactive Org Chart 1.0, he developed the Gadget Windowing Toolkit (GWT), a powerful, portable, robust (and now free!) replacement for Java's AWT. He also participates in the development of DTAI's JustDBC suite of JDBC (Java Database Connectivity) drivers, proxies, and servers. He was the primary developer of PAL++, a large C++ software library with almost 300 C++ classes for building graphic- and data-oriented applications. (5.97)
Any sample code included above is provided for your use on an "AS IS" basis, under the Netscape License Agreement - Terms of Use |
| © 2001 Sun Microsystems, Inc. | terms of use privacy policy | A Sun | Netscape Alliance |