Source code (simple text transfer)
Source code (object data transfer)
Advantages and disadvantages
ORBs and firewalls
URLConnection and firewall tunnelling/proxy server usage
The Java platform (VM, language, etc.) supports loading classes from any stream. This source for this stream can be a URL, a database connection, or a file. The ability to load a class at runtime from just about any source, over the network, creates many strange and interesting deployment scenarios for a Java based application. Applets are severely limited in what they can due, due to security restrictions placed on them by the VM, and are not conducive to deployment. Applications on the other hand, are a totally different ball game.
Unfortunately, client side deployment and application partitioning depend greatly on the requirements of the project. This makes it more of an art to do such things, rather than a precise science, because there are no right answers, only better or worse ones.
With web services, SOAP and XML RPC and session-less RESTful protocols becoming popular over the last few years, this tutorial will take you through the steps of crafting a very lightweight RPC mechanism that uses Java’s object serialization and HTTP (URLConnection and Servlets). It’s like using a RESTful API, except that the parameters passed are Java objects, and only HTTP POST is used for communication between the client and servlet.
Client side Java applications can justify their existence in situations where platform independence and heavy network dependence are top on the requirements list for the applications. In these situations, Java can be used in interesting ways to partition what is the “client” side from the “server” side.
For example, traditional “heavy” clients have a lot of resource requirements, and require a lot of classes to exist on the local VM in which they run. These classes could be heavy Oracle JDBC drivers, Weblogic JMS client classes, etc. In deploying a heavy client, there are advantages and disadvantages. The major advantage is:
- development is usually easier, simply because all the required classes on the client side can just be bundled with the app. There is no need to come up with nifty proxy services to offload some of the processing to the “server”.
The disadvantages are:
- the client side classes have to be updated for any changes made on the server that require bundled classes to change. For example, if Weblogic comes up with a different implementation of JMS, then the “new” classes have to be used by the client app. Client apps have to be dynamically upgradeable.
- If there are bandwidth restrictions (modem connection to the internet), then it becomes cumbersome to make major updates to a client apps codebase. This depends on the size of the codebase that needs to be updated, of course.
- Heavy clients are usually more resource intensive. Since they “do more things” on the client VM, they also take more resources to get these “things” done. This could be allocation of more threads, or acquiring more socket connections (for example, doing remote object lookups, and using stubs).
Now, if there is a restriction on the bandwidth and resources that a client app can acquire, then it might be a better idea to go with a thin client.
There is another very important reason for creating a thin client, even when none of the requirements listed above apply – firewalls. If the client app has to work even when it is behind a corporate firewall, then there is a compelling reason to create the client app as a thin client, and have it tunnel through HTTP. Why HTTP? Simply because most corporations have proxy servers that allow employees to connect to sites on the web. So, by piggy backing data over HTTP (that connects to a server that is not a web server), it is possible to “tunnel” through the firewall. Now, the firewall tunneling requirement necessitates the use of HTTP, otherwise we could choose any protocol that gets the job done.
A thin client changes the partitioning of the application space so that more objects and tasks are performed in the server realm. This has the following disadvantages:
- it takes more time to architect this solution, simply because more layers of abstraction need to be added, which invariably leads to more coding.
- it is more resource intensive on the server side. By using thin clients, more objects get pushed into the server, with more layers, which takes up more resources on the server side.
It does have its advantages:
- speed. Thin clients are usually always faster. Why? Because the servers (with powerful machines and fast network connections) can usually do things faster than the client host (with slower machines and less bandwidth). However, more server resources are taken up as a result.
- scalability. It is easier to add hosts to a server farm, due to better manageability on the server side. In order to improve performance (if the application is done correctly), you simply have to add more hosts to offload processing, and get faster LAN connections. It is easier to do this, than to deal with many copies of the client app, on a variety of different client hosts, perhaps running a wide variety of OSes.
The proxy pattern is the most important thing to use in creating a thin client. By adding a level of indirection, or abstraction, between the client objects, and the actual server objects, you too can create thin(ner) clients. The “way” of doing things is quite consistent, move heavy objects from the client app, to the server side, while allowing access to the interfaces exposed by the server side objects. Now, access to the server side objects can be streamlined by using a lightweight protocol (like HTTP or raw sockets), instead of using ORBs. However, there is a delicate balance to doing this. You have to create layers that do low level protocol translation (to “object-speak”) in the client code. This is replication functionality that you can get from ORBs. However, ORBs were meant for a slightly different application than thin clients.
Again, you would only want to undertake these tasks if performance, and client app size and resource usage were key requirements in the design of your app. If they are not, then it is easier to go with heavy clients.
ORBs support location transparency for object references, which allows you to use remote references to access server side objects. However, the ORB uses its own protocol and sockets at the lowest level to “pull this off”. Now, the implementation strategies used by commercially available ORBs might not be condusive to deploying thin clients, because the ORBs might have been designed to do other things in different environments (like exist on the server side, with plenty of memory and processor power, and plentiful bandwidth).
There are 3rd party implementations of these lightweight protocol layers that allow you to create proxies for objects, but the project requirements might not warrant their use.
Figure 1 illustrates this pattern.
The Servlet API, for example, chooses to have Servlets implement the Runnable interface rather than extending the Thread class. The end result of this is that there is one instance of the each Servlet class, and the Servlet container creates a new thread for each HTTP request that comes in (for that Servlet) and attaches it to the same Servlet object. This is the Singleton pattern, where only the same (single) instance of a class is used anywhere that an object of that class is required. Now, you can override this default behaviour of Servlets, by having your Servlet extend SingleThreadedModel. This starts to emulate the behavior that you would get by extending Thread. When you extend SingleThreadedModel, a new Servlet object is created for every thread that is created to service each HTTP request. This behavior is illustrated in Figure 2. Please refer to the source code example below on the details of implementing Runnable.
The behavior of RMI objects is very similar to this. When a client looks up a remote reference, they get a stub to the skeleton of the remote object. Now, there is only one remote object, per remote reference. However, a multitude of clients can connect to this singular remote object and invoke methods on it. Sounds like the SIngleton pattern at work again. The skeleton creates a new Thread that runs through the singular remote object (in addition to managing all the protocol translation between it and the stub). This is very similar to the Servlet mechanism. This kind of design strategy exists in most server frameworks, from Servlets, to RMI, to CORBA ORBs.
However, in EJB, the model is a little different. When your client code gets a reference to the home interface implementation, you usually call the craete() method, which gives you a remote reference to a bean. Now, you get a new bean for each remote interface, rather than sharing one bean. This is not the Singleton pattern. However, the Home interface does use the typical ORB model, where you get a stub to a skeleton that points to the object that implements the Home interface. This Home object (or factory) is responsible for returning remote references that point to unique beans (or server side objects).
You can use traditional distributed computing layers (like RMI, or CORBA, or Weblogic’s RMI/T3) to create thin layers. However, the client code will need access to the ORB classes (which are required for the stub and skeleton communication). This is sufficient for most projects.
However, instead of using normal stubs on the client application, it is possible to add another layer between the stub and the actual client. Also a lightweight protocol (like HTTP) can be used to talk between the thin client and the stub (which now exists on the server side as well). Now, the stub can exist on any host in the server environment, everything doesnt have to be one one machine. Figure 2 illustrates this.
In Figure 2, you can see that the remote object (that implements your remote interface) could exist in the Servlet container itself, or it can be a remote reference itself, that uses RMI (or someother ORB technology) to connect to some object on another host. So the Servlet gateway serves as a thin proxy that allows access to the heavier remote objects that actually live on the server side.
This partitioning is simply taking the ORB concept to another level, but providing a light protocol to access the actual remote reference from the actual client. I will have code examples on how to do this later, but the key classes on the client side is java.net.URLConnection, and the key class on the server side is javax.servlet.HttpServlet.
Listed below is some source code that shows how to use java.net.URLConnection on the thin client, and a Servlet on the server side to tunnel.
Here is the code for the Servlet:
Here is the code for the client (written as an Applet):
The only difference between transferring text (Strings) and objects (or byte arrays) are:
- object streams have to be used to write and read to/from the URLConnection and Servlet
- byte arrays get transmitted from the client to the servlet, rather than plain text, and so the mime type for the URLConnection (on the client) has to be different.
Here is some code that does the object data transfer on the client side, using URLConnection:
A sample getBytesFromObject(Object) implementation is shown below:
The process( Object o ) method should be used to do something the object that was received from the Servlet.
In order to do non-textual data transfer to a Servlet, you have to use the service() method in your Servlet, not the doPost() method, like it was done above. Here is a new Servlet that deals in data objects, rather than just plain text:
The process(Object) method should perform some task on the object just received from the client, and it should return a value, that has to be transmitted back to the client.
The getBytesFromObject(Object) uses the same exact implementation that was used in the client code.
There are some consequences to doing this super light client:
- you can’t have call backs from the server object to the client object. Everything is “client pull”, however, you can emulate an event based system using this underlying “pull mechanism”.
- you can’t pass remote references as return values. You would have to explicitly pass URIs that actually have information it it that will allow the client stub to access another server side object through the gateway.
- development time might increase simply because there is one more layer between the client and the remote object.
The advantages are:
- no need to bundle any ORB or app server specific classes with the client code.
- since the client doesnt have to do lookups and instantiate any stubs (except the really light one), performance is very good, and resource overhead is very low. The burden is simply shifted to the server.
- no need to update dependency classes (that apply to implementation specifics of the app server), since everything is delegated to the server side proxy object through the gateway.
Nothing is free. In order to get super lightness, there are tradeoffs in development time, and resource overhead when deploying to the server side. Whether you will do this or not, depends entirely on the requirements of the project. There is no right answer, it all depends on what you want to do.
In order to get through firewalls, many vendors have created ORB implementations that have an HTTP gateway, that allow you to tunnel through HTTP in order to get to the actual server side object. This is a hybrid of traditional ORB technologies and the super light HTTP gateway approach. This middle of the road approach has advantages and disadvantages of both ORBs and the light gateway.
The advantages are:
- you can tunnel through firewalls, while taking advantage of the simplicity of using a traditional ORB (like RMI over T3, as provided by Weblogic).
The disadvantages are:
- you will have to bundle app server specific classes, and you will have to updated them on the client (when the app server is upgraded).
- resource usage is similar to using a normal ORB, because there are heavy stubs on the client side.
- the protocol is heavier than HTTP, simply because these protocols are ORB protocols, which are designed to do a lot more than act as a simple HTTP gateway for an application.
Here is some information on Weblogic:
The following is an excerpt from http://www.weblogic.com/docs51/classdocs/API_t3.html:
HTTP port-80 access. The WebLogic Server can act as an HTTP server listening on port 80 for serving HTTP servlets. Setting up the WebLogic Server to listen on port 80 means that T3Clients can also communicate with the WebLogic Server across a firewall in standard fashion. This gives all T3Client applications — using WebLogic Events, WebLogic JDBC, and/or WebLogic Beans, etc — port 80 access to WebLogic. The WebLogic Server is designed to serve as a fully-featured webserver for an arbitrary files; in addition, it can proxy to another server.
The URLConnection class has the ability to use a Proxy server to get out of a firewall. Let’s say the applet or java app that is running on the client host is behind a firewall. In this case, the JVM acutally supports the use of proxy servers in the URLConnection class. By using the following lines of code:
Where proxyServer is the hostname of your proxy server, and the proxyPort is the port number of your proxy server. This is what allows a client to “tunnel” through a firewall. You don’t have to worry about this if your client host is not behind a firewall.
In order to read more about firewalls and low level network connections, follow this link.
Depending on your project requirements, you will have to define what “thin” means to you. You might be able to get away with normal ORBs (like RMI/IIOP). Or you might have to get fancy and use something like Weblogic’s RMI/T3, that allows you to tunnel through firewalls. Or, you might have to go with the lightweight HTTP gateway.