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
Xamarin