今天老周和大夥伴們聊聊有關 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);
兩種方法任選其一就可以了,不需要重複配置。
現在我們們執行一下示例。
預設開啟主頁是這樣的。
然後把 URL 改為 /ft3。
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,結果如下。
好了,今天的節目就到此了,下次有空我們們再聊。