TT Lab
はじめる
学ぶ 学習パス コース

ネットワーク基礎

HTTP を手でやり取りする

TT Labで続きを見る

目標

HTTPは難しくありません。TCPの上で、約束された文字をやり取りするだけです。ところが、ライブラリがその約束を代わりに守ってくれるので、食い違ったときに、何が食い違ったのかを読めなくなります。

ここでは、その約束を手で守ってみて、ライブラリが隠したものを1つずつ見ます。

用意するもの

python3 /opt/lab/netplan/httpsrv.py 8080 &

パスごとに、わざと違う形で応答する小さなサーバーです。

/            Content-Length 로 길이를 알려 준다
/chunked     길이를 모른 채 조각으로 나눠 보낸다
/slow        첫 조각을 보내고 잠시 쉰다
/nolength    길이도 조각도 없이, 연결을 닫아 끝을 알린다
/moved  /seeother  /keepme    301 · 303 · 307

残すもの

01-raw.txt        손으로 친 요청과 그 응답
02-nohost.txt     Host 를 뺐을 때와 1.0 으로 보냈을 때
03-length.txt     본문의 끝을 알리는 세 방법
04-keepalive.txt  한 연결에 요청 두 번
05-redirect.txt   301 · 303 · 307 의 차이
06-timing.txt     구간별 시간
07-notes.md       다음에 읽을 사람에게

リクエストを手で打つ

ライブラリなしで、TCP接続に直接HTTPリクエストの文字列を書いて、/の応答を受け取り、01-raw.txtに残してください。送ったものと受け取ったものを、両方残します。

bashだけでできます。

exec 3<>/dev/tcp/127.0.0.1/8080
printf 'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n' >&3
timeout 3 cat <&3

行末は必ず\r\nにします。ヘッダーの最後に、空行がもう1つ必要です。その空行が、「ヘッダーはここまで」という合図です。

Hostを抜いてみる

HostヘッダーなしでHTTP/1.1のリクエストを送り、同じリクエストをHTTP/1.0でも送って、結果を02-nohost.txtに残してください。なぜ1.1から必須になったのかを1行書きます。

1.1では、400 Bad Requestがルールです。サーバーの好みではなく、仕様です。

1つのアドレスに複数のサイトを載せられるようになったことで、必須になりました。サーバーは、どのサイト宛のリクエストかを、Hostでしか知ることができません。

本文はどこで終わるのか

/・/chunked・/nolengthをそれぞれ受け取り、本文の終わりを知らせる3つの方法を03-length.txtに残してください。chunkedは加工せず、元のバイトのまま残します。

curlは、chunkedを自動的に結合してくれるので、元のものが見えません。/dev/tcpで直接受け取ってください。

チャンクごとに16進数のサイズの行が前に付き、最後にサイズ0のチャンクが終わりを知らせます。その0がないと、受け取る側は待ち続けます。

1つの接続で2回

接続を1つ開いたまま、リクエストを2回送って、レスポンス2つを受け取り、04-keepalive.txtに残してください。接続を再利用すると何が節約されるのかを1行書きます。

Connection: closeを外すと、サーバーが接続を維持します。最初の応答を、Content-Lengthの分だけ読んでから、2つ目のリクエストを同じfdで送ります。

Pythonでやると、長さの分だけ正確に読むのが簡単です。

301・303・307は何が違うのか

3つのパスをそれぞれ受け取って、応答を05-redirect.txtに残し、3つの違いを書いてください。特に、再度リクエストするときにメソッドを変えてもよいかを書きます。

3つともLocationを返しますが、再リクエストの方法が違います。

決済リクエストがリダイレクトに出会ったとき、どちらなのかが、そのまま事故と正常を分けます。

遅い区間を数字で指し示す

/と/slowを、それぞれcurlの書式変数で測って06-timing.txtに残し、どの区間が伸びたのかを書いてください。これらの値が累積であることも、あわせて書きます。

curl -sS -o /dev/null -w 'dns=%{time_namelookup} tcp=%{time_connect} ttfb=%{time_starttransfer} total=%{time_total}\n' http://127.0.0.1:8080/

数字は累積です。time_connectが0.32なら、接続までに0.32秒かかったという意味であり、接続に0.32秒かかったという意味ではありません。区間ごとの時間は、引き算して求めます。

/slowは、最初のバイトを送った後で休みます。すると、ttfbは速いのに、totalだけが大きくなります。

次に読む人へ

ここで見たものの中から、4つ以上を選んで07-notes.mdにまとめてください。何をしたかではなく、なぜそうなるのかを書きます。

数か月後の自分が読むつもりで書いてください。「/chunkedを受け取った」は役に立たず、「長さを事前に知らないときは、チャンクに分けて送り、サイズ0のチャンクで終わりを知らせます。その0がないと、受け取る側が待ち続けます」は役に立ちます。