查找 Web 服务中 SOAP 消息的大小






4.64/5 (6投票s)
一个简单的 SoapExtension,用于检查进出您 Web 服务的 SOAP 消息。
引言
这是一篇简短、简单的文章,介绍了 SOAP 标头和 SOAP 扩展。
代码在 SOAP 消息通过 Web 服务时拦截它们,并将它们的大小作为 SOAP 标头提供给您的服务和客户端。该代码还可以将消息保存到 Web 服务器上的 XML 文件中,因为在编写 Web 服务时,查看正在传输的实际 SOAP 消息通常很有用。
这并不是什么高深的学问,但我发现它非常有价值。
使用代码
您可能知道,每个 SOAP 信封包含两部分:标头和正文。此代码的目的是用 SOAP 消息的大小填充 SOAP 标头,以便在您的 Web 服务内部可以访问它。这是通过 SoapExtension
完成的。
此扩展还会出于调试目的将 SOAP 消息保存到 Web 服务器上的 XML 文件中,但此功能仅在 DEBUG
版本中启用。它将输入的和输出的 SOAP 保存到 Web 应用程序文件夹的“Soap”子目录中的文件中,因此如果您使用此功能,请确保此目录存在并且 ASPNET 用户对其具有写入访问权限。
SOAP 标头
首先,我们需要一个标头类来保存大小信息。声明一个新的 SOAP 标头很简单
public class SoapSizeHeader : SoapHeader
{
private int _Size = 0;
public int SoapSizeHeaderSize
{
get { return _Size; }
set { _Size = value; }
}
}
接下来,我们需要将此标头添加到您的 WebService
。您可以让您的 WebService
从以下类派生,或者直接添加字段和属性
public class SoapSizeService : WebService
{
private SoapSizeHeader _SoapSizeHeader = new SoapSizeHeader();
public SoapSizeHeader SoapSizeHeader
{
get { return _SoapSizeHeader; }
set { _SoapSizeHeader = value; }
}
}
属性
主代码将包含在一个名为 SoapSizeExtension
的类中,该类从 SoapExtension
派生。在此之前,我将向您展示如何从您的 WebService
类挂钩新的标头和扩展。
要将您的标头添加到传递的 SOAP 中,您需要在 WebService
类中的 Web 方法上添加一个 SoapHeaderAttribute
。您还可以使用一个属性来添加 SOAP 扩展
partial class MyWebService : SoapSizeService
{
[WebMethod]
[SoapHeader( "SoapSizeHeader",
Direction=SoapHeaderDirection.InOut )]
[SoapSizeExtension]
public string HelloWorld()
{
return "Hello, world";
}
}
SoapSizeExtensionAttribute
类很简单,它只是重写 ExtensionType
属性以返回 SoapExtension
的类型
[AttributeUsage( AttributeTargets.Method )]
public class SoapSizeExtensionAttribute : SoapExtensionAttribute
{
private int _Priority;
public override Type ExtensionType
{
get { return typeof( SoapSizeExtension ); }
}
public override int Priority
{
get { return _Priority; }
set { _Priority = value; }
}
}
SoapSizeExtension
这个类完成了工作。它挂钩到消息处理系统以访问正在传递的原始 SOAP。这是一个相当复杂的过程,因此我将分步解释该类。
首先,我们声明类并添加一些 private
字段
public partial class SoapSizeExtension : SoapExtension
{
private Stream _OldStream = null;
private Stream _NewStream = null;
private string _SoapInput = null;
#if DEBUG
private string _InputFilepath = null;
private string _OuputFilepath = null;
#endif
}
初始化:SoapExtension
类有一些用于初始化的 abstract
成员。我们不使用它们,所以我们只是将它们存根出来
partial class SoapSizeExtension
{
// The SOAP extension was configured to run
// using an attribute
public override object GetInitializer(
LogicalMethodInfo methodInfo,
SoapExtensionAttribute attribute )
{
return null;
}
// The SOAP extension was configured to run using
// a configuration file
public override object GetInitializer(Type serviceType)
{
return null;
}
public override void Initialize( object initializer )
{
}
}
ChainStream
:这是第一个有趣的方法。它给了我们一个机会在消息处理周期中分离输入流和输出流。在这里,我们保存输入流的引用,并返回一个我们稍后将填充的新流
partial class SoapSizeExtension
{
public override Stream ChainStream( Stream stream )
{
_OldStream = stream;
return _NewStream = new MemoryStream();
}
}
ProcessMessage
:这是最重要的一个方法。它在每个周期中被调用四次,每次使用不同的 SoapMessageStage
。首先,当消息从客户端到达时,它会从 SOAP 反序列化,并调用 BeforeDeserialize
和 AfterDeserialize
。然后服务执行其工作,并将结果序列化为 SOAP,并调用 BeforeSerialize
和 AfterSerialize
。此方法在每个阶段调用一个 private
方法
partial class SoapSizeExtension
{
public override void ProcessMessage( SoapMessage message )
{
switch ( message.Stage )
{
case SoapMessageStage.BeforeDeserialize:
BeforeDeserialize();
break;
case SoapMessageStage.AfterDeserialize:
AfterDeserialize( message );
break;
case SoapMessageStage.BeforeSerialize:
BeforeSerialize( message );
break;
case SoapMessageStage.AfterSerialize:
AfterSerialize();
break;
default: throw new InvalidOperationException("Invalid stage");
}
}
}
SOAP 标头:在查看各个方法之前,我们需要知道我们的目标是什么。这是我们试图修改的 SOAP
<soap:Header>
<SoapSizeHeader xmlns="http://tempuri.org">
<SoapSizeHeaderSize>1764</SoapSizeHeaderSize>
</SoapSizeHeader>
</soap:Header>
正则表达式:我们需要找到 SoapSizeHeaderSize
元素并将其值设置为 SOAP 消息的大小。这将使该值可供 Web 服务使用。使用正则表达式可以最轻松地实现这一点
partial class SoapSizeExtension
{
private static Regex regexSoapSizeHeaderSize = new Regex(
@"(?<before><soap:Header>.*?<SoapSizeHeaderSize>)" +
@"\d+" +
@"(?<after></SoapSizeHeaderSize>.*?</soap:Header>)",
RegexOptions.IgnoreCase |
RegexOptions.Singleline |
RegexOptions.Compiled
);
}
BeforeDeserialize
:现在我们可以查看各个方法了。BeforeDeserialize
是第一个被调用的方法。在此方法中,我们可以访问来自客户端的传入 SOAP 消息。我们从 _OldStream
读取消息,获取其长度并设置标头值,然后将修改后的消息写入 _NewStream
partial class SoapSizeExtension
{
private void BeforeDeserialize()
{
StreamReader input = new StreamReader( _OldStream );
_SoapInput = input.ReadToEnd();
_OldStream.Position = 0;
int length = _SoapInput.Length;
_SoapInput = regexSoapSizeHeaderSize.Replace(
_SoapInput, "${before}" + length + "${after}", 1 );
StreamWriter output = new StreamWriter( _NewStream );
output.Write( _SoapInput );
output.Flush();
_NewStream.Position = 0;
}
}
AfterDeserialize
:当 _NewStream
被系统反序列化后,将调用 AfterDeserialize
。在此方法中,我们可以访问我们的 WebService
对象,因此我们可以使用 MapPath
方法来查找 Web 服务器上的应用程序目录。然后,我们可以将修改后的 SOAP 消息写入文件。注释掉的代码是设置 SoapSizeHeader
值的另一种方法
partial class SoapSizeExtension
{
private void AfterDeserialize( SoapMessage message )
{
//foreach ( SoapHeader header in message.Headers )
//{
// SoapSizeHeader soapSizeHeader =
header as SoapSizeHeader;
//
// if ( soapSizeHeader != null )
// soapSizeHeader.SoapSizeHeaderSize =
_SoapInput.Length;
//}
#if DEBUG
if ( _InputFilepath == null )
{
SoapServerMessage serverMessage =
message as SoapServerMessage;
WebService service =
serverMessage.Server as WebService;
_InputFilepath =
service.Server.MapPath( "Soap\\input.xml" );
}
WriteFile( _InputFilepath, _SoapInput );
#endif
_SoapInput = null;
}
}
BeforeSerialize
:Web 方法执行后,返回值会被序列化为 SOAP 以返回给客户端。首先调用 BeforeSerialize
,在这里我们只是使用 WebService
对象再次解析路径
partial class SoapSizeExtension
{
private void BeforeSerialize( SoapMessage message )
{
#if DEBUG
if ( _OuputFilepath == null )
{
SoapServerMessage serverMessage =
message as SoapServerMessage;
WebService service =
serverMessage.Server as WebService;
_OuputFilepath =
service.Server.MapPath( "Soap\\output.xml" );
}
#endif
}
}
AfterSerialize
:这是最后一个方法。在这里,我们可以访问响应 SOAP,因此我们可以为客户端设置 SoapSizeHeader
值
partial class SoapSizeExtension
{
private void AfterSerialize()
{
_NewStream.Position = 0;
StreamReader input = new StreamReader( _NewStream );
string soap = input.ReadToEnd();
int length = soap.Length;
soap = regexSoapSizeHeaderSize.Replace(
soap, "${before}" + length + "${after}", 1 );
StreamWriter output = new StreamWriter( _OldStream );
output.Write( soap );
output.Flush();
#if DEBUG
WriteFile( _OuputFilepath, soap );
#endif
}
}
WriteFile
:最后一个 private
方法只是一个辅助方法,用于将 SOAP 消息存储到文件中
partial class SoapSizeExtension
{
#if DEBUG
private void WriteFile( string filepath, string soap )
{
FileStream fs = null;
StreamWriter writer = null;
try
{
fs = new FileStream( filepath,
FileMode.Create, FileAccess.Write );
writer = new StreamWriter( fs );
writer.Write( soap );
}
finally
{
if ( writer != null ) writer.Close();
if ( fs != null ) fs.Close();
}
}
#endif
}
WebMethod
有了 SoapSizeExtension
,我们现在可以从我们的 WebMethod
访问 SoapSizeHeader
值了
partial class MyWebService : SoapSizeService
{
[WebMethod]
[SoapHeader( "SoapSizeHeader",
Direction=SoapHeaderDirection.InOut )]
[SoapSizeExtension]
public string HelloWorld()
{
if (this.SoapSizeHeader.SoapSizeHeaderSize > 1024)
return "You talk too much";
return "Hello, world";
}
}
结论
此代码是 SoapHeader
和 SoapExtension
的一个非常简单的用法。您可以按原样使用它,但也可以将其用作编写更复杂扩展的基础。
历史
- 2005年12月14日:版本 1
- 首次发布。