ESP32でHTTPClientを使ってウェブサーバーにGET、POSTするやり方|HTTPSアクセスまで

著者画像
Toshihiko Arai

はじめに

ESP32でウェブサーバーにHTTPリクエストをするやり方をご紹介します。HTTPClientを使うと簡単にGET、POSTできますよ。SSLを使ったHTTPSでも可能ですが、ここではもっとも簡単なHTTP通信での方法をご紹介いたします。

開発環境

この記事の執筆時では、次の開発環境でプログラムを実行テストしました。

項目
Arduino ESP32-DevKitC
IDE Platform IO
ファームウェア arduino-esp32#2.0.2
HTTPClient ver 2.0.0

ダミーサーバーの用意

ESP32でHTTPリクエストのテストを行う場合は、ダミーサーバーを用意しておくと便利です。Pythonで簡単に立ち上げることができます。必要ない場合は読み飛ばしてください。

パソコン内の適当な場所に、次のような構成でファイルを用意します。

.
├── dummy_root
│   └── status.json
└── dummy_server.py

dummy_server.py の内容は次のとおりです。

from http.server import HTTPServer, BaseHTTPRequestHandler
import os
import re
import logging
import socket

host_list = socket.gethostbyname_ex(socket.gethostname())
lan_address = host_list[2][1]

dirname = os.path.dirname(__file__)
server_ip = "0.0.0.0"
server_port = 9998

lan_url = "http://{}:{:d}".format(lan_address, server_port)
url = "http://{}:{:d}".format(server_ip, server_port)


os.chdir(dirname + "/dummy_root/")


def load_file(path):
    with open(path, mode='r') as f:
        s = f.read()
        return s.encode('utf-8')

class S(BaseHTTPRequestHandler):

    def do_GET(self):
        # logging.info("GET request,\nPath: %s\nHeaders:\n%s\n", str(self.path), str(self.headers))

        m = re.match(r'^/status$', self.path)
        if m != None:
            self.send_response(200)
            self.send_header('Content-type', 'application/json')
            self.end_headers()
            self.wfile.write(load_file("status.json"))


    def do_POST(self):
        print('======================= POST =======================');
        print(self.path)

        m = re.match(r'^/data$', self.path)
        if m != None:
            # {"device_name": "esp32", "temperature": 21.6}

            content_length = int(self.headers['Content-Length']) # <--- Gets the size of data
            post_data = self.rfile.read(content_length) # <--- Gets the data itself
            print('======================= HTTP Request =======================');
            logging.info("POST request,\nPath: %s\nHeaders:\n%s\n\nBody:\n%s\n",
                    str(self.path), str(self.headers), post_data.decode('utf-8'))

            self.send_response(200)
            self.send_header('Content-Type', 'application/json')
            self.end_headers()
            self.wfile.write('{"status":"ok"}'.encode('utf-8'))
        
def web_server():
    logging.basicConfig(level=logging.INFO)
    
    httpd = HTTPServer(('', server_port), S)

    logging.info('Starting httpd...\n')
    httpd.serve_forever()


if __name__ == "__main__":
    print(lan_url)
    print(url)
    web_server()
    # t = threading.Thread(target = web_server)
    # t.start()

    # cmd = 'open {}'.format(url)
    # print(cmd)
    # os.system(cmd)

status.json の内容は次のとおりです。

{
    "TIMESTAMP": "2022-10-30T04:07:00",
    "STATUS": 1
}

GETでHTTPリクエストしたい

ESP32でGETリクエストを使う用途としましては、サーバー上に何らかのステータス状態を置いておき、ESP32からアクセスして確認させることでESP32の動作をコントロールするといった使い方ができます。AWSのIoT Coreで言うところのシャドウのような使い方ですね。AWSではMQTTプロトコルで通信しますが、ちょっとした用途でしたらHTTPリクエストで十分です。

次はESP32からウェブサーバーにGETのHTTPリクエストを送信するプログラム例です。

#include <HTTPClient.h>
#include <WiFi.h>

#include "Arduino.h"

#ifndef WIFI_SSID
#define WIFI_SSID "xxxxx"  // WiFi SSID (2.4GHz only)
#endif

#ifndef WIFI_PASSWORD
#define WIFI_PASSWORD "xxxxx"  // WiFiパスワード
#endif

String url = "http://192.168.61.5:9998/status";

void setup() {
    Serial.begin(115200);

    WiFi.mode(WIFI_STA);
    delay(500);
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
}

void loop() {
    WiFiClient client;
    HTTPClient http;

    if (!http.begin(client, url)) {
        Serial.println("Failed HTTPClient begin!");
        return;
    }

    Serial.println("HTTPClient begin!");
    http.addHeader("Content-Type", "application/json");
    int responseCode = http.GET();
    String body = http.getString();
    Serial.println(responseCode);
    Serial.println(body);

    http.end();

    delay(5000);
}

簡単ですね! setup() でWiFiクライアントを立ち上げておきます。 loop() 内で HttpClientWiFiClient をつっこみ、GETでHTTPリクエストします。 responseCode には、HTTPリクエストのレスポンスコードが格納されてます。200404 エラーなどです。 HTTPリクエストで返された本文の内容は getString() で取得できます。内容がjsonであれば、そのままjsonの文字列が String 型で返されます。

ESP32でJSONをパースすにはjson11を使うと便利です。

POSTでHTTPリクエストしたい

ESP32にセンサを取り付けてそのデータをウェブサーバーに投げて記録したいことは多いですよね。

次はESP32からウェブサーバーにPOSTのHTTPリクエストを送信するプログラム例です。

#include <HTTPClient.h>
#include <WiFi.h>

#include "Arduino.h"

#ifndef WIFI_SSID
#define WIFI_SSID "xxxxx"  // WiFi SSID (2.4GHz only)
#endif

#ifndef WIFI_PASSWORD
#define WIFI_PASSWORD "xxxxx"  // WiFiパスワード
#endif

String url = "http://192.168.61.5:9998/data";

void setup() {
    Serial.begin(115200);

    WiFi.mode(WIFI_STA);
    delay(500);
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
}

void loop() {
    WiFiClient client;
    HTTPClient http;
    String json = R"({"device_name": "esp32", "temperature": 21.6})";

    if (!http.begin(client, url)) {
        Serial.println("Failed HTTPClient begin!");
        return;
    }
    Serial.println(json);
    http.addHeader("Content-Type", "application/json");
    int responseCode = http.POST(json);
    String body = http.getString();

    Serial.println(responseCode);
    Serial.println(body);

    http.end();

    delay(5000);
}

GETとほぼ同じなので説明はいいでしょう。POSTで投げたいデータは http.POST(データ内容) で指定します。

この記事の最初にご紹介したダミーサーバーをお使いになれば、次のようなHTTPリクエストが確認できるはずです。

INFO:root:POST request,
Path: /data
Headers:
Host: 192.168.61.5:9998
User-Agent: ESP32HTTPClient
Connection: keep-alive
Accept-Encoding: identity;q=1,chunked;q=0.1,*;q=0
Content-Type: application/json
Content-Length: 44



Body:
{"device_name": "esp32", "temperature": 21.6}

HTTPClientを使ったHTTPSアクセス(証明書検証なし)

次は、 ESP32 でHTTPS URLに接続してデータを取得する例です。ただし、この例ではSSL証明書の検証を行っていません。

ソースコード

#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>


#ifndef WIFI_SSID
#define WIFI_SSID ""  // TODO WiFi SSID (2.4GHz only)
#endif

#ifndef WIFI_PASSWORD
#define WIFI_PASSWORD ""  // TODO WiFiパスワード
#endif

#ifndef END_POINT
#define END_POINT ""  // TODO アクセス先URL
#endif


void setup() {
    Serial.begin(115200);
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

    while (WiFi.status() != WL_CONNECTED) {
        delay(1000);
        Serial.println("Connecting to WiFi...");
    }

    Serial.println("Connected to WiFi");
}

void loop() {
    if ((WiFi.status() == WL_CONNECTED)) {

        HTTPClient http;

        http.begin(END_POINT);
        int httpCode = http.GET();

        if (httpCode > 0) {
            String payload = http.getString();
            Serial.println(payload);
        }
        http.end();   //Close connection
    }
    delay(10000);    //Send a request every 10 seconds
}

HTTPClientについて

HTTPClient は高レベルのHTTPクライアントライブラリです。HTTPリクエスト(GET, POST, PUT, DELETEなど)を簡単に扱うための関数を提供します。URLに基づいてHTTPリクエストを送信し、レスポンスを取得するのに使用されます。レスポンスのボディやヘッダーの読み取り、HTTPステータスコードの取得などが可能です。HTTPClient は内部的に WiFiClient や WiFiClientSecure を使用して、実際のTCP接続を行います。

WiFiClientSecureを使ったHTTPSアクセス(証明書検証なし)

WiFiClientSecure を使ってHTTPSでリクエストを行う例です。HTTPヘッダーなどを自由にカスタマイズできるため、HTTPClient よりも、よりきめ細かな実装ができます。

ソースコード

#include <Arduino.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>


#ifndef WIFI_SSID
#define WIFI_SSID ""  // TODO WiFi SSID (2.4GHz only)
#endif

#ifndef WIFI_PASSWORD
#define WIFI_PASSWORD ""  // TODO WiFiパスワード
#endif

#ifndef HOST
#define HOST ""  // TODO アクセス先のホスト
#endif

#ifndef PAGE
#define PAGE ""  // TODO アクセス先のページ
#endif


void setup() {
    Serial.begin(115200);
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

    while (WiFi.status() != WL_CONNECTED) {
        delay(1000);
        Serial.println("Connecting to WiFi...");
    }

    Serial.println("Connected to WiFi");
}

void loop() {
    if (WiFi.status() == WL_CONNECTED) {
        WiFiClientSecure client;
        client.setInsecure(); // サーバー証明書の検証なしで接続

        if (!client.connect(HOST, 443)) {
            Serial.println("connection failed");
            return;
        }

        String request = "GET " + String(PAGE) + " HTTP/1.1\r\nHost: " + String(HOST) + "\r\nConnection: close\r\n\r\n";
        client.print(request);
        Serial.println("request sent");

        bool isHeader = true;
        String header, body;

        while (client.connected() || client.available()) {
            if (client.available()) {
                String line = client.readStringUntil('\n');
                if (line == "\r") {
                    isHeader = false;
                    continue;
                }

                if (isHeader) {
                    header += line + "\n"; // ヘッダーを保存
                } else {
                    body += line + "\n"; // ボディを保存
                }
            }
        }
        client.stop();

        Serial.println("Headers:\n" + header);
        Serial.println("Body:\n" + body);
    }

    delay(10000);  // 10秒毎にリクエスト
}

WiFiClientSecureについて

WiFiClientSecure は、SSL/TLSを用いた安全なTCP接続を提供するクライアントライブラリです。このクラスは WiFiClient クラスを拡張し、HTTPSとの通信(SSL/TLSを使用した暗号化通信)をサポートします。サーバーのSSL証明書の検証、クライアント証明書の使用など、セキュリティに関連する追加機能があります。低レベルのTCP通信を行う際に使用され、HTTPのプロトコルレベルの詳細(ヘッダーの構築、メソッドの指定など)は自分で処理する必要があります。

証明書の検証なしでアクセスするリスク

証明書の検証なしでアクセスするリスクに関してChatGPTに聞いてみました。

証明書を確認しないHTTPS通信でも暗号化はされていますが、セキュリティリスクが存在します。通信自体は暗号化されているため、中間者(第三者)による通信内容の直接的な読み取り(傍受)は防げますが、他のセキュリティ上の問題が生じる可能性があります。

証明書の確認を省略した場合のリスク:

  1. 中間者攻撃(Man-in-the-Middle Attack): 証明書を検証しないと、攻撃者が偽の証明書を使って自身を正当なサーバーと偽装することが可能です。これにより、クライアントとサーバー間の通信が攻撃者によって傍受・改ざんされる可能性があります。

  2. 偽のサーバーへの接続: 証明書の検証なしにHTTPS通信を行うと、クライアントは偽のサーバーに接続しているかもしれないというリスクがあります。この場合、送信されるデータは攻撃者によって容易にアクセスされる可能性があります。

HTTPS通信はSSL/TLSプロトコルを使用して暗号化されます。これは、送受信されるデータが暗号化されるため、中間者によるデータの直接的な読み取りを防ぎます。しかし、証明書の検証を行わない場合、その暗号化された通信が本当に正当なサーバーと行われているのか、その保証がありません。

結論として、 HTTPS通信では通信内容は暗号化されていますが、証明書の検証を省略することによるセキュリティリスクは依然として高いです。セキュアな通信を保証するためには、証明書の検証を行うことが重要です。特に、機密性の高いデータを扱う場合や公開される製品・サービスでは、証明書の検証を適切に行うべきです。

Amazonで探す