简体   繁体   English

ASP.NET Response.Filter:对整个内容/完整响应

[英]ASP.NET Response.Filter: on the entire content/full response

I am attempting to create response filter in order to modify the html being produced by another web application. 我正在尝试创建响应过滤器,以修改由另一个Web应用程序生成的html。

As shown below I created a Response.Filter derived from Stream . 如下所示,我创建了一个从Stream派生的Response.Filter

The Response.Filter writes in chunks, I end up with a big mess if the response is larger than a single chunk. Response.Filter以块的形式编写,如果响应大于单个块,则最终会陷入混乱。

On Write , the content is cached to another MemoryStream to buffer all of the chunks. Write ,内容被缓存到另一个MemoryStream以缓冲所有块。

On Flush , the full content, now in the MemoryStream is converted to a string and the filter logic kicks in. Flush ,现在在MemoryStream的所有内容都将转换为字符串,并且过滤逻辑开始。

The modified content is then written back out to the originating stream. 然后将修改后的内容写回原始流。

What is the proper implementation for a Response.Filter that works on the entire content at once? 可同时处理整个内容的Response.Filter的正确实现是什么?

HttpModule.cs : HttpModule.cs

using System;
using System.Web;

namespace ACME
{
    public class HttpModule : IHttpModule
    {
        void IHttpModule.Init(HttpApplication context)
        {
            context.BeginRequest += ContextBeginRequest;
        }

        private void ContextBeginRequest(object sender, EventArgs e)
        {
            HttpApplication app = sender as HttpApplication;

            if (app != null)
            {
                if ( app.Request.FilePath.ToLower().EndsWith(".aspx") )
                {
                    app.Response.Filter = new ACMEFilter(app.Response.Filter);
                }
            }
        }

        void IHttpModule.Dispose()
        {
            // Nothing to dispose; 
        }
    }
}

ACMEFilter.cs : ACMEFilter.cs

using System;
using System.IO;
using System.Web;
using System.Text;
using System.Text.RegularExpressions;

namespace ACME
{
    internal class ACMEFilter: Stream
    {
        private readonly Stream _outputStream;
        private MemoryStream _cachedStream = new MemoryStream(1024);

        public override bool CanRead
        {
            get { return true; }
        }

        public override bool CanSeek
        {
            get { return true; }
        }

        public override bool CanWrite
        {
            get { return true; }
        }

        public override long Length
        {
            get { return 0; }
        }

        public override long Position { get; set; }

        public ACMEFilter(Stream stream)
        {
            _outputStream = stream;
        }

        public override void Flush()
        {
            Encoding encoding = HttpContext.Current.Response.ContentEncoding;
            string content = encoding.GetString(_cachedStream.ToArray());

            // filter logic here!
            content += "";

            byte[] buffer = encoding.GetBytes(content);
            _cachedStream = new MemoryStream();
            _cachedStream.Write(buffer, 0, buffer.Length);

            _outputStream.Write(_cachedStream.ToArray(), 0, (int)_cachedStream.Length);
            _cachedStream.SetLength(0);

            _outputStream.Flush();
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            return _cachedStream.Read(buffer, offset, count);
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            return _cachedStream.Seek(offset, origin);
        }

        public override void SetLength(long value)
        {
            _cachedStream.SetLength(value);
        }

        public override void Close()
        {
            _cachedStream.Close();
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            _cachedStream.Write(buffer, offset, count);
        }
    }
}

web.config : web.config

<system.webServer>
    <modules>
      <add name="ACME" type="ACME.HttpModule, ACME" preCondition="managedHandler" />
    </modules>
<system.webServer>

What is the proper implementation for a Response.Filter that works on the entire content at once? 可同时处理整个内容的Response.Filter的正确实现是什么?

By definition, a stream does not work on a piece of text/bytes all at once. 根据定义,流不能一次处理全部文本/字节。

Also, Flush() may be called more than once on a stream, so it is invalid to write the headers in this method as it may end up putting them multiple times in the response. 另外,在流上可能多次调用Flush() ,因此在此方法中写入标头是无效的,因为它可能最终将它们多次放入响应中。


app.Response.Filter = new ACMEFilter(app.Response.Filter);

The above seems to implement the decorator pattern (which is common with streams), but the implementation you posted doesn't have a constructor. 上面的代码似乎实现了装饰器模式(这在流中很常见),但是您发布的实现没有构造函数。 Normally, you would pass the stream you are writing to to the constructor in order to wrap it, then override all of the methods and write to the inner stream instance. 通常,您将要写入的流传递给构造函数以对其进行包装,然后重写所有方法并写入内部流实例。 These overridden methods are where you need to put the custom logic for writing the output the way you want, storing any state as necessary to flag different sections of the stream for customization. 这些覆盖的方法是您需要放置自定义逻辑以按所需方式写入输出的地方,并根据需要存储任何状态以标记流的不同部分以进行自定义。

internal class ACMEFilter : Stream
{
    private readonly Stream _innerStream;

    public ACMEFilter(Stream innerStream)
    {
        _innerStream = innerStream;
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        // Implement custom filter logic here,
        // storing any flags as class-level variables as needed
        // to act as switches across many calls to this and other
        // methods of the stream.

        _innerStream.Write(buffer, offset, count);
    }

    // Other member overrides...
}

There is an article that explains more in depth how to create a response filter https://weblog.west-wind.com/posts/2009/Nov/13/Capturing-and-Transforming-ASPNET-Output-with-ResponseFilter that specifically covers the fact that the stream data needs to be processed in chunks as the application provides it, and offers an alternative approach. 有一篇文章更深入地解释了如何创建响应过滤器https://weblog.west-wind.com/posts/2009/Nov/13/Capturing-and-Transforming-ASPNET-Output-with-ResponseFilter涵盖了以下事实:在应用程序提供时,流数据需要分块处理,并提供了一种替代方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM