 | Level: Intermediate Richard Sitze (rsitze@us.ibm.com), Advisory Software Engineer, IBM Software Group
28 Apr 2004 In this article, the author examines how JAX-RPC SOAP handlers process SOAP message headers. Specifically, he shows how a handler adds a SOAP header to an outgoing message and how a corresponding handler removes the SOAP header from an incoming message. In addition, he presents the JAX-RPC programmatic configuration and deployment models as they relate to this topic.
In this article, I show you how you can extend a Web service, defined by WSDL, by adding SOAP headers.
I show how SOAP handlers create and process SOAP message headers
and how to configure handlers appropriately.
Basic JAX-RPC SOAP handlers
Let's start by examining a JAX-RPC invocation in Figure 1.
Figure 1. JAX-RPC/SOAP message flow
A single JAX-RPC invocation is represented on the wire
as two SOAP messages: the request and the response.
A WSDL document defines the service interface, and therefore the SOAP content,
for both the request and the response.
When you deploy a JAX-RPC SOAP handler into the message flow,
you gain access to both messages
involved in the remote procedure call.
JAX-RPC SOAP handlers are JAX-RPC handlers in that they support
the RPC invocation model via the
handleRequest(),
handleResponse(), and
handleFault() methods.
They are SOAP handlers in that message content
is represented to the handler as SOAP content
in the form of a SAAJ object (see the Resources section for more on these objects).
 |
J2EE Web services SOAP handlers
The J2EE Web services runtime, based on JAX-RPC,
enforces restrictions
on the changes that a SOAP handler may make to a SOAP message.
Even here there are ample opportunities for mistakes.
Responsibility remains with the handler for correct behavior.
|
|
Handlers manipulate primitive SOAP content directly,
and are not aware of expected content as defined in WSDL.
The handler developer is responsible
for the correctness of the SOAP message,
in contrast to the strongly-typed JAX-RPC service interfaces.
Extending services
SOAP headers provide the channel that allows
out-of-band information to be exchanged between
the client and service.
This exchange of additional information provides
an opportunity for extending the behavior
represented by a service interface
without changing the interface and, therefore, without changing WSDL.
This is valid because SOAP headers need not be defined in WSDL.
(For more on this, take a look at WS-I Basic Profile Version 1.0a; see Resources for a link.)
While a message can be extended with a SOAP header
not defined by WSDL/Schema,
the content of the SOAP header should still be well-defined.
All producers and consumers of the header
must agree on its content.
JAX-RPC SOAP handlers are the operational means
for working with such out-of-band information. You can specifically work with such information by deploying a handler on the client
and a corresponding handler on the service, as shown in Figure 2.
Figure 2. JAX-RPC/SOAP message flow with handlers
With respect to the SOAP header information,
the four processing points represented by the two handlers are:
-
Client-side
handleRequest():
Attach extended content into the outgoing request message.
-
Server-side
handleRequest():
Extract extended content from the incoming request message.
-
Server-side
handleResponse()/handleFault():
Attach extended content into the outgoing response message.
-
Client-side
handleResponse()/handleFault():
Extract extended content from the incoming response message.
Note the point of information exchange within each handler
from the request to the response message
using the MessageContext,
as discussed in the article "How to create a simple JAX-RPC handler" (see Resources for a link).
Let's explore these concepts in the context of a specific example.
Example: Processing message content
For this simple example,
I deploy collaborating handlers that
sign outgoing messages by placing a signature in the header
and verify that the signature on incoming messages is correct.
The signature is a representation of the message body;
if the body has been changed, the signature verification will fail.
The signer's name must be in the handler's configuration;
the client and service are not required to sign
using the same name.
You need an algorithm for signing messages,
which will be represented by the SignatureTool interface.
Both the client and service handlers must
sign outgoing and verify incoming messages,
so an abstract class, SignHandler, will provide the common code.
ClientSignHandler and ServiceSignHandler
extend SignHandler
and bring everything together.
Listing 1. SignatureTool.java
interface SignatureTool {
/**
* @return Result of signer signing content.
*/
public SOAPElement getSignature(String signersName,
SOAPElement content) throws SOAPException;
/**
* @return true if content was signed, unchanged, by signer of signature.
* This is only true if the content is the same content signed originally
* by a signer, resulting in signature.
*/
public boolean isSignatureValid(SOAPElement signature,
SOAPElement content) throws SOAPException;
}
|
Handlers can sign messages
by calling SignatureTool.getSignature()
with content extracted from an outgoing SOAP message.
Likewise, verification of the signature is performed
by calling isSignatureValid()
with the signature and the content extracted from an incoming SOAP message.
Processing outgoing messages
You process an outgoing message by adding a signature to
the message as a new header block.
SignHandler.signOutgoing(), shown in Listing 2, demonstrates how to
add a header block to an outgoing SOAP message.
This method will:
- Locate
SOAPHeader in the envelope.
- Add a new child SOAP header element.
- Create and set content for the new header element.
Listing 2. SignHandler.signOutgoing()
abstract class SignHandler implements Handler
{
. . .
/**
* Name of required property, in HandlerInfo handler configuration,
* that specifies who signs outgoing messages.
*/
public static String SIGNERS_NAME_PROPERTY;
private SignatureTool signatureTool;
private HandlerInfo info;
/**
* Obtain signer's name from handler configuration (HandlerInfo),
* and sign message.
* @throws SignException if SIGNERS_NAME_PROPERTY is not available
* in the handler config.
*/
public void signOutgoing(SOAPMessageContext mc) throws SignException {
// SIGNERS_NAME_PROPERTY is required to be on the configuration.
Map config = info.getHandlerConfig();
Object nameObj = config.get(SIGNERS_NAME_PROPERTY);
String name = (String)nameObj;
try {
// Dig down into message, locate or create header block.
SOAPMessage msg = mc.getMessage();
SOAPPart part = msg.getSOAPPart();
SOAPEnvelope envelope = part.getEnvelope();
SOAPHeader header = envelope.getHeader();
/**
* Create new header element.
* We don't specify a role on this header element,
* meaning the target role is the "ultimate destination".
*/
SOAPHeaderElement headerElement
= (SOAPHeaderElement)header.addChildElement(SIGN_ELEMENT,
SIGN_PREFIX,
SIGN_NS_URI);
// Locate portion of message content that is to be signed.
SOAPElement content = getContent(part);
/**
* Create new element representing signature,
* and add as child to new header element.
*/
SOAPElement element = signatureTool.getSignature(name, content);
headerElement.addChildElement(element);
} catch (SOAPException e) {
e.printStackTrace();
throw new SignException("Unable to sign message", e);
}
}
. . .
// See resources for full sample code.
}
|
After processing, a SOAP envelope will look like the one in Listing 3.
Listing 3. Outbound SOAP message
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<!-- original header content -->
<!-- New header element, inserted by handler -->
<sign:sign xmlns:sign="uri://org.example.webservices.signature.Sign">
<!-- output of SignatureTool.getSignature() -->
</sign:sign>
</soap:Header>
<soap:Body>
<!-- original body content -->
</soap:Body>
</soap:Envelope>
|
Processing incoming messages
You process an incoming message to verify that the signature is valid.
SignHandler.checkIncoming(), shown in Listing 4,
demonstrates how to locate a header block, a non-obvious process.
This method will:
- Locate
SOAPHeader in the envelope.
- Determine the set of roles in which the current SOAP node is acting (more on this below).
- Determine the set of headers that the current handler understands.
- Process headers that this handler understands and that are bound
to the set of roles in which the current SOAP node is acting.
Listing 4. SignHandler.checkIncoming()
abstract class SignHandler implements Handler
{
. . .
/**
* Name of required property, in HandlerInfo handler configuration,
* that specifies who signs outgoing messages.
*/
public static String SIGNERS_NAME_PROPERTY;
private static QName SIGN_HEADER;
private SignatureTool signatureTool;
private HandlerInfo info;
/**
* Look for signature on incoming message.
* If signature not found, then continue message processing.
* If signature is found, then verify that it is "correct".
* If correct, then continue message processing.
* If not correct, then throw SignException.
*/
public void checkIncoming(SOAPMessageContext mc) throws SignException {
try {
SOAPMessage msg = mc.getMessage();
SOAPPart part = msg.getSOAPPart();
SOAPEnvelope envelope = part.getEnvelope();
/**
* Locate portion of message content that is to be signed.
*/
SOAPElement content = getContent(part);
/**
* Dig down through SOAP headers looking for matches to
* SIGN_HEADER, that are also targeted for this SOAP Node.
*/
SOAPHeader header = envelope.getHeader();
if (header != null) {
/**
* The roles the node acts in are specified by the
* MessageContext.getRoles() method.
* If roles not found, default to "ultimate destination".
*/
String[] roles = mc.getRoles();
if (roles == null || roles.length == 0) {
roles = new String[] { "" };
}
for (int ridx = 0; ridx < roles.length; ridx++) {
String role = roles[ridx];
/**
* Examine headers bound to each role this node acts in.
* Headers are determined to be targeted for a SOAP Node by
* matching the node's roles with the header's actor role.
*
* So now go through list of headers associated with
* the role we are currently working on.
*/
Iterator headerElementIter
= header.examineHeaderElements(role);
while (headerElementIter.hasNext()) {
SOAPHeaderElement headerElement
= (SOAPHeaderElement)headerElementIter.next();
// Is header recognized by this handler?
Name headerElementName
= headerElement.getElementName();
if (equals(headerElementName, SIGN_HEADER)) {
/**
* Look for SOAPElement(s) in header,
* ignoring mixed content.
*/
Iterator headerIter
= headerElement.getChildElements();
while (headerIter.hasNext()) {
Object elementObj = headerIter.next();
if (elementObj instanceof SOAPElement) {
SOAPElement element
= (SOAPElement)elementObj;
signatureTool.isSignatureValid(element,
content);
}
}
}
}
}
}
} catch (SOAPException e) {
e.printStackTrace();
throw new SignException("Unable to verify signature", e);
}
}
. . .
// See resources for full sample code.
}
|
More on roles
The roles in which a SOAP node acts are expected to be available
from the MessageContext.
The code in Listing 4 demonstrates that WebSphere Application Server
maps the empty string to the ultimate destination
(the default actor).
The proper value for the ultimate destination is
not clearly specified by the SOAP 1.1 specification,
so this may vary from implementation to implementation.
A more detailed discussion will be deferred to a future tip.
The handlers
The SignHandler class is oriented
toward processing incoming and outgoing messages.
The handler interface is oriented toward processing request and response messages.
The two come together in the handler implementations.
ClientSignHandler, shown in Listing 5, is very simple.
It directs the request message context for processing as an outgoing message
and the response/fault message context for processing as an incoming message.
This complements the ServiceSignHandler.
Listing 5. ClientSignHandler
public class ClientSignHandler extends SignHandler
{
public boolean handleRequest(MessageContext mc) {
signOutgoing((SOAPMessageContext)mc);
return true;
}
public boolean handleResponse(MessageContext mc) {
checkIncoming((SOAPMessageContext)mc);
return true;
}
public boolean handleFault(MessageContext mc) {
checkIncoming((SOAPMessageContext)mc);
return true;
}
}
|
ServiceSignHandler, shown in Listing 6, is just as simple.
It directs the request message context for processing as an incoming message
and response/fault message context for processing as an outgoing message.
This complements the ClientSignHandler.
Listing 6. ServiceSignHandler
public class ServiceSignHandler extends SignHandler
{
public boolean handleRequest(MessageContext mc) {
checkIncoming((SOAPMessageContext)mc);
return true;
}
public boolean handleResponse(MessageContext mc) {
signOutgoing((SOAPMessageContext)mc);
return true;
}
public boolean handleFault(MessageContext mc) {
signOutgoing((SOAPMessageContext)mc);
return true;
}
}
|
Configuring JAX-RPC SOAP handlers
To complete this example,
I configure and deploy the handlers.
The handlers look for a configuration property specifying
the signer's name.
More importantly,
the runtime expects all header elements processed by handlers
to be specified in the deployment information.
For the client,
I will deploy the handler using the JAX-RPC programmatic interface.
For the service,
I will deploy the handler using a
J2EE for Web Services deployment descriptor.
JAX-RPC 1.0 programmatic configuration: HandlerInfo()
HandlerInfo(), shown in Listing 7, defines the handler, its configuration,
and the header elements that the handler is expected to process.
In this example, the header elements are exposed
as the static property ClientSignHandler.HEADERS.
Listing 7. Setting up HandlerInfo()
Map hConfig = new HashMap();
hConfig.put(ClientSignHandler.SIGNERS_NAME_PROPERTY, "Linus");
HandlerInfo hInfo = new HandlerInfo(ClientSignHandler.class,
hConfig,
ClientSignHandler.HEADERS);
|
To register the handler, the HandlerInfo()
must be added (appended) to the end of the handler chain.
It's a good practice to make no assumption about handlers that may
or may not be on the handler chain.
Listing 8. Registering the handler
// Obtain service, for JAX-RPC 1.0 this may be implementation specific.
StockQuoteService service = ...;
// Must match WSDL's port QName
QName portQName = new QName("http://stock.webservices.example.org",
"StockQuote");
service.getHandlerRegistry().getHandlerChain(portQName).add(hInfo);
|
Web Services for J2EE 1.3 deployment model: webservices.xml
In a J2EE managed environment,
use of the programmatic interface will result in an exception.
The J2EE deployment descriptor for Web services, webservices.xml,
must be used to specify the handlers deployed on a port.
The Web services deployment descriptor mirrors the
information set programmatically using HandlerInfo().
It specifies
name/value pair configuration properties (init-param),
the handler class name (handler-class),
and the headers that the handler understands (soap-header). You can see all of this in action in Listing 9.
Listing 9. webservices.xml
. . .
<port-component>
<port-component-name>StockQuote</port-component-name>
<wsdl-port>
<namespaceURI>http://stock.webservices.example.org</namespaceURI>
<localpart>StockQuote</localpart>
</wsdl-port>
. . .
<handler>
<handler-name>Signature Handler</handler-name>
<handler-class>org.example.webservices.signature.
ServiceSignHandler</handler-class>
<init-param>
<param-name>org.example.webservices.signature.SignersName</param-name>
<param-value>Snoopy</param-value>
</init-param>
<soap-header>
<namespaceURI>uri://
org.example.webservices.signature.Sign</namespaceURI>
<localpart>sign</localpart>
</soap-header>
</handler>
</port-component>
. . .
|
Some best practices for implementing handlers
I've shown you a few best-practices related to designing and implementing handlers:
-
Leverage symmetry of message flow, where appropriate.
Client request processing mirrored by service response/fault processing
and service request processing mirrored by client response/fault processing,
is a recurring pattern in handler development.
Provide a common implementation based on the outbound/inbound pattern
to leverage common code.
-
Separate processing of
SOAPHeaderElement
from processing of header content:
In this sample, I separated processing of the
SOAPHeaderElement from processing of the header content.
The handler locates and processes the SOAPHeaderElement.
The structure and processing of the header element content
has been delegated to SignatureTool by abstracting
the content to a single SOAPElement.
-
Understood headers should be defined by
HandlerInfo or a deployment descriptor.
This is more of a rule than a best practice.
All headers to be processed by a handler should be defined to the
runtime via HandlerInfo or the appropriate deployment descriptor.
JAX-RPC does not provide a similar point of configuration for
headers that are created by a handler.
This topic will be revisited in a future tip.
 |
Summary
Existing SOAP-based Web services can be extended
by client/service handlers that collaborate to process
outbound and inbound messages.
Processing outbound messages is fairly straightforward,
while processing inbound messages is nontrivial.
In this tip, you saw one road map for processing inbound messages,
and learned that the runtime must always be configured
with the header elements that are to be processed by each handler.
Download | Name | Size | Download method |
|---|
| ws-tip-extend.zip | 41.0 KB | HTTP |
Resources
About the author  | |  | Richard A. Sitze is a member of the IBM WebSphere Web services development team.
He has been involved with the Apache Axis SOAP engine,
Jakarta Commons Logging,
and Jakarta Commons Discovery open source projects.
His previous work with IBM includes CORBA ORB interoperability and Internet banking services.
His work before IBM included business systems,
real-time control systems, firmware,
networking communication protocols, threaded kernels,
and multi-processor UNIX kernel development. Contact Richard at rsitze@us.ibm.com.
|
Rate this page
|  |