程式的生命週期往往比我們想像中的長,通常年紀愈大的程式包袱愈重,後面接手的人肩負的壓力也愈重,如果我們知道有一個字串叫 http://www.xxx.com ,現在因為政策的關係必須改成 https://www.xxx.com ,偏偏 http://www.xxx.com 被到處寫死在資料庫及程式原始碼裡面,除了把寫死的那些找出來改之外,我們還可以怎麼做?
在 ASP.NET 有一個古老的東西叫 Response.Filter
,下面簡述 ASP.NET 頁面傳到 Client 端的流程。
ASP.NET 頁面透過 HttpWriter 寫到串流物件,最終傳到使用者的電腦上呈現,而我們可以用 Response.Filter 在 HttpWriter 與串流物件中間插入一個我們自訂的串流物件,來攔截頁面內容趁機加工。
建立一個自訂的串流物件
我假設我自訂的串流物件叫 ReplacerStream 繼承 Stream 類別,其中在 Write 方法中將要準備寫入的頁面資料置換掉我們想要的特定字串。
public class ReplacerStream : Stream
{
private readonly Stream responseFilter;
private readonly Encoding contentEncoding;
public ReplacerStream(Stream responseFilter, Encoding contentEncoding)
{
this.responseFilter = responseFilter;
this.contentEncoding = contentEncoding;
}
...省略
public override void Write(byte[] buffer, int offset, int count)
{
var content = this.contentEncoding.GetString(buffer);
var contentBytes = this.contentEncoding.GetBytes(
Regex.Replace(content, "http://www.xxx.com", "https://www.xxx.com", RegexOptions.IgnoreCase));
this.responseFilter.Write(contentBytes, 0, contentBytes.Length);
}
}
建立一個 HttpModule
自訂串流物件建立好後,接著就要用它來取代 Response.Filter,我們需要在 ASP.NET 管線中的其中一個 Event 去做這件事情,至於要選擇在哪一個 Event 來做還滿重要的,因為當頁面已完成輸出之後 Response.Filter 基本上就沒啥用處了,因此我們必須要在輸出頁面前來替換掉 Response.Filter。
我選擇在 PostRequestHandlerExecute
這個 Event 裡面將 Response.Filter 取代為自訂的串流物件,而且只針對 ContentType 是 text/html
的回應內容。
public class ReplacerModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.PostRequestHandlerExecute += this.OnPostRequestHandlerExecute;
}
public void Dispose()
{
}
private void OnPostRequestHandlerExecute(object sender, EventArgs eventArgs)
{
var context = ((HttpApplication)sender).Context;
if (context.Response.ContentType.Equals("text/html"))
{
context.Response.Filter = new ReplacerStream(context.Response.Filter, context.Response.ContentEncoding);
}
}
}
在 Web.Config 中新增 HttpModule
寫好 HttpModule 後,在 Web.Config 中 <system.webServer>\<modules>
的位置加進去就行了。
原本的 HTML 頁面內容是 http 字串,在經過自訂的 Response.Filter 之後就都被置換成 https 了。
參考資料
< Source Code >