web-dev-qa-db-fra.com

Obtention des données RAW Soap à partir d'un client de référence Web s'exécutant dans ASP.net

J'essaie de dépanner un client de service Web dans mon projet actuel. Je ne suis pas sûr de la plate-forme du serveur de service (très probablement LAMP). Je crois qu'il y a une faute de leur côté de la clôture car j'ai éliminé les problèmes potentiels avec mon client. Le client est un proxy de référence Web de type ASMX standard généré automatiquement à partir du service WSDL.

Ce que je dois obtenir, c’est le RAW SOAP Messages (demande et réponses)

Quelle est la meilleure manière de s'occuper de ça?

90
Andrew Harry

J'ai apporté les modifications suivantes dans web.config pour obtenir l’enveloppe SOAP (demande/réponse)]. Toutes les informations brutes SOAP) seront exportées dans le fichier 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>
127
Nadeem Abbasi

Vous pouvez implémenter un SoapExtension qui enregistre la demande complète et la réponse dans un fichier journal. Vous pouvez ensuite activer SoapExtension dans le fichier web.config, ce qui facilite l'activation/la désactivation à des fins de débogage. Voici un exemple que j'ai trouvé et modifié pour mon usage personnel. Dans mon cas, la journalisation a été effectuée par log4net, mais vous pouvez remplacer les méthodes de journalisation par les vôtres.

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

Vous ajoutez ensuite la section suivante à votre web.config où YourNamespace et YourAssembly pointent vers la classe et l'assembly de votre SoapExtension:

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

Vous ne savez pas pourquoi tout le monde s'inquiète avec web.config ou une classe de sérialiseur. Le code ci-dessous a fonctionné pour moi:

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

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

Essayez Fiddler2 Il vous permettra d’examiner les demandes et les réponses. Il convient de noter que Fiddler fonctionne avec les trafics http et https.

21
Aaron Fischer

Il semble que la solution de Tim Carter ne fonctionne pas si l'appel à la référence Web lève une exception. J'ai essayé d'obtenir la réponse Web brute afin de pouvoir l'examiner (dans le code) dans le gestionnaire d'erreurs une fois que l'exception est levée. Cependant, je constate que le journal de réponse écrit par la méthode de Tim est vide lorsque l'appel lève une exception. Je ne comprends pas tout à fait le code, mais il semble que la méthode de Tim coupe le processus après le point où .Net a déjà invalidé et ignoré la réponse Web.

Je travaille avec un client qui développe manuellement un service Web avec un codage de bas niveau. À ce stade, ils ajoutent leurs propres messages d'erreur de processus internes sous forme de messages au format HTML dans la réponse AVANT la réponse formatée SOAP. Bien entendu, la référence Web .Net automagic explose à cet égard. Si Je pouvais obtenir la réponse HTTP brute après la levée d’une exception. Je pouvais rechercher et analyser toute réponse SOAP dans la réponse HTTP renvoyée mélangée et savoir qu’ils avaient reçu mes données correctement ou non.

Plus tard ...

Voici une solution qui fonctionne, même après une exécution (notez que je ne suis qu'après la réponse - pourrait également recevoir la demande):

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

Voici comment vous le configurez dans le fichier de configuration:

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

"TestCallWebService" devrait être remplacé par le nom de la bibliothèque (qui s’est avéré être le nom de l’application de console de test dans laquelle je travaillais).

Vous ne devriez vraiment pas avoir à aller à ChainStream; vous devriez pouvoir le faire plus simplement à partir de ProcessMessage en tant que:

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 vous recherchez SoapMessage.Stream, il est supposé être un flux en lecture seule que vous pouvez utiliser pour inspecter les données à ce stade. Il s'agit d'une erreur, car si vous lisez le flux, le traitement ultérieur des bombes sans erreur de données trouvées (le flux était à la fin) et vous ne pouvez pas rétablir la position au début.

Fait intéressant, si vous appliquez les deux méthodes, les méthodes ChainStream et ProcessMessage, la méthode ProcessMessage fonctionnera, car vous avez modifié le type de flux ConnectStream en MemoryStream dans ChainStream, et MemoryStream autorise les opérations de recherche. (J'ai essayé de convertir ConnectStream sur MemoryStream - ce n'était pas autorisé.)

Donc ..... Microsoft doit autoriser les opérations de recherche sur le type ChainStream ou faire de SoapMessage.Stream une copie en lecture seule comme elle est supposée l'être. (Écrivez votre membre du Congrès, etc ...)

Un autre point. Après avoir créé un moyen de récupérer la réponse HTTP brute après une exception, je n’ai toujours pas reçu la réponse complète (comme déterminé par un renifleur HTTP). En effet, lorsque le service Web de développement a ajouté les messages d'erreur HTML au début de la réponse, il n'a pas ajusté l'en-tête Content-Length. La valeur Content-Length était donc inférieure à la taille du corps de la réponse. Tout ce que j'ai, c'est le nombre de caractères de la longueur du contenu - le reste était manquant. Évidemment, lorsque .Net lit le flux de réponse, il lit simplement le nombre de caractères Content-Length et ne permet pas que la valeur Content-Length soit éventuellement fausse. C'est comme il se doit; mais si la valeur de l'en-tête Content-Length est incorrecte, le seul moyen d'obtenir le corps de la réponse complet consiste à utiliser un renifleur HTTP (l'utilisateur HTTP Analyzer de http://www.ieinspector.com ).

6
Chuck Bevitt

Je préférerais que la structure se charge de la journalisation en vous connectant à un flux de journalisation qui se connecte en tant que processus de la structure sous-jacente. Ce qui suit n’est pas aussi clair que je l’aimerais, car vous ne pouvez pas choisir entre requête et réponse dans la méthode ChainStream. Voici comment je le gère. Merci à Jon Hanna pour l'idée dominante d'un flux

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

Vous n'avez pas spécifié la langue que vous utilisez, mais en supposant que vous puissiez utiliser C #/.NET extensions SOAP .

Sinon, utilisez un renifleur tel que Wireshark

0
nzpcmad