http client first version made by claude opus
This commit is contained in:
@@ -1,2 +1,3 @@
|
||||
idf_component_register(SRCS "http_client.c"
|
||||
INCLUDE_DIRS "include")
|
||||
idf_component_register(SRCS "http_client.cpp"
|
||||
INCLUDE_DIRS "." "../shared"
|
||||
PRIV_REQUIRES esp_http_client)
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include "http_client.h"
|
||||
|
||||
void func(void)
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
// HTTP Client Component — synchronous GET / POST wrapper around esp_http_client.
|
||||
// See tdd/http_client_component.md for design rationale.
|
||||
|
||||
#include "http_client.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include "esp_http_client.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "types.hpp"
|
||||
|
||||
internal const char *kTagHttpClient = "HTTP_CLIENT";
|
||||
|
||||
// ── Internal receive buffer ─────────────────────────────────────────────────
|
||||
|
||||
struct http_receive_buffer_t
|
||||
{
|
||||
char *buf;
|
||||
size_t len;
|
||||
size_t capacity;
|
||||
};
|
||||
|
||||
// ── Event handler ───────────────────────────────────────────────────────────
|
||||
|
||||
internal esp_err_t http_event_handler(esp_http_client_event_t *evt)
|
||||
{
|
||||
auto *recv = (http_receive_buffer_t *)evt->user_data;
|
||||
|
||||
switch (evt->event_id)
|
||||
{
|
||||
case HTTP_EVENT_ON_DATA:
|
||||
{
|
||||
if (recv == nullptr)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
size_t new_len = recv->len + evt->data_len;
|
||||
|
||||
// Grow buffer if needed
|
||||
if (new_len > recv->capacity)
|
||||
{
|
||||
// Double or fit, whichever is larger
|
||||
size_t new_cap = recv->capacity * 2;
|
||||
if (new_cap < new_len)
|
||||
{
|
||||
new_cap = new_len;
|
||||
}
|
||||
|
||||
// +1 for potential null terminator
|
||||
char *new_buf = (char *)realloc(recv->buf, new_cap + 1);
|
||||
if (new_buf == nullptr)
|
||||
{
|
||||
ESP_LOGE(kTagHttpClient, "realloc failed (%zu bytes)", new_cap + 1);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
recv->buf = new_buf;
|
||||
recv->capacity = new_cap;
|
||||
}
|
||||
|
||||
memcpy(recv->buf + recv->len, evt->data, evt->data_len);
|
||||
recv->len = new_len;
|
||||
break;
|
||||
}
|
||||
|
||||
case HTTP_EVENT_ERROR:
|
||||
ESP_LOGE(kTagHttpClient, "HTTP_EVENT_ERROR");
|
||||
break;
|
||||
|
||||
case HTTP_EVENT_ON_CONNECTED:
|
||||
case HTTP_EVENT_HEADERS_SENT:
|
||||
case HTTP_EVENT_ON_HEADER:
|
||||
case HTTP_EVENT_ON_FINISH:
|
||||
case HTTP_EVENT_DISCONNECTED:
|
||||
case HTTP_EVENT_REDIRECT:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// ── Shared perform helper ───────────────────────────────────────────────────
|
||||
|
||||
// Performs an HTTP request, accumulates body into recv buffer.
|
||||
// On success, recv->buf contains the raw response body (not null-terminated).
|
||||
// Caller is responsible for cleaning up recv->buf on both success and failure.
|
||||
internal esp_err_t http_perform(const char *url,
|
||||
esp_http_client_method_t method,
|
||||
const char *post_data,
|
||||
size_t post_data_len,
|
||||
const char *content_type,
|
||||
http_receive_buffer_t *recv,
|
||||
int *out_status_code)
|
||||
{
|
||||
*out_status_code = 0;
|
||||
|
||||
esp_http_client_config_t config = {};
|
||||
config.url = url;
|
||||
config.method = method;
|
||||
config.event_handler = http_event_handler;
|
||||
config.user_data = recv;
|
||||
config.timeout_ms = 10000;
|
||||
config.buffer_size = 1024;
|
||||
config.buffer_size_tx = 1024;
|
||||
|
||||
esp_http_client_handle_t client = esp_http_client_init(&config);
|
||||
if (client == nullptr)
|
||||
{
|
||||
ESP_LOGE(kTagHttpClient, "Failed to init HTTP client for %s", url);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
// Set POST body if provided
|
||||
if (post_data != nullptr && post_data_len > 0)
|
||||
{
|
||||
esp_http_client_set_post_field(client, post_data, (int)post_data_len);
|
||||
}
|
||||
|
||||
// Set Content-Type header if provided
|
||||
if (content_type != nullptr)
|
||||
{
|
||||
esp_http_client_set_header(client, "Content-Type", content_type);
|
||||
}
|
||||
|
||||
ESP_LOGI(kTagHttpClient, "%s %s",
|
||||
method == HTTP_METHOD_POST ? "POST" : "GET", url);
|
||||
|
||||
esp_err_t err = esp_http_client_perform(client);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(kTagHttpClient, "HTTP request failed: %s", esp_err_to_name(err));
|
||||
esp_http_client_cleanup(client);
|
||||
return err;
|
||||
}
|
||||
|
||||
*out_status_code = esp_http_client_get_status_code(client);
|
||||
int64_t content_length = esp_http_client_get_content_length(client);
|
||||
|
||||
if (*out_status_code >= 200 && *out_status_code < 300)
|
||||
{
|
||||
ESP_LOGI(kTagHttpClient, "Response: %d, %zu bytes received",
|
||||
*out_status_code, recv->len);
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGW(kTagHttpClient, "Response: %d (content-length: %lld)",
|
||||
*out_status_code, content_length);
|
||||
}
|
||||
|
||||
esp_http_client_cleanup(client);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// ── Public API ──────────────────────────────────────────────────────────────
|
||||
|
||||
char *http_build_url(const char *host, uint16_t port, const char *path)
|
||||
{
|
||||
// "http://" (7) + host + ":" (1) + port (max 5) + path + null
|
||||
size_t host_len = strlen(host);
|
||||
size_t path_len = path ? strlen(path) : 0;
|
||||
size_t url_len = 7 + host_len + 1 + 5 + path_len + 1;
|
||||
|
||||
char *url = (char *)malloc(url_len);
|
||||
if (url == nullptr)
|
||||
{
|
||||
ESP_LOGE(kTagHttpClient, "Failed to allocate URL buffer");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
snprintf(url, url_len, "http://%s:%u%s", host, port, path ? path : "");
|
||||
return url;
|
||||
}
|
||||
|
||||
esp_err_t http_get_text(const char *url, http_text_response_t *out)
|
||||
{
|
||||
memset(out, 0, sizeof(*out));
|
||||
|
||||
http_receive_buffer_t recv = {};
|
||||
int status_code = 0;
|
||||
|
||||
esp_err_t err = http_perform(url, HTTP_METHOD_GET,
|
||||
nullptr, 0, nullptr,
|
||||
&recv, &status_code);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
free(recv.buf);
|
||||
return err;
|
||||
}
|
||||
|
||||
// Null-terminate the text body
|
||||
if (recv.buf != nullptr)
|
||||
{
|
||||
recv.buf[recv.len] = '\0'; // Safe: we always allocate capacity + 1
|
||||
}
|
||||
|
||||
out->status_code = status_code;
|
||||
out->body = recv.buf;
|
||||
out->body_len = recv.len;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t http_get_binary(const char *url, http_binary_response_t *out)
|
||||
{
|
||||
memset(out, 0, sizeof(*out));
|
||||
|
||||
http_receive_buffer_t recv = {};
|
||||
int status_code = 0;
|
||||
|
||||
esp_err_t err = http_perform(url, HTTP_METHOD_GET,
|
||||
nullptr, 0, nullptr,
|
||||
&recv, &status_code);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
free(recv.buf);
|
||||
return err;
|
||||
}
|
||||
|
||||
out->status_code = status_code;
|
||||
out->data = (uint8_t *)recv.buf;
|
||||
out->data_len = recv.len;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t http_post_json(const char *url, const char *json_body,
|
||||
http_text_response_t *out)
|
||||
{
|
||||
memset(out, 0, sizeof(*out));
|
||||
|
||||
http_receive_buffer_t recv = {};
|
||||
int status_code = 0;
|
||||
|
||||
size_t body_len = json_body ? strlen(json_body) : 0;
|
||||
|
||||
esp_err_t err = http_perform(url, HTTP_METHOD_POST,
|
||||
json_body, body_len,
|
||||
"application/json",
|
||||
&recv, &status_code);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
free(recv.buf);
|
||||
return err;
|
||||
}
|
||||
|
||||
// Null-terminate the text body
|
||||
if (recv.buf != nullptr)
|
||||
{
|
||||
recv.buf[recv.len] = '\0';
|
||||
}
|
||||
|
||||
out->status_code = status_code;
|
||||
out->body = recv.buf;
|
||||
out->body_len = recv.len;
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
void func(void);
|
||||
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
// ── Result Types ────────────────────────────────────────────────────────────
|
||||
|
||||
// Text/JSON response. Caller must free(body) after use.
|
||||
struct http_text_response_t
|
||||
{
|
||||
int status_code; // HTTP status (200, 404, 500, …)
|
||||
char *body; // Heap-allocated, null-terminated
|
||||
size_t body_len; // Byte length (excluding null terminator)
|
||||
};
|
||||
|
||||
// Binary response (e.g. PNG image). Caller must free(data) after use.
|
||||
struct http_binary_response_t
|
||||
{
|
||||
int status_code;
|
||||
uint8_t *data; // Heap-allocated binary buffer
|
||||
size_t data_len; // Byte length
|
||||
};
|
||||
|
||||
// ── Functions ───────────────────────────────────────────────────────────────
|
||||
|
||||
// Build "http://<host>:<port><path>".
|
||||
// Returns a heap-allocated string; caller must free().
|
||||
char *http_build_url(const char *host, uint16_t port, const char *path);
|
||||
|
||||
// GET a text/JSON resource. Blocks until complete.
|
||||
// On success (ESP_OK): out is filled. Caller must free(out->body).
|
||||
// On failure: out is zeroed, returns an esp_err_t.
|
||||
esp_err_t http_get_text(const char *url, http_text_response_t *out);
|
||||
|
||||
// GET a binary resource (e.g. PNG image). Blocks until complete.
|
||||
// On success (ESP_OK): out is filled. Caller must free(out->data).
|
||||
// On failure: out is zeroed, returns an esp_err_t.
|
||||
esp_err_t http_get_binary(const char *url, http_binary_response_t *out);
|
||||
|
||||
// POST a JSON body, receive a JSON response. Blocks until complete.
|
||||
// json_body must be a null-terminated JSON string.
|
||||
// On success (ESP_OK): out is filled. Caller must free(out->body).
|
||||
// On failure: out is zeroed, returns an esp_err_t.
|
||||
esp_err_t http_post_json(const char *url, const char *json_body,
|
||||
http_text_response_t *out);
|
||||
@@ -0,0 +1,6 @@
|
||||
version: "1.0.0"
|
||||
description: "Calendink HTTP Client Component"
|
||||
|
||||
dependencies:
|
||||
idf:
|
||||
version: '>=5.0.0'
|
||||
Reference in New Issue
Block a user