using System;
using System.IO;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Diagnostics;
using System.Messaging;
using System.Web.Services.Protocols;
namespace WSAltTransports
{
/// <summary>WSAltTransports.MSMQRequestCreator</summary>
/// <author>SGregory</author>
/// <date>02 January 2003</date>
/// <remarks>Creation Factory for MSMQ-style WebRequests</remarks>
public sealed class MSMQRequestCreator : IWebRequestCreate
{
/// <summary>WSAltTransports.MSMQRequestCreator.Create</summary>
/// <author>SGregory</author>
/// <date>02 January 2003</date>
/// <remarks>Creates an MSMQWebRequest object</remarks>
/// <param name="Uri" type="System.Uri">URI indicating endpoint</param>
/// <returns type="System.Net.WebRequest">MSMQWebRequest object</returns>
public WebRequest Create(
Uri Uri)
{
// Create a new request
return new MSMQWebRequest(
Uri,
m_ResponseUri);
}
/// <summary>WSAltTransports.MSMQRequestCreator.ResponseUri</summary>
/// <value>Response Url for the request</value>
public Uri ResponseUri
{
get
{
return m_ResponseUri;
}
set
{
m_ResponseUri = value;
}
}
// Private member data
private Uri m_ResponseUri = null;
}
/// <summary>WSAltTransports.MSMQWebRequest</summary>
/// <author>SGregory</author>
/// <date>16 January 2003</date>
/// <remarks>This class handles the specific implementation of the MSMQ transport</remarks>
public sealed class MSMQWebRequest : MyBaseWebRequest
{
/// <summary>WSAltTransports.MSMQWebRequest</summary>
/// <author>SGregory</author>
/// <date>02 January 2003</date>
/// <remarks>This is the ; point for all "MSMQ://" requests</remarks>
public MSMQWebRequest(
Uri Url,
Uri ResponseUrl)
{
// Check the scheme for the Uri is as expected
if(Url.Scheme.ToLower() != "msmq") // This class is only for msmq urls
throw new NotSupportedException("This protocol [" + Url.Scheme + "] is not supported");
// Set data members
m_RequestUri = Url;
m_szMethod = string.Empty; // No default for this class
this.ResponseUri = ResponseUrl;
}
// Force MSMQ to derive a default response Queue name from the supplied request name
// Simply add the _resp to the AbsoluteUri to create a new Uri
protected override void SetResponseUriFromRequestUri(Uri requestUri)
{
this.ResponseUri = new Uri(requestUri.AbsoluteUri + "_resp");
}
/// <summary>WSAltTransports.MSMQWebRequest.GetResponse</summary>
/// <author>SGregory</author>
/// <date>16 January 2003</date>
/// <remarks>
/// Does the work of placing the serialised Soap message on the named Queue
/// and waiting for a response on the Response Queue.
/// NOTES:
/// 1) The URI for the MSMQ Queue names look like:
/// msmq://./private$/Q1
/// This is simply because the Uri processor does not seem to like the format:
/// msmq:.\private$\Q1
/// Hence, the URI needs to be processed slightly to reverse slashes etc.
/// If anyone finds a nice way to get round this please let me know.
/// 2) The default Response Queue to wait on is "<queuename>_resp"
/// e.g. if an outbound Queue is called ".\private$\Q1" the response Queue will
/// be named ".\private$\Q1_Resp" by default unless it is changed by calling the
/// public property <code>ResponseUri</code> on this class.
/// </remarks>
/// <returns type="System.Net.WebResponse">Our own special brand of WebResponse</returns>
public override WebResponse GetResponse()
{
MSMQWebResponse objMSMQWebResponse = null;
System.Messaging.Message msg = null;
MessageQueue objQueue = null;
try
{
// Figure out the return URI, or generate one on the fly
if (this.ResponseUri == null)
SetResponseUriFromRequestUri(m_RequestUri);
// We need to interpret the MSMQ URI a little
string strQueueName = this.m_RequestUri.LocalPath.Replace("/", "\\");
if (strQueueName.ToUpper().IndexOf("PRIVATE$") >= 0)
strQueueName = "." + strQueueName;
// We need to interpret the response MSMQ URI a little too!
string strQueueNameResp = this.ResponseUri.LocalPath.Replace("/", "\\");
if (strQueueNameResp.ToUpper().IndexOf("PRIVATE$") >= 0)
strQueueNameResp = "." + strQueueNameResp;
// Create an MSMQ message. It requires:
//
// * a label
// * a body
//
// We shall make it recoverable
System.Messaging.Message objMsg = new System.Messaging.Message();
objMsg.BodyStream = m_RequestStream;
objMsg.Recoverable = true;
objMsg.ResponseQueue = new MessageQueue(strQueueNameResp);
// Open the Queue for writing
objQueue = GetQ(strQueueName, true);
try
{
// Send the message under a single MSMQ internal transaction
objQueue.Send(objMsg, MessageQueueTransactionType.Single);
}
catch(Exception e)
{
// An exception occured when sending - this can be down to the
// response queue (!) that was supplied in the objMsg.ResponseQueue
// slot not existing
if (e.Message.IndexOf("Queue is not registered in the DS.") >= 0)
{
throw new WebException(
"Failed to locate MSMQ response queue '" + strQueueNameResp + "' when trying to send request.",
e,
WebExceptionStatus.NameResolutionFailure,
null);
}
else
{
// Throw the exception as a geenral send failure
throw new WebException(
"Failed to send the message to the MSMQ request queue.",
e,
WebExceptionStatus.SendFailure,
null);
}
}
finally
{
// Free resources on main Queue
objQueue.Close();
// And close serialised Soap Stream....
m_RequestStream.InternalClose();
}
// Get the ID of the outbound message to use as the Correlation ID
// to look for on any inbound messages
string strCorrelationID = objMsg.Id;
// Either open or create a private MSMQ Queue on the local machine
try
{
TimeSpan tWaitResp = new TimeSpan(0, 0, m_intTimeout / 1000);
objQueue = GetQ(strQueueNameResp, false);
// The receive must be done in a loop since, by observation it has
// been seen that other threads may manipulate the MSMQ read cursor
// underneath us (as we can't sharedenyall).
// So we need to effectively retry the receive, knowing that no-one else
// will be after OUR message as no-one else should have our Correlation ID.
bool blnRetryReceive = true;
while (blnRetryReceive == true)
{
try
{
// Wait for a message with our moniker one it
msg = objQueue.ReceiveByCorrelationId(strCorrelationID, tWaitResp, MessageQueueTransactionType.Single);
// If we've found it we are done
blnRetryReceive = false;
}
catch(Exception e)
{
// If this was an error other than the cursor error we
// are expecting then throw it to our next handler
if (e.Message.IndexOf("Message that the cursor is currently pointing to has been removed from the queue") < 0)
throw e;
}
}
}
catch(Exception e)
{
// This handler handles exceptions from the receive process that are non-retryable
// These include timeouts waiting on the message, and all other exceptions.
if (e.Message.IndexOf("Timeout for the requested operation has expired") >= 0)
{
// Throw the exception as a Timeout
throw new WebException(
"Timeout waiting on MSMQ resource.",
e,
WebExceptionStatus.Timeout,
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 MSMQ response queue.",
e,
WebExceptionStatus.ReceiveFailure,
null);
}
}
finally
{
// Free resources on main Queue
objQueue.Close();
}
// If we get here all is well.
// Create a return, and stream results into it...
objMSMQWebResponse = new MSMQWebResponse();
objMSMQWebResponse.SetDownloadStream(msg.BodyStream);
}
catch(Exception e)
{
// All exceptions caught here that are not of the WebException variety
// get cast as ProtocolErrors - these are exceptions that we do not have
// a mapping to a standard WebException status type.
// Close serialised Soap Stream....if it needs closing
m_RequestStream.InternalClose();
// Have we already processed this exception in some way?
if ((e is System.Net.WebException) == false)
{
// No - Create a MSMQWebResponse - this is a protocol specific error
objMSMQWebResponse = new MSMQWebResponse(
(int)QueueTransportErrors.UnexpectedFailure,
e.Message,
e.StackTrace);
// And throw the exception
throw new WebException(
"MSMQ Pluggable Protocol failure.",
e,
WebExceptionStatus.ProtocolError,
objMSMQWebResponse);
}
else
{
// It's prepared already from a lower level, so just
// throw it on up the chain
throw e;
}
}
// If we get here we are in business, so return a repsonse
return objMSMQWebResponse;
}
/// <summary>WSAltTransports.MSMQWebRequest.GetQ</summary>
/// <author>SGregory</author>
/// <date>16 January 2003</date>
/// <remarks>Attempts to open a Queue with the passed name</remarks>
/// <param name="vstrQueueName" type="string">Name of Queue to open</param>
/// <param name="vblnIsRequestQueue" type="System.bool">true if this is a request queue</param>
/// <returns type="System.Messaging.MessageQueue">Type instance representing a Queue</returns>
private MessageQueue GetQ(
string vstrQueueName,
bool vblnIsRequestQueue)
{
MessageQueue msgQ = null;
// Does the Queue exist?
if (MessageQueue.Exists(vstrQueueName) == false)
{
string strTypeOfQueue = (vblnIsRequestQueue == true) ? "request" : "response";
string strTypeOfOperation = (vblnIsRequestQueue == true) ? "send request" : "read response";
// No - report this
throw new WebException(
"Failed to locate MSMQ " + strTypeOfQueue + " queue '" + vstrQueueName + "' when trying to " + strTypeOfOperation + ".",
WebExceptionStatus.NameResolutionFailure);
}
else
{
try
{
// The Queue exists already. So just open it
msgQ = new MessageQueue(vstrQueueName);
}
catch(Exception e)
{
string strTypeOfQueue = (vblnIsRequestQueue == true) ? "request" : "response";
string strTypeOfOperation = (vblnIsRequestQueue == true) ? "send request" : "read response";
throw new WebException(
"Failed to open MSMQ " + strTypeOfQueue + " queue '" + vstrQueueName + "' when trying to " + strTypeOfOperation + ".",
e,
WebExceptionStatus.ConnectFailure,
null);
}
}
// Either way, return the Queue object to the caller
return msgQ;
}
}
/// <summary>WSAltTransports.MSMQWebResponse</summary>
/// <author>SGregory</author>
/// <date>16 January 2003</date>
/// <remarks>
/// The MSMQ Response - we totally derive from MyBaseWebResponse here
/// </remarks>
public sealed class MSMQWebResponse : MyBaseWebResponse
{
/// <summary>WSAltTransports.MSMQWebResponse.MSMQWebResponse</summary>
/// <author>Simon G</author>
/// <date>24 February 2003</date>
/// <remarks>Default constructor - used simply to set up the base class member data</remarks>
internal MSMQWebResponse()
{
}
/// <summary>WSAltTransports.MSMQWebResponse.MSMQWebResponse</summary>
/// <author>Simon G</author>
/// <date>24 February 2003</date>
/// <remarks>Error information constructor</remarks>
/// <param name="vintStatusCode" type="int">WebResponse Code e.g. 500</param>
/// <param name="vstrStatusDescription" type="string">WebResponse Error description</param>
/// <param name="vstrLog" type="string">Associated stack trace or similar</param>
/// <postconditions>Error information set up</postconditions>
internal MSMQWebResponse(
int vintStatusCode,
string vstrStatusDescription,
string vstrLog)
{
// Delegate to the base class for the setting up of the
// error information. This is explicitly shown here through
// the use of "base."
base.SetErrorInformation(
vintStatusCode,
vstrStatusDescription,
vstrLog);
}
}
}