PowerShellでHTTPを生で送ってみる ― TCPの上にHTTPが乗る感覚をlocalhostで確かめる

ネットワーク・RFCカテゴリを表すパンダのイラスト ネットワーク・RFC

この記事について
この記事は、生成AIを活用した自動生成フローで作成しています。RFC 9112のHTTP/1.1メッセージ構造と.NETのTcpClient / NetworkStream APIを確認し、外部サイトへ接続しないlocalhost実験として実装しています。

検証ステータス:📘 RFC・.NET公式API確認済み・localhostサンプル実装済み・PowerShell実機確認は未実施

PowerShellでHTTPを生で送ってみる ― TCPの上にHTTPが乗る感覚をlocalhostで確かめる

HTTPはTCPそのものではありません。HTTP/1.1では、TCP接続の上でrequest line、header fields、空行、必要ならbodyという形のメッセージをやり取りします。

普段は Invoke-WebRequest やブラウザが隠してくれる部分を、TcpClient へ文字列を直接書くと見えやすくなります。

今回は1台のPCだけで完結させる

外部Webサイトを実験台にせず、PowerShellを2つ開きます。

  1. 片方で 127.0.0.1:8085 の簡易HTTPサーバーを起動

  2. もう片方でTCP接続

  3. HTTP/1.1のGET requestを送信

  4. サーバーが受信内容を表示

  5. 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を終えます。

ここで "" を2つ並べて 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を知らない

.NETTcpClient は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は文字数ではなくbyte数

サーバー側では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を学ぶときの入口として使えます。

GitHubサンプル

公式情報・一次情報

ライセンス:本記事のテキスト/コードは特記なき限り CC BY 4.0 です。引用の際は出典URL(本ページ)を明記してください。
利用ポリシー もご参照ください。

コメント

タイトルとURLをコピーしました