#勉強したことの備忘録です
前回記事にてm5stack
でhttp
のサーバーサンプルプログラムを動かしました。
シンプルにm5stack
をサーバーとしてhttp
通信を使ってブラウザ上でボタンクリックの情報を受けとると言ったものです。
ですが、m5stack
側からクライアントから送られてくるデータをテキスト形式で見ることができるので勉強がてら色々調べてみました。
※自分の思い込みで書いていますので間違っている情報もあると思います。間違っていたらコメントしていただけると助かります。
#サーバープログラムを理解する
m5stack
にて、SimpleWiFiServer
というWifi
ライブラリ内にあるサンプルプログラムでhttp
の動きを確認しました。
このサンプルプログラムでは、クライアント側の送信文字列はシリアルで出力され、サーバー側の応答はプログラムでprintln()
で送信しているためどちらの動きもわかりやすくなっています。
確認した事柄は以下になります。
- クライアントからの処理
-
iphone
のsafari
から接続した場合 -
windows 10
でgoogle chrome
から接続した場合
-
- サーバー側の応答
-
http
の送信文字列形式
-
http
通信については、基本的に以下のものを参考に読み解いていきます。
#クライアント側の送信
teraterm
で受信した文字列が見れるのでブラウザがこのサーバーに対してどういう要求を投げているのか確認することができます。
###iphone
のsafari
から接続した場合
iphone
のsafari
から接続した場合は、以下のようなリクエストメッセージが来ているようです。(長いので折り畳んでいます。)
リクエストメッセージ(iphone)
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.
なぜか一回読み込んだだけで4回も読み込みが発生しています。
なぜなのだろうと調べてみたら、以下の記事で説明がありました。
iphone
のブラウザのマーク?を取得するようです。しかし、4回も読み込んでるのにあまり遅さを感じないあたり通信速度や処理速度の速さを実感しますね。こういうことを試さないと本当にわからないです。
応答で来ているメッセージは以下の8(or 7)のコマンド?が来ています。
(今回デバッグで受け取っているリクエストメッセージはteraterm
の設定で\n
と\r
を共に改行として読み込んでいるため、2行の空きができています。)
-
GET
- 1行目はコマンドと、
http
通信のバージョンを送信しています。GET
メソッドはクライアント側からサーバーに対して後ろの引数に対する応答を要求するコマンドです。
- 1行目はコマンドと、
-
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
から接続した場合は、以下のリクエストメッセージを送っている様子です。(こちらも折りたたんでいます)
応答メッセージ(chrome)
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.
こちらも2回読み込みが発生しています。.icoを要求しているということはこちらも同じくブラウザのマーク?を確認しているのだと思います。
応答メッセージについては、基本的にiphone
のsafari
とほぼ同じですが、順番や要求データ形式の種類、言語情報など違っている部分もあり、これがOSやブラウザの違いなんだとわかります。
#サーバー側の応答
http
通信はクライアント->サーバー->クライアントと、サーバーがクライアント側のメッセージを受信してやり取りをするプロトコルです。
UART
やSSPI
でいうと、マスターはクライアント、スレーブ(子機)側がサーバーといった印象でしょうか。
サンプルプログラムでは、サーバー側の応答について記載があります。
##http
の送信文字列形式
サンプルプログラムでのレスポンスを行っている部分は以下になります。(折りたたんでいます)
レスポンスメッセージ(`m5stack`)
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);
}
}
}
改行を二回確認したら、以下の文字列を送信しています。
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
通信するプログラムを作って文字列送受信を行ってみたいと思います。