Pregunta

Estoy tratando de solucionar problemas de un cliente de servicio web en mi proyecto actual. No estoy seguro de la plataforma del servidor de servicios (probablemente LAMP). Creo que hay una falla en su lado de la cerca, ya que he eliminado los posibles problemas con mi cliente. El cliente es un proxy de referencia web de tipo ASMX estándar generado automáticamente desde el servicio WSDL.

A lo que necesito llegar es a los mensajes RAW SOAP (solicitud y respuestas)

¿Cuál es la mejor manera de hacer esto?

¿Fue útil?

Solución

Hice los siguientes cambios en web.config para obtener el Sobre SOAP (Solicitud / Respuesta). Esto generará toda la información de SOAP sin procesar en el archivo 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>

Otros consejos

Puede implementar una SoapExtension que registre la solicitud completa y la respuesta a un archivo de registro. Luego puede habilitar SoapExtension en web.config, lo que facilita el encendido / apagado con fines de depuración. Aquí hay un ejemplo que he encontrado y modificado para mi propio uso, en mi caso el registro fue realizado por log4net, 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); }
    }
}

Luego agrega la siguiente sección a su web.config donde YourNamespace y YourAssembly señalan la clase y el ensamblaje de su SoapExtension:

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

No estoy seguro de por qué tanto 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();
}

Pruebe Fiddler2 le 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 estado tratando de obtener la resonancia web sin procesar para poder examinarla (en código) en el controlador de errores una vez que se produce 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 interrumpe 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 codificación de bajo nivel. En este punto, están agregando sus propios mensajes de error de proceso interno como mensajes con formato HTML en la respuesta ANTES de la respuesta con formato SOAP. Por supuesto, la referencia web automática .Net explota en esto. Si pudiera obtener la respuesta HTTP sin procesar después de que se produzca 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.

Más tarde ...

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

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:

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

" TestCallWebService " debería reemplazarse con el nombre de la biblioteca (que resultó ser el nombre de la aplicación de consola de prueba en la que estaba trabajando).

Realmente no deberías tener que ir a ChainStream; debería poder hacerlo de manera más simple 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 punto. Esto es un error porque si lee la transmisión, las bombas de procesamiento posteriores sin errores de datos encontrados (la transmisión estaba al final) y no puede restablecer la posición al principio.

Curiosamente, si usa ambos métodos, ChainStream y ProcessMessage, el método ProcessMessage funcionará porque cambió el tipo de transmisión 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 operaciones de búsqueda en el tipo ChainStream o hacer que SoapMessage.Stream sea realmente una copia de solo lectura como se supone que debe ser. (Escriba a su congresista, etc.)

Un punto más. Después de crear una forma de recuperar la respuesta HTTP sin procesar después de una excepción, aún no obtuve la respuesta completa (según lo determinado por un sniffer HTTP). Esto se debió a que cuando el servicio web de desarrollo agregó los mensajes de error HTML al comienzo de la respuesta, no ajustó 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 de valor de longitud de contenido; el resto faltaba. Obviamente, cuando .Net lee la secuencia de respuesta, solo lee el número de caracteres de Longitud de contenido y no permite que el valor de Longitud de contenido sea incorrecto. Esto es como debería ser; pero si el valor del encabezado Content-Length es incorrecto, la única forma de obtener el cuerpo de respuesta completo es con un sniffer HTTP (utilizo HTTP Analyzer de http://www.ieinspector.com ).

Preferiría que el marco haga el registro por usted enganchando una secuencia de registro que se registre a medida que el marco procesa esa secuencia subyacente. Lo siguiente no es tan limpio como me gustaría, ya que no puede decidir entre solicitud y 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 pueda usar Extensiones SOAP .

De lo contrario, utilice un sniffer como Wireshark

Me doy cuenta de que llego bastante tarde a la fiesta, y dado que el idioma no se especificó realmente, aquí hay una solución VB.NET basada en la respuesta de Bimmerbound, en caso de que alguien se encuentre con esto y necesite una solución. Nota: debe tener una referencia a la clase stringbuilder en su proyecto, si aún no lo tiene.

 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 quiera lanzarle también).

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top