iT邦幫忙

2026 iThome 鐵人賽

DAY 13
0

昨天我已經成功把:

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 輸入:

http://127.0.0.1:5000/hello

Server 解析出:

Method : GET
Path : /hello
Version : HTTP/1.1

接著得到:

執行 Hello Handler

成功。

測試二:/products

接著改成:

http://127.0.0.1:5000/products

這次得到:

Path : /products
執行 Products Handler

也成功。

測試三:不存在的 /abc

最後輸入:

http://127.0.0.1:5000/abc

這次:

Path : /abc
找不到對應的 Route

所以最簡單版本的 Routing 其實已經出現了:

Path

if / else

選擇不同 Handler

https://ithelp.ithome.com.tw/upload/images/20260814/20183395hBLyd2HU9Y.png

https://ithelp.ithome.com.tw/upload/images/20260814/20183395mjgD2VyChd.png

https://ithelp.ithome.com.tw/upload/images/20260814/20183395IBMwqe7YLH.png


做到這裡,我第一次發現:

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")

而是建立一張對照表。

可以把它想成:

Key Value

"/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 的名字

因此我又把它往前推一步。

https://ithelp.ithome.com.tw/upload/images/20260814/20183395tw0hOfcIiQ.png

https://ithelp.ithome.com.tw/upload/images/20260814/20183395UIX6nm0LHZ.png

https://ithelp.ithome.com.tw/upload/images/20260814/20183395hc21voJD2P.png


這次把:

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

https://ithelp.ithome.com.tw/upload/images/20260814/20183395JzEuhggAxN.png

https://ithelp.ithome.com.tw/upload/images/20260814/20183395R90X2tWiuA.png

https://ithelp.ithome.com.tw/upload/images/20260814/20183395qDKThHr2P9.png


完成自己的實驗後,再回頭查看 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


上一篇
Framework 如何看懂 GET /products HTTP/1.1?
下一篇
Server 到底要回傳什麼,Browser 才看得懂?
系列文
程式設計沒有告訴你的事:30 天破解每一個 Why15
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言