昨天我們一路研究到 Parameter Binding。
也就是:
Framework 先透過 Reflection 看 Handler 需要什麼參數,
再從:
Route
Query
Header
Body
這些地方找到資料,最後轉成正確的 Type。
所以現在 Framework 已經有機會做到:
Request
↓
找到 Handler
↓
準備 Parameters
↓
執行 Handler
但這時又出現一個新的問題。
Handler 執行完之後,到底會回傳什麼?
我們之前自己的 Mini Web Framework 很簡單。
Handler 幾乎都是:
Func
所以 Framework 可以直接假設:
Handler 執行完
↓
一定得到 HttpResponse
但真正的 Web Framework 不可能限制所有 Handler 都只能回傳同一種東西。
有些 Handler 可能回傳:
string
有些可能回傳:
User
有些可能回傳:
List
有些可能只想回:
404
甚至有些 Handler 可能根本沒有要回傳內容。
那 Framework 要怎麼把這些不同的結果,最後全部變成 Browser 看得懂的 HTTP Response?
這就是今天想理解的問題。
💭 我原本以為……
以前看到:
return "Hello";
我會覺得:
這就是 Response。
看到:
return user;
也會覺得:
Framework 就把 user 回傳出去。
但 Day14 已經知道,其實 Browser 真正收到的不是:
C# string
也不是:
C# User Object
而是 HTTP Response。
例如:
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: ...
Hello
所以真正的流程應該不是:
Handler
↓
直接送給 Browser
而是:
Handler
↓
Return Value
↓
Framework 處理
↓
HttpResponse
↓
Browser
這中間還有一層轉換。
Handler Return Value 到底是什麼?
假設 Handler 是:
GetHello()
回傳:
"Hello"
那 Framework 得到的是:
string
如果 Handler 是:
GetUser()
回傳:
User
Framework 得到的則是一個 User Object。
如果 Handler 是:
GetProducts()
回傳:
List
那又是另一種 Type。
所以 Framework 執行 Handler 之後,不能直接假設:
這一定是一份完整的 HTTP Response。
比較合理的做法是:
先取得 Handler Result
再根據 Result 的 Type 決定怎麼轉換。
例如:
string
↓
text/plain
User Object
↓
JSON
List
↓
JSON Array
HttpResponse
↓
直接送出
null
↓
可能是空 Response
這就是 Framework 又多出來的一層工作。
為什麼 string 不能直接送?
表面上看:
"Hello"
確實最後也會出現在 Browser。
但 HTTP Response 還需要:
Status Code
Headers
Content-Type
Content-Length
Body
所以 Framework 如果收到:
"Hello"
至少還要幫它補成:
Status = 200
Content-Type = text/plain
Body = Hello
再由 HttpResponse 負責真正送出去。
所以:
string
只是 Handler Result。
不是完整的 HTTP Response。
Object 又怎麼辦?
假設 Handler 回傳:
User
裡面有:
Name = Andy
Age = 20
Browser 不認識 C# 的 User Object。
所以 Framework 必須先把它轉成一種可以傳輸的格式。
最常見的就是 JSON。
例如:
User Object
↓
Serialization
↓
{"name":"Andy","age":20}
接著才能建立:
HTTP/1.1 200 OK
Content-Type: application/json
...
{"name":"Andy","age":20}
所以前幾天學到的 Serialization,今天又接回來了。
Day20 研究的是:
JSON
↓
C# Object
這叫 Deserialization。
今天剛好反方向:
C# Object
↓
JSON
這就是 Serialization。
整個來回開始變成:
Client
↓
JSON
↓
Deserialization
↓
C# Object
↓
Handler
↓
C# Object
↓
Serialization
↓
JSON
↓
Client
HttpResponse 又出現了
Day15 我們自己建立過:
HttpResponse
裡面有:
StatusCode
StatusText
ContentType
Body
所以現在一個很自然的設計就是:
不管 Handler 回傳什麼,
Framework 最後都先把它轉成:
HttpResponse
例如:
string
↓
HttpResponse
User
↓
JSON
↓
HttpResponse
404 Result
↓
HttpResponse
最後全部統一:
HttpResponse.Send()
這樣 Browser 那一端就不需要知道 Handler 原本到底回傳什麼。
所以可以整理成:
Handler
↓
Result
↓
Result Processing
↓
HttpResponse
↓
Send
↓
Browser
這其實就是統一出口。
為什麼 Framework 需要統一出口?
假設每種 Handler Result 都自己處理 NetworkStream。
那就會變成:
string Handler
↓
自己 Write
User Handler
↓
自己 JSON Serialize
↓
自己 Write
404 Handler
↓
自己組 404
↓
自己 Write
最後所有底層細節又會散回各個地方。
但如果全部先轉成:
HttpResponse
就可以變成:
各種 Result
↓
統一轉換
↓
HttpResponse
↓
同一套 Send()
這樣 Response Pipeline 才會比較乾淨。
Result Type 可以怎麼分類?
對我們自己的 Mini Web Framework 來說,一開始不用支援太多。
可以先想像幾種情況。
第一種:
string
例如:
return "Hello";
Framework 可以轉成:
StatusCode = 200
ContentType = text/plain
Body = Hello
第二種:
普通 C# Object
例如:
return user;
Framework 可以:
Serialize 成 JSON
ContentType = application/json
StatusCode = 200
第三種:
HttpResponse
如果 Handler 本身已經回傳:
HttpResponse
那 Framework 就可以直接使用。
這樣最小版本其實就已經很有 Framework 的感覺了。
Result Conversion 是什麼?
目前可以把它理解成:
把 Handler 回傳的東西,轉成統一 HttpResponse 的過程。
例如:
Result = "Hello"
↓
判斷 Type 是 string
↓
建立 Text Response
Result = User
↓
判斷是 Object
↓
Serialize JSON
↓
建立 JSON Response
Result = HttpResponse
↓
直接使用
也就是:
object result
↓
ConvertResult(...)
↓
HttpResponse
這會是一個很重要的 Framework 元件。
Reflection 又可以派上用場嗎?
可以。
Framework 執行 Handler 後,可以知道:
Return Type
也可以看到實際得到的:
Result Object
例如:
result.GetType()
可能得到:
System.String
User
List
HttpResponse
Framework 就可以根據不同 Type 決定轉換策略。
當然,真正 Framework 的做法可能更完整。
例如:
Result Interface
Result Executor
Formatter
Serializer
Content Negotiation
等等。
但我們現在先理解最小版本:
不同 Return Value
↓
判斷
↓
轉成 HttpResponse
就足夠了。
Content-Type 為什麼又出現?
因為不同的結果,Browser 要用不同方式理解。
如果 Handler 回傳:
Hello
可以是:
Content-Type: text/plain
如果回傳:
{"name":"Andy"}
則應該是:
Content-Type: application/json
如果未來回 HTML:
則可能是:
Content-Type: text/html
所以 Result Conversion 不只決定:
Body 是什麼
還要決定:
Content-Type 是什麼
這樣 Browser 才知道收到的資料應該怎麼解讀。
Status Code 又怎麼決定?
最簡單情況下:
Handler 正常回傳結果
↓
200 OK
找不到 Route
↓
404 Not Found
Request 格式錯誤
↓
400 Bad Request
Server 執行出錯
↓
500 Internal Server Error
所以 Result Conversion 最後也會和 Status Code 有關。
不過對 Mini Web Framework 來說,目前不用一次處理全部。
先做到:
正常 → 200
找不到 → 404
就足夠建立核心概念。
📚 官方怎麼說?
在 ASP.NET Core 中,Endpoint 或 Action 執行後會產生結果,而 Framework 會負責把結果寫入 HTTP Response。
不同開發模式中可能會看到:
string
IResult
IActionResult
Object Result
JSON Result
等等。
雖然細節不同,但核心概念很接近:
Handler 執行結果
↓
Framework 處理
↓
HTTP Response
也就是說,Handler 的 Return Value 和真正送到 Client 的 HTTP Response,並不是完全相同的東西。
中間還存在 Framework 的 Response Processing。
以前的我:
return user
↓
Browser 收到 user
現在的我:
Handler
↓
return User Object
↓
Framework
↓
Serialization
↓
JSON
↓
HttpResponse
↓
HTTP bytes
↓
Browser
所以:
return
只是把結果交回 Framework。
真正把結果送到 Browser 的,是 Framework 後面的 Response Pipeline。
這讓我重新理解:
Handler 並不是直接和 Browser 溝通。
Handler 其實是在和 Framework 溝通。
今天最大的發現是:
Framework 在 Handler 前後,其實各有一個轉換工作。
Handler 前:
HTTP Request Data
↓
Parameter Binding
↓
C# Parameters
Handler 後:
C# Return Value
↓
Result Conversion
↓
HTTP Response
也就是:
HTTP 世界
↓
Framework
↓
C# 世界
↓
Handler
↓
C# 世界
↓
Framework
↓
HTTP 世界
Framework 真正站在兩個世界中間。
這讓整個 Web Framework 的角色越來越完整。
Framework 執行 Handler 後,先取得 Return Value。
接著依照 Result 的 Type,決定要怎麼處理。
例如 string 轉成文字 Response,Object 轉成 JSON Response。
最後所有結果都可以統一整理成 HttpResponse,再由 Framework 寫回 Browser。
🛠 與 Mini Web Framework 的關聯
現在整個 Mini Web Framework 的方向已經逐漸完整:
Browser
↓
HttpRequest
↓
Routing
↓
Reflection
↓
Parameter Binding
↓
Handler
↓
Return Value
↓
Result Conversion
↓
HttpResponse
↓
Browser
前面我們建立的 HttpResponse,今天開始有更明確的定位。
它不只是:
自己手動建立的一個 Response Class。
而可以成為:
所有 Handler Result 最後共同轉換到的格式。
也就是:
string
Object
Error
其他結果
↓
HttpResponse
這會讓整個 Response Pipeline 變得更一致。
今天暫時不實作
目前依然先把核心觀念整理清楚。
因為前面的 HttpRequest、Headers、Query、Body、JSON、Reflection、Parameter Binding 還需要在最後幾天整合。
之後真正實作 Result Conversion 時,可以先從兩種最簡單情況開始:
string
↓
text/plain HttpResponse
普通 Object
↓
JSON HttpResponse
再讓:
HttpResponse.Send()
統一負責送回 Browser。
這樣就足夠做出一個很有 Framework 感的最小版本。
結尾
前幾天我們一直在研究:
Framework 怎麼把 Request 變成 Handler 可以使用的資料。
到了今天,方向終於反過來。
Handler 執行完後,Framework 又必須把 C# 世界的結果轉回 HTTP 世界。
所以整個流程可以非常漂亮地對稱:
HTTP Request
↓
HttpRequest
↓
Parameter Binding
↓
C# Parameters
↓
Handler
↓
C# Result
↓
Result Conversion
↓
HttpResponse
↓
HTTP Response
這讓我開始真正理解:
Framework 並不是只有 Routing。
Routing 只是在中間找到「誰來處理」。
Handler 前後其實還有很多工作。
前面負責:
把 HTTP 資料變成程式資料。
後面則負責:
把程式結果變回 HTTP 資料。
這兩邊一起完成,才是真正完整的 Request / Response Pipeline。
現在 Request 已經可以想像一路走到 Handler,再從 Handler 變成 Response。
但目前所有東西還是像一條固定流程。
如果未來每一個 Request 都想先經過:
Logging
Authentication
Exception Handling
Timing
CORS
是不是每個 Handler 都要自己寫一次?
真正的 Framework 又是怎麼讓 Request 在到達 Handler 前後,經過一層一層共用處理?