Routing Messages



Routing Messages

SOAP itself is inherently a one-way messaging format. It even assumes that these kind of routed and asynchronous messages may be sent. As you may remember from Chapter 9, SOAP specifies two different data containers: the <Header> and the <Body>.

The <Header> sends data that, by default, is for the final destination of the message. Of importance here is that it is the default destination. You can override this to indicate that other nodes along the message path may actually be the intended receiver for that <Header> data. This is done with the actor attribute on the <Header>, as shown in Listing 12.1.

Listing 12.1 Using the Actor Attribute in the <Header> of a SOAP Message
<soap:Envelope
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:xsd="http://www.w3.org/2001/XMLSchema"
     xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Header>
    <TokenHeader
      soap:actor="http://intermediate.com/"
      xmlns="http://keithba.com/Token">
      <foo />
    </TokenHeader>
  </soap:Header>
  <soap:Body>
  </soap:Body>
</soap:Envelope>

By design, the <Body> is intended for the final destination of the SOAP message. In other words, unlike the specific headers, a SOAP message <Body> is always intended for the final SOAP message recipient. There is no way to indicate that someone else should receive this message.

The SOAP specification doesn't tell us how to indicate through whom a message is to be sent. In other words, SOAP gives us a processing model with the headers, <Body>, and mustUnderstand and actor attributes. But it doesn't tell us how we are to use these headers to route a message to its final destination.

Message Paths with WS-Routing

WS-Routing fulfills the goal of specifying message paths. WS-Routing enables you to specify a message's forward message path. This is the main thrust of the protocol. It also enables you to specify a reverse message path. In addition, it provides the capability to give messages unique identifiers, and then specify the other messages to which they relate.

Note that it's not required that the initial message sender set these message paths, particularly the forward path. In fact, it's expected that routers and intermediaries along any message path will change this list of via elements as the message moves along. All will remove themselves from the list, and many may add a new via.

Forward message paths are specified with a SOAP header that is modified as it travels through the various message nodes. To specify this forward path, this header contains the following values:

  • A root element called path

  • An element that contains a fwd element

  • Any number (from zero to infinity) of via elements, contained in the fwd element

  • A to element that is the final destination of the message

  • A from element that is the sender of the message

  • The intent of the message, which is the action

Listing 12.2 illustrates the use of these elements, and Figure shows the path for this message.

A Sample WS-Routing Message
<SOAP:Envelope
      xmlns:SOAP =" http://schemas.xmlsoap.org/soap/envelope/">
   <SOAP:Header>
      <rp:path xmlns:rp="http://schemas.xmlsoap.org/rp/">
         <rp:action>http://www.AutoParts.com/SubmitPO</rp:action>
         <rp:to>http://autoparts.com/POServices</rp:to>
         <rp:fwd>
            <rp:via>http://firewall.fastcars.com</rp:via>
            <rp:via>http://firewall.autoparts.com</rp:via>
         </rp:fwd>
         <rp:from>http://fastcars.com/POSubmit</rp:from>
         <rp:id>uuid:88888888-444A-5566-9999-11112222AA99</rp:id>
      </rp:path>
   </SOAP:Header>
   <SOAP:Body>
      <PO xmlns="http://autoparts.com">
          ...
     </PO>
   </SOAP:Body>
</SOAP:Envelope>
4. A Message Path

graphics/12fig04.gif

A WS-Routing message also can specify the path for the response message. This is done with the rev element, which contains the same list of via elements, as shown in Listing 12.3. Figure shows the path for this message.

A Reverse Message Path
<SOAP:Envelope
      xmlns:SOAP =" http://schemas.xmlsoap.org/soap/envelope/ ">
   <SOAP:Header>
      <rp:path xmlns:rp="http://schemas.xmlsoap.org/rp/">
         <rp:action>http://www.AutoParts.com/SubmitPO</rp:action>
         <rp:to>http://autoparts.com/POServices</rp:to>
         <rp:fwd>
            <rp:via>http://firewall.fastcars.com</rp:via>
            <rp:via>http://firewall.autoparts.com</rp:via>
         </rp:fwd>
         <rp:rev>
            <rp:via>http://firewall.autoparts.com</rp:via>
            <rp:via>http://receiving.fastcars.com</rp:via>
         </rp:rev>
         <rp:from>http://fastcars.com/POSubmit</rp:from>
         <rp:id>uuid:88888888-444A-5566-9999-11112222AA99</rp:id>
      </rp:path>
   </SOAP:Header>
   <SOAP:Body>
      <PO xmlns="http://autoparts.com">
          ...
      </PO>
   </SOAP:Body>
</SOAP:Envelope>
5. A Reverse Message Path

graphics/12fig05.gif

WS-Routing also specifies a series of elements that can send a SOAP fault message in response to an error, such as a service being unavailable. The body of the SOAP message also returns a SOAP fault of the generic sort. Specifically, WS-Routing stipulates that you include the following:

  • A code element for specifying the numeric code of the error type

  • The reason element, which is a human-readable string that describes the error

Several other elements are possible, depending on the error. For example, the found element indicates that the endpoint has been moved. This element contains a list of possible new endpoints to try. Listing 12.4 uses the retryAfter element to specify the time in seconds to wait until trying again.

WS-Routing Fault
<SOAP:Envelope
      xmlns:SOAP =" http://schemas.xmlsoap.org/soap/envelope/">
   <SOAP:Header>
      <rp:path xmlns:rp="http://schemas.xmlsoap.org/rp/">
         <rp:action>http://www.AutoParts.com/SubmitPO</rp:action>
         <rp:id>uuid:88888888-444A-5566-9999-11112222AA99</rp:id>
         <rp:relatesTo>uuid:5555-5566-9999-1222AA99</rp:relatesTo>
         <rp:fault>
             <rp:code>812</rp:code>
             <rp:reason>Web service unavailable right now.</rp:reason>
             <rp:retryAfter>600</rp:retryAfter>
         </rp:fault>
      </rp:path>
   </SOAP:Header>
   <SOAP:Body>
      <SOAP:Fault>
              <SOAP:faultcode>SOAP:Server</SOAP:faultcode>
              <SOAP:faultstring>
                  WS-Routing Server Error
              </SOAP:faultstring>
      </SOAP:Fault>
   </SOAP:Body>
</SOAP:Envelope>

When sending a WS-Routing message over HTTP, you must also make sure that the action element is the same as the HTTP SOAPAction header; the specification states that these must match. The specification also details how WS-Routing should be used with TCP and DIME.

Building WS-Routing Applications

The code to build these headers is fairly simple with the WSE. With the WSE, you can easily insert intermediaries into the WS-Routing Path header with the Path property of the SoapContext. Following is an example message sender header:

Proxy.Path.To = "http://destination"; 
proxy.Path.Fwd.Via(("http://intermediary/router/router.ashx");

proxy.SubmitPO(po);

You can also use the WSE on ASP.NET services by merely installing it and adding the following HttpHandler to web.config in that virtual directory:

    <system.web> 
        <httpHandlers>
            <add verb="*" path="*.ashx" type="Microsoft.Web.Services.Routing.RoutingHandler, Microsoft.Web.Services" />
        </httpHandlers>
    </system.web>

By default, the response message will not contain a Path header, because the reverse path is implicit with HTTP. Listing 12.5 illustrates a typical message destination.

A Typical Final Destination
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Xml.Serialization;

namespace WSRouting
{
     [WebService(Namespace="http://autoparts.com/")]
     public class Service2 : System.Web.Services.WebService
     {
        [WebMethod]
        [SoapDocumentMethod( ParameterStyle=SoapParameterStyle.Bare )]
        public void SubmitPO( [XmlElement("PO")] PO po )
        {
               //do something with the PO code here
               return;
        }
     }
}