Acquisizione di dati RAW Soap da un client di riferimento Web in esecuzione in ASP.net


95

Sto cercando di creare problemi a sparare a un client di un servizio web nel mio progetto attuale. Non sono sicuro della piattaforma del Service Server (molto probabilmente LAMP). Credo che ci sia un difetto dalla loro parte del recinto poiché ho eliminato i potenziali problemi con il mio cliente. Il client è un proxy di riferimento Web di tipo ASMX standard generato automaticamente dal servizio WSDL.

Quello di cui ho bisogno sono i messaggi RAW SOAP (richieste e risposte)

Qual è il modo migliore per farlo?

Risposte:


135

Ho apportato le seguenti modifiche web.configper ottenere la busta SOAP (richiesta / risposta). Questo produrrà tutte le informazioni SOAP non elaborate nel file 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>

2
Questo non funziona per me nel 02/2012 utilizzando VS 2010. Qualcuno conosce una soluzione più aggiornata?
qxotk

2
Questo è utile. Tuttavia, nel mio caso le risposte sono compressi con G ed estrarre i codici ASCII o esadecimali dall'output combinato è un problema. I loro metodi di output alternativi sono binari o solo di testo?

2
@Dediqated: è possibile impostare il percorso del file trace.log modificando il valore dell'attributo initializeData nell'esempio precedente.
DougCouto

3
Non serve più, ho usato WireShark per vedere i dati SOAP grezzi. Ma grazie comunque!
Dediqated

7
Buona. Ma l'XML per me è come: 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 "encoding =" ut ....... Qualche idea su come formattarlo o avere solo dati xml .?
Habeeb

35

È possibile implementare un SoapExtension che registra la richiesta completa e la risposta in un file di registro. È quindi possibile abilitare SoapExtension in web.config, che semplifica l'attivazione / disattivazione per scopi di debug. Ecco un esempio che ho trovato e modificato per mio uso personale, nel mio caso la registrazione è stata eseguita da log4net ma puoi sostituire i metodi di log con i tuoi.

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

Quindi aggiungi la seguente sezione al tuo web.config dove YourNamespace e YourAssembly puntano alla classe e all'assembly di SoapExtension:

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

Ci provo. Avevo esaminato le estensioni del sapone, ma mi sembrava che fosse più per ospitare il servizio. Ti darà il segno di spunta se funziona :)
Andrew Harry

Sì, questo funzionerà per registrare le chiamate effettuate dalla tua applicazione web tramite il proxy generato.
John Lemp

Questo è il percorso che ho seguito, funziona molto bene e posso usare xpath per mascherare qualsiasi dato sensibile prima di registrarlo.
Dave Baghdanov

Avevo bisogno di aggiungere intestazioni alla richiesta soap sul client. Questo mi ha aiutato. rhyous.com/2015/04/29/…
Rhyous

Sono anche nuovo in c # e non sono sicuro di cosa inserire per il nome dell'assembly. L'estensione non sembra funzionare.
Collin Anderson

28

Non so perché tutto questo trambusto con web.config o una classe serializzatore. Il codice seguente ha funzionato per me:

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

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

17
Da dove myEnvelopeviene?
ProfK

6
Non capisco perché questa risposta non abbia più voti positivi, dritto e dritto al punto! Molto bene!
Batista

1
myEnvalope sarebbe l'oggetto che stai inviando tramite il servizio web. Non sono riuscito a farlo funzionare come descritto (in particolare la funzione textWriter.tostring) ma ha funzionato abbastanza bene per i miei scopi in modalità debug in quanto puoi vedere il raw xml dopo aver chiamato serialize. Apprezzo molto questa risposta, poiché alcuni degli altri hanno lavorato con risultati contrastanti (ad esempio, l'accesso tramite web.config ha restituito solo una parte dell'xml e non è stato neanche molto facile da analizzare).
user2366842

@Bimmerbound: funzionerebbe con i messaggi soap sicuri inviati tramite una VPN crittografata?
Our Man in Bananas

6
Questa risposta non produce buste di sapone, si limita a serializzare in xml. Come ottenere la richiesta della busta del sapone?
Ali Karaca

21

Prova Fiddler2 che ti permetterà di esaminare le richieste e le risposte. Potrebbe valere la pena notare che Fiddler funziona sia con il traffico http che con il traffico https.


È un servizio crittografato https
Andrew Harry

8
Fiddler può decriptare il traffico https
Aaron Fischer

1
Ho trovato questa soluzione migliore rispetto all'aggiunta di tracciamento a web / app.config. Grazie!
andyuk

1
Ma l'utilizzo di system.diagnostics nel file di configurazione non richiede l'installazione di app di terze parti
Azat

1
@AaronFischer: Fiddler funzionerebbe con i messaggi soap inviati tramite una VPN crittografata?
Our Man in Bananas

6

Sembra che la soluzione di Tim Carter non funzioni se la chiamata al riferimento Web genera un'eccezione. Ho cercato di ottenere la risposta web non elaborata in modo da poterla esaminare (nel codice) nel gestore degli errori una volta generata l'eccezione. Tuttavia, sto riscontrando che il registro delle risposte scritto dal metodo di Tim è vuoto quando la chiamata genera un'eccezione. Non capisco completamente il codice, ma sembra che il metodo di Tim si inserisca nel processo dopo il punto in cui .Net ha già invalidato e scartato la risposta web.

Sto lavorando con un cliente che sta sviluppando manualmente un servizio web con codifica di basso livello. A questo punto, stanno aggiungendo i propri messaggi di errore di processo interno come messaggi formattati HTML nella risposta PRIMA della risposta formattata SOAP. Naturalmente, il riferimento web di automagic .Net esplode su questo. Se potessi ottenere la risposta HTTP non elaborata dopo che è stata generata un'eccezione, potrei cercare e analizzare qualsiasi risposta SOAP all'interno della risposta HTTP di ritorno mista e sapere che hanno ricevuto i miei dati OK o meno.

Dopo ...

Ecco una soluzione che funziona, anche dopo un'esecuzione (nota che sto solo dopo la risposta - potrei anche ottenere la richiesta):

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

Ecco come configurarlo nel file di configurazione:

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

"TestCallWebService" dovrebbe essere sostituito con il nome della libreria (che era il nome dell'app della console di test su cui stavo lavorando).

Non dovresti davvero andare su ChainStream; dovresti essere in grado di farlo più semplicemente da ProcessMessage come:

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 cerchi SoapMessage.Stream, dovrebbe essere un flusso di sola lettura che puoi usare per ispezionare i dati a questo punto. Questo è un errore perché se leggi il flusso, le bombe di elaborazione successive senza dati trovati errori (il flusso era alla fine) e non puoi ripristinare la posizione all'inizio.

È interessante notare che, se si utilizzano entrambi i metodi, ChainStream e ProcessMessage, il metodo ProcessMessage funzionerà perché è stato modificato il tipo di flusso da ConnectStream a MemoryStream in ChainStream e MemoryStream consente le operazioni di ricerca. (Ho provato a trasmettere ConnectStream a MemoryStream - non era consentito.)

Quindi ..... Microsoft dovrebbe consentire le operazioni di ricerca sul tipo ChainStream o rendere SoapMessage.Stream veramente una copia di sola lettura come dovrebbe essere. (Scrivi il tuo membro del Congresso, ecc ...)

Un ulteriore punto. Dopo aver creato un modo per recuperare la risposta HTTP non elaborata dopo un'eccezione, non ho ancora ricevuto la risposta completa (come determinato da uno sniffer HTTP). Questo perché quando il servizio Web di sviluppo ha aggiunto i messaggi di errore HTML all'inizio della risposta, non ha modificato l'intestazione Content-Length, quindi il valore Content-Length era inferiore alla dimensione del corpo della risposta effettiva. Tutto quello che ho ottenuto è stato il numero di caratteri del valore Content-Length - il resto mancava. Ovviamente, quando .Net legge il flusso di risposta, legge solo il numero di caratteri Content-Length e non consente che il valore Content-Length sia errato. Questo è come dovrebbe essere; ma se il valore dell'intestazione Content-Length è sbagliato, l'unico modo per ottenere l'intero corpo della risposta è con uno sniffer HTTP (io utilizzo HTTP Analyzer dahttp://www.ieinspector.com ).


2

Preferirei che il framework eseguisse la registrazione per te collegandosi a un flusso di registrazione che registra come il framework elabora quel flusso sottostante. Quanto segue non è così pulito come vorrei, poiché non è possibile decidere tra richiesta e risposta nel metodo ChainStream. Quello che segue è come lo gestisco. Ringraziamo Jon Hanna per l'idea principale di un flusso

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

1
@TimCarter ha funzionato per me, l'unica cosa che ho cancellato è la parte client, che mi dà un nome con i due punti, causando un'eccezione. Quindi, una volta eliminata questa riga, funziona bene per coloro che vogliono avere un file per ogni richiesta / risposta. L'unica cosa da aggiungere è qualcosa di ovvio se leggi le altre risposte, ed è che devi aggiungere il nodo web.config per indicare soapExtension. Grazie!
netadictos

1

Ecco una versione semplificata della risposta principale. Aggiungilo <configuration>all'elemento del tuo file web.configo App.config. Creerà un trace.logfile nella bin/Debugcartella del progetto . In alternativa, è possibile specificare un percorso assoluto per il file di registro utilizzando l' initializeDataattributo.

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

Avverte che gli attributi maxdatasizee tracemodenon sono consentiti, ma aumentano la quantità di dati che possono essere registrati ed evitano di registrare tutto in esadecimale.


0

Non hai specificato quale lingua stai usando, ma assumendo C # / .NET potresti usare estensioni SOAP .

Altrimenti, usa uno sniffer come Wireshark


1
Sì, ho provato WireShark, può mostrarmi solo le informazioni di intestazione, il contenuto del sapone è crittografato.
Andrew Harry

Forse perché stai usando https. Le estensioni SOAP per quanto posso ricordare funzionano a un livello superiore, quindi dovresti essere in grado di vedere i dati dopo che sono stati decrittografati.
rbrayb

2
Usa Fiddler invece di Wireshark, decrittografa https fuori dalla scatola.
Rodrigo Strauss

-1

Mi rendo conto di essere abbastanza in ritardo per la festa, e poiché la lingua non è stata effettivamente specificata, ecco una soluzione VB.NET basata sulla risposta di Bimmerbound, nel caso in cui qualcuno si imbattesse in questo e abbia bisogno di una soluzione. Nota: devi avere un riferimento alla classe stringbuilder nel tuo progetto, se non lo hai già fatto.

 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

Chiama semplicemente la funzione e restituirà una stringa con l'xml serializzato dell'oggetto che stai tentando di passare al servizio web (realisticamente, questo dovrebbe funzionare anche per qualsiasi oggetto che ti interessa lanciare su di esso).

Come nota a margine, la chiamata di sostituzione nella funzione prima di restituire l'xml consiste nell'eliminare i caratteri vbCrLf dall'output. Il mio ne aveva molti all'interno dell'xml generato, tuttavia questo ovviamente varierà a seconda di ciò che stai cercando di serializzare e penso che potrebbero essere eliminati durante l'invio dell'oggetto al servizio web.


A chi mi ha schiaffeggiato con il voto negativo, non esitare a farmi sapere cosa mi manca / cosa può essere migliorato.
user2366842
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.