ESPHome  2024.12.2
http_request.h
Go to the documentation of this file.
1 #pragma once
2 
3 #include <list>
4 #include <map>
5 #include <memory>
6 #include <utility>
7 #include <vector>
8 
12 #include "esphome/core/component.h"
13 #include "esphome/core/defines.h"
14 #include "esphome/core/helpers.h"
15 #include "esphome/core/log.h"
16 
17 namespace esphome {
18 namespace http_request {
19 
20 struct Header {
21  const char *name;
22  const char *value;
23 };
24 
25 // Some common HTTP status codes
26 enum HttpStatus {
30 
31  /* 3xx - Redirection */
39 
40  /* 4XX - CLIENT ERROR */
48 
49  /* 5xx - Server Error */
51 };
52 
59 inline bool is_redirect(int const status) {
60  switch (status) {
62  case HTTP_STATUS_FOUND:
66  return true;
67  default:
68  return false;
69  }
70 }
71 
80 inline bool is_success(int const status) { return status >= HTTP_STATUS_OK && status < HTTP_STATUS_MULTIPLE_CHOICES; }
81 
83 
84 class HttpContainer : public Parented<HttpRequestComponent> {
85  public:
86  virtual ~HttpContainer() = default;
89  uint32_t duration_ms;
90 
91  virtual int read(uint8_t *buf, size_t max_len) = 0;
92  virtual void end() = 0;
93 
94  void set_secure(bool secure) { this->secure_ = secure; }
95 
96  size_t get_bytes_read() const { return this->bytes_read_; }
97 
98  protected:
99  size_t bytes_read_{0};
100  bool secure_{false};
101 };
102 
103 class HttpRequestResponseTrigger : public Trigger<std::shared_ptr<HttpContainer>, std::string &> {
104  public:
105  void process(std::shared_ptr<HttpContainer> container, std::string &response_body) {
106  this->trigger(std::move(container), response_body);
107  }
108 };
109 
111  public:
112  void dump_config() override;
113  float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
114 
115  void set_useragent(const char *useragent) { this->useragent_ = useragent; }
116  void set_timeout(uint16_t timeout) { this->timeout_ = timeout; }
117  void set_watchdog_timeout(uint32_t watchdog_timeout) { this->watchdog_timeout_ = watchdog_timeout; }
118  uint32_t get_watchdog_timeout() const { return this->watchdog_timeout_; }
119  void set_follow_redirects(bool follow_redirects) { this->follow_redirects_ = follow_redirects; }
120  void set_redirect_limit(uint16_t limit) { this->redirect_limit_ = limit; }
121 
122  std::shared_ptr<HttpContainer> get(std::string url) { return this->start(std::move(url), "GET", "", {}); }
123  std::shared_ptr<HttpContainer> get(std::string url, std::list<Header> headers) {
124  return this->start(std::move(url), "GET", "", std::move(headers));
125  }
126  std::shared_ptr<HttpContainer> post(std::string url, std::string body) {
127  return this->start(std::move(url), "POST", std::move(body), {});
128  }
129  std::shared_ptr<HttpContainer> post(std::string url, std::string body, std::list<Header> headers) {
130  return this->start(std::move(url), "POST", std::move(body), std::move(headers));
131  }
132 
133  virtual std::shared_ptr<HttpContainer> start(std::string url, std::string method, std::string body,
134  std::list<Header> headers) = 0;
135 
136  protected:
137  const char *useragent_{nullptr};
138  bool follow_redirects_{};
139  uint16_t redirect_limit_{};
140  uint16_t timeout_{4500};
141  uint32_t watchdog_timeout_{0};
142 };
143 
144 template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
145  public:
146  HttpRequestSendAction(HttpRequestComponent *parent) : parent_(parent) {}
147  TEMPLATABLE_VALUE(std::string, url)
148  TEMPLATABLE_VALUE(const char *, method)
149  TEMPLATABLE_VALUE(std::string, body)
150  TEMPLATABLE_VALUE(bool, capture_response)
151 
152  void add_header(const char *key, TemplatableValue<const char *, Ts...> value) { this->headers_.insert({key, value}); }
153 
154  void add_json(const char *key, TemplatableValue<std::string, Ts...> value) { this->json_.insert({key, value}); }
155 
156  void set_json(std::function<void(Ts..., JsonObject)> json_func) { this->json_func_ = json_func; }
157 
158  void register_response_trigger(HttpRequestResponseTrigger *trigger) { this->response_triggers_.push_back(trigger); }
159 
160  void register_error_trigger(Trigger<> *trigger) { this->error_triggers_.push_back(trigger); }
161 
162  void set_max_response_buffer_size(size_t max_response_buffer_size) {
163  this->max_response_buffer_size_ = max_response_buffer_size;
164  }
165 
166  void play(Ts... x) override {
167  std::string body;
168  if (this->body_.has_value()) {
169  body = this->body_.value(x...);
170  }
171  if (!this->json_.empty()) {
172  auto f = std::bind(&HttpRequestSendAction<Ts...>::encode_json_, this, x..., std::placeholders::_1);
173  body = json::build_json(f);
174  }
175  if (this->json_func_ != nullptr) {
176  auto f = std::bind(&HttpRequestSendAction<Ts...>::encode_json_func_, this, x..., std::placeholders::_1);
177  body = json::build_json(f);
178  }
179  std::list<Header> headers;
180  for (const auto &item : this->headers_) {
181  auto val = item.second;
182  Header header;
183  header.name = item.first;
184  header.value = val.value(x...);
185  headers.push_back(header);
186  }
187 
188  auto container = this->parent_->start(this->url_.value(x...), this->method_.value(x...), body, headers);
189 
190  if (container == nullptr) {
191  for (auto *trigger : this->error_triggers_)
192  trigger->trigger();
193  return;
194  }
195 
196  size_t content_length = container->content_length;
197  size_t max_length = std::min(content_length, this->max_response_buffer_size_);
198 
199  std::string response_body;
200  if (this->capture_response_.value(x...)) {
202  uint8_t *buf = allocator.allocate(max_length);
203  if (buf != nullptr) {
204  size_t read_index = 0;
205  while (container->get_bytes_read() < max_length) {
206  int read = container->read(buf + read_index, std::min<size_t>(max_length - read_index, 512));
207  App.feed_wdt();
208  yield();
209  read_index += read;
210  }
211  response_body.reserve(read_index);
212  response_body.assign((char *) buf, read_index);
213  allocator.deallocate(buf, max_length);
214  }
215  }
216 
217  if (this->response_triggers_.size() == 1) {
218  // if there is only one trigger, no need to copy the response body
219  this->response_triggers_[0]->process(container, response_body);
220  } else {
221  for (auto *trigger : this->response_triggers_) {
222  // with multiple triggers, pass a copy of the response body to each
223  // one so that modifications made in one trigger are not visible to
224  // the others
225  auto response_body_copy = std::string(response_body);
226  trigger->process(container, response_body_copy);
227  }
228  }
229  container->end();
230  }
231 
232  protected:
233  void encode_json_(Ts... x, JsonObject root) {
234  for (const auto &item : this->json_) {
235  auto val = item.second;
236  root[item.first] = val.value(x...);
237  }
238  }
239  void encode_json_func_(Ts... x, JsonObject root) { this->json_func_(x..., root); }
241  std::map<const char *, TemplatableValue<const char *, Ts...>> headers_{};
242  std::map<const char *, TemplatableValue<std::string, Ts...>> json_{};
243  std::function<void(Ts..., JsonObject)> json_func_{nullptr};
244  std::vector<HttpRequestResponseTrigger *> response_triggers_{};
245  std::vector<Trigger<> *> error_triggers_{};
246 
247  size_t max_response_buffer_size_{SIZE_MAX};
248 };
249 
250 } // namespace http_request
251 } // namespace esphome
void set_json(std::function< void(Ts..., JsonObject)> json_func)
Definition: http_request.h:156
std::shared_ptr< HttpContainer > post(std::string url, std::string body)
Definition: http_request.h:126
const float AFTER_WIFI
For components that should be initialized after WiFi is connected.
Definition: component.cpp:26
void set_follow_redirects(bool follow_redirects)
Definition: http_request.h:119
void set_useragent(const char *useragent)
Definition: http_request.h:115
uint16_t x
Definition: tt21100.cpp:17
void register_error_trigger(Trigger<> *trigger)
Definition: http_request.h:160
bool is_redirect(int const status)
Returns true if the HTTP status code is a redirect.
Definition: http_request.h:59
T * allocate(size_t n)
Definition: helpers.h:690
void add_json(const char *key, TemplatableValue< std::string, Ts... > value)
Definition: http_request.h:154
void set_max_response_buffer_size(size_t max_response_buffer_size)
Definition: http_request.h:162
mopeka_std_values val[4]
bool is_success(int const status)
Checks if the given HTTP status code indicates a successful request.
Definition: http_request.h:80
std::shared_ptr< HttpContainer > post(std::string url, std::string body, std::list< Header > headers)
Definition: http_request.h:129
void encode_json_func_(Ts... x, JsonObject root)
Definition: http_request.h:239
Application App
Global storage of Application pointer - only one Application can exist.
std::string build_json(const json_build_t &f)
Build a JSON string with the provided json build function.
Definition: json_util.cpp:21
void deallocate(T *p, size_t n)
Definition: helpers.h:709
uint8_t status
Definition: bl0942.h:74
void set_watchdog_timeout(uint32_t watchdog_timeout)
Definition: http_request.h:117
void IRAM_ATTR HOT yield()
Definition: core.cpp:24
void process(std::shared_ptr< HttpContainer > container, std::string &response_body)
Definition: http_request.h:105
HttpRequestSendAction(HttpRequestComponent *parent)
Definition: http_request.h:146
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
uint8_t end[39]
Definition: sun_gtil2.cpp:31
void register_response_trigger(HttpRequestResponseTrigger *trigger)
Definition: http_request.h:158
void encode_json_(Ts... x, JsonObject root)
Definition: http_request.h:233
An STL allocator that uses SPI or internal RAM.
Definition: helpers.h:675
Helper class to easily give an object a parent of type T.
Definition: helpers.h:530