arduino-audio-tools
HttpRequest.h
1 #pragma once
2 #include "AudioConfig.h"
3 #ifdef USE_URL_ARDUINO
4 
5 #include "HttpChunkReader.h"
6 #include "HttpHeader.h"
7 #include "HttpTypes.h"
8 #include "Url.h"
9 #include "AudioLogger.h"
10 
11 #define CHUNK_SIZE 1024
12 
13 namespace audio_tools {
14 
26 class HttpRequest : public BaseStream {
27  public:
28  friend class URLStream;
29 
30  HttpRequest() = default;
31 
33 
34  HttpRequest(Client &client) { setClient(client); }
35 
36  void setClient(Client &client) {
37  this->client_ptr = &client;
38  this->client_ptr->setTimeout(clientTimeout);
39  }
40 
41  // the requests usually need a host. This needs to be set if we did not
42  // provide a URL
43  void setHost(const char *host) {
44  LOGI("setHost %s", host);
45  this->host_name = host;
46  }
47 
48  operator bool() { return client_ptr != nullptr ? (bool)*client_ptr : false; }
49 
50  virtual bool connected() {
51  return client_ptr == nullptr ? false : client_ptr->connected();
52  }
53 
54  virtual int available() override {
55  if (reply_header.isChunked()) {
56  return chunk_reader.available();
57  }
58  return client_ptr != nullptr ? client_ptr->available() : 0;
59  }
60 
62  void end() override {
63  if (connected()) {
64  // write final 0 chunk if necessary
65  if (is_chunked_output_active) {
66  client_ptr->println(0, HEX);
67  client_ptr->println();
68  client_ptr->flush();
69  is_chunked_output_active = false;
70  }
71  LOGI("stop");
72  client_ptr->stop();
73  }
74  }
75 
76  virtual void stop() {
77  end();
78  }
79 
81  virtual int post(Url &url, const char *mime, const char *data, int len = -1) {
82  LOGI("post %s", url.url());
83  return process(POST, url, mime, data, len);
84  }
85 
87  virtual int post(Url &url, const char *mime, Stream &data, int len = -1) {
88  LOGI("post %s", url.url());
89  return process(POST, url, mime, data, len);
90  }
91 
93  virtual int put(Url &url, const char *mime, const char *data, int len = -1) {
94  LOGI("put %s", url.url());
95  return process(PUT, url, mime, data, len);
96  }
97 
99  virtual int put(Url &url, const char *mime, Stream &data, int len = -1) {
100  LOGI("put %s", url.url());
101  return process(PUT, url, mime, data, len);
102  }
103 
105  virtual int del(Url &url, const char *mime = nullptr,
106  const char *data = nullptr, int len = -1) {
107  LOGI("del %s", url.url());
108  return process(DELETE, url, mime, data, len);
109  }
110 
112  virtual int get(Url &url, const char *acceptMime = nullptr,
113  const char *data = nullptr, int len = -1) {
114  LOGI("get %s", url.url());
115  this->accept = acceptMime;
116  return process(GET, url, nullptr, data, len);
117  }
118 
120  virtual int head(Url &url, const char *acceptMime = nullptr,
121  const char *data = nullptr, int len = -1) {
122  LOGI("head %s", url.url());
123  this->accept = acceptMime;
124  return process(HEAD, url, nullptr, data, len);
125  }
126 
127  // reads the reply data
128  virtual int read(uint8_t *str, int len) {
129  TRACED();
130  if (reply_header.isChunked()) {
131  return chunk_reader.read(*client_ptr, str, len);
132  } else {
133  return client_ptr->read(str, len);
134  }
135  }
136 
137  size_t readBytes(uint8_t *str, size_t len) override {
138  return read(str, len);
139  }
140 
141  size_t readBytesUntil(char terminator, char *buffer, size_t length) {
142  TRACED();
143  return client_ptr->readBytesUntil(terminator, buffer, length);
144  }
145 
146  // read the reply data up to the next new line. For Chunked data we provide
147  // the full chunk!
148  virtual int readln(uint8_t *str, int len, bool incl_nl = true) {
149  TRACED();
150  if (reply_header.isChunked()) {
151  return chunk_reader.readln(*client_ptr, str, len);
152  } else {
153  return chunk_reader.readlnInternal(*client_ptr, str, len, incl_nl);
154  }
155  }
156 
157  // provides the head information of the reply
158  virtual HttpReplyHeader &reply() { return reply_header; }
159 
161  virtual HttpRequestHeader &header() { return request_header; }
162 
164  virtual void setAgent(const char *agent) { this->agent = agent; }
165 
166  virtual void setConnection(const char *connection) {
167  this->connection = connection;
168  }
169 
170  virtual void setAcceptsEncoding(const char *enc) {
171  this->accept_encoding = enc;
172  }
173 
174  virtual void setAcceptMime(const char *mime) { this->accept = mime; }
175 
176  size_t contentLength() {
177  const char *len_str = reply().get(CONTENT_LENGTH);
178  int len = 0;
179  if (len_str != nullptr) {
180  len = atoi(len_str);
181  } else {
182  LOGI("no CONTENT_LENGTH found in reply");
183  }
184  return len;
185  }
186 
189  bool isReady() { return is_ready; }
190 
192  void addRequestHeader(const char *header, const char *value) {
193  request_header.put(header, value);
194  }
195 
196  Client &client() { return *client_ptr; }
197 
198  // process http request and reads the reply_header from the server
199  virtual int process(MethodID action, Url &url, const char *mime,
200  const char *data, int lenData = -1) {
201  int len = lenData;
202  if (data != nullptr && len <= 0) {
203  len = strlen(data);
204  }
205  processBegin(action, url, mime, len);
206  // posting data parameter
207  if (len > 0 && data != nullptr) {
208  LOGI("Writing data: %d bytes", len);
209  client_ptr->write((const uint8_t *)data, len);
210  LOGD("%s", data);
211  }
212  return processEnd();
213  }
214 
215  // process http request and reads the reply_header from the server
216  virtual int process(MethodID action, Url &url, const char *mime,
217  Stream &stream, int len = -1) {
218  if (!processBegin(action, url, mime, len))
219  return -1;
220  processWrite(stream);
221  return processEnd();
222  }
223 
225  virtual bool processBegin(MethodID action, Url &url, const char *mime,
226  int lenData = -1) {
227  TRACED();
228  int len = lenData;
229  is_ready = false;
230  if (client_ptr == nullptr) {
231  LOGE("The client has not been defined");
232  return false;
233  }
234  if (http_connect_callback) {
235  http_connect_callback(*this, url, request_header);
236  }
237  if (!this->connected()) {
238  LOGI("process connecting to host %s port %d", url.host(), url.port());
239  int is_connected = connect(url.host(), url.port(), clientTimeout);
240  if (!is_connected) {
241  LOGE("Connect failed");
242  return false;
243  }
244  } else {
245  LOGI("process is already connected");
246  }
247 
248 #if defined(ESP32) && defined(ARDUINO)
249  LOGI("Free heap: %u", (unsigned)ESP.getFreeHeap());
250 #endif
251 
252  reply_header.setProcessed();
253 
254  host_name = url.host();
255  request_header.setValues(action, url.path());
256  if (lenData > 0) {
257  request_header.put(CONTENT_LENGTH, lenData);
258  }
259  request_header.put(HOST_C, host_name);
260  request_header.put(CONNECTION, connection);
261  request_header.put(USER_AGENT, agent);
262  request_header.put(ACCEPT_ENCODING, accept_encoding);
263  request_header.put(ACCEPT, accept);
264  request_header.put(CONTENT_TYPE, mime);
265  request_header.write(*client_ptr);
266 
267  return true;
268  }
269 
271  virtual void processWrite(Stream &stream) {
272  uint8_t buffer[CHUNK_SIZE];
273  int total = 0;
274  int total_written = 0;
275  while (*client_ptr && stream.available() > 0) {
276  int result_len = stream.readBytes(buffer, CHUNK_SIZE);
277  total += result_len;
278  int written = write(buffer, result_len);
279  total_written += written;
280  LOGI("--> Bytes read %d vs written %d", result_len, written);
281  delay(1);
282  }
283  client_ptr->flush();
284  LOGI("Total bytes read %d vs written %d", total, total_written);
285  }
286 
289  size_t write(const uint8_t *data, size_t len) override {
290  TRACED();
291  size_t result = 0;
292  if (isChunked()) {
293  if (len > 0) {
294  is_chunked_output_active = true;
295  client_ptr->println(len, HEX);
296  result = client_ptr->write(data, len);
297  client_ptr->println();
298  }
299  } else {
300  result = client_ptr->write(data, len);
301  }
302  return result;
303  }
304 
306  virtual int processEnd() {
307  TRACED();
308  // if sending is chunked we terminate with an empty chunk
309  if (isChunked()) {
310  if (is_chunked_output_active) client_ptr->println(0, HEX);
311  client_ptr->println();
312  is_chunked_output_active = false;
313  }
314  LOGI("Request written ... waiting for reply");
315  // Commented out because this breaks the RP2040 W
316  // client_ptr->flush();
317  reply_header.read(*client_ptr);
318 
319  // if we use chunked tranfer we need to read the first chunked length
320  if (reply_header.isChunked()) {
321  chunk_reader.open(*client_ptr);
322  };
323 
324  // wait for data
325  is_ready = true;
326  return reply_header.statusCode();
327  }
328 
330  void setOnConnectCallback(void (*callback)(
331  HttpRequest &request, Url &url, HttpRequestHeader &request_header)) {
332  http_connect_callback = callback;
333  }
334 
336  void setTimeout(int timeoutMs) { clientTimeout = timeoutMs; }
337 
339  bool isChunked() { return request_header.isChunked(); }
340 
341  protected:
342  Client *client_ptr = nullptr;
343  Url url;
344  HttpRequestHeader request_header;
345  HttpReplyHeader reply_header;
346  HttpChunkReader chunk_reader = HttpChunkReader(reply_header);
347  const char *agent = nullptr;
348  const char *host_name = nullptr;
349  const char *connection = CON_KEEP_ALIVE;
350  const char *accept = ACCEPT_ALL;
351  const char *accept_encoding = IDENTITY;
352  bool is_ready = false;
353  int32_t clientTimeout = URL_CLIENT_TIMEOUT; // 60000;
354  void (*http_connect_callback)(HttpRequest &request, Url &url,
355  HttpRequestHeader &request_header) = nullptr;
356  bool is_chunked_output_active = false;
357 
358  // opens a connection to the indicated host
359  virtual int connect(const char *ip, uint16_t port, int32_t timeout) {
360  TRACED();
361  client_ptr->setTimeout(timeout / 1000); // client timeout is in seconds!
362  request_header.setTimeout(timeout);
363  reply_header.setTimeout(timeout);
364  int is_connected = this->client_ptr->connect(ip, port);
365  LOGI("is connected %s with timeout %d", is_connected ? "true" : "false", (int)timeout);
366  return is_connected;
367  }
368 };
369 
370 } // namespace audio_tools
371 
372 #endif
Base class for all Streams. It relies on write(const uint8_t *buffer, size_t size) and readBytes(uint...
Definition: BaseStream.h:34
Definition: NoArduino.h:139
Http might reply with chunks. So we need to dechunk the data. see https://en.wikipedia....
Definition: HttpChunkReader.h:14
void setTimeout(int timeoutMs)
Set the timout.
Definition: HttpHeader.h:308
static void end()
release static temp buffer
Definition: HttpHeader.h:305
void write(Client &out)
writes the full header to the indicated HttpStreamedMultiOutput stream
Definition: HttpHeader.h:275
bool read(Client &in)
reads the full header from the request (stream)
Definition: HttpHeader.h:230
Reading and Writing of Http Replys.
Definition: HttpHeader.h:448
Reading and writing of Http Requests.
Definition: HttpHeader.h:389
Simple API to process get, put, post, del http requests I tried to use Arduino HttpClient,...
Definition: HttpRequest.h:26
void setTimeout(int timeoutMs)
Defines the client timeout in ms.
Definition: HttpRequest.h:336
bool isChunked()
we are sending the data chunked
Definition: HttpRequest.h:339
virtual int post(Url &url, const char *mime, Stream &data, int len=-1)
http post
Definition: HttpRequest.h:87
virtual int get(Url &url, const char *acceptMime=nullptr, const char *data=nullptr, int len=-1)
http get
Definition: HttpRequest.h:112
virtual int processEnd()
Ends the http request processing and returns the status code.
Definition: HttpRequest.h:306
virtual HttpRequestHeader & header()
provides access to the request header
Definition: HttpRequest.h:161
virtual int put(Url &url, const char *mime, const char *data, int len=-1)
http put
Definition: HttpRequest.h:93
virtual void processWrite(Stream &stream)
Writes (Posts) the data of the indicated stream after calling processBegin.
Definition: HttpRequest.h:271
virtual void setAgent(const char *agent)
Defines the agent.
Definition: HttpRequest.h:164
void end() override
same as end()
Definition: HttpRequest.h:62
size_t write(const uint8_t *data, size_t len) override
Definition: HttpRequest.h:289
virtual bool processBegin(MethodID action, Url &url, const char *mime, int lenData=-1)
starts http request processing
Definition: HttpRequest.h:225
virtual int put(Url &url, const char *mime, Stream &data, int len=-1)
http put
Definition: HttpRequest.h:99
virtual int head(Url &url, const char *acceptMime=nullptr, const char *data=nullptr, int len=-1)
http head
Definition: HttpRequest.h:120
virtual int post(Url &url, const char *mime, const char *data, int len=-1)
http post
Definition: HttpRequest.h:81
void setOnConnectCallback(void(*callback)(HttpRequest &request, Url &url, HttpRequestHeader &request_header))
Callback which allows you to add additional paramters dynamically.
Definition: HttpRequest.h:330
bool isReady()
Definition: HttpRequest.h:189
void addRequestHeader(const char *header, const char *value)
Adds/Updates a request header.
Definition: HttpRequest.h:192
virtual int del(Url &url, const char *mime=nullptr, const char *data=nullptr, int len=-1)
http del
Definition: HttpRequest.h:105
Definition: NoArduino.h:125
Represents the content of a URL as Stream. We use the WiFi.h API.
Definition: URLStream.h:26
URL parser which breaks a full url string up into its individual parts.
Definition: Url.h:22
void stop()
Public generic methods.
Definition: AudioRuntime.h:27
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition: AudioConfig.h:868