【转】HttpCompress
一
在PreRequestHandlerExecute 事件里边用DeflateStream修饰的Response.Filter替代Response.Filter
public sealed class CompressionModule : IHttpModule
{
#region IHttpModule Members
/// <summary>
/// Disposes of the resources (other than memory) used by the module
/// that implements <see cref="T:System.Web.IHttpModule"></see>.
/// </summary>
void IHttpModule.Dispose()
{
// Nothing to dispose;
}
/// <summary>
/// Initializes a module and prepares it to handle requests.
/// </summary>
/// <param name="context">An <see cref="T:System.Web.HttpApplication"></see>
/// that provides access to the methods, properties, and events common to
/// all application objects within an ASP.NET application.
/// </param>
void IHttpModule.Init(HttpApplication context)
{
if (BlogSettings.Instance.EnableHttpCompression)
{
context.PreRequestHandlerExecute += new EventHandler(context_PostReleaseRequestState);
}
}
#endregion
private const string GZIP = "gzip";
private const string DEFLATE = "deflate";
#region Compress page
/// <summary>
/// Handles the BeginRequest event of the context control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
void context_PostReleaseRequestState(object sender, EventArgs e)
{
HttpApplication app = (HttpApplication)sender;
if (app.Context.CurrentHandler is System.Web.UI.Page && app.Request["HTTP_X_MICROSOFTAJAX"] == null)
{
if (IsEncodingAccepted(DEFLATE))
{
app.Response.Filter = new DeflateStream(app.Response.Filter, CompressionMode.Compress);
SetEncoding(DEFLATE);
}
else if (IsEncodingAccepted(GZIP))
{
app.Response.Filter = new GZipStream(app.Response.Filter, CompressionMode.Compress);
SetEncoding(GZIP);
}
}
else if (app.Context.Request.Path.Contains("WebResource.axd"))
{
app.Context.Response.Cache.SetExpires(DateTime.Now.AddDays(30));
}
}
/// <summary>
/// Checks the request headers to see if the specified
/// encoding is accepted by the client.
/// </summary>
private static bool IsEncodingAccepted(string encoding)
{
HttpContext context = HttpContext.Current;
return context.Request.Headers["Accept-encoding"] != null && context.Request.Headers["Accept-encoding"].Contains(encoding);
}
/// <summary>
/// Adds the specified encoding to the response headers.
/// </summary>
/// <param name="encoding"></param>
private static void SetEncoding(string encoding)
{
HttpContext.Current.Response.AppendHeader("Content-encoding", encoding);
}
#endregion
}
二:
HttpCompress是在.NET内部通过httpModules模型来实现自定义的压缩模块。
在PreRequestHandlerExecute 事件里边用DeflateStream修饰的Response.Filter替代Response.Filter
public sealed class CompressionModule : IHttpModule
{
#region IHttpModule Members
/// <summary>
/// Disposes of the resources (other than memory) used by the module
/// that implements <see cref="T:System.Web.IHttpModule"></see>.
/// </summary>
void IHttpModule.Dispose()
{
// Nothing to dispose;
}
/// <summary>
/// Initializes a module and prepares it to handle requests.
/// </summary>
/// <param name="context">An <see cref="T:System.Web.HttpApplication"></see>
/// that provides access to the methods, properties, and events common to
/// all application objects within an ASP.NET application.
/// </param>
void IHttpModule.Init(HttpApplication context)
{
if (BlogSettings.Instance.EnableHttpCompression)
{
context.PreRequestHandlerExecute += new EventHandler(context_PostReleaseRequestState);
}
}
#endregion
private const string GZIP = "gzip";
private const string DEFLATE = "deflate";
#region Compress page
/// <summary>
/// Handles the BeginRequest event of the context control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
void context_PostReleaseRequestState(object sender, EventArgs e)
{
HttpApplication app = (HttpApplication)sender;
if (app.Context.CurrentHandler is System.Web.UI.Page && app.Request["HTTP_X_MICROSOFTAJAX"] == null)
{
if (IsEncodingAccepted(DEFLATE))
{
app.Response.Filter = new DeflateStream(app.Response.Filter, CompressionMode.Compress);
SetEncoding(DEFLATE);
}
else if (IsEncodingAccepted(GZIP))
{
app.Response.Filter = new GZipStream(app.Response.Filter, CompressionMode.Compress);
SetEncoding(GZIP);
}
}
else if (app.Context.Request.Path.Contains("WebResource.axd"))
{
app.Context.Response.Cache.SetExpires(DateTime.Now.AddDays(30));
}
}
/// <summary>
/// Checks the request headers to see if the specified
/// encoding is accepted by the client.
/// </summary>
private static bool IsEncodingAccepted(string encoding)
{
HttpContext context = HttpContext.Current;
return context.Request.Headers["Accept-encoding"] != null && context.Request.Headers["Accept-encoding"].Contains(encoding);
}
/// <summary>
/// Adds the specified encoding to the response headers.
/// </summary>
/// <param name="encoding"></param>
private static void SetEncoding(string encoding)
{
HttpContext.Current.Response.AppendHeader("Content-encoding", encoding);
}
#endregion
}
二:
HttpCompress是在.NET内部通过httpModules模型来实现自定义的压缩模块。
1. 配置
<httpModules>
<add name="CompressionModule" type="blowery.Web.HttpCompress.HttpModule, blowery.web.HttpCompress"/>
</httpModules>
2.注册事件钩子
context.ReleaseRequestState += new EventHandler(this.CompressContent);
context.PreSendRequestHeaders += new EventHandler(this.CompressContent);
(IHttpModule事件)
3.在代码发送前压缩
void CompressContent(object sender, EventArgs e) {
HttpApplication app = (HttpApplication)sender;
if(!app.Context.Items.Contains(INSTALLED_KEY)) {
//设置标志避免重复压缩
app.Context.Items.Add(INSTALLED_KEY, INSTALLED_TAG);
//设置缓存敏感参数
app.Response.Cache.VaryByHeaders["Accept-Encoding"] = true;
//根据Accept-Encoding寻找合适的压缩过滤模块CompressingFilter : HttpOutputFilter;
//如果没有设置Accept-Encoding,或者寻找失败均是直接返回,即不压缩
...
CompressingFilter filter = GetFilterForScheme(types, app.Response.Filter, settings);
if(filter == null){
// if we didn't find a filter, bail out
return;
}
app.Response.Filter = filter;
}
}
HttpApplication app = (HttpApplication)sender;
if(!app.Context.Items.Contains(INSTALLED_KEY)) {
//设置标志避免重复压缩
app.Context.Items.Add(INSTALLED_KEY, INSTALLED_TAG);
//设置缓存敏感参数
app.Response.Cache.VaryByHeaders["Accept-Encoding"] = true;
//根据Accept-Encoding寻找合适的压缩过滤模块CompressingFilter : HttpOutputFilter;
//如果没有设置Accept-Encoding,或者寻找失败均是直接返回,即不压缩
...
CompressingFilter filter = GetFilterForScheme(types, app.Response.Filter, settings);
if(filter == null){
// if we didn't find a filter, bail out
return;
}
app.Response.Filter = filter;
}
}
4.压缩类实现
HttpOutputFilter ,不支持Read, Seek,GetLength等操作,与之相关控制属性返回false, 具体方法或属性则抛异常new NotSupportedException();
using System;
using System.IO;
namespace blowery.Web.HttpCompress {
/// <summary>
/// The base of anything you want to latch onto the Filter property of a <see cref="System.Web.HttpResponse"/>
/// object.
/// </summary>
/// <remarks>
/// <p></p>These are generally used with <see cref="HttpModule"/> but you could really use them in
/// other HttpModules. This is a general, write-only stream that writes to some underlying stream. When implementing
/// a real class, you have to override void Write(byte[], int offset, int count). Your work will be performed there.
/// </remarks>
public abstract class HttpOutputFilter : Stream {
private Stream _sink;
/// <summary>
/// Subclasses need to call this on contruction to setup the underlying stream
/// </summary>
/// <param name="baseStream">The stream we're wrapping up in a filter</param>
protected HttpOutputFilter(Stream baseStream) {
_sink = baseStream;
}
/// <summary>
/// Allow subclasses access to the underlying stream
/// </summary>
protected Stream BaseStream {
get{ return _sink; }
}
/// <summary>
/// False. These are write-only streams
/// </summary>
public override bool CanRead {
get { return false; }
}
/// <summary>
/// False. These are write-only streams
/// </summary>
public override bool CanSeek {
get { return false; }
}
/// <summary>
/// True. You can write to the stream. May change if you call Close or Dispose
/// </summary>
public override bool CanWrite {
get { return _sink.CanWrite; }
}
/// <summary>
/// Not supported. Throws an exception saying so.
/// </summary>
/// <exception cref="NotSupportedException">Thrown. Always.</exception>
public override long Length {
get { throw new NotSupportedException(); }
}
/// <summary>
/// Not supported. Throws an exception saying so.
/// </summary>
/// <exception cref="NotSupportedException">Thrown. Always.</exception>
public override long Position {
get { throw new NotSupportedException(); }
set { throw new NotSupportedException(); }
}
/// <summary>
/// Not supported. Throws an exception saying so.
/// </summary>
/// <exception cref="NotSupportedException">Thrown. Always.</exception>
public override long Seek(long offset, System.IO.SeekOrigin direction) {
throw new NotSupportedException();
}
/// <summary>
/// Not supported. Throws an exception saying so.
/// </summary>
/// <exception cref="NotSupportedException">Thrown. Always.</exception>
public override void SetLength(long length) {
throw new NotSupportedException();
}
/// <summary>
/// Closes this Filter and the underlying stream.
/// </summary>
/// <remarks>
/// If you override, call up to this method in your implementation.
/// </remarks>
public override void Close() {
_sink.Close();
}
/// <summary>
/// Fluses this Filter and the underlying stream.
/// </summary>
/// <remarks>
/// If you override, call up to this method in your implementation.
/// </remarks>
public override void Flush() {
_sink.Flush();
}
/// <summary>
/// Not supported.
/// </summary>
/// <param name="buffer">The buffer to write into.</param>
/// <param name="offset">The offset on the buffer to write into</param>
/// <param name="count">The number of bytes to write. Must be less than buffer.Length</param>
/// <returns>An int telling you how many bytes were written</returns>
public override int Read(byte[] buffer, int offset, int count) {
throw new NotSupportedException();
}
}
}
using System.IO;
namespace blowery.Web.HttpCompress {
/// <summary>
/// The base of anything you want to latch onto the Filter property of a <see cref="System.Web.HttpResponse"/>
/// object.
/// </summary>
/// <remarks>
/// <p></p>These are generally used with <see cref="HttpModule"/> but you could really use them in
/// other HttpModules. This is a general, write-only stream that writes to some underlying stream. When implementing
/// a real class, you have to override void Write(byte[], int offset, int count). Your work will be performed there.
/// </remarks>
public abstract class HttpOutputFilter : Stream {
private Stream _sink;
/// <summary>
/// Subclasses need to call this on contruction to setup the underlying stream
/// </summary>
/// <param name="baseStream">The stream we're wrapping up in a filter</param>
protected HttpOutputFilter(Stream baseStream) {
_sink = baseStream;
}
/// <summary>
/// Allow subclasses access to the underlying stream
/// </summary>
protected Stream BaseStream {
get{ return _sink; }
}
/// <summary>
/// False. These are write-only streams
/// </summary>
public override bool CanRead {
get { return false; }
}
/// <summary>
/// False. These are write-only streams
/// </summary>
public override bool CanSeek {
get { return false; }
}
/// <summary>
/// True. You can write to the stream. May change if you call Close or Dispose
/// </summary>
public override bool CanWrite {
get { return _sink.CanWrite; }
}
/// <summary>
/// Not supported. Throws an exception saying so.
/// </summary>
/// <exception cref="NotSupportedException">Thrown. Always.</exception>
public override long Length {
get { throw new NotSupportedException(); }
}
/// <summary>
/// Not supported. Throws an exception saying so.
/// </summary>
/// <exception cref="NotSupportedException">Thrown. Always.</exception>
public override long Position {
get { throw new NotSupportedException(); }
set { throw new NotSupportedException(); }
}
/// <summary>
/// Not supported. Throws an exception saying so.
/// </summary>
/// <exception cref="NotSupportedException">Thrown. Always.</exception>
public override long Seek(long offset, System.IO.SeekOrigin direction) {
throw new NotSupportedException();
}
/// <summary>
/// Not supported. Throws an exception saying so.
/// </summary>
/// <exception cref="NotSupportedException">Thrown. Always.</exception>
public override void SetLength(long length) {
throw new NotSupportedException();
}
/// <summary>
/// Closes this Filter and the underlying stream.
/// </summary>
/// <remarks>
/// If you override, call up to this method in your implementation.
/// </remarks>
public override void Close() {
_sink.Close();
}
/// <summary>
/// Fluses this Filter and the underlying stream.
/// </summary>
/// <remarks>
/// If you override, call up to this method in your implementation.
/// </remarks>
public override void Flush() {
_sink.Flush();
}
/// <summary>
/// Not supported.
/// </summary>
/// <param name="buffer">The buffer to write into.</param>
/// <param name="offset">The offset on the buffer to write into</param>
/// <param name="count">The number of bytes to write. Must be less than buffer.Length</param>
/// <returns>An int telling you how many bytes were written</returns>
public override int Read(byte[] buffer, int offset, int count) {
throw new NotSupportedException();
}
}
}
CompressingFilter 增加了压缩相关的属性和方法,如,写头
using System;
using System.IO;
using System.Web;
namespace blowery.Web.HttpCompress {
/// <summary>
/// Base for any HttpFilter that performing compression
/// </summary>
/// <remarks>
/// When implementing this class, you need to implement a <see cref="HttpOutputFilter"/>
/// along with a <see cref="CompressingFilter.ContentEncoding"/>. The latter corresponds to a
/// content coding (see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.5)
/// that your implementation will support.
/// </remarks>
public abstract class CompressingFilter : HttpOutputFilter {
private bool hasWrittenHeaders = false;
/// <summary>
/// Protected constructor that sets up the underlying stream we're compressing into
/// </summary>
/// <param name="baseStream">The stream we're wrapping up</param>
/// <param name="compressionLevel">The level of compression to use when compressing the content</param>
protected CompressingFilter(Stream baseStream, CompressionLevels compressionLevel) : base(baseStream) {
_compressionLevel = compressionLevel;
}
/// <summary>
/// The name of the content-encoding that's being implemented
/// </summary>
/// <remarks>
/// See http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.5 for more
/// details on content codings.
/// </remarks>
public abstract string ContentEncoding { get; }
private CompressionLevels _compressionLevel;
/// <summary>
/// Allow inheriting classes to get access the the level of compression that should be used
/// </summary>
protected CompressionLevels CompressionLevel {
get { return _compressionLevel; }
}
/// <summary>
/// Keeps track of whether or not we're written the compression headers
/// </summary>
protected bool HasWrittenHeaders {
get { return hasWrittenHeaders; }
}
/// <summary>
/// Writes out the compression-related headers. Subclasses should call this once before writing to the output stream.
/// </summary>
protected void WriteHeaders() {
// this is dangerous. if Response.End is called before the filter is used, directly or indirectly,
// the content will not pass through the filter. However, this header will still be appended.
// Look for handling cases in PreRequestSendHeaders and Pre
HttpContext.Current.Response.AppendHeader("Content-Encoding", this.ContentEncoding);
//HttpContext.Current.Response.AppendHeader("X-Compressed-By", "HttpCompress");
hasWrittenHeaders = true;
}
}
}
using System.IO;
using System.Web;
namespace blowery.Web.HttpCompress {
/// <summary>
/// Base for any HttpFilter that performing compression
/// </summary>
/// <remarks>
/// When implementing this class, you need to implement a <see cref="HttpOutputFilter"/>
/// along with a <see cref="CompressingFilter.ContentEncoding"/>. The latter corresponds to a
/// content coding (see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.5)
/// that your implementation will support.
/// </remarks>
public abstract class CompressingFilter : HttpOutputFilter {
private bool hasWrittenHeaders = false;
/// <summary>
/// Protected constructor that sets up the underlying stream we're compressing into
/// </summary>
/// <param name="baseStream">The stream we're wrapping up</param>
/// <param name="compressionLevel">The level of compression to use when compressing the content</param>
protected CompressingFilter(Stream baseStream, CompressionLevels compressionLevel) : base(baseStream) {
_compressionLevel = compressionLevel;
}
/// <summary>
/// The name of the content-encoding that's being implemented
/// </summary>
/// <remarks>
/// See http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.5 for more
/// details on content codings.
/// </remarks>
public abstract string ContentEncoding { get; }
private CompressionLevels _compressionLevel;
/// <summary>
/// Allow inheriting classes to get access the the level of compression that should be used
/// </summary>
protected CompressionLevels CompressionLevel {
get { return _compressionLevel; }
}
/// <summary>
/// Keeps track of whether or not we're written the compression headers
/// </summary>
protected bool HasWrittenHeaders {
get { return hasWrittenHeaders; }
}
/// <summary>
/// Writes out the compression-related headers. Subclasses should call this once before writing to the output stream.
/// </summary>
protected void WriteHeaders() {
// this is dangerous. if Response.End is called before the filter is used, directly or indirectly,
// the content will not pass through the filter. However, this header will still be appended.
// Look for handling cases in PreRequestSendHeaders and Pre
HttpContext.Current.Response.AppendHeader("Content-Encoding", this.ContentEncoding);
//HttpContext.Current.Response.AppendHeader("X-Compressed-By", "HttpCompress");
hasWrittenHeaders = true;
}
}
}
执行类,利用压缩类实现了Write,Close,Flush
using System;
using System.IO;
using System.IO.Compression;
namespace blowery.Web.HttpCompress {
/// <summary>
/// Summary description for DeflateFilter.
/// </summary>
public class DeflateFilter : CompressingFilter {
/// <summary>
/// compression stream member
/// has to be a member as we can only have one instance of the
/// actual filter class
/// </summary>
private DeflateStream m_stream = null;
/// <summary>
/// Basic constructor that uses the Normal compression level
/// </summary>
/// <param name="baseStream">The stream to wrap up with the deflate algorithm</param>
public DeflateFilter(Stream baseStream) : this(baseStream, CompressionLevels.Normal) { }
/// <summary>
/// Full constructor that allows you to set the wrapped stream and the level of compression
/// </summary>
/// <param name="baseStream">The stream to wrap up with the deflate algorithm</param>
/// <param name="compressionLevel">The level of compression to use</param>
public DeflateFilter(Stream baseStream, CompressionLevels compressionLevel) : base(baseStream, compressionLevel) {
m_stream = new DeflateStream(baseStream, CompressionMode.Compress);
}
/// <summary>
/// Write out bytes to the underlying stream after compressing them using deflate
/// </summary>
/// <param name="buffer">The array of bytes to write</param>
/// <param name="offset">The offset into the supplied buffer to start</param>
/// <param name="count">The number of bytes to write</param>
public override void Write(byte[] buffer, int offset, int count) {
if(!HasWrittenHeaders) WriteHeaders();
m_stream.Write(buffer, offset, count);
}
/// <summary>
/// Return the Http name for this encoding. Here, deflate.
/// </summary>
public override string ContentEncoding {
get { return "deflate"; }
}
/// <summary>
/// Closes this Filter and calls the base class implementation.
/// </summary>
public override void Close() {
m_stream.Close();
}
/// <summary>
/// Flushes that the filter out to underlying storage
/// </summary>
public override void Flush() {
m_stream.Flush();
}
}
}
using System.IO;
using System.IO.Compression;
namespace blowery.Web.HttpCompress {
/// <summary>
/// Summary description for DeflateFilter.
/// </summary>
public class DeflateFilter : CompressingFilter {
/// <summary>
/// compression stream member
/// has to be a member as we can only have one instance of the
/// actual filter class
/// </summary>
private DeflateStream m_stream = null;
/// <summary>
/// Basic constructor that uses the Normal compression level
/// </summary>
/// <param name="baseStream">The stream to wrap up with the deflate algorithm</param>
public DeflateFilter(Stream baseStream) : this(baseStream, CompressionLevels.Normal) { }
/// <summary>
/// Full constructor that allows you to set the wrapped stream and the level of compression
/// </summary>
/// <param name="baseStream">The stream to wrap up with the deflate algorithm</param>
/// <param name="compressionLevel">The level of compression to use</param>
public DeflateFilter(Stream baseStream, CompressionLevels compressionLevel) : base(baseStream, compressionLevel) {
m_stream = new DeflateStream(baseStream, CompressionMode.Compress);
}
/// <summary>
/// Write out bytes to the underlying stream after compressing them using deflate
/// </summary>
/// <param name="buffer">The array of bytes to write</param>
/// <param name="offset">The offset into the supplied buffer to start</param>
/// <param name="count">The number of bytes to write</param>
public override void Write(byte[] buffer, int offset, int count) {
if(!HasWrittenHeaders) WriteHeaders();
m_stream.Write(buffer, offset, count);
}
/// <summary>
/// Return the Http name for this encoding. Here, deflate.
/// </summary>
public override string ContentEncoding {
get { return "deflate"; }
}
/// <summary>
/// Closes this Filter and calls the base class implementation.
/// </summary>
public override void Close() {
m_stream.Close();
}
/// <summary>
/// Flushes that the filter out to underlying storage
/// </summary>
public override void Flush() {
m_stream.Flush();
}
}
}