ESP32で『なんちゃってWebサーバ』を作る

ESP32

今度はWebサーバを作る

概要

Arduinoでもやりましたが、今度はWebサーバを作ってみましょう。世界中で広く使われているものだけに難しそうに感じるかもしれませんが、実は基本的な原理は『オウム返しサーバ』とそれほど変わりません。

とはいえ、例によって極限まで簡単な仕様にします。

  • 一般のWebサーバと同じく、TCP80番ポートで待ち受けする
  • 対応するメソッドはGETのみ
  • というか、リクエストの内容に関わらず、リクエストを送信してきたクライアントに対して固定のHTMLドキュメントをレスポンスとして送信する
  • レスポンスを送信したらTCP切断

です。

 

回路・配線

ESP32-DevkitC本体だけで動作します。電源供給用のUSBケーブル以外、外付け部品・配線は必要ありません。

 

プログラム

ソースコード

C++
#include "WiFi.h"
#include "wifisecret.h"

// リスンポートを23番としてサーバのインスタンスを作成
IPAddress serverIP(192,168,0,101);
IPAddress gatewayIP(192,168,0,1);
IPAddress subnetMask(255,255,255,0);
uint16_t  listenPort=80;
WiFiServer server(listenPort);

void setup(){
  // 無線LANへの接続
  WiFi.config(serverIP, gatewayIP, subnetMask);
  WiFi.begin(_SSID, _PASS);
  while(true){
    if(WiFi.status() == WL_CONNECTED)break;
    delay(1000);
  }
  // サーバの起動
  server.begin();
}

void loop(){
  WiFiClient client = server.available();
  if(client){
    // クライアントと接続した時の処理
    // 1行目のみマジメに受信する
    String currentLine = "";
    while(true){
      if(!client.connected()){
        currentLine = "";
        break;
      }
      if(client.available()){
        // 受信した文字がある場合
        char c = client.read();
        if(c == '\n'){
          break;
        }
        if(c != '\r'){
          currentLine += c;
        }
      }
    }
    if(currentLine!=""){
      // 2行目以降は読み飛ばす
      while(client.available()){
        char c=client.read();
      }
      if(currentLine.startsWith("GET ")){ // 一応メソッドがGETであることを確認
        client.print(getHTMLDocument());
      }
    }
    client.stop();
    while(client.connected());
  }
}

// HTMLドキュメントの送信
String getHTMLDocument(){
    String responseBody = "";
    responseBody += "<!DOCTYPE html>\r\n";
    responseBody += "<html><head><title>test page</title></head>\r\n";
    responseBody += "<body>\r\n";
    responseBody += "<h1>ESP32-DevkitC</h1>\r\n";
    responseBody += "this is test page.\r\n";
    responseBody += "</body></html>\r\n";
    String responseHeader = "";
    responseHeader += "HTTP/1.0 200 ok\r\n";
    responseHeader += "Content-type: text/html\r\n";
    responseHeader += "Content-length: " + String(responseBody.length()) + "\r\n";
    responseHeader += "\r\n";
    return responseHeader+responseBody;
}

プログラムの解説

setup()関数
C++
void setup(){
  // 無線LANへの接続
  WiFi.config(serverIP, gatewayIP, subnetMask);
  WiFi.begin(_SSID, _PASS);
  while(true){
    if(WiFi.status() == WL_CONNECTED)break;
    delay(1000);
  }
  // サーバの起動
  server.begin();
}

今回はsetup()関数中の動作確認用シリアル通信をすべて削除しました。恐ろしく短くなりました。

loop()関数
C++
void loop(){
  WiFiClient client = server.available();
  if(client){
    // クライアントと接続した時の処理
    // 1行目のみマジメに受信する
    String currentLine = "";
    while(true){
      if(!client.connected()){
        currentLine = "";
        break;
      }
      if(client.available()){
        // 受信した文字がある場合
        char c = client.read();
        if(c == '\n'){
          break;
        }
        if(c != '\r'){
          currentLine += c;
        }
      }
    }
    if(currentLine!=""){
      // 2行目以降は読み飛ばす
      while(client.available()){
        char c=client.read();
      }
      if(currentLine.startsWith("GET ")){ // 一応メソッドがGETであることを確認
        client.print(getHTMLDocument());
      }
    }
    client.stop();
    while(client.connected());
  }
}

だいたい『オウム返しサーバ』と同じですが、いくつか違う点もあります。

『オウム返しサーバ』では一行受信→一行送信を繰り返しましたが、今回のWebサーバでは受信→送信を一回やったら通信終了なので、while(true)のループが二重ではなくなっています。

その代わり一回の受信が複数行となります。まぁ今回は『ブラウザが何を送ってこようが構わずに固定のhtmlを返信する』という仕様なので、最初の一行以外は完全に無視することにして

C++
      while(client.available()){
        char c=client.read();
      }

の部分で読み飛ばしています。

変数currentLine にはクライアントから受信したメッセージの最初の一行が保持されていますが、Webブラウザからのリクエストの場合、これは

HTML
GET /ファイルパス HTTPバージョン

のような形式になっています。このプログラムでは一応、最初の4文字が “GET “(4文字目は半角空白)であることを確認していますが、これは次のネタへの伏線です。

その後、

C++
        client.print(getHTMLDocument());

の部分でレスポンス(html文書)を送信しています。

getHTMLDocument()関数
C++
// HTMLドキュメントの送信
String getHTMLDocument(){
    String responseBody = "";
    responseBody += "<!DOCTYPE html>\r\n";
    responseBody += "<html><head><title>test page</title></head>\r\n";
    responseBody += "<body>\r\n";
    responseBody += "<h1>ESP32-DevkitC</h1>\r\n";
    responseBody += "this is test page.\r\n";
    responseBody += "</body></html>\r\n";
    String responseHeader = "";
    responseHeader += "HTTP/1.0 200 ok\r\n";
    responseHeader += "Content-type: text/html\r\n";
    responseHeader += "Content-length: " + String(responseBody.length()) + "\r\n";
    responseHeader += "\r\n";
    return responseHeader+responseBody;
}

getHTMLDocument()関数は、HTTPのレスポンスとして送信する文字列を作成しています。作成される文字列は以下の通りです。

HTML
HTTP/1.0 200 ok
Content-type: text/html
Content-length: 130

<!DOCTYPE html>
<html><head><title>test page</title></head>
<body>
<h1>ESP32-DevkitC</h1>
this is test page.
</body></html>

httpのレスポンスとして送出する文字列は、最初に現れる空行(この例では4行目)より上と下で役割が異なります。空行より上(この例では1~3行目)をレスポンスヘッダ、空行より下(この例では5~10行目)をレスポンスボディといいます。

このうちレスポンスボディは、見ての通りHTMLドキュメントそのものですので、内容についての説明は不要でしょう。このプログラムでは固定の内容を変数responseBodyにセットしています。

ここでは、ブラウザでは見ることの出来ない、レスポンスヘッダ部分について解説します。

ステータス行
HTTP/1.0 200 ok

レスポンスヘッダのうち、1行目をステータス行といい、必須です。『200』の部分を応答状態コードといい、3桁の数値で表されます。『200』は『リクエストが成功した』(クライアントから要求されたコンテンツが正常に送信された)ことを表します。今回はリクエスト内容と無関係にHTMLドキュメントを送りつけるという簡易すぎる仕様なので、固定で200を送信しています。

レスポンスヘッダフィールド

2~3行目はレスポンスヘッダフィールドといい、レスポンスボディで送信するドキュメントに関する情報を記述します。フィールドには多くの種類があるのですが、最低限ここに挙げる2つはないとクライアント側が正常に処理できないことがあります。

Content-type: text/html

Content-Typeフィールドは、送信するドキュメントの種類です。ここではHTML文書を表す”text/html”としています。これがないとブラウザでHTML文書として正常に表示されないことがあります。

Content-length: 130

Content-lengthフィールドは、送信するドキュメントの長さ(バイト数)です。responseBody.length()の値を挿入しています。正確にはlength()はバイト数ではなく文字数を求める関数なのですが、今回はHTMLドキュメント内に半角文字しかないので問題ありません。

 

実行

サーバを起動して、ブラウザのアドレス欄にサーバのアドレス(サンプルソースの通りなら 192.168.0.101)を入力すると、このようにサンプルページが表示されます。

 

まとめ

  • Webサーバは、クライアントからのリクエストに応じてHTMLドキュメントなどを返す

コメント

タイトルとURLをコピーしました