Day1 的時候,我問自己的問題其實不是:
我要怎麼做一個 Web Framework?
而是:
為什麼我學了這麼多程式,遇到真正的問題時,還是常常不知道程式到底怎麼運作?
以前學程式時,我很容易記住:
這段程式怎麼寫。
這個 API 怎麼呼叫。
這個 Framework 要怎麼用。
但如果再多問一句:
為什麼?
我就常常回答不出來。
為什麼 CPU 看不懂 C#?
為什麼 Build 之後會產生 DLL?
為什麼 Server 可以一直等待 Request?
為什麼 Browser 輸入網址之後,Server 就知道我要去哪裡?
為什麼只寫 return "Hello" 就可以讓 Browser 收到 Response?
為什麼 ASP.NET Core 可以自動把 JSON 變成 C# Object?
為什麼 Handler 的參數不用自己一個一個從 Request 裡拿?
這些原本看起來完全不同的問題,最後竟然全部接到了一起。
所以來到 Day30,最後真正想回答的問題是:
這 30 天,我到底理解了什麼?
以及:
我真的做出了什麼?
💭 回到 Day1 的我
一開始,我對 Framework 的理解其實很簡單。
Framework 就是一個:
幫我把很多東西做好了的工具。
例如 ASP.NET Core。
我知道可以建立 Controller。
知道可以設定 Route。
知道可以 return JSON。
知道可以使用 Request。
但我不知道:
Request 從哪裡來?
Route 為什麼會找到 Controller?
return 的東西最後怎麼回到 Browser?
Framework 到底替我做了多少事情?
所以以前使用 Framework 時,很多東西對我來說都很像:
魔法。
輸入:
app.MapGet(...)
然後它就可以工作。
但這 30 天做的事情,就是把這些「魔法」一層一層拆開。
第一段旅程:C# 到底怎麼跑起來?
最開始,我甚至沒有直接碰 HTTP。
而是從一個更底層的問題開始:
CPU 看得懂 C# 嗎?
答案當然是不行。
所以一路追下去:
C# Source Code
↓
Compiler
↓
IL
↓
Assembly
↓
CLR
↓
JIT
↓
Machine Code
↓
CPU
以前只知道:
按下 Run
程式就執行。
現在至少開始知道,Run 中間其實跨過了很多層。
Day3 開始看 Compiler。
Day4 開始拆 Build。
Day5 看 Build 後為什麼產生這麼多檔案。
Day6 第一次用 ILDasm 打開自己的 DLL。
Day7 看 C# 改變之後 IL 真的也跟著改變。
這幾天最重要的不是背:
ldstr
call
ret
而是第一次真正看到:
我寫的 C# 並不是 CPU 最後直接執行的東西。
中間存在很多轉換。
這個觀念後來其實一直重複出現在 Framework 裡。
高階程式碼
↓
底層形式
Framework 的工作也常常是在做類似的事情。
第二段旅程:程式為什麼可以變成 Server?
接著開始研究:
為什麼一般程式跑完就結束,
Web Server 卻可以一直等待 Request?
一開始只用:
while(true)
證明程式可以不結束。
接著又用:
Console.ReadLine()
理解「等待」和「一直忙碌執行」不是完全一樣的事情。
然後第一次真正接觸:
TcpListener
TcpClient
NetworkStream
那時候才開始理解:
Web Server 並不是一個特別神祕的程式。
它也是一個正在執行的 Process。
只是在某個 Port 上等待 Client Connection。
所以:
Browser
↓
TCP Connection
↓
Server
這條線第一次出現。
第三段旅程:Browser 到底傳了什麼?
第一次真的用 Browser 連自己的 Server 時,我看到了:
GET / HTTP/1.1
Host: ...
User-Agent: ...
Accept: ...
以前一直說:
Browser 會傳 Request。
但直到自己把原始資料印出來,才真正知道 Request 長什麼樣。
後來把網址改成:
/hello
真的看到:
GET /hello HTTP/1.1
這件事情其實非常簡單。
但它讓我第一次知道:
Browser 裡的網址 Path,最後真的會出現在 HTTP Request 裡。
接著開始拆:
Method
Path
Version
Headers
Query
Body
原本只有一大串 HTTP Text,
最後慢慢變成:
HttpRequest
├── Method
├── Path
├── Version
├── Headers
├── Query
└── Body
這讓我第一次理解:
HttpRequest 並不是 Browser 傳來的一個 C# Object。
它是 Framework 根據 HTTP Data 建立出來的抽象。
第四段旅程:Routing 原來沒有那麼神祕
以前使用 Framework:
/hello
↓
Hello Handler
會覺得 Routing 好像是一個很複雜的功能。
但真正從零開始做時,第一版其實只是:
if path == "/hello"
然後:
if / else
慢慢變成:
Dictionary
接著變成:
Route Table
最後:
Path
↓
找到 Handler
↓
執行 Handler
這時才真正理解 Routing 的核心:
Matching
Dispatching
也就是:
Request 要交給誰?
真正 Framework 的 Routing 當然比我們的 Dictionary 複雜很多。
但核心問題其實沒有改變。
第五段旅程:Handler 執行完之後呢?
找到 Handler 後,最開始只是:
Console.WriteLine()
結果只出現在 Server Console。
Browser 根本看不到。
所以開始研究:
HTTP Response。
第一次自己手動建立:
HTTP/1.1 200 OK
Content-Type
Content-Length
空白行
Body
再把 Response:
string
↓
Encoding
↓
bytes
↓
NetworkStream.Write()
送回 Browser。
當 Browser 第一次真的顯示:
Hello!
那個瞬間其實很重要。
因為整個流程第一次完整閉環:
Browser
↓
Request
↓
Server
↓
Handler
↓
Response
↓
Browser
接著又做:
404 Not Found
才開始理解 Status Code 不只是我們平常看到的一個數字。
它真的存在 HTTP Response 中。
第六段旅程:為什麼需要 HttpRequest 和 HttpResponse?
做到 Request 和 Response 都能工作後,新的問題開始變成:
程式可以跑。
但為什麼越來越亂?
Program.cs 同時知道:
TCP
HTTP
Parsing
Routing
Response
Encoding
NetworkStream
所有事情。
於是第一次建立:
HttpResponse
把:
StatusCode
ContentType
Body
Send()
集中起來。
接著又想到:
Response 都變成 Object 了。
Request 為什麼還是一堆:
string
Split
parts[0]
parts[1]
所以又開始設計:
HttpRequest。
這時我第一次真的理解:
Object 的價值不只是把幾個變數包在一起。
更重要的是:
建立一個其他程式可以依賴的介面。
例如:
parts[1]
和:
request.Path
最後可能都是:
/products
但它們代表完全不同的設計。
第七段旅程:Framework 為什麼可以這麼「自動」?
接著問題一路往更高層走。
Query String:
?name=Andy
怎麼變成:
Query["name"]
Request Body:
JSON
怎麼變成:
C# Object
Handler:
CreateUser(User user)
Framework 又怎麼知道:
它需要 User?
這時開始碰到:
Serialization
Deserialization
Reflection
Metadata
Parameter Binding
以前看到:
User user
會覺得 Framework 就是自動把資料放進來。
現在至少可以拆成:
Handler
↓
Reflection
↓
發現 Parameter Type = User
Request
↓
Body
↓
JSON
兩邊接起來:
JSON
↓
Deserialize()
↓
User Object
↓
Handler Parameter
所以「自動」不是沒有規則。
只是 Framework 幫我們把規則包起來了。
第八段旅程:Middleware 和 Pipeline
最後又出現:
Logging
Authentication
Exception Handling
這些每個 Request 都可能需要的功能。
難道每個 Handler 都自己寫?
於是開始理解:
Middleware。
Request
↓
Middleware A
↓
Middleware B
↓
Handler
↓
Middleware B
↓
Middleware A
↓
Response
以及:
next()
代表:
把控制權交給後面的 Pipeline。
這時才開始理解:
Framework 不只是在管理資料。
它也在管理:
程式的執行流程。
最後所有東西串起來後:
Browser
↓
TCP
↓
HttpRequest
↓
Middleware Pipeline
↓
Routing
↓
Parameter Binding
↓
Handler
↓
Result Conversion
↓
HttpResponse
↓
TCP
↓
Browser
這就是這 30 天最後慢慢拼出的圖。
我最後做出了什麼?
這次專案叫:
Mini Web Framework
它當然不是 ASP.NET Core。
也不是一個可以拿去正式部署大型網站的 Production Framework。
官方文件和自己的 Framework 有什麼差別?
這 30 天大量查 Microsoft 官方文件後,我最大的感受是:
以前看官方文件時,
常常只是在找:
這個 API 怎麼用?
但自己拆過一次 Framework 後,再看到:
HttpRequest
HttpResponse
Routing
Middleware
Reflection
Model Binding
這些名詞,
開始不只是記:
怎麼使用。
而是會想到:
它為什麼需要存在?
例如看到 HttpRequest,
會想到:
Browser 傳來的其實是 HTTP Data。
看到 Routing,
會想到:
Request Path 必須被 Match 到 Handler。
看到 Middleware,
會想到:
Pipeline 和 Delegate。
看到 Model Binding,
會想到:
Reflection + Request Data + Type Conversion。
所以官方文件好像也沒有以前那麼「魔法」了。
🧠 30 天最大的認知更新
Day1:
我想知道 Framework 怎麼用。
Day30:
我開始知道 Framework 為什麼存在。
以前:
return "Hello";
現在看到的是:
Handler Result
↓
Framework
↓
HttpResponse
↓
HTTP
↓
bytes
↓
TCP
↓
Browser
以前:
Request.Path
現在看到的是:
TCP bytes
↓
HTTP Parsing
↓
Request Target
↓
Path
↓
HttpRequest.Path
以前:
app.MapGet()
現在看到的是:
Route Registration
↓
Route Table
↓
Request
↓
Matching
↓
Handler
以前:
User user
現在看到的是:
Handler Metadata
↓
Reflection
↓
Parameter Type
+
Request Body
↓
JSON
↓
Deserialization
↓
Parameter Binding
↓
User
這就是這 30 天最大的差別。
不是多背了多少 API。
而是看到 API 後,
開始有能力往下問:
Why?
如果要選一個最大的發現,
我覺得不是:
TCP
不是:
Routing
不是:
Reflection
也不是:
Middleware。
而是:
抽象沒有讓底層消失。
它只是把底層複雜度藏到更適合的位置。
我們寫:
request.Path
不代表 HTTP Parsing 不存在。
我們寫:
return user
不代表 Serialization 不存在。
我們寫:
MapGet
不代表 Routing 不存在。
我們使用 Framework,
並不是底層突然不需要了。
只是 Framework 替我們承擔那些細節。
所以:
Framework 的價值不是「魔法」。
而是:
管理複雜度。
這是這 30 天對我來說最重要的一個答案。