关于本文
本文由利用生成式 AI 创建的自动化工作流生成。其中确认了 RFC 9112 的 HTTP/1.1 消息结构以及 .NET 的 TcpClient / NetworkStream API,并作为不连接外部网站的 localhost 实验进行了实现。验证状态:📘 已确认 RFC 与 .NET 官方 API・已实现 localhost 示例・尚未在 PowerShell 真实设备上进行验证
使用 PowerShell 发送原始 HTTP — 在本地确认 HTTP 运行在 TCP 之上的感觉
HTTP 并不等同于 TCP。在 HTTP/1.1 中,我们在 TCP 连接之上收发呈 request line、header fields、空行以及按需配置的 body 形状的消息。
平常由 Invoke-WebRequest 和浏览器帮我们隐藏的部分,通过直接向 TcpClient 编写字符串,会变得更容易观察。
本次实验仅在单台 PC 上完成
不将外部网页作为实验对象,而是打开两个 PowerShell。
在一端启动
127.0.0.1:8085的简易 HTTP 服务器在另一端建立 TCP 连接
发送 HTTP/1.1 的 GET request
服务器显示接收到的内容
返回
200 OK
sequenceDiagram
participant C as PowerShell Client
participant T as TCP localhost:8085
participant S as PowerShell Server
C->>T: TCP connect
T->>S: connection accepted
C->>S: GET / HTTP/1.1 + headers + CRLF CRLF
S-->>C: HTTP/1.1 200 OK + headers + body
S-->>C: TCP close
发送的 HTTP 看起来只是普通的字符串
客户端侧的核心代码如下。
$request = @(
"GET / HTTP/1.1"
"Host: localhost:8085"
"Connection: close"
""
""
) -join "`r`n"
$requestBytes = [System.Text.Encoding]::ASCII.GetBytes($request)
$stream.Write($requestBytes, 0, $requestBytes.Length)
$stream.Flush()
HTTP/1.1 的 request line 是 GET / HTTP/1.1。其后紧跟 header fields,并通过空行来结束 header section。
这里之所以排列两个 "" 并用 CRLF 进行拼接,是为了在末尾生成 CRLF CRLF。
不要省略 Host header
RFC 9112 要求在 HTTP/1.1 request 中包含 Host header field。如果因为是教学材料就只发送 GET / HTTP/1.1,就会将作为 HTTP/1.1 重要组成部分的内容给隐藏掉。
因此,即便在 localhost 实验中,我们也明确指定以下内容。
Host: localhost:8085
TcpClient 不懂 HTTP
.NET 的 TcpClient 是处理 TCP 连接的类。通过 GetStream() 获取的 NetworkStream 用于收发字节序列(byte 列)。
也就是说,在此代码中,
flowchart TB
A[HTTPの文字列] --> B[ASCII bytes]
B --> C[NetworkStream]
C --> D[TCP]
D --> E[localhost server]
这样的层级结构是由我们自己手动搭建的。
掌握了这种感觉后,就能更容易地理解 HTTP client API 其实是在“遵循 HTTP 规则创建消息,并在其下方使用 transport”。
响应的 Content-Length 是字节数,而不是字符数
在服务器端,将 body 转换为 UTF-8 后再创建 Content-Length。
$body = "Hello from localhost HTTP over TCP`n"
$bodyBytes = [System.Text.Encoding]::UTF8.GetBytes($body)
$header = @(
"HTTP/1.1 200 OK"
"Content-Type: text/plain; charset=utf-8"
"Content-Length: $($bodyBytes.Length)"
"Connection: close"
""
""
) -join "`r`n"
由于 Content-Length 表示八位字节数(octet),因此当 body 中包含日语等多字节字符时,不能直接使用 .Length 的字符数,这一点非常重要。
HTTPS 并不完全是这样
本教材使用的是明文 HTTP。而在 HTTPS 中,是在 TCP 之上叠加 TLS,并在其之上收发 HTTP。
HTTP TLS TCP IP
因此,即使将此示例指向 443 端口并仅发送 HTTP 字符串,也无法实现正确的 HTTPS 通信。它可以作为接下来学习 TLS 的入门。

コメント