Getting RAW Soap Data from a Web Reference Client running in ASP.net

Viewed 126667

I'm trying to trouble shoot a web service client in my current project. I'm not sure of the platform of the Service Server (Most likely LAMP). I believe there is a fault on their side of the fence as i have eliminated the potential issues with my client. The client is a standard ASMX type web reference proxy auto generated from the service WSDL.

What I need to get to is the RAW SOAP Messages (Request and Responses)

What is the best way to go about this?

10 Answers

I made following changes in web.config to get the SOAP (Request/Response) Envelope. This will output all of the raw SOAP information to the file trace.log.

<system.diagnostics>
  <trace autoflush="true"/>
  <sources>
    <source name="System.Net" maxdatasize="1024">
      <listeners>
        <add name="TraceFile"/>
      </listeners>
    </source>
    <source name="System.Net.Sockets" maxdatasize="1024">
      <listeners>
        <add name="TraceFile"/>
      </listeners>
    </source>
  </sources>
  <sharedListeners>
    <add name="TraceFile" type="System.Diagnostics.TextWriterTraceListener"
      initializeData="trace.log"/>
  </sharedListeners>
  <switches>
    <add name="System.Net" value="Verbose"/>
    <add name="System.Net.Sockets" value="Verbose"/>
  </switches>
</system.diagnostics>

You can implement a SoapExtension that logs the full request and response to a log file. You can then enable the SoapExtension in the web.config, which makes it easy to turn on/off for debugging purposes. Here is an example that I have found and modified for my own use, in my case the logging was done by log4net but you can replace the log methods with your own.

public class SoapLoggerExtension : SoapExtension
{
    private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
    private Stream oldStream;
    private Stream newStream;

    public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
    {
        return null;
    }

    public override object GetInitializer(Type serviceType)
    {
        return null;
    }

    public override void Initialize(object initializer)
    {

    }

    public override System.IO.Stream ChainStream(System.IO.Stream stream)
    {
        oldStream = stream;
        newStream = new MemoryStream();
        return newStream;
    }

    public override void ProcessMessage(SoapMessage message)
    {

        switch (message.Stage)
        {
            case SoapMessageStage.BeforeSerialize:
                break;
            case SoapMessageStage.AfterSerialize:
                Log(message, "AfterSerialize");
                    CopyStream(newStream, oldStream);
                    newStream.Position = 0;
                break;
                case SoapMessageStage.BeforeDeserialize:
                    CopyStream(oldStream, newStream);
                    Log(message, "BeforeDeserialize");
                break;
            case SoapMessageStage.AfterDeserialize:
                break;
        }
    }

    public void Log(SoapMessage message, string stage)
    {

        newStream.Position = 0;
        string contents = (message is SoapServerMessage) ? "SoapRequest " : "SoapResponse ";
        contents += stage + ";";

        StreamReader reader = new StreamReader(newStream);

        contents += reader.ReadToEnd();

        newStream.Position = 0;

        log.Debug(contents);
    }

    void ReturnStream()
    {
        CopyAndReverse(newStream, oldStream);
    }

    void ReceiveStream()
    {
        CopyAndReverse(newStream, oldStream);
    }

    public void ReverseIncomingStream()
    {
        ReverseStream(newStream);
    }

    public void ReverseOutgoingStream()
    {
        ReverseStream(newStream);
    }

    public void ReverseStream(Stream stream)
    {
        TextReader tr = new StreamReader(stream);
        string str = tr.ReadToEnd();
        char[] data = str.ToCharArray();
        Array.Reverse(data);
        string strReversed = new string(data);

        TextWriter tw = new StreamWriter(stream);
        stream.Position = 0;
        tw.Write(strReversed);
        tw.Flush();
    }
    void CopyAndReverse(Stream from, Stream to)
    {
        TextReader tr = new StreamReader(from);
        TextWriter tw = new StreamWriter(to);

        string str = tr.ReadToEnd();
        char[] data = str.ToCharArray();
        Array.Reverse(data);
        string strReversed = new string(data);
        tw.Write(strReversed);
        tw.Flush();
    }

    private void CopyStream(Stream fromStream, Stream toStream)
    {
        try
        {
            StreamReader sr = new StreamReader(fromStream);
            StreamWriter sw = new StreamWriter(toStream);
            sw.WriteLine(sr.ReadToEnd());
            sw.Flush();
        }
        catch (Exception ex)
        {
            string message = String.Format("CopyStream failed because: {0}", ex.Message);
            log.Error(message, ex);
        }
    }
}

[AttributeUsage(AttributeTargets.Method)]
public class SoapLoggerExtensionAttribute : SoapExtensionAttribute
{
    private int priority = 1; 

    public override int Priority
    {
        get { return priority; }
        set { priority = value; }
    }

    public override System.Type ExtensionType
    {
        get { return typeof (SoapLoggerExtension); }
    }
}

You then add the following section to your web.config where YourNamespace and YourAssembly point to the class and assembly of your SoapExtension:

<webServices>
  <soapExtensionTypes>
    <add type="YourNamespace.SoapLoggerExtension, YourAssembly" 
       priority="1" group="0" />
  </soapExtensionTypes>
</webServices>

Try Fiddler2 it will let you inspect the requests and response. It might be worth noting that Fiddler works with both http and https traffic.

Here's a simplified version of the top answer. Add this to the <configuration> element of your web.config or App.config file. It will create a trace.log file in your project's bin/Debug folder. Or, you can specify an absolute path for the log file using the initializeData attribute.

  <system.diagnostics>
    <trace autoflush="true"/>
    <sources>
      <source name="System.Net" maxdatasize="9999" tracemode="protocolonly">
        <listeners>
          <add name="TraceFile" type="System.Diagnostics.TextWriterTraceListener" initializeData="trace.log"/>
        </listeners>
      </source>
    </sources>
    <switches>
      <add name="System.Net" value="Verbose"/>
    </switches>
  </system.diagnostics>

It warns that the maxdatasize and tracemode attributes are not allowed, but they increase the amount of data that can be logged, and avoid logging everything in hex.

I would prefer to have the framework do the logging for you by hooking in a logging stream which logs as the framework processes that underlying stream. The following isn't as clean as I would like it, since you can't decide between request and response in the ChainStream method. The following is how I handle it. With thanks to Jon Hanna for the overriding a stream idea

public class LoggerSoapExtension : SoapExtension
{
    private static readonly string LOG_DIRECTORY = ConfigurationManager.AppSettings["LOG_DIRECTORY"];
    private LogStream _logger;

    public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
    {
        return null;
    }
    public override object GetInitializer(Type serviceType)
    {
        return null;
    }
    public override void Initialize(object initializer)
    {
    }
    public override System.IO.Stream ChainStream(System.IO.Stream stream)
    {
        _logger = new LogStream(stream);
        return _logger;
    }
    public override void ProcessMessage(SoapMessage message)
    {
        if (LOG_DIRECTORY != null)
        {
            switch (message.Stage)
            {
                case SoapMessageStage.BeforeSerialize:
                    _logger.Type = "request";
                    break;
                case SoapMessageStage.AfterSerialize:
                    break;
                case SoapMessageStage.BeforeDeserialize:
                    _logger.Type = "response";
                    break;
                case SoapMessageStage.AfterDeserialize:
                    break;
            }
        }
    }
    internal class LogStream : Stream
    {
        private Stream _source;
        private Stream _log;
        private bool _logSetup;
        private string _type;

        public LogStream(Stream source)
        {
            _source = source;
        }
        internal string Type
        {
            set { _type = value; }
        }
        private Stream Logger
        {
            get
            {
                if (!_logSetup)
                {
                    if (LOG_DIRECTORY != null)
                    {
                        try
                        {
                            DateTime now = DateTime.Now;
                            string folder = LOG_DIRECTORY + now.ToString("yyyyMMdd");
                            string subfolder = folder + "\\" + now.ToString("HH");
                            string client = System.Web.HttpContext.Current != null && System.Web.HttpContext.Current.Request != null && System.Web.HttpContext.Current.Request.UserHostAddress != null ? System.Web.HttpContext.Current.Request.UserHostAddress : string.Empty;
                            string ticks = now.ToString("yyyyMMdd'T'HHmmss.fffffff");
                            if (!Directory.Exists(folder))
                                Directory.CreateDirectory(folder);
                            if (!Directory.Exists(subfolder))
                                Directory.CreateDirectory(subfolder);
                            _log = new FileStream(new System.Text.StringBuilder(subfolder).Append('\\').Append(client).Append('_').Append(ticks).Append('_').Append(_type).Append(".xml").ToString(), FileMode.Create);
                        }
                        catch
                        {
                            _log = null;
                        }
                    }
                    _logSetup = true;
                }
                return _log;
            }
        }
        public override bool CanRead
        {
            get
            {
                return _source.CanRead;
            }
        }
        public override bool CanSeek
        {
            get
            {
                return _source.CanSeek;
            }
        }

        public override bool CanWrite
        {
            get
            {
                return _source.CanWrite;
            }
        }

        public override long Length
        {
            get
            {
                return _source.Length;
            }
        }

        public override long Position
        {
            get
            {
                return _source.Position;
            }
            set
            {
                _source.Position = value;
            }
        }

        public override void Flush()
        {
            _source.Flush();
            if (Logger != null)
                Logger.Flush();
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            return _source.Seek(offset, origin);
        }

        public override void SetLength(long value)
        {
            _source.SetLength(value);
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            count = _source.Read(buffer, offset, count);
            if (Logger != null)
                Logger.Write(buffer, offset, count);
            return count;
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            _source.Write(buffer, offset, count);
            if (Logger != null)
                Logger.Write(buffer, offset, count);
        }
        public override int ReadByte()
        {
            int ret = _source.ReadByte();
            if (ret != -1 && Logger != null)
                Logger.WriteByte((byte)ret);
            return ret;
        }
        public override void Close()
        {
            _source.Close();
            if (Logger != null)
                Logger.Close();
            base.Close();
        }
        public override int ReadTimeout
        {
            get { return _source.ReadTimeout; }
            set { _source.ReadTimeout = value; }
        }
        public override int WriteTimeout
        {
            get { return _source.WriteTimeout; }
            set { _source.WriteTimeout = value; }
        }
    }
}
[AttributeUsage(AttributeTargets.Method)]
public class LoggerSoapExtensionAttribute : SoapExtensionAttribute
{
    private int priority = 1;
    public override int Priority
    {
        get
        {
            return priority;
        }
        set
        {
            priority = value;
        }
    }
    public override System.Type ExtensionType
    {
        get
        {
            return typeof(LoggerSoapExtension);
        }
    }
}

You haven't specified what language you are using but assuming C# / .NET you could use SOAP extensions.

Otherwise, use a sniffer such as Wireshark

Related