|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionThis article describes the exploitation of the pluggable transport infrastructure provided as part of ASP.NET Web Services, which allows developers to deliver Soap messages built in the document/literal format to be delivered to an end point via mechanisms other than default HTTP transport. In this article, a framework that supports the addition of alternative physical delivery mechanisms is presented, and two such implementations are included – MSMQ and Websphere MQ are chosen to illustrate a store-forward capability for a more 'guaranteed' Web Services request delivery. The framework that will be developed integrates the Web Services Enhancements (WSE) 1.0 for secure 2-way transport of messages over non-HTTP mechanisms, thereby giving the developer a choice of 'trusted' (clear message) or 'untrusted' (secure message) delivery to the chosen endpoint. Soap was designed as a transport neutral protocol to be used in combination with a variety of transport protocols such as HTTP(S), SMTP, FTP etc. to deliver structured and typed information between two participants. A great deal of work has gone into making ASP.NET Web Services support Soap message generation in a way that promotes interoperability within heterogeneous environments (for example .NET consuming a BEA Weblogic Server hosted Web Service) but to date the standard access to Web Services within the Microsoft arena has been almost exclusively geared to HTTP. Soap over HTTPThere are of course strengths and weaknesses of the HTTP transport – a definite plus point is the maturity of the associated security infrastructure, which has risen on the back of the ubiquity of the web in general. Conversely, a weakness is seen to be the lack of any form of guaranteed and reliable (single message only) delivery within the protocol, which has spawned solutions such as HTTPR from IBM. Add to this the fact that the back-end Service has to physically be on line for the request to be received over HTTP, and there are some pretty compelling reasons to want to find alternative ways to "get the message across". Soap over alternative transportsWhile ASP.NET does a lot to promote the HTTP-centric binding to Web Services, HTTP is not the only protocol available for transporting Soap. Indeed, a little talked-of area is the Web Services infrastructure in place within the .NET framework that allows message delivery mechanism and endpoint to be altered either statically (design time) or dynamically (run time). The real power of this infrastructure – named "pluggable protocols" by Microsoft – is that the transport method can be altered underneath the logical client request to direct Soap via any sensible medium the developer cares to implement. What this case study will demonstrate is the ability to effectively switch transports underneath ASP.NET client between HTTP, MSMQ and Websphere MQ. It will demonstrate how trivial it is to alter the 'target' of a Soap Web Service request from a standard IIS hosted variant, to a listener waiting on an MSMQ or MQ queue. We'll demonstrate the exercising of API using standard types like strings, arrays, binary data and custom serializable types. We'll also show how this support can be achieved in an asynchronous fashion. Securing SoapIt is likely that the queuing style transports would find use within the enterprise i.e. within the trusted network of an organization – with their commensurate (typically reduced) security requirements. However, by allowing non-HTTP delivery options, we've potentially taken away a layer of proven security that we ought to investigate a replacement for. Fortunately, a great deal of work has been undertaken in developing some joint standards for a new generation of web services protocols covering (amongst others) security, message routing and message attachments. This push really centers round support for securing messages between multiple processing nodes involved in a business transaction in a transport-agnostic way – without having to rely on technologies like SSL which only work well point-to-point. This effort has culminated recently in the release of Microsoft's implementation of the aforementioned standards in the form of the Web Services Enhancements (WSE) 1.0 package. So, the case study will also show how the implementation of WS-Security within the WSE 1.0 can be employed to secure (in our case digitally sign and symmetrically encrypt) the messages produced by ASP.NET clients and Servers instead of using the native security mechanisms inherent in the two queuing products. We'll also look at the DIME support provided by the WSE as an alternative way to send binary data, which was developed for transmission of large binary data streams. Article goalsHopefully by the end of the case study the following aspects will be clarified:
Context for workThe original context for the work involved a Proof of Concept project designed to determine how easily a .NET client could access an existing Java-oriented backend using Web Services for interoperability, but via IBM Websphere MQ rather than HTTP. This article provides a .NET oriented back-end on the grounds that in doing so we can keep the skill sets covering the article constrained to .NET and can demonstrate secure communications more easily by virtue of having WSE at both ends. However, idiosyncrasies of interoperation with a Java service implementation will be identified at appropriate points throughout the article. System RequirementsThe built solution will require the following deployment environment: 1. Windows 2000 SP2 or Windows XP Pro with MSMQ and IIS installed 2. .NET Framework 1.0 (SP1 or above) 3. WSE 1.0 SP1 runtime 4. (For MQ support only ) Websphere MQ 5.3 Trial Release [2] 5. (For MQ support only) VB6 runtime [10] In order to rebuild the solution binaries, you will require the following additional tools: 6. VS.NET (C#) 7. WSE 1.0 SP1 – full installation recommended 8. VS.NET WSE Settings Tool (optional) [8] Skills requirementFrom a skills perspective, the reader should have:
Article goalsEssentially we aim to make progress in the following areas:
WSDL and Web Services proxy generation in .NETInterface contracts are described using WSDL (akin to IDL for COM). Once this WSDL is available at a URL or within a file, a Web Services proxy class can be generated either by directly using the standalone tool WSDL.EXE shipped with the SDK or implicitly when using the Visual Studio .NET 'Add Web Reference' facility:
Figure 1: The WSDL-2-Proxy process The generated proxy file (usually called [SoapDocumentMethodAttribute(...)] public string IsCollaboratorRegistered(string vstrEnvUUID) { object[] results = this.Invoke("IsCollaboratorRegistered", new object[] { vstrEnvUUID }); return ((string)(results[0])); } With the standard proxy in place, clients can the access the Web Service at the appropriate URL using code similar to the following: // Get an instance of the proxy localhost.RegService objWSReg = localhost.RegService(); // Endpoint can be varied at runtime etc. objWSReg.Url = "http://192.168.32.1/MyService.asmx"; // Make the call string strRetXML = objWSReg.IsCollaboratorRegistered("12345678901234567890123456789012"); Being able to vary the URL and to describe this URL in terms of a "protocol scheme" and an "end point" is the key aspect of the approach. At runtime, the single line within the proxy containing the call
We'll break this down further later in the article to show how the pluggable protocol infrastructure is combined with Soap serialization to build and deliver messages. In particular there are several methods that can be overridden within the proxy to tap into or even replace the message transport protocol process. Defining Internet and other Network Transports for Web ServicesWhile the de-facto delivery protocol for Soap messages is HTTP, there is an infrastructure in place within the .NET framework that allows message delivery mechanisms to be altered either statically (design time) or dynamically (run time). The .NET Framework uses three specific classes to provide the information required to access Internet / Network resources through a request / response model: 1. the 2. the 3. the Client applications create [NOTE: A Uniform Resource Identifier (URI) is a compact string of characters for identifying an abstract or physical resource as defined in RFC2396. The "scheme" is a standard part of the URI, which can be broken down into a number of distinct segments. All network identifiers are capable of mapping into one of the parts. The scheme is typically the protocol identifier e.g. http, ftp etc.] Out of the box there is support for three specific protocol schemes - "http:", "https:" and "file:". We will create new versions of the WebRequest and WebResponse classes to provide support for a further two:
One can imagine that additional support could be added for other schemes such as "mailto:" or "ftp:" for SMTP and FTP respectively. Note at this point that attempting to create a WebRequest for a Uri involving a scheme that is unsupported: Uri uri = "mq://accountq1"; WebRequest newWebRequest = WebRequest.Create(uri); will yield an exception of the form Defining custom transportsThe
Figure 2: The Object model for pluggable protocols The main operations and attributes have been included to show the key aspects of any implementation. Common AspectsSeveral of the attributes and operations that can be overridden by
descendants of the We define the following new derived types to extend the hierarchy:
Three criteria must be met in order for a protocol-specific class to be used as a pluggable protocol: 1. Preparing for scheme registrationThe implementation must incorporate a version of the
public class MSMQRequestCreator : IWebRequestCreate { public WebRequest Create(Uri uri) { // Create a new MSMQ request object return new MSMQWebRequest(uri); } } 2. Scheme registration and Uri creationDescendant classes of WebRequest that wish to implement new protocols must be registered with the WebRequest class in order to be allowed to manage the details of making the actual connections to non-standard network resources like Queues. This is usually achieved in a similar fashion to the following: // Register the "mq:" prefix here for WebRequest MQRequestCreator objCreator = new MQRequestCreator(); WebRequest.RegisterPrefix("mq", objCreator); This effectively allows the subsequent hooks up of the Create code for an instance of the new scheme, such that the code we mentioned above: Uri uri = "mq://accountq1"; WebRequest newWebRequest = WebRequest.Create(uri); will then work since the new scheme is known to .NET Web Services infrastructure. 3. Implementation of key overridden membersThe derived classes must override the abstract methods and properties of
As implied above, we'll implement any overrides common to MQ and MSMQ
versions in Execution of a WebRequest-derived instanceHere we'll attempt to map the general client-side serialization and transportation process onto the discrete calls made by the .NET Web Services infrastructure during a synchronous web method call. The diagram depicts the following sequence of events on the “request” execution path:
Figure 3: Interaction diagram for WebRequest Key facets of operation include:
If you do get the chance, I'd strongly recommend a little trawl through the debugger, setting breakpoints all over the code as it really is instructive as to the ordering of events in the overall process. Implementing MSMQ and Websphere MQ transportsAssuming we can access the serialized Soap message (which we deal with
below), we need to hook in a protocol handler, based on Miscellaneous common Request aspectsSeveral overridden properties are not required for operation and so may be allowed to throw aNotSupported exception e.g.: public override IWebProxy Proxy { /* override */ get { throw new NotSupportedException(); } /* override */ set { throw new NotSupportedException(); } } Others, like public override int Timeout { /* override */ get { return m_intTimeout; } /* override */ set { m_intTimeout = value; } } These aspects, shared by MSMQ and MQ implementations, are abstracted into the
Providing a Stream for serialization
public override Stream GetRequestStream() { if (m_RequestStream == null) m_RequestStream = new MemoryStream(); else throw new InvalidOperationException("Request stream already retrieved"); return m_RequestStream; } As we would not expect the stream to be provided twice in the course of a
single Web Services method call, and we also do not anticipate ever sharing
NOTE: I hope this is obvious but just in case, we don't do the serialization ourselves. We simply provide a container (stream) for the Web Services infrastructure to do so. Miscellaneous common Response aspectsIn addition, Common facets of a Queue-oriented transportThere are several operational aspects we would like to incorporate into our queue management. A "Request Timeout"The standard generated proxy includes a timeout data member, which has a default value and can be set prior to invocation of the actual Web Service call. We'd like that timeout to be honored by the Queuing implementations and we'd like them to raise exceptions like the standard protocol handlers would in the event of a timeout. Request-Response matching (at the protocol level)In a multi user scenario, it is likely that many requests will be outstanding at any one time, and that responses to those requests will be received concurrently. In this situation there is a requirement to be able to match up the response to the associated request. There are principally two options available to support this feature: 1. Support some form of Request ID at the application level API (i.e. in WSDL) that the Service can act on. 2. Use any native support provided by the infrastructure to correlate the request to the response. While the first option would give us the ability to support a
transport-neutral way of correlating messages, it would be at the expense of the
queue protocol handlers, which would need to effectively peek at all the
messages in the queue to identify a response. Taking option 1 also perverts the
purity of the Application API somewhat, in that each method call is required to
support an extra parameter just to allow the consolidation to occur. Finally,
both forms of queuing provide support for correlation by including a
A "Response Uri"Although the transportation infrastructure was developed explicitly to support non-standard protocols, it predominantly revolves around a target endpoint and assumes a single point of contact around the delivery of the request and response. Actually, when it comes to queuing we'd like to be able to give a client the ability to describe both a request Uri (endpoint to hit) and a response Uri, which the endpoint service could effectively call back on with the results of the call. What this means in our parlance is that we want be able to describe both a request queue and a response queue for the target of the request and target of the response respectively.
Figure 4: Object Model for MySoapClientProtocol The best approach appears to be to derive from
The resulting class is called A point worth bearing in mind is that any changes made to this proxy are lost
during regeneration of the proxy using the VS.NET menu item "Update Web
Reference" or when the Web Reference is removed. It may be worth backing up the
In terms of how the response queue information might be used, one could
imagine an implementation within the handlers "Uri representation" of a QueueAs was suggested in the section entitled "Defining Internet and other Network
Transports for Web Services" that the
The URI syntax does not require that the scheme-specific-part have any general structure or set of semantics, which is common amongst all URI. However, a subset of URI do share a common syntax for representing hierarchical relationships within the namespace. This "generic URI" syntax consists of a sequence of four main components:
Well know examples include http formats like:
In order to make use of the pluggable protocol infrastructure we are required to map MSMQ and MQ style queue names into this syntax. As can be seen in the next section, this can be achieved with varying degrees of ease depending on which queuing implementation we are talking about. Handling ErrorsAs part of the general .NET Web Services pluggable protocol infrastructure,
version of the
Figure 5: WebException Object Model The two queuing protocols support this approach, using the Status property to
reflect the following
These status codes are mapped from the specific MSMQ / MQ failure codes within the relevant protocol specific sections below. MSMQ-specific implementationAll the work is done in the overridden Accessing MSMQ functionsIn order to make use of MSMQ, the
Figure 6: System.Messaging Queue Name FormatThere are several ways to identify an MSMQ queue – by Path, by Format Name or by Queue Label. The Format Name is the native, unique representation of the Queue. The Path is a friendly representation of the Queue, which must be resolved to a Format Name by the MSMQ Directory Services. Normally the format of a Queue Path specification as understood by MSMQ takes one of the following forms:
We need to be able to express this kind of Path level information in an Uri
format to work with the pluggable protocol infrastructure. Unfortunately there
does not appear to be a particularly nice fit with MSMQ Path Names in this case.
The RFC 2396 entitled "Uniform Resource Identifiers (URI): Generic Syntax"
suggests that several characters are disallowed from the URI specification and
hence removed during parsing by implementations such as the .NET
While this then works well with Uri, it requires two things:
However, we'd like to describe the queue in the standard way – as it may well
be being used elsewhere in the wider context of our application, may be read
from a configuration file etc. So we need to provide some assistance. As we'd
ideally like to not have to differentiate between caller code making requests
against MSMQ and code doing so against MQ (or indeed HTTP etc.), we will add a
new method called // Get Queue name (from configuration file perhaps) string strMyQ = ".\\private$\\myq1"; // Create the Service proxy objHelloWorldService = new localhost.Service1(); // Set up the URI objHelloWorldService.Url = objHelloWorldService.FormatCustomUri("msmq://" + strMyQ); … // Make call string[] arrRes = objHelloWorldService.HelloWorldArr("Simon"); To be clear, the MSMQ ProcessingThis section essentially describes the guts of the GetResponse() method. First, we need to place the Soap message on the MSMQ queue described in the request Uri. As just explained, for MSMQ the Uri format of a queue name looks a little awkward and needs to be re- formatted to be MSMQ-friendly: // Create a message System.Messaging.Message objMsg = new System.Messaging.Message(); // Simply hook in the stream as the Body of the message objMsg.BodyStream = m_RequestStream; // Here we need to interpret the MSMQ Uri a little string strQueueName = this.m_RequestUri.LocalPath.Replace("/", "\\"); if (strQueueName.ToUpper().IndexOf("PRIVATE$") > = 0) strQueueName = "." + strQueueName; // Open the Queue for writing too objQueue = GetQ(strQueueName); // Send the message under a single MSMQ internal transaction objQueue.Send(objMsg, MessageQueueTransactionType.Single); // Free resources on main Queue objQueue.Close(); Making use of the MSMQ "Single" transaction type ensures a delivery of one (and only one) message onto the queue. For more sophisticated heterogeneous transactions support (e.g. covering the audit of the sending of this message to, say, a database) we would need to look at integrating this core work with .NET Enterprise Services to make use of COM+ style transactions. Note that, since we established we would use transport-specific correlation, the MSMQ message ID of the outbound message is captured and remembered at this point. We will rely on the Back End process to provide this ID as a "Correlation ID" (both MSMQ and MQ support this feature) to allow us to look for a matching response more easily: // Get the ID of the outbound message to use as the Correlation ID // to look for on any inbound messages string strCorrelationID = objMsg.Id; Once the message is placed on the queue, we then wait for a response to the message on a queue defined by the response Uri with a Correlation ID match. We wait for the period defined by the caller who can influence the timeout when creating the proxy: // Open the response MSMQ Queue and wait for a message try { … // Wait for a response with the Correct Correlation ID TimeSpan tWaitResp = new TimeSpan(0, 0, m_intTimeout / 1000); objQueue = GetQ(strQueueNameResp); msg = objQueue.ReceiveByCorrelationId(strCorrelationID, tWaitResp); } catch (Exception e) { … } One of two things will happen as a result of waiting on the response Queue: 1. An error occurs (error accessing the outbound / inbound queue, timeout waiting for the message response from the Back End etc. In this case, the error received from the managed MSMQ provider is thrown direct to the caller. Referring to the general WebException status code table presented in the section named "Handling Errors", here we list the specific mapping of major MSMQ failure scenarios to corresponding WebException status codes:
Note, in the latter case, an catch( … ) { // Have we already processed this exception in some way? if ((e is System.Net.WebException) == false) { // No - Create an MSMQWebResponse - this is a protocol specific error objMSMQWebResponse = new MSMQWebResponse( (into)QueueTransportErrors.UnexpectedFailure, e.Message, e.StackTrace); // And throw the exception throw new WebException( "MSMQ Pluggable Protocol failure.", e, WebExceptionStatus.ProtocolError, objMSMQWebResponse); } } 2. A Soap message is received as a stream and the
// Create a return, and stream results into it... objMSMQWebResponse = new MSMQWebResponse(); objMSMQWebResponse.SetDownloadStream(msg.BodyStream); … return objMSMQWebResponse; Websphere MQ-specific implementationAs with the MSMQ support, all the work is done in the overridden
Figure 7: IBM ActiveX classes for MQ Accessing MQ functionsIn order to make use of MQ, the only credible option available to us in Websphere MQ 5.3 is the COM wrapper that is provided with the MQ Client (MQ is optional. The demo application can run on any combination of HTTP, MSMQ and MQ transports. This is configurable within the demo). The COM wrapper must be included as a COM interop reference in our project. The additional notes section describes an alternative that improves the overall throughput when using MQ, for now the COM wrapper will suffice. Note that the resulting interop assembly, called
This signing can be achieved by undertaking the following steps:
Once this has been done, and the resultant assembly included as a reference to the project, the host project could then be signed and deployed to the GAC as required. Queue Name Access and FormatThe model for accessing an MQ queue is different to that for MSMQ. Principally, whereas MSMQ object hierarchy is fairly "flat" in nature, MQ employs a hierarchical structure that involves a 3-tier access consisting of:
Further to this, the name of a queue is built from two parts:
A major thing to note here is that, unlike MSMQ, entity names are case
sensitive. Indeed there are several 'gotchas' when working with a combination of
the Uri class (which if you remember lower- cases host names). When using the
framework, a tell tale sign of a naming issue is a If connecting to a queue called "fred" the default Queue Manager, one would use a Uri notation like: objHelloWorldService.Url = "mq://fred" If connecting to a queue called "bill" on a Queue Manager called "QM_john" , the Uri for this would be: objHelloWorldService.Url = "mq://QM_john/bill" Hopefully this gives a flavor of MQ queue nomenclature. In order to help
unbundled a Uri into a Queue Manager / Queue name combination, a utility method
called // What is the Queue Manager and Queue Name we need to direct the message at? ResolveManagerAndQueueName( this.m_RequestUri, out strQueueManagerName, out strQueueName); MQ ProcessingApart from the obvious semantic differences described in the last section, the main differences between this implementation and the MSMQ version are in the syntax when sending messages and waiting on a response Queue. Also, whereas MSMQ provides good support for direct use of streams when sending and receiving messages, there is no such support for MQ (at least when using the ActiveX wrapper) and we must rely predominantly on sending and receiving byte arrays instead. So, since MQ does not support the automatic use of streams as the body of a message, we need to convert the stream to an array of bytes, which can then be used to prime the outbound message: // Access the Soap serialised stream as an array of bytes byte[] bytBody = new Byte[m_RequestStream.Length]; m_RequestStream.Read(bytBody, 0, bytBody.Length); Given we have the Soap body, and have resolved the request and response Queue Manager / Queue Name combinations (shown above), the first thing to do is to get an MQ Session. This is required before any queuing work may be attempted: // Access a Session objMQSession = new MQAX200.MQSessionClass(); Next, we attempt a connect to the Queue Manager, and ask it for a Queue object given the name: // Get the named Queue Manager objQueueManager = GetQManager(objMQSession, strQueueManagerName, true); // Get the named Queue from the Manager objQueue = GetQ(objQueueManager, strQueueName, true); Once this has succeeded, we construct a message setting up message options, and write the body of the message: // Set up the message to send objMsg = (MQAX200.MQMessage)objMQSession.AccessMessage(); objPutMsgOpts = (MQAX200.MQPutMessageOptions)objMQSession.AccessPutMessageOptions(); // We need to send a byte array to cover off binary attachments etc.... objMsg.Write(bytBody); Putting the message on the queue uses the (default) message options we just set up : // Finally, put the message to the queue objQueue.Put(objMsg, objPutMsgOpts); [NOTE: If conversing with MQ running on AS/400 which operates in EBCDIC, the message would explicitly need to indicate it was in ASCII format prior to sending - objMsg.Format = "MQSTR " ;] Note that, since we established we would use transport-specific correlation, the MSMQ message ID of the outbound message is captured and remembered at this point. We will rely on the Back End process to provide this ID as a "Correlation ID" (both MSMQ and MQ support this feature) to allow us to look for a matching response more easily: // Get the ID of the outbound message to use as the Correlation ID // to look for on any inbound messages string strCorrelationID = msg.MessageId; Once the message is placed on the queue, we then wait for a response to the message on a queue defined by the response Uri with a Correlation ID match. We wait for the period defined by the caller who can influence the timeout when creating the proxy: try { // Open the response MQ Queue and wait for a message objQueueResponse = GetQ(objQueueManager, strQueueNameResp, false); objMsgResp = (MQAX200.MQMessage)objMQSession.AccessMessage(); objMsgResp.CorrelationId = strCorrelationID; objGetMsgOpts = (MQAX200.MQGetMessageOptions) objMQSession.AccessGetMessageOptions(); objGetMsgOpts.Options = (int)MQAX200.MQ.MQGMO_SYNCPOINT + (int)MQAX200.MQ.MQGMO_WAIT; objGetMsgOpts.WaitInterval = this.Timeout; objQueueResponse.Get( objMsgResp, objGetMsgOpts, System.Reflection.Missing.Value); // Use the appropriate reader strMsgRecv = objMsgResp.MessageData.ToString(); } catch (Exception e) { ... } One of two things will happen as a result of waiting on the response Queue: 1. An error occurs (error accessing the outbound / inbound queue, timeout waiting for the message response from the Back End etc. In this case, the error received from the managed MSMQ provider is thrown direct to the caller. Referring to the general WebException status code table presented in the section named "Handling Errors", here we list the specific mapping of major MSMQ failure scenarios to WebException status codes:
2. A Soap message is received as a byte array, converted to a stream, and the
// Convert the result into a byte array byte[] buf = new System.Text.UTF8Encoding().GetBytes(strMsgRecv); MemoryStream stResponse = new MemoryStream(); stResponse.Write(buf, 0, buf.Length); // Create a return, and stream results into it... objMQWebResponse = new MQWebResponse(); objMQWebResponse.SetDownloadStream(stResponse); … return objMQWebResponse; Tapping into the serialized Soap streamAs explained above, when implementing a The However, it appears (from a degree of single stepping through the code flow)
that there is no direct access in
Figure 8: Access to the Soap request stream Observation of the UML Sequence diagram presented earlier in the Case Study
will confirm that the infrastructure has already closed the stream at the point
we need it. We need to come up with an alternative approach to gain access to
the stream before it is disposed of. It turns out that the Microsoft
infrastructure has to The following two options are available to us:
Figure 9: MySoapStream So, we provide a class called public override void Close() { // DON'T CLOSE! BUT REWIND! m_Stream.Position = 0; } internal void InternalClose() { m_Stream.Close(); } We also change the implementation of the GetRequestStream() method to use this new type of stream: public override Stream GetRequestStream() { if (m_RequestStream == null) m_RequestStream = new MySoapStream(new MemoryStream(), true, true, true); else throw new InvalidOperationException("Request stream already retrieved."); return m_RequestStream; } Hence, revisiting the MSMQ WebRequest handler as an example, the
public override WebResponse GetResponse() { … // Create a message using the Stream objMsg.BodyStream = m_RequestStream; objMsg.Recoverable = true; … // Open the Queue for writing objQueue = GetQ(strQueueName); // Send the message under a single MSMQ internal transaction objQueue.Send(objMsg, MessageQueueTransactionType.Single); … // And close Soap Stream m_RequestStream.InternalClose(); … // Now wait on a response … } In this case, the stream may be used directly as the MSMQ message body using
the Once the message has been sent, we can close the stream ourselves using our
Hooking the new protocols into the generated proxy classesNow we have two new transport protocol handlers, let's make them useable from the proxy class. The resultant proxy now looks a little different to its original "freshly-generated" form: public class Service1 : MySoapClientProtocol { ... // The individual methods of the API are unchanged from their generated form [System.Web.Services.Protocols.SoapDocumentMethodAttribute(...)] public string[] HelloWorldArr(string name) { object[] results = this.Invoke("HelloWorldArr", new object[] {name}); return ((string[])(results[0])); } // The individual methods of the API are unchanged from their generated form ... // Overridden methods for directing Soap messages protected override WebRequest GetWebRequest(Uri uri) { // Delegate to a common generator for new protocols return ProxyCommon.GetWebRequest(uri, this); } protected override WebResponse GetWebResponse(WebRequest webReq) { // Delegate to a common generator for new protocols return ProxyCommon.GetWebResponse(webReq); } } The proxy now derives from Using the new proxy from a clientNow the hard work is out of the way, clients of the proxy can make use of it in the following way: localhost.Service1 objHelloWorldService = null; string strRequestUrl = "msmq://.\\private$\\myreq"; string strResponseUrl = "msmq://.\\private$\\myresp"; try { // Create the Service proxy objHelloWorldService = new localhost.Service1(); // Set up the request Queue via the Uri objHelloWorldService.Url = objHelloWorldService.FormatCustomUri(strRequestUrl); // Set up an alternative response Queue other than "<QUEUENAME>_resp" objHelloWorldService.ResponseUrl = objHelloWorldService.FormatCustomUri(strResponseUrl); // And the timeout objHelloWorldService.Timeout = vintTimeoutInSecs * 1000; // Run the method and print out the return Console.WriteLine(objHelloWorldService.HelloWorld("Simon")); } catch(Exception e) { ... } Support for asynchronous message deliveryThere are numerous articles describing how to take advantage of asynchronous support in Web Services (see foot of the article for details), so we'll not dwell on this subject too much. Suffice to say there are three basic options when implementing asynchronous calling:
Whichever method is chosen they all rely on access to a token returned to them once an asynchronous call has begun. The generated proxy classes have synchronous and asynchronous versions of each method call – the asynchronous ones include a pair of methods of the form: public System.IAsyncResult BeginHelloWorldArr( string name, System.AsyncCallback callback, object asyncState) { return this.BeginInvoke("HelloWorldArr", new object[] {name}, callback, asyncState); } public string[] EndHelloWorldArr(System.IAsyncResult asyncResult) { object[] results = this.EndInvoke(asyncResult); return ((string[])(results[0])); } The While not particularly complicated, asynchronous request support is nicely
encapsulated in an assembly like The key aspect of the support is a class called private Asynchronizer m_ssiReturnImage = null;
The caller of a method would include the use of the Asynchronizer as follows,
in our example first hooking up the delegate-based callback, and then invoking
the standard // Create the Service proxy objHelloWorldService = new localhost.Service1(); // Set up the URI objHelloWorldService.Url = objHelloWorldService.FormatCustomUri(vstrURI); // And the timeout objHelloWorldService.Timeout = vintTimeoutInSecs * 1000; // Create a new Async token m_ssiReturnImage = new Asynchronizer( new AsyncCallback(this.ReturnImageCallback), objHelloWorldService); // Begin the method call IAsyncResult ar = m_ssiReturnImage.BeginInvoke( new ReturnImageEventHandler(objHelloWorldService.ReturnImage), null); Any asynchronous responses would be directed to an event handler based on a delegate, which is defined with the return type of the standard synchronous version of the method call we are "asynchronizing": protected delegate byte[] ReturnImageEventHandler(); In this case, the responses to the call (namely the image data in this case) are accessible through a data member in the asynchronous token: protected void ReturnImageCallback(IAsyncResult ar) { localhost.Service1 objHelloWorldService = (localhost.Service1)ar.AsyncState; ... AsynchronizerResult asr = (AsynchronizerResult) ar; byte[] bytImage = (byte[])asr.SynchronizeInvoke.EndInvoke(ar); ... } Faking it – the "Back-end Service"So far, we've majored on what it takes to get the client side talking in the right way to the request and response queues. This is pretty meaningless unless we have some form of service to pick off messages and process them. In real life solutions, one would expect the use of Web Services to be targeted at heterogeneous environments where the client and server run on different platform and Web Services is a nice way to mesh the two worlds. However, due to a desire to keep the skill set required to keep up with the article to a manageable amount, we'll use .NET application at the back to consume the messages and produce (dummy) responses in order to allow the clients message reception to be tested. It should be noted that the service exists purely to act as a counterpoint to the client framework we have developed, and so very little work has gone into making it robust or scalable – but there's enough there to demonstrate the client capability. In the non-WSE arena we are currently describing, the Service really can be quite basic. Its job is to perform the following steps:
The logic is contained within one class, This first reads the supplied app.config file for all application level
settings (that is those within the // Read configuration settings NameValueCollection colSettings = ConfigurationSettings.AppSettings; Each queue found in the configuration file is added to the relevant pot: string[] arrVals = colSettings.GetValues(strKeyName); if (arrVals[0].ToLower().IndexOf("msmq://") == 0) arrMSMQQueuesToMonitor[intNumMSMQQueues++] = arrVals[0].Replace("msmq://", string.Empty); else if (arrVals[0].ToLower().IndexOf("mq://") == 0) arrMQQueuesToMonitor[intNumMQQueues++] = arrVals[0].Replace("mq://", string.Empty); The MSMQ and MQ queues are distilled from the collection, as is a polling delay (the back end service does not currently employ a thread pool). The following simple loop is then entered: // Wait on some queues - some MSMQ, and some MQ... for (;;) { // Do MSMQ queues first foreach (string strName in arrMSMQQueuesToMonitor) { WaitOnMSMQQ(strName, intPollDelay); } // Now MQ queues foreach (string strName in arrMQQueuesToMonitor) { WaitOnMQQ(strName, intPollDelay); } } WaitOnMSMQ implementationThis method is straightforward. First we wrap all work in a try-catch block in order to trap all exceptions and filter out "healthy" ones such as a timeout waiting on request message: try { … // Do the work here } catch(Exception e) { // Ignore timeouts as this simply means no queue message was there if (e.Message.IndexOf("Timeout for the requested operation has expired") < 0) TSLog("Exception caught in WSAltRouteBEFake[MSMQ]: " + e.Message + e.StackTrace); } What we'll do first is wait a while: // We'll need to wait a little time TimeSpan tWaitResp = new TimeSpan(0, 0, 0, 0, vintPollTimerDelay); // Get hold of the request queue MessageQueue objQueue = GetQ(vstrQueueName); // Wait for a message Message objRequestMsg = objQueue.Receive( tWaitResp, MessageQueueTransactionType.Single); // Close the Queue objQueue.Close(); Assuming we got a message, i.e. no exception has been thrown, we open up the message and get the data content into a byte array, while picking off some salient attributes such as a potential Correlation ID and response queue name: // Get the message content direct from the BodyStream string strResp = string.Empty; byte[] bufIn = new Byte[objRequestMsg.BodyStream.Length]; objRequestMsg.BodyStream.Position = 0; objRequestMsg.BodyStream.Read(bufIn, 0, (int)msg.BodyStream.Length); string strCorrelationId = objRequestMsg.Id; // Close stream objRequestMsg.BodyStream.Close(); // Get the response queue - we shall assume a default but // hope the client has identified a queue for us to call back on string strResponseQueue = vstrQueueName + "_resp"; if (objResponseMsg.ResponseQueue != null) strResponseQueue = objRequestMsg.ResponseQueue.QueueName; We then check to see if this is a known message and if so, build a response message for it. There is very little intelligence in the matching method or generation of the response message – it exists purely to serve the client response code. // Fake out a response to the message strResp = BuildFakeResponseFromBytes("MSMQ" , bufIn, strResponseQueue); If we identified a request and were able to generate a response for it, then send it back to the client via the response queue using the Correlation ID: // If we have a valid response...use it if (strResp.Length > 0) { // Get the response queue objQueue = GetQ(strResponseQueue); // Send a message back MemoryStream stResp = new MemoryStream(); stResp.Write(new UTF8Encoding().GetBytes(strResp), 0, strResp.Length); Message objMsg = new Message(); objMsg.BodyStream = stResp; objMsg.Recoverable = true; objMsg.CorrelationId = strCorrelationId; // Send the message under a single MSMQ internal transaction objQueue.Send(objMsg, MessageQueueTransactionType.Single); // Free resources on response Queue objQueue.Close(); } Here we are geared up to use the That's really all that is required to effect basic end-end Web Service processing over the medium of queues. We could stop at this point or exploit a very interesting new technology that will help us secure our messages independent of the transport over which we are running them. How Web Services Enhancements (WSE) alters the picture for Client and ServerThe WSE is Microsoft's initial implementation of some of the first 2nd generation GXA Web Services specifications - WS-Security, WS-Attachments (via DIME) and WS-Routing / WS-Referral. It was released in Dec 2002, after 4 months in beta test. The whole standardization push continues the initiative to improve the capability for interaction / interoperability within the web service stack and implementations of these particular standards will really form the baseline for entry-level trading partner applications based around the Internet. While it is anticipated that Soap over a queued transport will find use within the trusted networks of a single organization, this does not negate the possibility that the data will require protection in transit between enterprise applications (e.g. sensitive payroll data). So, the main aspect of the WSE that is interesting to us in this article stems from the implementation of the WS-Security specification; namely the ability to:
It should be noted that this functionality represents a subset of what the WSE is capable of providing. Interesting features we are not using here include the use of binary security tokens (which could be capable of housing a Kerberos ticket for example) and asymmetric encryption using X509 certificates. In addition, as we are using WSE and WSE implements the WS-Attachments specification, we can look at extending our demonstration application to explore the ability to add binary attachments via DIME support. It will become evident that the DIME support does not follow the standard Soap processing model – expect to see it's implementation alter over the next 12 months. WSE overviewThe WSE is a data processing engine, which essentially applies (under instruction) a series of transformations to outbound Soap messages once they have been serialized by the .NET Web Services infrastructure, and conversely applies further transformations on incoming Soap messages prior to their de-serialization into objects / parameters. These transformations involve the inclusion of additional Soap headers and in some cases (like encryption) alterations to the body of a Soap message as well. The API provided by the WSE is relatively low level which makes it great for getting in amongst and influencing the operation of both client and server side. A great article covering the inner workings of the WSE can be found in [4]. We'll draw out the salient points in the sections that follow to give us some context for the changes we shall make to our client and server. The physical manifestation of the WSE 1.0 is a single GAC-installed assembly
called The WSE Filter Pipeline ArchitectureThe single most major change between the beta version of the software (called the WSDK, released Aug 2002) and the WSE 1.0 is that the functionality effecting the transformations on Soap messages has been repackaged into a series of filters. These filters are one of two varieties, Output filters concentrate on manipulation of an outbound Soap message, whereas Input filters operate on incoming Soap messages.
Figure 10: Filter Pipeline Architecture
Each set of filters is organized into either an input filter or output filter pipeline, which out-of-the-box runs the filters in the following order (for Client-to-Server flow only):
Figure 11: Filter Pipeline Architecture in more detail (Client -> Server flow) Amongst other things this ensures that the encryption and signing (Security) are applied to a Soap payload that all other modifications have been completed on and that securing happens just prior to sending of a request, whereas decryption and signature verification happen straight after reception of a request and before any other data sensitive operations can take place. The main thing to remember here is that each pipeline operates on a Soap envelope to produce different content within that envelope. The pipeline concept within the WSE is a very powerful one, as pipelines can
be manipulated programmatically to remove or reorder stages, and new custom
pipeline stages may be introduced based on the WSE-provided base classes
The integration of the WSE pipeline execution of input and output filters with the standard Soap serialization and delivery mechanisms needs to be managed in two different ways:
The purpose of the new proxy base class and server side extension is to sit between the transport and Soap serialization / de- serialization modules, intercepting messages and building / analyzing Soap headers as appropriate. As a result it is highly possible that when using, say, signature verification within a Web Service, should this verification detect tampering and generate an appropriate Soap fault, not a single line of application business logic will ever have been called by the time the fault is generated. This nicely insulates application code from WSEs infrastructure code, which seems an attractive benefit. The WSE SoapWebRequest and SoapWebResponseThe WSE itself employs the .NET Web Services pluggable protocol
infrastructure we talked about earlier to provide two new communication classes
called The Our interest within the framework will revolve around integrating our queue-based classes with these new Soap web request / response classes, and in particular picking up the resultant Soap stream for use by our protocol handlers. However, in the case of our Back End Service, The WSE SoapContext for communicationWe've talked about the operation of the WSE but so far not about how this processing is influenced by any application code we may write. The SoapContext class is the vehicle by which application code and WSE formatting code indirectly communicate. This happens in one of two ways, depending on the point in the process:
The class contains several attributes of interest:
Figure 12: WSE Soap Context The Envelope contains XML marking the "wor | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||