一覧WB-02-2 · HTTPメッセージ

HTTPの本文サイズとgzip圧縮

同じ日本語の本文を圧縮なし・gzipの二通りで取得します。先頭バイト、gzipの検査値と展開後サイズを開き、curlが自動展開した場合のファイル長とヘッダーの関係も確認します。

説明

HTTP本文の長さは文字数ではなくバイト数で扱います。gzipで圧縮して送る場合は、圧縮された表現の長さと、展開後の本文の長さも異なります。

同じ日本語の本文を圧縮なし・gzipの二通りで取得します。先頭バイト、gzipの検査値と展開後サイズを開き、curlが自動展開した場合のファイル長とヘッダーの関係も確認します。

1圧縮なしの本文を保存

ヘッダーと本文を別ファイルへ保存します。Accept-Encodingは指定しません。

curl -sS -D plain.headers -o plain.binヘッダーと本文を別々に保存 http://127.0.0.1:18080/unicode cat plain.headers応答ヘッダーを表示

Content-Encodingはなく、Content-Lengthはそのままの本文のバイト数です。-Dはヘッダー、-oは本文の保存先で、二つを混ぜずに確認できます。

2文字数と保存バイト数

日本語を含む本文について、文字列の長さとバイト列の長さを比較します。

python3 - <<'PYCODE' from pathlib import Path body = Path("plain.bin").read_bytes()保存した本文をバイトで読む print("characters",len(body.decode("utf-8"))復号後の文字数,"bytes",len(body)本文のバイト数) print("first line",repr(body.decode("utf-8").splitlines()[0])) print("first bytes",body[:23].hex(" ")先頭行付近のバイト列) PYCODE

各行には"あいうえお"の5文字があり、それぞれUTF-8で3バイトです。HTMLタグと改行も本文の一部です。Content-Lengthと一致するのはbytesの値で、charactersの値ではありません。

3gzipのまま保存する

Accept-Encodingでgzipを許容し、圧縮された本文をそのまま保存します。

curl -sS --raw -H "Accept-Encoding: gzip"gzipを要求して未展開で保存 -D gzip.headers -o gzip.bin圧縮応答の保存先 http://127.0.0.1:18080/unicode cat gzip.headers圧縮方式と長さを確認

Content-Encoding: gzipが付き、Content-Lengthはgzip.binのバイト数になります。Content-Typeは展開後の表現であるtext/htmlのままです。圧縮方式と本文のメディア型は別の情報です。

4圧縮データの先頭と末尾を調べる

gzipの先頭ヘッダーと、末尾のCRC32・元サイズを読み、展開した内容と照合します。

python3 inspect_gzip.pygzipの構造・検査値・展開結果を照合

先頭の1f 8bがgzipの識別子、08はDEFLATEです。末尾8バイトはlittle-endianのCRC32とISIZEで、ISIZEは元サイズの下位32ビットです。この本文では展開後サイズと一致し、CRC32も展開後本文から計算した値と一致します。

5curlで自動展開する

--compressedを付けて取得し、ヘッダーと展開後の本文を保存します。

curl -sS --compressed対応する圧縮を要求して展開 -D decoded.headers -o decoded.txt元のヘッダーと展開後の本文 http://127.0.0.1:18080/unicode cat decoded.headers転送時の長さを確認

curlは対応する圧縮方式を要求し、この応答を展開してdecoded.txtへ保存します。保存したヘッダーには転送時のContent-EncodingとContent-Lengthが残るため、ヘッダーの長さが展開後ファイルの長さと異なっても、それだけで破損とはいえません。

6展開後の同一性を確認する

圧縮なしと自動展開後のバイト列を比較し、圧縮列のサイズも表示します。

python3 - <<'PYCODE' from pathlib import Path plain = Path("plain.bin").read_bytes() decoded = Path("decoded.txt").read_bytes() packed = Path("gzip.bin").read_bytes() print("plain",len(plain),"gzip",len(packed),"decoded",len(decoded))三種類の長さ print("same bytes",plain==decoded展開後の全バイトを比較) PYCODE

展開後は圧縮なしの本文と全バイトが一致します。転送量の比較にはgzipのサイズを、復号後の内容の比較には展開後のバイト列を使います。

7本文が短い場合の圧縮サイズ

短い本文でも常に小さくなるとは限りません。2バイトの本文をgzipへ変換します。

python3 - <<'PYCODE' import gzip tiny = b"A\n" packed = gzip.compress(tiny,mtime=0)短い本文をgzip化 print(len(tiny),len(packed)元と圧縮後のサイズ,gzip.decompress(packed)==tiny内容の復元を確認) PYCODE

元は2バイトでも、gzipのヘッダー・圧縮データ・検査値を付けると大きくなります。今回の繰り返し本文では圧縮効果がありましたが、データの長さや内容で効果は変わります。

まとめ

Content-LengthはそのHTTP本文のバイト数です。UTF-8の文字数や展開後サイズとは異なる場合があります。

Content-Typeは表現の形式、Content-Encodingは適用された圧縮などの方式を表します。

自動展開後のファイルと元のヘッダーを比較するときは、どの段階のサイズを扱っているかを揃えます。

操作履歴と解説を振り返れます。

シェル / curl・HTTP/1.1

1
curl -sS -D plain.headers -o plain.bin http://127.0.0.1:18080/unicode cat plain.headers
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 230
Vary: Accept-Encoding
2
python3 - <<'PYCODE' from pathlib import Path body = Path("plain.bin").read_bytes() print("characters",len(body.decode("utf-8")),"bytes",len(body)) print("first line",repr(body.decode("utf-8").splitlines()[0])) print("first bytes",body[:23].hex(" ")) PYCODE
characters 130 bytes 230
first line '<p>あいうえお</p>'
first bytes 3c 70 3e e3 81 82 e3 81 84 e3 81 86 e3 81 88 e3 81 8a 3c 2f 70 3e 0a
3
curl -sS --raw -H "Accept-Encoding: gzip" -D gzip.headers -o gzip.bin http://127.0.0.1:18080/unicode cat gzip.headers
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 47
Vary: Accept-Encoding
Content-Encoding: gzip
4
python3 inspect_gzip.py
plain characters: 130 plain bytes: 230
gzip bytes: 47 header: 1f 8b 08 00 00 00 00 00 00 ff
trailer: 20 12 b5 93 e6 00 00 00
CRC32: 93b51220 match: True
ISIZE: 230 decoded bytes: 230
decoded equals plain: True
5
curl -sS --compressed -D decoded.headers -o decoded.txt http://127.0.0.1:18080/unicode cat decoded.headers
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 47
Vary: Accept-Encoding
Content-Encoding: gzip
6
python3 - <<'PYCODE' from pathlib import Path plain = Path("plain.bin").read_bytes() decoded = Path("decoded.txt").read_bytes() packed = Path("gzip.bin").read_bytes() print("plain",len(plain),"gzip",len(packed),"decoded",len(decoded)) print("same bytes",plain==decoded) PYCODE
plain 230 gzip 47 decoded 230
same bytes True
7
python3 - <<'PYCODE' import gzip tiny = b"A\n" packed = gzip.compress(tiny,mtime=0) print(len(tiny),len(packed),gzip.decompress(packed)==tiny) PYCODE
2 22 True

演習完了