arduino-audio-tools
HttpRequest.h
1 #pragma once
2 #include "AudioConfig.h"
3 #ifdef USE_URL_ARDUINO
4 
5 #include "AudioHttp/HttpChunkReader.h"
6 #include "AudioHttp/HttpHeader.h"
7 #include "AudioHttp/HttpTypes.h"
8 #include "AudioHttp/Url.h"
9 #include "AudioTools/AudioLogger.h"
10 
11 #define CHUNK_SIZE 1024
12 
13 namespace audio_tools {
14 
26 class HttpRequest {
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() {
55  if (reply_header.isChunked()) {
56  return chunk_reader.available();
57  }
58  return client_ptr != nullptr ? client_ptr->available() : 0;
59  }
60 
61  virtual void stop() {
62  if (connected()) {
63  LOGI("stop");
64  client_ptr->stop();
65  }
66  }
67 
69  virtual int post(Url &url, const char *mime, const char *data, int len = -1) {
70  LOGI("post %s", url.url());
71  return process(POST, url, mime, data, len);
72  }
73 
75  virtual int post(Url &url, const char *mime, Stream &data, int len = -1) {
76  LOGI("post %s", url.url());
77  return process(POST, url, mime, data, len);
78  }
79 
81  virtual int put(Url &url, const char *mime, const char *data, int len = -1) {
82  LOGI("put %s", url.url());
83  return process(PUT, url, mime, data, len);
84  }
85 
87  virtual int put(Url &url, const char *mime, Stream &data, int len = -1) {
88  LOGI("put %s", url.url());
89  return process(PUT, url, mime, data, len);
90  }
91 
93  virtual int del(Url &url, const char *mime = nullptr,
94  const char *data = nullptr, int len = -1) {
95  LOGI("del %s", url.url());
96  return process(DELETE, url, mime, data, len);
97  }
98 
100  virtual int get(Url &url, const char *acceptMime = nullptr,
101  const char *data = nullptr, int len = -1) {
102  LOGI("get %s", url.url());
103  this->accept = acceptMime;
104  return process(GET, url, nullptr, data, len);
105  }
106 
108  virtual int head(Url &url, const char *acceptMime = nullptr,
109  const char *data = nullptr, int len = -1) {
110  LOGI("head %s", url.url());
111  this->accept = acceptMime;
112  return process(HEAD, url, nullptr, data, len);
113  }
114 
115  // reads the reply data
116  virtual int read(uint8_t *str, int len) {
117  TRACED();
118  if (reply_header.isChunked()) {
119  return chunk_reader.read(*client_ptr, str, len);
120  } else {
121  return client_ptr->read(str, len);
122  }
123  }
124 
125  size_t readBytesUntil(char terminator, char *buffer, size_t length) {
126  return client_ptr->readBytesUntil(terminator, buffer, length);
127  }
128 
129  // read the reply data up to the next new line. For Chunked data we provide
130  // the full chunk!
131  virtual int readln(uint8_t *str, int len, bool incl_nl = true) {
132  if (reply_header.isChunked()) {
133  return chunk_reader.readln(*client_ptr, str, len);
134  } else {
135  return chunk_reader.readlnInternal(*client_ptr, str, len, incl_nl);
136  }
137  }
138 
139  // provides the head information of the reply
140  virtual HttpReplyHeader &reply() { return reply_header; }
141 
143  virtual HttpRequestHeader &header() { return request_header; }
144 
146  virtual void setAgent(const char *agent) { this->agent = agent; }
147 
148  virtual void setConnection(const char *connection) {
149  this->connection = connection;
150  }
151 
152  virtual void setAcceptsEncoding(const char *enc) {
153  this->accept_encoding = enc;
154  }
155 
156  virtual void setAcceptMime(const char *mime) { this->accept = mime; }
157 
158  size_t contentLength() {
159  const char *len_str = reply().get(CONTENT_LENGTH);
160  int len = 0;
161  if (len_str != nullptr) {
162  len = atoi(len_str);
163  } else {
164  LOGI("no CONTENT_LENGTH found in reply");
165  }
166  return len;
167  }
168 
171  bool isReady() { return is_ready; }
172 
174  void addRequestHeader(const char *header, const char *value) {
175  request_header.put(header, value);
176  }
177 
178  Client &client() { return *client_ptr; }
179 
180  // process http request and reads the reply_header from the server
181  virtual int process(MethodID action, Url &url, const char *mime,
182  const char *data, int lenData = -1) {
183  int len = lenData;
184  if (data != nullptr && len <= 0) {
185  len = strlen(data);
186  }
187  processBegin(action, url, mime, len);
188  // posting data parameter
189  if (len > 0 && data != nullptr) {
190  LOGI("Writing data: %d bytes", len);
191  client_ptr->write((const uint8_t *)data, len);
192  LOGD("%s", data);
193  }
194  return processEnd();
195  }
196 
197  // process http request and reads the reply_header from the server
198  virtual int process(MethodID action, Url &url, const char *mime,
199  Stream &stream, int len = -1) {
200  if (!processBegin(action, url, mime, len))
201  return -1;
202  processWrite(stream);
203  return processEnd();
204  }
205 
207  virtual bool processBegin(MethodID action, Url &url, const char *mime,
208  int lenData = -1) {
209  TRACED();
210  int len = lenData;
211  is_ready = false;
212  if (client_ptr == nullptr) {
213  LOGE("The client has not been defined");
214  return false;
215  }
216  if (http_connect_callback) {
217  http_connect_callback(*this, url, request_header);
218  }
219  if (!this->connected()) {
220  LOGI("process connecting to host %s port %d", url.host(), url.port());
221  int is_connected = connect(url.host(), url.port(), clientTimeout);
222  if (!is_connected) {
223  LOGE("Connect failed");
224  return false;
225  }
226  } else {
227  LOGI("process is already connected");
228  }
229 
230 #if defined(ESP32) && defined(ARDUINO)
231  LOGI("Free heap: %u", (unsigned)ESP.getFreeHeap());
232 #endif
233 
234  reply_header.setProcessed();
235 
236  host_name = url.host();
237  request_header.setValues(action, url.path());
238  if (lenData > 0) {
239  request_header.put(CONTENT_LENGTH, lenData);
240  }
241  request_header.put(HOST_C, host_name);
242  request_header.put(CONNECTION, connection);
243  request_header.put(USER_AGENT, agent);
244  request_header.put(ACCEPT_ENCODING, accept_encoding);
245  request_header.put(ACCEPT, accept);
246  request_header.put(CONTENT_TYPE, mime);
247  request_header.write(*client_ptr);
248 
249  return true;
250  }
251 
253  virtual void processWrite(Stream &stream) {
254  uint8_t buffer[CHUNK_SIZE];
255  int total = 0;
256  int total_written = 0;
257  while (*client_ptr && stream.available() > 0) {
258  int result_len = stream.readBytes(buffer, CHUNK_SIZE);
259  total += result_len;
260  int written = write(buffer, result_len);
261  total_written += written;
262  LOGI("--> Bytes read %d vs written %d", result_len, written);
263  delay(1);
264  }
265  client_ptr->flush();
266  LOGI("Total bytes read %d vs written %d", total, total_written);
267  }
268 
271  virtual size_t write(uint8_t *data, size_t len) {
272  size_t result = 0;
273  if (isChunked()) {
274  client_ptr->println(len);
275  if (len > 0) {
276  result = client_ptr->write(data, len);
277  }
278  client_ptr->println();
279  } else {
280  result = client_ptr->write(data, len);
281  }
282  return result;
283  }
284 
286  virtual int processEnd() {
287  // if sending is chunked we terminate with an empty chunk
288  if (isChunked()) {
289  write(nullptr, 0);
290  }
291  LOGI("Request written ... waiting for reply")
292  // Commented out because this breaks the RP2040 W
293  // client_ptr->flush();
294  reply_header.read(*client_ptr);
295 
296  // if we use chunked tranfer we need to read the first chunked length
297  if (reply_header.isChunked()) {
298  chunk_reader.open(*client_ptr);
299  };
300 
301  // wait for data
302  is_ready = true;
303  return reply_header.statusCode();
304  }
305 
307  void setOnConnectCallback(void (*callback)(
308  HttpRequest &request, Url &url, HttpRequestHeader &request_header)) {
309  http_connect_callback = callback;
310  }
311 
313  void setTimeout(int timeoutMs) { clientTimeout = timeoutMs; }
314 
316  bool isChunked() { return request_header.isChunked(); }
317 
318  protected:
319  Client *client_ptr = nullptr;
320  Url url;
321  HttpRequestHeader request_header;
322  HttpReplyHeader reply_header;
323  HttpChunkReader chunk_reader = HttpChunkReader(reply_header);
324  const char *agent = nullptr;
325  const char *host_name = nullptr;
326  const char *connection = CON_KEEP_ALIVE;
327  const char *accept = ACCEPT_ALL;
328  const char *accept_encoding = IDENTITY;
329  bool is_ready = false;
330  int32_t clientTimeout = URL_CLIENT_TIMEOUT; // 60000;
331  void (*http_connect_callback)(HttpRequest &request, Url &url,
332  HttpRequestHeader &request_header) = nullptr;
333 
334  // opens a connection to the indicated host
335  virtual int connect(const char *ip, uint16_t port, int32_t timeout) {
336  client_ptr->setTimeout(timeout / 1000); // client timeout is in seconds!
337  request_header.setTimeout(timeout);
338  reply_header.setTimeout(timeout);
339  int is_connected = this->client_ptr->connect(ip, port);
340  LOGI("is connected %s with timeout %d", is_connected ? "true" : "false", (int)timeout);
341  return is_connected;
342  }
343 };
344 
345 } // namespace audio_tools
346 
347 #endif
Definition: NoArduino.h:138
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:313
bool isChunked()
we are sending the data chunked
Definition: HttpRequest.h:316
virtual int post(Url &url, const char *mime, Stream &data, int len=-1)
http post
Definition: HttpRequest.h:75
virtual int get(Url &url, const char *acceptMime=nullptr, const char *data=nullptr, int len=-1)
http get
Definition: HttpRequest.h:100
virtual int processEnd()
Ends the http request processing and returns the status code.
Definition: HttpRequest.h:286
virtual HttpRequestHeader & header()
provides access to the request header
Definition: HttpRequest.h:143
virtual int put(Url &url, const char *mime, const char *data, int len=-1)
http put
Definition: HttpRequest.h:81
virtual void processWrite(Stream &stream)
Writes (Posts) the data of the indicated stream after calling processBegin.
Definition: HttpRequest.h:253
virtual void setAgent(const char *agent)
Defines the agent.
Definition: HttpRequest.h:146
virtual bool processBegin(MethodID action, Url &url, const char *mime, int lenData=-1)
starts http request processing
Definition: HttpRequest.h:207
virtual int put(Url &url, const char *mime, Stream &data, int len=-1)
http put
Definition: HttpRequest.h:87
virtual int head(Url &url, const char *acceptMime=nullptr, const char *data=nullptr, int len=-1)
http head
Definition: HttpRequest.h:108
virtual int post(Url &url, const char *mime, const char *data, int len=-1)
http post
Definition: HttpRequest.h:69
void setOnConnectCallback(void(*callback)(HttpRequest &request, Url &url, HttpRequestHeader &request_header))
Callback which allows you to add additional paramters dynamically.
Definition: HttpRequest.h:307
bool isReady()
Definition: HttpRequest.h:171
void addRequestHeader(const char *header, const char *value)
Adds/Updates a request header.
Definition: HttpRequest.h:174
virtual size_t write(uint8_t *data, size_t len)
Definition: HttpRequest.h:271
virtual int del(Url &url, const char *mime=nullptr, const char *data=nullptr, int len=-1)
http del
Definition: HttpRequest.h:93
Definition: NoArduino.h:125
Represents the content of a URL as Stream. We use the WiFi.h API.
Definition: URLStream.h:25
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:12
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition: AnalogAudio.h:10
void delay(uint32_t ms)
Waits for the indicated milliseconds.
Definition: Millis.h:11