Webページの生成と配信
同一LAN上のクライアント・プロキシ・配信元という構成の試料で、HTTPの内容とEthernet・IPv4・TCPのバイト列を対応させます。価格の入力・保存・本文の生成を進め、動的な本文と保存済みの静的な本文がいつ変わるか比較します。
説明
Webページを表示するまでには、クライアントが要求を送り、配信側が本文を返すやり取りがあります。リバースプロキシを置く構成では、利用者からの接続と、背後の配信元への接続は別々です。
同一LAN上のクライアント・プロキシ・配信元という構成の試料で、HTTPの内容とEthernet・IPv4・TCPのバイト列を対応させます。価格の入力・保存・本文の生成を進め、動的な本文と保存済みの静的な本文がいつ変わるか比較します。
1接続先と生成元の構成
この試料はHTTP/1.1を同一LANで運ぶ構成です。プロキシは/shop/item1を配信元の/items/1へ対応付けます。
cat topology.txt機器・接続・本文の生成元クライアントの接続先はプロキシのTCP80です。プロキシは別のTCP接続で配信元の9000へ要求します。同じLANでも、アプリケーション上の中継を挟むと接続は一つではありません。
2中継前後のHTTP要求
クライアント側と配信元側の要求メッセージを表示します。
python3 flow.py messages中継前後の要求内容パスとHostが変わり、X-Forwarded-Hostには元のHostが入っています。これはこの試料の転送規則です。X-Request-ID: r1で同じ処理の記録を対応付けますが、このヘッダーだけで相手を認証できるわけではありません。
3二つのTCP接続を照合する
PCAP試料には、接続確立後の要求二つと応答二つの四フレームが入っています。接続先・seq・ack・HTTPのバイト数を表示します。
python3 flow.py framesPCAPから接続とシーケンス番号を取得クライアント49152→プロキシ80と、プロキシ53000→配信元9000は別の組です。それぞれ独立したシーケンス番号を使います。応答のackは、その接続で受け取った要求の先頭seqに要求バイト数を加えた値です。
4クライアント側の一フレームを開く
最初のフレームについて、各ヘッダーの範囲と実際のバイトを表示します。
python3 flow.py frame 1要求フレームをヘッダーごとに分解Ethernetは0x0000から14バイト、IPv4は0x000eから20バイト、TCPは0x0022から20バイトで、HTTPは0x0036からです。EtherTypeの08 00がIPv4、IPのprotocol=6がTCPを示します。TCPの宛先ポート00 50は80です。
5配信元へ向かうフレームを開く
次のフレームで、配送情報とHTTP内容を比較します。
python3 flow.py frame 2中継後のヘッダーと要求本文送信元IPはプロキシ、宛先IPは配信元、宛先ポートは23 28、つまり9000です。HTTPのHostはorigin.internal:9000へ変わっています。単に元のTCPパケットの宛先だけを変更して渡したものではなく、別接続上のHTTP要求です。
6応答の本文を取り出す
配信元から返る三番目のフレームを開きます。
python3 flow.py frame 3応答ヘッダーとHTML本文HTTP/1.1 200 OKと、text/htmlの本文が返ります。Content-Lengthは本文のバイト数で、EthernetやIP、TCP、HTTPヘッダーの長さは含みません。<p>のタグを文書として解釈して表示するのは、受け取ったブラウザ側の役割です。
7同じ内容の静的配信と動的生成
初期状態では、保存済みitem1.htmlと、prices.jsonから作る本文は同じ価格120を含みます。
python3 flow.py get /static/item1.html保存済みの静的本文
python3 flow.py get /shop/item1元データから生成した本文どちらも同じ本文を返します。返ったHTMLだけを見ても、保存済みファイルを読んだのか、その時点で生成したのかは決められません。生成元と要求処理を対応付けて確認します。
8入力側だけの値を変更する
入力側で価格150を準備します。flow.pyは入力側と配信元側の保存先を分けています。
python3 flow.py draft 150入力側の値だけを変更入力側は150ですが、配信元のprices.jsonは120です。画面の入力欄へ値を入れた状態と、配信元で保存が完了した状態は同じではありません。
9保存前の動的本文を確認する
入力側の値を変更しただけの状態で、動的な本文を取得します。
python3 flow.py get /shop/item1現在の配信元データから本文を生成価格は120のままです。動的生成であっても、参照している配信元のデータが更新されなければ価格は変わりません。
10要求を受けて配信元へ保存する
準備した値をPOSTのJSON本文へ組み立て、型と範囲の検証後に配信元へ保存する処理を実行します。
python3 flow.py submit要求の作成・検証・配信元への保存POSTの本文は{"pen":150}で、Content-Lengthはそのバイト数です。保存後は204 No Contentを返し、配信元の値も150になります。204には応答本文を付けません。入力の表示、要求の送信、保存の完了を別々の状態として扱います。
11保存後の動的本文
更新した配信元データから、同じURLの本文を生成します。
python3 flow.py get /shop/item1更新済みのデータから生成価格は150になります。要求時にprices.jsonを参照するので、保存後の値が反映されます。
12静的ファイルはまだ元の内容
同じ時点で保存済みの静的ファイルを取得します。
python3 flow.py get /static/item1.html保存済み成果物をそのまま取得価格は120のままです。元データを更新しても、別に保存してあるHTMLファイルは自動では書き換わりません。静的配信で最新の価格を反映するには、配信する成果物も更新します。
13静的な成果物を作り直す
更新済みの元データからitem1.htmlを生成し直し、再取得します。
python3 flow.py publish元データから成果物を再生成
python3 flow.py get /static/item1.html更新した静的本文を取得静的本文にも150が反映されます。静的か動的かは、HTMLに動きがあるかではなく、どの段階で配信本文を用意するかの違いです。プロキシによる中継と本文の生成方法も、別の設計要素として組み合わせられます。
まとめ
プロキシの前後には別々の接続があり、Hostやパスも転送規則によって変わります。
HTTPのメッセージはTCP・IP・Ethernetの内側に入り、各層のヘッダーは異なる情報を持ちます。
入力側の変更、配信元への保存、動的生成、静的成果物の更新は、それぞれ別の時点です。
操作履歴と解説を振り返れます。
シェル / HTTP配信経路の試料
cat topology.txt同一LAN: 192.0.2.0/24 / Ethernet / HTTP/1.1
[client 192.0.2.10:49152]
|
| GET /shop/item1 Host: learn.example.com
v
[reverse proxy 192.0.2.20:80]
|
| GET /items/1 Host: origin.internal:9000
v
[origin 192.0.2.30:9000]
|
+-- 動的: prices.json -> <p>pen: 120</p>
+-- 静的: item1.html -> 保存済みの本文python3 flow.py messages[client request] GET /shop/item1 HTTP/1.1 Host: learn.example.com X-Request-ID: r1 [upstream request] GET /items/1 HTTP/1.1 Host: origin.internal:9000 X-Request-ID: r1 X-Forwarded-Host: learn.example.com
python3 flow.py frames1 192.0.2.10:49152 -> 192.0.2.20:80 seq=1001 ack=9001 HTTP=71 bytes 2 192.0.2.20:53000 -> 192.0.2.30:9000 seq=2001 ack=8001 HTTP=108 bytes 3 192.0.2.30:9000 -> 192.0.2.20:53000 seq=8001 ack=2109 HTTP=95 bytes 4 192.0.2.20:80 -> 192.0.2.10:49152 seq=9001 ack=1072 HTTP=95 bytes
python3 flow.py frame 10x0000..0x000d Ethernet: 02 00 00 00 00 14 02 00 00 00 00 0a 08 00 0x000e..0x0021 IPv4: 45 00 00 6f 00 01 40 00 40 06 b6 69 c0 00 02 0a c0 00 02 14 0x0022..0x0035 TCP: c0 00 00 50 00 00 03 e9 00 00 23 29 50 18 20 00 7d 6c 00 00 IPv4 192.0.2.10 -> 192.0.2.20, TTL=64, protocol=6 TCP 49152 -> 80, seq=1001, ack=9001, PSH+ACK 0x0036 HTTP: GET /shop/item1 HTTP/1.1 Host: learn.example.com X-Request-ID: r1
python3 flow.py frame 20x0000..0x000d Ethernet: 02 00 00 00 00 1e 02 00 00 00 00 14 08 00 0x000e..0x0021 IPv4: 45 00 00 94 00 02 40 00 40 06 b6 2f c0 00 02 14 c0 00 02 1e 0x0022..0x0035 TCP: cf 08 23 28 00 00 07 d1 00 00 1f 41 50 18 20 00 cb 98 00 00 IPv4 192.0.2.20 -> 192.0.2.30, TTL=64, protocol=6 TCP 53000 -> 9000, seq=2001, ack=8001, PSH+ACK 0x0036 HTTP: GET /items/1 HTTP/1.1 Host: origin.internal:9000 X-Request-ID: r1 X-Forwarded-Host: learn.example.com
python3 flow.py frame 30x0000..0x000d Ethernet: 02 00 00 00 00 14 02 00 00 00 00 1e 08 00 0x000e..0x0021 IPv4: 45 00 00 87 00 03 40 00 40 06 b6 3b c0 00 02 1e c0 00 02 14 0x0022..0x0035 TCP: 23 28 cf 08 00 00 1f 41 00 00 08 3d 50 18 20 00 86 c8 00 00 IPv4 192.0.2.30 -> 192.0.2.20, TTL=64, protocol=6 TCP 9000 -> 53000, seq=8001, ack=2109, PSH+ACK 0x0036 HTTP: HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 Content-Length: 16 <p>pen: 120</p>
python3 flow.py get /static/item1.html
python3 flow.py get /shop/item1HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 Content-Length: 16 <p>pen: 120</p> HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 Content-Length: 16 <p>pen: 120</p>
python3 flow.py draft 150input: {"pen": 150}
origin: {"pen":120}python3 flow.py get /shop/item1HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 Content-Length: 16 <p>pen: 120</p>
python3 flow.py submitPOST /api/price HTTP/1.1
Host: learn.example.com
Content-Type: application/json
Content-Length: 11
{"pen":150}
HTTP/1.1 204 No Content
origin: {"pen":150}python3 flow.py get /shop/item1HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 Content-Length: 16 <p>pen: 150</p>
python3 flow.py get /static/item1.htmlHTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 Content-Length: 16 <p>pen: 120</p>
python3 flow.py publish
python3 flow.py get /static/item1.htmlitem1.html: <p>pen: 150</p> HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 Content-Length: 16 <p>pen: 150</p>
演習完了