VS's Cassini (Web Server) + Fiddler = Remote testing

From time to time, I have a need to debug a web site from a browser not installed on my current machine.  Safari/Mac or phone browsers are the quintessential example.  Granted, if it happens server-side, it happens for all browsers, and if it happens client-side, debugging server-side may not help.  But there's that one random time every now and again when this becomes quite important: debug server, client isn't on localhost.  Perhaps it's easier to replay the request from the browser where the problem happened rather than figure out how to simulate it locally.  Perhaps it's a weird combination of race conditions that make it happen.  Perhaps it's how the cookies built up that led to cruft not easily reproducible elsewhere.  Alas, being able to debug the server from a non-local client comes in mighty handy now-and-again.

As we all know, Visual Studio's built in web server (originally based on Cassini) is hard-coded to only bind to loopback.  If I had my way that wouldn't be the case, but here we are.  Traditionally, we'd need to install our site in IIS, attach to the w3svc service, and debug that way.  (Even if you're not on a 64-bit box, you can empathize with how intense that is.)

Fiddler is an awesome tool for watching network traffic flow between browser and web server.  In the end, everything you send between server and browser is either HTML, CSS, or JavaScript (or binary content like images and plug-ins), so watching it happen is incredibly insiteful.  We'll not use Fiddler for that here, but I highly recommend it for that work as well.  (There's times when watching the traffic yields insights quite difficult to see any other way.)  Alas, today we'll just mis-use it as a proxy server that can forward traffic back and forth from a LAN address to a loopback address.

Here's the steps:

1. Download and install Fiddler from www.fiddlertool.com on the same machine as Visual Studio.

2. Tools menu -> Fiddler Options -> Connections tab: click on "Allow remote computers to connect", and push ok.  (While I'm in here, I usually turn off IPv6 in the General tab as that's another no-no in VS's version of Cassini.)

3.  Rules menu -> Customize Rules.  For me, it opened up CustomRules.js in notepad.

4. Scroll down to the function labeled OnBeforeRequest and add a line kinda like this:

if (oSession.host.toLowerCase() == "192.168.1.5:8888") oSession.host = "localhost:45678";

Replace the first IP with the address of your machine (ipconfig /all from a cmd will give you this), and replace the port at the end with what ever you set / VS assigned to your project.  (Inside Visual Studio, in the project properties window, in the web tab, I always set it to "Specific Port" so I don't have to come in here and futz with it every time VS wants to "help".)

This line says "if the inbound request says it's going to this ip and port (that fiddler is listening to), reroute it to this other ip and port (that Visual Studio's web debugger is listening to).

5. Scroll down a bit further in CustomRules.js to the OnBeforeResponse function and add a line kinda like this:

oSession.utilReplaceInResponse("localhost:45678","192.168.1.5:8888");

You'll also need to change the IP and port here to match the changes above as well, but note they're reversed.

This line says "if the body of the outbound message includes text that routes to Visual Studio's debugger, rewrite it to point to Fiddler."

6. Save this file, restart Fiddler, and try it out.

(You may also have to adjust firewall rules on your development machine to allow inbound TCP connections on port 8888.)

And that's it!  Now you're remote debugging your project from a non-local address.

One final note: ideally, we'd create another rule in OnBeforeResponse that would rewrite headers as well.  I haven't been very successful in doing that, so I haven't included all the ways that don't work.  But if you're sending back a redirect to a full url without this rule, your client will happily time out looking for content on itself.  Why itself?  Because you redirected to localhost.  :D

Rob

Logging and Rewriting Service Calls

I love web services.  I love how easy it is to wire up a JavaScript client either through jQuery or through a ServiceProxy.  I love how I can point a .net client at a service, and I get strongly typed command parameters.  And my latest love of asmx services in C# is the SoapExtension.  A SoapExtension is a class that allows you to inspect outgoing or incoming services to get at the data involved.  I use them for logging service calls (frequency, duration, errors), and for rewriting output (fixed a "double html encoded" bug, remove whitespace, rewrite SoapFaults, etc).

The technique is incredibly awesome, and I can't claim original ownership, but I can claim excellent results.  A few simple steps:

1. Create a class that derives from SoapExtension.

2. Override ProcessMessage, handling the SoapMessage's Stage that you're interested in.

3. Add either a web.config entry (to handle all inbound and outbound service calls), or put an attribute on each WebMethod you want to track.

Voila!  You've got a soap extension.

Here's my ExampleSoapExtension (business logic replaced with TODO to keep it simple):

namespace ExampleNamespace {

    #region using
    using System;
    using System.Data.Linq;
    using System.IO;
    using System.Linq;
    using System.Text.RegularExpressions;
    using System.Threading;
    using System.Web;
    using System.Web.Services.Protocols;
    #endregion

    public class ExampleSoapExtension : SoapExtension {

        private Stream originalStream; // The original stream
        private Stream internalStream; // Our new stream we chained into place

        public ExampleSoapExtension() {
        }

        // Called the first time the Web service is used if configured with a config file
        public override object GetInitializer( Type t ) {
            return typeof( ExampleSoapExtension );
        }

        // Called the first time the Web service is used if configured with an attribute
        public override object GetInitializer( LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute ) {
            return attribute;
        }

        // Called each time the Web service is used and gets passed the data from GetInitializer() method
        public override void Initialize( object initializer ) {
            // Get the attribute here and pull whatever you need off it.
            //ExampleSoapExtensionAttribute attr = initializer as ExampleSoapExtensionAttribute;
        }

        // The ChainStream() method gives us a chance to grab the SOAP messages as they go by
        public override Stream ChainStream( Stream stream ) {
            // Save the original stream
            originalStream = stream;
            // Create and return our own in its place
            internalStream = new MemoryStream();

            return internalStream;
        }

        // The ProcessMessage() method is called between each step in the server-side process
        public override void ProcessMessage( SoapMessage message ) {

            switch ( message.Stage ) {

                case SoapMessageStage.BeforeDeserialize:
                    // About to handle incoming SOAP request

                    string requestContent = StreamToText( this.originalStream, false );
                    WriteIncomingToLog( message, requestContent );
                    TextToStream( requestContent, this.internalStream, true );
                    break;

                case SoapMessageStage.AfterDeserialize:
                    // incoming SOAP request has been deserialized

                    WriteInputToLog( message );
                    break;

                case SoapMessageStage.BeforeSerialize:
                    // About to prepare outgoing SOAP Response

                    WriteOutputToLog( message );
                    break;

                case SoapMessageStage.AfterSerialize:
                    // outgoing SOAP response is ready to go

                    string responseContent = null;

                    if ( message.Exception != null ) {
                        // Rewrite soap fault as valid soap message with internal error code
                        // Though it might be nice to do this before serialization, we can't inject our own return value easily
                        responseContent = RewriteError( message );
                    } else {
                        responseContent = StreamToText( this.internalStream, true );
                    }

                    WriteOutgoingToLog( message, responseContent );
                    TextToStream( responseContent, this.originalStream, false );
                    break;

                default:
                    throw new Exception( "invalid stage" );
            }
        }

        private static string StreamToText( Stream stream, bool rewindBefore ) {
            if ( rewindBefore ) {
                stream.Position = 0;
            }
            TextReader reader = new StreamReader( stream );
            string content = reader.ReadToEnd();
            return content;
        }

        private static void TextToStream( String text, Stream stream, bool rewindAfter ) {
            TextWriter writer = new StreamWriter( stream );
            writer.Write( text );
            writer.Flush();
            if ( rewindAfter ) {
                stream.Position = 0;
            }
        }

        #region WriteIncomingToLog
        /// <summary>
        /// Write the incoming stream content to the logs
        /// </summary>
        private void WriteIncomingToLog( SoapMessage Message, string Content ) {

            HttpRequest req = HttpContext.Current.Request;

            // TODO: Log various parameters here including the incoming xml and start date

        }
        #endregion

        #region WriteInputToLog
        private void WriteInputToLog( SoapMessage Message ) {

            ServiceInputObject input = null;
            if ( Message.MethodInfo.InParameters.Length > 0 ) {
                try {
                    input = Message.GetInParameterValue( 0 ) as ServiceInputObject;
                } catch ( Exception /*ex*/ ) {
                    input = null;
                }
            }
            if ( input != null ) {
           
                // TODO: Log the message details
               
            }

        }
        #endregion

        #region WriteOutputToLog
        private void WriteOutputToLog( SoapMessage Message ) {

            if ( Message.Exception != null ) {
                // The return object doesn't exist because an exception was thrown
                // RewriteError will create one
                return;
            }

            ServiceOutputObject output = null;
            if ( Message.MethodInfo != null && Message.MethodInfo.ReturnType != null && Message.MethodInfo.ReturnType.FullName != "System.Void" ) {
                output = Message.GetReturnValue() as ServiceOutputObject;
            } else {
                output = null;
            }
            if ( output != null ) {
           
                // TODO: Log the output details
               
            }

        }
        #endregion

        #region WriteOutgoingToLog
        /// <summary>
        /// Write the message in the stream to the log4net log
        /// </summary>
        private void WriteOutgoingToLog( SoapMessage Message, string Content ) {

            string outboundData = GetStreamText( StreamToLog );

            // TODO: Log the outgoing xml string and end date

        }
        #endregion

        #region CaptureError
        /// <summary>
        /// Insure the exception is logged
        /// </summary>
        private Stream CaptureError( SoapMessage Message, Stream stream ) {

            // Log the exception
            Exception ex = Message.Exception;
            while ( ex != null && ex.InnerException != null && ex.GetType() == typeof( SoapException ) ) {
                ex = ex.InnerException;
            }
           
            // TODO: Log the exception
           
            // TODO: Form the output reply and serialize it
            string soapMessage = null;

            // Now send this as the out stream instead of the <soap:Fault> stream we had before
            MemoryStream outStream = new MemoryStream();
            TextWriter writer = new StreamWriter( outStream );
            writer.WriteLine( soapMessage );
            writer.Flush();
            outStream.Position = 0;

            // And send the 200 status instead of 400
            //HttpContext.Current.Response.StatusCode = 200;

            // Here's the content to send instead of what ever you had
            return outStream;
        }
        #endregion

    }

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

        public ExampleSoapExtensionAttribute() {
            this.Priority = 1;
        }

        public override int Priority { get; set; }

        // Specifies the class of the SOAP Extension to use with this method
        public override Type ExtensionType {
            get { return typeof( ExampleSoapExtension ); }
        }

    }

}

My current quest: how do I do this in WCF?  Is there such a thing as a "message watcher" or "handler" or "pipeline interceptor"?  All these terms on Google come up blank.  With WCF's new configuration-less services and MS's desire to move on past asmx, WCF seems a foregone conclusion.  For me, this is the last hurdle.

Rob