【ASP.NET Core】URL重寫

東邪獨孤 發表於 2022-05-16
.Net

今天老周和大夥伴們聊聊有關 Url Rewrite 的事情,翻譯過來就是 URL 重寫。

這裡不得不提一下,URL重定向與重寫的不同。

1、URL重定向是客戶端(通常是瀏覽器)向伺服器請求地址A,然後伺服器要求重定向到B,返回狀態碼 301 或 302 給客戶端,並且夾帶一個 Location 的標頭,其值表示要重定向的目標 URL,即B;隨後客戶端再用B向伺服器發起請求,若成功,伺服器返回內容並夾帶狀態碼 200。

2、URL重寫只在伺服器上轉換URL,當客戶端請求地址A進入伺服器後,伺服器自行處理並轉向B。最後返回B地址的內容,夾帶狀態碼 200。此過程只在伺服器上發生,不需要與客戶端進行多次通訊。因此瀏覽器位址列中的URL也不會發生變化。

 

-------------------------------------------------- 超級分界線 ------------------------------------------------------

實現 URL 重寫不需要向服務容器註冊功能類,但可以在 Service 集合中配置 Options。你需要通過 RewriteOptions 物件來指定重定向的規則。定義規則的方法是實現 IRule 介面。此介面只有一個 ApplyRule 方法。在實現該方法時,根據需要修改 HttpContext.Request.Path 來設定新的 URL。

------------------------------------------------------------------------------------------------------------------------

下面我們們直接上示例,這裡我寫了一個簡單的URL重寫規則。

    public sealed class MyRule : IRule
    {
        public void ApplyRule(RewriteContext context)
        {
            var request = context.HttpContext.Request;
            var oldPath = request.Path;
            // 正規表示式來匹配
            var match = Regex.Match(oldPath, "/ft(\\d+)");
if(match == null || match.Success == false)
            {
                context.Result = RuleResult.ContinueRules;
                return;
            }
            // 找出匹配的分組
            var matchedval = match!.Groups[1].Value;
            // 新的URL
            PathString newPath = "/fight-action/" + matchedval;
            request.Path = newPath;  //修改為新URL
            context.Result = RuleResult.SkipRemainingRules;
        }
    }

這個規則是這樣的:客戶端請求 https://localhost/ft3,通過正規表示式,找出URL中的數值 3 ,然後改為新的 URL:https://localhost/fight-action/3。即

https://host/ft{數值}  ---->  https://host/fight-action/{數值}

為什麼要重寫 URL 呢,假設網站內部實現某功能的 URL 很長,很難看,很難記憶,使用者看到它就想抽它。為了讓使用者覺得好記好看,在公開的前臺 UI 或者 Web API 中使用一個更短更方便記憶的 URL。這又是為何呢?老周做個假設:假設你有個新聞系統,按照最初的開發設計,寫一篇新聞的 URL 是 http://killer/news_manager/addnew。嗯,這個路徑邏輯清晰、層次分明、表義明瞭(對開發人員來說,這樣好維護,模組化);可對使用者來說,他哪管你模組化還是分屍化,他就覺得這太長,不好記,也不好輸入。行,我們們給來個 URL 重寫,對外公開的 URL 變成 http://killer/addnews,而伺服器內部還是轉回原來的地址來處理,但客戶端是毫無察覺的。

還有一種情況是網站修改了,後臺的結構變了,API 的結構變了,可你懶得把所有前臺 UI 改動。於是,你也可以寫個 URL 重寫規則,讓舊 URL 自動轉到新的 URL 上,同樣客戶端毫無察覺的(明修____,暗渡____)。

 

回到 ASP.NET Core 主體程式碼,這裡為了節省體力和腦力,老周就不做 HTML 頁了,直接 MapGet 代表每個頁面。

// 模擬一些路徑
app.MapGet("/", () => "燕雙鷹戰鬥儀");
app.MapGet("/fight-action/{mode}", (int mode) => mode switch
{
    0     => "全自動掃射裝載中……",
    1     => "裝逼兩分鐘,開掛三小時",
    2     => "無限子彈碾壓",
    3     => "我賭你的槍裡沒有子彈",
    4     => "腦漿警告",
    5     => "像你這樣的人應該怎麼改變,不會改變的,只有X",
    _     => "外掛已到期"
});

路徑 /fight-action 後面一段是路由引數 mode,然後這個數值會隨同引數 mode 傳入lambda 表示式,內部根據 mode 的值返回不同的字串。

 

使用 URL 重寫我們不需要向服務容器新增依賴注入物件,直接在 HTTP 管線上以中介軟體方式 Use 一下即可。

app.UseRewriter();

不過,這個無引數呼叫是未新增任何自定義重寫規則的,我們們有兩種方法新增規則。

第一種方法:保持 UseRewriter 方法無引數呼叫,使用服務容器來 Config 一下 RewriteOptions 選項類。這個方法實際是在服務容器中生成了 IOptions<RewriteOptions> 物件,中介軟體類 RewriteMiddleware 的建構函式會注入這個選項類例項。

    // 以下為 .NET 原始碼
    public RewriteMiddleware(
        RequestDelegate next,
        IWebHostEnvironment hostingEnvironment,
        ILoggerFactory loggerFactory,
        IOptions<RewriteOptions> options)
    {
        // ……
        _next = next;
        _options = options.Value;
        _fileProvider = _options.StaticFileProvider ?? hostingEnvironment.WebRootFileProvider;
        _logger = loggerFactory.CreateLogger<RewriteMiddleware>();
    }

現在,我們們把剛剛寫的 MyRule 規則配置一下。

var builder = WebApplication.CreateBuilder(args);
// 配置 rewrite options
builder.Services.Configure<RewriteOptions>(rwo =>
{
    rwo.Add(new MyRule());
});
var app = builder.Build();

 

第二種方法:在HTTP 管理線中呼叫 UseRewriter 方法前,直接 new 一個 RewriteOptions 例項,然後新增剛剛寫的規則。最後呼叫 UseRewriter 方法的帶引數版本,把 options 傳給它即可。

RewriteOptions rwopt = new();
rwopt.Add(new MyRule());
app.UseRewriter(rwopt);

 

兩種方法任選其一就可以了,不需要重複配置。

現在我們們執行一下示例。

預設開啟主頁是這樣的。

【ASP.NET Core】URL重寫

 

 

然後把 URL 改為 /ft3。

【ASP.NET Core】URL重寫

 

 

URL 已經跳轉,不過瀏覽器位址列不會有變化,而且不會返回 301、302 給客戶端,因為這個跳轉過程是在伺服器上完成的。

 

------------------------------------ 未知分界線 -------------------------------------------

其實,RewriteOptions 補充了一些擴充套件方法,使得我們們在簡單重寫URL(不需要特複雜的邏輯分析,用正則就能搞定的)時可以不去實現 IRule 介面。故,我們們這個示例可以改成這樣更簡潔的實現。

var builder = WebApplication.CreateBuilder(args);
// 配置 rewrite options
builder.Services.Configure<RewriteOptions>(rwo =>
{
    rwo.AddRewrite("ft(\\d+)", "fight-action/$1", true);
});
var app = builder.Build();

第一個引數是正規表示式,括號中表示捕捉為一個分組,第二個引數是新的 URL,其中“$1”表示引用正規表示式中捕捉的分組編號,我們們這個正規表示式中只有一個分組,即捕捉數值的 \d+,所以用 $1 引用它;如果有其他分組,那就依此類推,$2、$3、$4。它的意思就是引用捕捉到這個分組的值。如,匹配 ft2,捕捉到數值 2,然後替換 $1,使新的URL為 fight-action/2。注意在匹配和替換的 URL 都不用“/”開頭,反正類庫在處理時也會刪除“/”的,所以我們就沒必須加“/”。

 

再測試一下。

執行後,轉到 /ft5,結果如下。

【ASP.NET Core】URL重寫

 

 

好了,今天的節目就到此了,下次有空我們們再聊。