SOAP, the Simple Object Access Protocol, is a lightweight toolkit for building Web services. It is an amalgam of ubiquitous technologies - HTTP and XML. Though the likes of Microsoft, IBM, and the Apache Software Foundation normally have little in common, all support it as a foundation for deploying Web services. One of the great advantages of SOAP's lightweight nature is the simplicity of server-side programming. A SOAP service needs no knowledge of the SOAP environment. In fact, just about any Java class that exposes public methods can be turned into a SOAP service.
Unfortunately, sophistication usually brings complication. While there is little you need to do to write a SOAP-based Web service, it is difficult for a service to know much about the context of a request being made. Should the service evolve over time, you might want the client to provide a service version so a proper response may be sent to a back-revision client. If a service is available to a number of applications, it may be useful to know which is making a request. The obvious approach to solving this problem is to add additional parameters for these things to the service. This quickly becomes annoying for the service programmer, as every method in every service would require these parameters. Things become tedious for the client programmer also, as such information is relatively static and would be duplicated across every call.
A Basic SOAP Web Service
Consider Listing 1 (all code listings may be found below). TestService certainly is a trivial service, but no special coding is required to make this a SOAP service. The SOAP RPC router and Java reflection do the work of receiving a request and matching it up with a deployment descriptor and method signature. The code to call it would look like Listing 2. You will need a copy of SOAP from Apache to try the examples. These were built with version 2.0. Use the SOAP administrative interface to deploy the service.
The obvious way to add information like application name and service version to this example would be to add parameters to the call. A better approach would be to develop an out-of-band communication channel with SOAP services. Likewise, the service would need some way of finding these values while servicing a request. As HTTP is one of the component technologies, can the additional information be included in HTTP headers?
HTTP and SOAP
Looking at the conversation between the example client and server above, we see the conversation shown in Listing 3. With the exception that the content of the POST is a SOAP payload, there is nothing unusual about this interaction. So, if we are to use HTTP headers to transmit out-of-band messages, how can we affect the SOAP conversation to include additional headers? It might
be tempting to use the SOAPAction value, but its definition is somewhat fuzzy and probably should be avoided. The mechanism we should use is not obvious from the client example above, but there is
a way.
Extending the SOAPHTTPConnection
There is a method on the Call object that enables us to provide an instance of a SOAPTransport. SOAPTransport is responsible for transmitting the SOAP payload to the server and receiving the response. Normally, this would be an instance of SOAPHTTP Connection, though you could use an SMTP-based transport. Looking at the documentation for SOAPTransport, we see that the send() method enables us to contribute headers to the request. There is also a getHeaders() method that enables us to see the headers from the response. Our solution lies in extending SOAPHTTPConnection and providing our own send(), as in Listing 4.
Our send() arranges for the out-of-band messages to be included with any headers SOAP has inserted, then calls super.send(). The messages are stored in a static hash table, so they will be shared across every instance of this SOAPTransport. We have also included some helper methods to add, remove, and get messages from the hash table. To use this new SOAPTransport we modify TestMain as in Listing 5.
The static initial-ization block will ensure that any mes-sages are set on our SOAPTra-nsport before we have a chance to use it. If TestMain was a servlet, the APP_NAME and APP_VER values might be obtained from the servlet conf-iguration and set dur-ing the servlet's init(). Running the modified sample, the message sent from the client now looks like Listing 6.
Creating a Service Context for SOAP Services
The headers are now included in the request being sent to the server, and the server is accepting them, though not acting upon them. On the receiving end, the challenge is the fact that the SOAP service is running in a context-free environment. If we can solve our problem without building a dependency upon SOAP mechanisms into our services, we will be able to use our services in other environments. Listing 7 shows a class that provides the necessary context.
Note the use of an InheritableThreadLocal to store a hash table. Values stored with
a ThreadLocal's get() and set() methods are unique to that thread. An Inheritable ThreadLocal ensures that any threads created after a value is set inherit that value. This is important since, server-side, the request is driven by a servlet called RPCRouterServlet. Most servlet containers, such as Tomcat or JRun, will create a pool of threads to service requests. Each time we make a request, a thread is taken from the pool and returned when the request is complete. Our service may be called by any number of applications (and versions of applications) and, using an InheritableThreadLocal, the application name and version may be attached to the thread servicing the request.
Next, we must extend the SOAP RPCRouterServlet as in Listing 8. This extension intercepts the call to doPost() (RPCRouterServlet rejects GET requests), extracts the messages, and adds them to
the ServiceContext. Following the call to super.doPost(), it calls ServiceContext.clear() to ensure that the thread does not service another request with incorrect information attached to it. The final step, Listing 9, updates the service to use the ServiceContext. Now, running the TestMain class:
> java TestMain foo
The returned value was 'The argument you sent was 'foo', your application name is 'TestMain', your application version is '1.0'"
Cautions
The idea of using a ThreadLocal or InheritableThreadLocal client-side to transmit user credentials (i.e., value of Http ServletRequest.getRemoteUser()) might be tempting. Doing so asks the client of a service to identify itself honestly. Sending a username and password would be done in the clear. Unless you secure access to your services to trusted clients or have trusted clients digitally sign the message, it probably isn't a good idea.
If you look at SOAPSMTPConnection you'll see that, although it does implement the SOAPTransport interface, it ignores the headers parameter on the send() method. This technique will not work with an unmodified Apache SOAP implementation if you are using SMTP as a transport.
Summary
Using HTTP headers as an out-of-band communication channel is relatively straight-forward, and the channel can just as easily be extended to be bi-directional. Use of this technique should take some unnecessary drudgery out of SOAP programming.
Author Bio:
Mark Moore is currently an independend consultant. Previously, he was chief architect for KPMG International's Global Knowledge Exchange, deploying knowledge-sharing capabilities to 80,000 KPMG employees in more than 40 countries. He has been designing and developing Web-based knowledge management and collaborative solutions
for the last six years. SINANJU@MEDIAONE.NET
All Rights Reserved
Copyright © 2004 SYS-CON Media, Inc.
E-mail:
info@sys-con.com