mirror of
https://github.com/esphome/esphome.git
synced 2025-03-25 20:18:15 +00:00
247 lines
8.2 KiB
C++
247 lines
8.2 KiB
C++
#pragma once
|
|
|
|
#include <list>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "esphome/components/json/json_util.h"
|
|
#include "esphome/core/application.h"
|
|
#include "esphome/core/automation.h"
|
|
#include "esphome/core/component.h"
|
|
#include "esphome/core/defines.h"
|
|
#include "esphome/core/helpers.h"
|
|
#include "esphome/core/log.h"
|
|
|
|
namespace esphome {
|
|
namespace http_request {
|
|
|
|
struct Header {
|
|
const char *name;
|
|
const char *value;
|
|
};
|
|
|
|
// Some common HTTP status codes
|
|
enum HttpStatus {
|
|
HTTP_STATUS_OK = 200,
|
|
HTTP_STATUS_NO_CONTENT = 204,
|
|
HTTP_STATUS_PARTIAL_CONTENT = 206,
|
|
|
|
/* 3xx - Redirection */
|
|
HTTP_STATUS_MULTIPLE_CHOICES = 300,
|
|
HTTP_STATUS_MOVED_PERMANENTLY = 301,
|
|
HTTP_STATUS_FOUND = 302,
|
|
HTTP_STATUS_SEE_OTHER = 303,
|
|
HTTP_STATUS_NOT_MODIFIED = 304,
|
|
HTTP_STATUS_TEMPORARY_REDIRECT = 307,
|
|
HTTP_STATUS_PERMANENT_REDIRECT = 308,
|
|
|
|
/* 4XX - CLIENT ERROR */
|
|
HTTP_STATUS_BAD_REQUEST = 400,
|
|
HTTP_STATUS_UNAUTHORIZED = 401,
|
|
HTTP_STATUS_FORBIDDEN = 403,
|
|
HTTP_STATUS_NOT_FOUND = 404,
|
|
HTTP_STATUS_METHOD_NOT_ALLOWED = 405,
|
|
HTTP_STATUS_NOT_ACCEPTABLE = 406,
|
|
HTTP_STATUS_LENGTH_REQUIRED = 411,
|
|
|
|
/* 5xx - Server Error */
|
|
HTTP_STATUS_INTERNAL_ERROR = 500
|
|
};
|
|
|
|
/**
|
|
* @brief Returns true if the HTTP status code is a redirect.
|
|
*
|
|
* @param status the HTTP status code to check
|
|
* @return true if the status code is a redirect, false otherwise
|
|
*/
|
|
inline bool is_redirect(int const status) {
|
|
switch (status) {
|
|
case HTTP_STATUS_MOVED_PERMANENTLY:
|
|
case HTTP_STATUS_FOUND:
|
|
case HTTP_STATUS_SEE_OTHER:
|
|
case HTTP_STATUS_TEMPORARY_REDIRECT:
|
|
case HTTP_STATUS_PERMANENT_REDIRECT:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Checks if the given HTTP status code indicates a successful request.
|
|
*
|
|
* A successful request is one where the status code is in the range 200-299
|
|
*
|
|
* @param status the HTTP status code to check
|
|
* @return true if the status code indicates a successful request, false otherwise
|
|
*/
|
|
inline bool is_success(int const status) { return status >= HTTP_STATUS_OK && status < HTTP_STATUS_MULTIPLE_CHOICES; }
|
|
|
|
class HttpRequestComponent;
|
|
|
|
class HttpContainer : public Parented<HttpRequestComponent> {
|
|
public:
|
|
virtual ~HttpContainer() = default;
|
|
size_t content_length;
|
|
int status_code;
|
|
uint32_t duration_ms;
|
|
|
|
virtual int read(uint8_t *buf, size_t max_len) = 0;
|
|
virtual void end() = 0;
|
|
|
|
void set_secure(bool secure) { this->secure_ = secure; }
|
|
|
|
size_t get_bytes_read() const { return this->bytes_read_; }
|
|
|
|
protected:
|
|
size_t bytes_read_{0};
|
|
bool secure_{false};
|
|
};
|
|
|
|
class HttpRequestResponseTrigger : public Trigger<std::shared_ptr<HttpContainer>, std::string &> {
|
|
public:
|
|
void process(std::shared_ptr<HttpContainer> container, std::string &response_body) {
|
|
this->trigger(std::move(container), response_body);
|
|
}
|
|
};
|
|
|
|
class HttpRequestComponent : public Component {
|
|
public:
|
|
void dump_config() override;
|
|
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
|
|
|
void set_useragent(const char *useragent) { this->useragent_ = useragent; }
|
|
void set_timeout(uint16_t timeout) { this->timeout_ = timeout; }
|
|
void set_watchdog_timeout(uint32_t watchdog_timeout) { this->watchdog_timeout_ = watchdog_timeout; }
|
|
uint32_t get_watchdog_timeout() const { return this->watchdog_timeout_; }
|
|
void set_follow_redirects(bool follow_redirects) { this->follow_redirects_ = follow_redirects; }
|
|
void set_redirect_limit(uint16_t limit) { this->redirect_limit_ = limit; }
|
|
|
|
std::shared_ptr<HttpContainer> get(std::string url) { return this->start(std::move(url), "GET", "", {}); }
|
|
std::shared_ptr<HttpContainer> get(std::string url, std::list<Header> headers) {
|
|
return this->start(std::move(url), "GET", "", std::move(headers));
|
|
}
|
|
std::shared_ptr<HttpContainer> post(std::string url, std::string body) {
|
|
return this->start(std::move(url), "POST", std::move(body), {});
|
|
}
|
|
std::shared_ptr<HttpContainer> post(std::string url, std::string body, std::list<Header> headers) {
|
|
return this->start(std::move(url), "POST", std::move(body), std::move(headers));
|
|
}
|
|
|
|
virtual std::shared_ptr<HttpContainer> start(std::string url, std::string method, std::string body,
|
|
std::list<Header> headers) = 0;
|
|
|
|
protected:
|
|
const char *useragent_{nullptr};
|
|
bool follow_redirects_;
|
|
uint16_t redirect_limit_;
|
|
uint16_t timeout_{4500};
|
|
uint32_t watchdog_timeout_{0};
|
|
};
|
|
|
|
template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
|
|
public:
|
|
HttpRequestSendAction(HttpRequestComponent *parent) : parent_(parent) {}
|
|
TEMPLATABLE_VALUE(std::string, url)
|
|
TEMPLATABLE_VALUE(const char *, method)
|
|
TEMPLATABLE_VALUE(std::string, body)
|
|
TEMPLATABLE_VALUE(bool, capture_response)
|
|
|
|
void add_header(const char *key, TemplatableValue<const char *, Ts...> value) { this->headers_.insert({key, value}); }
|
|
|
|
void add_json(const char *key, TemplatableValue<std::string, Ts...> value) { this->json_.insert({key, value}); }
|
|
|
|
void set_json(std::function<void(Ts..., JsonObject)> json_func) { this->json_func_ = json_func; }
|
|
|
|
void register_response_trigger(HttpRequestResponseTrigger *trigger) { this->response_triggers_.push_back(trigger); }
|
|
|
|
void set_max_response_buffer_size(size_t max_response_buffer_size) {
|
|
this->max_response_buffer_size_ = max_response_buffer_size;
|
|
}
|
|
|
|
void play(Ts... x) override {
|
|
std::string body;
|
|
if (this->body_.has_value()) {
|
|
body = this->body_.value(x...);
|
|
}
|
|
if (!this->json_.empty()) {
|
|
auto f = std::bind(&HttpRequestSendAction<Ts...>::encode_json_, this, x..., std::placeholders::_1);
|
|
body = json::build_json(f);
|
|
}
|
|
if (this->json_func_ != nullptr) {
|
|
auto f = std::bind(&HttpRequestSendAction<Ts...>::encode_json_func_, this, x..., std::placeholders::_1);
|
|
body = json::build_json(f);
|
|
}
|
|
std::list<Header> headers;
|
|
for (const auto &item : this->headers_) {
|
|
auto val = item.second;
|
|
Header header;
|
|
header.name = item.first;
|
|
header.value = val.value(x...);
|
|
headers.push_back(header);
|
|
}
|
|
|
|
auto container = this->parent_->start(this->url_.value(x...), this->method_.value(x...), body, headers);
|
|
|
|
if (container == nullptr) {
|
|
return;
|
|
}
|
|
|
|
size_t content_length = container->content_length;
|
|
size_t max_length = std::min(content_length, this->max_response_buffer_size_);
|
|
|
|
std::string response_body;
|
|
if (this->capture_response_.value(x...)) {
|
|
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
|
|
uint8_t *buf = allocator.allocate(max_length);
|
|
if (buf != nullptr) {
|
|
size_t read_index = 0;
|
|
while (container->get_bytes_read() < max_length) {
|
|
int read = container->read(buf + read_index, std::min<size_t>(max_length - read_index, 512));
|
|
App.feed_wdt();
|
|
yield();
|
|
read_index += read;
|
|
}
|
|
response_body.reserve(read_index);
|
|
response_body.assign((char *) buf, read_index);
|
|
allocator.deallocate(buf, max_length);
|
|
}
|
|
}
|
|
|
|
if (this->response_triggers_.size() == 1) {
|
|
// if there is only one trigger, no need to copy the response body
|
|
this->response_triggers_[0]->process(container, response_body);
|
|
} else {
|
|
for (auto *trigger : this->response_triggers_) {
|
|
// with multiple triggers, pass a copy of the response body to each
|
|
// one so that modifications made in one trigger are not visible to
|
|
// the others
|
|
auto response_body_copy = std::string(response_body);
|
|
trigger->process(container, response_body_copy);
|
|
}
|
|
}
|
|
container->end();
|
|
}
|
|
|
|
protected:
|
|
void encode_json_(Ts... x, JsonObject root) {
|
|
for (const auto &item : this->json_) {
|
|
auto val = item.second;
|
|
root[item.first] = val.value(x...);
|
|
}
|
|
}
|
|
void encode_json_func_(Ts... x, JsonObject root) { this->json_func_(x..., root); }
|
|
HttpRequestComponent *parent_;
|
|
std::map<const char *, TemplatableValue<const char *, Ts...>> headers_{};
|
|
std::map<const char *, TemplatableValue<std::string, Ts...>> json_{};
|
|
std::function<void(Ts..., JsonObject)> json_func_{nullptr};
|
|
std::vector<HttpRequestResponseTrigger *> response_triggers_;
|
|
|
|
size_t max_response_buffer_size_{SIZE_MAX};
|
|
};
|
|
|
|
} // namespace http_request
|
|
} // namespace esphome
|