Files
espasynchttpreq/src/asynchttprequest.cpp
T

537 lines
17 KiB
C++
Raw Normal View History

2021-04-07 20:03:49 +02:00
#include "asynchttprequest.h"
#include "sdkconfig.h"
#define LOG_LOCAL_LEVEL CONFIG_LOG_LOCAL_LEVEL_ASYNC_HTTP
// system includes
#include <chrono>
#include <string_view>
2021-04-07 20:03:49 +02:00
#include <cstring>
#include <assert.h>
2021-11-15 11:44:17 +01:00
#include <algorithm>
2021-04-07 20:03:49 +02:00
// esp-idf includes
#include <esp_log.h>
2021-08-04 22:10:25 +02:00
// 3rdparty lib includes
#include <fmt/core.h>
2022-03-21 17:13:12 +01:00
#include <cpputils.h>
2024-01-30 15:42:06 +01:00
#include <cleanuphelper.h>
#include <taskutils.h>
#include <tickchrono.h>
2021-04-07 20:03:49 +02:00
using namespace std::chrono_literals;
namespace {
constexpr const char * const TAG = "ASYNC_HTTP";
2021-11-30 17:19:35 +01:00
constexpr int TASK_RUNNING_BIT = BIT0;
2021-04-07 20:03:49 +02:00
constexpr int START_REQUEST_BIT = BIT1;
constexpr int REQUEST_RUNNING_BIT = BIT2;
constexpr int REQUEST_FINISHED_BIT = BIT3;
constexpr int END_TASK_BIT = BIT4;
2021-11-30 17:19:35 +01:00
constexpr int TASK_ENDED_BIT = BIT5;
constexpr int ABORT_REQUEST_BIT = BIT6;
2021-04-07 20:03:49 +02:00
} // namespace
AsyncHttpRequest::AsyncHttpRequest(const char *taskName, espcpputils::CoreAffinity coreAffinity) :
m_taskName{taskName},
2021-04-07 20:03:49 +02:00
m_coreAffinity{coreAffinity}
{
2021-08-04 22:10:25 +02:00
assert(m_eventGroup.handle);
2021-04-07 20:03:49 +02:00
}
AsyncHttpRequest::~AsyncHttpRequest()
{
endTask();
}
2023-02-27 17:26:55 +01:00
std::expected<void, std::string> AsyncHttpRequest::startTask()
{
2021-11-30 17:19:35 +01:00
if (m_taskHandle)
{
2021-11-30 17:19:35 +01:00
constexpr auto msg = "http task handle is not null";
ESP_LOGW(TAG, "%s", msg);
2023-02-27 17:26:55 +01:00
return std::unexpected(msg);
}
2021-11-30 17:19:35 +01:00
if (const auto bits = m_eventGroup.getBits(); bits & TASK_RUNNING_BIT)
{
constexpr auto msg = "http task already running";
ESP_LOGW(TAG, "%s", msg);
2023-02-27 17:26:55 +01:00
return std::unexpected(msg);
2021-11-30 17:19:35 +01:00
}
2021-11-30 17:19:35 +01:00
m_eventGroup.clearBits(TASK_RUNNING_BIT | START_REQUEST_BIT | REQUEST_RUNNING_BIT | REQUEST_FINISHED_BIT | END_TASK_BIT | TASK_ENDED_BIT | ABORT_REQUEST_BIT);
if (auto result = espcpputils::createTask(requestTask, m_taskName, 3096, this, 10, &m_taskHandle, m_coreAffinity);
result != pdPASS)
{
2021-08-04 22:10:25 +02:00
auto msg = fmt::format("failed creating http task {}", result);
ESP_LOGE(TAG, "%.*s", msg.size(), msg.data());
2023-02-27 17:26:55 +01:00
return std::unexpected(std::move(msg));
}
2021-08-04 22:10:25 +02:00
if (!m_taskHandle)
{
constexpr auto msg = "http task handle is null";
ESP_LOGW(TAG, "%s", msg);
2023-02-27 17:26:55 +01:00
return std::unexpected(msg);
}
ESP_LOGD(TAG, "created http task %s", m_taskName);
2021-11-30 17:19:35 +01:00
if (const auto bits = m_eventGroup.waitBits(TASK_RUNNING_BIT, false, false, std::chrono::ceil<espcpputils::ticks>(1s).count());
bits & TASK_RUNNING_BIT)
2021-08-04 22:10:25 +02:00
return {};
2021-11-30 17:19:35 +01:00
ESP_LOGW(TAG, "http task %s TASK_RUNNING_BIT bit not yet set...", m_taskName);
while (true)
2021-11-30 17:19:35 +01:00
if (const auto bits = m_eventGroup.waitBits(TASK_RUNNING_BIT, false, false, portMAX_DELAY);
bits & TASK_RUNNING_BIT)
break;
2021-08-04 22:10:25 +02:00
return {};
}
2023-02-27 17:26:55 +01:00
std::expected<void, std::string> AsyncHttpRequest::endTask()
{
2021-08-04 22:10:25 +02:00
if (const auto bits = m_eventGroup.getBits();
2021-11-30 17:19:35 +01:00
!(bits & TASK_RUNNING_BIT))
2021-08-04 22:10:25 +02:00
return {};
else if (bits & END_TASK_BIT)
{
constexpr auto msg = "Another end request is already pending";
ESP_LOGE(TAG, "%s", msg);
2023-02-27 17:26:55 +01:00
return std::unexpected(msg);
}
2021-08-04 22:10:25 +02:00
m_eventGroup.setBits(END_TASK_BIT);
2021-11-30 17:19:35 +01:00
if (const auto bits = m_eventGroup.waitBits(TASK_ENDED_BIT, true, false, std::chrono::ceil<espcpputils::ticks>(1s).count());
bits & TASK_ENDED_BIT)
{
ESP_LOGD(TAG, "http task %s ended", m_taskName);
2021-08-04 22:10:25 +02:00
return {};
}
2021-11-30 17:19:35 +01:00
ESP_LOGW(TAG, "http task %s TASK_ENDED_BIT bit not yet set...", m_taskName);
while (true)
2021-11-30 17:19:35 +01:00
if (const auto bits = m_eventGroup.waitBits(TASK_ENDED_BIT, true, false, portMAX_DELAY);
bits & TASK_ENDED_BIT)
break;
ESP_LOGD(TAG, "http task %s ended", m_taskName);
2021-08-04 22:10:25 +02:00
return {};
}
bool AsyncHttpRequest::taskRunning() const
{
if (const auto bits = m_eventGroup.getBits();
bits & TASK_RUNNING_BIT)
return true;
return false;
}
2023-02-27 17:26:55 +01:00
std::expected<void, std::string> AsyncHttpRequest::createClient(std::string_view url, esp_http_client_method_t method,
2024-01-29 13:21:53 +01:00
int timeout_ms,
const std::optional<cpputils::ClientAuth> &clientAuth)
{
2021-08-04 22:10:25 +02:00
if (m_client)
{
2021-08-04 22:10:25 +02:00
constexpr auto msg = "m_client already created";
ESP_LOGE(TAG, "%s", msg);
2023-02-27 17:26:55 +01:00
return std::unexpected(msg);
}
2024-01-30 12:28:15 +01:00
esp_http_client_config_t config {
.url = url.data(),
.method = method,
.timeout_ms = timeout_ms,
.max_authorization_retries = 1,
.event_handler = staticHttpEventHandler,
.user_data = this,
.is_async = true,
};
if (clientAuth)
{
config.client_key_pem = clientAuth->clientKey.data();
config.client_key_len = clientAuth->clientKey.size();
config.client_cert_pem = clientAuth->clientCert.data();
config.client_cert_len = clientAuth->clientCert.size();
}
2021-08-04 22:10:25 +02:00
m_client = espcpputils::http_client{&config};
2021-08-04 22:10:25 +02:00
if (!m_client)
{
2021-12-11 23:23:33 +01:00
auto msg = fmt::format("http client could not be constructed (url={})", url);
ESP_LOGE(TAG, "%.*s", msg.size(), msg.data());
2023-02-27 17:26:55 +01:00
return std::unexpected(std::move(msg));
}
ESP_LOGD(TAG, "created http client %s", m_taskName);
2021-08-04 22:10:25 +02:00
return {};
}
2023-02-27 17:26:55 +01:00
std::expected<void, std::string> AsyncHttpRequest::deleteClient()
{
2021-08-04 22:10:25 +02:00
if (!m_client)
return {};
if (inProgress())
{
constexpr auto msg = "request still in progress";
ESP_LOGW(TAG, "%s", msg);
2023-02-27 17:26:55 +01:00
return std::unexpected(msg);
}
2021-08-04 22:10:25 +02:00
m_client = {};
2021-08-04 22:10:25 +02:00
return {};
2021-04-07 20:03:49 +02:00
}
bool AsyncHttpRequest::hasClient() const
{
return m_client;
}
2023-02-27 17:26:55 +01:00
std::expected<void, std::string> AsyncHttpRequest::start(std::string_view url,
2024-01-29 13:21:53 +01:00
esp_http_client_method_t method,
const std::map<std::string, std::string> &requestHeaders,
std::string_view requestBody, int timeout_ms,
const std::optional<cpputils::ClientAuth> &clientAuth)
2021-04-07 20:03:49 +02:00
{
2021-08-04 22:10:25 +02:00
if (!m_taskHandle)
2021-04-07 20:03:49 +02:00
{
2021-11-30 17:19:35 +01:00
if (auto result = startTask(); !result)
2023-02-27 17:26:55 +01:00
return std::unexpected(std::move(result).error());
2021-04-07 20:03:49 +02:00
}
if (inProgress())
{
constexpr auto msg = "another request still in progress";
ESP_LOGW(TAG, "%s", msg);
2023-02-27 17:26:55 +01:00
return std::unexpected(msg);
}
if (m_client)
{
2022-01-11 13:59:47 +01:00
ESP_LOGW(TAG, "old http client still constructed, destructing now");
m_client = {};
}
2024-01-29 13:21:53 +01:00
if (auto result = createClient(url, method, timeout_ms, clientAuth); !result)
2023-02-27 17:26:55 +01:00
return std::unexpected(std::move(result).error());
for (auto iter = std::cbegin(requestHeaders); iter != std::cend(requestHeaders); iter++)
if (const auto result = m_client.set_header(iter->first, iter->second); result != ESP_OK)
2021-12-06 17:12:10 +01:00
{
auto msg = fmt::format("m_client.set_header() failed: {} ({} {})", esp_err_to_name(result), iter->first, iter->second);
ESP_LOGW(TAG, "%.*s", msg.size(), msg.data());
2023-02-27 17:26:55 +01:00
return std::unexpected(std::move(msg));
2021-12-06 17:12:10 +01:00
}
if (!requestBody.empty())
{
if (const auto result = m_client.open(requestBody.size()); result != ESP_OK)
2021-12-06 17:12:10 +01:00
{
auto msg = fmt::format("m_client.open() failed: {} ({})", esp_err_to_name(result), requestBody.size());
ESP_LOGW(TAG, "%.*s", msg.size(), msg.data());
2023-02-27 17:26:55 +01:00
return std::unexpected(std::move(msg));
2021-12-06 17:12:10 +01:00
}
if (const auto written = m_client.write(requestBody); written < 0)
2021-12-06 17:12:10 +01:00
{
auto msg = fmt::format("m_client.write() failed: {}", written);
ESP_LOGW(TAG, "%.*s", msg.size(), msg.data());
2023-02-27 17:26:55 +01:00
return std::unexpected(std::move(msg));
2021-12-06 17:12:10 +01:00
}
else if (written != requestBody.size())
2021-12-06 17:12:10 +01:00
{
auto msg = fmt::format("m_client.write() written size mismatch: {} != {}", written, requestBody.size());
ESP_LOGW(TAG, "%.*s", msg.size(), msg.data());
2023-02-27 17:26:55 +01:00
return std::unexpected(std::move(msg));
2021-12-06 17:12:10 +01:00
}
}
m_buf.clear();
clearFinished();
m_eventGroup.setBits(START_REQUEST_BIT);
return {};
}
2023-02-27 17:26:55 +01:00
std::expected<void, std::string> AsyncHttpRequest::retry(std::optional<std::string_view> url,
2024-01-29 13:21:53 +01:00
std::optional<esp_http_client_method_t> method,
const std::map<std::string, std::string> &requestHeaders,
2024-01-30 12:28:15 +01:00
std::string_view requestBody, std::optional<int> timeout_ms)
{
if (!m_taskHandle)
{
if (auto result = startTask(); !result)
2023-02-27 17:26:55 +01:00
return std::unexpected(std::move(result).error());
}
if (inProgress())
{
constexpr auto msg = "another request still in progress";
ESP_LOGW(TAG, "%s", msg);
2023-02-27 17:26:55 +01:00
return std::unexpected(msg);
}
if (!m_client)
{
constexpr auto msg = "http client is null";
ESP_LOGW(TAG, "%s", msg);
2023-02-27 17:26:55 +01:00
return std::unexpected(msg);
}
2021-12-06 17:12:10 +01:00
if (url)
if (const auto result = m_client.set_url(*url); result != ESP_OK)
{
auto msg = fmt::format("m_client.set_url() failed: {} ({})", esp_err_to_name(result), *url);
ESP_LOGW(TAG, "%.*s", msg.size(), msg.data());
2023-02-27 17:26:55 +01:00
return std::unexpected(std::move(msg));
2021-12-06 17:12:10 +01:00
}
2021-12-06 17:12:10 +01:00
if (method)
if (const auto result = m_client.set_method(*method); result != ESP_OK)
{
auto msg = fmt::format("m_client.set_method() failed: {}", esp_err_to_name(result));
ESP_LOGW(TAG, "%.*s", msg.size(), msg.data());
2023-02-27 17:26:55 +01:00
return std::unexpected(std::move(msg));
2021-12-06 18:14:44 +01:00
}
if (timeout_ms)
if (const auto result = m_client.set_timeout_ms(*timeout_ms); result != ESP_OK)
{
auto msg = fmt::format("m_client.set_timeout_ms() failed: {}", esp_err_to_name(result));
ESP_LOGW(TAG, "%.*s", msg.size(), msg.data());
2023-02-27 17:26:55 +01:00
return std::unexpected(std::move(msg));
2021-12-06 17:12:10 +01:00
}
for (auto iter = std::cbegin(requestHeaders); iter != std::cend(requestHeaders); iter++)
if (const auto result = m_client.set_header(iter->first, iter->second); result != ESP_OK)
2021-12-06 17:12:10 +01:00
{
auto msg = fmt::format("m_client.set_header() failed: {} ({} {})", esp_err_to_name(result), iter->first, iter->second);
ESP_LOGW(TAG, "%.*s", msg.size(), msg.data());
2023-02-27 17:26:55 +01:00
return std::unexpected(std::move(msg));
2021-12-06 17:12:10 +01:00
}
if (!requestBody.empty())
{
if (const auto result = m_client.open(requestBody.size()); result != ESP_OK)
2021-12-06 17:12:10 +01:00
{
auto msg = fmt::format("m_client.open() failed: {} ({})", esp_err_to_name(result), requestBody.size());
ESP_LOGW(TAG, "%.*s", msg.size(), msg.data());
2023-02-27 17:26:55 +01:00
return std::unexpected(std::move(msg));
2021-12-06 17:12:10 +01:00
}
if (const auto written = m_client.write(requestBody); written < 0)
2021-12-06 17:12:10 +01:00
{
auto msg = fmt::format("m_client.write() failed: {}", written);
ESP_LOGW(TAG, "%.*s", msg.size(), msg.data());
2023-02-27 17:26:55 +01:00
return std::unexpected(std::move(msg));
2021-12-06 17:12:10 +01:00
}
else if (written != requestBody.size())
2021-12-06 17:12:10 +01:00
{
auto msg = fmt::format("m_client.write() written size mismatch: {} != {}", written, requestBody.size());
ESP_LOGW(TAG, "%.*s", msg.size(), msg.data());
2023-02-27 17:26:55 +01:00
return std::unexpected(std::move(msg));
2021-12-06 17:12:10 +01:00
}
}
2021-08-04 22:10:25 +02:00
m_buf.clear();
2021-04-07 20:03:49 +02:00
clearFinished();
2021-08-04 22:10:25 +02:00
m_eventGroup.setBits(START_REQUEST_BIT);
2021-04-07 20:03:49 +02:00
2021-08-04 22:10:25 +02:00
return {};
2021-04-07 20:03:49 +02:00
}
2023-02-27 17:26:55 +01:00
std::expected<void, std::string> AsyncHttpRequest::abort()
2021-11-30 17:19:35 +01:00
{
if (const auto bits = m_eventGroup.getBits(); !(bits & (START_REQUEST_BIT | REQUEST_RUNNING_BIT)))
2023-02-27 17:26:55 +01:00
return std::unexpected("no ota job is running!");
2021-11-30 17:19:35 +01:00
else if (bits & ABORT_REQUEST_BIT)
2023-02-27 17:26:55 +01:00
return std::unexpected("an abort has already been requested!");
2021-11-30 17:19:35 +01:00
m_eventGroup.setBits(ABORT_REQUEST_BIT);
ESP_LOGI(TAG, "http request abort requested");
return {};
}
bool AsyncHttpRequest::inProgress() const
{
2021-08-04 22:10:25 +02:00
return m_eventGroup.getBits() & (START_REQUEST_BIT | REQUEST_RUNNING_BIT);
}
2021-04-07 20:03:49 +02:00
bool AsyncHttpRequest::finished() const
{
2021-08-04 22:10:25 +02:00
return m_eventGroup.getBits() & REQUEST_FINISHED_BIT;
2021-04-07 20:03:49 +02:00
}
2023-02-27 17:26:55 +01:00
std::expected<void, std::string> AsyncHttpRequest::result() const
2021-04-07 20:03:49 +02:00
{
2021-08-04 22:10:25 +02:00
if (const auto bits = m_eventGroup.getBits();
bits & REQUEST_RUNNING_BIT)
{
constexpr auto msg = "request still running";
ESP_LOGW(TAG, "%s", msg);
2023-02-27 17:26:55 +01:00
return std::unexpected(msg);
}
else if (!(bits & REQUEST_FINISHED_BIT))
{
constexpr auto msg = "request not finished";
ESP_LOGW(TAG, "%s", msg);
2023-02-27 17:26:55 +01:00
return std::unexpected(msg);
}
2021-08-04 22:10:25 +02:00
if (m_result != ESP_OK)
2023-02-27 17:26:55 +01:00
return std::unexpected(fmt::format("http request failed: {}", esp_err_to_name(m_result)));
2021-04-07 20:03:49 +02:00
2021-08-04 22:10:25 +02:00
return {};
2021-04-07 20:03:49 +02:00
}
void AsyncHttpRequest::clearFinished()
2021-04-07 20:03:49 +02:00
{
2021-08-04 22:10:25 +02:00
m_eventGroup.clearBits(REQUEST_FINISHED_BIT);
2021-04-07 20:03:49 +02:00
}
esp_err_t AsyncHttpRequest::httpEventHandler(esp_http_client_event_t *evt)
{
2021-11-15 11:44:17 +01:00
switch(evt->event_id)
{
2021-04-07 20:03:49 +02:00
case HTTP_EVENT_ON_HEADER:
if (evt->header_key && evt->header_value)
{
if (m_collectResponseHeaders)
m_responseHeaders.emplace(std::make_pair(evt->header_key, evt->header_value));
2021-04-07 20:03:49 +02:00
if (strcasecmp(evt->header_key, "Content-Length") == 0)
{
unsigned int size;
if (std::sscanf(evt->header_value, "%u", &size) == 1)
{
//ESP_LOGD(TAG, "reserving %u bytes for http buffer", size);
2021-11-15 11:44:17 +01:00
m_buf.reserve(std::min(size, m_sizeLimit));
2021-04-07 20:03:49 +02:00
}
else
{
ESP_LOGW(TAG, "Could not parse Content-Length header \"%s\"", evt->header_value);
}
}
}
break;
case HTTP_EVENT_ON_DATA:
if (!evt->data)
ESP_LOGW(TAG, "handler with invalid data ptr");
else if (evt->data_len <= 0)
ESP_LOGW(TAG, "handler with invalid data_len %i", evt->data_len);
2021-11-15 11:44:17 +01:00
else if (m_buf.size() >= m_sizeLimit)
return ESP_ERR_NO_MEM;
else
2021-11-15 11:44:17 +01:00
{
const auto remainingSize = m_sizeLimit - m_buf.size();
m_buf += std::string_view((const char *)evt->data, std::min<size_t>(evt->data_len, remainingSize));
if (remainingSize < evt->data_len)
return ESP_ERR_NO_MEM;
}
2021-04-07 20:03:49 +02:00
break;
2021-11-15 11:44:17 +01:00
default:
;
2021-04-07 20:03:49 +02:00
}
return ESP_OK;
}
2021-11-15 11:44:17 +01:00
esp_err_t AsyncHttpRequest::staticHttpEventHandler(esp_http_client_event_t *evt)
{
auto _this = reinterpret_cast<AsyncHttpRequest*>(evt->user_data);
assert(_this);
return _this->httpEventHandler(evt);
}
2021-04-07 20:03:49 +02:00
void AsyncHttpRequest::requestTask(void *ptr)
{
auto _this = reinterpret_cast<AsyncHttpRequest*>(ptr);
2021-04-07 20:03:49 +02:00
assert(_this);
2021-08-04 22:10:25 +02:00
_this->requestTask();
}
void AsyncHttpRequest::requestTask()
{
2021-11-30 17:19:35 +01:00
m_eventGroup.setBits(TASK_RUNNING_BIT);
// cleanup on task exit
auto helper = cpputils::makeCleanupHelper([&](){
2021-11-30 17:19:35 +01:00
m_eventGroup.clearBits(TASK_RUNNING_BIT);
m_eventGroup.setBits(TASK_ENDED_BIT);
2021-08-04 22:10:25 +02:00
m_taskHandle = NULL;
vTaskDelete(NULL);
});
2021-04-07 20:03:49 +02:00
while (true)
{
2021-08-04 22:10:25 +02:00
if (const auto bits = m_eventGroup.waitBits(START_REQUEST_BIT|END_TASK_BIT, true, false, portMAX_DELAY);
bits & END_TASK_BIT)
break;
else if (!(bits & START_REQUEST_BIT))
continue;
2021-08-04 22:10:25 +02:00
assert(m_client);
2021-04-07 20:03:49 +02:00
{
2021-08-04 22:10:25 +02:00
const auto bits = m_eventGroup.getBits();
assert(!(bits & START_REQUEST_BIT));
assert(!(bits & REQUEST_RUNNING_BIT));
assert(!(bits & REQUEST_FINISHED_BIT));
2021-04-07 20:03:49 +02:00
}
2021-08-04 22:10:25 +02:00
m_eventGroup.setBits(REQUEST_RUNNING_BIT);
auto helper2 = cpputils::makeCleanupHelper([&](){
2021-11-30 17:19:35 +01:00
m_eventGroup.clearBits(REQUEST_RUNNING_BIT | ABORT_REQUEST_BIT);
2021-08-04 22:10:25 +02:00
m_eventGroup.setBits(REQUEST_FINISHED_BIT);
});
2021-04-07 20:03:49 +02:00
{
esp_err_t result;
do
2021-11-30 17:19:35 +01:00
{
2022-03-21 10:58:50 +01:00
m_buf.clear();
2021-08-04 22:10:25 +02:00
result = m_client.perform();
2024-01-30 15:42:06 +01:00
ESP_LOG_LEVEL_LOCAL((cpputils::is_in(result, ESP_OK, EAGAIN, EINPROGRESS, ESP_ERR_HTTP_EAGAIN) ? ESP_LOG_DEBUG : ESP_LOG_WARN),
TAG, "m_client.perform() returned: %s", result == EAGAIN ? "EAGAIN" : (result == EINPROGRESS ? "EINPROGRESS" : esp_err_to_name(result)));
2021-11-30 17:19:35 +01:00
if (m_eventGroup.clearBits(ABORT_REQUEST_BIT) & ABORT_REQUEST_BIT)
{
ESP_LOGW(TAG, "abort request received");
result = ESP_FAIL;
break;
}
}
2024-01-30 15:42:06 +01:00
while (cpputils::is_in(result, EAGAIN, EINPROGRESS, ESP_ERR_HTTP_EAGAIN));
2021-11-30 17:19:35 +01:00
2021-08-04 22:10:25 +02:00
m_result = result;
m_statusCode = m_client.get_status_code();
2021-04-07 20:03:49 +02:00
}
2022-01-11 13:59:47 +01:00
{
const auto result = m_client.close();
ESP_LOGD(TAG, "m_client.close() returned: %s", esp_err_to_name(result));
}
2021-04-07 20:03:49 +02:00
}
}