Tuesday, February 22, 2011

How to Use an XML Gateway with an Asynchronous Web Service using WS-Addressing


In general synchronous web-services are simpler and more common than asynchronous web services. I like them, because for 99% of cases, the security can be done at the transport level using 2-way SSL. Asynchronous web-services introduce additional security challenges - mainly that messages are likely to be in memory or on disk where the transport is not there to keep the contents of the message secure. The purpose of this post is not to explore the security challenges of using asynchronous web-services, but another complexity - proper handling of web-services callbacks through an intermediary.

One of the main uses of an XML gateway is to encapsulate the end-point of the actual service from the caller. This approach is aligned with SOA best practices, but from a security perspective not letting people know where your service actual lives is a really good idea. This principal can also apply in the callback use case - we may want to hide the location of callback URL from the actual service - let's for the sake of this discussion consider it need to know. The end service can just callback to the gateway, and the gateway will deliver the message back to its appropriate destination.

Now assuming that we're not passing the location of the callback - in WS-Addressing this is called the wsa:ReplyTo address - then when the gateway finally receives the response, how does it know where to send the message? To solve this problem, the gateway needs to create a cache of replyTo URLs keyed off of the messageId. Each request has a wsa:MessageId. When the server sends its reply, the original message id is reference in the wsa:RelatesTo header. This way the gateway can correlate the request with the response.

Understanding the Example
I've created an example project that demonstrates how the gateway works in the above scenario. I'm using the gateway to play the role of 3 different use case actors - client, gateway, and server.


  • Client - SOAPBox is sending the initial request to the service. I created an HTTP service listening on port 11000 to serve as the destination of the final wsa:ReplyTo.
  • Gateway - When the service gets invoked, the gateway stores the wsa:ReplyTo in a cache (keyed by the wsa:MessageId) and modifies the wsa:ReplyTo and wsa:To fields accordingly. In the callback service, the gateway retrieves the original wsa:ReplyTo from the cache (pulling the key from the wsa:RelatedTo) field, and sends the request to the original wsa:ReplyTo
  • Server - This is just using the gateway to simulate the back end server. When it receives the message, it simply sticks the file on disk. The gateway has a directory scanner configured. Directory Scanner is the ability for the gateway to process files sitting on the file system. When the Directory Scanner finds a file, it modifies the response (read: appends "Hello" to the message) and then sends it to the address in the wsa:ReplyTo which is the gateway.
You can see what an end-to-end flow looks like in the Real Time Monitor

The Gateway Policy Details
The example contains policies for all three roles.


I wanted to spend some time here to review in a little more detail the gateway and what it does when it processes the request, and handles the callback. When the request arrives, the original message looks something like this:


<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">
<soap:Header>
<wsa:MessageID>uuid:6B29FC40-CA47-1067-B31D-00DD010662DA</wsa:MessageID>
<wsa:ReplyTo>
<wsa:Address>http://localhost:11000/callback</wsa:Address>
</wsa:ReplyTo>
<wsa:To>http://localhost:12000/SoapContext/SoapGreeterPort</wsa:To>
<wsa:Action>Greet</wsa:Action>
</soap:Header>
<soap:Body>
<x1:greetMeOneWay xmlns:x1="http://apache.org/hello_world_soap_http/types">
<!-- Element must appear exactly once -->
<x1:requestType>Asynch Client using WS-Addressing</x1:requestType>
</x1:greetMeOneWay>
</soap:Body>
</soap:Envelope>


The message gets picked up and processed by the following policy:



I'm using policy shortcuts to encapsulate all of the details, but you get the basic idea. The message leaving the gateway has been transformed to have the gateway address in the wsa:ReplyTo and the server's address in the wsa:To. This is the resulting message sent to the server.


<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">
<soap:Header>
<wsa:MessageID>uuid:6B29FC40-CA47-1067-B31D-00DD010662DA</wsa:MessageID>


<wsa:Action>Greet</wsa:Action>
<wsa:ReplyTo>
<wsa:Address>http://localhost:12000/SoapContext/GreetCallbackSoapPort</wsa:Address>
</wsa:ReplyTo><wsa:To>http://localhost:13000/SoapContext/SoapGreeterPort</wsa:To></soap:Header>
<soap:Body>
<x1:greetMeOneWay xmlns:x1="http://apache.org/hello_world_soap_http/types">
<!-- Element must appear exactly once -->
<x1:requestType>Asynch Client using WS-Addressing</x1:requestType>
</x1:greetMeOneWay>
</soap:Body>
</soap:Envelope>


So when the reply comes back from the server, and arrives back at the gateway, it has a new wsa:messageId, but the original messageId is available in the wsa:RelatesTo header.


<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<wsa:Action
xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" />
<wsa:MessageID
xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">
uuid:Id-0000012e5303f409-0000000000a96fd2-2
</wsa:MessageID>
<wsa:RelatesTo
xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">
uuid:6B29FC40-CA47-1067-B31D-00DD010662DA
</wsa:RelatesTo>
<wsa:To xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">
http://localhost:12000/SoapContext/GreetCallbackSoapPort
</wsa:To>
</soap:Header>
<soap:Body>
<x1:greetMeResponse
xmlns:x1="http://apache.org/hello_world_soap_http/types">
<!-- Element must appear exactly once -->
<x1:responseType>
Hello Asynch Client using WS-Addressing
</x1:responseType>
</x1:greetMeResponse>
</soap:Body>
</soap:Envelope>


The callback policy then picks this message up and applies the following filters:




This is the basic caching pattern used by the gateway. The cache's key is the wsa:messageId set by the incoming request. This can then be retrieved from the cache by pulling the wsa:RealtesTo messageId. The resulting URL is then set as the destination (wsa:To) of the response, and the reply is sent. The final message looks like this:


<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<wsa:Action
xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" />
<wsa:MessageID
xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">
uuid:Id-0000012e53675a57-0000000001f76db2-18
</wsa:MessageID>
<wsa:RelatesTo
xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">
uuid:6B29FC40-CA47-1067-B31D-00DD010662DA
</wsa:RelatesTo>
<wsa:To xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">
http://localhost:11000/callback
</wsa:To>
</soap:Header>
<soap:Body>
<x1:greetMeResponse
xmlns:x1="http://apache.org/hello_world_soap_http/types">
<!-- Element must appear exactly once -->
<x1:responseType>
Hello Asynch Client using WS-Addressing
</x1:responseType>
</x1:greetMeResponse>
</soap:Body>
</soap:Envelope>
I've exported this project and added it to the Vordel Incubator. You can download the config here. Let me know what you think.















1 comment:

  1. Note: Changed the location of the file since the original post to include the version of the gateway

    ReplyDelete