現在のプロジェクトで Web サービス クライアントのトラブルシューティングを試みています。Service Server のプラットフォームがわかりません (おそらく LAMP)。クライアントとの潜在的な問題を排除したため、フェンスの側に障害があると思います. クライアントは、サービス WSDL から自動生成された標準 ASMX タイプの Web 参照プロキシです。
私が取得する必要があるのは、RAW SOAP メッセージ (要求と応答) です。
これについて最善の方法は何ですか?
現在のプロジェクトで Web サービス クライアントのトラブルシューティングを試みています。Service Server のプラットフォームがわかりません (おそらく LAMP)。クライアントとの潜在的な問題を排除したため、フェンスの側に障害があると思います. クライアントは、サービス WSDL から自動生成された標準 ASMX タイプの Web 参照プロキシです。
私が取得する必要があるのは、RAW SOAP メッセージ (要求と応答) です。
これについて最善の方法は何ですか?
web.config
SOAP (要求/応答) エンベロープを取得するために、次の変更を行いました。これにより、生の 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>
完全な要求と応答をログ ファイルに記録する 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>
なぜ web.config やシリアライザー クラスに大騒ぎするのかわかりません。以下のコードは私のために働いた:
XmlSerializer xmlSerializer = new XmlSerializer(myEnvelope.GetType());
using (StringWriter textWriter = new StringWriter())
{
xmlSerializer.Serialize(textWriter, myEnvelope);
return textWriter.ToString();
}
Fiddler2を試してみると、リクエストとレスポンスを検査できます。Fiddler は http と https の両方のトラフィックで動作することに注意してください。
Web 参照への呼び出しで例外がスローされた場合、Tim Carter のソリューションは機能しないようです。例外がスローされたら、エラーハンドラーで(コードで)調べることができるように、生のWeb応答を取得しようとしています。ただし、呼び出しが例外をスローしたときに、Tim のメソッドによって書き込まれた応答ログが空白になっていることがわかりました。私はコードを完全には理解していませんが、Tim の方法は、.Net が Web 応答を無効にして破棄した時点以降のプロセスに割り込んでいるようです。
低レベルのコーディングを使用して手動で 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 の両方の方法を実行すると、ProcessMessage メソッドが機能します。これは、ChainStream でストリーム タイプを ConnectStream から MemoryStream に変更したためであり、MemoryStream ではシーク操作が可能です。(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 アナライザーを使用していますhttp://www.ieinspector.com )。
使用している言語を指定していませんが、C#/.NET を想定してSOAP 拡張機能を使用できます。
それ以外の場合は、 Wiresharkなどのスニファを使用します
私はパーティーにかなり遅れていることに気付きました.言語が実際に指定されていなかったので、誰かがたまたまこれに出くわして解決策が必要な場合に備えて、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 を返す前の関数内の置換呼び出しは、出力から vbCrLf 文字を削除することです。私は生成されたxml内にそれらの束を持っていましたが、これはシリアライズしようとしているものによって明らかに異なり、オブジェクトがWebサービスに送信されている間に削除される可能性があると思います.