从ASP.net中运行的Web参考客户端获取RAW Soap数据


95

我正在尝试在当前项目中拍摄Web服务客户端。我不确定服务服务器的平台(很可能是LAMP)。我相信他们的做法是错误的,因为我已经消除了与客户的潜在问题。客户端是从服务WSDL自动生成的标准ASMX类型的Web参考代理。

我需要了解的是RAW SOAP消息(请求和响应)

最好的方法是什么?

Answers:


135

我进行了以下更改web.config以获取SOAP(请求/响应)信封。这会将所有原始SOAP信息输出到该文件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>

2
在使用VS 2010的02/2012中,这对我不起作用。有人知道最新的解决方案吗?
qxotk 2012年

2
这是有帮助的。但是,在我的情况下,响应被压缩,并且从组合输出中拉出ASCII或十六进制代码是很痛苦的。它们的替代输出方法是二进制还是纯文本?

2
@Dediqated-您可以通过在上面的示例中调整initializeData属性的值来设置trace.log文件的路径。
DougCouto

3
它不再有用了,我使用wireshark来查看原始的SOAP数据。但是还是谢谢你!
2013年

7
好。但是XML对我来说就像:System.Net详细:0:[14088] 00000000:3C 3F 78 6D 6C 20 76 65-72 73 69 6F 6E 3D 22 31:<?xml version =“ 1 System.Net Verbose: 0:[14088] 00000010:2E 30 22 20 65 6E 63 6F-64 69 6E 67 3D 22 75 74:.0“ encoding =” ut .......任何想法如何格式化它或仅包含xml数据。?
Habeeb 2015年

35

您可以实现SoapExtension,将完整的请求和响应记录到日志文件中。然后,您可以在web.config中启用SoapExtension,从而可以轻松打开/关闭进行调试。这是我发现并修改以供自己使用的示例,在我的情况下,日志记录是由log4net完成的,但是您可以用自己的日志方法替换。

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); }
    }
}

然后,将以下部分添加到web.config中,其中YourNamespace和YourAssembly指向SoapExtension的类和程序集:

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

我会去的。我研究了肥皂扩展,但在我看来,它更像是托管服务。如果可行的话,请打勾:)
安德鲁·哈里

是的,这将可以记录通过生成的代理从Web应用程序发出的调用。
John Lemp

这是我所采用的方法,它确实运行良好,并且可以在记录日志之前使用xpath屏蔽所有敏感数据。
Dave Baghdanov 2014年

我需要在客户端的soap请求中添加标头。这对我有帮助。rhyous.com/2015/04/29/...
Rhyous

我也是c#的新手,不确定要为Assembly名称加上什么。该扩展程序似乎无法运行。
科林·安德森

28

不确定为什么要对web.config或序列化程序类大惊小怪。下面的代码为我工作:

XmlSerializer xmlSerializer = new XmlSerializer(myEnvelope.GetType());

using (StringWriter textWriter = new StringWriter())
{
    xmlSerializer.Serialize(textWriter, myEnvelope);
    return textWriter.ToString();
}

17
哪里myEnvelope来的?
ProfK

6
我不明白为什么这个答案没有更多的支持,直截了当!做得好!
巴蒂斯塔2015年

1
myEnvalope将是您通过Web服务发送过来的对象。我不能像描述的那样使它工作(特别是textWriter.tostring函数),但它在调试模式下可以很好地用于我的目的,因为您可以在调用序列化后看到原始的xml。非常感谢这个答案,因为其他一些答案的结果是混合的(例如,使用web.config进行记录仅返回了xml的一部分,而且也不容易解析出来)。
user2366842 2015年

@Bimmerbound:这可用于通过加密VPN发送的安全肥皂消息吗?
我们在香蕉里的人2016年

6
这个答案不会产生肥皂信封,它只是序列化为xml。如何获得肥皂信封的要求?
阿里·卡拉卡

21

尝试Fiddler2,它将使您检查请求和响应。可能值得注意的是Fiddler可以同时处理HTTP和HTTP流量。


这是一个https加密的服务
Andrew Harry

8
提琴手可以在解除加密HTTPS流量
亚伦菲舍尔

1
我发现此解决方案比将跟踪添加到web / app.config更好。谢谢!
andyuk

1
但是在配置文件中使用system.diagnostics不需要安装第三方应用程序
Azat 2013年

1
@AaronFischer:Fiddler是否可以处理通过加密VPN发送的肥皂消息?
我们在香蕉里的人2016年

6

如果对Web参考的调用引发异常,则似乎Tim Carter的解决方案不起作用。我一直在尝试获取原始Web共振,因此一旦引发异常,就可以在错误处理程序中检查(在代码中)。但是,我发现当调用引发异常时,由Tim的方法编写的响应日志为空。我不完全理解代码,但是看来.tim已经在.Net已经失效并丢弃了Web响应之后切入了过程。

我正在与使用低级编码手动开发Web服务的客户端一起工作。此时,他们将自己的内部过程错误消息作为HTML格式的消息添加到SOAP格式的响应之前的响应中。当然,.net网站的自动魔术在此方面引起了轰动。如果在引发异常后可以得到原始的HTTP响应,则可以在混合返回的HTTP响应中查找并解析任何SOAP响应,并知道它们是否接收到我的数据。

以后...

这是一个即使在执行后仍然有效的解决方案(请注意,我只是在响应之后-也可以获取请求):

namespace ChuckBevitt
{
    class GetRawResponseSoapExtension : SoapExtension
    {
        //must override these three methods
        public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
        {
            return null;
        }
        public override object GetInitializer(Type serviceType)
        {
            return null;
        }
        public override void Initialize(object initializer)
        {
        }

        private bool IsResponse = false;

        public override void ProcessMessage(SoapMessage message)
        {
            //Note that ProcessMessage gets called AFTER ChainStream.
            //That's why I'm looking for AfterSerialize, rather than BeforeDeserialize
            if (message.Stage == SoapMessageStage.AfterSerialize)
                IsResponse = true;
            else
                IsResponse = false;
        }

        public override Stream ChainStream(Stream stream)
        {
            if (IsResponse)
            {
                StreamReader sr = new StreamReader(stream);
                string response = sr.ReadToEnd();
                sr.Close();
                sr.Dispose();

                File.WriteAllText(@"C:\test.txt", response);

                byte[] ResponseBytes = Encoding.ASCII.GetBytes(response);
                MemoryStream ms = new MemoryStream(ResponseBytes);
                return ms;

            }
            else
                return stream;
        }
    }
}

这是在配置文件中配置它的方式:

<configuration>
     ...
  <system.web>
    <webServices>
      <soapExtensionTypes>
        <add type="ChuckBevitt.GetRawResponseSoapExtension, TestCallWebService"
           priority="1" group="0" />
      </soapExtensionTypes>
    </webServices>
  </system.web>
</configuration>

应将“ TestCallWebService”替换为库的名称(恰好是我正在使用的测试控制台应用程序的名称)。

您真的不必去ChainStream。您应该能够通过ProcessMessage更简单地执行此操作,如下所示:

public override void ProcessMessage(SoapMessage message)
{
    if (message.Stage == SoapMessageStage.BeforeDeserialize)
    {
        StreamReader sr = new StreamReader(message.Stream);
        File.WriteAllText(@"C:\test.txt", sr.ReadToEnd());
        message.Stream.Position = 0; //Will blow up 'cause type of stream ("ConnectStream") doesn't alow seek so can't reset position
    }
}

如果您查找SoapMessage.Stream,它应该是一个只读流,您可以在此时使用它检查数据。这是一个搞砸的原因,因为如果您确实读取了流,随后的处理炸弹没有数据发现错误(流在末尾),并且您无法将位置重置为开始。

有趣的是,如果同时使用ChainStream和ProcessMessage这两种方法,则ProcessMessage方法将起作用,因为您已将流类型从ConnectStream更改为ChainStream中的MemoryStream,并且MemoryStream确实允许查找操作。(我尝试将ConnectStream强制转换为MemoryStream-不允许。)

因此..... Microsoft应该允许对ChainStream类型进行查找操作,或者使SoapMessage.Stream真正像预期的那样成为只读副本。(写信给国会议员,等等。)

还有一点。在创建了一种在异常发生后检索原始HTTP响应的方法后,我仍然没有获得完整的响应(由HTTP嗅探器确定)。这是因为当开发Web服务将HTML错误消息添加到响应的开头时,它没有调整Content-Length标头,因此Content-Length值小于实际响应主体的大小。我得到的只是Content-Length值的字符数-其余的都丢失了。显然,当.Net读取响应流时,它仅读取Content-Length字符数,并且不允许Content-Length值可能是错误的。这是应该的;但是,如果Content-Length标头值错误,则获取整个响应正文的唯一方法是使用HTTP嗅探器(我来自http://www.ieinspector.com)。


2

我希望框架通过挂接一个日志流来为您进行日志记录,该日志流在框架处理该基础流时进行记录。以下内容并不尽如人意,因为您无法在ChainStream方法中确定请求和响应之间。以下是我的处理方式。感谢Jon Hanna提出的最重要的创意

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);
        }
    }
}

1
@TimCarter它对我有用,我唯一删除的是客户端部分,该部分给我一个带有冒号的名称,从而导致异常。因此,一旦我删除了这一行,它对于那些希望为每个请求/响应拥有一个文件的人来说就可以正常工作。如果您阅读其他答案,唯一要添加的内容是显而易见的,并且必须添加web.config节点以指示soapExtension。谢谢!
netadictos

1

这是最佳答案的简化版本。将此添加到<configuration>您的web.configApp.config文件的元素中。它将trace.log在您项目的bin/Debug文件夹中创建一个文件。或者,您可以使用initializeData属性为日志文件指定绝对路径。

  <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>

它警告不允许使用maxdatasizetracemode属性,但它们会增加可记录的数据量,并避免以十六进制记录所有内容。


0

您尚未指定要使用的语言,但是假设使用C#/。NET,则可以使用SOAP扩展

否则,请使用嗅探器,例如Wireshark


1
是的,尝试过wireshark,它只能显示标题信息,soap内容已加密。
Andrew Harry

那可能是因为您使用的是https。据我所记得,SOAP扩展是在更高级别上进行的,因此您应该能够在解密数据之后看到它们。
rbrayb

2
使用Fiddler而不是Wireshark,它可以立即将https解密。
罗德里戈·斯特劳斯

-1

我意识到我参加聚会已经很晚了,并且由于实际上并未指定语言,因此这是一个基于Bimmerbound答案的VB.NET解决方案,以防万一有人偶然发现此问题并需要解决方案。注意:您还需要在项目中引用stringbuilder类(如果尚未)。

 Shared Function returnSerializedXML(ByVal obj As Object) As String
    Dim xmlSerializer As New System.Xml.Serialization.XmlSerializer(obj.GetType())
    Dim xmlSb As New StringBuilder
    Using textWriter As New IO.StringWriter(xmlSb)
        xmlSerializer.Serialize(textWriter, obj)
    End Using


    returnSerializedXML = xmlSb.ToString().Replace(vbCrLf, "")

End Function

只需调用该函数,它就会返回一个字符串,其中包含您要传递给Web服务的对象的序列化xml(实际上,这也应适用于您想扔给它的任何对象)。

附带说明一下,在返回xml之前,函数中的replace调用是从输出中删除vbCrLf字符。我的文件在生成的xml中有很多,但是显然这要取决于您要序列化的内容,我认为在将对象发送到Web服务期间它们可能会被剥离。


如果有人对我投反对票,请随时让我知道我的缺失/可以改善的地方。
user2366842 '18
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.