Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Secure Web Services via TCP/IP

0.00/5 (No votes)
30 May 2003 1  
Describes an approach for delivery of Soap Messages serialised using ASP.NET Web Client Services over TCP/IP

Secure Web Services via TCP/IP

Introduction

This short article builds on the previous article "Secure Web Service via Message oriented Middleware" [1] and extends the framework developed in the former to cover TCP/IP-based communications.

Since the solution will merely extend the original framework with support for a new communications protocol, all the original facilities (such as the ability to secure data using the Web Services Enhancements (WSE) 1.0) are capable of being utilized within the IP communications channel.

It is strongly recommended that the reader become familiar with the first article prior to reading this one as it explains the rationale for the design of the Microsoft "pluggable protocols" architecture.

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.

Article Goals

Simply, we aim to do the following in this article:

  • Implement a new transport handler for TCP/IP within the pluggable protocols framework developed in the previous article. This would allow for the support of all the advanced features developed in the earlier code; namely Web Service Enhancements (WSE) security support, DIME support for binary attachments, etc.
  • Introduce a mechanism for altering the Soap content of a message to a proprietary format using Soap Extensions and attributes. We’d typically want to do this when dealing with a legacy back end Service. The use of attributes to decorate Web Service (proxy) methods would mean we could simply remove the attributes at a later date to reinstate the standard Soap data format as the back-end the Service is updated for Soap support.

System Requirements

The built solution will require the following deployment environment:

  1. Windows 2000 SP2 or Windows XP Pro
  2. .NET Framework 1.0 (SP1 or above)
  3. WSE 1.0 SP1 runtime.

In order to rebuild the solution binaries, you will require the following additional tools:

  1. VS.NET (C#)
  2. WSE 1.0 SP1 – full installation recommended
  3. VS.NET WSE Settings Tool (optional)

Skills Requirement

From a skills perspective, the reader should have:

  • familiarity with C# to an intermediate level. Exposure to writing and consuming a basic Web Service in .NET, since we will be examining the client side infrastructure (including the WSDL-generated proxy) in some detail.
  • some appreciation of the work being done in the "second-generation" Web Services arena like WS-Security and WS-Attachments / DIME would be advantageous.

Implementing the TCP/IP Transport

From the original article, we know we can gain access to the Soap stream. Given the framework is in place, we merely need to hook in a new protocol handler, based on WebRequest. This section highlights the key facets of the implementation for the IP handler.

TCP/IP-Specific Implementation

As with the MSMQ and MQ handlers, all the work is done in the overridden GetResponse() method.

Accessing IP Functions

The .NET Framework contains a set of managed classes to control the use of IP communications both at a high level via abstracted types such as TcpClient, and using a lower level API like Sockets. In order to make use of IP, the System.Net.Sockets namespace must be included in the project:

The System.Net.Sockets namespace

IP Address / Port Format

In terms of the standard Uri specification discussed in the first article, an IP-based Service is identified by a scheme, an IP Address and Port Number as follows:

ip://127.0.0.1:42000

TCP/IP Processing

This section essentially describes the guts of the GetResponse() method.

First, we need to access the serialized Soap message, which has been built in the m_RequestStream data member variable. We read it into a byte array for use shortly. We also close that stream now that we have finished with it:

// Access the Soap serialised stream as an array of bytes
byte[] bytBody = new byte[m_RequestStream.Length];
m_RequestStream.Read(bytBody, 0, bytBody.Length);
m_RequestStream.InternalClose();        

Next, a TcpClient connection is created. Here, the timeout slot provided by the framework is also wired in and represents the receive timeout of the underlying socket:

// Create a TcpClient
TcpClient objTcpClient = new TcpClient();

// Set up the receive timeout on the socket - once breached, a SocketException
// would be thrown
objTcpClient.ReceiveTimeout = m_intTimeout;

// Also, set the outbound buffer size
objTcpClient.SendBufferSize = bytBody.Length;        

We then attempt to connect to the address and port represented by the Uri:

objTcpClient.Connect(
    strHostIPAddr, 
    intHostPort);

// Get a Network stream – this is how we’ll read a result shortly
networkStream = objTcpClient.GetStream();        

Once done, we can turn our attention to sending the message:

// Send the message over the Network stream
networkStream.Write(
    bytBody, 
    0, 
    bytBody.Length);        

After tidying the TcpClient object, we are in a position to listen for a result. We actually proactively ask the network stream directly for some results:

// Read the socket response if we can...
byte[] bytResponseBody = new byte[objTcpClient.ReceiveBufferSize];

try
{
    // Reads the NetworkStream into a byte buffer. Default is 8192 bytes.
    // NOTE – you should raise this number or implement "chunked" receives 
    // getting several blocks until all the stream is completely read.
    networkStream.Read(bytResponseBody, 0, 
                       (int) objTcpClient.ReceiveBufferSize);
}
catch(Exception e)
{
    // This handles exceptions from the receive process that are non-retryable
    // These include timeouts waiting on the message, and all other exceptions.
    if (e.InnerException != null &&
      e.InnerException.GetType() == typeof(System.Net.Sockets.SocketException))
    {
        if (((SocketException)e.InnerException).ErrorCode == 10060) 
                                                              // WSAETIMEDOUT
            // Throw the exception as a Timeout
            throw new WebException(
                "Timeout waiting on IP Socket resource.", 
                e.InnerException,
                WebExceptionStatus.Timeout,
                null);
        else
            // Just throw it up the way as a Receive failure
            throw new WebException(
                "Failed to receive the message from the IP Socket.", 
                e.InnerException,
                WebExceptionStatus.ReceiveFailure,
                null);
    }
    else
    if ((e is System.Net.WebException) == false)
    {
        // Just throw it up the way as a Receive failure
        throw new WebException(
            "Failed to receive the message from the IP Socket.", 
            e,
            WebExceptionStatus.ReceiveFailure,
            null);
    }
}
finally
{
    // Free resources on socket
    objTcpClient.Close();
}        

One of two things will happen as a result of waiting on a response from the remote host:

  1. An error occurs (error receiving the response message, timeout waiting for the message response from the Back End, etc. In this case, the error received from the underlying Socket receive is thrown direct to the caller. Referring to the general WebException status code table presented in the section named "Handling Errors" in the first article, here we list the specific mapping of major Socket failure scenarios to corresponding WebException status codes:
    WebException Status Triggering event
    ConnectFailure Failed to connect to the remote host given by IP Address and Port Number when calling objTcpClient.Connect() in IPWebRequest::GetResponse()
    NameResolutionFailure N/a
    SendFailure Failed to send the message to the remote server during call to networkStream.Write() in IPWebRequest::GetResponse()
    Timeout Failed to receive a response from the remote server before the timeout period was reached in IPWebRequest::GetResponse()
    ReceiveFailure Failed to receive the corresponding message response from the remote server for a non-timeout reason in IPWebRequest::GetResponse()
    ProtocolError Any other failure within IPWebRequest / IPWebResponse. In this case, an IPWebResponse is created which details the exact failure.

    Note, in the latter case, an IPWebResponse is created and passed as a parameter to the constructor of the WebException class – this also wraps up the actual exception which will become the 'InnerException' of the WebException:

    catch( … )
    {
       // Have we already processed this exception in some way?
       if ((e is System.Net.WebException) == false)
       {
          // No - Create an IPWebResponse - this is a protocol specific error
          objIPWebResponse = new IPWebResponse(
                (into)QueueTransportErrors.UnexpectedFailure, 
                e.Message, 
                e.StackTrace);
    
           // And throw the exception
           throw new WebException(
                "IP Sockets Pluggable Protocol failure.", 
                e,
                WebExceptionStatus.ProtocolError,
                objIPWebResponse);
       }
    }
  2. A Soap message is received as a stream and the IPWebResponse instance is created and primed directly with the returned stream, in preparation for the Web Services client-side infrastructure to process it:
    // Create a return, and stream results into it...
    objIPWebResponse = new IPWebResponse();
    objIPWebResponse.SetDownloadStream(msg.BodyStream);            
    …
    return objIPWebResponse;

Using the New Proxy From a Client

The only other real change to any of the code in the original article is that the console test program needs to be extended to use IP:

localhost.Service1 objHelloWorldService = null;
string strRequestUrl = "ip://127.0.0.1:42000";

try
{
    // Create the Service proxy
    objHelloWorldService = new localhost.Service1();

    // Set up the request Queue via the Uri
    objHelloWorldService.Url
                    = objHelloWorldService.FormatCustomUri(strRequestUrl);

    // And the timeout
    objHelloWorldService.Timeout = vintTimeoutInSecs * 1000;

    // Run the method and print out the return
    Console.WriteLine(objHelloWorldService.HelloWorld("Simon"));
}
catch(Exception e)
{
    ...
}

Extending the "Back-end Service"

The logic contained within the class BEService is extended to include a listener that waits on traffic on a certain port, and responds to it appropriately. The class first reads the supplied app.config file for all application level settings (that is those within the <appsettings> node):

// Read configuration settings
NameValueCollection colSettings = ConfigurationSettings.AppSettings;        

The port to listen on is found in the configuration file:

string strPortToListenOn = ConfigurationSettings.AppSettings["PortToListenOn"];
int intPortToListenOn = 42000;
if (strPortToListenOn != null &&
    strPortToListenOn.Length > 0)
    intPortToListenOn = Convert.ToInt32(strPortToListenOn);        

The listening on the socket is handled differently to that for the queue-based resources. In this case, a separate thread is created, which handles the listening code as described below.

ProcessIPMessages() Implementation

This method is straightforward. First, we create a high level listener and gear it up to listening on the given IP port. Then we start it listening:

// Create a listener
TcpListener tcpListener = new TcpListener(intPortToListenOn);

// Start the listening
tcpListener.Start();        

At this point, we can accept any inbound messages:

// Listen for a socket inbound connect
TcpClient objTcpClient = tcpListener.AcceptTcpClient();        

If we get past the above call, we have (inbound) data so we should access it:

// Read a request
networkStream = objTcpClient.GetStream();

byte[] bytMessage = new byte[objTcpClient.ReceiveBufferSize];

// Reads the NetworkStream into a byte buffer. Default is 8192 bytes.
// NOTE – you should raise this number or implement "chunked" receives getting
// several blocks until all the stream is completely read.
networkStream.Read(bytMessage, 0, (int) objTcpClient.ReceiveBufferSize);
string strIn = new UTF8Encoding().GetString(bytMessage).Trim(arrTrimChars);        

Once we have the strIn variable populated, we can tap into the existing Back-end functions like BuildFakeResponseFromString(), i.e., 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 = BuildFakeResponseFromString(
        "IP", 
        strIn,
        env.Context);        

If we identified a request and were able to generate a response for it, then send it back to the client via the network stream:

// If we have a valid response...use it
if (strResp.Length > 0)
{
    // Write the message back, using the response queue we were given...
    byte[] sendBytes = Encoding.ASCII.GetBytes(strResp);
    networkStream.Write(sendBytes, 0, sendBytes.Length);
}        

Once done, we flush and close the network stream.

That’s really the totality of the enhancements required to make the basic end-end Web Service processing work via TCP/IP.

Changes to Use of Web Services Enhancements (WSE)

Put simply there are none. Or, at least, very few. No changes are required within the client code as the SoapWebRequest class does the lions share of the work.

Updating the Back-end Service to Use WSE for Secure Communications

The only alteration involves the extension of the method ProcessIPMessages() described above to include raw WSE pipeline processing as occurs for the MSMQ and MQ listening paths – this is highlighted in bold below.

Invoking the Inbound Message Pipeline for Requests

Assuming we got a message, i.e., no exception has been thrown, we open up the message and get the data content into a stream:

// Process through WSE - this may be a passthru operation
// if WSE was not used to create the stream
MemoryStream stMessage = new MemoryStream();
stMessage.Write(bytMessage, 0, bytMessage.Length);
                
// Hmmm - we need to see if this is DIME based or not
// To do this we shall simply see if we can read the stream
// as a DIME stream - creating an Envelope in any case
SoapEnvelope env = InputStreamToEnvelope(stMessage);

// Close stream
stMessage.Close();

// Create Pipeline - with default filter collection for input and output
// (i.e. we are not overriding default behaviour in any way)
Pipeline objWSEPipe = new Pipeline();
                
// Run the default input pipeline
objWSEPipe.ProcessInputMessage(env);

// Extract the resultant Envelope
strIn = env.OuterXml;

// Fake out a response to the message
strResp = BuildFakeResponseFromString(
        "IP", 
        strIn,
        env.Context);        

The emboldened text shows the hydration of a SoapEnvelope object from the inbound message stream. Once we have this envelope, we can create a WSE pipeline and run the default input filters against the envelope as described in the original article.

Once the pipeline process completes, our input buffer is then stuffed with the results of the pipeline run, ready to be processed by the rest of our application code.

Invoking the Outbound Message Pipeline for Responses

This is identical as for the MSMQ and MQ oriented messages, all the code being encapsulated in BuildFakeResponseFromString(). As before, we are supporting 2-way secure messages.

At this point, we now have the ability to send WSE-secured data over an IP link.

Manipulating the Soap Data Content Over a Transport

As the TCP/IP transport was being implemented, a new requirement surfaced around the ability to manipulate the Soap stream into a format suitable for a legacy back-end system. This system expected data sent over an IP link to be in a certain proprietary format (hash-delimited) for the next few months, but the expectation was that the back-end would be upgraded to use Soap over TCP/IP sometime thereafter.

From our perspective then, we need to consider how we can create a short-term client-side approach supporting the generation of messages in our proprietary data format, whilst allowing the use of full-blown Soap messages at a later date and with minimal re-coding effort – ideally in fact just by declarative means – rather than distinct core code changes. This can be achieved with the use of SoapExtensions [2] and the associated SoapExtensionAttribute type.

SoapExtension-derivations allow custom processing to be hooked into the Soap serialization / deserialization process. Previous implementations based on this kind of processing have added support such as custom encryption of a Soap payload, tracing of Soap messages etc.

Using Soap extensions at the client proxy, we can tap the Soap stream just after serialization by hooking into the SoapMessageStage.AfterSerialize processing stage to re-present the Soap formatted stream as a custom stream.

Similarly, we can intercept the returned (proprietary we assume) response from the back-end and reformat back into well-known Soap for the standard de-serialization process to work.

Implementing the New SoapExtension

The extension we are writing is called Soap2ArgosExtension. After overriding the Chain() method to prepare for inserting our own stream in the context chain, we hook the method to access the Soap stream both before it is sent by the client (outbound) and then again before the response is de-serialized by the client (inbound), thus:

public override void ProcessMessage(
    SoapMessage message) 
{
    // Here, we are interested in capturing the stream first after the
    // SOAP call has been serialised into XML ("client-side").
    // Then again within the response before it is deserialized.
    //
    // Hence, for capture we hook:
    //
    //    SoapMessageStage.AfterSerialize (only ever run at the Client)
    //    SoapMessageStage.BeforeDeserialize (only ever run at the Client)
    switch (message.Stage) 
    {
        case SoapMessageStage.BeforeSerialize:
            break;
    
        case SoapMessageStage.AfterSerialize:
            // Transform Soap to Argos API
            TransformSoap2Argos(message);
            break;

        case SoapMessageStage.BeforeDeserialize:
            // Transform Argos to Soap API
            TransformArgos2Soap(message);
            break;

        case SoapMessageStage.AfterDeserialize:
            break;

        default:
            throw new ArgumentException(
                "Invalid SoapMsgCaptureExtension Soap Message stage [" +
                 message.Stage + "]", "message");
    }
}        

The private methods Transform*2*() above merely delegate to a common routine called TransformUsingReader(). Once this has run, the target stream is written with the newly derived stream. It should be appreciated that TransformUsingReader() is a very specialist method which would need to be rewritten for each bespoke back-end implementation. The given example just implements a fictitious scenario where the stream expected by the back-end is a flat, delimited string made up of the method call name and one or more parameter values (see below).

A portion of the method is shown below – this handles the outbound transform to turn Soap into our proprietary message format:

// Here we manipulate the Soap to become a flat message format
bool blnAPICallBuilt = false;
XmlTextReader objReader = new XmlTextReader(newStream);
StringBuilder objFlatAPICall = new StringBuilder(1024);

// Append a nice marker so the other end knows what to expect
objFlatAPICall.Append("@@");

while (objReader.Read())
{
    if (blnAPICallBuilt == false)
    {
        // Pick out the stuff we like, based on the depth.
        // NOTE:
        //    <soap:envelope> is at depth 0
        //    <soap:body> is at depth 1
        //    <METHOD>> is at depth 2
        //    <PARM NAME> is at depth 3
        //    <PARM VALUE> is at depth 4 (or at least hangs off 3)
        //
        switch (objReader.Depth)
        {
            case 2:        // Method Name
                objFlatAPICall.Append(objReader.Name);
                objFlatAPICall.Append("#");
                break;
            case 4:        // Parameter Value
                objFlatAPICall.Append(objReader.Value);
                blnAPICallBuilt = true;
                break;
            default:
                break;
        }
    }
}

// Extract resultant flat thang
byte[] bytNewFormat = new UTF8Encoding().GetBytes(objFlatAPICall.ToString());
                    
// to return it
newStream = new MemoryStream();
newStream.Write(bytNewFormat, 0, bytNewFormat.Length);        

In the above case, we use the XmlTextReader to read through the XML content and an instance of the StringBuilder class to construct proprietary messages that look like:

@@<method name>#<parmvalue1>#<parmvalue2> ...

It is messages of this format we actually ship on the wire to the back-end.

Exposing the New SoapExtension to the Generated Proxy

Now we have a SoapExtension to express the transformation of data, we need to find a way to wire this into the client proxy. This is achieved using declarative attributes – specifically in code by using a class derived from SoapExtensionAttribute. The class definition indicates that the class derives from the base SoapExtensionAttribute and can be targeted only at methods (i.e., the attribute makes no sense if declared at the individual parameter level, class level, etc.). The declaration looks like:

[AttributeUsage(AttributeTargets.Method)]
public class Soap2ArgosExtensionAttribute : SoapExtensionAttribute 
{
    ...        

Implementing this class allows an attribute to be declared against the relevant methods as shown:

[System.Web.Services.Protocols.SoapDocumentMethodAttribute(...)]
public string DoesTrinketExistInMySize(int viSize) {
   object[] results = this.Invoke("DoesTrinketExistInMySize",
                                   new object[] {viSize});
   return ((string)(results[0]));
}
        
[System.Web.Services.Protocols.SoapDocumentMethodAttribute(...)]
[Soap2ArgosExtension]
public string DoesTrinketExistInMySize2(int viSize) {
   object[] results = this.Invoke("DoesTrinketExistInMySize2", 
                                   new object[] {viSize});
   return ((string)(results[0]));
}        

The first proxy method declaration shows a standard invocation. The second decorates the method on the proxy class with an attribute, which hooks in our extra processing at run time.

The beauty of this approach is that, when the back-end becomes capable of supporting real Soap messages, the only change required is to remove the attribute from the method declaration above.

Updating the Back-end Service to Support the Proprietary Format

There is a minor modification to the back-end service, which involves the detection of the "@@" leading characters indicating our specialist format. The logic to listen for IP runs this code on receipt of data:

// Do we have some special processing for bespoke messages - prefixed with "@@"
if (strIn.IndexOf("@@") == 0)
{
   strResp = BuildFakeArgosResponseFromString(
                "IP", 
                strIn);
}
else
{
   // Normal Soap processing
   ...
} 

The code used to build the response is very basic (remember that we are not interested in the server-side implementation – as long as it delivers data back to the client side framework...):

// If we can find our API method call marker then return a stock response
if (vstrIn.IndexOf("DoesTrinketExistInMySize2") >= 0)
{
   string strSize = "UNKNOWN";

   if (vstrIn.IndexOf("#") >= 0)
      strSize = vstrIn.Substring(vstrIn.IndexOf("#")+1, 1);

   strResp = "@@DoesTrinketExistInMySize2#Leatherhead, " + 
        strSize + 
        " High St." + 
        vstrScheme + 
        "-FlatMsg";
}
else
{
   TSLog("  *** (BuildFakeResponseFromBytes) Spotted inbound " + vstrScheme + 
         " [UNKNOWN in message " + vstrIn + "] message");
} 

Finally, strResp is returned to the caller via TCP/IP:

// If we have a valid response...use it
if (strResp.Length >  0)
{
   // Write the message back, using the network stream...
   byte[] sendBytes = Encoding.ASCII.GetBytes(strResp);
   networkStream.Write(sendBytes, 0, sendBytes.Length);
}  

Using the New Proxy From a Client

Now the hard work is out of the way, clients of the proxy can make use of it in the following way – in fact, close inspection of the updated client calling code will show there are no changes from the original article:

localhost.Service1 objHelloWorldService = null;
string strRequestUrl = "ip://100.100.100.217:42000";

try
{
    // Create the Service proxy
    objHelloWorldService = new localhost.Service1();

    // Set up the request Uri via the Uri
    objHelloWorldService.Url
                     = objHelloWorldService.FormatCustomUri(strRequestUrl);

    // And the timeout
    objHelloWorldService.Timeout = vintTimeoutInSecs * 1000;

    // Run the method and print out the return
    Console.WriteLine(objHelloWorldService.HelloWorld("Simon"));
}
catch(Exception e)
{
    ...
}  

It should be noted that this has only been tested with non-WSE’d messages over MSMQ, MQ, HTTP and TCP/IP transports.

Framework Packaging

The classes we have been describing in the article that make up the framework are packaged into an assembly called WSIPTransports.dll:

WSIPTransports packaging

The leftmost types make up the support for extra facilities such as the ResponseUrl. The other types make up our implementation of the socket-based pluggable protocols. The assembly is signed which makes it eligible for the GAC. It is named in order to be a companion to the original framework DLL, WSQTransports.dll.

The Sample Application

The demo application has been extended to cover the new IP transport.

Due to the nature of the work being focused far more on a technology than a real world application of it, the demo is basic in that it shows the interaction of 2 console applications – Client and Server – exchanging messages using the framework we’ve developed in the last article and this one.

The demo shows both passing "clear" and "WSE-secured" Soap messages based on various standard data type and DIME API combinations. While the demo is basic, it does illustrate the various concepts fairly well.

The demo running two clients and one back-end MQ / MSMQ / IP service

The demo shows the Back End service processing incoming Soap messages on TCP/IP sockets, on MSMQ and MQ queues and in both cases returning responses. The handling of non-trivial data types (real serialized custom objects) is also included in the demo. The screen shot actually shows the processing of complex types based on an arbitrary class called "Product". It shows arrays of these "Products" being serialized and passed between Server and Client. In the screenshot above, two clients can be seen running Soap calls over queues and via HTTP.

In order to assess the level of concurrency possible, start up one instance of the back end service, followed by as many instances of the test client as you would like to test. All instances of the client should complete their test runs without any exceptions being thrown.

Application Introduction

The following diagram introduces the discrete portions of the sample application and depicts their fit within the client server model:

Article deliverables

The two new artifacts are:

  • the framework code that implements the pluggable IP protocols lives within a single Assembly (WSIPTransports.dll) so that it can be used within multiple projects,
  • the custom SoapExtension responsible for transforming data between Soap and some arbitrary proprietary format (ArgosAPIExtensions.dll)

The four changed artifacts are:

  • Each of the configuration files (the ones shipped with WSAltRouteBEFake.exe and WSAltRoute.exe) are updated to support TCP/IP – as shown in the section below
  • The framework is driven off an enhanced sample console application (WSAltRouteTest.exe), which tests various flavors of API over various transports including the new TCP/IP variant. This new version of the console driver application also exercises the Soap-proprietary conversion functionality.
  • An updated back-end process includes the TCP/IP listening code as well as proprietary data format support (WSAltRouteBEFake.exe)

Application Setup

[The new steps for IP support are highlighted] The zip file contains several projects described in the section below. The key actions for deployment are:

  • Ensure the prerequisites you require (especially MQ and WSE) are installed.
  • Unzip the file preserving folder structure.
  • Locate the \Bin folder.
  • Run the WSQSetup.exe executable to create the relevant queues. Follow the on-screen prompts.
  • Locate the \Client subfolder.
  • Edit the WSAltRouteTest.exe.config file, review and make any changes. The list of entries is:
    Entry Description
    NoTrace If true, does not dump the stack trace when an exception occurs
    EnableHTTPCalls If true, make the API calls over HTTP transport
    EnableMSMQCalls If true, make the API calls over MSMQ transport
    EnableMQCalls If true, make the API calls over MQ transport
    EnableIPCalls If true, make the API calls over IP transport
    HTTPUriNonWSE Endpoint of the WSAltRoute Web Service
    HTTPUriWSE Endpoint of the WSAltRouteWSE Web Service
    MSMQRequestQ Endpoint of the MSMQ request queue
    MSMQResponseQ Endpoint of the MSMQ response queue
    MQRequestQ Endpoint of the MQ request queue
    MQResponseQ Endpoint of the MQ response queue
    IPUri Endpoint of the IP remote service
    LocationOfImages Path for reading and writing images

    For example, if you do not want to run over HTTP, set EnableHTTPCalls value to false.

  • Re-locate the \Bin folder
  • Locate the \Server subfolder
  • Edit the WSAltRouteBEFake.exe.config file, review and make any changes. The list of entries is:
    Entry Description
    NoTrace If true, does not dump the stack trace when an exception occurs
    QueueToMonitor1 Name of the 1st Queue to monitor for Soap messages
    QueueToMonitor2 Name of the 2nd Queue to monitor for Soap messages
    QueueToMonitorX Name of the Xth Queue to monitor for Soap messages
    PortToListenOn Port for service to bind to when awaiting requests
    PollDelayTime Receive delay in ms
    LocationOfImages Path for reading and writing images

    For example, if you want to add a queue to monitor, add QueueToMonitor3 and set the associated value to the Uri name of the queue.

  • If wanting to explore the HTTP transport:
    • Locate the \WebServices folder
    • Copy the two subfolders (WSAltRoute and WSAltRouteWSE) to your main web site folder (typically c:\inetpub\wwwroot).
    • For the first folder, create a new IIS Virtual Directory called WSAltRoute off the default web site (port 80), pointing to the WSAltRoute target folder.
    • For the second folder, create a new IIS Virtual Directory called WSAltRouteWSE off the default web site (port 80), pointing to the WSAltRouteWSE target folder.
  • If wanting to explore the MSMQ transport:
    • Check that the MSMQ request and response queues defined in the WSAltRouteTest.exe.config of the client application exist. Use the Computer Management feature of Windows 2000 or Windows XP to check this.
  • If wanting to explore the MQ transport:
    • Check that the MQ request and response queues defined in the WSAltRouteTest.exe.config of the client application exist. Use the Websphere MQ Explorer tool to check this.
  • If wanting to explore the IP transport:
    • Check you have no personal firewalls running that could block IP requests on certain address / port combinations.
  • Re-locate the \Bin folder
  • Locate the \Images subfolder
  • Copy the two .gif files, CD.gif and Help.gif to the folder specified in both the WSAltRouteTest.exe.config of the client and WSAltRouteBEFake.exe.config of the server under the LocationOfImages key name.
  • To run the demo, start up the back end process WSAltRouteBEFake.exe first, observing the startup message appears ok. Then start up the client process WSAltRouteTest.exe. Activity should then be displayed in both consoles over the next few moments, as various Web Service API are exercised across the chosen transports. No exceptions should be seen.
  • If exceptions are seen, only the message portions of the exception may be shown. If this appears to be the case, stack trace can be turned on by altering the value for NoTrace in the relevant (typically client) application configuration file to false.

Application Build

The solution is made up of the following projects:

Name of project New / Updated Purpose
WSIPTransports New The code supporting the pluggable transport framework and the TCP/IP transport implementations.
ArgosAPIExtensions New SoapExtension indicating that the Soap should be transformed to a proprietary form.
WSAltRouteTest Updated Updated console driver which makes use of IP configuration defined in updated configuration file.
WSAltRouteBEFake Updated Updated back-end service supporting IP listening and proprietary data format detection and response.

NOTE: Updated refers to an extension to the same project from the original article.

Case Study Review

Here’s a recap of both the fundamentals underpinning the original case study and the major aspects of this particular follow-on article:

  • ASP.NET Web Services includes an infrastructure specifically designed to allow support of alternative transport protocols based on System.Net.WebRequest and System.Net.WebResponse.
  • In order to support new protocols, a scheme registration mechanism is provided as part of the infrastructure. Once new "network schemes" are registered, a client may make use of them when specifying an endpoint.
  • Like MSMQ and Websphere MQ, TCP/IP is easily capable of being wrapped with a transport protocol, and it is straightforward for the client to specify an end point based on a scheme. This has the effect of minimizing the code changes required for a client in order to make use of a new transport.
  • The WSE can easily secure data over the TCP/IP transport – or indeed any other transport.
  • As well as varying the transport over which data is sent, it is trivial to manipulate the actual data sent over any of the supported transports using a Soap extension and associated declarative attribute.

Further Work

Constructing this section is STILL like shooting fish in a barrel! A selection of things to consider include:

  • There are places in the code where strings are concatenated rather than built using StringBuilder. Because I use this 'technique', does not mean I endorse it!
  • A lot more work should be done to secure the various aspects of the solution. Two immediate ones springing to mind:
    • The use of StrongNameIdentityPermissionAttribute to explicitly control who calls sensitive assemblies (only callers signed with a particular strong name key)
    • The encryption keys should be stored outside the code, either in a (well secured) file or in the secure area of the Registry
  • The demo has been built around a very RPC-style of message protocol, where responses to requests are expected. Investigation of 1-way Soap messages would be interesting - this most closely maps to a Queue style send operation without the need for a corresponding receive and would be a useful addition to the library.
  • This framework has been used to interoperate with a Java back end using the Axis Toolkit 1.1 RC1 under J2SE 1.4.1. Unfortunately, throwing Java into the mix here would probably have resulted in double the copy! Depending on reaction to this article, a follow-up concentrating more on this interoperability aspect is possible.
  • It would be interesting to add a compression capability like the SmartLib compression library [9] into the DIME section for large attachments.
  • Other transports could be implemented within the framework – SMTP, FTP, SQL2000, etc. are good candidates. As an example, I've implemented TCP/IP recently within this framework which proved to be a trivial process.
  • As a separate note, thanks to Scott for pointing out that IBM now ships the Kolban-written managed classes for Websphere MQ [4].

Related Links

History

  • 31st May, 2003: Initial version

License

This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.

A list of licenses authors might use can be found here.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here