﻿# 如何讓在 Reverse Proxy 之後的網站正常運行 (URL Rewrite)

前幾天看到了[這篇文章](https://weblogs.asp.net/owscott/troubleshoot-asp-net-errors-remotely-while-appearing-local)感覺非常實用，也因此讓我想起之前曾經遇過的另一個問題，由於我公司內部的測試環境經常需要對外提供服務給客戶測試，但是對外的固定 IP 只有一個，且公司內部的測試機又好幾台，如果不用 Reverse Proxy 機制就無法對外共用一個 80 Port ( HTTP ) 給客戶使用，也因此我們用這種方式運作很久了，不過這樣的部署架構有個小問題，也就是無法正確取得用戶端的來源 IP 位址。

我們所做的網站在執行時經常需要取得用戶端的來源 IP 位址 ( 透過 REMOTE\_ADDR 伺服器變數 )，網站透過 Reverse Proxy 存取之後，所有在後端(內部網路)的網站所得到的 REMOTE\_ADDR 伺服器變數都會變成 Reverse Proxy 主機的 IP 位址，如果真要能取得用戶端的 IP 位址就必須改用 X-Forwarded-For 這個 HTTP 標頭(Header)才能拿到正確的來源 IP 位址。

其 HTTP 要求的流程如下：

![alt](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/2e3fdfb4b6d72820dbef-image_346.png "title")

由於我們只有在測試環境下才會需要特別取得 X-Forwarded-For 標頭的資料，未來程式部署到正式機後就不用做這個判斷才對，也因此最好的測試情況就是讓開發人員撰寫程式時盡可能的不要顧慮環境的差異，完全以「正式機」的模擬情境進行開發，如此一來才不會容易寫出有問題的程式或不安全的程式碼。

舉個例子來說，如果你的程式**很聰明**會自動判斷 REMOTE\_ADDR 伺服器變數或 X-Forwarded-For 標頭，當有 X-Forwarded-For 標頭的時候自動以這個標頭所顯示的 IP 來處理（例如記錄到資料庫），那麼你就犯了一個非常大的錯誤，也就是你的程式相信了 HTTP 標頭所傳來的資料，基於安全的程式開發來說，所有從用戶端(Client)傳來的任何資料都是不可信任的，也因此你絕對不能相信 X-Forwarded-For 標頭所取得的 IP 就是用戶端真正的 IP 位址！！

為了解決這個問題，我們就可以透過 [IIS URL Rewrite Module](http://www.iis.net/download/urlrewrite) 輕易的幫我們克服這個難題，以下就是在如上圖的「伺服器端」上所做的 [URL Rewrite](http://www.iis.net/download/urlrewrite) 模組設定：

1\. 先選取你要設定 URL Rewrite 的站台，並用滑鼠雙擊點選 URL Rewrite 進入設定

![alt](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/4724a518b6870653fb76-image_347.png "title")

2\. 接著先來設定有哪些**伺服器變數**(Server Variables)可以在之後設定的 Rewrite 規則中出現

![alt](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/e79f3fb198a4171fb3ce-image_348.png "title")

新增一個 REMOTE\_ADDR 伺服器變數進去

![alt](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/e67b9e4069d451f44058-image_349.png "title")

設定完後的畫面如下：

![alt](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/2c9f61b2e522e2e74225-image_350.png "title")

接著回上一層準備新增規則

![alt](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/19ccb7891707d121c465-image_351.png "title")

3\. 新增一個 URL Rewrite 規則

![alt](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/b6e090b28175ec326c9b-image_352.png "title")

新增一個空白規則(Blank rule)

![alt](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/2eff3f24eb08337ce3b2-image_353.png "title")

接著輸入以下參數： （點圖可放大）

[![image](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/8813f4b633ffc356d93a-image_thumb_43.png "image")](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/62fddf256cbddd68f754-image_354.png)

最後在套用之後即大功告成

![alt](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/ed81349d4835cfef56f0-image_355.png "title")

\----

這時我們來執行一個簡單的測試程式，將 REMOTE\_ADDR 伺服器變數印到頁面上：

```
<%@ Page Language="C#" %><%Response.Write(Request.ServerVariables["REMOTE_ADDR"] );%>
```

執行後你會發現這樣的結果並非預期的，因為 REMOTE\_ADDR 伺服器變數是不包含埠號 (Port) 的：

![alt](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/fd142c096f6fdece042b-image_356.png "title")

如果你覺得這點並不影響開發或紀錄的話，那麼你可以自動忽略這點小差異，但如果你像我一樣龜毛，那就要用較為複雜的方式解決了，也就是自行開發一個 [Custom Rewrite Provider](https://learn.microsoft.com/en-us/iis/extensions/url-rewrite-module/developing-a-custom-rewrite-provider-for-url-rewrite-module) 給 URL Rewrite Module 使用！

雖然 URL Rewrite 2.0 內建了 3 個[字串操作函式](https://learn.microsoft.com/en-us/iis/extensions/url-rewrite-module/url-rewrite-module-configuration-reference)，其中包括 ToLower (將字串轉小寫)、UrlEncode 與 UrlDecode 等等，如果我們要將輸出的埠號部分移除的話透過內建的 String function 是辦不到的，接下來我就要自行開發一組 **StripPort** 函式專門用來將字串中含有埠號的部分給移除掉。

1\. 建立專案與編譯的方式請參考 [Developing a Custom Rewrite Provider for URL Rewrite Module](https://learn.microsoft.com/en-us/iis/extensions/url-rewrite-module/developing-a-custom-rewrite-provider-for-url-rewrite-module) 文件，我最後撰寫完成的原始碼如下：

```csharp
using System;
using System.Collections.Generic;
using Microsoft.Web.Iis.Rewrite;

namespace StripPortProvider
{
    public class StripPortProvider : IRewriteProvider
    {
        #region IRewriteProvider Members
        public void Initialize(IDictionary<string, string> settings, IRewriteContext rewriteContext)
        {
        }

        public string Rewrite(string value)
        {
            int colonIndex = value.IndexOf(':');

            if (colonIndex == -1)
            {
                return value;
            }
            else
            {
                return value.Substring(0, colonIndex);
            }
        }
        #endregion
    }
}
```

2.  在 IIS 新增 Rewrite Provider

![alt](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/b4993b2dbeb3d9dda729-image_357.png "title")

![alt](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/4851b11de012d94fb148-image_358.png "title")

你可以自行定義名稱，並透過下拉選單的方式選取你自訂的 Rewrite Provider：  
**注意**：你自訂的 Rewrite Provider 必須要註冊進 GAC 才能透過 IIS 的管理介面選取！

![alt](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/343e0fd259ba3934a9aa-image_359.png "title")

第一次選取的時候會花上一些時間讀取 GAC 中可以被選取的組件 ( 需等待 10 ~ 40 秒左右 )

![alt](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/4a9498a3586471f8b589-image_360.png "title")

設定完成後的畫面如下：

![alt](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/fcfeb7747932745dad65-image_361.png "title")

3\. 最後就是回去修改之前設定的那一條規則：修正 Reverse Proxy 所造成的 REMOTE\_ADDR 錯亂

-   將原本設定的變數：{HTTP\_X\_FORWARDED\_FOR}
-   修改並加上自訂的 StripPort 函式：{StripPort:{HTTP\_X\_FORWARDED\_FOR}}

![alt](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/5d10681aa075e614a608-image_362.png "title")

修改完成後的設定畫面如下：  
**注意**：請記得按下「**套用**」此規則的異動才會生效喔！

![alt](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/9a198acdecad8dc78b55-image_363.png "title")

如此一來，就能將不需要的埠號部分給清除乾淨了：

![alt](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/d96c3b9255dc194b3e8c-image_364.png "title")

我只能說 IIS7 這個平台真的太強大了，而且 [URL Rewrite 2.0](https://blogs.iis.net/ruslany/iis-url-rewrite-2-0-release-to-web/) 所提供的 Custom Rewrite Provider 擴充方式也非常簡單，短短不到幾分鐘的時候就能開發出我要的功能，真是不可多得的好物。

**程式碼下載**

-   [StripPortProvider-v1.0.0.0.zip](http://cid-ce50199d9432ea57.office.live.com/self.aspx/%e5%85%ac%e9%96%8b/StripPortProvider-v1.0.0.0.zip)

**相關連結**

-   [URL Rewrite : The Official Microsoft IIS Site](http://www.iis.net/download/urlrewrite)
-   [Using the URL Rewrite Module](https://learn.microsoft.com/en-us/iis/extensions/url-rewrite-module/using-the-url-rewrite-module)
-   [Using URL Rewrite Module 2.0](https://learn.microsoft.com/en-us/iis/extensions/url-rewrite-module/using-url-rewrite-module-20)
-   [URL Rewrite Module 2.0 Configuration Reference](https://learn.microsoft.com/en-us/iis/extensions/url-rewrite-module/url-rewrite-module-20-configuration-reference)
-   [URL Rewrite Module : Installing and Configuring IIS 7 : The Official Microsoft IIS Site](https://web.archive.org/web/20120826032134/http://learn.iis.net:80/page.aspx/734/url-rewrite-module)
-   [IIS URL Rewrite Module](http://technet.microsoft.com/en-us/library/ee215194.aspx) ( TechNet )
-   [URL Rewrite Module Configuration Reference #String functions](https://learn.microsoft.com/en-us/iis/extensions/url-rewrite-module/url-rewrite-module-configuration-reference)
-   [Developing a Custom Rewrite Provider for URL Rewrite Module](https://learn.microsoft.com/en-us/iis/extensions/url-rewrite-module/developing-a-custom-rewrite-provider-for-url-rewrite-module)
-   [Troubleshoot ASP.NET Errors Remotely–while Appearing Local](https://weblogs.asp.net/owscott/troubleshoot-asp-net-errors-remotely-while-appearing-local)
