从运行在ASP.net中的Web引用客户端获取RAW Soap数据

我试图在我当前的项目中遇到一个Web服务客户端。 我不确定服务器的平台(很有可能是LAMP)。 我相信他们的篱笆旁有一个错误,因为我已经消除了我的客户的潜在问题。 客户端是从服务WSDL中自动生成的标准ASMXtypes的Web引用代理。

我需要去的是RAW 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,将日志文件的完整请求和响应logging下来。 然后,您可以在web.config中启用SoapExtension,从而可以轻松打开/closures以进行debugging。 这里是我为自己的使用find和修改的一个例子,在我的情况下,日志logging是由log4net完成的,但是你可以用你自己的日志方法replace。

 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和httpsstream量。

如果对Web引用的调用引发exception,则看起来像Tim Carter的解决scheme不起作用。 我一直在尝试在原始网站的反应,所以我可以检查它(在代码中)的error handling程序一旦抛出exception。 但是,我发现当调用抛出一个exception时,Tim的方法写的响应日志是空的。 我并不完全理解代码,但是似乎Tim的方法在.Net已经失效并丢弃了Web响应之后切入了这个过程。

我正在和一个用低级编码手动开发Web服务的客户端合作。 此时,他们将自己的内部stream程错误消息作为HTML格式的消息添加到SOAP格式响应之前的响应中。 当然,这个自动化的.Net Web参考文档也是如此。 如果在抛出exception后我可以得到原始的HTTP响应,那么我可以在混合返回的HTTP响应中查找并parsing任何SOAP响应,并知道他们收到了我的数据。

后来…

这是一个可以工作的解决scheme,即使在执行之后(请注意,我只是在响应之后 – 也可以得到请求):

 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文件中configuration的方法:

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

“TestCallWebService”应该被replace为库的名称(这正好是我正在使用的testing控制台应用程序的名称)。

你真的不应该去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,它应该是一个只读stream,你可以用它来检查这个数据。 这是一个拧紧的原因,如果你读的stream,后续处理炸弹没有数据发现错误(stream在结束),你不能重置位置的开始。

有趣的是,如果你同时使用ChainStream和ProcessMessage这两种方法,ProcessMessage方法将起作用,因为你在ChainStream中将streamtypes从ConnectStream改变为MemoryStream,并且MemoryStream确实允许查找操作。 (我试图将ConnectStream投射到MemoryStream – 不允许。)

所以……微软应该允许对ChainStreamtypes进行seek操作,或者使SoapMessage.Stream真正成为只读副本。 (写你的议员,等等…)

还有一点。 创build一个方法来检测exception后的原始HTTP响应,但仍然没有得到完整的响应(由HTTP嗅探器确定)。 这是因为当开发Web服务将HTML错误消息添加到响应的开头时,它不调整Content-Length标头,所以Content-Length值小于实际响应主体的大小。 我所得到的只是Content-Length值的字符数 – 其余的都没有了。 显然,当.Net读取响应stream时,它只是读取Content-Length字符数,并且不允许Content-Length值可能错误。 这是应该的; 但是如果Content-Length头部值是错误的,唯一的办法就是使用HTTP嗅探器( http://www.ieinspector.com的; I用户HTTP分析器)。

我宁愿让框架为你做日志logging,通过挂接日志stream作为基础stream的框架进程。 以下是不是我想要的干净,因为你不能在ChainStream方法中的请求和响应之间做出决定。 以下是我如何处理它。 感谢Jon Hanna提供了一个stream派的主意

 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等嗅探器

我意识到我对这个聚会已经很晚了,而且由于语言并没有真正指定,所以这里是一个基于Bimmerbound的答案的VB.NET解决scheme,以防万一有人偶然发现这个问题并需要一个解决scheme。 注意:如果你还没有,你需要在你的项目中引用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 

只需调用该函数,它将返回一个string,其中包含您尝试传递给Web服务的对象的序列化xml(实际上,这应该适用于您关心的任何对象)。

作为一个方面说明,在返回xml之前函数中的replace调用是从输出中去掉vbCrLf字符。 我有一堆在生成的XML内,但是这将明显不同,这取决于你想要序列化,我认为他们可能会被剥离在对象被发送到Web服务。