My first post on SOAP routing with WSE2 detailed some of the issues I ran into while trying to build a transport-agnostic replacement for the SoapHttpRouter. Although I referred to these issues as “routing roadblocks” (I’m a sucker for alliteration), I found workarounds for all of them. The result was this small chunk of code. There are problems with this code as written though: it only operates on the forward path (the Destination address) and ignores the return path (the ReplyTo/FaultTo/From headers).
Consider a duplex communication scenario. There are two services, each with a transport-agnostic Address URI. The first service, Svc1, listens for requests and sends responses. The second service, Svc2, listens for responses. When a message arrives at Svc1, a response is generated and sent back to Svc2 according to the ReplyTo endpoint present on the original message. Suppose that there are two servers involved here – Server A sends messages to Svc1 hosted on D, which processes the request and sends the message back to Svc2 on A. This general pattern of communication is illustrated by the TcpStockServiceSoapReceiver sample that ships with WSE2.
Now, let’s assume that A and D are separated by an arbitrary number of trusted intermediaries. The exact structure of this intermediary chain is unknown to both A and D; each server only knows about the next link (in deference to the “next hop” routing style preferred by WS-Addressing). Furthermore, let’s also assume that neither server can talk to each other directly. This last requirement implies that both the request and response messages need to travel through the intermediary chain. Thus, the intermediary must alter not only the Destination endpoint, but the ReplyTo/FaultTo/From addresses as well.
For the sake of example, let’s say that there are two intermediaries B and C separating our two servers. Outbound messages should flow from A -> B -> C -> D, and from D -> C -> B -> A on the return path. To figure out what sort of routing information B and C would need to store, I drew up the scenario on my whiteboard. The resultant picture looked like this:
Make this bigger -- RoutingScenario.jpg
A couple of observations: first, I constructed this scenario so that the Address URI of the destination service never changes – all routing takes place solely through the Via property of the address endpoint. Secondly, this shows that this scenario is a pure “next hop” scenario – servers have no knowledge of machines that are not directly adjacent to them.
Now let’s look at the routing tables on B for a moment. The rules for the outbound path to service one are not surprising. B listens on “Svc1 via B” and forwards the message to “Svc1 via C”. In order to preserve the return path of the response message, B must also alter the ReplyTo address and change it from “Svc2 via A” to “Svc2 via B”. What are the rules for the inbound path? According to our scenario, B must listen for responses on “Svc2 via B” and forward them on to “Svc2 via A”. Thus, B has two routing rules for the Svc2 URI.
Here is where we start to run into problems with the implementation of the ReferralCache class. The ReferralCache is essentially just a configurable URI -> URI map. However, as I’ve shown through B’s routing table, it’s not enough to ask the ReferralCache “this message is addressed to Svc2, where should it go?” because the answer depends on context. Unfortuantely, we have no way of feeding the necessary context to ReferralCache.ResolveUri(), since that function operates only on URI’s and not full EndpointReferences. Since the two entries for the Svc2 URI have different Via’s, an EndpointReference would be sufficient to differentiate the two.
The reason I included the additional intermediary C here is because it invalidates my first solution to the problem. I originally thought about having A set its ReplyTo to “Svc2/B”. That would free B from having to tweak the ReplyTo on the outbound message and therefore it would only need one routing entry to handle responses. However, it became clear that solution broke if there were multiple intermediaries in the picture. Since I’m of the opinion that the intermediary chain should be transparent so long as all those intermediaries are sufficiently trusted, a solution that worked only for exactly one intermediary was no solution at all.
The moral of the story? I don’t think I can use the ReferralCache and still get the functionality I want for my router. Not that writing an EPR->URI dictionary is so hard or anything; it just would have been nice to leverage the configurability and standardized implementation of the ReferralCache.
