iT邦幫忙

2026 iThome 鐵人賽

DAY 11
0

Day10,我終於讓自己寫的程式真正開始監聽網路連線。

透過:

TcpListener

以及:

AcceptTcpClient()

我的程式可以等待 Client 連進來,再透過 NetworkStream 讀取對方傳來的資料。

但這時又產生了一個新的問題。

我們一直在說:

Browser 會傳送 HTTP Request 給 Server。

可是以前使用 ASP.NET Core 時,我看到的通常已經是框架整理好的 Request,例如路徑、Method、Header 等資訊。

所以我其實從來沒有真正看過,Browser 原始傳進 Server 的 HTTP Request 到底長什麼樣?

今天就直接把它印出來看看。


當我在 Browser 輸入一個網址並按下 Enter 時,它到底傳了什麼東西給 Server?

今天我沒有使用 ASP.NET Core。

而是繼續使用昨天建立的 TCP Server。

在 Client 成功連線後,我透過:

using NetworkStream stream = client.GetStream();

byte[] buffer = new byte[4096];

int length = stream.Read(
    buffer,
    0,
    buffer.Length
);

先取得 Browser 傳來的資料。

但從網路讀進來的資料,本身其實是:

bytes

所以接著使用:

string request =
    Encoding.UTF8.GetString(buffer, 0, length);

把收到的 bytes 轉換成字串。

最後:

Console.WriteLine(request);

直接把它印出來。

這次我沒有先解析它。

我就是想看看:

Browser 到底原封不動傳了什麼過來?

https://ithelp.ithome.com.tw/upload/images/20260812/20183395n8vzTZ38Vd.png


接著,我啟動 Server。

畫面顯示:

正在等待 Browser...

然後在 Browser 輸入:

http://127.0.0.1:5000/

Server 很快就收到了連線。

而且這次不只是:

收到連線!

而是真的印出了一大串資料。

其中最前面是:

GET / HTTP/1.1
Host: 127.0.0.1:5000
Connection: keep-alive
Cache-Control: max-age=0
User-Agent: ...
Accept: ...
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: zh-TW, ...

這是我第一次直接看到 Browser 傳給自己 Server 的 HTTP Request 原始內容。

https://ithelp.ithome.com.tw/upload/images/20260812/20183395s60EbCoDWB.png

其中最重要的是第一行:

GET / HTTP/1.1

它可以拆成三個部分:

GET       /       HTTP/1.1
 │        │           │
 │        │           └─ HTTP Version
 │        │
 │        └─ Request Target
 │           (這次的 Path 是 /)
 │
 └─ HTTP Method

也就是說,光是第一行其實就已經告訴 Server:

Client 想用什麼方式,存取什麼資源,以及使用哪個 HTTP 版本。


第一個GET是 HTTP Method。

它表示這個 Request 想進行什麼類型的操作。

這次我們直接在 Browser 網址列輸入網址,因此 Browser 發出了:

GET

目前可以先把它理解成:

Client 想向 Server 取得某個資源。

未來還會遇到其他 Method,例如:

-GET
-POST
-PUT
-DELETE

所以 HTTP Request 不只是告訴 Server:我要去哪裡。

還會告訴 Server: 我想用什麼方式做這件事。

第二個部分:

/

就是這次 Request 要存取的位置。

我們輸入:

http://127.0.0.1:5000/

其中:

127.0.0.1

是主機。

5000

是 Port。

而最後的:

/

就是 Path。

所以 Browser 傳出的第一行才會是:

GET / HTTP/1.1

但我想確認一件事情,如果網址的 Path 改變,這裡真的也會跟著改嗎?

所以我又做了第二個實驗。


我把 Browser 網址改成:

http://127.0.0.1:5000/hello

再次送出 Request。

這次 Server 收到的第一行真的變成:

GET /hello HTTP/1.1

兩次放在一起就非常清楚:

http://127.0.0.1:5000/

GET / HTTP/1.1

http://127.0.0.1:5000/hello

GET /hello HTTP/1.1

所以我第一次親手確認:

Browser 網址中的 Path,真的會被寫進 HTTP Request 的 Request Line,這件事之後會非常重要。

因為未來我們做 Routing 時,Framework 就必須根據這些資訊判斷:

/hello
/users
/products

到底應該交給哪一段程式處理。

第一行最後還有:

HTTP/1.1

它表示這個 Request 使用的 HTTP Protocol Version。

所以:

GET /hello HTTP/1.1

可以直接讀成:

使用 HTTP/1.1,以 GET 方法要求 /hello。

到這裡,我終於不再只是把:

GET /hello HTTP/1.1

當成一串奇怪的文字,它其實有固定的結構。

今天看到的第一行有一個正式名稱:

Request Line

基本結構可以理解成:

Method + Request Target + HTTP Version

例如:

GET /hello HTTP/1.1

就是:

Method

GET /hello HTTP/1.1
↑ ↑
Target Version

這也代表未來如果我要自己做 Mini Web Framework,我至少得有能力把這一行拆開。

例如:

method = "GET";
path = "/hello";
version = "HTTP/1.1";

Framework 才能知道這個 Request 到底想做什麼。


第一行下面那些東西又是什麼?

除了:

GET /hello HTTP/1.1

Browser 還傳了一大堆東西。

例如:

Host: 127.0.0.1:5000
Connection: keep-alive
Cache-Control: max-age=0
User-Agent: ...
Accept: ...
Accept-Encoding: ...
Accept-Language: ...

這些就是:

HTTP Headers

Header 基本上是一組:

名稱: 值

例如:

Host: 127.0.0.1:5000

可以拆成:

Host
 ↓
Header Name

127.0.0.1:5000
       ↓
Header Value

不同 Header 會提供不同資訊。

例如今天實際看到的:

-Host
→ 要存取的 Host

User-Agent
→ Client/Browser 的相關資訊

-Accept
→ Client 可以接受哪些內容

-Accept-Encoding
→ Client 支援哪些內容編碼

-Accept-Language
→ Client 偏好的語言

所以 Browser 並不是只送:

GET /hello

而是同時附帶很多額外資訊給 Server。

https://ithelp.ithome.com.tw/upload/images/20260812/20183395nIKOL8STpl.png


做到這裡,我發現 HTTP Request 其實不是隨便的一串文字。

至少今天看到的 GET Request,可以先理解成:

┌──────────────────────────────┐
│ Request Line                 │
│ GET /hello HTTP/1.1          │
├──────────────────────────────┤
│ Headers                      │
│ Host: 127.0.0.1:5000         │
│ User-Agent: ...              │
│ Accept: ...                  │
│ Accept-Language: ...         │
├──────────────────────────────┤
│ 空白行                        │
└──────────────────────────────┘

之後某些 Request 還可能帶有:

Body

所以更完整的概念會是:

HTTP Request
│
├── Request Line
│
├── Headers
│
├── 空白行
│
└── Body(不一定有)

這跟以前只知道「Browser 送 Request」相比,已經具體非常多。


以前的我:

Browser
   ↓
HTTP Request
   ↓
ASP.NET Core
   ↓
Controller

現在的我開始看到中間真正發生的事情:

Browser
   ↓
建立 TCP Connection
   ↓
Server Accept
   ↓
NetworkStream
   ↓
讀取 bytes
   ↓
把 bytes 解碼
   ↓
GET /hello HTTP/1.1
Host: ...
...
   ↓
解析 HTTP Request
   ↓
之後才有可能做 Routing

也就是說,ASP.NET Core 平常交給我的 Request,其實已經是底層做完很多工作的結果。


今天最大的發現是:

URL 裡面的 /hello 並不會神奇地直接變成 ASP.NET Core 裡的 Route。

Browser 其實只是先送出:

GET /hello HTTP/1.1

然後 Server/Framework 必須:

收到 bytes
   ↓
理解 HTTP 格式
   ↓
解析 Request Line
   ↓
取得 /hello
   ↓
才有可能拿 /hello 去做 Routing

也就是說,Routing 並不是網路本身提供的能力,它是 Framework 在收到並理解 HTTP Request 之後,

才建立出來的更高層功能。

這對我們接下來自己做 Mini Web Framework 非常重要。


昨天,我們的 Mini Web Framework 已經可以:

監聽 Port
↓
接受 TCP Client
↓
讀取資料

今天又往上走了一層:


監聽 Port
↓
接受 TCP Client
↓
讀取 bytes
↓
轉成文字
↓
看到 HTTP Request

但目前我們仍然只是:

Console.WriteLine(request);

也就是:

看得到 Request,但還不懂 Request。

真正的 Framework 不可能每次都把:

GET /hello HTTP/1.1

整串文字丟給開發者自己處理。

它應該幫我們整理成類似:

Method  → GET
Path    → /hello
Version → HTTP/1.1
Headers → ...

所以 Mini Web Framework 下一個需要的能力已經很明顯了,解析 HTTP Request。


現在 Server 已經收到:

GET /hello HTTP/1.1

但對我們的程式來說,它目前仍然只是一段 string。

那 Framework 到底要怎麼從這串文字裡知道:

GET

是 Method, /hello 是 Path,

而HTTP/1.1又是 Version?

換句話說:

Framework 到底怎麼把一串 HTTP 文字,變成可以使用的 Request 物件?

這就是下一步我們要開始自己實作的東西。


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

尚未有邦友留言

立即登入留言