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

# やりたいこと
かっこよくターミナルからHTTPリクエストを送りたいと思いませんか？
TCPコネクションを `nc` コマンドで確立し、その後TCP通信で `GET` リクエストを送りましょう。
リクエストを送るホストは、ご存知の `google.com` の `ポート 80` とします。

# その前にHTTPとは？
OSI参照モデルでいうアプリケーション層にある**HTTP**というプロトコルは、Webページをブラウジングするためや、ログインIDやパスワードを送ることにも利用されるWWW(World Wide Web)を代表するプロトコルです。
Webサーバは、HTTPのフォーマットで書かれた `HTTPリクエスト` をクライアント（ブラウザ）から受け取り、 ステータスコードとともに `HTTPレスポンス` をクライアントに返します。

## HTTPリクエストのメソッド

|メソッド名  |説明|
|------|---|
|GET|URIを用いてサーバから情報を受け取るためのメソッド。サーバ側の情報を書き換えるといったことはしない。|
|HEAD|GETと基本的には同じだが、ステータスコードとヘッダーのみを返す点が異なる。|
|POST|クライアントからサーバになにかしらのデータを送る時に使われる。|
|PUT|クライアントが既に知っているURIにあるリソースを作ったり上書きしたりするために用いる。|
|DELETE|URIにあるリソースを削除するために用いる。|
|CONNECT|URIで指定されるサーバとのコネクションを確立する。|
|OPTIONS|対象リソースとの通信のためのオプションを返す。|
|TRACE|対象リソースへの経路に沿ったループバックテストを行う。|


https://www.tutorialspoint.com/http/http_requests.htm のなかで紹介された表を訳しただけです。
POSTとPUTの違いは、https://www.keycdn.com/support/put-vs-post を参考にしました。

# TCPコネクションを確立する
何はともあれ、TCPコネクションを確立しないとHTTP通信をすることはできません。
`nc` コマンドでは、トランスポート層のプロトコルである**TCP**と**UDP**を使ったコネクションを作ることができます。
なにもオプションを指定しない場合は、**TCP**コネクションを確立しようとします。

```
$ nc -v google.com 80
found 0 associations
found 1 connections:
     1:	flags=82<CONNECTED,PREFERRED>
	outif en0
	src 192.168.128.246 port 52041
	dst 172.217.161.206 port 80
	rank info not available
	TCP aux info available

Connection to google.com port 80 [tcp/http] succeeded!
```
`google.com` のWebサーバ（ポート80）とのTCPコネクションを結ぶことに成功しました。

このコネクションを通じて、対象とするホストにあるポートに対して、データの送受信をすることができます。

## GETリクエスト
このTCPコネクションを用いて、GETリクエストでHTMLを取得してみましょう。

```
GET / HTTP/1.1
```
と入力した後、改行し、もう一度空行を送信します。
すると、HTTPリクエストが返ってきます。

```
HTTP/1.1 200 OK
Date: Thu, 23 May 2019 16:04:22 GMT
Expires: -1
Cache-Control: private, max-age=0
Content-Type: text/html; charset=ISO-8859-1
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=2019-05-23-16; expires=Sat, 22-Jun-2019 16:04:22 GMT; path=/; domain=.google.com
Set-Cookie: NID=184=rWXmA7VSwF4GNfvX7kNWjSzTx4sRBODwanmpsOznfM1qEnjS_taYCnk_mit7kYqE6WcNdgHXApOPuNnXEGwExSI0fsdiWV3e2dykYLNY2Kdmca4cwIhn1S7l0THIeiM9Pfh1quKMHM4H1GN6h6lN30InP85WKgpbuu8H2D-Osvw; expires=Fri, 22-Nov-2019 16:04:22 GMT; path=/; domain=.google.com; HttpOnly
Accept-Ranges: none
Vary: Accept-Encoding
Transfer-Encoding: chunked

52b6
<!doctype html><html itemscope="" itemtype="http://schema.org/WebPage" lang="ja">
... （数十行にも及ぶため、省略）
```

一番上の行にある`200`という**ステータスコード**によって、サーバから返されるレスポンスがどのようなものであるかがわかります。
ステータスコードの詳細は、https://developer.mozilla.org/ja/docs/Web/HTTP/Status でみることができます。
ざっくりいうと、**200番台が成功**、**300番台がリダイレクトメッセージ**、**400番台がクライアントエラー**、**500番台がサーバエラー**のように分類されています。
`200`というステータスコードについて、上のURL内では次のように説明されています。
>200 OK
リクエストが成功したことを示します。成功が意味することは、 HTTP メソッドにより異なります:
GET: リソースが取り出され、メッセージ本文で転送されます。
HEAD: エンティティヘッダーがメッセージ本文内にあります。
PUT 又は POST: アクションの結果を表すリソースがメッセージ本文で転送されます。
TRACE: サーバーが受理したリクエストメッセージがメッセージ本文に含まれています。

この場合、GETリクエストでしたので、指定したURIからリソースが取り出され、メッセージ本文がヘッダーの後に送られてきています。

ついでに、**HEAD**メソッドでも同様に送ってみましょう。

```
HEAD / HTTP/1.1

HTTP/1.1 200 OK
Date: Thu, 23 May 2019 16:19:50 GMT
Expires: -1
Cache-Control: private, max-age=0
Content-Type: text/html; charset=ISO-8859-1
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=2019-05-23-16; expires=Sat, 22-Jun-2019 16:19:50 GMT; path=/; domain=.google.com
Set-Cookie: NID=184=pcNHM2fGSG7yQ8gkR1EgmWbAffLvLPXabxORlTrqWjHBablDRBEYc91PKbUwfnPS15_i7-Vt906vQkTIFpbvXUiJ8jfDaL4Pqsz4M_bz58uu6BCRNhs3Z_RqdYQyrC1z6Mxr9z-iANryEApMFiLYQHMyhPM5Vr-uOykygtTdJYg; expires=Fri, 22-Nov-2019 16:19:50 GMT; path=/; domain=.google.com; HttpOnly
Transfer-Encoding: chunked
Accept-Ranges: none
Vary: Accept-Encoding
```

## リクエストヘッダーを変えてみる
ここまでは、１行のHTTPリクエストをWebサーバに送信してきましたが、HTTPリクエストにもヘッダーを追加することで、Webサーバに情報を与えてあげることができます。
指定できるヘッダーはたくさんあります（https://flaviocopes.com/http-request-headers/ ）。

もちろん、GETだけでなく、POSTやPUTであっても同じようにヘッダーを用いることで通信の可能性を広げることができます。
`Cookie`もこのリクエストヘッダに追加することができます。

例えば、`User-Agent`を指定することで、特定のブラウザからアクセスしたという情報をWebサーバに伝えることができます。


```
GET / HTTP/1.1
User-Agent: Mozilla/5.0
```
ちょっと`Mozilla/5.0`からアクセスしたと嘘をついてみましょう。

```
HTTP/1.1 200 OK
Date: Thu, 23 May 2019 16:33:52 GMT
Expires: -1
Cache-Control: private, max-age=0
Content-Type: text/html; charset=UTF-8
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=2019-05-23-16; expires=Sat, 22-Jun-2019 16:33:52 GMT; path=/; domain=.google.com
Set-Cookie: NID=184=pKW_xUk70PPzuhvxzOxfHncQGORwfmqmE-IRr6VDf0j45_G58VZO9x3kpARSAvjzVtZGm8ID1AflpCX7_uwhjufqflgtdJOdKc5uJJj3yWj6ayGDPt_HjhOi8sxnDlLn_I8whAC29ySo1sqW7YvLGlFkkTNHq_HUbbRwahQrr5w; expires=Fri, 22-Nov-2019 16:33:52 GMT; path=/; domain=.google.com; HttpOnly
Accept-Ranges: none
Vary: Accept-Encoding
Transfer-Encoding: chunked

51ca
<!doctype html><html itemscope="" itemtype="http://schema.org/WebPage" lang="ja"><head><meta content="世界中のあらゆる情報を検索するためのツールを提供しています。さまざまな検索機能を活用して、お探しの情報を見つけてください。" name="description"><meta content="noodp" name="robots"><meta content="text/html; charset=UTF-8" http-equiv="Content-Type">
```

少し細かい話ですが、レスポンスヘッダのなかの`charset`が`UTF-8`に変更されました。
そのおかげでメッセージ部分が日本語で表示されるようになっています。

# まとめ
HTTPという超有名プロトコルも、元を正せばTCPコネクションでの特定フォーマットを用いたデータの送受信に過ぎないということがわかっていただけたでしょうか？
APIあたりの話も今後追加していこうと思います。