This article was published in the November 2002 issue of the IBM developerWorks journal .
There are at least three factors that contribute to the performance, or lack thereof, in web services:
- Network transaction time: How long it takes the client to make a request to the remote web service.
- The time it takes to handle the message: Specifically, the XML parsing, any flow management, invocation of the service, and the final response encoding.
- The time the service itself takes to execute: (Note that this does not include making a dynamic call to a piece of code.) This last piece is often the major culprit in illperforming services. It is easy forget that useful code can take some time to perform its function.
Web services provide a common interface to increasingly complex functions. While there is no specification for the underlying service, the basic notion is that you write bits of code and deploy them into the network. You might leverage this function through multiple transports, most commonly HTTP. Therein lies the inefficiency of web services: Many transports are not performancecentric and the wrappering and parsing of XML imparts overhead that direct procedure calls do not shoulder. However, as Albert Einstein once said, time is relative.
In the world of the Internet, most people think in terms of seconds. Many web sites strive for better than a six-second response time. Considering that you can use web service calls to build a web site, such as a news portal, the performance of a web service needs to be even faster. This is a challenge. The goal is to have around a three-second response for semi-involved web services. You can achieve this goal by looking at the part of web services you have control over: the bit of code that does all the magic.
Current Web caching systems leverage unique identifiers pointing to content fragments. They leverage the underlying protocols to keep track of the timeliness of data bits, such as HTML documents or images. While web services over the HTTP transport have endpoints (URLs), the parts of the Simple Object Access Protocol (SOAP) envelope that make it unique are the values for parameters in the SOAP envelope, as seen in Listing 1. Because SOAP is XML, several tags can have many different labels but still mark up the same data. Taken as a whole, it is possible for you to create a SOAP envelope that it is different from the next while representing exactly the same data. Moreover, if you decided to cache based on the individual unique parts of a SOAP envelope, there is no way to indicate if the query is suitable for such caching.
Developers tune current commercial Web caching solutions for specific types of situations and data. Furthermore, even if a solution exists, it might not take into consideration the desired action of the developer. While commercial tools may be available, rolling your own cache object provides the utmost in flexibility, control, and affordability.
Most processes involve calculation. The calculation can be an equation or a network transaction that results in a new value. Either way, you should identify the parts of the process that waste time. Consider the process of reporting atomic time. If it requires a Simple Network Time Protocol (SNTP) connection to a government service, it might waste time to do it more frequently than every several seconds. The time that it takes to complete a connection to the atomic time service affects the precision of the time reading. For this example, assume you only will keep 10-second precision. The network call to get the current time is a perfect item to cache because your 10-second constraint means the data will not be changing for, at most, 10 seconds. You only need to make a new connection about every 8 seconds to attempt to compensate for the network latency.
This style of cache is simple. Two static variables are involved: the last time you checked for the time and the time itself. The last time you checked acts as the variable to trigger refreshing the time from the timeserver. The time needs to be refreshed anytime the stored time is outdated. This is checked based on the timestamp of the last time check. Obviously if the client request for time comes in before the stored time expires, you return the cached time.
You also can generate and cache audio files for text-to-speech services. It takes a lot of resource to generate a sizable amount of data to represent an audio file. The service takes a string and returns the audio file as an attachment. You can store the dynamically generated audio and store the text representation as the key.
Simple enough, but consider this: How long do you keep these cached items around, and how often are other clients going to call the web service?
The first question goes to the fact that every system has limited resource. Keeping 100 MB of audio data in memory might be too storage intensive. You can design a cache to have a maximum size. It can keep the most used entries around while purging the oldest and least used. You might decide not to have a time limit for this cache.
The second issue highlights that the service might be so infrequently leveraged by other clients that caching anything might be useless. For example, assume the total transaction time of an imaginary web service is two seconds. If the calls to this web service are infrequent (less than one a minute), it might be determined that there is no need for caching. However, if the load is 100 calls a second, it might be justified. An alternative approach is to have a self-pruning cache that stores entries, as needed, but prunes them at an expiration time. Pruning the cache object reduces its memory footprint. Depending on the implementation, the cache could afford the ability to keep popular cached objects around longer than the infrequently used items.
The design of a cache is often based on the situation in which it will be used. This article covers both simple and complex examples. Other considerations include:
- Time sensitivity and courteous use of system resources
- Specific or generic cache (storing Objects or specific data types)
- Sensitivity of data cached: if it is a password, you might be able to use a one-way hash to store the password securely while gaining accelerated performance
Caching can be a useful tool in any application. In web services, you get to leverage the cache more often, as repetitive and high-load queries are many times the nature of the game. There are times when inserting a cache solution into your code is appropriate. Other times, when you can cache the entire result of a web service call, it is better to insert the cache check before the web service gets a chance to be invoked. To demonstrate this latter approach, assume you are using a Java(TM) development environment with Apache's Axis and a Web application server, such as IBM WebSphere. (Note that everything you need to work in this type of environment comes in the IBM WebSphere SDK for Web Services.) The goal in this exercise is to isolate the cache from the actual web service code.
Figure 1. Axis flow architecture

Apache's first SOAP implementation relied on the concept of a provider. This bit of code decoded the object and method from a SOAPenvelope, instantiated the local object, and returned the result to be wrapped up in a return SOAP envelope. Axis has a provider equivalent, but often it is referred to as the pivot point. This is because Axis provides an architecture that you can use to define transaction flows. You define the flows through handlers. Each handler is able to act on the current incoming and outgoing SOAP envelope. Handlers provide the ability to perform methods and services as part of a flow for a larger service, as shown in Figure 1. A pivot point is just another handler, but it indicates the point at which the request becomes a response. The pivot point is one place where you can insert your cache.
The cache for this example application is a generic solution. You will design the object as a singleton (this design pattern will limit this object to a single instance) and have the appropriate retrieve and store methods to manipulate the cache. The cache will store items for a fixed time and will spin a thread to prune the items as needed, as shown in Figure 2. There is only one limitation to what can be cached--the item must be an object.
Figure 2. High-level view of the self-pruning cache object

Because of the open source nature of many Apache projects, you can download the source code for the Axis SOAP implementation. (It is still beta at the time of this writing, but it may end up as a popular solution for Java web service work.) You will modify the RPCProvider class (org.apache.axis.providers.java.RPCProvider.java). There are a number of providers supporting multiple transports. While you could write your own, take the easy route and leverage all the hard work the Axis developers already have done. You simply can copy the RPCProvider developed by the Apache folks and add some code to end up with an RPCCachingProvider. To cache a response from a web service, you need to generate a unique key to use for indexing it. Based on the object to be called and the arguments found in the SOAP envelope, as seen in Listing 1, you can generate such a key. Specifically, you need to determine the service, the method, and the arguments.
Listing 1. Echo service sample SOAP envelope
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope
soapenv:encodingStyle=
http://schemas.xmlsoap.org/soap/encoding/
xmlns:soapenv=
http://schemas.xmlsoap.org/soap/envelope/
xmlns:xsd=http://www.w3.org/2001/XMLSchema
xmlns:xsi=
http://www.w3.org/2001/XMLSchema-instance
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/
soap/encoding/">
<soapenv:Body>
<ns1:echo xmlns:ns1="urn:Echo">
<arg0 xsi:type=
"xsd:string">Helloworld!</arg0>
</ns1:echo>
</soapenv:Body>
</soapenv:Envelope>
|
Note that this caching provider assumes that the arguments have string representations that are unique, but not unique per call. For example, when retrieved as a string, a byte array will look something like [B@4f25045a. Each byte array will look different and the cache will assume that each array is unique, causing the cache to not work effectively. While there are ways around this, this article keeps the key generation simple. As soon as you have a unique key, check to see if there is an item in the cache. If there is an item, return the stashed response. In this situation, where you are caching the resulting response of a web service call, you are caching the SOAP body, as delineated by the soapenv:Body tag in Listing 2. You might decide to update the time of the cached item to keep it around longer. However, if the cache does not have a matching item, you should continue to execute the call and store the resulting response fragment. You might specify that the cached item remains for 30 seconds at a time and refreshes each time it is requested, as shown in Listing 3.
Listing 2. Sample SOAP return envelope
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv=
"http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd=
"http://www.w3.org/2001/XMLSchema"
xmlns:xsi=
"http://www.w3.org/2001/XMLSchema-
instance">
<soapenv:Body>
<ns1:echoResponse
soapenv:encodingStyle=
http://schemas.xmlsoap.org/soap/encoding/
xmlns:ns1="urn:Echo">
<echoReturn xsi:type="xsd:string">
Hello world!</echoReturn>
</ns1:echoResponse>
</soapenv:Body>
</soapenv:Envelope>
|
Listing 3. Excerpt from caching provider code
// Build key for cache.
Stringkey=
operation.getMethod().toString();
for(inti=0;i<argValues.length;i++){
key+="::"+argValues[i];
}
// Get the cache instance.
// Make sure DEBUG is off so you don't fill
// up your logs. Check to see if there
// is an item for the key you created above.
Cache cache = Cache.getInstance();
cache.setDEBUG(false);
RPCElement resBody=
(RPCElement) cache.getCachedItem(key);
// If resBody is null, you want to cache it.
if (resBody == null) {
// Call the service and
// build the response body.
}
// In this case, cache the item every
// time to keep it around longer.
cache.addCachedItem(key, 30000, resBody);
resEnv.addBodyElement(resBody);
|
Implementing the caching system at the pivot point isolates it from the service code. The magic is in the deployment of the service. Instead of identifying the default provider (java:RPC), specify the pivot point as Handler and add a parameter to the service pointing to your new caching provider, as shown in Listing 4. This architecture lets you decide at the time of deployment if you will use caching. You could even externalize the time-to-live for cached items with a service parameter. Axis provides all of this functionality and flexibility. You can accomplish the same provider concept in the older Apache SOAP implementation, but you have a little more work to do to access the method and parameter values.
Listing 4. Sample deployment descriptor for an Echo service
<service name="urn:Echo" provider="Handler">
<parameter name="handlerClass"
value="com.ibm.webahead.providers.
java.RPCCachingProvider"/>
<parameter name=
"allowedMethods" value="echo"/>
<parameter name="className" value=
"com.ibm.webahead.services.Echo"/>
<parameter name="ttl" value="30000"/>
</service>
|
Now that you have explored cache systems and created a generic cache object, you can learn how to integrate caching in other web service solutions. While you implemented the generic cache object in a flow scenario, you can use it elsewhere. Having the code will not help unless you put it to work.
This first example examines the need to require authentication before accessing the web service to help restrict access and track usage. Sometimes you need to check a user's authentication credentials with a connection to a corporate LDAP directory or database, and the operation to cache is this transaction.
This example uses a fixed size cache, such as 1 MB of storage or 100 cached items. It uses the user ID as the key, and the value is the password stored as an MD5 hash. This ensures security while still being able to verify future passwords against the cached item. If authentication fails, you can remove the ID. Finally, for security's sake, think about timing out the cache because passwords change and IDs can be revoked.
The first time you check the user's credentials, store them in the cache upon a successful login. If the credentials prove to be invalid, return an error. Subsequent calls to the service get the security of password authentication without actually having to incur the overhead of a database connection or corporate LDAP directory lookup. While it might not seem like much saved, over time and with a heavy load, this adds up to a critical savings.
Object cache to defend against replay attacks
Sometimes, you may find that your web service is part of an elaborate system. In this second example, the system requires a client to first get an authenticated token and present that token to the subsequent service. To further secure the system, tokens only can be used once. Enabling the web service to process which tokens have been used before enforces this use once model. It prevents tokens from being maliciously submitted to gain access to a resource after the first use. This scenario leverages some of the digital signature concepts found in the Axis security demos and early versions of the IBM Web Services ToolKit. In those examples, digital signatures are created based on the SOAP envelope. The same verification test can be applied.
For example, you may generate a special time-stamped authentication token that is digitally signed to prevent tampering in transit. Upon accessing the second service, you can verify that the first service was the originator and that the token has integrity. This provides a method of securing many web service solutions; an object level cache lets you do it all quickly and easily.
In this instance, the cache system is of the self-pruning variety. All you need is a way to store the item by its unique key. You can set the time-to-live to be longer than the validity of the original token request. This cache will be self-pruning, so you maintain healthy resource usage.
Upon receiving a request, you can check if the item is not in the cache. If it is in the cache, throw an error. If it is not in the cache, you know this is a valid request and you process and store the item in the cache. When the packet has timed out and purged from the cache, change the error message from previously processed to token expired. Using the cache in this example speeds the performance because you can decide if you should process the request before you commit.
Getting your web services to perform well in high-stress situations is important to the survival of the technology. In the short term, you will see many Web caching experts redesign their tools and solutions to become more web service savvy. Even when they do, there will be times when intelligent use of caching at the service level is beneficial. As the underlying SOAP implementations mature, you will start to see design pattern references. They will talk about how to build fast and scalable web service solutions. Until then, roll your own. Even when the networked world catches up, there will be situations that perform better with a custom cache. As the popularity of web services increases, the demand on them will grow. As you build this distributed infrastructure, think speed. When you can squeeze your code by reusing prior calculation instead of repeating the underlying process, you gain time.
-
IBM WebSphere WebServices Development Kit WSDK
- Apache Axis
- IBM WebServices
- XML.com's WebServices Section
Brian D. Goodman works in the IBM Advanced Internet Technology lab, identifying, prototyping, and evangelizing emerging technologies. His recent work involves secure Web service hosting and high traffic portlet solutions. You can reach Brian at bgoodman@us.ibm.com.




