|
|||||||||||
SOA Addressing and Transport with WSE 2.0
Service Oriented Architecture (SOA) is a distributed architecture design pattern. In this second article of the series, we will demonstrate how to address SOAP messages in a transport-neutral way, and how to use the transport-neutral WSE 2.0 transport classes. In the process of doing so we will discuss the WS-Addressing protocol specification. We will also use these classes to begin our Mortgage Loan Service application which is being built as an example of a Service Oriented Architecture.
Assuming our business services are defined appropriately, let us focus on the implementation of our simple Mortgage Loan Service system. Let’s start with a simplified version. A loan officer's program will transmit the request for a mortgage directly to the bank's loan processing service. This service will return a loan application id to the loan officer's program. We will introduce the portal and the credit agency in future articles.
The most basic task is to send and receive messages. Sending and receiving a message requires 4 steps:
Create the message at the source Address the message to its destination Transmit the message through some transport mechanism (such as HTTP) Read the message at the destination.
Think about sending a postal letter or an email, and these steps should be familiar.
Creating and Reading a Message
In the "SOAP Envelope" code directory we have two examples that illustrate the flexibility of WSE in building a SOAP message using the Microsoft.Web.Services2.SoapEnvelope class.
A SOAP message contains three XML elements. A parent Envelope element has two children, a header and a body. The header element is optional, but must come before the body element. The header is used by various Web service specifications to implement their protocols. In this article we will look at the WS-Addressing protocol. The body element is where the actual message, or payload, is placed.
At this point we should clarify between two uses of the word message. From the point of view of the SOAP envelope, the message is in the body element. From the point of view of the transport mechanism, the SOAP envelope and all its child elements are the message. Distinguishing between these two usages is usually clear from context.
Since the SoapEnvelope class inherits from XmlDocument, the "Envelope with XML" sample demonstrates how to build your SOAP message as straight XML (see Listing 1). If you run this program you will see that it creates a valid SOAP message using the SoapEnvelope class.
SoapEnvelope envelope = new SoapEnvelope();
XmlAttribute wsaAttribute = envelope.CreateAttribute ("xmlns:wsa"); WsaAttribute.Value = "http://schemas.xmlsoap.org/ws/2004/03/addressing"; envelope.DocumentElement. SetAttributeNode(wsaAttribute);
XmlElement header = envelope.CreateHeader();
XmlElement actionElement = envelope.CreateElement ("wsa:Action", "http://schemas.xmlsoap.org/ws/2004/03/addressing"); actionElement.InnerText = "urn:Bank:LoanStatusResponse"; header.AppendChild(actionElement);
... XmlElement body = envelope.Body;
XmlElement bodyElement = envelope.CreateElement ("MortgageLoan"); body.AppendChild(bodyElement);
... XmlElement nameElement = envelope.CreateElement ("Name"); nameElement.InnerText = "Peter Jones"; bodyElement.AppendChild(nameElement);
XmlElement amountElement = envelope.CreateElement ("Amount"); amountElement.InnerText = "100000"; bodyElement.AppendChild(amountElement);
The other sample "Envelope with Serialized Body" demonstrates that you can use the SetBodyObject and GetBodyObject methods on the SoapEnvelope class to create the body of the SOAP message (see Listing 2). These methods use XML Serialization to serialize a .NET object to XML format, and to deserialize the XML format back to a .NET object. For more information about XML Serialization, read Chapter 11 of Dino Esposito's Applied XML Programming for Microsoft .NET.
MortgageLoan loan = new MortgageLoan(); loan.name = "John Smith"; loan.amount = 1000; envelope.SetBodyObject(loan, "http://www.reliablesoftware.com/MortgageLoan");
... MortgageLoan loan2 = (MortgageLoan)envelope. GetBodyObject(typeof(MortgageLoan), "http://www.reliablesoftware.com/MortgageLoan"); Console.WriteLine("Name: {0}, Amount: {1}", loan2.name, loan2.amount);
Both of these techniques will be used in our application.
Addressing a Message
Since a recipient of a SOAP message, may also transmit a SOAP message to another application, we will talk about producers and consumers of SOAP messages, not clients and servers. Sometimes people talk about service providers and service requestors.
SOAP messages are independent of the transport used to transmit them. WSE 2.0 ships with the ability to send SOAP messages over HTTP or TCP, and can be extended to use other messaging protocols. We need some way to define the address of a SOAP message independent of the transport mechanism. Messages (as we will see in our complete sample application where the message from the loan officer's application goes through a portal before it goes to the loan application service) often travel through intermediaries, and the replies may go back to the original sender of the message, not the most immediate sender. The transport protocols may be different in each "message hop".
The WS-Addressing protocol, now being formalized under the auspices of the W3C, defines how the destination of a Web service message can be defined independent of the transport protocol. The World Wide Web Consortium or W3C (http://www.w3c.org) is a standards organization for Web technologies. In the W3C terminology accepted standards are called recommendations. The first step in the process is a "Working Draft" which represents the work in progress. The WS-Addressing protocols are currently a Working Draft. When the working group has reached consensus, it circulates a "Last Call Working Draft" for public review. For WS-Addressing this is scheduled to occur around March 2005. The next step is a "Candidate Recommendation". This represents an explicit request for attempts to implement the specification to gain experience. For WS-Addressing this is scheduled to occur around May 2005. After the working group feels the protocol has been changed to reflect the results of the implementation experience, the proposal goes to "Proposed Recommendation" status. For WS-Addressing this is scheduled for around August of 2005. This represents a request to the W3C for the specification to be approved. If the W3C approves the specification, it becomes a "W3C Recommendation". For WS-Addressing this might occur around October of 2005. These dates come from the schedule on the W3C web site, and are not intended to be deadlines.
The WS-Addressing protocol is specified in three documents. The basic document is the Core specification. The Core specification defines two related abstract constructs: "Endpoint References" and "Message Addressing Properties".
An endpoint reference defines how to identify or reference a processor or some other resource that can be sent a Web service message. The Message Addressing Properties define the information that can be used to send an individual Web service message.
At the minimum, the Endpoint Reference is the URI (Uniform
Resource Identifier) associated with the endpoint. A URI can be either a
logical address or a network address. Associated with the
Other parts of the Endpoint Reference include the WSDL service port and port type, as well as a set of policies that describe the limitations or capabilities of the endpoint. These policies are there for the convenience of the producer, and may vary from the actual policies in effect when the Web service message is consumed.
Message Addressing Properties are used to identify the particular endpoint used in a single message. Most of these properties are self-explanatory. Two are mandatory. The destination of the message (a URI), and the message Action. The Action is used to identify the operation associated with the Web service message. It is usually a URI. The remaining properties are optional. The source property identifies the endpoint that originated the message. The reply property identifies the endpoint where message replies go. Note that this does not have to be the same value as the message source. The fault property identifies the endpoint where error messages are sent. The message id property is a URI that uniquely identifies this message. It is required if the reply or fault properties are present. It is also used when messages have to be resent, versioned, or secured. The relationship property has the message id of a related message.
Note that up to this point, the discussion about WS-Addressing has not mentioned SOAP at all. The core specification defines these properties in terms of the XML-Infoset, and would apply to any messaging protocol. The other two WS-Addressing protocol documents define how these concepts are bound to SOAP message header blocks, or described using WSDL. Header blocks are child elements of the SOAP Header element. For example, the SOAP Binding protocol has the URI of the endpoint reference address mapped to the message addressing destination property as a To child element of the SOAP header.
In the code directory for this article are two versions of the simplified version of the Mortgage Loan application. One uses the TCP transport protocol and the other the HTTP transport protocol. Here (Listing 3) are the addressing header blocks for the version of the Mortgage Loan application associated with the TCP protocol. Notice that the content of the addressing header elements is specific to the protocol. The elements themselves are protocol independent.
<soap:Envelope xmlns: wsa="http://schemas.xmlsoap.org/ws/2004/03/addressing" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope> <soap:Header> <wsa:Action> urn:Bank:MortgageLoanRequest</wsa:Action> <wsa:FaultTo> <wsa:Address> soap.tcp://localhost:8080/MortgageResponse </wsa:Address> </wsa:FaultTo> <wsa:MessageID> uuid:eee11352-8026-46ba-b104-a24eef2e643a </wsa:MessageID> <wsa:ReplyTo> <wsa:Address> soap.tcp://localhost:8080/MortgageResponse </wsa:Address> </wsa:ReplyTo> <wsa:To> soap.tcp://localhost/MortgageService </wsa:To> </soap:Header> … </soap:Envelope>
The To element is the address to which the message in the SOAP body is being sent. The ReplyTo element has the address to which any replies to this message should be sent. Notice it is not the same as the source. The FaultTo element has the address to which any SOAP faults should be sent. It happens to be identical to the ReplyTo address. The MessageID element has the unique identifier for the message. When I review the Mortgage Loan application code you will see how the Action element is used in message dispatching.
The WS-Addressing protocol provides a foundation for reliable messaging and notifications. The specification effort in these areas is in a much more primitive state, and there are competing specifications circulating in the Web services community.
WSE Classes That Model WS-Addressing Concepts
WSE 2.0 has several classes that model the WS-Addressing concepts. Note that these classes can be used with any transport protocol that WSE supports. They are found in the Microsoft.Web.Services2.Addressing namespace. I will demonstrate the use of these classes in the last section when I look at the current version of our case study.
The AddressingHeaders class has properties for all the individual classes that make up the SOAP addressing header blocks. An instance of this class is a property of the SoapContext class instance that is property of SoapEnvelope. You access the addressing information through this SoapEnvelope.Context property.
The EndpointReference class represents the Endpoint Reference to which a message is sent. A URI passed to the constructor of this class will be the URI that will appear in the To element of the addressing header block. For example:
Uri uri = new Uri( "soap.tcp://localhost/MortgageService"); EndpointReference er = new EndpointReference(uri);
Microsoft has an addressing protocol extension for this class (the Via property) which allows a service to be hosted on multiple transports, but have a single policy. See Hervy Wilson's blog (http://www.dynamic-cast.com/mt-archives/cat_wsaddressing.html) for more details.
The Action class represents the mandatory Action address header. Typically you pass a URI to the constructor of this class:
SoapEnvelope request = new SoapEnvelope(); request.Context.Addressing.Action = new Action("urn:Bank:MortgageLoanRequest");
The FaultTo, From, ReplyTo, MessageID, and RelatesTo classes all correspond to their corresponding header block elements.
Message Exchange Patterns
The WS-Addressing protocol supports the use any one of several message exchange patterns.
The most familiar is the request/response pattern that HTTP uses. Each request message is followed by a response message sent from the destination. While this response message could be an error message or a normal application response, it must be sent immediately, or within a very short time span.
The One-Way, or asynchronous exchange pattern is a message sent to the destination without an expectation of an immediate response. In fact, no response might be sent at all. Notification is an exchange pattern where another one-way response message is sent when the application has finished its processing. Notifications can also be used as callbacks, or events, where messages are sent without an initial request. It is also the foundation for the publish and subscribe pattern, the broadcast pattern, and the store and forward pattern.
Another message exchange pattern is polling. Polling can be based on either the one-way or request/response message pattern. As a variation to the one-way pattern, the sender uses the message id of the original message to make a request to see if the application has finished processing that message. As a variation of the request/response pattern, the original response sends back some identifier which is used in the status check. In either case the recipient of the poll sends back a response with either the result, or a message that the result is still pending.
Polling is typically used where the polling application is not, or cannot be setup as an endpoint to receive Web service messages. To receive notifications, the application has to be an endpoint.
This is not meant to be a complete list of message exchange patterns, but it shows how the flexible the WS-Addressing protocol can be. As the protocol’s specification points out, the one-way message exchange pattern is the basis for all the other patterns. The Message Addressing Properties are defined to make a one-way message feasible.
WSE Transport Classes
In order to have transport independent messaging, the WSE Transport classes must be usable with a variety of transport mechanisms. These transport classes are found in the Microsoft.Web.Services2.Messaging namespace. In addition, you must be able to programmatically set the addressing classes mentioned in the last session on producing a SOAP message, and access them when consuming a SOAP message. This section will outline the major features of the transport classes. I will demonstrate using them in an application in the next section when I discuss the Mortgage Loan application service example.
As mentioned earlier, a SoapContext instance is a property of the SoapEnvelope class. The Addressing property maps to the AddressingHeaders instance that holds the objects that implement WS-Addressing protocol. As I will discuss in a future article, the Security property holds the objects that implement the WS-Security protocol. The SoapContext class is found in the Microsoft.Web.Services2 namespace.
The SoapSender class offers the most flexibility in sending a SOAP message. The constructor takes an EndpointReference or a Uri for the destination of the message. The Send method takes as an argument the SoapEnvelope instance that represents the message you wish to send. It then dispatches the message to the destination. This method has no return value since this is a one-way message. You must explicitly set the addressing properties (such as Action) you require. Also, if you wish to implement request/response you have to set up the message producer to receive a SOAP message. The case study will illustrate how to do this.
EndpointReference destinationER = new EndpointReference(destinationUri); SoapSender sender = new SoapSender(destinationER); SoapEnvelope request = new SoapEnvelope(); ... sender.Send(request);
An alternative, simpler method of producing SOAP messages is to derive a class from the abstract SoapClient class. SoapClient itself derives from SoapSender. In the derived class constructor you pass the EndpointReference or Uri of the destination. You then implement a method that will be used by the caller of your class to send the message. Within this method you dispatch the SoapEnvelope instance that represents the outgoing message. You can use either of two methods in the base SoapClient class. The SendOneWay method does not return a value. The SendRequestResponse method returns a SoapEnvelope object that represents the response to the original message. Both of these methods are overloaded. The first argument to all of them is a string that represents the Action addressing header. The second argument can either be a SoapEnvelope instance, or an object that will be placed in the body of the SOAP message. The case study will have an example of using this class. How does the SendRequestResponse method work? The WS-Addressing protocol defines an anonymous URI. The idea behind this anonymous URI is that various technologies such as firewalls, DHCP and NAT made it impossible to define an endpoint in all circumstances. For example, in SOAP over HTTP, using this anonymous URI for the reply address means sending the response over the same channel that delivered the request message.
Similarly, there are two classes to use for consuming SOAP messages. You can derive your consumer class from the abstract SoapReceiver. You provide an implementation for the Receive method that processes the message Your register this method with the SoapReceivers collection to listen for requests.
EndpointReference er = new EndpointReference(uri); SoapReceivers.Add(er, typeof(MortgageService));
Since multiple messages can be sent to this endpoint, you can use the Action addressing property to dispatch the message to the appropriate handler code.
For a simpler alternative you can derive a class from the abstract SoapService class. SoapService, not surprisingly derives from SoapReceiver. You create a method to process the message and decorate it with the SoapMethod attribute whose value is the Action addressing property. The argument to this method must either be a SoapEnvelope or an XML serializable type.
[SoapMethod("urn:Bank:MortgageLoanRequest")] public long ProcessMortgageApplication (SoapEnvelope message) …
Analyzing the Case Study
The code directory for this class has two versions of our initial version of the Mortgage Loan Service. One uses the TCP protocol, the other the HTTP protocol. To get the HTTP protocol version to work make sure you follow the instructions in the readme.txt file. Between the two examples, they demonstrate the use of all the transport classes discussed in the previous section.
Let us examine the TCP version first. Start the Mortgage Service application. It is found in the MortgageService directory. Then run the MortgageClient program. You will see the window shown in Figure 1.
For simplicity, only the request loan amount can be set, the rest of the loan information is hard coded. The clear button clears the information in the form fields. Pressing the Send button transmits the loan request in a SOAP message to the service. If the request is successful, a mortgage application id will be returned. Otherwise, the Error Info field will have information from the SOAP fault.
First let's look at the Mortgage Client application. It is composed of two projects, MortgageApplication and MortgageClient. The MortgageApplication builds an assembly that defines the XML serializable version of the MortgageApplcation schema. This code was generated using the xsd tool that comes with the .NET Framework. For simplicity, the example builds the body of our SOAP message using XML serialization. If you were building a SOAP message that would be consumed by a non-.NET service, you might have to build it using the XML classes. The user interface code is found within Form.cs as well as the hard coding of most of the mortgage application. The click handler for the Send button has the following line of code:
MortgageRequest.SendMortgageRequest();
This invokes the routine that actually sends the mortgage request to the loan service (see Listing 4)
Uri destinationUri = new Uri("soap.tcp://localhost/MortgageService"); EndpointReference destinationER = new EndpointReference(destinationUri);
SoapSender sender = new SoapSender(destinationER);
SoapEnvelope request = new SoapEnvelope(); request.Context.Addressing.Action = new Action("urn:Bank:MortgageLoanRequest");
request.Context.Addressing.ReplyTo = new ReplyTo(MortgageResponseHost.uri); request.Context.Addressing.FaultTo = new FaultTo(MortgageResponseHost.uri);
request.SetBodyObject(Form1.application);
sender.Send(request);
Note how the EndpointReference is passed to the SoapSender constructor, the SoapEnvelope is constructed and the appropriate addressing headers are set, including the destination of the reply message. Since the program sends a one-way message, similar code will have to be set up in the client program to receive the reply. This is done in MortgageReply.cs. This code is similar to the code I will discuss in the Mortgage Service.
Now let's look at the Mortgage Service application. It is composed of three projects: MortgageApplication, MortgageProcessor, and MortgageService. The MortgageApplication project is the same one used in the client application. The MortgageProcessor project is the equivalent of the business object that handles the actual mortgage processing. In this first version it just records the request, and assigns a new loan number. This business object could be used in other applications or services. So it has to be kept isolated from the code that handles the service interface.
The main program registers the service using the SoapReceivers class discussed earlier. The MortgageService class derives from SoapReceiver. Within its Receive method (see Listing 5) it will process the message only if it has the correction Action value. The actual processing of the SOAP body occurs inside ProcessMortgageRequestMessage method. This method uses the XmlValidatingReader class to ensure that the XML document in the body of the SOAP message corresponds to the XML Schema definition. This step validates that the input document is in the format that the business object expects. Only after that is done is the XML document serialized to a .NET object. Instead of serializing to an object, the XML document could have been manipulated using the XML DOM inside the business object. I chose the first approach for simplicity since the thrust of the article is on WSE, not XML. Nonetheless, the validation step is extremely important.
Note how the reply message is sent using the value in the ReplyTo addressing header block.
protected override void Receive (SoapEnvelope message) { if (message.Context.Addressing.Action.Value == "urn:Bank:MortgageLoanRequest") { SoapEnvelope response = new SoapEnvelope(); response.Context.Addressing.Action = new Action("urn:Bank:MortgageLoanResponse"); XmlElement responseBody = response.CreateBody();
MortgageMessageProcessor mp = new MortgageMessageProcessor(); mp.ProcessMortgageRequestMessage( message.Body.InnerXml, response);
Uri responseUri = message.Context.Addressing.ReplyTo.Address.Value; SoapSender sender = new SoapSender(responseUri); sender.Send(response); return; } }
The service configuration file uses the allowRedirectedResponses element to enable the SOAP replies and faults to go to a different endpoint than the source. By default, this is not allowed to mitigate denial of service, and other similar attacks.
The HTTP version of the code is similar, except it uses the SoapClient and SoapService classes.
WSE 2.0 comes with a diagnostic trace facility. Figure 2 shows how to set the Diagnostics tab on the WSE Property dialog that is available on the context menu for the Project inside Visual Studio.NET. When this tab is set all SOAP messages sent and received from the application will be written to the designated log file. The projects in the sample code have diagnostics turned on.
Conclusion
This article has discussed how to address and transport SOAP messages using WSE. You have seen how to manipulate the SoapEnvelope class to build messages. The WS-Addressing protocol and its implementation in WSE 2.0 allow for transport-independent addressing and dispatch of SOAP messages. Hopefully, you now realize that SOAP and HTTP are independent of each other. The next step is to discuss how to secure a SOAP message using the approved WS-Security specification.
Michael Stiefel is the principal of Reliable Software, Inc. where he does training and consulting in Microsoft technologies for companies ranging from startups to the Fortune 500. He is the co-author of “Application Development Using C# and .NET” published by Prentice-Hall. Go to www.reliablesoftware.com for more information about his development work and the training courses he offers. |
||
All Content (c) 2000 - 2014 Reliable Software, Inc. All rights reserved. |