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 庫,修改對方網站內的設定檔使之生效,就可以輕鬆做到竊取用戶端輸入內容和輸出內容。不過修改設定檔不知道會不會讓人容易發覺呀??? |