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 #endif
309 
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 #if defined(ESP32) && defined(USE_WIFI_CLIENT_SECURE)
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 
335  return true;
336  }
337 
339  template <typename T>
340  int process(MethodID action, Url& url, const char* reqMime, T reqData,
341  int len = -1) {
342  TRACED();
343  // keep icy across redirect requests ?
344  const char* icy = request.header().get("Icy-MetaData");
345 
346  int status_code = request.process(action, url, reqMime, reqData, len);
347  // redirect
348  while (request.reply().isRedirectStatus()) {
349  const char* redirect_url = request.reply().get(LOCATION);
350  if (redirect_url != nullptr) {
351  LOGW("Redirected to: %s", redirect_url);
352  url.setUrl(redirect_url);
353  Client* p_client = &getClient(url.isSecure());
354  p_client->stop();
355  request.setClient(*p_client);
356  if (icy) {
357  request.header().put("Icy-MetaData", icy);
358  }
359  status_code = request.process(action, url, reqMime, reqData, len);
360  } else {
361  LOGE("Location is null");
362  break;
363  }
364  }
365  return status_code;
366  }
367 
369  Client& getClient(bool isSecure) {
370 #ifdef USE_WIFI_CLIENT_SECURE
371  if (isSecure) {
372  if (clientSecure == nullptr) {
373  clientSecure = new WiFiClientSecure();
374  clientSecure->setInsecure();
375  }
376  LOGI("WiFiClientSecure");
377  return *clientSecure;
378  }
379 #endif
380 #ifdef USE_WIFI
381  if (clientInsecure == nullptr) {
382  clientInsecure = new WiFiClient();
383  LOGI("WiFiClient");
384  }
385  return *clientInsecure;
386 #else
387  if (client == nullptr){
388  LOGE("Client not set");
389  stop();
390  }
391  return *client; // to avoid compiler warning
392 #endif
393  }
394 
395  inline void fillBuffer() {
396  if (isEOS()) {
397  // if we consumed all bytes we refill the buffer
398  read_size = readBytes(&read_buffer[0], read_buffer_size);
399  read_pos = 0;
400  }
401  }
402 
403  inline bool isEOS() { return read_pos >= read_size; }
404 
405  bool login() {
406 #ifdef USE_WIFI
407  if (network != nullptr && password != nullptr &&
408  WiFi.status() != WL_CONNECTED) {
409  TRACEI();
410  WiFi.begin(network, password);
411  while (WiFi.status() != WL_CONNECTED) {
412  Serial.print(".");
413  delay(500);
414  }
415  Serial.println();
416  delay(10);
417  return WiFi.status() == WL_CONNECTED;
418  }
419  return WiFi.status() == WL_CONNECTED;
420 #else
421  return false;
422 #endif
423  }
424 };
425 
426 } // namespace audio_tools
427 
428 #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:369
int process(MethodID action, Url &url, const char *reqMime, T reqData, int len=-1)
Process the Http request and handle redirects.
Definition: URLStream.h:340
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:28
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition: AudioConfig.h:872
uint32_t millis()
Returns the milliseconds since the start.
Definition: Time.h:12