http通信を勉強する

[myqiita-1]:https://qiita.com/zasshyu6/items/3d1cdf381e4befcf8ae4

[MDN-1]:https://developer.mozilla.org/ja/docs/Web

[response-5]:https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/User-Agent
[apple-1]:https://www.nishishi.com/blog/2017/07/apple_touch_ico.html


#勉強したことの備忘録です
[前回記事にて`m5stack`で`http`のサーバーサンプルプログラムを動かしました。][myqiita-1]
シンプルに`m5stack`をサーバーとして`http`通信を使ってブラウザ上でボタンクリックの情報を受けとると言ったものです。
ですが、`m5stack`側からクライアントから送られてくるデータをテキスト形式で見ることができるので勉強がてら色々調べてみました。

※自分の思い込みで書いていますので間違っている情報もあると思います。間違っていたらコメントしていただけると助かります。

#サーバープログラムを理解する
`m5stack`にて、`SimpleWiFiServer`という`Wifi`ライブラリ内にあるサンプルプログラムで`http`の動きを確認しました。
このサンプルプログラムでは、クライアント側の送信文字列はシリアルで出力され、サーバー側の応答はプログラムで`println()`で送信しているためどちらの動きもわかりやすくなっています。

確認した事柄は以下になります。

 - クライアントからの処理
    - `iphone`の`safari`から接続した場合
    - `windows 10`で`google chrome`から接続した場合
 - サーバー側の応答
    - `http`の送信文字列形式

`http`通信については、基本的に以下のものを参考に読み解いていきます。
>[開発者向けのウェブ技術][MDN-1]


#クライアント側の送信
`teraterm`で受信した文字列が見れるのでブラウザがこのサーバーに対してどういう要求を投げているのか確認することができます。

###`iphone`の`safari`から接続した場合
`iphone`の`safari`から接続した場合は、以下のようなリクエストメッセージが来ているようです。(長いので折り畳んでいます。)

<details><summary>リクエストメッセージ(iphone)</summary><div>

GET / HTTP/1.1

Host: 192.168.10.254

Upgrade-Insecure-Requests: 1

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 13_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.4 Mobile/15E148 Safari/604.1

Accept-Language: ja-jp

Accept-Encoding: gzip, deflate

Connection: keep-alive



Client Disconnected.

New Client.

GET /apple-touch-icon-120x120-precomposed.png HTTP/1.1

Host: 192.168.10.254

Accept: */*

Accept-Language: ja-jp

Connection: keep-alive

Accept-Encoding: gzip, deflate

User-Agent: MobileSafari/604.1 CFNetwork/1121.2.2 Darwin/19.2.0



Client Disconnected.

New Client.

GET /apple-touch-icon-120x120.png HTTP/1.1

Host: 192.168.10.254

Accept: */*

Accept-Language: ja-jp

Connection: keep-alive

Accept-Encoding: gzip, deflate

User-Agent: MobileSafari/604.1 CFNetwork/1121.2.2 Darwin/19.2.0



Client Disconnected.

New Client.

GET /apple-touch-icon-precomposed.png HTTP/1.1

Host: 192.168.10.254

Accept: */*

Accept-Language: ja-jp

Connection: keep-alive

Accept-Encoding: gzip, deflate

User-Agent: MobileSafari/604.1 CFNetwork/1121.2.2 Darwin/19.2.0



Client Disconnected.

New Client.

GET /apple-touch-icon.png HTTP/1.1

Host: 192.168.10.254

Accept: */*

Accept-Language: ja-jp

Connection: keep-alive

Accept-Encoding: gzip, deflate

User-Agent: MobileSafari/604.1 CFNetwork/1121.2.2 Darwin/19.2.0



Client Disconnected.

</details></div>

なぜか一回読み込んだだけで4回も読み込みが発生しています。
なぜなのだろうと調べてみたら、以下の記事で説明がありました。
>[サーバログにapple-touch-icon系URLの404エラーが多数記録されるので対策をしてみた記録][apple-1]

`iphone`のブラウザのマーク？を取得するようです。しかし、4回も読み込んでるのにあまり遅さを感じないあたり通信速度や処理速度の速さを実感しますね。こういうことを試さないと本当にわからないです。

応答で来ているメッセージは以下の8(or 7)のコマンド?が来ています。
(今回デバッグで受け取っているリクエストメッセージは`teraterm`の設定で`\n`と`\r`を共に改行として読み込んでいるため、2行の空きができています。)


 - `GET`
    - 1行目はコマンドと、`http`通信のバージョンを送信しています。`GET`メソッドはクライアント側からサーバーに対して後ろの引数に対する応答を要求するコマンドです。
 - `Host`
    - 接続先の名前です。今回はIPアドレスで接続しているので、IPアドレスが表示されています。
 - `Upgrade-Insecure-Requests`
    - `https`で接続するためのヘッダーです。最近のブラウザが`https`での通信を基本としているため、`http`での通信する場合でもこれを使って通信するようです。
 - `Accept`
    - 受け取るデータ形式を表しています。今回の場合は,すべてのタイプを宣言していますが、`q:=`で優先じゅにをつけています。優先順位は`thml`=`xml`=`xhtml`>そのほかのデータ形式です。
 - `User-Agent`
    - サーバーへ送っている端末のブラウザ情報、OS情報などなどを表示している部分です。これで接続している端末の種類やアプリケーションを判別できるそうです。
 - `Accept-Language`
    - サーバーへ送っている端末の使用している言語情報を表す部分です。おそらくOSから引っ張ってきているのかも？ここは補足的な用途として使う様子です。
 - `Accept-Encoding`
    - 送信するときに使う圧縮アルゴリズムです。文字エンコーディングとは違い、どの方法で圧縮するかを選択する部分です。
 - `Connection`
    - サーバーへの接続を続けるかどうかの選択部分です。`http 1.1`はデフォルトで`keep-alive`だそうです。

##`windows 10`で`google chrome`から接続した場合
`windows 10`で`google chrome`から接続した場合は、以下のリクエストメッセージを送っている様子です。(こちらも折りたたんでいます)

<details><summary>応答メッセージ(chrome)</summary><div>

New Client.

GET / HTTP/1.1

Host: 192.168.10.254

Connection: keep-alive

Upgrade-Insecure-Requests: 1

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9

Accept-Encoding: gzip, deflate

Accept-Language: ja,en-US;q=0.9,en;q=0.8



Client Disconnected.

New Client.

GET /favicon.ico HTTP/1.1

Host: 192.168.10.254

Connection: keep-alive

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36

Accept: image/webp,image/apng,image/*,*/*;q=0.8

Referer: http://192.168.10.254/

Accept-Encoding: gzip, deflate

Accept-Language: ja,en-US;q=0.9,en;q=0.8



Client Disconnected.

</details></div>

こちらも2回読み込みが発生しています。.icoを要求しているということはこちらも同じくブラウザのマーク？を確認しているのだと思います。

応答メッセージについては、基本的に`iphone`の`safari`とほぼ同じですが、順番や要求データ形式の種類、言語情報など違っている部分もあり、これがOSやブラウザの違いなんだとわかります。

#サーバー側の応答
`http`通信はクライアント->サーバー->クライアントと、サーバーがクライアント側のメッセージを受信してやり取りをするプロトコルです。
`UART`やS`SPI`でいうと、マスターはクライアント、スレーブ(子機)側がサーバーといった印象でしょうか。
サンプルプログラムでは、サーバー側の応答について記載があります。

##`http`の送信文字列形式
サンプルプログラムでのレスポンスを行っている部分は以下になります。(折りたたんでいます)

<details><summary>レスポンスメッセージ(`m5stack`)</summary><div>

```

while (client.connected()) {            // loop while the client's connected
      if (client.available()) {             // if there's bytes to read from the client,
        char c = client.read();             // read a byte, then
        Serial.write(c);                    // print it out the serial monitor
        if (c == '\n') {   
          Serial.println(outpurcount++);                 // if the byte is a newline character

          // if the current line is blank, you got two newline characters in a row.
          // that's the end of the client HTTP request, so send a response:
          if (currentLine.length() == 0) {
            
            // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
            // and a content-type so the client knows what's coming, then a blank line:
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println();

            // the content of the HTTP response follows the header:
            client.print("Click <a href=\"/H\">here</a> to turn ON the LED.<br>");
            client.print("Click <a href=\"/L\">here</a> to turn OFF the LED.<br>");

            // The HTTP response ends with another blank line:
            client.println();
            // break out of the while loop:
            break;
          } else {    // if you got a newline, then clear currentLine:
            currentLine = "";
          }
        } else if (c != '\r') {  // if you got anything else but a carriage return character,
          currentLine += c;      // add it to the end of the currentLine
        }

        // Check to see if the client request was "GET /H" or "GET /L":
        if (currentLine.endsWith(Response_H)) {
          //LED display "GET /H" 
          tft.setCursor(0, LCDWIDTH / 2);
          tft.println(Response_H);
        }
        if (currentLine.endsWith(Response_L)) {
          //LED display "GET /L" 
          tft.setCursor(0, LCDWIDTH / 2);
          tft.println(Response_L);
        }
      }
    }

```

</details></div>

改行を二回確認したら、以下の文字列を送信しています。

```
HTTP/1.1 200 OK
Content-type:text/html

Click <a href=\"/H\">here</a> to turn ON the LED.<br>"
Click <a href=\"/L\">here</a> to turn OFF the LED.<br>
```

1行目は、レスポンスのステータスを表す行です。接続プロトコル、ステータスコード、ステータスコード名を表しています。
2行目は、`HTTP`ヘッダーがおくられています。今回の場合はコンテンツタイプが`html`ですよって応答のみになっています。
3行目は区切りの改行です。どうやらこの改行は必須なようです。
4/5行目は、`html`を送っています。実際の`html`ファイルだとタグがもっとたくさんついていると思ったのですが、このサンプルプログラムでは`<body>`部分のみを送っている様子です。これだけでもブラウザでは表示されるので情報としては十分なようです。

このサンプルプログラムは`/H`か`/L`をクリックした場合`GET`してくる動きになるのですが、`/H`と`/L`以外を指定してもレスポンスメッセージは変わらないのでIPアドレスさえ合っていれば、何を`GET`しても同じ`html`を返します。
この辺りは、実際には`GET`で予想外の部分は来た場合はステータスでかの有名な`404 Not Found`を返してあげるように作る必要がありそうです。


#最後に
web系の基本の`http`通信について動きを見ながら勉強してみました。
学びなおす前は、サーバーは高機能でないと処理が間に合わないのでサーバーは高性能であるべき！という固定概念が自分の中にあったのでどうしてもサーバーとクライアントの処理順番が、サーバー->クライアントという流れじゃないのか？と長年悩み続けていました。
(制御関係で務めているのでどうしても性能がいいものが頭、マスターになるという考えがこびりついていました)
ですが、今回'm5stack'を使ってサーバーの動きを学びなおしたら頭の中がすっきりでき、かなりいい勉強になりました。

次回は、'm5stack'に対して`http`通信するプログラムを作って文字列送受信を行ってみたいと思います。