一覧WB-01-2 · Webの構成とURL

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配信経路の試料

1
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  -> 保存済みの本文
2
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
3
python3 flow.py frames
1 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
4
python3 flow.py frame 1
0x0000..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
5
python3 flow.py frame 2
0x0000..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
6
python3 flow.py frame 3
0x0000..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>
7
python3 flow.py get /static/item1.html python3 flow.py get /shop/item1
HTTP/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>
8
python3 flow.py draft 150
input: {"pen": 150}
origin: {"pen":120}
9
python3 flow.py get /shop/item1
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 16

<p>pen: 120</p>
10
python3 flow.py submit
POST /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}
11
python3 flow.py get /shop/item1
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 16

<p>pen: 150</p>
12
python3 flow.py get /static/item1.html
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 16

<p>pen: 120</p>
13
python3 flow.py publish python3 flow.py get /static/item1.html
item1.html: <p>pen: 150</p>
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 16

<p>pen: 150</p>

演習完了