ASP.netで実行されているWeb参照クライアントからRAW Soapデータを取得する


95

現在のプロジェクトでWebサービスクライアントをトラブルシューティングしようとしています。サービスサーバーのプラットフォーム(おそらくLAMP)がわかりません。私はクライアントの潜在的な問題を排除したので、フェンスの側に欠陥があると思います。クライアントは、サービスWSDLから自動生成された標準ASMXタイプのWeb参照プロキシです。

取得する必要があるのは、RAW SOAPメッセージ(リクエストとレスポンス)です。

これについて最善の方法は何ですか?

回答:


135

web.configSOAP(リクエスト/レスポンス)エンベロープを取得するために、次の変更を行いました。これにより、すべての未加工のSOAP情報がファイルに出力されます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
これは、VS 2010を使用して2012年2月に私には機能しません。誰かがより最新のソリューションを知っていますか?
qxotk

2
これは役に立ちます。ただし、私の場合、応答はGzip圧縮されており、ASCIIコードまたは16進コードを組み合わせた出力から取り出すのは面倒です。バイナリまたはテキストのみの代替出力方法ですか?

2
@Dediqated-上記の例のinitializeData属性の値を調整することにより、trace.logファイルへのパスを設定できます。
DougCouto、2013年

3
それはもう役に立たない、私は生のSOAPデータを表示するためにWiresharkを使用しました。とにかくありがとう!
2013年

7
良い。しかし、私にとってのXMLは次のようなものです: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 .......それをフォーマットする方法、またはxmlデータのみを持つ方法。?
Habeeb 2015年

35

リクエストとレスポンス全体をログファイルに記録するSoapExtensionを実装できます。次に、web.configでSoapExtensionを有効にすることができます。これにより、デバッグの目的で簡単にオン/オフを切り替えることができます。これは私が自分で使用するために見つけて変更した例です。私の場合、ロギングはlog4netによって行われましたが、ログメソッドを独自のものに置き換えることができます。

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

次に、次のセクションをweb.configに追加します。ここで、YourNamespaceおよびYourAssemblyは、SoapExtensionのクラスとアセンブリをポイントします。

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

やってみます。私は石鹸の拡張を調べましたが、サービスをホストするためのものであるように見えました。それがうまくいけばダニを与えるでしょう:)
Andrew Harry

はい、これは、生成されたプロキシを介してWebアプリケーションから行われた呼び出しをログに記録するために機能します。
John Lemp

これは私が使ったルートで、非常にうまく機能しています。xpathを使用して、ログに記録する前に機密データをマスクできます。
デイブバグダノフ2014年

クライアントのSOAPリクエストにヘッダーを追加する必要がありました。これは私を助けました。rhyous.com/2015/04/29/...
Rhyous

また、c#は初めてなので、アセンブリ名に何を付けるかわかりません。拡張機能が実行されていないようです。
コリンアンダーソン

28

なぜweb.configやシリアライザクラスのすべての大騒ぎがわからない。以下のコードは私のために働きました:

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

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

17
どこmyEnvelopeから来たの?
ProfK 2015年

6
なぜこの回答に正反対の賛成票がないのかわかりません!よくやった!
バティスタ

1
myEnvalopeは、Webサービスを介して送信するオブジェクトです。説明したとおりにこれを機能させることはできませんでしたが(特にtextWriter.tostring関数)、serializeを呼び出した後、生のxmlを表示できるという点で、デバッグモードでは十分に機能しました。他のいくつかは混合結果を処理したため、この回答を非常に高く評価します(たとえば、web.configを使用したロギングでは、xmlの一部のみが返され、解析も非常に簡単ではありませんでした)。
user2366842 2015年

@Bimmerbound:これは、暗号化されたVPNを介して送信される安全なSOAPメッセージで機能しますか?
バナナの私たちの男

6
この答えは石鹸のエンベロープを生成せず、XMLにシリアル化するだけです。石鹸の封筒のリクエストを取得するには?
アリカラカ

21

試してみてくださいFiddler2をし、それはあなたが要求と応答を検査できるようになります。Fiddlerがhttpトラフィックとhttpsトラフィックの両方で動作することは注目に値するかもしれません。


これはhttps暗号化サービスです
Andrew Harry、

8
Fiddlerはhttpsトラフィックの暗号化
Aaron Fischer

1
このソリューションは、web / app.configにトレースを追加するよりも優れていることがわかりました。ありがとう!
アンディク

1
ただし、構成ファイルでsystem.diagnosticsを使用する場合、サードパーティのアプリをインストールする必要はありません
Azat

1
@AaronFischer:Fiddlerは、暗号化されたVPNを介して送信されるSOAPメッセージを処理しますか?
バナナの私たちの男

6

Web参照への呼び出しが例外をスローした場合、Tim Carterのソリューションは機能しないようです。例外がスローされたら、エラーハンドラーでそれを(コードで)確認できるように、未加工のWeb応答を取得しようとしています。ただし、呼び出しが例外をスローすると、Timのメソッドによって書き込まれた応答ログが空白になることがわかりました。私はコードを完全には理解していませんが、.NetがすでにWeb応答を無効にして破棄した後、Timのメソッドがプロセスに割り込んでいるようです。

低レベルのコーディングを使用して手動でWebサービスを開発しているクライアントと協力しています。この時点で、SOAP形式の応答の前に、独自の内部プロセスエラーメッセージをHTML形式のメッセージとして応答に追加しています。もちろん、automagic .Net Webリファレンスはこれを爆破します。例外がスローされた後、生のHTTP応答を取得できた場合、混合戻りHTTP応答内のSOAP応答を探して解析し、データを正常に受信したかどうかを確認できます。

後で...

実行後でも機能する解決策を次に示します(私は応答の後でしかないことに注意してください-要求も取得できます)。

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

設定ファイルで設定する方法は次のとおりです。

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

「TestCallWebService」は、ライブラリの名前(たまたま、私が作業していたテストコンソールアプリの名前)に置き換える必要があります。

ChainStreamに行く必要はありません。あなたは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
    }
}

SoapMessage.Streamを検索すると、この時点でデータを検査するために使用できる読み取り専用ストリームであると想定されます。これはねじ込み式のエラーです。これは、ストリームを読み取った場合、データが見つからない後続の爆弾の処理エラー(ストリームが最後にあった)であり、位置を最初にリセットできないためです。

興味深いことに、ChainStreamとProcessMessageの両方の方法を実行すると、ChainStreamでストリームタイプをConnectStreamからMemoryStreamに変更し、MemoryStreamではシーク操作が許可されるため、ProcessMessageメソッドが機能します。(ConnectStreamをMemoryStreamにキャストしようとしましたが、許可されませんでした。)

したがって、Microsoftは、ChainStreamタイプでのシーク操作を許可するか、SoapMessage.Streamを本来の読み取り専用コピーにする必要があります。(下院議員などを書きなさい...)

もう1点。例外の後で未加工のHTTP応答を取得する方法を作成した後も、(HTTPスニファによって判断されるように)完全な応答が得られませんでした。これは、開発Webサービスが応答の先頭にHTMLエラーメッセージを追加したときに、Content-Lengthヘッダーを調整しなかったため、Content-Length値が実際の応答本文のサイズよりも小さかったためです。私が得たのは、Content-Length値の文字数だけでした-残りはありませんでした。明らかに、.Netが応答ストリームを読み取るときは、Content-Lengthの文字数を読み取るだけで、おそらくContent-Lengthの値が間違っていることは許されません。これはあるべき姿です。ただし、Content-Lengthヘッダー値が間違っている場合、応答本文全体を取得する唯一の方法は、HTTPスニッファー(http://www.ieinspector.com)。


2

フレームワークがその基になるストリームを処理するときにログを記録するロギングストリームをフックすることで、フレームワークにロギングを実行させたいと思います。次のコードは、ChainStreamメソッドで要求と応答のどちらを行うかを決定できないため、希望するほどきれいではありません。以下はその扱い方です。ストリームのアイデアを上書きしてくれたJon Hannaに感謝

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それは私に働きました、私が削除した唯一のものはクライアント部分です、それは私にコロン付きの名前を与え、例外を引き起こします。したがって、この行を削除すると、要求/応答ごとに1つのファイルが必要な場合にうまく機能します。追加する唯一のものは、他の回答を読んだ場合に明らかなものであり、web.configノードを追加してsoapExtensionを示す必要があるということです。どうも!
netadictos 2017

1

以下は、トップアンサーの簡略版です。これをまたはファイルの<configuration>要素に追加します。プロジェクトのフォルダにファイルが作成されます。または、属性を使用してログファイルの絶対パスを指定できます。web.configApp.configtrace.logbin/DebuginitializeData

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

maxdatasizeおよびtracemode属性が許可されていないことを警告しますが、これらはログに記録できるデータの量を増やし、16進数ですべてをログに記録することを避けます。


0

使用している言語を指定していませんが、C#/。NETを想定してSOAP拡張機能を使用できます。

それ以外の場合は、Wiresharkなどのスニファを使用します


1
ええ、wiresharkを試してみました。ヘッダー情報のみが表示され、石鹸のコンテンツは暗号化されています。
Andrew Harry、

それはおそらくあなたがhttpsを使用しているためです。私が覚えている限り、SOAP拡張機能はより高いレベルで機能するため、データが復号化された後にデータを表示できます。
rbrayb 2008年

2
Wiresharkの代わりにFiddlerを使用すると、すぐにhttpsが復号化されます。
Rodrigo Strauss

-1

私はパーティーにかなり遅れていることを理解しています。言語は実際には指定されていなかったため、誰かが偶然これに遭遇して解決策が必要な場合に備えて、Bimmerboundの回答に基づくVB.NETソリューションを示します。注:プロジェクトにstringbuilderクラスへの参照がまだない場合は、これを参照する必要があります。

 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

関数を呼び出すだけで、Webサービスに渡そうとしているオブジェクトのシリアル化されたxmlを含む文字列が返されます(現実的には、これはスローしたいオブジェクトに対しても機能するはずです)。

補足として、xmlを返す前の関数でのreplace呼び出しは、出力からvbCrLf文字を取り除くことです。私は生成されたxml内にそれらをたくさん持っていましたが、これは明らかにシリアル化しようとしているものによって異なり、オブジェクトがWebサービスに送信されている間に削除される可能性があると思います。


反対投票で私を平手打ちした人には、何が足りないか、何を改善できるかを遠慮なく知らせてください。
user2366842 2018
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.