手打ち HTTP でコシのあるネット生活

:::note warn
この記事は [広島大学 HiCoder & ゲーム制作同好会GSD Advent Calendar 2022
](https://adventar.org/calendars/7845) 1日目の記事です。
:::

# HTTP リクエストを送ってみる

みなさんが毎日使っている、HTTP こと、HyperText Transfer Protocol[^HTTP]。
普段は、生の HTTP リクエストを意識することはあまりありませんが、中身はプレーンテキストなので、手打ちでも通信をすることができます。

この記事では、手打ちで HTTP による通信をやってみることで、
普段ブラウザや curl がどんな仕事をしているか、思いを馳せてみましょう。

「手打ち」という響きには、うどんとか蕎麦みたいなプレミア感が出て良いです。
きっとあなたにとって特別な HTTP リクエストになるはずです。

今回は簡単な HTTP/1.1 で通信をします。

[^HTTP]:日本語訳が「ハイパーテキスト転送プロトコル」なので Haipa-Tekisuto Tensou Purotokoru と覚える方法があるとかないとか。

## Telnet クライアントを使えるようにする

HTTP/1.1 は TCP 上で取り交わしますので、まず TCP コネクションを張らなくてはいけません。
この用途で、telnet という CLI プログラムを使います。

Windows の場合は、[こちらの手順](https://www.imamura.biz/blog/27493) で内蔵の telnet クライアントを有効化してください。
MacOS の場合は、Homebrew でインストールできるようです。

```zsh
brew install telnet
```

## 相手方の IP アドレスを調べる

本当は telnet が自動でやってくれるのですが、ここではあえて相手方の IP アドレスを指定して通信します。

相手は HTTP/1.1 に返事してくれる場所ならどこでもいいです。
例えば Google にしてみましょう[^google]。IPv4 のアドレスを調べます。

[^google]: TCPコネクションのタイムアウトが長いので、キーボードから手でリクエストを打ち込めるのです。他のサービスでいいところがあったら教えてください。

```zsh
nslookup google.com
```

```
Name: google.com
Address: 142.250.207.110
```

## TCP コネクションを張る

ここからは、TCP コネクションのタイムアウトがあるので、手早く行う必要があります。

キーボードのタイピングに自信がない人や、タイムアウトの短いサーバーにアクセスするときは、あらかじめ次の見出しまで読んで、
リクエスト内容をメモ帳で下書きし、クリップボードに入れておくと、安定してリクエストできるでしょう。

次のコマンドで、google.com がいるサーバーの 80番ポートに、TCP で接続します。
80番はご存知の通り、HTTP の Well-Known port です。

```zsh
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 リクエストを作ることになります。

:::note
このレスポンスに HTML が入っているのは Google の親切なところです。
仮にブラウザがリダイレクトを追いかけなかったとしても、ユーザーが手動で正しい URL に移動できるようにリンクを返してあげているのですね。
:::

これで、あなたは一番簡単な HTTP リクエストを自分で発行できたことになります[^tada]。

[^tada]:推しバンドのライブで HTTP によるコール＆レスポンスを要求されても大丈夫でしょう。

# 解説

送ったリクエストと帰ってきたレスポンスを詳しく見てみましょう。

## リクエスト

### 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 のかたは [ダウンロード](https://slproweb.com/products/Win32OpenSSL.html) してください。

方法は、上記で telnet コマンドで接続するところを、次のように書き換えるだけです。

```zsh
openssl s_client -quiet -connect www.google.com:443 -crlf
```

コネクションができさえすれば、あとは同じです[^layer]。

[^layer]:HTTPS が HTTP over SSL であり、SSL 通信とレイヤーがしっかり分離されている恩恵です。

```
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](https://protopedia.net/prototype/1487) で、Twitter API と通信する時に使っています。
該当部分のソースは [このへん](https://github.com/plageoj/twitter-for-pc-g850/blob/2eba166b7dd25f7b3b56681915f5ed23dab3b670/lib/Request/Request.cpp#L52-L62) や [このへん](https://github.com/plageoj/twitter-for-pc-g850/blob/2eba166b7dd25f7b3b56681915f5ed23dab3b670/lib/Twitter/TwitterLib.cpp#L62-L69) にあります。よかったらこちらもご覧ください。

:::note warn
次は 12/4 [リュース04](https://adventar.org/users/52847) さんの「ゲーム制作初心者の自分語り」です。
:::