iT邦幫忙

2026 iThome 鐵人賽

DAY 18
0

前幾天,我們已經把 Request 慢慢整理成比較像 Framework 會使用的形式。

例如原本 Browser 傳來:

GET /hello HTTP/1.1

我們可以從裡面取得:

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

接著也開始理解 Headers。

但如果今天網址不是:

/hello

而是:

/hello?name=Andy

那問題就來了。

這整段到底都是 Path 嗎?

如果不是,那 Framework 要怎麼知道:

/hello

是網址真正的 Path,

而:

name=Andy

又是另外一種資料?

所以今天想理解的問題就是:

網址後面的 ?name=Andy,到底是什麼?

Framework 又是怎麼把它解析出來的?

💭 我原本以為……

以前看到網址:

/products?id=10

或:

/search?q=book

大概知道問號後面是在帶一些資料。

但以前不太會特別去想:

這些資料到底是怎麼進到程式裡的?

尤其在 Framework 中,我們常常可以很方便地取得 Query 的值。

所以很容易有一種錯覺:

好像 Browser 本來就是把:

Path = /hello
name = Andy

這樣整理好之後再傳過來。

但其實不是。

Browser 真正送來的 Request Line 可能會是:

GET /hello?name=Andy HTTP/1.1

所以 Server 一開始看到的,其實還是一整段:

/hello?name=Andy

Framework 必須自己把它拆開。

網址中的 ? 到底代表什麼?

例如:

/hello?name=Andy

可以先拆成:

/hello

Path

name=Andy

Query String

問號 ? 可以看成一個分界。

它前面的部分代表 Request 要存取的 Path。

它後面的部分則是 Query String。

所以:

/hello?name=Andy

並不是一整個單純的 Path。

而是:

Path = /hello

Query String = name=Andy

這件事對 Routing 很重要。

為什麼對 Routing 很重要?

假設我們的 Route Table 裡面有:

/hello

代表這個 Route 可以找到 Hello Handler。

但 Browser 今天送來:

/hello?name=Andy

如果 Framework 直接拿整段:

/hello?name=Andy

去 Route Table 裡找,

可能就會變成:

Route Table 裡只有 /hello

收到 /hello?name=Andy

比對失敗

找不到 Route

但其實使用者真正想去的 Route 還是:

/hello

name=Andy 只是額外附加的資料。

所以在 Routing 之前,Framework 應該先把 Request Target 拆開。

GET /hello?name=Andy HTTP/1.1

Request Target

/hello?name=Andy

拆開

Path = /hello
Query String = name=Andy

這樣 Router 才能拿:

/hello

去進行 Route Matching。

Query String 裡面又是什麼?

如果只有:

name=Andy

其實還算簡單。

可以理解成:

Key = name
Value = Andy

但 Query String 不一定只有一個值。

例如:

/products?category=book&page=2

問號後面是:

category=book&page=2

這裡其實有兩組資料:

category = book
page = 2

其中:

&

可以理解成不同 Query Parameter 之間的分隔符號。

而:

=

則是 Key 和 Value 之間的分隔。

所以:

category=book&page=2

可以一步一步拆成:

category=book
page=2

再變成:

category → book
page → 2

最後 Framework 就可以整理成比較容易使用的形式。

例如:

request.Query["category"] = "book"

request.Query["page"] = "2"

Query String 和 Query Parameter 一樣嗎?

這兩個很容易混在一起。

Query String 可以理解成問號後面的整段原始文字。

例如:

/products?category=book&page=2

其中 Query String 是:

category=book&page=2

而裡面的:

category=book

和:

page=2

則可以視為個別的 Query Parameters。

所以流程可以理解成:

URL

/products?category=book&page=2

Path = /products

Query String = category=book&page=2

解析

category = book
page = 2

HttpRequest 可以怎麼變得更完整?

前面我們規劃的 HttpRequest 第一版,可能只有:

Method
Path
Version

接著 Day17 加入 Headers。

現在 Day18 就可以再多出:

QueryString

甚至進一步有:

Query

最後概念可能變成:

HttpRequest
├── Method
├── Path
├── Version
├── Headers
├── QueryString
└── Query

例如 Browser 傳:

GET /hello?name=Andy HTTP/1.1

Framework 解析後:

Method = GET
Path = /hello
Version = HTTP/1.1
QueryString = name=Andy
Query["name"] = Andy

這就比直接保留:

/hello?name=Andy

好使用很多。

為什麼不要每次自己 Split?

理論上,Handler 自己做也可以。

例如收到:

/hello?name=Andy

之後,每一個 Handler 都自己找 ?,再自己找 =。

但這樣會有一個和前幾天完全一樣的問題:

Parsing 的細節會散落到整個 Framework。

例如:

Router

自己拆 Query

Handler A

自己拆 Query

Handler B

也自己拆 Query

最後每個地方都必須知道 URL 的底層格式。

比較合理的方向應該是:

Raw Request

HttpRequest Parsing

Path
QueryString
Query

Routing / Handler 使用

這樣只有 HttpRequest 需要知道 Query 的解析方式。

其他地方只需要:

request.Path

或:

request.Query["name"]

就可以了。

🧠 今天的認知更新

以前的我可能會把:

/hello?name=Andy

整體都當成網址。

現在我開始可以拆得更細:

/hello?name=Andy

Request Target

Path + Query String

Path = /hello
Query String = name=Andy

Query Parameter

name = Andy

也就是說:

網址列裡看到的一整段內容,到了 Framework 裡面其實會被拆成很多不同概念。

這些概念最後又會被整理成 Request 物件裡不同的 Property。

📚 官方怎麼說?

HTTP Request 的 Request Target 可以包含 Path 和 Query。

在 Web Framework 中,通常也會把 Path 與 Query 分開提供。

例如 ASP.NET Core 的 Request 物件中,就有 Path 和 QueryString 相關資訊,而 Query 也可以用來取得已解析的 Query Parameter。

所以今天自己思考的方向,其實和真正 Framework 的設計很接近。

Framework 並不是直接把整段:

/hello?name=Andy

當成單純的 Route。

而是先把不同資訊拆開,再分別交給 Routing 或其他功能使用。

🔍 今日最大的發現

今天最大的發現是:

Route 和 Query 其實是兩件不同的事情。

例如:

/hello?name=Andy

真正拿去 Routing 的應該是:

/hello

而:

name=Andy

則是這次 Request 附帶的額外資料。

所以整個流程應該更像:

Browser

GET /hello?name=Andy HTTP/1.1

Parsing

Path = /hello
Query = name: Andy

Routing 使用 Path

Handler 使用 Query

這也讓我更理解,Framework 不只是「幫我收到 Request」。

它還要把 Request 裡不同用途的資料分類整理。

Framework 收到:

/hello?name=Andy

之後,不應該把它當成一整個 Path。

而是先拆成:

Path = /hello

Query String = name=Andy

接著再把 Query String 解析成:

name = Andy

最後 Routing 使用 Path,而 Handler 可以使用 Query。

🛠 與 Mini Web Framework 的關聯

目前 Mini Web Framework 的 Request 已經越來越不像單純的一串字。

原本:

Raw HTTP Request

後來慢慢變成:

HttpRequest
├── Method
├── Path
├── Version
├── Headers
└── Query

整個 Request 流程也開始變成:

Browser

Raw HTTP

HttpRequest.Parse()

Method
Path
Version
Headers
Query

Routing

Handler

這代表我們自己做的 Framework 開始真正負責:

把 HTTP 底層格式轉換成程式比較好使用的資料。

結尾

今天雖然只是研究網址後面的一個問號,但其實讓我更清楚理解 Request Parsing 的重要性。

以前看到:

/hello?name=Andy

可能只會覺得這就是一個網址。

但現在可以明確拆成:

/hello

Path

name=Andy

Query String

name → Andy

Query Parameter

而且這個拆分會直接影響 Routing。

因為真正決定要執行哪一個 Handler 的是 Path,而 Query 則是 Request 額外攜帶的資料。

也就是:

Path 決定「要去哪裡」

Query 描述「這次還帶了什麼資料」

當 Framework 把這些底層資訊整理好後,開發者才可以方便地使用:

request.Path

和:

request.Query

而不需要每一次都重新解析 URL。

❓下一個 Why

現在我們已經開始把 Request 拆得越來越完整。

Method、Path、Version、Headers、Query 都慢慢出現了。

但還有一個很重要的東西一直沒真正處理:

Request Body。

像 POST 表單或 JSON 資料,通常就不會全部塞在網址裡。

那 Browser 傳來的 Body 到底放在哪裡?

Server 又要怎麼知道 Body 有多長、要怎麼把它讀完整?


上一篇
HTTP Headers 那麼多,Framework 到底怎麼把它們解析出來?
系列文
程式設計沒有告訴你的事:30 天破解每一個 Why18
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言