98 votes

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

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

37voto

duckworth Points 3156

Vous pouvez mettre en œuvre un SoapExtension qui enregistre le montant total de la demande et de la réponse dans un fichier journal. Vous pouvez ensuite activer le SoapExtension dans le web.config, ce qui le rend facile à activer/désactiver à des fins de débogage. Voici un exemple que j'ai trouvé et modifié pour mon propre usage, dans mon cas, l'enregistrement a été fait par log4net, mais vous pouvez remplacer les méthodes du journal avec votre propre.

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

Ensuite, vous ajoutez la section suivante à votre site web.config où YourNamespace et YourAssembly point à la classe et l'assemblage de votre SoapExtension:

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

22voto

Aaron Fischer Points 8919

Essayez Fiddler2, il vous permettra d’examiner les demandes et les réponses. Il est intéressant de noter que Fiddler fonctionne avec le trafic http et https.

6voto

Chuck Bevitt Points 91

Il ressemble à Tim Carter solution ne fonctionne pas si l'appel à la référence web lance une exception. J'ai essayé d'obtenir à la crue web resonse afin que je puisse l'examiner (dans le code) dans le gestionnaire d'erreur une fois que l'exception est levée. Cependant, je trouve que la réponse du journal écrit par Tim méthode est vide lors de l'appel déclenche une exception. Je ne comprends pas complètement le code, mais il semble que Tim méthode des coupes dans le processus après le point où .Net a déjà invalidé et mis au rebut de la réponse web.

Je suis en train de travailler avec un client qui est à l'élaboration d'un web service manuellement avec un faible niveau de codage. À ce moment, ils sont en ajoutant à leur propre processus interne messages d'erreur les messages au format HTML dans la réponse AVANT le SAVON réponse au format. Bien sûr, la automagique .Net web de référence souffle sur ce sujet. Si j'ai pu obtenir lors de la crue de la réponse HTTP, après une exception est levée, je pouvais regarder et analyser n'importe quel SAVON réponse dans le mixte de retour HTTP de la réponse et de savoir qu'ils ont reçu mes données sur OK ou pas.

Plus tard ...

Voici une solution qui ne fonctionne pas, même après une execption (notez que je suis seulement après la réponse pourrait obtenir de 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 le configurer dans le fichier de config:

<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 se trouvait être le nom de la console d'essai app je travaillais dans).

Vous ne devriez vraiment pas avoir à aller à l'ChainStream; vous devriez être en mesure de le faire plus simplement de ProcessMessage:

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.Cours d'eau, il est censé être une lecture seulement les flux que vous pouvez utiliser pour inspecter les données à ce point. C'est une vis en place parce que si vous le faites lire le flux, le traitement ultérieur des bombes avec pas de données disponibles erreurs (ruisseau était à la fin) et vous ne pouvez pas réinitialiser la position de début.

Fait intéressant, si vous faites les deux méthodes, la ChainStream et la ProcessMessage façons, la ProcessMessage méthode fonctionne parce que vous avez changé le type de flux de ConnectStream de MemoryStream dans ChainStream, et MemoryStream ne permettre les opérations de recherche. (J'ai essayé le casting du ConnectStream de MemoryStream - n'était pas le permettent.)

Donc ..... Microsoft devrait autoriser les opérations de recherche sur le ChainStream type ou faire de la SoapMessage.Stream vraiment une copie en lecture seule car il est censé être. (Écrire à votre député, etc...)

Un autre point. Après la création d'un moyen de récupérer les premières HTTP de la réponse après qu'une exception, je n'ai toujours pas la réponse complète (tel que déterminé par un HTTP sniffer). C'est parce que lorsque le développement de service web ajouté le code HTML des messages d'erreur au début de la réponse, il n'a pas d'ajuster le Contenu de l'en-tête de Longueur, de sorte que la Longueur du Contenu de valeur a été inférieure à la taille de la réponse réelle du corps. Tout ce que j'ai est la Longueur du Contenu de la valeur nombre de caractères - les autres ont été portées disparues. Évidemment, quand .Net lit le flux de réponse, il se contente de lire le Contenu de Longueur nombre de caractères et ne permet pas de la Longueur du Contenu de valeur éventuellement être trompé. C'est comme il devrait l'être; mais si le Contenu de l'en-tête de Longueur valeur est incorrecte, le seul moyen que vous aurez jamais obtenir l'ensemble du corps de la réponse est avec un HTTP sniffer (j'ai d'utilisateur HTTP Analyseur de http://www.ieinspector.com).

0voto

nzpcmad Points 15270

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

Sinon, utilisez un renifleur tel que Wireshark

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X