JSON을 이용한 ESP32 REST API 서버 완전한 튜토리얼

WebServerJson 예제 - REST API 서버

개요

이 예제는 JSON 요청과 응답을 처리하는 ESP32에서 REST API 서버를 만드는 방법을 보여주며, 현대적인 웹 애플리케이션과 모바일 앱 백엔드에 적합합니다.

주요 기능

  • REST API 엔드포인트 JSON 요청/응답 처리를 포함
  • POST 요청 처리 JSON 데이터 파싱을 포함
  • GET 엔드포인트 데이터 조회를 위한
  • 전문적인 JSON 응답 적절한 HTTP 상태 코드와 함께
  • 오류 처리 적절한 JSON 오류 메시지와 함께
  • CORS 지원 교차 출처 요청을 위한

준비물

1×ESP32 ESP-WROOM-32 개발 모듈 쿠팡 | 아마존
1×USB 케이블 타입-A to 타입-C (USB-A PC용) 쿠팡 | 아마존
1×USB 케이블 타입-C to 타입-C (USB-C PC용) 아마존
1×(추천) ESP32용 스크루 터미널 확장 보드 쿠팡 | 아마존
1×(추천) Breakout Expansion Board for ESP32 쿠팡 | 아마존
1×(추천) ESP32용 전원 분배기 쿠팡 | 아마존
공개: 이 포스팅 에 제공된 일부 링크는 아마존 제휴 링크입니다. 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

라이브러리 설치

다음 지시를 단계별로 따라가세요:

  • ESP32를 처음 사용하는 경우, Arduino IDE에서 ESP32용 환경 설정 튜토리얼을 참조하십시오.
  • USB 케이블을 사용하여 컴퓨터에 ESP32 보드를 연결하십시오.
  • 컴퓨터에서 Arduino IDE를 실행하십시오.
  • 적절한 ESP32 보드(예: ESP32)와 COM 포트를 선택하십시오.
  • Arduino IDE의 왼쪽에 있는 라이브러리 매니저 아이콘을 클릭하여 라이브러리 매니저를 엽니다.
  • ESP32용 웹 서버를 검색하고 DIYables의 mWebSockets를 찾으십시오.
  • MWebSockets 라이브러리를 추가하려면 Install 버튼을 클릭하십시오.
ESP32 웹 서버 라이브러리

웹 서버 JSON 예제

  • Arduino IDE에서 File 예제 ESP32용 웹 서버 WebServerJson 예제로 이동하여 예제 코드를 엽니다.

API 엔드포인트

조회 /api/data

JSON 형식으로 타임스탬프가 포함된 성공 메시지를 반환합니다.

응답:

{ "status": "success", "message": "GET request received", "timestamp": 12345 }

타임스탬프 값은 ESP32가 시작된 이후 경과한 밀리초를 나타냅니다(millis() 함수에서 얻은 값).

POST /api/data

JSON 데이터를 수신하고 수신된 키 값을 반환합니다.

요청:

{ "key": "your_value" }

응답:

{ "status": "success", "message": "Data received", "received_key": "your_value" }

설치 지침

1. 네트워크 구성

WebServerJson.ino 파일에서 WiFi 자격 증명을 편집하세요:

const char WIFI_SSID[] = "YOUR_WIFI_SSID"; const char WIFI_PASSWORD[] = "YOUR_WIFI_PASSWORD";

2. 코드 업로드 및 출력 모니터링

  1. ESP32를 컴퓨터에 연결하십시오
  2. Arduino IDE에서 올바른 보드와 포트를 선택하십시오
  3. WebServerJson.ino 스케치를 업로드하십시오
  4. 시리얼 모니터를 열고 (9600bps)
  5. WiFi 연결이 완료될 때까지 기다리십시오
  6. 표시된 IP 주소를 기록해 두십시오
  7. 시리얼 모니터에 IP 주소가 표시되지 않는 경우 ESP32 보드의 리셋 버튼을 누르십시오

사용 방법

cURL로 테스트하기

your-esp32-ip를 시리얼 모니터에 표시되는 실제 IP 주소로 바꿉니다.

테스트 GET 요청
curl -X GET http://your-esp32-ip/api/data

예상 출력:

Command Prompt
C:\Users\youruser>curl -X GET http://your-esp32-ip/api/data { "status": "success", "message": "GET request received", "timestamp": 12345 }

타임스탬프는 ESP32가 시작된 이후 경과한 밀리초를 나타냅니다.

JSON 데이터로 POST 요청 테스트
curl -X POST http://your-esp32-ip/api/data -H "Content-Type: application/json" -d "{\"key\": \"test_value\"}"

출력 예:

Command Prompt
C:\Users\youruser>curl -X POST http://your-esp32-ip/api/data -H "Content-Type: application/json" -d "{\"key\": \"test_value\"}" { "status": "success", "message": "Data received", "received_key": "test_value" }
다른 데이터로 POST 요청 테스트
curl -X POST http://your-esp32-ip/api/data -H "Content-Type: application/json" -d "{\"key\": \"hello_world\"}"

예상 출력:

Command Prompt
C:\Users\youruser>curl -X POST http://your-esp32-ip/api/data -H "Content-Type: application/json" -d "{\"key\": \"hello_world\"}" { "status": "success", "message": "Data received", "received_key": "hello_world" }
유효하지 않은 JSON 테스트 (오류 응답)
curl -X POST http://your-esp32-ip/api/data -H "Content-Type: application/json" -d "{invalid json}"

예상 출력:

Command Prompt
C:\Users\youruser>curl -X POST http://your-esp32-ip/api/data -H "Content-Type: application/json" -d "{invalid json}" { "status": "error", "message": "Invalid JSON" }
JSON 본문 누락 테스트 (오류 응답)
curl -X POST http://your-esp32-ip/api/data

예상 출력:

Command Prompt
C:\Users\youruser>curl -X POST http://your-esp32-ip/api/data { "status": "error", "message": "No JSON data received" }
지원되지 않는 메서드 테스트(오류 응답)
curl -X PUT http://your-esp32-ip/api/data -H "Content-Type: application/json" -d "{\"key\": \"test\"}"

예상 출력:

Command Prompt
C:\Users\youruser>curl -X PUT http://your-esp32-ip/api/data -H "Content-Type: application/json" -d "{\"key\": \"test\"}" { "status": "error", "message": "Method not allowed" }
존재하지 않는 엔드포인트 테스트 (404 오류)
curl -X GET http://your-esp32-ip/api/nonexistent

예상 출력:

Command Prompt
C:\Users\youruser>curl -X GET http://your-esp32-ip/api/nonexistent <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>404 Not Found</title> </head> <body> <h1>404 - Page Not Found</h1> <p>Method: GET</p> <p>Sorry, we couldn't find that page!</p> <a href="/">Return to Home</a> </body> </html>

Postman으로 테스트하기

테스트 GET 요청
  1. 새로운 GET 요청을 만듭니다
  2. URL을 http://your-esp32-ip/api/data로 설정합니다
  3. 요청을 보냅니다
  4. 응답에 상태, 메시지 및 타임스탬프가 포함되어 있는지 확인합니다
POST 요청 테스트
  1. 새로운 POST 요청을 만듭니다
  2. URL을 http://your-esp32-ip/api/data로 설정합니다
  3. 헤더 추가: Content-Type: application/json
  4. JSON 본문 추가: {"key": "test_value"}
  5. 요청을 전송합니다
  6. 응답에 수신된 키 값이 표시되는지 확인합니다

오류 응답 테스트

예제 코드에 나와 있는 대로 잘못된 요청을 보내 오류 처리를 테스트하세요:

누락된 JSON 데이터
curl -X POST http://your-esp32-ip/api/data

예상: {"status": "오류","message": "JSON 데이터가 수신되지 않았습니다"}

잘못된 JSON 형식
curl -X POST http://your-esp32-ip/api/data -H "Content-Type: application/json" -d "{invalid json}"

예상: {"status": "error","message": "잘못된 JSON"}

누락된 키 필드
curl -X POST http://your-esp32-ip/api/data -H "Content-Type: application/json" -d "{\"other_field\": \"value\"}"

예상: 키는 예제 코드에 따라 기본값으로 "none"을 가지게 됩니다: doc["key"] | "none"

지원되지 않는 HTTP 메서드
curl -X DELETE http://your-esp32-ip/api/data

예상: {"status": "오류","message": "허용되지 않는 메서드"}

코드 설명

라우트 구성

// Configure API routes server.addRoute("/api/data", handleApiData);

핸들러 함수 시그니처

모든 핸들러 함수는 이 시그니처를 따라야 한다:

void handleApiData(WiFiClient& client, const String& method, const String& request, const QueryParams& params, const String& jsonData) { // Handler implementation }

메서드 탐지 및 JSON 처리

void handleApiData(WiFiClient& client, const String& method, const String& request, const QueryParams& params, const String& jsonData) { Serial.print("[API] "); Serial.print(method); Serial.print(" request received"); if (method == "POST") { if (jsonData.length() == 0) { Serial.println("Error: No JSON data received"); client.println("HTTP/1.1 400 Bad Request"); client.println("Content-Type: application/json"); client.println("Connection: close"); client.println(); client.print("{\"status\": \"error\",\"message\": \"No JSON data received\"}"); return; } StaticJsonDocument<200> doc; DeserializationError error = deserializeJson(doc, jsonData); if (!error) { const char* key = doc["key"] | "none"; String response = JSON_RESPONSE; response.replace("%KEY%", key); server.sendResponse(client, response.c_str(), "application/json"); } else { client.println("HTTP/1.1 400 Bad Request"); client.println("Content-Type: application/json"); client.println("Connection: close"); client.println(); client.print("{\"status\": \"error\",\"message\": \"Invalid JSON\"}"); } } else if (method == "GET") { String response = JSON_GET_RESPONSE; response.replace("%TIMESTAMP%", String(millis())); server.sendResponse(client, response.c_str(), "application/json"); } else { client.println("HTTP/1.1 405 Method Not Allowed"); client.println("Content-Type: application/json"); client.println("Connection: close"); client.println(); client.print("{\"status\":\"error\",\"message\":\"Method not allowed\"}"); } }

연동 예시

자바스크립트 프런트엔드

// Control LED async function controlLED(action) { try { const response = await fetch('http://your-esp32-ip/api/led', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ action: action }) }); const result = await response.json(); console.log('LED control result:', result); } catch (error) { console.error('Error:', error); } } // Get sensor data async function getSensorData() { try { const response = await fetch('http://your-esp32-ip/api/sensor'); const data = await response.json(); console.log('Sensor data:', data); } catch (error) { console.error('Error:', error); } }

파이썬 클라이언트

import requests import json

LED 제어

def control_led(action): url = "http://your-esp32-ip/api/led" data = {"action": action} response = requests.post(url, json=data) return response.json()

상태 가져오기

def get_status(): url = "http://your-esp32-ip/api/status" response = requests.get(url) return response.json()

사용 방법

result = control_led("on") print(result) status = get_status() print(status)

오류 처리

HTTP 상태 코드

  • 200: 성공
  • 400: 잘못된 요청 (유효하지 않은 JSON, 누락된 매개변수)
  • 404: 엔드포인트를 찾을 수 없음
  • 405: 허용되지 않는 메서드
  • 500: 내부 서버 오류

오류 응답 형식

실제 예제 코드에 따라 서로 다른 오류가 특정 메시지를 반환합니다:

누락된 JSON 데이터 오류
{ "status": "error", "message": "No JSON data received" }

다음의 경우 반환됩니다: JSON 본문이 없는 POST 요청이 전송될 때

잘못된 JSON 형식 오류
{ "status": "error", "message": "Invalid JSON" }

다음과 같은 경우에 반환됩니다: JSON 데이터를 구문 분석할 수 없을 때(구문 오류)

허용되지 않는 메서드 오류
{ "status": "error", "message": "Method not allowed" }

다음과 같은 경우에 반환됩니다: 지원되지 않는 HTTP 메서드(PUT, DELETE, PATCH 등)를 사용할 때

404 페이지를 찾을 수 없음
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>404 Not Found</title> </head> <body> <h1>404 - Page Not Found</h1> <p>Method: [HTTP_METHOD]</p> <p>Sorry, we couldn't find that page!</p> <a href="/">Return to Home</a> </body> </html>

다음 경우에 반환됩니다: 존재하지 않는 엔드포인트에 접근할 때

맞춤 설정

새 핸들러 함수 추가

// Create additional handler for a new endpoint void handleApiStatus(WiFiClient& client, const String& method, const String& request, const QueryParams& params, const String& jsonData) { if (method == "GET") { String json = "{\"status\":\"online\",\"uptime\":" + String(millis() / 1000) + "}"; server.sendResponse(client, json.c_str(), "application/json"); } else { client.println("HTTP/1.1 405 Method Not Allowed"); client.println("Content-Type: application/json"); client.println("Connection: close"); client.println(); client.print("{\"status\":\"error\",\"message\":\"Method not allowed\"}"); } } // Register the new route in setup() server.addRoute("/api/status", handleApiStatus);

템플릿 기반의 JSON 응답

예제는 일관된 JSON 형식을 위해 템플릿 문자열을 사용합니다:

const char JSON_RESPONSE[] PROGMEM = R"rawliteral( { "status": "success", "message": "Data received", "received_key": "%KEY%" } )rawliteral"; // Usage in handler String response = JSON_RESPONSE; response.replace("%KEY%", extractedValue); server.sendResponse(client, response.c_str(), "application/json");

문제 해결

일반적인 문제

연결 문제

API 엔드포인트에 연결할 수 없으면:

  1. ESP32가 WiFi에 연결되어 있는지 확인하세요(시리얼 모니터에서 확인)
  2. 클라이언트와 ESP32가 동일한 네트워크에 있는지 확인하세요
  3. IP 주소가 정확한지 확인하세요
  4. ESP32가 리셋되지 않았는지 확인하세요(리셋되면 IP가 변경됩니다)

JSON 구문 분석 오류

유효하지 않은 JSON 응답을 받으면:

  1. Content-Type 헤더가 application/json으로 설정되어 있는지 확인하십시오
  2. JSON 구문이 유효한지 확인하십시오
  3. 특수 문자가 올바르게 이스케이프되었는지 확인하십시오
  4. JSON 페이로드가 너무 크지 않은지 확인하십시오(현재 제한: 200바이트)

POST 요청 이슈

POST 요청이 "No JSON data received"를 반환하면:

  1. 요청에 JSON 본문을 보내고 있는지 확인합니다.
  2. Content-Length 헤더가 올바르게 설정되었는지 확인합니다.
  3. HTTP 메서드가 실제로 POST인지 확인합니다.
  4. 간단한 JSON 예시로 {"key": "test"}를 사용해 테스트합니다.

메모리 문제

ESP32가 응답하지 않으면:

  1. 메모리 사용량 모니터링 - StaticJsonDocument는 200바이트를 사용합니다.
  2. 필요하면 JSON_RESPONSE 템플릿 크기를 줄이십시오.
  3. 가변 크기의 데이터에는 DynamicJsonDocument 사용을 고려하십시오.
  4. 사용자 정의 핸들러 함수의 메모리 누수를 확인하십시오.

성능 팁

JSON 처리 최적화

// Use appropriate document size for your data StaticJsonDocument<200> doc; // For small JSON objects StaticJsonDocument<1024> doc; // For larger JSON objects // Reuse string objects to reduce memory allocation String response; response.reserve(256); // Pre-allocate space response = JSON_RESPONSE; response.replace("%KEY%", value);

효율적인 응답 처리

// Send responses directly for simple cases client.print(F("{\"status\":\"ok\",\"value\":")); client.print(sensorValue); client.print(F("}")); // Use PROGMEM for large static responses const char LARGE_RESPONSE[] PROGMEM = R"({ "status": "success", "data": { ... } })";

다음 단계

  • URL 매개변수 처리를 위해 WebServerQueryStrings.ino를 살펴보세요
  • 실시간 통신을 위해 WebServerWithWebSocket.ino를 시도해 보세요
  • 데이터베이스나 클라우드 서비스와의 통합을 고려해 보세요

학습 자료