Obtener datos RAW Soap desde un cliente de referencia web ejecutándose en ASP.net

Estoy tratando de solucionar el problema de un cliente de servicio web en mi proyecto actual. No estoy seguro de la plataforma del servidor de servicio (muy probablemente LAMP). Creo que hay una falla en su lado de la valla ya que eliminé los posibles problemas con mi cliente. El cliente es un proxy de referencia web de tipo ASMX estándar generado automáticamente a partir del servicio WSDL.

Lo que necesito saber es los mensajes RAW SOAP (Solicitud y respuestas)

¿Cuál es la mejor manera de hacerlo?

web.config siguientes cambios en web.config para obtener el sobre SOAP (solicitud / respuesta). Esto generará toda la información SOAP sin procesar en el archivo trace.log .

                       

Puede implementar una SoapExtension que registra la solicitud completa y la respuesta a un archivo de registro. A continuación, puede habilitar SoapExtension en web.config, lo que facilita el encendido / apagado para fines de depuración. Aquí hay un ejemplo que he encontrado y modificado para mi propio uso, en mi caso Log4net hizo el registro pero puede reemplazar los métodos de registro con los suyos.

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

A continuación, agregue la siguiente sección a su web.config donde YourNamespace y YourAssembly apuntan a la clase y el ensamblaje de su SoapExtension:

      

No estoy seguro de por qué todo el alboroto con web.config o una clase de serializador. El siguiente código funcionó para mí:

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

Prueba Fiddler2 que te permitirá inspeccionar las solicitudes y la respuesta. Vale la pena señalar que Fiddler funciona con tráfico http y https.

Parece que la solución de Tim Carter no funciona si la llamada a la referencia web arroja una excepción. He intentado acceder a la resonancia web sin formato para poder examinarla (en código) en el controlador de errores una vez que se lanza la excepción. Sin embargo, descubro que el registro de respuestas escrito por el método de Tim está en blanco cuando la llamada arroja una excepción. No entiendo completamente el código, pero parece que el método de Tim recorta el proceso después del punto donde .Net ya ha invalidado y descartado la respuesta web.

Estoy trabajando con un cliente que está desarrollando un servicio web manualmente con encoding de bajo nivel. En este punto, están agregando sus propios mensajes de error de proceso interno como mensajes formateados en HTML en la respuesta ANTES de la respuesta formateada de SOAP. Por supuesto, la referencia web automática de .Net explota en esto. Si pudiera obtener la respuesta HTTP sin procesar después de lanzar una excepción, podría buscar y analizar cualquier respuesta SOAP dentro de la respuesta HTTP de respuesta mixta y saber que recibieron mis datos correctamente o no.

Luego …

Aquí hay una solución que funciona, incluso después de una excepción (tenga en cuenta que solo estoy después de la respuesta, podría obtener la solicitud también):

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

Así es como lo configura en el archivo de configuración:

  ...         

“TestCallWebService” debe sustituirse por el nombre de la biblioteca (que resultó ser el nombre de la aplicación de la consola de prueba en la que estaba trabajando).

Realmente no deberías tener que ir a ChainStream; deberías poder hacerlo más simplemente desde ProcessMessage como:

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

Si busca SoapMessage.Stream, se supone que es una secuencia de solo lectura que puede usar para inspeccionar los datos en este momento. Esto es un error porque si lee el flujo, las bombas de procesamiento subsiguientes sin errores de datos encontrados (el flujo estaba al final) y no puede restablecer la posición al principio.

Curiosamente, si haces ambos métodos, ChainStream y ProcessMessage, el método ProcessMessage funcionará porque cambiaste el tipo de flujo de ConnectStream a MemoryStream en ChainStream, y MemoryStream permite las operaciones de búsqueda. (Intenté transmitir el ConnectStream a MemoryStream, no estaba permitido).

Entonces … Microsoft debería permitir buscar operaciones en el tipo de ChainStream o hacer que SoapMessage.Stream sea realmente una copia de solo lectura como se supone que debe ser. (Escribe tu congresista, etc.)

Un punto adicional. Después de crear una forma de recuperar la respuesta HTTP sin procesar después de una excepción, todavía no obtuve la respuesta completa (según lo determinado por un sniffer HTTP). Esto se debía a que cuando el servicio web de desarrollo agregó los mensajes de error HTML al comienzo de la respuesta, no ajustaba el encabezado Content-Length, por lo que el valor Content-Length era menor que el tamaño del cuerpo de la respuesta real. Todo lo que obtuve fue el número de caracteres del valor de la longitud del contenido, el rest faltaba. Obviamente, cuando .Net lee la secuencia de respuesta, solo lee el número de caracteres de la longitud del contenido y no permite que el valor de la longitud del contenido sea incorrecto. Esto es como debería ser; pero si el valor del encabezado Content-Length es incorrecto, la única forma en que obtendrá el cuerpo completo de la respuesta es con un sniffer HTTP (el usuario I HTTP Analyzer de http://www.ieinspector.com ).

Preferiría que el marco haga el registro al conectar una secuencia de registro que registra a medida que el marco procesa ese flujo subyacente. Lo siguiente no es tan claro como me gustaría, ya que no puede decidir entre la solicitud y la respuesta en el método ChainStream. Lo siguiente es cómo lo manejo. Gracias a Jon Hanna por anular una idea de transmisión

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

No ha especificado qué idioma está utilizando, pero suponiendo que C # / .NET podría usar extensiones SOAP .

De lo contrario, use un sniffer como Wireshark

Me doy cuenta de que llegué bastante tarde a la fiesta, y como el lenguaje no se especificó en realidad, aquí hay una solución de VB.NET basada en la respuesta de Bimmerbound, en caso de que alguien se tope con esto y necesite una solución. Nota: debe tener una referencia a la clase de generador de cadenas en su proyecto, si no lo hace.

  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 

Simplemente llame a la función y devolverá una cadena con el xml serializado del objeto que está intentando pasar al servicio web (de manera realista, esto debería funcionar para cualquier objeto que le interese).

Como nota al margen, la llamada de reemplazo en la función antes de devolver el xml es quitar los caracteres vbCrLf de la salida. Los míos tenían un montón de ellos dentro del xml generado, sin embargo, esto obviamente variará dependiendo de lo que intente serializar, y creo que podrían ser eliminados durante el envío del objeto al servicio web.