Obtendo dados RAW Soap de um cliente de referência da Web em execução no ASP.net

95

Estou tentando localizar um cliente de serviço da web em meu projeto atual. Não tenho certeza da plataforma do servidor de serviço (provavelmente LAMP). Acredito que haja uma falha do lado deles da cerca, pois eliminei os possíveis problemas com meu cliente. O cliente é um proxy de referência da web do tipo ASMX padrão gerado automaticamente a partir do WSDL de serviço.

O que eu preciso é obter as mensagens RAW SOAP (solicitação e respostas)

Qual é a melhor maneira de fazer isso?

Andrew Harry
fonte

Respostas:

135

Fiz as seguintes alterações web.configpara obter o envelope SOAP (solicitação / resposta). Isso produzirá todas as informações brutas do SOAP para o arquivo 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>
Keltex
fonte
2
Isso não está funcionando para mim em 02/2012 usando o VS 2010. Alguém conhece uma solução mais atualizada?
qxotk
2
Isso é útil. No entanto, no meu caso, as respostas são compactadas com Gzip e extrair os códigos ASCII ou hexadecimais da saída combinada é uma dor. Seus métodos de saída alternativos são binários ou somente texto?
2
@Dediqated - você pode definir o caminho para o arquivo trace.log ajustando o valor do atributo initializeData no exemplo acima.
DougCouto
3
Não adianta mais, eu usei Wirehark para ver os dados SOAP brutos. Mas obrigado mesmo assim!
Dedicado
7
Boa. Mas o XML para mim é como: System.Net Verbose: 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 "codificação =" ut ....... Qualquer ideia de como formatá-lo ou ter apenas dados xml .?
Habeeb
35

Você pode implementar um SoapExtension que registra a solicitação completa e a resposta em um arquivo de log. Você pode então habilitar o SoapExtension no web.config, o que torna mais fácil ligar / desligar para propósitos de depuração. Aqui está um exemplo que encontrei e modifiquei para meu próprio uso. No meu caso, o registro foi feito por log4net, mas você pode substituir os métodos de registro pelos seus.

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

Em seguida, você adiciona a seguinte seção ao seu web.config, onde YourNamespace e YourAssembly apontam para a classe e o assembly de seu SoapExtension:

<webServices>
  <soapExtensionTypes>
    <add type="YourNamespace.SoapLoggerExtension, YourAssembly" 
       priority="1" group="0" />
  </soapExtensionTypes>
</webServices>
John Lemp
fonte
Eu vou tentar. Eu tinha pesquisado extensões de sabonete, mas me parecia que era mais para hospedar o serviço. Avisará se funcionar :)
Andrew Harry
Sim, isso funcionará para registrar as chamadas feitas de seu aplicativo da web por meio do proxy gerado.
John Lemp de
Esta é a rota que escolhi, está funcionando muito bem e posso usar o xpath para mascarar quaisquer dados confidenciais antes de registrá-los.
Dave Baghdanov
Eu precisava adicionar cabeçalhos à solicitação de sabão no cliente. Isso me ajudou. rhyous.com/2015/04/29/…
Rhyous
Também sou novo em c # e não tenho certeza do que colocar no nome do assembly. A extensão não parece funcionar.
Collin Anderson
28

Não tenho certeza por que tanto barulho com web.config ou uma classe de serializador. O código abaixo funcionou para mim:

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

using (StringWriter textWriter = new StringWriter())
{
    xmlSerializer.Serialize(textWriter, myEnvelope);
    return textWriter.ToString();
}
Bimmerbound
fonte
17
De onde myEnvelopevem?
ProfK
6
Eu não entendo porque essa resposta não tem mais votos positivos, direto e direto ao ponto! Bem feito!
Batista
1
myEnvalope seria o objeto que você está enviando por meio do serviço da web. Não consegui fazer isso funcionar conforme descrito (particularmente a função textWriter.tostring), mas funcionou bem o suficiente para meus propósitos no modo de depuração, pois você pode ver o xml bruto após chamar serialize. Agradeço muito essa resposta, já que alguns dos outros trabalharam com resultados mistos (o registro usando o web.config retornou apenas parte do xml, por exemplo, e também não foi muito fácil de analisar).
user2366842
@Bimmerbound: isso funcionaria com mensagens de sabão seguras enviadas por uma VPN criptografada?
Our Man in Bananas,
6
Essa resposta não produz envelope de sabão, apenas serializa em xml. Como obter um pedido de envelope de sabão?
Ali Karaca
21

Experimente o Fiddler2, ele permitirá que você inspecione as solicitações e a resposta. Pode ser importante notar que o Fiddler funciona com tráfego http e https.

Aaron Fischer
fonte
É um serviço criptografado https
Andrew Harry
8
O Fiddler pode descriptografar o tráfego https
Aaron Fischer
1
Descobri que essa solução é melhor do que adicionar rastreamento ao web / app.config. Obrigado!
andyuk,
1
Mas o uso de system.diagnostics no arquivo de configuração não requer a instalação de um aplicativo de terceiros
Azat,
1
@AaronFischer: O Fiddler funcionaria com mensagens de sabão enviadas por uma VPN criptografada?
Our Man in Bananas,
6

Parece que a solução de Tim Carter não funciona se a chamada para a referência da web lançar uma exceção. Tenho tentado obter a ressonância da web bruta para que possa examiná-la (no código) no manipulador de erros, uma vez que a exceção é lançada. No entanto, estou descobrindo que o log de resposta escrito pelo método de Tim está em branco quando a chamada lança uma exceção. Não entendo completamente o código, mas parece que o método de Tim interrompe o processo após o ponto em que .Net já invalidou e descartou a resposta da web.

Estou trabalhando com um cliente que está desenvolvendo um serviço da web manualmente com codificação de baixo nível. Neste ponto, eles estão adicionando suas próprias mensagens de erro de processo interno como mensagens formatadas em HTML na resposta ANTES da resposta formatada em SOAP. Claro, a referência da web automagic .Net explode com isso. Se eu pudesse obter a resposta HTTP bruta após uma exceção ser lançada, eu poderia procurar e analisar qualquer resposta SOAP dentro da resposta HTTP de retorno mista e saber se eles receberam meus dados corretamente ou não.

Mais tarde ...

Esta é uma solução que funciona, mesmo após uma execução (observe que estou apenas após a resposta - também poderia obter a solicitação):

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

Veja como configurá-lo no arquivo de configuração:

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

"TestCallWebService" deve ser substituído pelo nome da biblioteca (que por acaso era o nome do aplicativo de console de teste em que eu estava trabalhando).

Você realmente não deveria ter que ir para ChainStream; você deve ser capaz de fazer isso de forma mais simples a partir do 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
    }
}

Se você pesquisar SoapMessage.Stream, é suposto ser um fluxo somente leitura que você pode usar para inspecionar os dados neste ponto. Isso é uma bagunça, porque se você ler o fluxo, o processamento subsequente bombeará sem erros de dados encontrados (o fluxo estava no final) e você não pode redefinir a posição para o início.

Curiosamente, se você fizer os dois métodos, ChainStream e ProcessMessage, o método ProcessMessage funcionará porque você alterou o tipo de fluxo de ConnectStream para MemoryStream em ChainStream, e MemoryStream permite operações de busca. (Tentei transmitir o ConnectStream para MemoryStream - não foi permitido.)

Portanto ... a Microsoft deve permitir operações de busca no tipo ChainStream ou tornar o SoapMessage.Stream realmente uma cópia somente leitura, como deveria ser. (Escreva ao seu congressista, etc ...)

Mais um ponto. Depois de criar uma maneira de recuperar a resposta HTTP bruta após uma exceção, ainda não obtive a resposta completa (conforme determinado por um sniffer HTTP). Isso acontecia porque, quando o serviço da web de desenvolvimento incluía as mensagens de erro HTML no início da resposta, ele não ajustava o cabeçalho Content-Length, portanto, o valor Content-Length era menor que o tamanho do corpo da resposta real. Tudo o que consegui foi o número de caracteres do valor Content-Length - o resto estava faltando. Obviamente, quando .Net lê o fluxo de resposta, ele apenas lê o número de caracteres do Content-Length e não permite que o valor do Content-Length esteja possivelmente errado. É assim que deve ser; mas se o valor do cabeçalho Content-Length estiver errado, a única maneira de obter todo o corpo da resposta é com um sniffer HTTP (eu utilizo o HTTP Analyzer dohttp://www.ieinspector.com ).

Chuck Bevitt
fonte
2

Eu preferiria que a estrutura fizesse o registro para você conectando um fluxo de registro que registra como a estrutura processa esse fluxo subjacente. O seguinte não é tão claro quanto eu gostaria, já que você não pode decidir entre solicitação e resposta no método ChainStream. O seguinte é como eu lido com isso. Com agradecimentos a Jon Hanna por substituir uma ideia de fluxo

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

fonte
1
@TimCarter funcionou comigo, a única coisa que excluí é a parte do cliente, que me dá um nome com dois-pontos, causando uma exceção. Assim, uma vez que excluo esta linha, funciona bem para aqueles que desejam ter um arquivo para cada solicitação / resposta. A única coisa a acrescentar é algo óbvio se você ler as outras respostas, e é que você precisa adicionar o nó web.config para indicar o soapExtension. THX!
netadictos
1

Aqui está uma versão simplificada da resposta principal. Adicione isso ao <configuration>elemento do seu arquivo web.configou App.config. Isso criará um trace.logarquivo na bin/Debugpasta do seu projeto . Ou você pode especificar um caminho absoluto para o arquivo de log usando o initializeDataatributo.

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

Avisa que os atributos maxdatasizee tracemodenão são permitidos, mas aumentam a quantidade de dados que podem ser registrados e evitam registrar tudo em hexadecimal.

Collin Anderson
fonte
0

Você não especificou qual linguagem está usando, mas presumindo que C # / .NET você poderia usar extensões SOAP .

Caso contrário, use um sniffer como o Wireshark

rbrayb
fonte
1
Sim, tentei WireShark, ele só pode me mostrar as informações do cabeçalho, o conteúdo do sabão é criptografado.
Andrew Harry
Talvez porque você esteja usando https. As extensões SOAP, pelo que me lembro, funcionam em um nível superior, então você deve ser capaz de ver os dados depois que eles forem descriptografados.
rbrayb
2
Use o Fiddler em vez do Wireshark, ele descriptografa https pronto para uso.
Rodrigo Strauss
-1

Percebi que estou muito atrasado para a festa e, como o idioma não foi especificado, aqui está uma solução VB.NET baseada na resposta de Bimmerbound, caso alguém se depare com isso e precise de uma solução. Nota: você precisa ter uma referência à classe stringbuilder em seu projeto, se ainda não tiver.

 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

Simplesmente chame a função e ela retornará uma string com o xml serializado do objeto que você está tentando passar para o serviço da web (realisticamente, isso deve funcionar para qualquer objeto que você queira lançar nele também).

Como uma observação lateral, a chamada de substituição na função antes de retornar o xml é remover os caracteres vbCrLf da saída. O meu tinha um monte deles no xml gerado, no entanto, isso obviamente irá variar dependendo do que você está tentando serializar, e eu acho que eles podem ser removidos durante o envio do objeto para o serviço da web.

user2366842
fonte
Para quem me deu o voto negativo, sinta-se à vontade para me dizer o que estou perdendo / o que pode ser melhorado.
user2366842