代码改变世界

ASP.Net 重写IHttpModule 来拦截 HttpApplication 实现HTML资源压缩和空白过滤

2016-09-24 21:26  Dorisoy  阅读(1342)  评论(0编辑  收藏  举报

务实直接上代码:

1. 重写FilterModule.cs

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading.Tasks;
 6 using System.Web;
 7 using System.Text.RegularExpressions;
 8 using System.IO.Compression;
 9 
10 namespace Compress.FilterModule
11 {
12     public class FilterModule : IHttpModule
13     {
14         public void Dispose()
15         {
16             //
17         }
18 
19         /// <summary>
20         /// Init method is only used to register the desired event
21         /// </summary>
22         /// <param name="context"></param>
23         public void Init(HttpApplication context)
24         {
25             context.BeginRequest += new EventHandler(context_BeginRequest);
26             //context.EndRequest += new EventHandler(context_EndRequest);
27             //context.PostRequestHandlerExecute += new EventHandler(context_PostRequestHandlerExecute);
28         }
29 
30 
31         /// <summary>
32         /// Handles the BeginRequest event of the context control.
33         /// </summary>
34         /// <param name="sender">The source of the event.</param>
35         /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
36         void context_BeginRequest(object sender, EventArgs e)
37         {
38             HttpApplication app = sender as HttpApplication;
39             HttpContext context = app.Context;
40             if (context.CurrentHandler is System.Web.UI.Page)
41             {
42                 bool isPage = context.CurrentHandler.IsReusable;
43             }
44             if (app.Request.RawUrl.Contains(".aspx") || app.Request.RawUrl.EndsWith("/"))
45             {
46                 // HttpContext context = app.Context;
47                 HttpRequest request = context.Request;
48                 string acceptEncoding = request.Headers["Accept-Encoding"];
49                 HttpResponse response = context.Response;
50                 if (!string.IsNullOrEmpty(acceptEncoding))
51                 {
52                     acceptEncoding = acceptEncoding.ToUpperInvariant();
53                     if (acceptEncoding.Contains("GZIP"))
54                     {
55                         //var straem = new GZipStream(response.Filter, CompressionMode.Compress);
56                         response.Filter = new CompressWhitespaceFilter(response.Filter, CompressOptions.GZip);
57                         response.AppendHeader("Content-encoding", "gzip");
58                     }
59                     else if (acceptEncoding.Contains("DEFLATE"))
60                     {
61                         response.Filter = new CompressWhitespaceFilter(context.Response.Filter, CompressOptions.Deflate);
62                         response.AppendHeader("Content-encoding", "deflate");
63                     }
64                 }
65                 response.Cache.VaryByHeaders["Accept-Encoding"] = true;
66             }
67         }
68 
69         // Test
70         //void context_BeginRequest(object sender, EventArgs e)
71         //{
72         //    HttpApplication application = (HttpApplication)sender;
73         //    HttpContext context = application.Context;
74         //    context.Response.ContentType = "text/html";
75         //    context.Response.Charset = "GB2312";
76         //    context.Response.ContentEncoding = Encoding.Default;
77         //    context.Response.Write("<h1 style='color:#00f'>Treatment from HttpModule,Begin...</h1><hr>");
78         //}
79 
80         // Test
81         //void context_EndRequest(object sender, EventArgs e)
82         //{
83         //    HttpApplication application = (HttpApplication)sender;
84         //    HttpContext context = application.Context;
85         //    context.Response.ContentType = "text/html";
86         //    context.Response.Charset = "GB2312";
87         //    context.Response.ContentEncoding = Encoding.Default;
88         //    context.Response.Write("<hr><h1 style='color:#f00'>Treatment from HttpModule,End...</h1>");
89         //}
90 
91     }
92 }

 

2. 处理压缩和匹配自定义过滤 CompressWhitespaceFilter.cs

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Web;
  5 using System.IO;
  6 using System.Text;
  7 using System.Text.RegularExpressions;
  8 using System.IO.Compression;
  9 
 10 namespace Compress.ModuleDemo
 11 {
 12     public enum CompressOptions
 13     {
 14         GZip,
 15         Deflate,
 16         None
 17     }
 18 
 19     public class CompressWhitespaceFilter : Stream
 20     {
 21         StringBuilder responseHtml;
 22         const string _cssPattern = "(?<HTML><link[^>]*href\\s*=\\s*[\\\"\\']?(?<HRef>[^\"'>\\s]*)[\\\"\\']?[^>]*>)";
 23         const string _jsPattern = "(?<HTML><script[^>]*src\\s*=\\s*[\\\"\\']?(?<SRC>[^\"'>\\s]*)[\\\"\\']?[^>]*></script>)";
 24 
 25         private HttpApplication app;
 26         public HttpApplication App
 27         {
 28             get { return app; }
 29             set { app = value; }
 30         }
 31 
 32         private GZipStream _contentGZip;
 33         private DeflateStream _content_Deflate;
 34         private Stream _content;
 35         private CompressOptions _options;
 36         private bool disposed = false;
 37 
 38         private CompressWhitespaceFilter() { }
 39         public CompressWhitespaceFilter(Stream content, CompressOptions options)
 40         {
 41 
 42             responseHtml = new StringBuilder();
 43             if (options == CompressOptions.GZip)
 44             {
 45                 this._contentGZip = new GZipStream(content, CompressionMode.Compress, true);
 46                 this._content = this._contentGZip;
 47             }
 48             else if (options == CompressOptions.Deflate)
 49             {
 50                 this._content_Deflate = new DeflateStream(content, CompressionMode.Compress, true);
 51                 this._content = this._content_Deflate;
 52             }
 53             else
 54             {
 55                 this._content = content;
 56             }
 57             this._options = options;
 58         }
 59 
 60         public override bool CanRead
 61         {
 62             get { return this._content.CanRead; }
 63         }
 64 
 65         public override bool CanSeek
 66         {
 67             get { return this._content.CanSeek; }
 68         }
 69 
 70         public override bool CanWrite
 71         {
 72             get { return this._content.CanWrite; }
 73         }
 74 
 75         /// <summary>
 76         /// When overriding in a derived class, all buffers of the stream are cleared and all buffer data is written to the underlying device
 77         /// </summary>
 78         public override void Flush()
 79         {
 80             this._content.Flush();
 81             //Test
 82             //this._content.Dispose();
 83             //this._contentGZip.Dispose();
 84         }
 85 
 86 
 87         /// <summary>
 88         /// 重写Dispose方法,释放派生类自己的资源,并且调用基类的Dispose方法
 89         /// 使Gzip把缓存中余下的内容全部写入MemoryStream中,因为只有在Gzip流释放之后才能去承载对象中读取数据或判断数据大小
 90         /// </summary>
 91         /// <param name="disposing"></param>
 92         protected override void Dispose(bool disposing)
 93         {
 94             if (!this.disposed)
 95             {
 96                 try
 97                 {
 98                     if (disposing)
 99                     {
100                         // Release the managed resources you added in this derived class here.
101                         //xx.Dispose();
102                     }
103 
104                     //  Release the native unmanaged resources you added in this derived class here.
105                     //  xx.Close()
106 
107                     //if (_contentGZip != null)
108                     //    _contentGZip.Close();
109 
110                     //if (_content_Deflate != null)
111                     //    _content_Deflate.Close();
112 
113                     this._content.Close();
114                     this.disposed = true;
115                 }
116                 finally
117                 {
118                     // Call Dispose on your base class.
119                     base.Dispose(disposing);
120                 }
121             }
122         }
123 
124         public override long Length
125         {
126             get { return this._content.Length; }
127         }
128 
129         public override long Position
130         {
131             get
132             {
133                 return this._content.Position;
134             }
135             set
136             {
137                 this._content.Position = value;
138             }
139         }
140 
141         public override int Read(byte[] buffer, int offset, int count)
142         {
143             return this._content.Read(buffer, offset, count);
144         }
145 
146         public override long Seek(long offset, SeekOrigin origin)
147         {
148             return this._content.Seek(offset, origin);
149         }
150 
151         public override void SetLength(long value)
152         {
153             this._content.SetLength(value);
154         }
155 
156         public override void Write(byte[] buffer, int offset, int count)
157         {
158             byte[] data = new byte[count + 1];
159             Buffer.BlockCopy(buffer, offset, data, 0, count);
160             string s = System.Text.Encoding.UTF8.GetString(data);
161             s = Regex.Replace(s, "^\\s*", string.Empty, RegexOptions.Compiled | RegexOptions.Multiline);
162             s = Regex.Replace(s, "\\r\\n", string.Empty, RegexOptions.Compiled | RegexOptions.Multiline);
163             s = Regex.Replace(s, "<!--*.*?-->", string.Empty, RegexOptions.Compiled | RegexOptions.Multiline);
164             byte[] outdata = System.Text.Encoding.UTF8.GetBytes(s);
165             this._content.Write(outdata, 0, outdata.GetLength(0));
166         }
167 
168 
169         /// <summary>
170         /// Replcase stylesheet links with ones pointing to HttpHandlers that compress and cache the css
171         /// </summary>
172         /// <param name="html"></param>
173         /// <returns></returns>
174         public string ReplaceCss(string html)
175         {
176             // create a list of the stylesheets
177             List<string> stylesheets = new List<string>();
178             // create a dictionary used for combining css in the same directory
179             Dictionary<string, List<string>> css = new Dictionary<string, List<string>>();
180 
181             // create a base uri which will be used to get the uris to the css
182             Uri baseUri = new Uri(app.Request.Url.AbsoluteUri);
183 
184             // loop through each match
185             foreach (Match match in Regex.Matches(html, _cssPattern, RegexOptions.IgnoreCase))
186             {
187                 // this is the enire match and will be used to replace the link
188                 string linkHtml = match.Groups[0].Value;
189                 // this is the href of the link
190                 string href = match.Groups[2].Value;
191 
192                 // get a uri from the base uri, this will resolve any relative and absolute links
193                 Uri uri = new Uri(baseUri, href);
194                 string file = "";
195                 // check to see if it is a link to a local file
196                 if (uri.Host == baseUri.Host)
197                 {
198                     // check to see if it is local to the application
199                     if (uri.AbsolutePath.ToLower().StartsWith(app.Context.Request.ApplicationPath.ToLower()))
200                     {
201                         // this combines css files in the same directory into one file (actual combining done in HttpHandler)
202                         int index = uri.AbsolutePath.LastIndexOf("/");
203                         string path = uri.AbsolutePath.Substring(0, index + 1);
204                         file = uri.AbsolutePath.Substring(index + 1);
205                         if (!css.ContainsKey(path))
206                             css.Add(path, new List<string>());
207                         css[path].Add(file + (href.Contains("?") ? href.Substring(href.IndexOf("?")) : ""));
208                         // replace the origianl links with blanks
209                         html = html.Replace(linkHtml, "");
210                         // continue to next link
211                         continue;
212                     }
213                     else
214                         file = uri.AbsolutePath + uri.Query;
215                 }
216                 else
217                     file = uri.AbsoluteUri;
218                 string newLinkHtml = linkHtml.Replace(href, "css.axd?files=" + file);
219 
220                 // just replace the link with the new link
221                 html = html.Replace(linkHtml, newLinkHtml);
222             }
223 
224             StringBuilder link = new StringBuilder();
225             link.AppendLine("");
226             foreach (string key in css.Keys)
227             {
228                 link.AppendLine(string.Format("<link href='{0}css.axd?files={1}' type='text/css' rel='stylesheet' />", key, string.Join(",", css[key].ToArray())));
229 
230             }
231 
232             // find the head tag and insert css in the head tag
233             int x = html.IndexOf("<head");
234             int num = 0;
235             if (x > -1)
236             {
237                 num = html.Substring(x).IndexOf(">");
238                 html = html.Insert(x + num + 1, link.ToString());
239             }
240             return html;
241         }
242 
243         /// <summary>
244         /// Replcase javascript links with ones pointing to HttpHandlers that compress and cache the javascript
245         /// </summary>
246         /// <param name="html"></param>
247         /// <returns></returns>
248         public string ReplaceJS(string html)
249         {
250             // if the javascript is in the head section of the html, then try to combine the javascript into one
251             int start, end;
252             if (html.Contains("<head") && html.Contains("</head>"))
253             {
254                 start = html.IndexOf("<head");
255                 end = html.IndexOf("</head>");
256                 string head = html.Substring(start, end - start);
257 
258                 head = ReplaceJSInHead(head);
259 
260                 html = html.Substring(0, start) + head + html.Substring(end);
261             }
262 
263             // javascript that is referenced in the body is usually used to write content to the page via javascript, 
264             // we don't want to combine these and place them in the header since it would cause problems
265             // or it is a WebResource.axd or ScriptResource.axd
266             if (html.Contains("<body") && html.Contains("</body>"))
267             {
268                 start = html.IndexOf("<body");
269                 end = html.IndexOf("</body>");
270                 string head = html.Substring(start, end - start);
271 
272                 head = ReplaceJSInBody(head);
273 
274                 html = html.Substring(0, start) + head + html.Substring(end);
275             }
276 
277             return html;
278         }
279 
280         /// <summary>
281         /// Replaces the js in the head tag. (see ReplaceCss for comments)
282         /// </summary>
283         /// <param name="html"></param>
284         /// <returns></returns>
285         public string ReplaceJSInHead(string html)
286         {
287             List<string> javascript = new List<string>();
288             Dictionary<string, List<string>> js = new Dictionary<string, List<string>>();
289 
290             Uri baseUri = new Uri(app.Request.Url.AbsoluteUri);
291             foreach (Match match in Regex.Matches(html, _jsPattern, RegexOptions.IgnoreCase))
292             {
293                 string linkHtml = match.Groups[0].Value;
294                 string src = match.Groups[2].Value;
295 
296                 Uri uri = new Uri(baseUri, src);
297                 if (!Path.GetExtension(uri.AbsolutePath).Equals("js") && uri.AbsolutePath.Contains("WebResource.axd"))
298                     continue;
299                 if (uri.Host == baseUri.Host)
300                 {
301                     if (uri.AbsolutePath.ToLower().StartsWith(app.Context.Request.ApplicationPath.ToLower()))
302                     {
303                         int index = uri.AbsolutePath.LastIndexOf("/");
304                         string path = uri.AbsolutePath.Substring(0, index + 1);
305                         string file = uri.AbsolutePath.Substring(index + 1);
306                         if (!js.ContainsKey(path))
307                             js.Add(path, new List<string>());
308                         js[path].Add(file + (src.Contains("?") ? src.Substring(src.IndexOf("?")) : ""));
309                     }
310                     else
311                         javascript.Add(uri.AbsolutePath + uri.Query);
312 
313                 }
314                 else
315                     javascript.Add(uri.AbsoluteUri);
316                 html = html.Replace(linkHtml, "");
317             }
318 
319             int x = html.IndexOf("<head");
320             int num = html.Substring(x).IndexOf(">");
321             string link = "";
322 
323             foreach (string key in js.Keys)
324             {
325                 link = string.Format("<script src='{0}js.axd?files={1}' type='text/javascript' ></script>", key, string.Join(",", js[key].ToArray()));
326                 html = html.Insert(x + num + 1, link + Environment.NewLine);
327 
328             }
329             if (javascript.Count > 0)
330             {
331                 link = string.Format("<script src='js.axd?files={0}' type='text/javascript' /></script>", string.Join(",", javascript.ToArray()));
332                 html = html.Insert(x + num + 1, link + Environment.NewLine);
333             }
334             return html;
335         }
336 
337         /// <summary>
338         /// Replaces the js in the body. (see ReplaceCss for comments)
339         /// </summary>
340         /// <param name="html"></param>
341         /// <returns></returns>
342         public string ReplaceJSInBody(string html)
343         {
344             Uri baseUri = new Uri(app.Request.Url.AbsoluteUri);
345             foreach (Match match in Regex.Matches(html, _jsPattern, RegexOptions.IgnoreCase))
346             {
347                 string linkHtml = match.Groups[0].Value;
348                 string src = match.Groups[2].Value;
349 
350 
351                 Uri uri = new Uri(baseUri, src);
352                 if (!uri.AbsolutePath.EndsWith(".js") && !uri.AbsolutePath.Contains("WebResource.axd"))
353                     continue;
354                 string file = "";
355                 string path = "";
356                 if (uri.Host == baseUri.Host)
357                 {
358                     if (uri.AbsolutePath.ToLower().StartsWith(app.Context.Request.ApplicationPath.ToLower()))
359                     {
360                         int index = uri.AbsolutePath.LastIndexOf("/");
361                         path = uri.AbsolutePath.Substring(0, index + 1);
362                         file = uri.AbsolutePath.Substring(index + 1) + (src.Contains("?") ? src.Substring(src.IndexOf("?")) : "");
363                     }
364                     else
365                         file = uri.AbsolutePath + uri.Query;
366                 }
367                 else
368                     file = uri.AbsoluteUri;
369                 string newLinkHtml = linkHtml.Replace(src, path + "js.axd?files=" + file);
370                 html = html.Replace(linkHtml, newLinkHtml);
371             }
372             return html;
373         }
374 
375     }
376 }

在这里需要注意的是对GZIP 的释放,否则流数据会读取不到:

 1         /// <summary>
 2         /// 重写Dispose方法,释放派生类自己的资源,并且调用基类的Dispose方法
 3         /// 使Gzip把缓存中余下的内容全部写入MemoryStream中,因为只有在Gzip流释放之后才能去承载对象中读取数据或判断数据大小
 4         /// </summary>
 5         /// <param name="disposing"></param>
 6         protected override void Dispose(bool disposing)
 7         {
 8             if (!this.disposed)
 9             {
10                 try
11                 {
12                     if (disposing)
13                     {
14                         // Release the managed resources you added in this derived class here.
15                         //xx.Dispose();
16                     }
17 
18                     //  Release the native unmanaged resources you added in this derived class here.
19                     //  xx.Close()
20 
21                     //if (_contentGZip != null)
22                     //    _contentGZip.Close();
23 
24                     //if (_content_Deflate != null)
25                     //    _content_Deflate.Close();
26 
27                     this._content.Close();
28                     this.disposed = true;
29                 }
30                 finally
31                 {
32                     // Call Dispose on your base class.
33                     base.Dispose(disposing);
34                 }
35             }
36         }

 

对于C#非托管资源释放(Finalize/Dispose)方法理解:

http://www.cnblogs.com/lzhdim/archive/2009/11/04/1595845.html