2007-11-5 因项目需要,对每一个访问网站的请求要做原始数据记录,其中要包括几个要素: 1.客户端的IP 2.客户端请求的页面路径 3.客户端发出的请求头 4.服务器返回的正文内容。 在代码设计前分析了一下,前三个都很好解决,对于截获服务器返回的正文,准备用HttpResponse 对象中的Output 和 OutputStream 属性输出信息来解决。 可是在正式编码的过程中,发现Output和OutputStream 并不是想像中可以直接把数据转出取回,耗费了近两天的时间,想尽了一切办法可还是仅仅可以追加内容并无法读取。 在网上查阅到,对于HttpResponse 对象,仅仅可以使用过滤器来对其中将要输出的内容进行修改。 这个过滤器要继承自Stream 类,并要实现其中的虚方法。看来之前企图使用HttpWriter,TextWriter,Stream,HttpStream 这些类来转出数据完全是错误的。 现在有信心来截获服务器返回内容了,说干就干吧! 1.首先要建立一个简易过滤器。 代码如下: using System; using System.Collections.Generic; using System.Text; using System.Text.RegularExpressions; using System.IO; using System.Web; /**//// /// 定义原始数据EventArgs,便于在截获完整数据后,由事件传递数据 /// public class RawDataEventArgs : EventArgs { private string sourceCode; public RawDataEventArgs(string SourceCode) { sourceCode = SourceCode; } public string SourceCode { get { return sourceCode; } set { sourceCode = value; } } } //自定义过滤器 public class RawFilter : Stream { Stream responseStream; long position; StringBuilder responseHtml; /**//// /// 当原始数据采集成功后激发。 /// public event EventHandler OnRawDataRecordedEvent; public RawFilter(Stream inputStream) { responseStream = inputStream; responseHtml = new StringBuilder(); } //实现Stream 虚方法 Filter Overrides#region Filter Overrides public override bool CanRead { get { return true; } } public override bool CanSeek { get { return true; } } public override bool CanWrite { get { return true; } } public override void Close() { responseStream.Close(); } public override void Flush() { responseStream.Flush(); } public override long Length { get { return 0; } } public override long Position { get { return position; } set { position = value; } } public override int Read(byte[] buffer, int offset, int count) { return responseStream.Read(buffer, offset, count); } public override long Seek(long offset, SeekOrigin origin) { return responseStream.Seek(offset, origin); } public override void SetLength(long length) { responseStream.SetLength(length); } #endregion //关键的点,在HttpResponse 输入内容的时候,一定会调用此方法输入数据,所以要在此方法内截获数据 public override void Write(byte[] buffer, int offset, int count) { string strBuffer = System.Text.UTF8Encoding.UTF8.GetString(buffer, offset, count); //采用正则,检查输入的是否有页面结束符 Regex eof = new Regex("", RegexOptions.IgnoreCase); if (!eof.IsMatch(strBuffer)) { //页面没有输出完毕,继续追加内容 responseHtml.Append(strBuffer); } else { //页面输出已经完毕,截获内容 responseHtml.Append(strBuffer); string finalHtml = responseHtml.ToString(); //激发数据已经获取事件 OnRawDataRecordedEvent(this, new RawDataEventArgs(finalHtml)); //继续传递要发出的内容写入流 byte[] data = System.Text.UTF8Encoding.UTF8.GetBytes(finalHtml); responseStream.Write(data, 0, data.Length); } } } 至此,过滤器定义完毕了,接下来还需要把这个过滤器装配到HttpResponse 对象中。 为了能够截获整站的aspx 页面输出的内容,我们可以定义一个HttpModule 来完成。 代码如下: using System; using System.Web; using System.Collections.Generic; using System.Text; using System.IO; using System.Diagnostics; public class HttpRawDataModule : IHttpModule { IHttpModule 成员#region IHttpModule 成员 public void Dispose() { } public void Init(HttpApplication context) { //绑定事件,在对此请求处理过程全部结束后进行过滤操作 context.ReleaseRequestState += new EventHandler(context_ReleaseRequestState); } #endregion /**//// /// 对此HTTP请求处理的过程全部结束 /// /// /// void context_ReleaseRequestState(object sender, EventArgs e) { HttpApplication application = (HttpApplication)sender; //这里需要针对ASPX页面进行拦截,测试发现如果不这么做,Wap 访问站点图片容易显示为X,奇怪 string[] temp = application.Request.CurrentExecutionFilePath.Split(''.''); if (temp.Length > 0 && temp[temp.Length - 1].ToLower() == "aspx") { //装配过滤器 application.Response.Filter = new RawFilter(application.Response.Filter); //绑定过滤器事件 RawFilter filter = (RawFilter)application.Response.Filter; filter.OnRawDataRecordedEvent += new EventHandler(filter_OnRawDataRecordedEvent); } } /**//// /// 当原始数据采集到以后,入库 /// /// /// void filter_OnRawDataRecordedEvent(object sender, RawDataEventArgs e) { string allcode = e.SourceCode; WapSite.SiteDataClass wapdata = new WapSite.SiteDataClass(); wapdata.WriteRawDataLog(allcode); } } HttpModule 准备完毕,也装配上了过滤器,接下来还需要在配置文件中配置HttpModules配置节 ,把自定义的HttpModule 加入到HTTP处理管道中。 在Web.config 中增加配置节如下: 测试成功,能准确的获得服务器向客户端输出的HTML内容。 其中,在过滤器中,可以直接对即将要输出的内容做 对于字符串的任意处理。 而且采用这样的方式来对站点即将输出的内容做修改和采集,可以通过修改配置文件,随时打开和关闭,有很强的优越性和灵活性还有重用性。 记得看到过很多需要产生静态页面的网站,都是通过代码HttpWebRequest 向自己请求并记录返回的代码产生静态页面,不知道我当前介绍的方法是否更好写,比如需要产生静态页面时,不管是谁发出请求,由服务器检查自己是否有静态页面,否则产生静态页面,并转向。给出引子,希望大家还是自己开阔思路比较好。 这里我还想到一个额外的使用场景,比如入侵到一台支撑IIS 的服务器,上传自定义的过滤器和自定义的HttpModule 库,修改对方站点内的配置文件使之生效,就可以轻松做到窃取客户端输入内容和输出内容。不过修改配置文件不知道会不会让人容易发觉呀??? |