昨天我已經成功把:
GET /products HTTP/1.1
解析成:
Method = GET
Path = /products
Version = HTTP/1.1
也就是說,我的 Server 現在已經知道 Browser 想存取哪一個 Path。
但知道 Path 之後,又出現新的問題。
假設:
/hello
應該執行 Hello 的程式,而:
/products
應該執行 Products 的程式。
那 Framework 到底是怎麼做到:
Path
↓
找到對應的程式
↓
執行
這就是今天想破解的問題,Framework 到底怎麼根據 Path 找到正確的 Handler?
以前使用 ASP.NET Core 時,我可能會直接寫:
app.MapGet("/hello", () => "Hello");
或是使用 Controller:
[HttpGet("/products")]
public IActionResult Products()
{
...
}
Framework 就會自動知道:
/hello
→ 執行某段程式
/products
→ 執行另一段程式
以前我會覺得這好像是 Framework 本身「知道」網址要去哪裡。
但做到 Day12 後,我開始懷疑,Framework 應該不是天生就知道 /products 要去哪裡吧?
它應該必須先把 /products 和某一段程式建立關係,之後收到 Request 時才能找得到。
所以今天就從最笨、最直接的方法開始。
Day12 已經有:
string path = parts[1];
因此我第一個想到的方式就是直接判斷:
if (path == "/hello")
{
Console.WriteLine("執行 Hello Handler");
}
else if (path == "/products")
{
Console.WriteLine("執行 Products Handler");
}
else
{
Console.WriteLine("找不到對應的 Route");
}
接著依序測試三個網址。
測試一:/hello
Browser 輸入:
Server 解析出:
Method : GET
Path : /hello
Version : HTTP/1.1
接著得到:
執行 Hello Handler
成功。
測試二:/products
接著改成:
http://127.0.0.1:5000/products
這次得到:
Path : /products
執行 Products Handler
也成功。
測試三:不存在的 /abc
最後輸入:
這次:
Path : /abc
找不到對應的 Route
所以最簡單版本的 Routing 其實已經出現了:
Path
↓
if / else
↓
選擇不同 Handler



做到這裡,我第一次發現:
Routing 最核心的概念,其實就是「拿 Request 的資訊去做比對,再決定執行哪一段程式」。
至少在目前這個最小版本裡,就是:
/hello
→ Hello Handler
/products
→ Products Handler
其他
→ 找不到 Route
但是這種寫法馬上出現一個問題。
假設未來有:
/
/hello
/products
/users
/orders
/login
/logout
/profile
/settings
是不是就會變成:
if (...)
{
}
else if (...)
{
}
else if (...)
{
}
else if (...)
{
}
...
Route 越多,程式就越難整理。
所以我開始想,能不能不要一直寫 if / else,而是先把 Route 全部存起來?
接下來,我把 Route 改成:
var routes = new Dictionary<string, string>
{
["/hello"] = "Hello Handler",
["/products"] = "Products Handler"
};
這次不再寫:
if (path == "/hello")
而是建立一張對照表。
可以把它想成:
"/hello" → "Hello Handler"
"/products" → "Products Handler"
這已經很接近我第一次理解的:
Route Table
Dictionary 在這裡做了什麼?
Dictionary<TKey, TValue> 可以透過一個 Key 找到對應的 Value。
今天剛好可以拿來表示:
Path
↓
Handler
例如:
"/hello"
就是 Key。
而:
"Hello Handler"
就是 Value。
當收到:
path = "/products"
就可以拿 /products 去 Dictionary 裡面查。
TryGetValue() 又在做什麼?
我使用:
if (routes.TryGetValue(path, out string? handler))
{
Console.WriteLine($"找到 Route:{handler}");
}
else
{
Console.WriteLine("找不到對應的 Route");
}
目前可以把:
TryGetValue()
理解成:
拿指定的 Key 去 Dictionary 查詢,如果找得到,就把對應的 Value 取出來。
例如:
path = "/hello"
查:
routes
最後得到:
handler = "Hello Handler"
Microsoft 官方文件也說明,TryGetValue 可以在 Key 可能不存在時嘗試取得對應值,而不需要透過索引器取不到值後再處理例外。
把 Routing 改成 Dictionary 後,我又重新測試三次。
/hello
找到 Route:Hello Handler
/products
找到 Route:Products Handler
/abc
找不到對應的 Route
結果跟 if / else 一樣。
但是程式的設計已經不同。
原本:
Path
↓
很多 if / else
↓
Handler
現在:
Path
↓
Route Table
↓
查找
↓
Handler
Route 也開始變成「資料」,而不是全部散落在判斷式裡。
做到這裡,又發現一個問題。
目前:
["/hello"] = "Hello Handler"
右邊的:
"Hello Handler"
其實只是一個 string。
也就是說,找到 Route 後,我們實際取得的只是:
Hello Handler
這幾個字,它沒有真的執行任何程式。
所以這還不是真正的:
Path
→ Handler
比較像:
Path
→ Handler 的名字
因此我又把它往前推一步。



這次把:
Dictionary<string, string>
改成:
Dictionary<string, Action>
完整內容:
var routes = new Dictionary<string, Action>
{
["/hello"] = () =>
{
Console.WriteLine("Hello Handler 被執行了!");
},
["/products"] = () =>
{
Console.WriteLine("Products Handler 被執行了!");
}
};
這次 /hello 對應的不再是文字:
"Hello Handler"
而是真的一段可以被呼叫的程式。
Action 是什麼?
Action 是一種 Delegate。
目前我先把它理解成:
可以先保存,之後再呼叫執行的一段程式。
例如:
() =>
{
Console.WriteLine("Hello Handler 被執行了!");
}
這是一個 Lambda Expression。
它不是現在立刻執行,而是可以先被放進:
routes
裡面。
所以:
["/hello"] = () =>
{
Console.WriteLine("Hello Handler 被執行了!");
}
其實可以想成:
"/hello"
↓
一段真正可以執行的程式
C# 官方文件也有以 Dictionary 將 Key 對應到 Lambda/Action 的用法範例,這表示「把可執行行為當作 Value 保存」本身就是 C# Delegate 可以支援的模式,找到之後,真的執行 Handler
查 Route 的程式也改成:
if (routes.TryGetValue(path, out Action? handler))
{
Console.WriteLine("找到 Route,準備執行 Handler");
handler();
}
else
{
Console.WriteLine("找不到對應的 Route");
}
最重要的是:
handler();
這次不只是印出 Handler 的名字。
而是真的執行剛才存進去的程式。
現在的 Routing 流程
假設 Browser 傳來:
GET /products HTTP/1.1
Day12 已經可以解析:
Path = /products
接下來:
/products
↓
拿去 Route Table 查
↓
找到 Products Handler
↓
handler()
↓
執行
↓
Products Handler 被執行了!
這一次終於真的完成:
Path → Handler → Execute



完成自己的實驗後,再回頭查看 ASP.NET Core 官方 Routing 文件。
Microsoft 將 Routing 的工作描述為:
將傳入的 HTTP Request 進行比對,再把 Request 分派到應用程式中可執行的 Endpoint。
這和今天自己做出的概念非常接近:
Incoming Request
↓
取得 Path
↓
Match
↓
找到 Handler
↓
執行
ASP.NET Core 中真正的 Routing 當然遠比今天的複雜。
Dictionary<string, Action>
它還需要處理 Route Pattern、HTTP Method、Route Values、Endpoint Metadata 等更多資訊;官方文件也將 Endpoint 描述為應用程式中可執行的 Request Processing Code 單位。
但今天這個非常簡化的版本,已經抓到了 Routing 最核心的概念:Match Request → Dispatch to executable code
我們目前的 Mini Web Framework 已經一路走到:
Day10
TCP Server
↓
接受連線
Day11
↓
看到 HTTP Request
Day12
↓
解析 Method / Path / Version
Day13
↓
Routing
↓
找到 Handler
↓
執行 Handler
現在的核心已經變成:
Browser
↓
GET /hello HTTP/1.1
↓
HTTP Parsing
↓
Path = /hello
↓
Route Table
↓
Hello Handler
↓
Execute
這已經開始非常接近 Web Framework 最核心的骨架。
今天第一次自己實作 Routing 後,我才真正理解,Framework 並不是單純「知道網址要去哪裡」。
它需要先從 HTTP Request 中取得 Path,再利用事先建立好的 Route 資訊進行比對,最後找到真正可以處理這個 Request 的程式。
而且今天也從最簡單的:
if / else
一路改成:
Dictionary
最後再把 Handler 從:
一串名稱
變成:
真正可以執行的 Action
這個過程讓 Routing 從抽象的 Framework 功能,變成了一個我可以自己一步一步拆出來的流程。
但現在其實還有一個很大的問題。
雖然 Server 找到 Handler,也真的執行了:
Hello Handler 被執行了!
可是這些內容目前都只出現在:
Console
裡面。
Browser 還是沒有真正收到我們想回傳的頁面內容。
目前流程已經可以:
Browser
↓
Request
↓
Routing
↓
Handler
↓
執行
但是 Browser 發出 Request,本來就是在等待 Server 的回答。
那問題來了:
Server 執行完 Handler 之後,到底要怎麼把結果傳回 Browser?
也就是:
Request
↓
?
↓
Response