3. ünite / 10
HTTP, REST ve JSON: ESP32 bir web sunucusu
Yollar, yöntemler ve durum kodlarıyla ESP32 üzerinde küçük bir REST API: JSON gövde doğrulama, idempotentlik, belirteç ve istemci tarafı.
Bu ünitenin sonunda
- Bir HTTP isteğinin ve yanıtının parçalarını (yöntem, yol, başlık, gövde, durum kodu) okumak
- GET, POST, PUT ve DELETE yöntemlerini idempotentlik açısından karşılaştırmak
- Doğru durum kodunu seçmek: 200, 201, 204, 400, 401, 404, 405, 422
- ESP32'de ArduinoJson ile JSON üreten ve doğrulayan bir uç nokta yazmak
Ders notları
HTTP, tarayıcının web sayfasını çektiği protokoldür; ESP32 üzerinde de aynı protokolle küçük bir sunucu çalıştırabilirsin. İstemci (telefon, tarayıcı, başka bir cihaz) bir istek gönderir, sunucu bir yanıt döndürür. Bağlantı istek bitince kapanabilir: HTTP istek-yanıt modelidir, sunucu kendiliğinden konuşmaz.
Bir isteğin anatomisi
- Yöntem ve yol:
PUT /api/led. Yol bir kaynağı (LED), yöntem o kaynağa ne yapılacağını söyler. - Başlıklar:
Content-Type: application/json,Authorization: Bearer …gibi anahtar-değer satırları. - Gövde: JSON metni, örneğin
{"on": true}. - Durum kodu: yanıtın ilk satırı. 2xx başarı, 4xx istemci hatası, 5xx sunucu hatası.
REST: kaynaklar ve yöntemler
REST bir kuraldan çok bir üsluptur: her şey bir kaynaktır, yöntemler standarttır. GET okur ve hiçbir şeyi değiştirmez. PUT kaynağı verilen değere ayarlar; iki kez göndermek bir kez göndermekle aynı sonucu verir, yani idempotenttir. POST yeni bir kaynak oluşturur; iki kez göndermek iki zamanlama yaratır. DELETE siler ve o da idempotenttir. Ağ kötüyken istemci isteği yeniden gönderebileceği için bu ayrım önemlidir.
Durum kodları
Bu ünitenin sunucusu şunları döndürür: 200 OK başarılı okuma ya da güncelleme, 201 Created ve Location başlığı yeni zamanlama, 204 No Content başarılı silme, 400 Bad Request bozuk JSON, 401 Unauthorized belirteç yok ya da yanlış, 404 Not Found böyle bir yol ya da kayıt yok, 405 Method Not Allowed ve Allow başlığı yöntem desteklenmiyor, 422 Unprocessable Entity JSON geçerli ama alanlar anlamsız. Doğru kod, istemcinin hatayı otomatik olarak ayırt etmesini sağlar.
JSON ve bellek
ArduinoJson ile JsonDocument oluşturur, serializeJson ile metne çevirirsin. Gelen gövdeyi deserializeJson ile ayrıştırırken dönen hatayı mutlaka denetle ve alan türünü (is<bool>()) doğrula: cihazına gelen her bayt güvenilmezdir.
Simülasyon
REST oyun alanı: ESP32 web sunucusu
Seradaki ESP32'nin küçük REST API'sine istek gönder: GET /api/sensor, PUT /api/led, POST /api/schedules. Ham HTTP isteğini ve yanıtını, durum kodlarını (200, 201, 400, 401, 404, 405, 422) ve JSON gövdeyi gör.
- –Henüz istek yok
Kod
#include <WiFi.h>
#include <WebServer.h>
#include <ArduinoJson.h>
#include "secrets.h" // WIFI_SSID, WIFI_PASS
WebServer server(80);
const int LED_PIN = 2;
bool ledOn = false;
void sendJson(int code, JsonDocument& doc) {
String body;
serializeJson(doc, body);
server.send(code, "application/json", body);
}
void getLed() {
JsonDocument doc;
doc["on"] = ledOn;
sendJson(200, doc);
}
void putLed() {
JsonDocument in, out;
DeserializationError e = deserializeJson(in, server.arg("plain"));
if (e) { out["error"] = e.c_str(); return sendJson(400, out); } // JSON bozuk
if (!in["on"].is<bool>()) { out["error"] = "on must be true or false"; return sendJson(422, out); }
ledOn = in["on"]; // mutlak değer: PUT idempotent kalır
digitalWrite(LED_PIN, ledOn);
getLed(); // yeni durumu döndür
}
void setup() {
pinMode(LED_PIN, OUTPUT);
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) delay(250);
server.on("/api/led", HTTP_GET, getLed);
server.on("/api/led", HTTP_PUT, putLed);
server.onNotFound([] { server.send(404, "application/json", R"({"error":"not found"})"); });
server.begin();
}
void loop() { server.handleClient(); }import urequests as requests
import ujson
from secrets import TOKEN # belirteç kodda değil
BASE = 'http://sera.local/api' # Wi-Fi bağlantısı önceki ünitedeki connect() ile
H = {'Content-Type': 'application/json', 'Authorization': 'Bearer ' + TOKEN}
r = requests.get(BASE + '/sensor')
print(r.status_code, r.json()) # 200 {"temp": 22.4, "hum": 54.1}
r.close() # soketi bırak: ESP32'de sayısı sınırlı
r = requests.put(BASE + '/led', data=ujson.dumps({'on': True}), headers=H)
if r.status_code == 200:
print('LED:', r.json()['on'])
elif r.status_code in (400, 422):
print('bad request:', r.json()['error'])
elif r.status_code == 401:
print('token missing or wrong')
r.close()Kendin dene
REST oyun alanında önce GET /api/sensor gönder ve ham yanıtı oku. PUT /api/led ile {"on": true} gönder; sonra aynı isteği ikinci kez gönder: durum değişti mi? Gövdeyi {"on": "evet"} yap: hangi kod döndü? PUT /api/status dene ve Allow başlığına bak. POST /api/schedules ile iki kez {"at":"07:30","on":true} gönder: kaç kayıt oluştu, Location başlığı ne? Belirteç zorunluluğunu açıp belirteçsiz PUT gönder. Ardından Wokwi'de WebServer kodunu çalıştır.
Sık yapılan hatalar
- Her hataya 200 dönüp gövdeye "error" yazmak: istemci başarı ile başarısızlığı durum kodundan ayıramaz.
- Durumu değiştiren işlemi GET ile yapmak (GET /led/toggle): tarayıcı önbelleği ya da bir tarayıcı eklentisi LED'i kendiliğinden çevirebilir.
- deserializeJson hatasını denetlemeden alanları okumak: bozuk gövde sessizce yanlış değer üretir.
- urequests yanıtını kapatmamak (r.close()): birkaç istekten sonra soketler biter.
- Belirteci URL'ye koymak (?token=...): günlüklerde ve tarayıcı geçmişinde açıkta kalır, Authorization başlığı kullan.
Yazılım mühendisliği bağlantısı
API bir sözleşmedir: istemci ve sunucu ayrı ayrı geliştirilir, sözleşmede buluşur.
- Sözleşmeyi yaz: yollar, yöntemler, gövde şeması ve hata kodları (OpenAPI dosyası ya da tek sayfalık bir tablo).
- Sözleşme testleri: her yol için "geçerli istek 2xx, bozuk JSON 400, eksik alan 422, yanlış yöntem 405" sınamalarını otomatikleştir. Simülasyonun
handlefonksiyonu saf olduğu için bu testler tarayıcısız çalışır. - Sürümleme: alan eklemek geriye uyumludur, alan silmek değildir; kırıcı değişiklikte
/api/v2aç.
Kendini sına
POST /api/schedules başarıyla yeni bir zamanlama oluşturdu. En uygun durum kodu hangisidir?
Sunucu /api/status için yalnız GET destekliyor. PUT /api/status gelince ne döndürmeli?
PUT /api/led gövdesi {"on": "evet"}. JSON geçerli ama alan türü yanlış. Hangi kod?
Hangi yöntem idempotent değildir?
Bu üniteden sonra bitirme projesinde
- Gereksinim ve pin planı: G1–G8'i kendi serana uyarla, her biri için ölçülebilir kabul ölçütü yaz; iki düğümün pin planını planlayıcıda hatasız çıkar ve REST ile yerel durum sayfası tasarla.