Thursday, September 27, 2007

Routing and Referral

You can route messages in three ways using the facilities provided by WS-Routing and WS-Referral. We’ll look at each of these methods in turn.

We’ll start by looking at server-controlled routing, in which the route the message takes is determined by the details specified in a referral cache at the server. The client that makes a request to a Web service is unaware that the request has been forwarded to another destination to be processed.

The second form of routing that we’ll look at is a slight modification of server-controlled routing, in which the destination of the request is determined based on some other factor—the request might be forwarded to a different location depending on the time or day or the actual contents of the request. As with server-controlled routing, the client has no knowledge of the request being handled by a different destination than was requested.

The final form of routing we’ll look at is client-controlled routing, in which the client specifies the route that it wants the message to take. Each step along the way gets a chance to interact with the message, and possibly alter the route of the message, before passing it on to the next step.


The Path Object
The cornerstone of your control over message routing is the Path object. This object contains the routing details for the current request, and WSE uses it to determine where the message is actually sent. Instances of the Path object are used in two places.

The first place that we see the Path object is within the SoapContext object. As you’ll soon see when we look at the way that WSE implements routing, the usefulness of the Path object that is available within the SoapContext object is questionable. The Path object within the request’s SoapContext object is populated by the Routing input filter from the routing details, but the Path object within the response’s SoapContext object is undefined and cannot be altered. You can interrogate the Path object within the request’s SoapContext, but unless you manually route the incoming message, all you have is a read-only copy of the routing information.
Not until you start using the routing facilities that WSE provides do you see a Path object that is useful. As you’ll see in the next section, WSE provides a much more elegant way of implementing routers. Here’s where the second instance of the Path object makes an appearance: when you’re implementing a WSE router, you’re passed an instance of the Path object that contains the outgoing path that messages leaving the router will take. We’ll see this shortly.


Property
Description

Fwd
Maps to the element and contains the forward path details of the message. This property returns a collection of Via elements that map to the contained elements.

Rev
Maps to the element and contains the reverse path details of the message. This property returns a collection of Via elements that map to the contained elements.

To
Maps to the element and contains the ultimate destination of the message.


How WSE Routing Works


Routing in WSE is implemented by using a custom HTTP handler for the requested URL that can route the incoming message to the next intermediary. In WSE, this custom handler is the RoutingHandler class.
When an incoming message is received, the Routing input filter parses the routing header and populates the Path object of the request’s SoapContext object. Control is then passed to the ProcessRequest method of the RoutingHandler class; this is where all of the work to route the message occurs. The processes that occur in ProcessRequest are shown in Fig




Within ProcessRequest, we use the details in the Path object of the request’s SoapContext object to create an outgoing Path object that contains the details about the route the message must take after it leaves this router. The outgoing Path object is basically a copy of the path specified by the request’s SoapContext object, except that if the current router is the first element in the collection, it is moved from the Fwd collection to the Rev collection. (We want to keep details of the reverse path, and we don’t want to reroute to the same router!)

The ProcessRequestMessage method is then called and is passed a copy of the outgoing Path object—if the outgoing Path object isn’t populated, the ProcessRequestMessage method checks the referral cache for any details for this router, and these are copied from the referral cache to the Path object.

When control is passed back to ProcessMessage, the returned Path object is interrogated to determine the next intermediary for the message. If there are any Via elements that can be routed, the first one in the collection is used; if there are no Via elements, a call is made to the router specified in the To element. If we can’t determine a router that we can forward the message to, an exception of type SoapHeaderException is thrown.

Before making the call to the next router, the routing handler sets the Path object of the outgoing SoapContext object to the Path object returned from ProcessRequestMessage. The Routing output filter then uses the Path object to add the routing header to the request.

When control is returned from the next router, we call the ProcessResponseMessage method; the message returned from the remote call can then be modified if required.



Routing and HTTP
At the moment, the only transport protocols available for our Web services are HTTP and HTTPS. Due to the request/response nature of these protocols, we have a reverse path to the caller intrinsically defined, and we don’t need to define the elements in the routing header.

Although WS-Routing specifies the concept of a reverse path, when you use HTTP or HTTPS this path has no real meaning. It’s only when we start looking at other protocols that don’t have an intrinsic reverse path, such as Microsoft Message Queuing (MSMQ), that we need to concentrate on those details.

The current implementation of the routing input and output filters doesn’t populate the reverse path details by default—if there is no element in the incoming routing header, no header will be added to the request to the next router. We can, however, force the filter to populate the header, and when we look at client-controlled routing later in the article, you’ll see that if we want to keep track of the route that our message has taken to reach its destination, we must force the use of the reverse path.


The Destination Web Services
For all of our routing scenarios, we’ll use Web services that have exactly the same functionality. We’re not concerned with what we can do at the destination Web service; we’re concerned only with how the message arrives at that Web service.

Each Web service that we’ll use exposes one method, Echo, which simply returns a string containing the URL of the executing Web service as well as the route that the message took to reach it.

The route is determined by looking at the Path object of the request’s SoapContext object because this contains the route that the received message took.

We can interrogate the collections returned from the Rev and Fwd properties of the Path object and concatenate these to the return string, showing the route that the message took.



Server-Controlled Routing


Server-controlled routing is the easiest form of routing to implement. It simply allows you to forward a request to a different URL. This all happens without the knowledge of the client that is making the request to the Web service.

You can use this form of routing in several situations. You can use it to forward requests to a specific URL behind a firewall . Or you might use it if you know that the service you’ll ultimately use is likely to move and you don’t want to have to update all the clients when this occurs.







http://localhost/wscr/14/RouterA.asmx





http://localhost/wscr/14/TellMe.asmx


uuid:fa469956-0057-4e77-962a-81c5e292f2aa


We now need to tell the RoutingHandler that a referral cache is being used. As with all WSE configuration items, this is done in the element within web.config, as follows:







We must therefore manually provide a WSDL document for the RouterA.asmx Web service that’s based on TellMe.asmx, the real service we’re routing to. We can do this quite easily by viewing the WSDL for TellMe.asmx, saving it, and changing the reference to TellMe to RouterA. This effort involves a simple Find and Replace within the WSDL document that we’ve saved. The Replace will change names and references in the , , , and elements in the WSDL as well as the location attribute of the element:

location="http://localhost/wscr/14/RouterA.asmx" />
We could get away with changing only the element, but changing all references to the original Web service provides a further separation between the router and the service that will actually process the message.


Custom Routing


Another server-controlled routing option we can consider is routing the message based on the content of the message or some external factor.

We can easily accomplish this by deriving a new class from RoutingHandler and using this as the handler for the Web services we want to route.

By deriving from RoutingHandler, we gain the basic routing functionality that RoutingHandler provides. ProcessRequestMessage is called by ProcessRequest and we override the ProcessRequestMessage method to perform our custom routing; whatever Path details we specify will be used by the ProcessRequest method and the message will be routed to the correct destination.


public class CustomRouter : Microsoft.Web.Services.Routing.RoutingHandler
{

}




protected override void ProcessRequestMessage(SoapEnvelope message,
Path outgoingPath)
{
Via destinationService;

// get the time of day
DateTime dateNow = DateTime.Now;

// set the destination correctly
if(dateNow.Hour >=0 && dateNow.Hour <8)
{
destinationService = new Via(TokyoUri);
}
else if(dateNow.Hour >=8 && dateNow.Hour <16)
{
destinationService = new Via(LondonUri);
}
else
{
destinationService = new Via(NewYorkUri);
}

// now go to the route we've specified
outgoingPath.Fwd.Insert(0, destinationService);
}
This code is pretty standard. We have a series of conditions that check the time and set the destinationService object to the correct URL. To produce more maintainable code, we extract the URLs of the three Web services from web.config in the constructor for the class and store them as TokyoUri, LondonUri, and NewYorkUri.



Client-Controlled Routing


Client-controlled routing allows the client to specify the Web service it wants to call and to specify the route that the message takes to the destination Web service.


We first create an instance of the proxy class for the Web service just as we would for any other proxy class. We then have to add the routing details to the message.

As you saw when we looked at the Path object, the forward path is contained in the Fwd property. This is a collection of Via objects, and we add a new Via object to the Path for each router that we want to add to the route. The Via object constructor requires a Uri object, so we create one of these inline, passing in the URL that we want to route the request through:

// add the route to the message
proxy.RequestSoapContext.Path.Fwd.Add(new Via(new
Uri("http://localhost/wscr/14/" + cboFirst.Text)));
proxy.RequestSoapContext.Path.Fwd.Add(new Via(new
Uri("http://localhost/wscr/14/" + cboSecond.Text)));
proxy.RequestSoapContext.Path.Fwd.Add(new Via(new
Uri("http://localhost/wscr/14/" + cboThird.Text)));




The SOAP Messages


http://localhost/wscr/14/TellMe.asmx

http://localhost/wscr/14/RouterE.asmx
http://localhost/wscr/14/RouterF.asmx
http://localhost/wscr/14/RouterG.asmx





Working Routers
The routers we’ve looked at so far have all met the standard definition of a router— receive a request and forward it on to a new destination. However, we introduced a little variety into this paradigm when we looked at custom routers—rather than simply routing the message to a predetermined location, we performed some processing to determine the route.

We still haven’t covered every possible scenario. When we looked at client-controlled routing, you saw that this approach would be pointless unless the routers actually did some work. Otherwise, we could just jump directly to the ultimate destination.

What we need is a new type of router, a “working router,” that performs a real processing task and uses routing information that is specified elsewhere—either in the referral cache or in the routing header in the incoming message.

To create a working router, we follow the same process as for a custom router—we derive a new router from RoutingHandler and override the ProcessRequestMessage method. The difference between a custom router and a working router is that in a working router we don’t specify any routing details within the overridden ProcessRequestMessage method.

If we’re using client-controlled routing and we want to have a working router, we can simply create a derived class that specifies no routing details because all of the information to route the message is already contained with the Path object that ProcessRequestMessage receives. The basic approach is as follows:

public class WorkingRouter : Microsoft.Web.Services.Routing.RoutingHandler
{
protected override void ProcessRequestMessage(SoapEnvelope message,
Path outgoingPath)
{
// do some work here
}
}
For server-controlled routing, the process isn’t as simple as with client-controlled routing because the referral details are manipulated by the ProcessRequestMessage method that we’ve overridden. Within our overridden class, we need to call the ProcessRequestMessage on the base class to determine the route from the referral cache:

public class WorkingRouter : Microsoft.Web.Services.Routing.RoutingHandler
{
protected override void ProcessRequestMessage(SoapEnvelope message,
Path outgoingPath)
{
// do some work here
// get the routing information from the referral cache
base.ProcessRequestMessage(message, outgoingPath);
}
}
If we’re not sure whether the router will exist in a server-controlled or a client-controlled situation, we must use the server-controlled version of the working router. As you’ll recall, if we have routing details already specified, any details we specify in a referral cache are ignored



Routing and Timestamps


When we looked at timestamps above section, we mentioned that routing also affects the timestamps that are generated. When we route requests to Web services using any of the routing methods discussed in this article, we also record the timestamp details for the router in a element in the timestamp header.

Thankfully, the Timestamp filters handle this automatically; whenever we pass through a router, a Received element is added to the timestamp header containing the time the message was received as well as how long the message was delayed by the router.

If we turn on the Timestamp filters for our Web services, every router that is passed through on the way to the final destination will have a element added to the timestamp header. As an example, the following is the timestamp header that is received at TellMe.asmx when we pass the message through RouterA.asmx:


2003-02-26T22:01:58Z
2003-02-26T22:06:58Z
Actor="http://localhost/wscr/14/RouterA.asmx"
Delay="30234">
2003-02-26T22:02:08Z

No comments: