English 中文(简体)
从在ASP.net上运行的Web引用客户端获取原始肥皂数据。
原标题:
  • 时间:2008-11-19 00:38:25
  •  标签:

我正在解决当前项目中的网络服务客户端问题。我不确定服务服务器的平台(很可能是LAMP)。我相信他们那边有问题,因为我已经排除了客户端可能存在的问题。该客户端是从服务WSDL自动生成的标准ASMX类型的Web引用代理。

我需要获取的是原始SOAP消息(请求和响应)

如何才能最好地进行这件事?

最佳回答

我在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>
问题回答

您可以实现一个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.config或者序列化类。下面的代码适用于我:

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

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

试试Fiddler2,它可以让你查看请求和响应。值得注意的是,Fiddler可以处理http和https流量。

看起来Tim Carter的解决方案在调用网络引用时抛出异常时无法工作。我一直在尝试获取原始网络响应,以便在异常被抛出后可以在错误处理程序中对其进行检查(代码中)。然而,我发现当调用抛出异常时,Tim的方法写入的响应日志为空。我不完全理解这段代码,但它似乎是在.Net已经使网络响应失效和丢弃之后削减了这一过程的点。

我正在和一位客户合作,他们正在使用低级编码手动开发网络服务。目前为止,他们正在将自己的内部处理错误消息作为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:	est.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:	est.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方法将起作用,因为您在ChainStream中将流类型从ConnectStream更改为MemoryStream,并且MemoryStream允许寻求操作。(我尝试将ConnectStream强制转换为MemoryStream-但不允许。)

所以......微软应该允许在ChainStream类型上进行寻求操作,或者确保SoapMessage.Stream是真正的只读副本,正如其应该的那样。(写信给你的国会议员等等……)

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

这是顶部答案的简化版。将此添加到您的web.configApp.config文件的<configuration>元素中。它将在您项目的bin/Debug文件夹中创建一个trace.log文件。或者,您可以使用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属性,但这会增加可以记录的数据量,并且避免记录所有内容为十六进制。

我更喜欢框架通过钩入日志流来为您记录日志,该流在框架处理底层流时记录日志。下面的代码不够干净,因为您无法在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);
        }
    }
}

你没有指定你使用的语言,但是假设你使用C#/ .NET,你可以使用SOAP扩展

否则,使用诸如Wireshark的嗅探器。

这是在C#中获取原始消息的命令:

OperationContext.Current.RequestContext.RequestMessage

我浪费了3天时间尝试使用SOAP扩展,但它比我想象的要简单得多。SOAP扩展已过时。我建议阅读这些如何检查或修改MSDN客户端上的消息使用消息类

我意识到我来晚了,而且语言没有明确指定,这里提供一种基于Bimmerbound答案的VB.NET解决方案,以防有人偶然遇到并需要解决方案。注意:如果您尚未拥有字符串生成器类的引用,则需要在项目中添加。

 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 前的函数中的替换调用是为了从输出中去除 vbCrLf 字符。我的 xml 中有很多这样的字符,但这显然取决于您尝试序列化的内容,我认为它们可能在将对象发送到 Web 服务期间被剥离。





相关问题
热门标签