I mentioned in a previous posting that all dispatching in WSE 2.0 uses EndpointReferences from WS-Addressing. I also mentioned that we had extended the definition of an EndpointReference in the programming model to include the notion of transport address. Here's an overview of the mechanism.
In the simplest case, EndpointReference.Address is both the name of the target and its location. For example, http://localhost/myservice can be used as a name and a transport address. However, now that WSE 2.0 has a model to allow a single SoapService to be hosted on multiple transports we needed a mechanism to distinguish between these instances but still apply a single policy for them. In addition, the demise of WS-Routing left us looking for a means to refer to a service by name and then discover its location. Finally, in the case of the soap.tcp transport, we wanted users to have a means of specifying which network interface a particular SoapService was bound to - this is important in the case of multi-homed machines. Enter EndpointReference.Via.
Let's assume that you have a service that has a well-known name, for example urn:MyService. You now want to host that service so that it is accessible using the soap.tcp transport. Constructing an EndpointReference using the two URI's below will name the service and specify the transport that it is hosted on:
The soap.tcp transport will start a listener for the IP Any address on the machine and will accept messages sent to urn:MyService on any interface. Moreover, the policy engine will enforce policies for the endpoint named urn:MyService irrespective of which transport the message arrived on.
Now lets assume that:
As a sender of a message, you can also use two URI's to specify where WSE should deliver the message. Using the first two URI's given above, the <wsa:To> header will contain urn:MyService but the message will be sent using a connection to soap.tcp://mycomputer. Note that the EndpointReference.Via in this case is not part of the SOAP message itself, it is simply used to specify the transport address. However, when the message is received, the soap.tcp transport will inject a Via address based on the network interface that the message arrived on. It will then proceed to use both the <wsa:To> header and the generated Via to dispatch the message - only a SoapService registered with a matching Addresss and Via will receive the message.
The Via does not have to be specified directly at the sender, instead WSE 2.0 will use the ReferralCache to try to find a transport address if one is not specified. This mechanism is almost exactly the same as was used for the WS-Routing and WS-Referral implementation in WSE 1.0. A similar model is used with the built-in HTTP router: a message arrives with only a <wsa:To> header, the router takes that and uses its ReferralCache (or your override) to determine the next hop for the message.
Since <wsa:FaultTo>, <wsa:ReplyTo> and <wsa:From> are also EndpointReferences, a sender can specify a transport address in any of these and, in that case, it is carried in the message as part of the {any} element construct allowed in the EndpointReference schema.
For services that are not registered using SoapReceivers.Add - ASMX services or SoapServices using ASP.NET - you can use the SoapActor attribute to specify the name of the service. WSE 2.0 will ensure that a message arriving over HTTP has a <wsa:To> header that matches the value of this attribute if it is present, or matches the value of the request URL. Since the policy engine uses the <wsa:To> header to select the appropriate rules to enforce, this check prevents a sender from trying to use <wsa:To> to select an incorrect, possibly weaker, policy when the message is delivered. Note that this check means that you must use EndpointReference.Via if you plan to intercept messages using a tracing tool - if you don't the <wsa:To> header won't match the request URL when it is forwarded by the tracer and the message will be failed at the receiver.
WSE 2.0 also includes a new configuration entry, allowRedirectedResponses, that determines whether Via information included in <wsa:ReplyTo>, <wsa:FaultTo> or <wsa:From> headers is obeyed - the default for this setting is false. If you enable it, you must perform adequate verification of a signature over the value of the Via or you may be opening yourself up to becoming a reflector in an attack against another machine.
Finally, custom transport authors need to be aware of the processing that is required for EndpointReference.Via in order to ensure correct processing and protect the integrity of services; more on that in another entry later.
The WS-Addressing specification has been updated. I'm finalizing the changes in WSE 2.0, the most significant are:
WS-Addressing defines the Recipient as:
/wsa:Recipient This optional element (of type wsa:EndpointReferenceType) conveys the entire endpoint reference of the recipient. Senders MAY elect to add this header as a processing hint to downstream nodes.
This makes it the unexploded form of the EndpointReference that the sender uses when constructing a message. The unfortunate part of the definition above, in my opinion, is "Senders MAY elect...".
Dispatching incoming messages against EndpointReferences requires a complicated matching process between the EPR, it's ReferenceProperties and the various SOAP headers in the message. I think this could be greatly simplified if the dispatch took place against the Recipient header rather than the contents of all the message headers. Matching two EPR's could then be implemented as an IComparer class in the .NET Framework and this makes a nice mechanism if people want their own specialized comparisons.
Going a step further, you could imagine dispensing with the EPR explosion process and just using the Recipient header instead. However, I think that the idea of ReferenceProperties being exploded to SOAP headers is a means of bridging between existing services that need those headers (i.e. cannot deal directly with the Recipient header) and newer services that can operate solely with WS-Addressing.
Don asks about a mechanism for setting the <wsa:To> header in a SoapEnvelope using recent WSE 2.0 bits. Here's some more sample code (as best I recall it):
EndpointReference r = ...;
SoapEnvelope e = ...;
//
// Set the destination for the SoapEnvelope
//
e.Context.Addressing.Destination = r;
//
// Set the real headers in the SoapEnvelope
//
e.Context.Addressing.GetXml(e);
The last line above adds all the WS-Addressing headers from the envelope's SoapContext into the SoapEnvelope itself. Now, it's also possible to declare an instance of AddressingHeaders directly and use that instead:
AddressingHeaders h = new AddressingHeaders();
h.Destination = r;
h.GetXml(e);
One thing that this mechanism doesn't do is remove any existing headers. This can be done another way:
SoapEnvelope e = ...;
//
// Load any addressing headers, but do not remove them
//
e.Context.Addressing.LoadXml(e);
//
// Remove the headers from the SoapEnvelope
//
e.Context.Addressing.RemoveXml(e);
Now, the mechanisms above are, to my mind, still quite clumsy and I'd prefer a different programming model that makes headers an intrinsic part of the SoapEnvelope itself. That's something that will have to wait for WSE 3.0.
What? A month went by already? December was pretty busy on the WSE team as we locked down and killed bugs to get to a stable build before the holiday period and I just didn't get much time to think about writing a blog entry. I also like to take a couple of weeks break so have been slack even though I've not been at work. Starting the New Year with some code seems appropriate.
public class EndpointReference : ICloneable, IComparable
{
//
// Implicit conversion from a Uri
//
public static implicit operator EndpointReference(Uri address);
//
// Explicit conversion to a Uri (lossy)
//
public static explicit operator Uri(EndpointReference endpoint);
public static bool operator == (EndpointReference ep1, EndpointReference ep2);
public static bool operator != (EndpointReference ep1, EndpointReference ep2);
public EndpointReference(Uri address);
public EndpointReference(Uri address, Uri transportAddress);
//
// Copy constructor
//
public EndpointReference(EndpointReference endpoint);
public object Clone();
public int CompareTo(object other);
public override bool Equals(object other);
public bool Matches(SoapEnvelope envelope);
public Uri Address { get; }
public ReferenceProperties ReferenceProperties { get; set; }
}
public class From : EndpointReference
{
...
}
public class ReplyTo : EndpointReference
{
...
}
public class FaultTo : EndpointReference
{
...
}
...
public static void Main()
{
EndpointReference epr = new EndpointReference( new Uri("urn:MyService"), new Uri("soap.tcp://localhost/MyService") );
SoapReceivers.Add(epr, new MyService());
}
The EndpointReference class replaces the EndpointReferenceType class from the WSE 2.0 Tech Preview and is a lot more extensive in its implementation for the final release.
First off, we have some conversion operators. Conversion from a Uri is implicit and sets the Address property. Conversion to a Uri is explicit since it's a lossy operation - you're dumping all the other data in the EndpointReference just to extract the Address property so you need to be explicit about doing that. We also added a copy constructor that makes it simpler to construct an EndpointReference from one of it's subclasses, for example when assigning the ReplyTo as the Destination of a message.
EndpointReference is now cloneable which helps parts of the messaging infrastructure by providing a (limited) barrier that prevents accidental changes to an object reference that is in use by the dispatching mechanism. What I really want from the CLR though is a simple mechanism to mark an entire object instance graph as read-only, in the same way that I want the CLR to provide a default deep-copy (thinks back to Smalltalk/V days long ago...).
The equality operators have been implemented along with a CompareTo method. Equality itself is a little tricky as EndpointReferences have an open-content model in the schema. I have modified the OpenContent classes quite a lot since the Tech Preview and this helps with serialization / deserialization but not much with comparison (where is XmlElement.Equals when you need it?). The only truly robust way to do the comparison is performing an XML canonicalization step but this is very expensive so instead we perform a more light-weight comparison that should serve just as well. Deep-tree comparisons will, however, be expensive operations. CompareTo is used when we use EndpointReferences in Hashtables with an EndpointReferenceComparer object; it's not really useful outside of this.
The Matches method is the current mechanism for determine whether the contents of a SoapEnvelope's <soap:Header> match an EndpointReference. It essentially looks for headers that match the Address property and then elements in the ReferenceProperties collection. I'll be looking at making the dispatch mechanism more robust next week (it needs a "best-match" algorithm) and thinking about whether the Match method needs to be pluggable to allow for user-defined comparisons.
Keith and I have spent the last 3 weeks hammering through the low-level WSE 2.0 programming model: everywhere we find a Uri we change it to an EndpointReference. All message sending and receiving is EndpointReference centric. As someone at work remarked "It's the new URI".
If you haven't read through the WS-Addressing specification, here's a quick summary of what EndpointReferences are and how they are processed.
Every EndpointReference contains a single Address and, optionally, ReferenceProperties. When serialized into a message, the Address becomes the <wsa:To> header and every item in the ReferenceProperties becomes a header in the <soap:Header>. The Address is the core and represents a role; it may also be a physical address.
The primary WS-Addressing constructs of Destination, From, ReplyTo and FaultTo are all represented as EndpointReferences. When sending a message, the Destination is "exploded" as above. The receiver[1] is expected to take one of From, ReplyTo or FaultTo from the message and use them as the Destination for the response, subsequent one-way message or fault:
In the simplest case, WSE treats the Address component of the EndpointReference as a physical address for a service. We don't do any fancy translation of this address to determine a physical address as does the current SOAP Mail sample. Others at work may disagree, but I don't like this approach: it is used in the sample because the WSE 2.0 Tech Preview doesn't separate logical address from physical address.
At the next level, WSE 2.0 treats the Address component as the logical name for a service and then uses it's ReferralCache to map that name to a physical name[2]. This is very similar to the routing process in WSE 1.0 except that the lookup only ever returns a single physical address - it can't define a path for the message. This approach allows a service with a single name to be hosted over multiple transports and it supports the signing of the <wsa:To> header (derived from the Address) since it is now a constant in the message however many intermediaries in may travel through[3].
Finally, developers can specify the transport address directly as part of an EndpointReference construct. This is an extension to the base WS-Addressing specification but it does not affect wire-level interoperability, just the WSE programming model.
The change to EndpointReference is reflected throughout the programming model - the WS-Addressing headers are now a fixed part of the message and are not handled through the filter chain (yes, we'll still be able to talk to "legacy" HTTP Web Services that don't understand the Addressing headers provided that they follow the SOAP processing model). Building around EndpointReference has also allowed us to introduce a more powerful message dispatching process. The Tech Preview dispatches messages based on the <wsa:To> header and the <wsa:RelatesTo> header (for responses); for the 2.0 release, registration of receivers and dispatching is now fully EndpointReference based (including ReferenceProperties) and this opens new possibilities for developers in deciding how to handle incoming messages.
Moreover, the SoapSender/SoapReceiver programming model of the Tech Preview is now underpinned by a new, fundamental layer that offers another programming model (for those brave enough to use it) that is very simple and much more consistent across all transports. Plumbing a transport for a messaging product (such as MSMQ or MQSeries) that is typically unidirectional should be much easier: I'll be testing this theory out this week with Kevin's MSMQ transport, after which it will be time to check-in :-)
[1] Note that, for technical reasons, ReplyTo and FaultTo are currently ignored if you write your Web Service using the .NET Framework ASMX infrastructure instead of the WSE Messaging programming model.
[2] Benjamin may not entirely approve of this scheme, but we're some way off from solving the binding problem through either WSDL or WS-Policy.
[3] The current SOAP Mail sample has a problem in this regard as it tries to overload the <wsa:To> header as both role and transport address. It therefore needs to modify the transport address in order to unpick the soap.mail scheme and produce an http address to forward the message to: strictly speaking the <wsa:To> header should be modified to reflect this new address but doing so would break the signature.
Dave Angers asked in a comment on my WS-Routing post about message dispatching, specifically regarding the restrictions on the <wsa:To> and <wsa:RelatesTo> headers.
The block of transport URI's in the <wsa:RelatesTo> header is because the Tech Preview uses the contents of this header (if it is present) to dispatch messages. Preventing the use of transport URI's (e.g. soap.tcp, http, etc.) means that we also block unwanted transport cross-over scenarios, for example where it's possible to send a message over, say, MSMQ and, because of the content of the <wsa:RelatesTo> header, have the message dispatched to a TCP receiver that was supposed to be hidden. The check on the <wsa:To> header has the same effect - it ensures that you are using the right transport to talk to a specific receiver.
Now, Dave's questions regarding this tight binding of the <wsa:To> header to a transport address are right on target and are part of the reason why I am currently separating the notions of <wsa:To> (i.e. logical Role or Actor) from transport address. We will still block unwanted transport cross-over scenarios (i.e. if you register using TCP you won't get message delivered over some other transport) and we'll have a looser definition for the contents of <wsa:To>.
We're still working out the exact details of how this will work, I hope to be able to say more about it before we release.
The WS-Routing specification was published a long time ago and we implemented it together with WS-Referral (although we didn't call it that) in WSE 1.0. Neither specification has garnered much attention and, with the arrival of WS-Addressing, both are somewhat superseded.
For the WSE 2.0 Tech Preview, we removed several components of the /Path header and replaced them with their equivalents from WS-Addressing. Despite the fact that we added additional transports to WSE for the Tech Preview we left the HTTP-based Router largely untouched and didn't do anything for the other transports.
Moving forward for the final release we're going to complete the transition over to a WS-Addressing world and remove the last vestiges of WS-Routing, although we'll continue to provide support for SOAP intermediaries, in particular content-based routers. Routing will be "next hop" based with the sender and intermediaries able to determine where the message is to be sent next and we'll be cleanly separating the notion of Actor / Role from transport address. This is a pretty big change to the product and is affecting lots of areas, particularly the transport layers - if you've built a custom transport for WSE 2.0 Tech Preview, you should expect to have to rework it for the final release.