この記事は 広島大学 HiCoder & ゲーム制作同好会GSD Advent Calendar 2022
1日目の記事です。
HTTP リクエストを送ってみる
みなさんが毎日使っている、HTTP こと、HyperText Transfer Protocol1。
普段は、生の HTTP リクエストを意識することはあまりありませんが、中身はプレーンテキストなので、手打ちでも通信をすることができます。
この記事では、手打ちで HTTP による通信をやってみることで、
普段ブラウザや curl がどんな仕事をしているか、思いを馳せてみましょう。
「手打ち」という響きには、うどんとか蕎麦みたいなプレミア感が出て良いです。
きっとあなたにとって特別な HTTP リクエストになるはずです。
今回は簡単な HTTP/1.1 で通信をします。
Telnet クライアントを使えるようにする
HTTP/1.1 は TCP 上で取り交わしますので、まず TCP コネクションを張らなくてはいけません。
この用途で、telnet という CLI プログラムを使います。
Windows の場合は、こちらの手順 で内蔵の telnet クライアントを有効化してください。
MacOS の場合は、Homebrew でインストールできるようです。
brew install telnet
相手方の IP アドレスを調べる
本当は telnet が自動でやってくれるのですが、ここではあえて相手方の IP アドレスを指定して通信します。
相手は HTTP/1.1 に返事してくれる場所ならどこでもいいです。
例えば Google にしてみましょう2。IPv4 のアドレスを調べます。
nslookup google.com
Name: google.com
Address: 142.250.207.110
TCP コネクションを張る
ここからは、TCP コネクションのタイムアウトがあるので、手早く行う必要があります。
キーボードのタイピングに自信がない人や、タイムアウトの短いサーバーにアクセスするときは、あらかじめ次の見出しまで読んで、
リクエスト内容をメモ帳で下書きし、クリップボードに入れておくと、安定してリクエストできるでしょう。
次のコマンドで、google.com がいるサーバーの 80番ポートに、TCP で接続します。
80番はご存知の通り、HTTP の Well-Known port です。
telnet 142.250.207.110 80
HTTP リクエストを送る
次の内容が、送る HTTP リクエストの内容です。簡単ですね。
google.com
というホストに対して、/
の内容を GET
でリクエストします。
GET / HTTP/1.1
Host: google.com
TCP がつながったら、この内容を素早く打ち込みます。
下書きした人は端末上でペースト操作をします(Ctrl+Shift+V など)。
telnet クライアントによっては、打った内容が画面に表示されないかもしれませんが
気にせず打ち込みます。
最後の改行も忘れず送信してください。
改行を送信すると、すぐに向こうのサーバーから返事があるはずです。
Google の場合はこんな返事でした(レスポンスの HTML は整形しています)。
HTTP/1.1 301 Moved Permanently
Location: http://www.google.com/
Content-Type: text/html; charset=UTF-8
Cross-Origin-Opener-Policy-Report-Only: same-origin-allow-popups; report-to="gws"
Report-To: {"group":"gws","max_age":2592000,"endpoints":[{"url":"https://csp.withgoogle.com/csp/report-to/gws/other"}]}
Date: Tue, 29 Nov 2022 13:12:14 GMT
Expires: Thu, 29 Dec 2022 13:12:14 GMT
Cache-Control: public, max-age=2592000
Server: gws
Content-Length: 219
X-XSS-Protection: 0
X-Frame-Options: SAMEORIGIN
<HTML>
<HEAD>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>301 Moved</TITLE>
</HEAD>
<BODY>
<H1>301 Moved</H1>
The document has moved <A HREF="http://www.google.com/">here</A>.
</BODY>
</HTML>
長いレスポンスですが、見るべきは最初の2行です。つまり、
「君がアクセスしようとした http://google.com/
は、https://www.google.com/
に移動したから、そっちを見に行ってくれ」
ということです。
HTTP クライアントであるあなたはこれを解釈して、次は https://www.google.com/
を GET
するような HTTP リクエストを作ることになります。
このレスポンスに HTML が入っているのは Google の親切なところです。
仮にブラウザがリダイレクトを追いかけなかったとしても、ユーザーが手動で正しい URL に移動できるようにリンクを返してあげているのですね。
これで、あなたは一番簡単な HTTP リクエストを自分で発行できたことになります3。
解説
送ったリクエストと帰ってきたレスポンスを詳しく見てみましょう。
リクエスト
HTTP リクエスト
GET / HTTP/1.1
「HTTP/1.1 を使って、あなたのサーバーの /
を GET
します」という意です。
リクエストが POST
メソッドなら、最初が POST
になりますし、
リクエスト先が /path/to/index.html
なら2番目のフィールドが /path/to/index.html
になります。
ヘッダ
Host: google.com
2行目からは HTTP ヘッダを入れる場所になります。ここでは Host
ヘッダを指定しました。
今回アクセスした IP アドレスの 142.250.207.110
には、複数のホストがいる可能性があります。
現に、kix06s11-in-f14.1e100.net
という名前のホストも、同じ IP アドレスに解決されます。
このときにアクセスしたいサーバが google.com
であることを指定するのが、Host
ヘッダです。
これがないと、サーバはリクエストが自分に来ているかどうか判断できません。
HTTP ヘッダには他にもいろいろなものがあります。
プレーンテキストで返事してほしいなら、
Accept: text/plain
日本語で返事してほしいなら、
Accept-Language: ja-JP
自分が HTTP リクエストを手入力していることを伝えたいなら(伝わるかどうかはわかりませんが)、
User-Agent: handwriting
などなど、続けて入力することができます。
リクエストの終了
(空行)
次の空行は、リクエストが終わったことを伝えるためのものです。
サーバーは、多少キーボード入力が遅くても、この空行が来るまではリクエストが終わらないと思って待っていてくれます。
逆に空行を入れないと返事が返ってきません。そういうプロトコルです。
レスポンス
HTTP レスポンス
HTTP/1.1 301 Moved Permanently
レスポンスステータスコード 301 のレスポンスです。
これは、アクセスしようとしたコンテンツがそこにはなく、永続的に移動したことを示します。
ヘッダ
Location: http://www.google.com/
Content-Type: text/html; charset=UTF-8
Location
ヘッダはコンテンツの移動先を、Content-Type
ヘッダはこの下に続くコンテンツが UTF-8 の HTML であることを示します。
そのほかのヘッダは割愛です。
ボディ
ヘッダから1行空けて、HTML 本文が返ってきています。
もちろん、Content-Type
が HTML でない場合には、それに応じて違う内容の本文が返ってきます。
HTTPS でリクエストするには
先ほどのレスポンスで、我々は https://www.google.com/
にアクセスしなければならないことがわかりました。
ところが、telnet では SSL/TLS の通信はできないので、HTTPS は通信できません。
この用途では openssl が使えます。
MacOS ならデフォルトで入っているはず。
Windows のかたは ダウンロード してください。
方法は、上記で telnet コマンドで接続するところを、次のように書き換えるだけです。
openssl s_client -quiet -connect www.google.com:443 -crlf
コネクションができさえすれば、あとは同じです4。
GET / HTTP/1.1
Host: www.google.com
今度は、大きなレスポンスが返ってきました。
HTTP/1.1 200 OK
Date: Tue, 29 Nov 2022 13:56:47 GMT
Expires: -1
Cache-Control: private, max-age=0
Content-Type: text/html; charset=ISO-8859-1
Cross-Origin-Opener-Policy-Report-Only: same-origin-allow-popups; report-to="gws"
Report-To: {"group":"gws","max_age":2592000,"endpoints":[{"url":"https://csp.withgoogle.com/csp/report-to/gws/other"}]}
P3P: CP="This is not a P3P policy! See g.co/p3phelp for more info."
Server: gws
X-XSS-Protection: 0
X-Frame-Options: SAMEORIGIN
Set-Cookie: 1P_JAR=2022-11-29-13; expires=Thu, 29-Dec-2022 13:56:47 GMT; path=/; domain=.google.com; Secure
Set-Cookie: AEC=AakniGMbUGYXxfVFLLcGURQEFTPychfxs9chpKcj0PQK0Hj3b5w7uNIZAw; expires=Sun, 28-May-2023 13:56:47 GMT; path=/; domain=.google.com; Secure; HttpOnly; SameSite=lax
(略)
5a4d
<!doctype html>
<html itemscope="" itemtype="http://schema.org/WebPage" lang="ja">
(略)
きちんとレスポンスコード 200 が返ってきていることがわかります。
しかも、HTML の言語は日本語になっていますね。
このレスポンス全体は、ぜひ自分の目で確かめてみてください。
で、こんなこといつ使うの?
本記事で取り上げた HTTP リクエストを自力で組み立てるというテクニックは、拙作 Twitter for PC-G850 で、Twitter API と通信する時に使っています。
該当部分のソースは このへん や このへん にあります。よかったらこちらもご覧ください。
次は 12/4 リュース04 さんの「ゲーム制作初心者の自分語り」です。