Skip to main content

wsHttpDualBinding - a non-interoperable binding

Posted by arungupta on August 2, 2007 at 11:07 AM PDT

Based upon a

user request
, I'll explain why

wsDualHttpBinding
(a

system-provided binding in WCF
) is not an interoperable binding. This blog
entry will explain the service endpoint code, client code, generated WSDL
and the SOAP messages exchanged based upon the
DualHttp
Binding Sample
that is bundled with the

Windows SDK
samples. This is also an update to an

earlier attempt
to explain wsDualHttpBinding.

In the sample, I replaced the default wsDualHttpBinding with
an equivalent custom binding (after removing the security) as:

<bindings>
  <customBinding>
    <binding name="Binding1">
      <reliableSession />
      <compositeDuplex />
      <oneWay />
      <textMessageEncoding
                     messageVersion="Soap12WSAddressing10"
                     writeEncoding="utf-8" />
      <httpTransport authenticationScheme="Anonymous"
                     bypassProxyOnLocal="false"
                     hostNameComparisonMode="StrongWildcard"
                     proxyAuthenticationScheme="Anonymous"
                     realm=""
                     useDefaultWebProxy="true" />
    </binding>
  </customBinding>
</bindings>

The wsDualHttpBinding, also known as Composite Duplex
or Full Duplex Binding, provides a bi-directional communication between Client and Endpoint. In a single
direction communication, the client can invoke a set of operations on a service
and this interface is referred as primary interface in Duplex
binding. The set of operations that a service can invoke on the client is
called as callback interface.

The sample in Windows SDK is a trivial calculator service where primitive
Math operations are performed in a running session on the "primary"
interface and results are returned on the "callback" interface.

Service Endpoint Code

Let's understand the service endpoint code first. The
"primary" service endpoint interface looks like:

[ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples", 
                 SessionMode=SessionMode.Required
,
                 CallbackContract=typeof(ICalculatorDuplexCallback))]
public interface ICalculatorDuplex
{
  [OperationContract(IsOneWay=true)]
  void Clear();
  [OperationContract(IsOneWay = true)]
  void AddTo(double n);

  ...
}

Three points to note in this code:

  • A duplex contract requires a session, because a
    context must be established to correlate the set of messages being
    sent between client and service. Even though this is specified using

    			SessionMode=SessionMode.Required
    attribute but the default
    value of
    			SessionMode=SessionMode.Allowed
    (equivalent of not
    specifying) will be sufficient as well. This is because all Duplex
    bindings in .NET maintain a transport session.
  • Callback interface is specified using

    			CallbackContract
    attribute.
  • All the methods are declared as One-way operations
    otherwise the response can be returned on the transport back channel
    itself. The documentation on this particular binding is limited.

The "callback" interface is defined as:

public interface ICalculatorDuplexCallback
{
  [OperationContract(IsOneWay = true)]
  void Result(double result);

  ...
}

In order for a service endpoint to establish a
connection with the "callback" interface on client, a

		CallbackChannel
is obtained from the OperationContext
in the implementation of the "primary" interface as:

public class CalculatorService : ICalculatorDuplex
{
  double result;
  ICalculatorDuplexCallback callback = null;

  public CalculatorService()
  {
    result = 0.0D;
    callback = OperationContext.Current.GetCallbackChannel<ICalculatorDuplexCallback>();
  }

Another variable is initialized to return the running
result. The implementation of each method in the "primary" interface
then invokes a method on the "callback" interface to return the running
result as:

public void AddTo(double n)
{
  result += n;
  callback.Result(result);
}

Generated WSDL

Now let's look at the generated WSDL fragments. The
policy assertion elements are:

<wsp:All>
  <wsrm:RMAssertion xmlns:wsrm="http://schemas.xmlsoap.org/ws/2005/02/rm/policy">
    <wsrm:InactivityTimeout Milliseconds="600000" />
    <wsrm:AcknowledgementInterval Milliseconds="200" />
  </wsrm:RMAssertion>
  <cdp:CompositeDuplex xmlns:cdp="http://schemas.microsoft.com/net/2006/06/duplex" />
  <ow:OneWay xmlns:ow="http://schemas.microsoft.com/ws/2005/05/routing/policy" />
  <wsaw:UsingAddressing />
</wsp:All>

The wsrm:RMAssertion
and wsaw:UsingAddressing elements are bound to a known
namespace and their behavior is
clearly
documented
. However the specification of cdp:CompositeDuplex
and ow:OneWay elements are unclear at this time. This does
not allow any WSDL-based interoperability whenever these elements are
included.

All methods from the "primary"
and the "callback" interface are generated in one wsdl:portType.
The methods from the "primary" interface are generated as
One-way operations. But methods from
the "callback" interface are generated as
Notification operation.
For example, one of the methods from "callback" interface looks like:

<wsdl:operation msc:isInitiating="true" msc:isTerminating="false" name="Result">
  <wsdl:output
      wsaw:Action="http://Microsoft.ServiceModel.Samples/ICalculatorDuplex/Result"
      message="tns:ICalculatorDuplex_Result_OutputCallbackMessage"/>
</wsdl:operation>

JAX-WS, the core of
Metro, supports only
Request-Response
and One-way operations.
This is the second place where WSDL-based interoperability will not work
with any JAX-WS-based WSDL import tool, such as

wsimport
. Moreover, the WSDL-to-Java mapping defined by the JAX-WS
specification requires each wsdl:portType map to a single Java
interface. This WSDL design pattern requires two interfaces to be generated
from a single wsdl:portType.

There are some other elements
in namespace prefix bound to "http://schemas.microsoft.com/ws/2005/12/wsdl/contract"
and their purpose is also unclear. Rest of the WSDL is pretty
straight-forward.

Client side code

On the client side, svcutil (WSDL
importing tool for .NET 3.0) generates the "primary" and "callback"
interface from the WSDL. The "callback" is implemented as:

public class CallbackHandler : ICalculatorDuplexCallback
{
  public void Result(double result)
  {
    Console.WriteLine("Result({0})", result);
  }

  public void Equation(string eqn)
  {
    Console.WriteLine("Equation({0})", eqn);
  }
}

This client instance is
initialized with the callback implementation as:

class Client
{
  static void Main()
  {
    // Construct InstanceContext to handle messages on callback interface
    InstanceContext instanceContext = new InstanceContext(new CallbackHandler());

    // Create a client with given client endpoint configuration
    CalculatorDuplexClient client = new CalculatorDuplexClient(instanceContext);

And then the client invokes the
service endpoint normally as shown below:

// Call the AddTo service operation.
double value = 100.00D;
client.AddTo(value);

...

SOAP messages

Lets look at the SOAP messages
exchanged between client and endpoint now. The first call from the client to
an endpoint triggers a protocol handshake for establishing a session. The
CreateSequence protocol message looks like:

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">
  <s:Header>
    <a:Action s:mustUnderstand="1">http://schemas.xmlsoap.org/ws/2005/02/rm/CreateSequence</a:Action>
    <a:ReplyTo>
      <a:Address>http://iamfine.sfbay.sun.com/Temporary_Listen_Addresses/bfd8c103-b0f9-4c65-9cb6-fbebb7d1517b/4e0cdb31-2451-4fb6-84b8-dc286e5f26c8</a:Address>
    </a:ReplyTo>
    <a:MessageID>urn:uuid:51918652-9a78-4ba3-82f5-e68ecd664d42</a:MessageID>
    <a:To s:mustUnderstand="1">http://localhost:8888/</a:To>
  </s:Header>
  <s:Body>
    <CreateSequence xmlns="http://schemas.xmlsoap.org/ws/2005/02/rm">
      <AcksTo>
        <a:Address>http://iamfine.sfbay.sun.com/Temporary_Listen_Addresses/bfd8c103-b0f9-4c65-9cb6-fbebb7d1517b/4e0cdb31-2451-4fb6-84b8-dc286e5f26c8</a:Address>
      </AcksTo>
      <Offer>
        <Identifier>urn:uuid:b1116e69-f1dd-45b0-8495-129645038160</Identifier>
      </Offer>
    </CreateSequence>
  </s:Body>
</s:Envelope>

The WCF runtime uses the Windows HTTP.SYS library to host an
endpoint at the address specified in a:ReplyTo. This address is
used for all subsequent messages sent on the callback channel. This message
is used to create a session for the "primary" interface. The message also
carries an offer, in the SOAP Body, to create a "callback" interface
session.

The CreateSequenceResponse protocol message
returns "primary" interface session identifier and also accepts the offered
"callback" session. The message looks like:

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">
  <s:Header>
    <a:Action s:mustUnderstand="1">http://schemas.xmlsoap.org/ws/2005/02/rm/CreateSequenceResponse</a:Action>
    <a:RelatesTo>urn:uuid:51918652-9a78-4ba3-82f5-e68ecd664d42</a:RelatesTo>
    <a:To s:mustUnderstand="1">http://iamfine.sfbay.sun.com/Temporary_Listen_Addresses/bfd8c103-b0f9-4c65-9cb6-fbebb7d1517b/4e0cdb31-2451-4fb6-84b8-dc286e5f26c8</a:To>
  </s:Header>
  <s:Body>
    <CreateSequenceResponse xmlns="http://schemas.xmlsoap.org/ws/2005/02/rm">
      <Identifier>urn:uuid:d483898c-4bd3-4077-ba04-07a9010ab27f</Identifier>
      <Accept>
        <AcksTo>
          <a:Address>http://localhost:8888/</a:Address>
        </AcksTo>
      </Accept>
    </CreateSequenceResponse>
  </s:Body>
</s:Envelope>

Now, because of the way each method is implemented (invoking
callback.Result(result) method at the end of each "primary"
operation), a response to a request received by an endpoint is returned over
the callback channel. This happens under-the-cover even though all messages
in the "primary" interface are defined as One-way operations.

The behavior is quite analogous to a Request-Response
operation primitive. I wonder what are the usecases of wsDualHttpBinding ?

Summary

Finally, I summarize the reasons that makes
wsDualHttpBinding a non-interoperable binding:

  1. The specifications of cdp:CompositeDuplex
    and ow:OneWay are not available and these elements will
    thus be ignored by the Metro WSDL importing tool.

  2. The operations from
    "callback" interface are mapped as
    Notification operation
    in the WSDL. This operation primitive is not supported by Metro.

  3. On the service endpoint,
    all the operations from "primary" and "callback" interface are mapped to
    a single wsdl:portType. On the client side, wsdl:portType
    is mapped to separate "primary" and "callback" interfaces. The Java-to-WSDL
    mapping defined by the JAX-WS specification allows one-to-one mapping
    between Java interface and wsdl:portType.

Technorati:
webservices
interoperability
wcf
projectmetro
jax-ws
wsit

Related Topics >>