65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.64/5 (6投票s)

2005年12月14日

CPOL

4分钟阅读

viewsIcon

61407

downloadIcon

501

一个简单的 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 反序列化,并调用 BeforeDeserializeAfterDeserialize。然后服务执行其工作,并将结果序列化为 SOAP,并调用 BeforeSerializeAfterSerialize。此方法在每个阶段调用一个 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";
    }
}

结论

此代码是 SoapHeaderSoapExtension 的一个非常简单的用法。您可以按原样使用它,但也可以将其用作编写更复杂扩展的基础。

历史

  • 2005年12月14日:版本 1
    • 首次发布。
© . All rights reserved.