• Feeds

    Subscribe in a reader

  • Ads

Adventures in Next-Hop Routing (part 1 of n)

I’ve been playing around a bit with the routing samples included with WSE2. Since WS-Routing has now been subsumed by WS-Addressing, I wanted to see what the changes were to the routing story in WSE2. If you’re interested in doing the same thing, the best place to start is to look at the Routing sample that ships with WSE2. This sample is pretty good if you’re only interested in routing to HTTP endpoints. Routing to non-HTTP transports does not seem to be supported by the SoapHttpRouter (apparently, it’s called the SoapHttpRouter for a reason).

Given that I’ve been working with custom (non-HTTP) transports, I thought it might be interesting to try and create a transport-agnostic SOAP router. Conceptually, routing SOAP messages is a pretty straightforward endeavor (side note: “pretty straightforward” is software-speak for “harder than you’d think”…). Logically, a router is just a special kind of service. The router looks at the destination address of an incoming message, indexes into some table of forwarding addresses, and then forwards on the message to the new address. Easy cheesy, right? Well, pretty much – if you look at the code at the bottom of this post, you’ll see that the code isn’t really all that complex. The ReferralCache class (for now) does most of the work involved in maintaining the table of forwarding addresses, so all I really had to do was write the core messaging code. The implementation I have right now is far from done (and probably far from correct, at this point), but it does forward messages from one service to another.

Routing Roadblock #1: What endpoint is my router listening on, exactly?

EndpointReferences can encode more information than just a URI. For routing, we care about two properties: Address and Via. Given Hervey’s comments about TransportAddress, it makes sense to use the Address property of an EPR to uniquely identify the service and the Via (TransportAddress) to encode delivery information. I wanted to design my router implementation so routing would take place solely based on the Via and ignore the Address URI.

Since WSE2’s message dispatch algorithms involving matching both Address and Via, I needed to construct an endpoint that represented “any Address with this Via” and set up my router to listen on that endpoint. It turns out that there is a standard address URI called the AnyRole which can be used as a wildcard for situations like this. This special URI is represented as a constant on the WSAddressing class in Microsoft.Web.Services2.Addressing. Using this wildcard, I was able to add my router to SoapReceivers using this code:

   EndpointReference routerEpr = new EndpointReference( WSAddressing.AnyRole );
   routerEpr.Via = new Via( new Uri( “soap.tcp://localhost:2323” ) );

This makes my router agnostic to Address, which is what I wanted.

Routing Roadblock #2: “This envelope’s already been processed!”

If you look at the code, you’ll see that I’m duplicating the contents of the SoapEnvelope received by the router and forwarding on a new message. My first attempt did not do this; it did in-place alterations on the message passed to Receive() and resent the modified message using a SoapSender. However, I got an exception when I tried to resend the message. Basically, once a message has been processed its useful life is over; you can’t resend it again. And the “IsProcessed” bit is not user-settable, either, so my only option was to duplicate the contents of the old message using the LoadXml() function on SoapEnvelope.

Routing Roadblock #3: What to do about MessageId?
Every message carries around a unique MessageId as part of its addressing headers. This MessageId is very important, because it’s how the message originator will correlate subsequent responses back to the message that evoked them (responses will carry the original MessageId in the RelatesTo header). If the originator is using a duplex (asynchronous) messaging pattern, the RelatesTo header will be the only mechanism of contextualizing the response. Thus, it’s important for any intermediaries to preserve MessageId as a message flows through the chain – otherwise, everyone will get confused.

Since MessageId is read-only, I ended up having to reuse the AddressingHeader structure from the original message as the addressing header block for the forwarded message. If I had done a copy-by-value here, messages would spontaneously take on a new MessageId as they passed through the router.

Solving these 3 problems got me to a very simple (read: dumb) implementation that can forward requests to different addresses and transport types. The implementation below covers the outbound case, but does not do anything to fix up addressing headers relating to response messages (ReplyTo/FaultTo). There’s a reason for this, which I’ll have to cover in a different post because this one has rambled on for long enough already.  

Code: SoapRouter.cs.txt

#1 mark on 6.04.2004 at 9:51 AM

If I would route/adapt a soaprequest for an existing .NET 1.1/SOAP Toolkit web service, SoapReceiver could be the right approach?

#2 Steve Maine on 6.04.2004 at 9:57 AM

Either that, or SoapService (which is a subclass of SoapReciver). SoapService gives you the added benefit of exposing service operations via [SoapMethod], which gives you more of an ASMX-style programming model.Depends on what you're trying to do.

#3 mark on 6.04.2004 at 12:23 PM

I would expose a WS-I BP conmpliant ws router to the internet, re-using existing internal web services already developed, without rewriting proxies.it was just an idea.. ;-)