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

StackOverflow https://stackoverflow.com/questions/300674

Question

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 qu'il me faut, ce sont les messages RAW SOAP (demande et réponses)

Quelle est la meilleure façon de s'y prendre?

Était-ce utile?

La solution

J'ai apporté les modifications suivantes dans web.config pour obtenir l'enveloppe SOAP (demande / réponse). Cela va générer toutes les informations brutes SOAP 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>

Autres conseils

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>

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

Essayez Fiddler2 pour vous permettre d'inspecter les demandes et les réponses. Il convient de noter que Fiddler fonctionne avec les trafics http et https.

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à été invalidé et a ignoré la réponse Web.

Je travaille avec un client qui développe un service Web manuellement avec un codage de bas niveau. À ce stade, ils ajoutent leurs propres messages d'erreur de processus internes en tant que messages au format HTML dans la réponse AVANT la réponse au format 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 pourrais rechercher et analyser toute réponse SOAP dans la réponse HTTP renvoyée mixte et savoir qu’ils ont reçu mes données correctement ou non.

Plus tard ...

Voici une solution qui fonctionne, même après une exception (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 s’agit d’un flux en lecture seule que vous pouvez utiliser pour inspecter les données à ce stade. C’est une erreur, car si vous lisez le flux, le traitement ultérieur des bombes ne contenant aucune erreur de données (le flux était à la fin) et que vous ne pouvez pas rétablir la position au début.

Fait intéressant, si vous utilisez 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é.)

Alors ..... 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 obtenu 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 est avec un renifleur HTTP (l'utilisateur HTTP Analyzer de http://www.ieinspector.com ).

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 traitant de ce flux sous-jacent. 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);
        }
    }
}

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

Sinon, utilisez un renifleur tel que Wireshark

Je me rends compte que je suis assez tard pour la fête, et puisque le langage n’a pas été précisé, voici une solution VB.NET basée sur la réponse de Bimmerbound, au cas où quelqu'un tomberait par hasard dessus et aurait besoin d’une solution. Remarque: vous devez avoir une référence à la classe stringbuilder dans votre projet, si ce n'est déjà fait.

 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

Appelez simplement la fonction et elle renverra une chaîne contenant le xml sérialisé de l'objet que vous tentez de transmettre au service Web (de manière réaliste, cela devrait fonctionner pour tout objet que vous souhaitez également lui envoyer).

En remarque, l’appel de remplacement dans la fonction avant de renvoyer le fichier XML consiste à supprimer les caractères vbCrLf de la sortie. Le mien en contenait plusieurs dans le xml généré, mais cela va évidemment varier en fonction de ce que vous essayez de sérialiser. Je pense qu'ils risquent d'être supprimés lors de l'envoi de l'objet au service Web.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top