This builds a complete REST API for a small in-memory "sensor database": list, fetch-by-id, create, partial-update, and delete, with real HTTP semantics (bearer-token auth, Content-Type checking, and a spread of status codes). It is the step up from Basic: same three-part structure (global server, setup() registers routes, loop() pumps handle()), but now the handlers do meaningful work.
static const char *SSID = "YOUR_SSID";
static const char *PASSWORD = "YOUR_PASSWORD";
static const char *EXPECTED_TOKEN = "Bearer secret_admin_token";
struct SensorDevice
{
int id;
char name[16];
float temperature;
bool active;
bool in_use;
};
#define MAX_SENSORS 4
static SensorDevice sensor_db[MAX_SENSORS] = {
{0, "Living Room", 22.4, true, true},
{1, "Kitchen", 24.1, true, true},
{2, "Garage", 15.8, false, true},
{3, "", 0.0, false, false}
};
bool is_authorized(
const HttpReq *req)
{
return (auth_hdr && strcmp(auth_hdr, EXPECTED_TOKEN) == 0);
}
bool json_get_float(const char *json, const char *key, float &out_val)
{
char key_pattern[32];
snprintf(key_pattern, sizeof(key_pattern), "\"%s\":", key);
const char *ptr = strstr(json, key_pattern);
if (!ptr)
return false;
ptr += strlen(key_pattern);
while (*ptr == ' ' || *ptr == '\t')
ptr++;
out_val = (float)atof(ptr);
return true;
}
bool json_get_string(const char *json, const char *key, char *out_buf, size_t max_len)
{
char key_pattern[32];
snprintf(key_pattern, sizeof(key_pattern), "\"%s\":", key);
const char *ptr = strstr(json, key_pattern);
if (!ptr)
return false;
ptr += strlen(key_pattern);
while (*ptr == ' ' || *ptr == '\t' || *ptr == '"')
ptr++;
size_t i = 0;
while (*ptr && *ptr != '"' && *ptr != ',' && *ptr != '}' && i < (max_len - 1))
out_buf[i++] = *ptr++;
out_buf[i] = '\0';
return true;
}
bool json_get_bool(
const char *json,
const char *key,
bool &out_val)
{
char key_pattern[32];
snprintf(key_pattern, sizeof(key_pattern), "\"%s\":", key);
const char *ptr = strstr(json, key_pattern);
if (!ptr)
return false;
ptr += strlen(key_pattern);
while (*ptr == ' ' || *ptr == '\t')
ptr++;
if (strncmp(ptr, "true", 4) == 0) { out_val = true; return true; }
if (strncmp(ptr, "false", 5) == 0) { out_val = false; return true; }
return false;
}
void handle_get_sensors(uint8_t slot_id,
HttpReq *req)
{
bool filter_by_active = (active_filter != nullptr);
bool active_target_val = (filter_by_active && strcmp(active_filter, "1") == 0);
char response_buf[512];
int len = snprintf(response_buf, sizeof(response_buf), "[");
bool first = true;
for (int i = 0; i < MAX_SENSORS; i++)
{
if (!sensor_db[i].in_use)
continue;
if (filter_by_active && (sensor_db[i].active != active_target_val))
continue;
if (!first)
len += snprintf(response_buf + len, sizeof(response_buf) - len, ",");
first = false;
len += snprintf(response_buf + len, sizeof(response_buf) - len,
"{\"id\":%d,\"name\":\"%s\",\"temp\":%.1f,\"active\":%s}", sensor_db[i].id, sensor_db[i].name,
sensor_db[i].temperature, sensor_db[i].active ? "true" : "false");
}
snprintf(response_buf + len, sizeof(response_buf) - len, "]");
server.
send(slot_id, 200,
"application/json", response_buf);
}
void handle_get_sensor_by_id(uint8_t slot_id,
HttpReq *req)
{
if (strlen(req->
path) <= 13)
{
server.
send(slot_id, 400,
"text/plain",
"Missing sensor ID");
return;
}
int id = atoi(req->
path + 13);
if (id < 0 || id >= MAX_SENSORS || !sensor_db[id].in_use)
{
server.
send(slot_id, 404,
"application/json",
"{\"error\":\"Sensor not found\"}");
return;
}
char response_buf[192];
snprintf(response_buf, sizeof(response_buf), "{\"id\":%d,\"name\":\"%s\",\"temp\":%.1f,\"active\":%s}",
sensor_db[id].id, sensor_db[id].name, sensor_db[id].temperature, sensor_db[id].active ? "true" : "false");
server.
send(slot_id, 200,
"application/json", response_buf);
}
void handle_create_sensor(uint8_t slot_id,
HttpReq *req)
{
if (!is_authorized(req))
{
server.
send(slot_id, 401,
"text/plain",
"401 Unauthorized: Invalid token");
return;
}
if (!content_type || strstr(content_type, "application/json") == nullptr)
{
server.
send(slot_id, 400,
"text/plain",
"400 Bad Request: Content-Type must be application/json");
return;
}
int empty_slot = -1;
for (int i = 0; i < MAX_SENSORS; i++)
if (!sensor_db[i].in_use) { empty_slot = i; break; }
if (empty_slot == -1)
{
server.
send(slot_id, 409,
"application/json",
"{\"error\":\"Database table full\"}");
return;
}
const char *body = (
const char *)req->
body;
char name[16] = "";
float temp = 0.0;
bool active = false;
if (!json_get_string(body, "name", name, sizeof(name)) || !json_get_float(body, "temp", temp) ||
{
server.
send(slot_id, 400,
"application/json",
"{\"error\":\"Invalid JSON format or missing keys\"}");
return;
}
sensor_db[empty_slot].id = empty_slot;
strncpy(sensor_db[empty_slot].name, name, sizeof(sensor_db[empty_slot].name) - 1);
sensor_db[empty_slot].temperature = temp;
sensor_db[empty_slot].active = active;
sensor_db[empty_slot].in_use = true;
char response_buf[192];
snprintf(response_buf, sizeof(response_buf), "{\"id\":%d,\"name\":\"%s\",\"status\":\"created\"}", empty_slot,
sensor_db[empty_slot].name);
server.
send(slot_id, 201,
"application/json", response_buf);
}
void handle_patch_sensor(uint8_t slot_id,
HttpReq *req)
{
if (!is_authorized(req))
{
server.
send(slot_id, 401,
"text/plain",
"401 Unauthorized: Invalid token");
return;
}
if (strlen(req->
path) <= 13)
{
server.
send(slot_id, 400,
"text/plain",
"Missing sensor ID");
return;
}
int id = atoi(req->
path + 13);
if (id < 0 || id >= MAX_SENSORS || !sensor_db[id].in_use)
{
server.
send(slot_id, 404,
"application/json",
"{\"error\":\"Sensor not found\"}");
return;
}
const char *body = (
const char *)req->
body;
float new_temp;
bool new_active;
if (json_get_float(body, "temp", new_temp))
sensor_db[id].temperature = new_temp;
sensor_db[id].active = new_active;
char response_buf[192];
snprintf(response_buf, sizeof(response_buf), "{\"id\":%d,\"name\":\"%s\",\"temp\":%.1f,\"active\":%s}",
sensor_db[id].id, sensor_db[id].name, sensor_db[id].temperature, sensor_db[id].active ? "true" : "false");
server.
send(slot_id, 200,
"application/json", response_buf);
}
void handle_delete_sensor(uint8_t slot_id,
HttpReq *req)
{
if (!is_authorized(req))
{
server.
send(slot_id, 401,
"text/plain",
"401 Unauthorized: Invalid token");
return;
}
if (strlen(req->
path) <= 13)
{
server.
send(slot_id, 400,
"text/plain",
"Missing sensor ID");
return;
}
int id = atoi(req->
path + 13);
if (id < 0 || id >= MAX_SENSORS || !sensor_db[id].in_use)
{
server.
send(slot_id, 404,
"application/json",
"{\"error\":\"Sensor not found\"}");
return;
}
sensor_db[id].in_use = false;
}
void setup()
{
Serial.begin(115200);
delay(1000);
Serial.println("\n--- PC Advanced REST CRUD Example ---");
{
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi Associated!");
Serial.print("Local IP: ");
Serial.printf("IP: %u.%u.%u.%u\n", (unsigned)(ip & 0xFF), (unsigned)((ip >> 8) & 0xFF),
(unsigned)((ip >> 16) & 0xFF), (unsigned)((ip >> 24) & 0xFF));
int32_t result = server.
begin(80);
if (result < 0)
{
Serial.printf("begin() failed (error %d)\n", result);
return;
}
Serial.println("REST API server running on port 80");
Serial.println("Admin token: 'Authorization: Bearer secret_admin_token'");
}
void loop()
{
}
Single-port HTTP server with deterministic, zero-allocation execution.
void send_empty(uint8_t slot_id, int code)
Send a headers-only HTTP response and close the connection.
void send(uint8_t slot_id, int code, const char *content_type, const char *payload)
Send an HTTP response with a body and close the connection.
int32_t begin(const WebServerConfig *cfg=nullptr)
Initialize all connection slots and open all registered listeners.
void on(const char *path, HttpMethod method, Handler callback)
Register a route handler.
void handle()
Drive the server - call every Arduino loop() iteration.
void set_cors(const char *origin)
Enable CORS by pre-building the Access-Control headers.
const char * http_get_query(const HttpReq *req, const char *key)
Look up a query parameter value by name (case-sensitive).
bool json_get_bool(const char *json, const char *key, bool *out)
Read a top-level boolean member (true/false) from a JSON object body.
bool init_wifi_physical(const char *, const char *)
Connect to a WiFi access point.
uint32_t pc_net_egress_ip(void)
IPv4 (network byte order) of the current egress interface, or 0 if none.
bool wifi_ready()
True if the WiFi station link is up (associated + an IP is assigned).
Layer 1 (Physical) - link bring-up and live egress-interface reporting.
Layer 7 (Application) - public HTTP routing API.
Fully-parsed HTTP/1.1 request.
uint8_t body[BODY_BUF_SIZE+1]
Stored body bytes, always null-terminated.
char path[MAX_PATH_LEN]
URL path, null-terminated; no query string.