Fyzikální kabinet FyzKAB

ESP32: HTTP a HTTPS požadavek

K tématu dnešního článku se dostaneme tak trochu oklikou. V jednom projektu bylo potřeba modulem ESP32 zjistit, jaká je veřejná adresa Wi-Fi sítě, ve které byl modul přihlášen. Situaci okolo privátních a veřejné IP adresy ukazuje obrázek č. 1. Zatímco se jednotlivé zařízení v rámci domácí sítě (např. Vaší Wi-Fi) vzájemně identifikují pomocí privátních adres 192.168.8.x, tak směrem do Internetu přistupují přes Wi-Fi směrovač, který má tzv. veřejnou adresu – zde kupříkladu 82.129.80.111.

Public vs local IP addresa
Obrázek č. 1 – Každé zařízení v síti má soukromou IP adresu a směrovač
má veřejnou IP adresu pro komunikaci se zbytkem internetu.

První možností, která nás napadla, bylo použití dotazu na některý ze serverů, které jsou již k tomuto účelu na internetu k dispozici. Když se již umíme modulem ESP32 dotázat na NTL server pro synchronizaci času (viz článek: ESP32: Získání času a data ze NTP serveru), tak proč bychom nezvládli dotaz na běžný webový server. Pokusíme se tedy na takový server připojit a on nám naši veřejnou IP adresu prozradí. A dokonce zkusíme vybrat takový server, který tyto informaci vrací v čisté textové podobě – takovým serverem je například server: https://api.ipify.org, nebo https://api.my-ip.io/ip.txt.

A tady jsme právě narazili na ten problém! Oba tyto servery jsou na zabezpečeném protokolu HTTPS, takže klasický http-klient, který si na modulu ESP32 pro účel získání IP adresy vytvoříme, při dotazu selže.

Jak tedy v tomto případě získat veřejnou IP adresu?

Řešení je dvojí, buď musíme najít server, který je ještě na nezabezpečeném protokolu HTTP, nebo začít řešit otázku, jak lze modulem ESP32 vytvořit HTTPS požadavek. A tím se dostáváme k tématu dnešního článku…

Dnešní článek nám tedy nejen ukáže, jak modulem ESP32 získat veřejnou IP adresu naší Wi-Fi sítě, to nám ale bude jen ukázkou toho, jak vytvořit jednoduchý klient, který zašle HTTPS dotaz na zadaný server.

HTTP dotaz – získání veřejné IP adresy modulu

Když už jsme úvodní část dnešního článku pojali tak trochu jako detektivku, vyřešíme tedy nejdříve náš případ hledání veřejné IP adresy modulem ESP32 a pak se vrhneme na problém toho HTTPS dotazu.

Řešení problému jsme již naznačili před chvílí, zkrátka využijeme server, který nám vrací IP adresu v podobě čistého textu a je stále realizován na protokolu HTTP – například server: http://checkip.amazonaws.com nebo http://checkip.dyndns.org (tedy pokud nám v druhém případě nevadí vrácená zpětná informace v podobě jednoduché html stránky).

Následující kód ukazuje jednoduchý program pro zjištění veřejné IP adresy pomocí HTTP dotazu. Uvádíme ho zde nejen, abychom si ukázali, jak zjistit tu hledanou veřejnou IP adresu, ale abychom získali kód, na kterém pak budeme stavět dále.

/* HTTP zjisteni verejne IP */
#include <WiFi.h>
#include <HTTPClient.h>

const char* ssid = "Jmeno-Vasi-WiFi-site";
const char* password = "heslo-vasi-wifi-site";

//zadani jmena nebo IP adresy serveru pro dotaz
// const char* serverName = "http://checkip.dyndns.org";
const char* serverName = "http://checkip.amazonaws.com";

HTTPClient http;
String Server_Response;

// funkce SETUP se spusti jednou pri stisknuti tlacitka reset nebo pri zapnuti desky.
void setup() {
  Serial.begin(115200);

  WiFi.begin(ssid, password);
  Serial.println("Pripojeni k WiFi");
  while(WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Pripojen k Wifi s IP adresou: ");
  Serial.println(WiFi.localIP());

  delay(1000);

  if(WiFi.status() == WL_CONNECTED ) {
    Serial.print("Dotazuji server: ");
    Serial.println(serverName);
    Server_Response = httpGETRequest(serverName);
    Serial.print("Verejna IP adresa: ");
    Serial.println(Server_Response);
  }

}

// funkce LOOP bezi stale dokola.
void loop() {
  ;
}

// ----
String httpGETRequest(const char* serverName) {
  http.begin(serverName);
  int httpResponseCode = http.GET();   // zaslani http dotazu

  String payload = "--";

  if (httpResponseCode > 0) {
    Serial.print("Kod HTTP odpovedi: ");
    Serial.println(httpResponseCode);
    payload = http.getString();
  } else {
    Serial.print("Chyba - kod: ");
    Serial.println(httpResponseCode);
  }

  http.end();   // konec prenosu

  return payload;

}

Princip kódu

Úvodní část, tedy část kódu do procedury setup() až do vteřinového čekání uvnitř těla procedury setup(), řeší připojení modulu ESP32 k domácí Wi-Fi síti. Tuto část jsme již poznali a vysvětlili si v článku Wi-Fi přístup k modulu ESP32, proto ji zde přeskočíme. Za zmínku zde jen stojí řádka:

#include <HTTPClient.h>

která připojuje k projektu knihovnu pro vytvoření HTTP klienta a dále pak řádky:

HTTPClient http;
String Server_Response;

které vytvoří instanci HTTP klienta v objektu nazvaném http a deklarují proměnnou Server_Response, která bude obsahovat návratový řetězec odpovědi serveru.

Jakmile se modul ESP32 připojí k Wi-Fi síti, vyčká se 1 vteřinu a pokud je modul opravdu připojen (ověřeno podmínkou WiFi.status() == WL_CONNECTED), odešle dotaz na zadaný server.

if (WiFi.status() == WL_CONNECTED) {
  Serial.print("Dotazuji server: ");
  Serial.println(serverName);
  Server_Response = httpGETRequest(serverName);
  Serial.print("Verejna IP adresa: ");
  Serial.println(Server_Response);
}

Pro dotaz na server je vytvořena funkce httpGETRequest se vstupním parametrem adresy serveru. Kód této funkce je deklarován na konci programu a my se na něj nyní podíváme podrobněji.

String httpGETRequest(const char* serverName) {
  http.begin(serverName);
  int httpResponseCode = http.GET();   // zaslani http dotazu

  String payload = "--";

  if (httpResponseCode > 0) {
    Serial.print("Kod HTTP odpovedi: ");
    Serial.println(httpResponseCode);
    payload = http.getString();
  } else {
    Serial.print("Chyba - kod: ");
    Serial.println(httpResponseCode);
  }

  http.end();   // konec prenosu

  return payload;
}

Nejdříve se naváže spojení se serverem, poté se do proměnné httpResponseCode načte kód odpovědi serveru. Obecně platí, že kladné kódy (např. 200, 404 apod.) odpovídají nějaké konkrétní odpovědi (např. OK, File not found apod.), záporná hodnota návratového kódu indikuje vnitřní chybu modulu ESP32. Pokud je tedy kód odpovědi kladný, načte se i tělo dopovědi do proměnné payload. V případě dotazu na server, který vrací čistý text s číslem veřejné IP adresy, se zde načte to, co hledáme. Pokud byste se dotázali na nějaký webový server (např. seznam.cz), vrátil by se HTML kód dotázané stránky.

Po výpisu návratového kódu serveru na sériový port funkce dále jako svou hlavní návratovou hodnotu httpGETRequest vrátí tělo dopovědi serveru. Tato hodnota je v proceduře setup() vypsána na sériový port a program končí.

Všimněte si, že procedura hlavní smyčky loop() je zde zcela prázdná. Po vypsání lokální a veřejné IP adresy na sériový port program končí v nekonečné prázdné smyčce. Pochopitelně za normálních podmínek by hlavní smyčka dále obsahovala další instrukce „užitečné práce“.

Po přeložení kódu a odeslání do modulu ESP32 (nezapomeňte před kompilací do programu zadat jméno vaší Wi-Fi sítě a platné přístupové heslo) by po následném resetu ESP32 měl vypadat výstup na sériovém monitoru podobně jako na obrázku č. 2.

http verejna IP serial port
Obrázek č. 2 – získání veřejné IP adresy pomcí HTTP dotazu

Pokud bychom v kódu do proměnné serverName zapsali libovolnou adresu serveru s protokolem HTTPS, vrátil by program chybový kód (záporná hodnota kódu) a veřejná IP by se nenačetla (výstupem by byla nastavená počáteční hodnota --).


HTTPS dotaz – získání veřejné IP adresy modulu

Nyní se podíváme, jak přistupovat k serverům s HTTPS protokolem. Podrobné vysvětlení toho, jak HTTPS funguje, je mimo rozsah tohoto příspěvku, ale smíříme se s tím, že HTTPS je v zásadě zabezpečená verze HTTP, což znamená, že data vyměňovaná mezi serverem a klientem jsou šifrována. Aby bylo možné navázat spojení HTTPS, musí server poskytnout svůj digitální certifikát, který obsahuje jeho veřejný šifrovací klíč, potřebný pro úvodní protokolový handshake.

Je však důležité poznamenat, že připojení klienti obvykle nevědí, zda je certifikát serveru, na který se snaží přistoupit, platný, což znamená, že nevědí, zda mu mohou důvěřovat. Postup ověření certifikátu tedy kontroluje, kdo vydal certifikát serveru, ke kterému se připojujeme. Pokud bychom ani potom serveru nedůvěřovali, postoupíme o další úroveň výše a tak dále a vytvoříme řetězec certifikátů. V určitém okamžiku musíme někomu věřit! Proto klienti obvykle důvěřují kořenovým certifikačním autoritám, které jsou na vrcholu certifikačního řetězce. Webové prohlížeče mají seznam certifikačních autorit, kterým budou důvěřovat, když je najdou v certifikačním řetězci.

Jenže! Protože budeme provádět naši žádost z modulu ESP32 a ne z prohlížeče, znamená to, že budeme muset zadat do modulu certifikát nějaké certifikační autority, které důvěřujeme. Pochopitelně budeme muset použít pro ověření certifikačního řetězce certifikát platný pro webovou stránku, na kterou se snažíme dostat.

Nyní se tedy podíváme, jak získat potřebný certifikát.

Získání certifikátu

Chcete-li získat certifikát kořenové certifikační autority, otevřeme požadovanou stránku, ke které budeme chtít přistoupit ve webovém prohlížeči. My si to ukážeme v prohlížeči FireFox, protože tam se řetězec certifikátu nejlépe stáhne.

Kontrola certifikátů v prohlížeči FireFox
Obrázek č. 3 – Kontrola certifikátů v prohlížeči FireFox

Po načtení stránky klikneme na symbol zámečku vedle URL stránky (viz obrázek č. 3). Poté ve vyskakovacím okně, které se objeví, musíme kliknout na podrobnosti Zabezpečeného spojení a následně na Více informací – viz obrázek č. 4.

vyskakovací okna Zabezpečení stránky v prohlížeči FireFox
Obrázek č. 4 – vyskakovací okna Zabezpečení stránky v prohlížeči FireFox

V dalším kroku se zobrazí dialogové okno Informace o stránce, kde klikneme na kartu Zabezpečení. Na této kartě klikneme na tlačítko Zobrazit certifikát – viz obrázek č. 5.

Postup zobrazení certifikátu
Obrázek č. 5 – Postup zobrazení certifikátu

Prohlížeč FireFox otevře nové okno, ve kterém jsou vypsány porobnosti o platných certifikátech. Vybereme si ten, jehož záložka je nejvíce vpravo (nejvyšší autorita), a sjedeme po stránce až ke skupině položek „Různé“. Tam je i odkaz na možnost Stáhnout PEM (certifikát) – viz obrázek č. 6.

Postup stažení certifikátu

Obrázek č. 6 – Postup stažení certifikátu

Kliknutím na odkaz pro stažení certifikátu se nám stáhne textový soubor s kódem certifikátu, který můžeme otevřít kupříkladu v Poznámkovém bloku – viz obrázek č. 7.

Získání kódu certifikátu v podobě textového souboru
Obrázek č. 7 – Získání kódu certifikátu v podobě textového souboru

Právě jsme získali certifikát pro naše použití v modulu ESP32! I když popravdě řečeno, aby jej bylo možné použít v programovém kódu prostředí Arduino IDE a přeložit do modulu ESP32, je ještě potřeba certifikát převést na víceřádkový řetězec. To obnáší jednotlivé řádky vsadit do uvozovek, doplnit značkami \n na konci řádky (pro zalomení řádek v řetězci). Nakonec každý řádek víceřádkového řetězce, který bude pokračovat, doplnit zpětným lomítkem. Převedení kódu certifikátu do podoby víceřádkového řetězce můžete vidět v ukázkovém kódu níže.

Poznámka
Je třeba zmínit, že každý certifikát má omezenou dobu platnosti. Jakmile platnost certifikátu daného serveru vyprší, je třeba celý postup opakovat a získat aktuálně platný certifikát. To je také ten důvod, proč je dobré si stáhnout kořenový certifikát (co nejvyšší autority), ten obvykle mívá nejdelší dobu expirace.
 

Kód

Pro náš kód použijeme programový kód z odstavce HTTP požadavku. Jak jsme již dříve naznačili, jen jej trochu doplníme. Budou nám k tomu stačit následující tři kroky:

  1. Pochopitelně první úpravou bude doplnění certifikátu. Ten uložíme do globální proměnné root_ca.
  2. Druhým krokem je změna adresy serveru, ke kterému budeme přistupovat (nyní zadáme ten s protokolem HTTPS).
  3. Třetím krokem je doplnění odkazu na certifikát – druhý parametr funkce http.begin (najdeme uvnitř funkce httpGETRequest)

To je opravdu vše – koneckonců podívejte se na následující kód a porovnejte jej s tím předešlým. To, co je v následujícím kódu nové, je zvýrazněno červeně.

/* HTTPS zjisteni verejne IP */
#include <WiFi.h>
#include <HTTPClient.h>

const char* ssid = "Jmeno-Vasi-WiFi-site";
const char* password = "heslo-vasi-wifi-site";

// Replace it with the CA Certificate of your server
const char* root_ca = \
"-----BEGIN CERTIFICATE-----\n" \
"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\n" \
"-----END CERTIFICATE-----\n";

//zadani jmena nebo IP adresy serveru
const char* serverName = "https://api.my-ip.io/ip.txt";

HTTPClient http;
String Server_Response;

// funkce SETUP se spusti jednou pri stisknuti tlacitka reset nebo pri zapnuti desky.
void setup() {
  Serial.begin(115200);

  WiFi.begin(ssid, password);
  Serial.println("Pripojeni k WiFi");
  while(WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Pripojen k Wifi s IP adresou: ");
  Serial.println(WiFi.localIP());

  delay(1000);

  if(WiFi.status() == WL_CONNECTED ) {
    Serial.print("Dotazuji server: ");
    Serial.println(serverName);
    Server_Response = httpGETRequest(serverName);
    Serial.print("Verejna IP adresa: ");
    Serial.println(Server_Response);
  }

}

// funkce LOOP bezi stale dokola.
void loop() {
  ;
}

// ----
String httpGETRequest(const char* serverName) {
  http.begin(serverName, root_ca);   // Specify the URL and certificate
  int httpResponseCode = http.GET();   // zaslani http dotazu

  String payload = "--";

  if (httpResponseCode > 0) {
    Serial.print("Kod HTTP odpovedi: ");
    Serial.println(httpResponseCode);
    payload = http.getString();
  } else {
    Serial.print("Chyba - kod: ");
    Serial.println(httpResponseCode);
  }

  http.end();   // konec prenosu

  return payload;

}

Chceme-li kód otestovat, jednoduše jej zkompilujeme v prostředí Arduino IDE a nahrajte na desku ESP32. Poté otevřeme sériový monitor a zkontrolujte výsledek. Měli bychom získat výstup podobný obrázku č. 8, který ukazuje odpověď na HTTPS požadavek: https://api.my-ip.io/ip.txt

Výstup modulu ESP32 po HTTPS požadavku
Obrázek č. 8 – Výstup modulu ESP32 po HTTPS požadavku.
Poděkování:
Velké poděkování autorům článku ESP32 Arduino: HTTPS GET Request, kteří ve svém článku velice názorně ukázali, jak získat kód certifikátu, bez kterého bychom provedení HTTPS dotazu nedokázali realizovat.
 

Závěr

V dnešním článku jsme si sice ukázali možnost získání veřejné IP adresy, ale vlastně to bylo nepodstatné. Hlavním přínosem měla být ukázka získání certifikátu webového serveru, ke kterému chceme prostřednictvím modulu ESP32 přistupovat. Přístup k nějakému webovému serveru může být využit například jako možnost uložení aktuálně naměřených dat – kupříkladu data z domácí DIY meteorologické stanice. Data uložená na serveru pak mohou sloužit jako základ veřejně přístupné webové stránky, které bude tyto data vkusně prezentovat. Takovým serverem ani nemusí být náš vlastní server, ale některý z profesionálních serverů pro sběr dat z meteorologických stanic (např. jako jsou wunderground.com nebo ecowitt.net). Vzhledem k tomu, že HTTPS protokol se na Internetu stává standardem, je potřeba mít do budoucna dotazy na servery s HTTPS protokolem pro modul ESP32 vyřešené. Snad Vám v tomto směru náš článek aspoň trochu pomohl.

UPOZORNĚNÍ:
Nesouhlasíme s vyřazením Newtonových zákonů, Ohmova zákona a zákona zachování energie z učiva fyziky základních škol v České republice!