Creating Callback Enabled Clients for Asynchronous Web Services
by Reza Shafii
03/14/2005
Abstract
Web services are a promising technology that play an important role in Service Oriented
Architectures (SOA). A key, emerging aspect of this technology is the ability to provide asynchronous
services. Although today’s web service standards contain specifications for providing such
asynchronous services, the details on using them from a client application perspective can be
confusing and unclear. Web services callbacks are an important element in the realization of these
asynchronous services. This article provides a practical guide to the creation of client
applications for web services that contain callback operations. All code segments shown in this
article are from the included example, which you can download. The example contains the complete
implementation of these code segments and usage guidelines.
Terminology
Before starting a discussion on callback-enabled clients, it is important that we clarify the
terminology involved. The following diagram shows the main entities involved when a client uses a
Web service with a callback operation.

Figure 1. A client making a call to a web service
This figure depicts a client making a call to a web service. The web service can then respond,
at some later time, by making a call back to the client. Therefore, a client of a web service that
contains a callback operation is special in that the client itself must provide an endpoint. We call
this the callback endpoint and define an endpoint as a unique address identified by a URI to
which SOAP request messages can be sent.
The client itself starts the interaction with the web service by sending a SOAP message to the
web service endpoint. The correlated request messages sent by the client to the web service
and their associated response messages form the client initiated operations. As mentioned,
the client can process the request messages sent by the web service to its own callback endpoint.
Together, the correlated request and response messages are referred to as a callback
operation.
With this terminology in mind, let's take a closer look at the concept of web service callbacks
and their relationship to conversational web services and asynchronous web service invocation.
Asynchronous vs. Callbacks vs. Conversational
The concept of an asynchronous web service invocation is sometimes confused with the idea of web
service callbacks and conversational web services. Although related, the three concepts are
different.
An asynchronous web service invocation means that the client is able to send a SOAP
message request without blocking to receive the corresponding response message from the server. The
process of making such asynchronous web service calls on BEA WebLogic Platform 8.1 web services is
already comprehensively documented and is therefore
not discussed further in this article.
Web service callbacks refer to scenarios that involve the web service provider sending a
SOAP message back to its clients. The Web Services Description
Language (WSDL) specifications define such operations as being of the type
“solicit/response.” Clients of web services that support callback operations must have
a web service endpoint themselves, which the web service can use for sending callback requests at
any point in time, or, in other words, asynchronously. Note that this is unrelated to the
concept of asynchronous invocation discussed earlier.
A web service is conversational if the series of messages going back and forth
between the two endpoints are tracked with a unique conversation ID with specific operations flagged
to start and end the flow of messages. Web services that provide callbacks are also, by definition,
conversational. This is because in order for the web service to be able to make a callback call on a
client, it has to have its callback endpoint information. This is only possible after an initial
invocation has been made by the client. Therefore, at the very least, the initial operation
initiated by the client and the callback operation made by the web service are related to each other
and must be tracked with a unique conversation ID. If this is not done, there is no way for the
client to differentiate between callback operations relating to different initiating calls.
We will now take these considerations into account and create callback-enabled clients. As we've
just seen, these clients form the basis of solicit-response and conversational web services.
Creating Callback-Enabled Clients
As discussed earlier, callback-enabled web service clients need to provide a callback endpoint
capable of asynchronously receiving and processing callback operation messages. To avoid the
complications of having to provide a callback endpoint, a technique known as polling
can be used as an alternative. However, this technique requires the client to periodically call the
server to check for callback events. The overhead of these calls can be significant if the event
in question does not occur frequently. This overhead can be avoided by having the client provide a
callback endpoint and process the callback operations directly.
It should also be noted that although it is possible to create callback-enabled clients by using
a web service over JMS (if such binding is offered), this approach has some limitations, one of
the important ones being that it forces the client to use the same JMS implementation as the web
service provider. We will therefore concentrate on accomplishing our task over the HTTP
transport. There are two areas that require attention in creating such a client: the creation of
proper client-initiated SOAP messages, and the handling of callback operations.
When our client initiates operations of a web service containing a callback, it needs to somehow
include the URI of the callback endpoint on which it is listening within the request message. The Web Services Addressing and the SOAP
Conversation Protocol specifications both define a SOAP header element that allows you to
achieve this goal. Ideally, the specification used in specifying the callback endpoint would not
matter. However, most web services containers, including BEA WebLogic Server 8.1, do not yet contain
an implementation of the Web Services Addressing specifications. Currently, BEA WebLogic Workshop
8.1 web services support the SOAP Conversation Protocol specifications and that is the
example used within our example client.
According to the SOAP Conversation Protocol, the required SOAP headers differ depending on the
phase of the conversation. For the first client-initiated (start) operation within the conversation,
we need to provide the web service with our callback endpoint through the
callbackLocation header element. All operations, including the first one, will also
require a unique ID that will be used in our SOAP messages throughout the dialogue. This is achieved
through the conversationID element. The following is an
example of a SOAP request message that starts a callback-enabled conversation:
<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:enc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:env="http://schemas.xmlsoap.org/soap/envelop/"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
<soapenv:Header>
<con:StartHeader soapenv:actor="http://schemas.xmlsoap.org/soap/actor/next"
soapenv:mustUnderstand="0"
xmlns:con="http://www.openuri.org/2002/04/soap/conversation/">
<con:conversationID>[123456]:192.168.1.100:8181</con:conversationID>
<con:callbackLocation>
http://192.168.1.100:8181/StockNotificationCallback
</con:callbackLocation>
</con:StartHeader>
</soapenv:Header>
<soapenv:Body>
<n1:registerForThresholdNotif xmlns:n1="http://www.openuri.org/">
<n1:stockTicker>CCC</n1:stockTicker>
<n1:threshold>10</n1:threshold>
</n1:registerForThresholdNotif>
</soapenv:Body>
</soapenv:Envelope>
Most
of the existing Java web service toolkits (for example, BEA WebLogic’s
clientgen, Apache Axis, and JWSDP) allow you to create a proxy
library that can be used by client applications to easily invoke web
services operations. However, none of these frameworks supports
callback operations with the main issue being that their proxies do
not generate the required headers. Until such support is provided,
it would be highly desirable to be able to use all the benefits of
these frameworks (for example, complex type XML mapping) by extending them
with support for callback operations. One technique for
achieving this result is the use of a SOAP
message handler.
All
the web services toolkits named above can support
SOAP message handlers. A message handler is a Java class that
implements the javax.xml.rpc.handler.GenericHandler
interface and contains methods called right before sending out (or
after receiving) a SOAP message. We can introduce a message handler
within our client that is able to find out the current phase of a
particular conversation and accordingly extend the outgoing request
message with the required headers.
It is
important to note that the client SOAP message handler must be able
to determine the phase of the conversation that the message in
question belongs to so that the appropriate header elements are
created. The generation of the conversation ID is also a task that
is left up to the client handler.
Once
the extended request message is received by the web service
endpoint, it will then send request messages to the callback
endpoint specified by the callbackLocation
element of the start message. In most cases, the client process
itself needs to provide this endpoint and handle the callback
messages appropriately. If our client is running within a web
service container, this could be accomplished by deploying a web
service with the callback operation within the same container.
However, if the client is not running within a container, the job can
be accomplished by implementing the callback endpoint within a
lightweight container embedded in the client process itself. This
gives us the ability to invoke the client generated
operations and handle the incoming callback operations within the
same process context. Note that the processing entity behind the
callback endpoint (and within the client) needs to be able to handle
the XML mapping of the message parameters as well as the dispatching
of the operation to the right area of code.
Off
course the client could also choose to specify a callback endpoint
living within a container separate from the calling process by
simply setting the callbackLocation
header element appropriately.
As we
have seen, the creation of clients for web services containing
callback operations is not trivial and contains complex elements
including the handling of SOAP messages, management of the
conversation (both in terms of phases and IDs), mapping of
parameters, and dispatching of operations. These complexities are
all abstracted when the web service in question is accessed through
a BEA WebLogic Workshop Service Control, which automatically
performs all these operations for the user.
Creating Callback-Enabled Client Using a Service Control
The
accessing of web services through a WebLogic Workshop Service
Control is already well-documented.
This solution is by far the simplest way to use callback-enabled
web services if the client entity is able to use controls (that is, it is a
Java Process Definition business process or another control; Page
Flows are not able to use control callbacks). All the
complexities involved in accessing callback-enabled web
services are abstracted by this method.
Example Stock Notification Service
The
example included in this article contains a Stock Notification
conversational web service and a sample client illustrating the
concepts discussed here. The web service provides
operations allowing clients to register stock tickers and an
associated threshold price. The service then monitors the stock, and
every time the price passes that threshold, notifies the client
through a callback (onThresholdPassed()).

Figure 2. This diagram illustrates the configuration for our example
It is
clear that a polling technique is not an optimal solution for the
creation of our client: Stock prices could pass their associated
thresholds on a frequent basis or very seldom. A short polling
interval could therefore lead to many unnecessary calls whereas a
long polling interval could result in the client being notified of a
threshold crossing long after the fact.
The
implementation of a callback endpoint within the client application
seems like a better choice. Let's first look at how the example
client sends operations to the StockNotification
web service. We have implemented the client using two distinct
techniques. The first technique uses the WebLogic Workshop-generated
proxy library and is implemented by the makeCallUsingBEAProxy()
method of
the StockNotificationClient
class:
public void makeCallUsingBEAProxy() {
try {
// Get the proxy instance so that we can start calling the web service operations
StockNotificationSoap sns = null;
StockNotification_Impl snsi = new StockNotification_Impl();
sns = snsi.getStockNotificationSoap();
// Register our conversational handler so that it can properly set our
// our conversation headers with a callback location
QName portName = new QName("http://www.openuri.org/",
"StockNotificationSoap");
HandlerRegistry registry = snsi.getHandlerRegistry();
List handlerList = new ArrayList();
handlerList.add(new HandlerInfo(ClientConversationHandler.class, null, null));
registry.setHandlerChain(portName, handlerList);
// Register, call some methods, then sleep a bit so that our callback
// location can receive some notifications and finally finish the conversation.
sns.registerForThresholdNotif("AAAAA", "100");
sns.addToRegistration("BBBBB", "5");
StockList stocks = sns.getAllRegisteredStocks();
...
sns.endAllNotifications();
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
As you can see in this code segment, we are able to use the classes
generated by clientgen as we would normally. The only thing that
stands out in this code as compared to the invocation of a web
service without callbacks is the instantiation and registration of a
client-side SOAP message handler at the beginning of the method.
This handler intercepts the outgoing SOAP messages and adds the
necessary conversational headers. Every time a SOAP request message
is sent from the client process the handleRequest()
method of our message handler is called with a MessageContext
object containing information about the outgoing SOAP request. Below
is a code segment of our example client’s message handler:
package weblogic.webservice.core.handler;
...
public class ClientConversationHandler
extends GenericHandler {
public boolean handleRequest(MessageContext ctx) {
...
if (phase.equals("START")) {
headerElement
= (SOAPHeaderElement) header
.addChildElement("StartHeader",
"con",
"http://www.openuri.org/2002/04/soap/conversation/");
headerElement.addChildElement("con:callbackLocation")
.addTextNode(CALLBACK_URI);
}
else if (phase.equals("CONTINUE") || phase.equals("FINISH")) {
headerElement
= (SOAPHeaderElement) header
.addChildElement("ContinueHeader",
"con",
"http://www.openuri.org/2002/04/soap/conversation/");
}
headerElement.addChildElement("con:conversationID").addTextNode(convID);
...
}
}
The
proxy library generated by the BEA clientgen tool already uses a
default client-side SOAP message handler that creates the
conversational StartHeader
header element. Unfortunately, currently clientgen output does not
support callback operations and therefore the default handler does
not create the required callbackLocation
sub-element. We therefore have to ensure that our own message
handler overrides the default conversational handler so as to manage
the extra task of creating the callbackLocation
header. We do this by ensuring that our handler is created within
the same package as the default handler, namely
weblogic.webservice.core.handler,
and that it has the exact same class name as
ClientConversationHandler.
Note that this technique involves overriding the existing
conversation message handler and therefore is not guaranteed to be
forward-compatible. This example, however, does show how a message
handler could be used for the creation of the callback headers and
this concept can be used with any other web services framework
supporting SOAP message handlers.
An
alternative to the technique of using SOAP message handlers along
with proxy classes is to use the JAXM API to directly create the
SOAP request messages and send them to the web service. This is the
second technique used by our example client and is implemented
within the makeCallsUsingJAXM() and
callMethodFromFile()
methods of the StockNotificationClient
class:
public void makeCallsUsingJAXM() {
callMethodFromFile("C:\\registerForNotifSOAP.xml");
...
callMethodFromFile("C:\\endAllNotifSOAP.xml");
}
private void callMethodFromFile(String fileName) {
try {
// Create a SOAP connection object
SOAPConnectionFactory scf = SOAPConnectionFactory.newInstance();
SOAPConnection conn = scf.createConnection();
// Create the SOAP request message from the specified file
MessageFactory mf = MessageFactory.newInstance();
SOAPMessage msg = mf.createMessage();
SOAPPart sp = msg.getSOAPPart();
// Read the file content into a string and replace the conversation ID with
// the current one.
String fileContent = readFileIntoString(fileName);
fileContent = fileContent.replaceFirst(CONV_ID_HOLDER,
ConvIDGenerator.getInstance().
getConversationID());
StreamSource prepMsg = new StreamSource(new StringBufferInputStream(
fileContent));
sp.setContent(prepMsg);
msg.saveChanges();
// Make the actual call
conn.call(msg, TARGET_SERVICE_URI);
conn.close();
}
catch (Exception ex) {
throw new RuntimeException(ex);
}
}
The
callMethodFromFile()
method reads the SOAP message from the specified file, replaces the
message’s conversation ID with the client’s current
conversation ID, and finally makes the actual call. When using the
JAXM approach, the Java to XML mapping of operation parameters and
return values must be implemented by the client. We have avoided
this task by using pre-generated SOAP messages (that already contain
the required parameter values) from the file. Although this approach
serves as a good conceptual illustration, it is most likely not a
feasible solution for clients containing a reasonable level of
complexity. Such clients must create the messages programmatically
from scratch using JAXM along with a Java to XML mapping API.
Now
that we have reviewed the areas of our client responsible for
creating the web service operations, let's turn to the
implementation of the client’s callback endpoint and the
processing of the callback messages coming from the web service.
Since our callback endpoint needs to be able to handle the incoming
HTTP requests, a servlet could be a logical choice. Further, it
would be highly desirable to have a servlet that is able to extract
the SOAP messages included within the incoming HTTP requests and
provide it to us in an easy-to-use form. The ReqRespListener
interface and the JAXMServlet
class of the javax.xml.messaging
package provide us with this functionality. Once properly deployed,
servlets that use this interface and class will have all targeted
HTTP post requests automatically converted to SOAPMessage
instances passed to us through the onMessage()
method. The following code shows the onMessage()
method implementation for our example client.
public class CallBackHandlerServlet
extends JAXMServlet
implements ReqRespListener {
public SOAPMessage onMessage(SOAPMessage message) {
try {
// We have just received a SOAP message at the callback
// endpoint. In this example we will just assume that
// the message received is in fact the onThresholdPassed
// callback request. However, a servlet handling
// multiple callbacks cannot make this assumption and
// should process the SOAP message to see what callback
// it relates to and do as appropriate for each.
String stockTicker = extractOnThresholdPassedArgument(message);
System.out.println("[DEV2DEV CALLBACK EXAMPLE]: Received treshold notification
for: " + stockTicker);
// Now we have to create a proper response to the callback
// request. Returning this response as part of the onMessage()
// method will ensure that the SOAP response gets back to the server.
SOAPMessage response = createThresholdPassedResponse();
return response;
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
...
}
Once a
callback request message is received by the onMessage()
method, our client then extracts the stock ticker argument, prints
it to the standard output, and creates the correlated response
message that is returned to the web service by our servlet. Clients
that use web services that contain more than a single callback
operation will also need to dispatch the incoming request to an
appropriate area of client code based on the corresponding callback
operation.
Although
our servlet contains the appropriate code for the processing of
incoming onThresholdPassed()
callback messages from the web services, it needs to be deployed
within a servlet container listening on the callback URI to complete
the implementation of the callback endpoint. The Jetty
open source project contains a lightweight servlet container that
can be embedded within our client. The deployment of the
CallBackHandlerServlet
servlet within an embedded Jetty container allows us to have the
callback endpoint reside within the client Java process. The
StockNotificationCallbackProcessor
class is a utility class used by our client to start the Jetty
server and deploy the CallBackHandlerServlet
servlet:
public class StockNotificationCallbackProcessor {
public static final String CALLBACK_SERVER_PORT = ":8181";
public static final String CALLBACK_SERVLET_CLASS =
"asynchwsconsumer.CallBackHandlerServlet";
public static final String CALLBACK_SERVLET_NAME = "CallBackHandlerServlet";
private HttpServer server;
public StockNotificationCallbackProcessor() {}
public void startListening() {
try {
// Configure the server so that we listen on the callback URI
server = new Server();
server.addListener(CALLBACK_SERVER_PORT);
ServletHttpContext context = (ServletHttpContext) server.getContext("/");
context.addServlet(CALLBACK_SERVLET_NAME, "/*",
CALLBACK_SERVLET_CLASS);
// Start the http server
server.start();
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
public void stopListening() {
try {
server.stop();
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
}
The
startListening()
method deploys the CallBackHandlerServlet
on port 8181 and configures the embedded servlet container so that
all HTTP requests are directed to this port. This method also starts
the embedded servlet container.
Now
that we have examined both the creation of the web service
operations and the processing of the callback operations, let's see
how the main()
method of our example client uses these elements:
public static void main(String[] args) {
StockNotificationClient snClient = new StockNotificationClient();
snClient.snCallbackProcessor.startListening();
snClient.makeCallUsingBEAProxy();
ConvIDGenerator.getInstance().resetConversationID();
snClient.makeCallsUsingJAXM();
snClient.snCallbackProcessor.stopListening();
}
As you can see, the method starts by setting up the callback endpoint
(which, as we saw before, involves starting the embedded servlet
container and deploying the callback processing servlet to it). The
method then uses the BEA clientgen proxy to make calls on the stock
notification web service. Given that this interaction with the web
services completes the conversation by calling the
endAllNotifications() end operation,
the client needs to change the conversation ID to start a new
conversation. Our client uses the ConvIDGenerator
singleton class to manage conversation IDs, and that class uses the
java.rmi.dgc.VMID
class to create properly formatted unique conversation IDs.
Once
the conversation ID has been changed, the client then uses JAXM to
once again make calls on the stock notification web service. Finally,
the client stops the embedded servlet container and the process
ends.
We
have now completed our analysis of the example client that can be
downloaded as part of this article. When you run this client against
the stock notification web services, both the client and the web
service will display messages to the standard output describing the
various phases of the conversation including the processing of the
callback operations.
Conclusion
This
article shows the steps involved in the creation of a stand-alone
client process for web services containing callback operations. Such
a client needs to manage the conversational aspects of the web
service calls and provide an endpoint that can be used by the web
service for callback operations.
References
Reza Shafii works for BEA Systems professional services.
|