arduino-audio-tools
URLStream.h
1 #pragma once
2 
3 #include "AudioConfig.h"
4 #ifdef USE_URL_ARDUINO
5 
6 #if defined(ESP32)
7 #include <Client.h>
8 #include <WiFi.h>
9 #include <WiFiClientSecure.h>
10 #include <esp_wifi.h>
11 #endif
12 
13 #include "AbstractURLStream.h"
14 #include "HttpRequest.h"
15 #include "AudioTools/CoreAudio/AudioBasic/Str.h"
16 
17 namespace audio_tools {
18 
26 class URLStream : public AbstractURLStream {
27  public:
28  URLStream(int readBufferSize = DEFAULT_BUFFER_SIZE) {
29  TRACED();
30  setReadBufferSize(readBufferSize);
31  }
32 
33  URLStream(Client& clientPar, int readBufferSize = DEFAULT_BUFFER_SIZE) {
34  TRACED();
35  setReadBufferSize(readBufferSize);
36  setClient(clientPar);
37  }
38 
39  URLStream(const char* network, const char* password,
40  int readBufferSize = DEFAULT_BUFFER_SIZE) {
41  TRACED();
42  setReadBufferSize(readBufferSize);
43  setSSID(network);
44  setPassword(password);
45  }
46 
47  URLStream(const URLStream&) = delete;
48 
49  ~URLStream() {
50  TRACED();
51  end();
52 #ifdef USE_WIFI_CLIENT_SECURE
53  if (clientSecure != nullptr) {
54  delete clientSecure;
55  clientSecure = nullptr;
56  }
57 #endif
58 #ifdef USE_WIFI
59  if (clientInsecure != nullptr) {
60  delete clientInsecure;
61  clientInsecure = nullptr;
62  }
63 #endif
64  }
65 
67  void setClient(Client& clientPar) override { client = &clientPar; }
68 
70  void setSSID(const char* ssid) override { this->network = ssid; }
71 
73  void setPassword(const char* password) override { this->password = password; }
74 
75  void setReadBufferSize(int readBufferSize) {
76  read_buffer_size = readBufferSize;
77  }
78 
80  virtual bool begin(const char* urlStr, const char* acceptMime = nullptr,
81  MethodID action = GET, const char* reqMime = "",
82  const char* reqData = "") override {
83  LOGI("%s: %s", LOG_METHOD, urlStr);
84  if (!preProcess(urlStr, acceptMime)) {
85  LOGE("preProcess failed");
86  return false;
87  }
88  int result = process<const char*>(action, url, reqMime, reqData);
89  if (result > 0) {
90  size = request.contentLength();
91  LOGI("size: %d", (int)size);
92  if (size >= 0 && wait_for_data) {
93  waitForData(clientTimeout);
94  }
95  }
96  total_read = 0;
97  active = result == 200;
98  LOGI("==> http status: %d", result);
99 #if USE_AUDIO_LOGGING
100  custom_log_level.reset();
101 #endif
102  return active;
103  }
104 
106  virtual bool begin(const char* urlStr, const char* acceptMime,
107  MethodID action, const char* reqMime, Stream& reqData,
108  int len = -1) {
109  LOGI("%s: %s", LOG_METHOD, urlStr);
110  if (!preProcess(urlStr, acceptMime)) {
111  LOGE("preProcess failed");
112  return false;
113  }
114  int result = process<Stream&>(action, url, reqMime, reqData, len);
115  if (result > 0) {
116  size = request.contentLength();
117  LOGI("size: %d", (int)size);
118  if (size >= 0 && wait_for_data) {
119  waitForData(clientTimeout);
120  }
121  }
122  total_read = 0;
123  active = result == 200;
124  LOGI("==> http status: %d", result);
125 #if USE_AUDIO_LOGGING
126  custom_log_level.reset();
127 #endif
128  return active;
129  }
130 
131  virtual void end() override {
132  if (active) request.stop();
133  active = false;
134  clear();
135  }
136 
137  virtual int available() override {
138  if (!active || !request) return 0;
139 
140  int result = request.available();
141  LOGD("available: %d", result);
142  return result;
143  }
144 
145  virtual size_t readBytes(uint8_t* data, size_t len) override {
146  if (!active || !request) return 0;
147 
148  int read = request.read((uint8_t*)&data[0], len);
149  if (read < 0) {
150  read = 0;
151  }
152  total_read += read;
153  LOGD("readBytes %d -> %d", (int)len, read);
154  return read;
155  }
156 
157  virtual int read() override {
158  if (!active) return -1;
159  // lazy allocation since this is rarely used
160  read_buffer.resize(read_buffer_size);
161 
162  fillBuffer();
163  total_read++;
164  return isEOS() ? -1 : read_buffer[read_pos++];
165  }
166 
167  virtual int peek() override {
168  if (!active) return -1;
169  // lazy allocation since this is rarely used
170  read_buffer.resize(read_buffer_size);
171 
172  fillBuffer();
173  return isEOS() ? -1 : read_buffer[read_pos];
174  }
175 
176  virtual void flush() override {}
177 
178  virtual size_t write(uint8_t) override { return not_supported(0); }
179 
180  virtual size_t write(const uint8_t*, size_t len) override {
181  return not_supported(0);
182  }
183 
185  virtual HttpRequest& httpRequest() override { return request; }
186 
187  operator bool() { return active && request.isReady(); }
188 
190  virtual void setTimeout(int ms) { clientTimeout = ms; }
191 
194  void setPowerSave(bool ps) { is_power_save = ps; }
195 
197  void setAutoCreateLines(bool flag) {
198  httpRequest().reply().setAutoCreateLines(flag);
199  }
200 
202  void setConnectionClose(bool close) {
203  httpRequest().setConnection(close ? CON_CLOSE : CON_KEEP_ALIVE);
204  }
205 
207  void clear() {
208  httpRequest().reply().clear();
209  httpRequest().header().clear();
210  read_buffer.resize(0);
211  read_pos = 0;
212  read_size = 0;
213  }
214 
216  void addRequestHeader(const char* header, const char* value) {
217  request.header().put(header, value);
218  }
219 
221  void setOnConnectCallback(void (*callback)(
222  HttpRequest& request, Url& url, HttpRequestHeader& request_header)) {
223  request.setOnConnectCallback(callback);
224  }
225 
226  void setWaitForData(bool flag) { wait_for_data = flag; }
227 
228  int contentLength() { return size; }
229 
230  size_t totalRead() { return total_read; }
231 
233  virtual bool waitForData(int timeout) {
234  TRACED();
235  uint32_t end = millis() + timeout;
236  if (request.available() == 0) {
237  LOGI("Request written ... waiting for reply");
238  while (request.available() == 0) {
239  if (millis() > end) break;
240  // stop waiting if we got an error
241  if (request.reply().statusCode() >= 300) {
242  LOGE("Error code recieved ... stop waiting for reply");
243  break;
244  }
245  delay(500);
246  }
247  }
248  LOGD("available: %d", request.available());
249  return request.available() > 0;
250  }
251 
252 #if USE_AUDIO_LOGGING
254  void setLogLevel(AudioLogger::LogLevel level) { custom_log_level.set(level); }
255 #endif
256  const char* urlStr() { return url_str.c_str(); }
257 
258  protected:
259  HttpRequest request;
260 #if USE_AUDIO_LOGGING
261  CustomLogLevel custom_log_level;
262 #endif
263  Str url_str;
264  Url url;
265  long size;
266  long total_read;
267  // buffered single byte read
268  Vector<uint8_t> read_buffer{0};
269  uint16_t read_buffer_size = DEFAULT_BUFFER_SIZE;
270  uint16_t read_pos;
271  uint16_t read_size;
272  bool active = false;
273  bool wait_for_data = true;
274  // optional
275  const char* network = nullptr;
276  const char* password = nullptr;
277  Client* client = nullptr; // client defined via setClient
278 #ifdef USE_WIFI
279  WiFiClient* clientInsecure = nullptr; // wifi client for http
280 #endif
281 #ifdef USE_WIFI_CLIENT_SECURE
282  WiFiClientSecure* clientSecure = nullptr; // wifi client for https
283 #endif
284  int clientTimeout = URL_CLIENT_TIMEOUT; // 60000;
285  unsigned long handshakeTimeout = URL_HANDSHAKE_TIMEOUT; // 120000
286  bool is_power_save = false;
287 
288  bool preProcess(const char* urlStr, const char* acceptMime) {
289  TRACED();
290 #if USE_AUDIO_LOGGING
291  custom_log_level.set();
292 #endif
293  url_str = urlStr;
294  url.setUrl(url_str.c_str());
295  int result = -1;
296 
297  // close it - if we have an active connection
298  if (active) end();
299 
300 #ifdef USE_WIFI
301  // optional: login if necessary if no external client is defined
302  if (client == nullptr){
303  if (!login()){
304  LOGE("Not connected");
305  return false;
306  }
307  }
308 
309 #endif
310  // request.reply().setAutoCreateLines(false);
311  if (acceptMime != nullptr) {
312  request.setAcceptMime(acceptMime);
313  }
314 
315  // setup client
316  Client& client = getClient(url.isSecure());
317  request.setClient(client);
318 
319  // set timeout
320  client.setTimeout(clientTimeout / 1000);
321  request.setTimeout(clientTimeout);
322 
323 #ifdef ESP32
324  // There is a bug in IDF 4!
325  if (clientSecure != nullptr) {
326  clientSecure->setHandshakeTimeout(handshakeTimeout);
327  }
328 
329  // Performance optimization for ESP32
330  if (!is_power_save) {
331  esp_wifi_set_ps(WIFI_PS_NONE);
332  }
333 #endif
334  return true;
335  }
336 
338  template <typename T>
339  int process(MethodID action, Url& url, const char* reqMime, T reqData,
340  int len = -1) {
341  TRACED();
342  // keep icy across redirect requests ?
343  const char* icy = request.header().get("Icy-MetaData");
344 
345  int status_code = request.process(action, url, reqMime, reqData, len);
346  // redirect
347  while (request.reply().isRedirectStatus()) {
348  const char* redirect_url = request.reply().get(LOCATION);
349  if (redirect_url != nullptr) {
350  LOGW("Redirected to: %s", redirect_url);
351  url.setUrl(redirect_url);
352  Client* p_client = &getClient(url.isSecure());
353  p_client->stop();
354  request.setClient(*p_client);
355  if (icy) {
356  request.header().put("Icy-MetaData", icy);
357  }
358  status_code = request.process(action, url, reqMime, reqData, len);
359  } else {
360  LOGE("Location is null");
361  break;
362  }
363  }
364  return status_code;
365  }
366 
368  Client& getClient(bool isSecure) {
369 #ifdef USE_WIFI_CLIENT_SECURE
370  if (isSecure) {
371  if (clientSecure == nullptr) {
372  clientSecure = new WiFiClientSecure();
373  clientSecure->setInsecure();
374  }
375  LOGI("WiFiClientSecure");
376  return *clientSecure;
377  }
378 #endif
379 #ifdef USE_WIFI
380  if (clientInsecure == nullptr) {
381  clientInsecure = new WiFiClient();
382  LOGI("WiFiClient");
383  }
384  return *clientInsecure;
385 #else
386  if (client == nullptr){
387  LOGE("Client not set");
388  stop();
389  }
390  return *client; // to avoid compiler warning
391 #endif
392  }
393 
394  inline void fillBuffer() {
395  if (isEOS()) {
396  // if we consumed all bytes we refill the buffer
397  read_size = readBytes(&read_buffer[0], read_buffer_size);
398  read_pos = 0;
399  }
400  }
401 
402  inline bool isEOS() { return read_pos >= read_size; }
403 
404  bool login() {
405 #ifdef USE_WIFI
406  if (network != nullptr && password != nullptr &&
407  WiFi.status() != WL_CONNECTED) {
408  TRACEI();
409  WiFi.begin(network, password);
410  while (WiFi.status() != WL_CONNECTED) {
411  Serial.print(".");
412  delay(500);
413  }
414  Serial.println();
415  delay(10);
416  return WiFi.status() == WL_CONNECTED;
417  }
418  return WiFi.status() == WL_CONNECTED;
419 #else
420  return false;
421 #endif
422  }
423 };
424 
425 } // namespace audio_tools
426 
427 #endif
Abstract Base class for all URLStream implementations.
Definition: AbstractURLStream.h:11
Definition: NoArduino.h:139
void setAutoCreateLines(bool is_auto_line)
automatically create new lines
Definition: HttpHeader.h:294
HttpHeader & clear()
clears the data
Definition: HttpHeader.h:94
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
virtual HttpRequestHeader & header()
provides access to the request header
Definition: HttpRequest.h:161
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
virtual const char * c_str()
provides the string value as const char*
Definition: StrView.h:379
Definition: NoArduino.h:125
Represents the content of a URL as Stream. We use the WiFi.h API.
Definition: URLStream.h:26
virtual HttpRequest & httpRequest() override
provides access to the HttpRequest
Definition: URLStream.h:185
virtual bool begin(const char *urlStr, const char *acceptMime=nullptr, MethodID action=GET, const char *reqMime="", const char *reqData="") override
Execute http request: by default we use a GET request.
Definition: URLStream.h:80
virtual void setTimeout(int ms)
Defines the client timeout.
Definition: URLStream.h:190
void setPowerSave(bool ps)
Definition: URLStream.h:194
Client & getClient(bool isSecure)
Determines the client.
Definition: URLStream.h:368
int process(MethodID action, Url &url, const char *reqMime, T reqData, int len=-1)
Process the Http request and handle redirects.
Definition: URLStream.h:339
void setSSID(const char *ssid) override
Sets the ssid that will be used for logging in (when calling begin)
Definition: URLStream.h:70
virtual bool begin(const char *urlStr, const char *acceptMime, MethodID action, const char *reqMime, Stream &reqData, int len=-1)
Execute e.g. http POST request which submits the content as a stream.
Definition: URLStream.h:106
virtual bool waitForData(int timeout)
waits for some data - returns false if the request has failed
Definition: URLStream.h:233
void setClient(Client &clientPar) override
(Re-)defines the client
Definition: URLStream.h:67
void setOnConnectCallback(void(*callback)(HttpRequest &request, Url &url, HttpRequestHeader &request_header))
Callback which allows you to add additional paramters dynamically.
Definition: URLStream.h:221
void clear()
Releases the memory from the request and reply.
Definition: URLStream.h:207
void setAutoCreateLines(bool flag)
If set to true, new undefined reply parameters will be stored.
Definition: URLStream.h:197
void addRequestHeader(const char *header, const char *value)
Adds/Updates a request header.
Definition: URLStream.h:216
void setConnectionClose(bool close)
Sets if the connection should be close automatically.
Definition: URLStream.h:202
void setPassword(const char *password) override
Sets the password that will be used for logging in (when calling begin)
Definition: URLStream.h:73
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:823
uint32_t millis()
Returns the milliseconds since the start.
Definition: Time.h:12