Domanda

Sto cercando di risolvere un problema con un client di servizi web nel mio progetto attuale. Non sono sicuro della piattaforma del Service Server (molto probabilmente LAMP). Credo che ci sia un errore dalla loro parte della barriera in quanto 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.

Ciò di cui ho bisogno sono i messaggi SOW RAW (richiesta e risposte)

Qual è il modo migliore per farlo?

È stato utile?

Soluzione

Ho apportato le seguenti modifiche in web.config per ottenere la busta SOAP (Request / Response). Ciò 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>

Altri suggerimenti

È 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 a scopi di debug. Ecco un esempio che ho trovato e modificato per uso personale, nel mio caso la registrazione è stata effettuata da log4net ma è possibile sostituire i metodi di registro con i propri.

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 in cui YourNamespace e YourAssembly puntano alla classe e all'assembly di SoapExtension:

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

Non so perché tutto il clamore 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();
}

Prova Fiddler2 che ti permetterà di ispezionare le richieste e la risposta. Vale la pena notare che Fiddler funziona sia con il traffico http che https.

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 scoprendo 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 abbia interrotto il processo dopo il punto in cui .Net ha già invalidato e scartato la risposta web.

Sto lavorando con un client che sta sviluppando un servizio Web manualmente con codifica di basso livello. A questo punto, stanno aggiungendo i propri messaggi di errore di processo interni come messaggi in formato HTML nella risposta PRIMA della risposta in formato SOAP. Naturalmente, il riferimento web 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.

Più tardi ...

Ecco una soluzione che funziona, anche dopo un'esecuzione (nota che sono solo dopo la risposta - potrebbe 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 lo stream, le successive bombe di elaborazione senza dati rilevano errori (il flusso era alla fine) e non puoi ripristinare la posizione all'inizio.

È interessante notare che se si eseguono 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 operazioni di ricerca. (Ho provato a trasmettere ConnectStream a MemoryStream - non era permesso.)

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

Un altro punto. Dopo aver creato un modo per recuperare la risposta HTTP non elaborata dopo un'eccezione, non ho ancora ottenuto 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 regolato 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 in lunghezza contenuto - il resto mancava. Ovviamente, quando .Net legge il flusso di risposta, legge semplicemente il numero di caratteri Lunghezza contenuto e non consente che il valore Lunghezza contenuto sia errato. Questo è come dovrebbe essere; ma se il valore dell'intestazione Content-Length è errato, l'unico modo per ottenere l'intero corpo della risposta è con uno sniffer HTTP (utilizzo HTTP Analyzer da http://www.ieinspector.com ).

Preferirei che il framework eseguisse la registrazione per te collegandosi a un flusso di registrazione che registra mentre il framework elabora quel flusso sottostante. Quanto segue non è pulito come vorrei, dal momento che non è possibile decidere tra richiesta e risposta nel metodo ChainStream. Di seguito è come lo gestisco. Grazie a Jon Hanna per aver ignorato l'idea di streaming

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

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

Altrimenti, utilizza uno sniffer come Wireshark

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 imbatta in questo e abbia bisogno di una soluzione. Nota: se non lo fai già devi avere un riferimento alla classe stringbuilder nel tuo progetto.

 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

Basta chiamare la funzione e restituirà una stringa con l'xml serializzato dell'oggetto che si sta tentando di passare al servizio Web (realisticamente, questo dovrebbe funzionare anche per qualsiasi oggetto a cui si desidera lanciarlo).

Come nota a margine, la chiamata di sostituzione nella funzione prima di restituire l'xml è di eliminare i caratteri vbCrLf dall'output. I miei ne avevano un sacco all'interno dell'xml generato, tuttavia questo ovviamente varierà a seconda di ciò che stai cercando di serializzare, e penso che potrebbero essere rimossi durante l'oggetto inviato al servizio web.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top