做到昨天,Mini Web Framework 的核心架構已經差不多成形了。
目前整體流程可以想成:
Browser
↓
MiniWebServer
↓
HttpRequest
↓
Middleware Pipeline
↓
Router
↓
Handler
↓
HttpResponse
↓
Browser
一路做到這裡,很容易產生一種感覺:
功能都有了,那應該就完成了吧?
但其實還不能這麼快下結論。
因為:
可以跑一次
和
真的可以穩定使用
其實是兩回事。
例如:
/hello 可以成功
不代表 /products 一定成功。
成功的 Route 可以執行
不代表不存在的 Route 一定會正確回 404。
第一次 Request 正常
也不代表第二次、第三次 Request 還會正常。
Middleware 看起來有執行
也不代表每一次 Request 都真的按照正確順序經過。
所以今天不再新增新的 Framework 功能。
今天想做的是:
完整驗證前面建立的所有核心流程。
也就是:
Framework 組好了,但它真的能用嗎?
💭 我原本以為……
以前寫程式時,很容易把:
Build succeeded
或:
程式成功跑出一次結果
當成:
完成。
例如:
輸入 /hello
Browser 顯示 Hello!
就會覺得:
Routing 做好了。
但現在開始發現,Framework 是一個會一直處理不同 Request 的系統。
所以只測試一種 Happy Path 根本不夠。
至少要問:
正常 Request 可以嗎?
不同 Route 可以嗎?
錯誤 Route 可以嗎?
連續 Request 可以嗎?
Middleware 每次都會執行嗎?
Response Status 正確嗎?
如果某一層出錯怎麼辦?
這些問題才比較接近:
這個 Framework 到底能不能真正運作。
什麼是整合測試?
前面我們很多實驗都是測單一概念。
例如:
Day12
↓
測 Request Parsing
Day13
↓
測 Routing
Day14
↓
測 Response
Day15
↓
測 HttpResponse
這些都比較像:
把某一個功能單獨拿出來看。
但今天要做的是:
把所有東西接在一起後,再從最外面測一次。
例如:
Browser
↓
Request
↓
Middleware
↓
Router
↓
Handler
↓
Response
↓
Browser
如果這整條流程可以正常跑通,就代表各個元件之間至少能一起合作。
這種「多個元件一起驗證」的思路,就是今天最重要的地方。
第一個要測什麼?
最基本的當然還是:
/hello
例如:
GET /hello HTTP/1.1
預期流程:
Request 進入 Server
↓
HttpRequest 解析成功
↓
Middleware 執行
↓
Router 找到 /hello
↓
Hello Handler
↓
200 OK
↓
Browser 顯示 Hello!
這個測試雖然簡單,但它其實一次驗證了很多東西。
包括:
TCP
Request Parsing
Routing
Handler
Response
Middleware
只要其中一層有問題,最後都可能看不到 Hello。
第二個測試:/products
接著不能只測一條 Route。
還要測:
/products
因為如果:
/hello 可以
/products 不行
就可能代表 Router 註冊或匹配其實還有問題。
所以第二個測試應該確認:
GET /products HTTP/1.1
↓
Router
↓
Products Handler
↓
200 OK
↓
Products Page
這可以證明:
Framework 不是只對某一條 Route 寫死。
而是真的可以處理不同 Route。
第三個測試:不存在的 Route
這個測試非常重要。
例如:
/abc
Route Table 裡沒有。
Framework 不應該:
Crash
卡住
或完全不回應。
應該清楚地:
找不到 Route
↓
404 Not Found
↓
回給 Browser
所以:
GET /abc HTTP/1.1
↓
Router Match Failed
↓
404 HttpResponse
↓
Browser 顯示 404 Not Found
如果這個測試成功,表示:
Framework 已經不只會處理成功情況。
也開始可以處理最基本的錯誤情況。
200 和 404 都要確認
不能只看 Browser Body。
因為:
Hello!
和:
404 Not Found
只是 Body。
真正 HTTP Response 還包含:
Status Code。
所以理想上也應該確認:
/hello
↓
200 OK
/products
↓
200 OK
/abc
↓
404 Not Found
這樣才能知道:
Framework 不只是顯示不同文字。
而是真的建立了不同的 HTTP Response。
Middleware 要怎麼測?
Day28 加入 Middleware 後,如果只是看到:
有印一行 Logging
其實還不夠。
更重要的是確認執行順序。
例如預期:
Request 開始:GET /hello
↓
Handler
↓
Request 完成:GET /hello
這樣才能證明:
Middleware 真的是:
Before
↓
next
↓
After
而不是單純在某個地方:
Console.WriteLine()
所以 Console 的輸出順序本身就可以成為一種測試。
例如:
[Logging] Request Start: GET /hello
找到 Route
執行 Hello Handler
已回傳 200 OK
[Logging] Request End: GET /hello
如果這個順序正確,就可以更有信心:
Pipeline 真的有被組起來。
連續 Request 為什麼要測?
這是我們自己寫 Server 時特別重要的一點。
因為 Framework 不是只服務一個 Request 就關掉。
它需要:
Request 1
↓
處理完成
Request 2
↓
繼續處理
Request 3
↓
繼續處理
例如可以依序測:
/hello
/products
/abc
再回到:
/hello
如果 Server 每次都可以繼續:
正在等待 Browser…
就代表最基本的 Server Loop 還正常。
如果第一個 Request 成功後程式就結束,
那就不是一個能持續服務的 Web Server。
之前學過的 while(true) 在這裡又重新變得重要。
Browser 為什麼可能送出不只一次 Request?
前面實驗時已經看過,有時候 Browser 可能出現多個連線或額外 Request。
所以測試時不能看到 Console 多出一行就立刻認為:
Framework 壞了。
Browser 本身可能有:
重新連線
額外資源請求
其他行為。
更重要的是觀察:
每一份被 Framework 收到的 Request,有沒有被正常處理。
這也是自己手刻 Server 和使用成熟 Framework 最大的差異之一。
以前這些細節幾乎都被藏起來了。
Request Parsing 還有風險嗎?
有。
目前我們為了學習,很多地方都使用非常簡化的 Parsing。
例如:
Split
一次 NetworkStream.Read()
固定 buffer
這些都不是真正 Production HTTP Server 應該使用的完整處理方式。
尤其 Day19 已經知道:
TCP 是 byte stream。
一次 Read 不保證完整取得整個 Request。
所以 Day29 測試成功,代表的是:
目前這個學習環境下的 Mini Framework 可以運作。
不是代表:
我們已經做出完整符合所有 HTTP 情況的 Production Server。
這個界線非常重要。
因為這次專案的目標是:
理解 Framework。
不是取代 Kestrel。
測試時發現問題代表失敗嗎?
不是。
反而相反。
今天的目的就是:
把問題找出來。
例如可能發現:
/hello 正常
/products 正常
/abc 卡住
那就表示:
404 流程沒有接完整。
或者:
第一次 Request 有 Logging
第二次沒有
那表示:
Middleware Pipeline 可能只建立或執行一次。
再例如:
Response Body 正常
但 Status Code 不對
那就表示:
Response 建立邏輯有問題。
所以 Testing 的價值就是:
讓「我以為它可以」
變成:
「我有證據知道它在哪些情況可以」。
這兩句話差非常多。
錯誤處理要做到多完整?
最後兩天時間有限。
所以 Day29 不需要突然做一套完整 Error Handling Framework。
但至少可以想清楚三種結果:
正常
↓
200
Route 不存在
↓
404
Framework 發生未預期錯誤
↓
最好不要直接整個 Server Crash
如果 Middleware Pipeline 已經有空間,也可以想像:
Exception Middleware
↓
try
↓
next()
↓
catch
↓
500
即使最後成品沒有把所有細節做完,
至少架構上已經知道:
500 應該放在哪一層處理。
這比每個 Handler 自己 try/catch 更符合 Framework 設計。
要測什麼才算 Day29 完成?
對這個 Mini Web Framework 來說,可以建立一份最小 Checklist。
第一:
Server 可以正常啟動。
第二:
/hello 回 200 + Hello!
第三:
/products 回 200 + Products Page
第四:
不存在 Route 回 404。
第五:
連續 Request 不會讓 Server 結束。
第六:
Logging Middleware 每次 Request 都有執行。
第七:
Request → Router → Handler → Response 的流程可以從 Console 觀察。
如果這七項都通過,
其實就已經足以證明:
Mini Web Framework 的核心真的串起來了。
📚 官方怎麼說?
在正式軟體開發中,Testing 不只是確認某一個 Method 是否能執行。
當多個 Component 需要合作時,也需要確認它們之間的整合是否符合預期。
對 Web Application 而言,Request Processing 往往跨越:
Middleware
Routing
Endpoint
Response
等多個元件。
所以只驗證單一 Class 並不足以代表整個 Request Flow 正常。
而我們今天做的雖然只是手動的最小整合測試,
核心想法也是:
從 Request 進入,到 Response 離開,
整條流程一起驗證。
🧠 今天的認知更新
以前的我:
跑出一次 Hello
↓
成功
現在的我:
不同 Route
↓
不同 Handler
↓
正確 Response
錯誤 Route
↓
404
連續 Request
↓
Server 繼續執行
Middleware
↓
每次都按照順序執行
全部通過後,
才能比較有把握說:
Framework 的核心流程真的可以工作。
🔍 今日最大的發現
今天最大的發現是:
「可以執行」和「可以被信任」之間,中間還隔著 Testing。
前面一直在寫:
Framework 應該怎麼做。
今天開始反過來問:
我怎麼知道它真的有做到?
例如:
Router 真的會找到不同 Handler 嗎?
Middleware 真的包住 Handler 嗎?
404 真的會回出去嗎?
第二個 Request 真的還能進來嗎?
這些都不能只靠:
我覺得程式應該會這樣跑。
而要透過實際測試確認。
Framework 組裝完成後,不能只看它能不能啟動。
還需要從外部真的送出不同 Request,
確認 Request Parsing、Middleware、Routing、Handler、Response、404 等元件可以一起正確合作。
只有整條 Request / Response Flow 都經過驗證,才比較能確認這個成品真的成立。
🛠 與 Mini Web Framework 的關聯
Day26:
把 Framework 拆成不同元件。
Day27:
把 Router、Handler、Response 接起來。
Day28:
加入 Middleware Pipeline。
Day29:
不再新增大量功能。
而是把所有東西一起測一次。
整個驗證流程可以是:
啟動 Framework
↓
GET /hello
↓
200 Hello!
GET /products
↓
200 Products Page
GET /abc
↓
404 Not Found
再送 /hello
↓
仍然成功
同時 Console:
Middleware Before
↓
Routing
↓
Handler
↓
Response
↓
Middleware After
如果全部符合預期,
那這 29 天從零開始追的所有 Why,
就真的開始變成一個可以展示的成果。
今天的實作目標
Day29 真正實作時,重點不是再新增 Class。
而是整理一份測試紀錄。
可以實際測:
/hello
/products
/abc
連續多次 Request
Logging Middleware
並把 Console 結果和 Browser 畫面保留下來。
如果中間遇到 Bug,
就記錄:
問題是什麼
為什麼發生
最後怎麼修
其實這種內容非常適合放在 Day29 文章裡。
因為真實專案的最後階段本來就不會永遠一次成功。
Debug 本身就是開發的一部分。
結尾
一路做到 Day29,今天第一次沒有急著問:
下一個功能要加什麼?
而是回頭看:
前面做的東西到底能不能一起工作?
這讓我發現,做成品和做練習題很不一樣。
單獨做 Routing 時,只需要確認:
Path 能不能找到 Handler。
單獨做 Response 時,只需要確認:
Browser 能不能看到 Hello。
但當它們真正變成 Framework,
所有元件都必須在同一個 Request 裡合作。
Browser
↓
Server
↓
Request
↓
Middleware
↓
Router
↓
Handler
↓
Response
↓
Browser
只要任何一層出問題,整條流程就可能失敗。
所以今天最大的工作不是增加功能。
而是建立信心:
我不是只是把很多概念寫在文章裡。
這些概念真的可以被組成一條可以執行的 Framework Pipeline。
而明天,就是最後一天。
❓最後一個 Why
Day1 的時候,我問的是:
為什麼學了那麼多程式,遇到真正的問題時,我還是不知道程式到底怎麼運作?
29 天後,我已經一路從:
C#
Compiler
IL
JIT
CPU
走到:
TCP
HTTP
Routing
Request
Response
Reflection
Binding
Middleware
最後甚至自己組出一個 Mini Web Framework。
所以最後一天,不應該再繼續新增新功能。
真正該問的是:
這 30 天,我到底理解了什麼?
以及:
我真的做出了什麼?